第5章 Java中类、对象、接口 及包的概念 5.1 类的基本概念 5.2 类的继承概念 5.3 抽象类和接口 5.4 包
5.1 类的基本概念 5.1.1 类的定义 5.1.2 类的对象 5.1.3 构造方法 5.1.4 类成员的修饰符
5.1 类的基本概念 传统的过程式语言,如C,由于它的设计方式与客观世界之间存在差距,使得程序的编写首先必须定义所要实现的功能,然后确定需采取的步骤,即所谓的“逐步求精”的结构程序设计方法。实际开发中,当程序大到一定程度的时候,其调试和维护就变得很困难,使用过程式语言就会感到力不从心了。
前面我们说过, Java是一种纯面向对象的编程语言,而面向对象程序设计是近些年来才发展起来的程序设计方法,其基本思想是将现实世界中的事物抽象为对象,抽象出来的对象被赋给相应的状态和行为,通过对消息的响应完成一定的任务。在现实生活中,任何事物都可以被认为是对象(Object),如: ① 电梯 ② 街头的自动售货机 ③ 街上跑的汽车 ④ 凳子 ⑤ 人 ……
上面列举的对象都有两个共性: (1) 具有一定的状态和特性。比如汽车有轮胎、发动机、方向盘等。 (2) 每个对象对应一组特定的操作。比如汽车需保养、加油、清洗等。 面向对象程序设计方法就是把现实世界中对象的状态和操作抽象为程序设计语言中的对象,达到二者的统一。同一种对象的所有共性进行抽象,又得到了类的概念。
所以,面向对象程序设计中的对象是由描述状态的变量和对这些变量进行维护和操作的一系列方法组成的事务处理单位,而类相当于创建对象实例的模板,通过对其实例化得到同一类的不同实例。本章我们将讨论类的特性、成员变量,方法、对象的建立及初始化、对象的继承及接口与包等内容。
类是对一个或几个相似对象的描述,它把不同对象具有的共性抽象出来,定义某类对象共有的变量和方法,从而使程序员实现代码的复用,所以说,类是同一类对象的原型。创建一个类,相当于构造一个新的数据类型,而实例化一个类就得到一个对象。Java为我们提供了大量的类库,如果从已知类库入手来构造自己的程序,不仅能有效地简化程序设计,而且能很好地学习面向对象程序设计方法。 一个类的实现包含两部分内容:声明和实体。类的各部分组成如图5.1所示。
图 5.1
类声明包括关键字class、类名及类的属性。类名必须是合法的标识符,类的属性为一些可选的关键字。其声明格式如下: 5.1.1 类的定义 1. 声明 类声明包括关键字class、类名及类的属性。类名必须是合法的标识符,类的属性为一些可选的关键字。其声明格式如下: [public|private|friendly|protected] [abstract][final] class className [extends superclassName][implements interfaceNameList] {...}
其中,第一项属于访问控制符,它不仅针对于类,类的变量、方法的访问也有该项的限制,我们后面会做专门的介绍。其他的修饰符说明如下: ● abstract:声明该类不能被实例化。 ● final:声明该类不能被继承,即没有子类。 ● className:类名。
● extends superclassName:extends 语句扩展superclassName为该类的父类。 ● implements interfaceNameList:声明类可实现一个或多个接口。 如图5.1中的 public class stack {...} 即为类的声明。
2 类的实体 类体是类的主要部分,包括变量的说明及该类所支持的方法,我们习惯称之为成员变量和成员方法。类体说明的格式为 class className { variable Declaration //成员变量 method Declaration //成员方法 } 请看下例:
[例5-1] 定义一个描述矩形的类,它包括长和宽两个数据成员以及设置长和宽的方法、计算矩形面积的方法。 class Rectangle{ double length, width; void setdim(double w, double l){ width=w; length=l; } double area( ){ return length*width ;
5.1.2 类的对象 定义了类后,就可以创建该类的变量,创建该类的变量称为类的实例化,类的变量也称为类对象 1 对象的创建 5.1.2 类的对象 定义了类后,就可以创建该类的变量,创建该类的变量称为类的实例化,类的变量也称为类对象 1 对象的创建 Java中创建新的对象必须使用new语句,其一般格式为: className objectName = new className( parameter List); 此表达式隐含了三个部分,即:对象说明、实例化和初始化。
如: Rectangle rect1=new Rectangle( ) ; ● 对象说明:上面的声明中, Rectangle rect1是对象的说明;用来说明对象rect1所属的类是Rectangle。 ● 实例化:new是实例化对象的运算符,它可以创建新的对象并且为对象分配内存空间。 ● 初始化:new运算符后紧跟着一个构造方法的调用。为对象进行初始化。一旦初始化,所有的实例变量也将被初始化,即算术类型初始化为0,布尔逻辑型初始化为false,复合类型初始化为null。
2. 对象的使用 类是通过实例化为对象来使用的,而对象的使用是通过引用对象变量或调用对象的方法来实现的。对象变量和方法均是通过运算符“.”来实现的。 1).变量的引用 对象变量引用的一般格式为: objectName.variableName 例如: class example { int x; } example a = new example(); a.x=100;
2).对象方法的引用 与对象变量引用一样,对象方法的引用一般格式为: objectName.methodName([argument List]); 例如: class example { int x; int add(int a){ return a+1; } } example a = new example(); a.x=100; System.out.println( a.add(10)); System.out.println(new example().add(10)); 再看下例:
[例5-2]:已知矩形的长和宽,利用 [例5-1] Rectangle类,计算矩形面积。 class eg5_5{ public static void main(String args[]){ Rectangle r1=new Rectangle(); double ar ; r1. setdim(10,20); ar= r1.area() ; System.out.println(“矩形面积是:”+ ar); } } 完成例5-2的另一种方法如例5-3:
public static void main(String args[]){ Rectangle r1=new Rectangle(); [例5-3]: class Rectangle{ …… } class eg5_5{ public static void main(String args[]){ Rectangle r1=new Rectangle(); r1.height=20; r1.width=10; System.out.println(“矩形面积是:” +r1.area()); 虽然通过直接引用对象变量可以改变对象的属性,但是它没有任何意义(比如,我们在例5-3中,设置其高height、宽width是负的,程序并不认为其非法)。
所以,较好的做法是:不直接对变量进行操作,而由类提供一些方法,对变量的引用可以通过这些方法来进行,以确保给定变量的数值是有意义的。这样一来,Rectangle类将提供setWidth、setHeight、getWidth以及getHeight方法来设置或者获得宽度和高度。设置变量的方法将在调用者试图将width和height设置为负数的时候给出一个错误。这样能够更好地体现数据的封装和隐蔽。例如,加上上述的几个方法后,例5-3的Rectangle类变为:
public class Rectangle { double length, width; public void setWidth(int w) { if (w < 0) System.out.println("Illegal number! "); else width=w; } public void setLength(int l) { ... } public int getWidth() { return width;
public int getlength(){ return length; } public double area() { return width * length;
5.1.3 构造方法 构造方法是一个特殊的方法。Java 中的每个类都有构造方法,用来初始化该类新创建的对象。构造方法的特点: (1) 构造方法具有和类名相同的名称。 (2) 构造方法不返回任何数据类型。 (3) 构造方法只能由new运算符调用。 (4) 每个类可以包含零个或者多个构造方法,不同的构造方法根据参数的不同来决定要初始化的新对象的状态(方法重载) 。
构造方法的实体跟一般方法的实体是相似的,均包含局部变量声明、循环以及其他的语句。 如果类不提供构造方法,系统会自动提供缺省的构造方法,它为类对象的成员变量赋数据类型的默认值。 【例5-4】用构造方法来初始化长方形的长和宽。
class Rects{ double length, width; Rects(double w, double l) { width=w; length=l; } double area( ) { return length*width ; class eg5_6{ public static void main(String args[]){ Rects r1=new Rects(10,20); System.out.println(“矩形面积是:”+r1.area()); }
若局部变量名和成员变量名同名,可用关键字this表示当前对象。如: Rects(double width, double length){ this. width= width ; this.length=length; }
构造方法重载示例。 【例5-5】用不同的构造方法,分别初始化长方形和正方形的边长。 class Rects{ double length, width; Rects(double w, double l) { width=w; length=l; } Rects(double l) { width=l; length=l; double area( ) { return length*width ;
class eg5_7{ public static void main(String args[]){ Rects r1=new Rects(10,20); Rects r2=new Rects(5); double ar; ar=r1.area( ) ; System.out.println(“矩形面积:”+ar); ar=r2.area( ) ; System.out.println(“正方形面积:”+ar); }
5.1.4 类成员的修饰符 1.成员变量 Java中变量的说明可以分为两种:类成员变量的说明和方法变量的说明。其变量声明格式为 [public|protected|private] [static][final] [transient][volatile] type variableName 上述声明格式中,第一项指的是访问控制格式(我们后面会有介绍),另外的几项说明如下:
static: 成员控制修饰符,说明该类型的变量为静态变量,或者称之为类变量。说明静态变量类型后则该类的所有实例对象都可以对其共享。 final:常量声明修饰符,用该符号声明后,在程序的运行过程中不能再改变它的值。比如: final int INDEX = 1000; static final int LOOP=10;
volatile:异步控制修饰符,表示多个并发线程共享的变量,这使得各线程对该变量的访问保持一致。 transient:存储控制临时变量修饰符,因为在缺省的情况下,类中所有变量都是对象永久状态的一部分,将对象存档时,必须同时保存这些变量。用该限定词修饰的变量指示Java虚拟机:该变量并不属于对象的永久状态。它主要用于实现不同对象的存档功能。
总之,从变量定义的不同位置及所使用的限定词不同来看,变量可以分为三类:实例变量、局部变量和静态变量。 如果在类的方法代码段之外声明且没有限定词static,则为实例变量。从它的定义我们可以看出,实例变量与类紧密相关,如果一个类有多个实例对象,那么每个实例对象都有自己的实例变量拷贝,之间并不影响。
如果在类的方法本体之中声明,则为局部变量。 如果将一个实例变量声明为static,则为静态变量,或称之为类变量。静态变量在类声明后就可以直接引用,但实例变量则不能,必须在实例化对象后才可以使用。 下面我们对实例变量与类变量加以详细地说明,以加深理解。比如我们可以如下来声明一个成员变量:
class MyClass { float variable1; static int variable2 } 该例中声明了一个实例变量variable1和一个类变量variable2。今后当我们创建类的实例的时候,系统就会为该实例创建一个类实例的副本,但系统为每个类分配类变量仅仅只有一次,而不管类创建的实例有多少。
在程序中可通过一个实例或者类本身来访问类变量。例如: … MyClass A=new MyClass(); MyClass B=new MyClass(); A.variable1=100; A.variable2=200; B.variable1=300; B.variable2=400;
System.out.println("A.variable1= "+A.variable1); System.out.println(“B.variable1= "+B.variable1); System.out.println(“B.variable2= "+B.variable2); ...
每实例化一个新MyClass对象的时候,就得到了一个和MyClass对象有联系的variable1的新副本。当一个成员变量用关键字static被指定为类变量后,其第一次调用的时候,系统就会为它创建一个副本,之后,类的所有实例均共享了该类变量的相同副本。所以上述程序段的输出结果为: A.variable1= 100 A.variable2= 400 B.variable1= 300 B.variable2= 400
2.成员方法 Java程序通过方法完成对类和对象属性的操作。方法定义了在类成员变量上的一系列操作,它只能在类的内部声明并加以实现,其他的对象通过调用对象的方法得到该对象的服务。
方法声明的一般格式如下: [public/protected/private][static][final][abstract][native][synchronized] returnType methodName([param List]) { methodBody }
● static: 说明该方法为静态方法( 也称作类方法),与之对应,其他的方法就为实例方法。类方法只能操作类变量而不能访问定义在类中的实例变量,这是实际使用过程中经常出错的地方。例如: class A { int x; static public int x() { return x; } class A { static int x; static public int x() { return x; }
● abstract: 说明一个方法是抽象方法。 ● final:final方法类似于常量的定义,它说明一个方法为终极方法,即它不能被子类重载。说明为final的方法往往与关键字private一起使用,避免出错。例如: ... private final meth_final() {...}
5.2 类的继承概念 Java通过子类实现继承。继承指的是某个对象所属的类具有上一层次对象的某些属性。在Java中,所有的类都是通过直接或间接地继承java.lang.Object类得到的,如下图所示。
在类的继承过程中,被继承的类为父类或超类,继承得到的类为子类。子类继承父类的状态和行为,也可以修改父类的状态或重写父类的行为(方法),同时也可以再添加新的状态和行为(方法)。Java不支持多重继承。
5.2.1 子类的创建 class subclassName extends superclass Name {...} 例如: 5.2.1 子类的创建 通过关键字extends来创建某个类的子类,其语法如下: class subclassName extends superclass Name {...} 例如: class Rectangle extends Shape {...} 这样,类Rectangle就可以继承父类Shape中的成员变量和方法。请看下例: [例5-5] 利用矩形类Rectangle,生成长方体类Cube,计算体积。
class Cube extends Rectangle{ double height; void seth(double h) { class Rectangle{ double length, width; void setdim(double w, double l) { width=w; length=l; } double area( ) { return length*width ; class Cube extends Rectangle{ double height; void seth(double h) { height=h ; } double vol( ) { return area()*height ;
class Inheritance{ public static void main(String args[]){ Cube c1=new Cube(); c1.setdim(10,5); c1.seth(20); System.out.println(“长方体体积=” +c1.vol()); }
前面一些例子中,在作类的定义时,并没有指明继承于某个父类,比如: public class Rectangle { ... } 此时,隐含认为类Rectangle缺省继承于类Object。当然,继承父类中的private属性的变量和方法也是受限制的,这是大家需要注意的地方。我们可以发现,通过继承的关系,使得成熟的代码可以获得重用的好处,大大提高了编程的效率。同时,由类封装而带来的数据隐藏,也可以提高程序的可维护性。
5.2.2 成员变量的隐藏和方法的重写 子类通过隐藏父类的成员变量和重写父类的方法,可以把父类的状态和行为改变为自身的状态和行为。 5.2.2 成员变量的隐藏和方法的重写 子类通过隐藏父类的成员变量和重写父类的方法,可以把父类的状态和行为改变为自身的状态和行为。 1. 变量的隐藏 继承给我们带来方便,但如果使用中概念不清,也可能会给我们带来一些问题。假设我们实现了某个类的继承,当子类中的成员变量与父类中的成员变量同名时,应该怎么办呢?
Java解决这一问题的办法是采用所谓的变量隐藏机制。也就是说,如果该种情况发生,则父类中的变量将被隐藏起来。例如: class superClass { int x ; ...} class subClass extends superClass{ int x ; // 此时,此处变量x隐藏了父类的同名变量 可能有的读者会说,那我非要用父类中的同名变量,应该怎么办呢?
当引用父类中的变量时,必须使用super关键字。如 class subClass extends superClass{ int x ; x=10 ; //引用本类中的变量 super.x=20 ; //引用父类中的变量 }
2. 方法置换 与我们前面所介绍的方法重载很相似,Java中的方法置换指的是子类中的方法名与父类中的某个方法名相同,此时,子类中的同名方法就被称为置换方法。置换方法与父类中的同名方法具有相同的方法名、相同的返回值类型和相同的参数表。如果要调用父类中的同名方法,也使用关键字super进行前缀修饰。例如:
class SuperClass{ int x; void setX( ){ x=0; } … } class SubClass extends SuperClass{ int x; //隐藏了父类的变量x void setX( ) { //重写了父类的方法 setX() x=5; } …. } 请看下例:
class MyClass1 { [例5-6] public static void main(String args[]){ int x,y; public MyClass1(int x, int y) { this.x=x; this.y=y; } public void test(){ System.out.println("superclass is: "+x+" "+y); } } class MyClass2 extends MyClass1 { public MyClass2(int x, int y) { this.x=x; this.y=y; public static void main(String args[]){ MyClass2 m2= new MyClass2(5,6); MyClass1 m1= new MyClass1(0,0); m1.test(); m2.test(); } public void test(){ super.test(); System.out.println("subclass is: "+ x+" "+y); 运行结果: superclass is: 0 0 subclass is: 5 6
5.2.3 关键字super 与关键字this相似(我们知道,this表示当前对象),关键字super表示当前对象的父对象, java中通过super来实现对父类成员的访问,super用来引用当前对象的父类。Super 的使用有三种情况: 1)访问父类被隐藏的成员变量,如: super.variable; 2)调用父类中被重写的方法,如: super.Method([paramlist]); 3)调用父类的构造函数,如: super([paramlist]);
使用中我们须注意的是,父类的构造方法必须是子类构造方法的第一条语句,因为对象必须首先执行高层次的初始化。请看下例:
【例5-7】 import java.io.*; class SuperClass{ int x; SuperClass( ) { x=3; System.out.println("in Super: x=" +x); } void doSome( ) { System.out.println(“In SuperClass"); } }
class SubClass extends SuperClass { int x; SubClass( ) { super( ); //调用父类的构造方法, //super( ) 要放在方法中的第一句 System.out.println("in Sub:x="+x); } void doSome( ) { super.doSome( ); //调用父类的方法 System.out.println(“In SubClass”); } }
public class Inheritance { public static void main(String args[]) { SubClass subC=new SubClass(); subC.doSome(); } }
5.3 抽象类和接口 5.3.1. 抽象类 java语言中,用abstract 关键字来修饰一个类时,这个类叫做抽象类,用abstract 关键字来修饰一个方法时,这个方法叫做抽象方法。格式如下: abstract class abstractClass{ …} //抽象类 abstract returnType abstractMethod([paramlist]) //抽象方法
例如: abstract class Shape{ // Shape为抽象类 abstract void draw(); // draw()为抽象方法 } 说明: 1.抽象方法只有方法说明而没有方法体。 2.抽象方法的实现须由该方法所在类的子类来实现。 3.抽象类必须被继承,抽象类不能被实例化,抽象类不一定要包含抽象方法。 4.若类中包含了抽象方法,则该类必须被定义为抽象类。 请看[例5-8] :
abstract class Shape{ // 定义抽象类Shape abstract double area(double a1, double a2); //定义抽象方法area() } class Rectangle extends Shape{// 实现抽象类的方法 double area(double a1, double a2) { return a1*a2 ; } class Triangle extends Shape{ // 实现抽象类的方法 return (a1*a2)*0.5 ; }
class eg5-16 { public static void main(String args[]) { System.out.println(" Rectangle area=" +(new Rectangle()).area(4,8)); System.out.println("Triangle area=" +(new Triangle ()).area(4,8 )); } }
注意事项: (1) 构造方法不能定义为抽象方法。 (2) 最终方法不能说明为抽象方法。 (3) static和private修饰符不能用于抽象方法。 (4) 不能重载父类中的抽象方法。
Java中的继承机制: Java只支持单一继承,即一个子类只有一个父类. 实际的开发过程中,可能会碰到需要多重继承的情况,这应该 怎么办?
5.3.2 接口 Java中的接口机制为我们提供了实现多重继承的可能。 5.3.2 接口 Java中的接口机制为我们提供了实现多重继承的可能。 接口是抽象类的一种,只包含常量和抽象方法的定义,而没有变量和方法的实现。 它提供方法协议的封装,但不限制子类如何实现这些方法,从而使得类的继承变得简单而灵活。
接口的定义 接口的定义包括:接口声明和接口体。 与类的声明格式相似,接口的声明格式如下: [Modifiers] interface interfaceName [extends superinterface_List] { interfaceBody } 接口隐含修饰符为abstract,当然,也可以显式指明为abstract,但是没有必要。
void Mymethod(int x, int y); ...} 例如: public interface Myinterface { String name; final int x=10; void Mymethod(int x, int y); ...}
一个接口可以通过关键字extends扩展另外的接口,这跟类可以扩展的概念一样。但是,类只能扩展一个另外的类,而接口可以扩展任意多个接口。我们知道,在类的扩展中,所有的类,其超类均为Object,而接口没有所谓的超接口。 所有定义在接口中的变量隐含为静态的(static) 、终极的(final)和公共的(public) 。所有定义在接口中的方法隐含为public和abstact。相关修饰符可以省略。
2.接口的实现 用户定义的类要使用某个接口时,必须首先实现这一接口。 可以通过关键字implements说明该类要实现的一个或多个接口,之后在类体中完成接口所有方法的代码。 [ 例5-9]: interface Irect { //定义接口Irect int w=3, l=4 ; void comp( ) ; }
class Crect implements Irect { public void comp( ){ //实现接口中的Runnable方法 System.out.println("Rectangle area=" +w*l ) ; } } public class eg5-17{ public static void main(String args[]) { (new Crect( )).comp( ) ; }
接口功能: 通过接口我们可以使处于不同层次、互不相干的类具有相同的行为。总的来说,接口的功能可以归纳为如下几点: (1) 通过接口可以实现不相干类的相同行为而不需考虑这些类之间的层次关系。 (2) 通过接口可以指明多个类需要实现的方法。 (3) 通过接口可以了解对象的交互界面而不需了解对象所对应的类。
5.4 包(package) 为了使得类更容易地被发现和使用,以及避免名字空间的冲突、控制访问,Java不支持全局变量和全局方法,程序员要捆绑相关的类和接口到包中,所有变量和方法都是在类中定义,而每个类又是包的一部分。
5.4.1 包的定义 包由package语句创建,其一般形式为: package packageName; 5.4.1 包的定义 包由package语句创建,其一般形式为: package packageName; 此语句必须放在java程序的第一行。 例如,可在[例5-7]程序的第一行田家语句: package javaprogrom ; 我们常常把自己设计的类或接口组成一个或多个包,使得功能相近、用途相同、关系密切的类和接口纳入一个包中。
5.4.2 包的引入 一般来说,java.lang包中定义了基本的类和接口,当编译的时候,Java编译器会自动将所需要的内容引入。 5.4.2 包的引入 一般来说,java.lang包中定义了基本的类和接口,当编译的时候,Java编译器会自动将所需要的内容引入。 如果要引入用户自定义的包,可以使用import语句引入。该语句的功能是引入包或包中的类,从而使得当前类可以用省略的形式来使用包中的类或接口。
有: ① import packageName; ② import packageName.class; ③ import package.*; 这三种格式大致相同,但存在一些细微的区别。第一种格式允许只用包名中的最后一部分来指定包名,例如: import java.lang.String;
String类的变量和方法(如果最后项所指为类),当最后项为包时,则可以直接使用对应包中的类及方法。 这样,今后就可以直接使用包中 String类的变量和方法(如果最后项所指为类),当最后项为包时,则可以直接使用对应包中的类及方法。 第二种格式与第一种相似,只是所指为确定的某一类。 第三种格式最常用,其功能为使包中的所有类都可以用其类名直接访问,例如: import java.lang.*; import 语句在程序中可以有任意多个,例如: import java.awt.*; import java.net.*;
5.4.3 Java中的常用包 Java有一些编程中常用的包、类和接口。例如字符串处理、网络开发、窗口调用、数学计算及异常处理等等。这里,为了更好的理解,我们给出Java中常用的包,它与Windows一样,集成为一个应用程序接口API(Application Programming Interface),如表5.2所示
表5.2 常用包列表 包 名 功 能 描 述 java.applet 使用Applet的类和接口 java.awt 表5.2 常用包列表 包 名 功 能 描 述 java.applet 使用Applet的类和接口 java.awt Java抽象窗口工具集 java.awt.image 图像处理类 java.awt.peer 使Java的窗口系统跨平台移植的类和接口 java.io 输入/输出类和接口 java.lang Java的核心类,包括字符串、数学计算、异常处理等 java.net 网络应用类和接口 java.util 优化程序设计的一些类,如矢量队列等 javax.swing 扩展的一些Java类和接口
5.4.4 类及类成员的访问控制 表5.1给出了每一种访问指示的访问等级。 5.4.4 类及类成员的访问控制 前面我们多次提到过对类、变量及方法的访问控制属性,比如有:private、friendly、protected及public。Java中最低访问控制范围是类的级别,与之对应也分为四种:同一个类、同一个包、不同包的子类及不同包的非子类。 表5.1给出了每一种访问指示的访问等级。
表5.1 访问控制权限表 访问指示 类 子类 包 所有 private √ protected public friendly
类的访问控制权限只能为 public和friendly 变量和方法的访问控制可以为上面四种的任何一种。 ● private private成员只能被它所定义的类或类的不同对象所访问。外部访问这个变量就会出错,private成员就像不能告诉任何人的秘密,所以,任何不需要他人直接访问的成员都应该定义为private类型。
● protected 定义为protected的类成员允许类本身、子类以及在相同包中的类访问它。
public是Java中最简单的访问控制符。 修饰为public的成员在任何类中、任何包中都可以访问,它相当于是无任何秘密可言,有点相当于C语言中的外部变量。
● friendly friendly关键字表示友元类,Java中如果不显式设置成员访问控制的时候(即缺省的访问控制),则隐含使用friendly访问控制。 该访问控制允许在相同包中的类成员之间相互可以访问。就像在相同包中的类是互相信任的朋友。