学习目标 .了解面向对象的思想,能够说出面向对象的三大特性。 .掌握类的定义,能够独立完成类的定义。 .掌握对象的创建和使用,能够独立完成对象的创建,并通过对象访问对象属性和 方法。 .掌握对象的引用传递,能够独立实现对象的引用传递。 .熟悉Java的4种访问控制权限,能够在类中灵活使用访问控制权限实现类成员的访 问控制。 .熟悉类的封装特性,能够说出为什么要封装以及如何实现封装。 .掌握构造方法的定义和重载,能够独立定义并重载构造方法。 .熟悉this关键字,能够使用this关键字调用成员属性、成员方法以及构造方法。 .了解代码块的应用,能够说出普通代码块和构造块的特点。 .熟悉static关键字的使用,能够说出静态属性、静态方法和静态代码块的特点。 前面学习的知识都属于Java的基本程序设计范畴,属于结构化程序开发。若使用结构 化方法开发软件,其稳定性、可修改性和可重用性都比较差。在软件开发过程中,用户的需 求随时都有可能发生变化,为了更好地适应用户需求的变化,Java采用了面向对象的程序 设计思想。在本章和第4章中,将为读者详细讲解Java面向对象的特性。 3.面向对象的思想 1 面向对象是一种符合人类思维习惯的编程思想。现实生活中存在各种形态的事物,这 些事物之间存在着各种各样的联系。在程序中使用对象映射现实中的事物,使用对象的关 系描述事物之间的联系,这种思想就是面向对象。 提到面向对象,自然会想到面向过程,面向过程就是通过分析得出解决问题所需的步 骤,然后用函数把这些步骤逐一实现,使用的时候依次调用函数就可以了。面向对象则把构 成问题的事物按照一定规则划分为多个独立的对象,然后通过调用对象的方法来解决问题。 当然,一个应用程序会包含多个对象,通过多个对象的相互配合实现应用程序的功能,这样 当应用程序功能发生变动时,只需要修改个别的对象就可以了,从而使代码维护起来更加方 便。面向对象的特性主要可以概括为封装性、继承性和多态性,接下来对这3种特性进行简 第3章面向对象(上) 83 单介绍。 1. 封装性 封装是面向对象的核心思想。它有两层含义:一层含义是把对象的属性和行为看成 一 个密不可分的整体,将这两者“组合”在一起(即封装在对象中); 另一层含义指信息隐藏, 将 不想让外界知道的信息隐藏起来,例如,学开车时只需要知道如何操作汽车,不必知道汽 车 内部是如何工作的 。 2. 继承性 继承性主要描述的是类与类之间的关系。通过继承,可以在原有类的基础上对功能 进 行扩展。例如,有一个汽车类,该类描述了汽车的普通特性和功能。进一步再产生轿车类 , 而轿车类中不仅应该包含汽车类的特性和功能,还应该增加轿车类特有的功能,这时,可 以 让轿车类继承汽车类,在轿车类中单独添加其独有的特性和方法就可以了。继承不仅增 强 了代码的复用性,提高了开发效率,还降低了程序产生错误的可能性,为程序的维护以及 扩 展提供了便利 。 3. 多态性 多态性是指在一个类中定义的属性和方法被其他类继承后,它们可以具有不同的数据 类型或表现出不同的行为,这使得同一个属性和方法在不同的类中具有不同的语义。例如, 汽车和飞机同样是交通工具,汽车在陆地上行驶,而飞机在天空中飞行,所以不同的对象表 现的行为是不一样的。多态的特性使程序更抽象、便捷,有助于开发人员设计程序时分组协 同开发。 面向对象的思想仅靠上面的介绍是无法真正理解的,只有通过大量的实践才能真正领 悟面向对象的思想。 3.类与对象 2 在面向对象技术中,为了做到让程序对事物的描述与事物在现实中的形态保持一致,提 出了两个概念,即类和对象。在Java程序中类和对象是最基本、最重要的单元。类表示某 类群体的一些基本特征抽象,对象表示一个个具体的 事物。 例如,在现实生活中,学生这个群体就可以表示为 一个类,而某个具体的学生就可以称为对象。一个具体 的学生有自己的姓名和年龄等信息,这些信息在面向对 象的概念中称为属性;学生可以看书和打篮球,看书和 打篮球这些行为在类中就称为方法。类与对象的关系 如图3-1所示。 在图3-1中,学生可以看作一个类,小明、李华、 大 军都是学生类的对象。类用于描述多个对象的共同 特 图3-1类与对象的关系 Java基础入门(第84 3版) 征,它是对象的模板。对象用于描述现实中的个体,它是类的实例。对象是根据类创建的, 一个类可以对应多个对象。 3.2.1 类的定义 在面向对象的思想中最核心的就是对象,创建对象的前提是定义一个类。类是Java中 一个重要的引用数据类型,也是组成Java程序的基本要素,所有的Java程序都是基于 类的。类 是对象的抽象,用于描述一组对象的共同特征和行为。类中可以定义成员变量和成 员方法。成员变量用于描述对象的特征,成员变量也被称作对象的属性;成员方法用于描述 对象的行为,可简称为方法。 类的定义格式如下: class 类名{ 成员变量; 成员方法; } 根据上述格式定义Student(学生)类,成员变量包括name(姓名)、age(年龄)、sex(性 别);成员方法包括读书read()。Student类定义的示例代码如下所示: class Student { String name; //声明String 类型的变量name int age; //声明int 类型的变量age String sex; //声明String 类型的变量sex //定义read()方法 void read() { System.out.println("大家好,我是" + name + ",我在看书!"); } } 以上代码中定义了Student类。其中,Student是类名,name、age、sex是成员变量,read() 是成员方法,在成员方法read()中可以直接访问成员变量name。 脚下留心:局部变量与成员变量的不同 在Java中,定义在类中的变量称为成员变量,定义在方法中的变量称为局部变量。如 果在某个方法中定义的局部变量与成员变量同名,这种情况是允许的,此时,在方法中通过 变量名访问的是局部变量,而非成员变量,请阅读下面的示例代码: class Student { int age = 30; //类中定义的变量称作成员变量 void read() { int age = 50; //方法内部定义的变量称作局部变量 System.out.println("大家好,我" + age + "岁了,我在看书!"); } } 第3章 面向对象(上) 85 上述代码中,在Student类的read()方法中有一条打印语句,打印了变量age,此时打 印的是局部变量age,也就是说,当有另一个程序调用read()方法时,输出的age值为50,而 不是30。 3.2.2 对象的创建与使用 3.2.1节定义了Student类。要想使用一个类,则必须创建该类的对象。在Java程序中 可以使用new关键字创建对象,使用new关键字创建对象的具体格式如下: 类名对象名= null; 对象名= new 类名(); 上述格式中,创建对象分为声明对象和实例化对象两步。也可以直接通过下面的方式 创建对象: 类名对象名= new 类名(); 例如,创建Student类的实例对象,示例代码如下: Student stu = new Student(); 上述代码中,newStudent()用于创建Student类的一个实例对象(称为Student对象), Studentstu声明了一个Student类的变量stu。运算符=将新创建的实例对象地址赋值给 变量stu,变量stu引用的对象简称为stu对象。 了解对象的创建之后,就可以使用类创建对象,示例代码如下: class Student { String name; // 声明name 属性 void read() { System.out.println("大家好,我是" + name + ",我在看书!"); } }p ublic class Test { public static void main(String[] args[]) { Student stu = new Student(); // 创建并实例化Student 对象 } } 上述代码在main()方法中实例化了Student对象,对象名为stu。使用new 关键字创 建的对象在堆内存中分配空间。stu对象的内存分配如图3-2所示。 由图3-2可知,对象名stu保存在栈内存中,而对象的属性信息则保存在对应的堆内 存中。创 建对象后,可以使用对象访问类中的某个属性或方法,对象属性和方法的访问通过点 (.)运算符实现,具体格式如下: 对象名.属性名 对象名.方法名 Java基础入门(第86 3版) 图3-2 stu对象的内存分配 下面通过一个案例学习对象属性和方法的访问,如文件3-1所示。 文件3-1 Example01.java 1 class Student { 2 String name; //声明name 属性 3 void read() { 4 System.out.println("大家好,我是" + name); 5 } 6 } 7 public class Example01 { 8 public static void main(String[] args) { 9 Student stu1 = new Student(); //创建第一个Student 对象 10 Student stu2 = new Student(); //创建第二个Student 对象 11 stu1.name = "小明"; //为stu1 对象的name 属性赋值 12 stu1.read(); //调用对象的方法 13 stu2.name = "李华"; 14 stu2.read(); 15 } 16 } 在文件3-1中,分别定义了Student类和Example01类。Student类中声明了name属 性和read()方法。Example01类的main()方法中创建了两个Student对象———stu1和 stu2。第11行代码为stu1对象的name属性赋值为小明;第12行代码通过stu1对象调用 read()方法;同理,第13、14行代码为stu2对象的name属性赋值为李华,并通过stu2对象 调用read()方法。 文件3-1的运行结果如图3-3所示。 图3-3 文件3-1的运行结果 第3章 面向对象(上) 87 从图3-3可以看出,stu1对象和stu2对象在调用read()方法时,打印的name值不相 同。这是因为stu1对象和stu2对象在系统内存中是两个完全独立的个体,它们分别拥有 各自的name属性,对stu1对象的name属性进行赋值并不会影响stu2对象的name属性 的值。为stu1对象和stu2对象中的name属性赋值后,stu1对象和stu2对象的内存变化 如图3-4所示。 图3-4 stu1对象和stu2对象的内存变化 由图3-4可知,程序分别实例化了两个Student对象stu1和stu2,它们分别指向各自的 堆内存空间。 3.2.3 对象的引用传递 类属于引用数据类型,引用数据类型的内存空间可以同时被多个栈内存引用。下面通 过一个案例详细讲解对象的引用传递,如文件3-2所示。 文件3-2 Example02.java 1 class Student { 2 String name; //声明name 属性 3 int age; //声明age 属性 4 void read() { 5 System.out.println("大家好,我是"+name+",年龄"+age); 6 } 7 } 8 class Example02 { 9 public static void main(String[] args) { 10 Student stu1 = new Student(); //创建stu1 对象并实例化 11 Student stu2 = null; //创建stu2 对象,但不对其进行实例化 12 stu2 = stu1; //stu1 给stu2 分配空间使用权 13 stu1.name = "小明"; //为stu1 对象的name 属性赋值 14 stu1.age = 20; 15 stu2.age = 50; 16 stu1.read(); //调用对象的方法 17 stu2.read(); 18 } 19 } 在文件3-2中,分别定义了Student类和Example02类。Student类中声明了name属 8 8 Java基础入门(第3版) 性、age属性和read() 方法。Example02 类的main() 方法中创建了两个Student对象——— stu1和stu2,程序中只对stu1对象进行了实例化,对stu2对象未进行实例化。第12 行代 码是stu1对象给stu2对象分配空间使用权;第13 、14 行代码是分别对stu1对象的name 属 性和age属性赋值;第15 行代码是对stu2对象的age属性赋值;第16 、17 行代码分别通过 stu1对象和stu2对象调用read() 方法。 文件3-2的运行结果如图3-5所示。 图3- 5 文件3-2的运行结果 由图35可知,1对象和s2对象输出的内容是一致的,这是因为s2对象获得了 -stututustu1对象的堆内存空间的使用权。在文件3-2中,第14 行代码对stu1对象的age属性赋值 之后,第15 行代码通过stu2对象对age属性值进行了修改。实际上所谓的引用传递,就是 将一个堆内存空间的使用权分配给多个栈内存空间使用,每个栈内存空间都可以修改堆内 存空间的内容。文件3-2中stu1对象和stu2对象引用传递的内存分配如图3-6所示。 图3- 6 文件3-2中对象引用传递的内存分配 在图3-6中,步骤一声明对象stu1和stu2,使用new创建Student对象stu1并为其赋 值,使用new创建对象时会开辟一个堆内存空间,对象stu1指向开辟的堆内存地址0x001; 步骤二通过对象stu1给对象stu2分配内存空间使用权,对象stu2指向堆内存地址0x001; 在步骤三,由于对象stu1指向堆内存地址0x001,所以对象stu1修改属性值就是修改堆内 存中对象的值,堆内存中name 的值修改为“小明”,age的值修改为20;步骤四与步骤三类 似,对象stu2也指向堆内存地址0x001,堆内存中age的值修改为50,最终结果对象stu1的 第3章 面向对象(上) 89 age属性值也是50。 小提示:一个栈内存空间只能指向一个堆内存空间。如果想要再指向其他堆内存空 间,就必须先断开已有的指向,才能分配新的指向。 3.2.4 访问控制权限 在Java中,针对类、成员方法和属性,Java提供了4种访问控制权限,分别是private、 default、protected和public。这4种访问控制权限按级别由低到高的次序,如图3-7所示。 图3-7 访问控制权限 这4种访问控制权限具体介绍如下: (1)private:私有访问权限。用于修饰类的属性和方法,也可以修饰内部类。类的成 员一旦使用了private关键字修饰,则该成员只能在本类中访问。 (2)default:默认访问权限。如果一个类中的属性或方法没有任何访问权限声明,则该属 性或方法就是默认访问权限,可以被本包中的其他类访问,但是不能被其他包的类访问。 (3)protected:受保护的访问权限。如果一个类中的成员使用了protected关键字修 饰,则只能被本包及不同包的子类访问。 (4)public:公共访问权限。如果一个类中的成员使用了public关键字修饰,则该成员 可以在所有类中被访问,不管是否在同一个包中。 下面通过一张表总结上述访问控制权限,如表3-1所示。 表3-1 访问控制权限 访问范围private default protected public 同一个类√ √ √ √ 同一个包中的类√ √ √ 不同包的子类√ √ 全局范围√ 下面通过一段代码演示4种访问控制权限修饰符的用法: public class Test { public int aa; //aa 可以被所有的类访问 protected boolean bb; //bb 可以被所有子类以及本包的类访问 void cc() { //默认访问权限,能在本包内访问 System.out.println("包访问权限"); } //private 权限的内部类,即私有的类,只能在本类中访问 private class InnerClass { } } 需要注意的是,外部类的访问权限只能是public或default,所以Test类只能使用 Java基础入门(第90 3版) public修饰或者不写修饰符。局部成员是没有访问控制权限的,因为局部成员只在其所在 的作用域内起作用,不可能被其他类访问,如果在程序中对局部成员使用访问控制权限修饰 符,编译器会报错。错误示例代码如下: public class Test { void cc() { //默认访问权限,能在本包内使用 public int aa; //错误,局部变量没有访问控制权限 protected boolean bb; //错误,局部变量没有访问控制权限 System.out.println("包访问权限"); } //private 权限的内部类,即私有的类,只能在本类中访问 private class InnerClass { } } 运行上述代码,控制台会报错,如图3-8所示。 图3-8 局部成员访问控制权限错误 小提示:Java程序的文件名 如果一个源文件中定义的所有类都没有使用public修饰,那么这个源文件的文件名可 以是一切合法的文件名;如果一个源文件中定义了一个使用public修饰的类,那么这个源 文件的文件名必须与public修饰的类名相同。 3.3 封装性 封装是面向对象的核心思想,掌握封装对于学习Java面向对象的内容十分重要。本节 对封装进行详细讲解。 3.3.1 为什么要封装 在Java面向对象的思想中,封装是指将类的实现细节包装、隐藏起来的方法。封装可 以被认为是一道保护屏障,防止本类的代码和数据被外部类定义的代码随机访问。下面通 过一个例子具体讲解什么是封装,如文件3-3所示。 文件3-3 Example03.java 1 class Student{ 2 String name; //声明name 属性 第3章 面向对象(上) 91 3 int age; //声明age 属性 4 void read() { 5 System.out.println("大家好,我是"+name+",年龄"+age); 6 } 7 } 8 public class Example03 { 9 public static void main(String[] args) { 10 Student stu = new Student(); //创建学生对象 11 stu.name = "张三"; //为对象的name 属性赋值 12 stu.age = -18; //为对象的age 属性赋值 13 stu.read(); //调用对象的方法 14 } 15 } 在文件3-3中,第12行代码将age属性赋值为-18岁,这在程序中是不会有任何问题 的,因为int的值可以取负数;但在现实中,-18明显是一个不合理的年龄值。为了避免这 种错误的发生,在设计Student类时,应该对成员变量的访问做出一些限定,不允许外界随 意访问,这就需要实现类的封装。 3.3.2 如何实现封装 类的封装是指将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象的内 部信息,而是通过该类提供的方法实现对内部信息的访问。 封装的具体实现过程是:在定义一个类时,将类中的属性私有化,即使用private关键 字修饰类的属性。私有属性只能在它所在的类中被访问。如果外界想要访问私有属性,需 要提供一些使用public修饰的公有方法,其中包括用于获取属性值的getXxx()方法(也称 为getter方法)和设置属性值的setXxx()方法(也称为setter方法)。 修改文件3-3,使用private关键字修饰name属性和age属性以及其对应的getter/ setter方法,演示如何实现类的封装,如文件3-4所示。 文件3-4 Example04.java 1 class Student{ 2 private String name; //声明name 属性 3 private int age; //声明age 属性 4 public String getName() { 5 return name; 6 } 7 public void setName(String name) { 8 this.name = name; 9 } 10 public int getAge() { 11 return age; 12 } 13 public void setAge(int age) { 14 if(age < 0){ 15 System.out.println("您输入的年龄有误!"); 16 } else { 17 this.age = age; Java基础入门(第92 3版) 18 } 19 } 20 public void read() { 21 System.out.println("大家好,我是"+name+",年龄"+age); 22 } 23 } 24 public class Example04 { 25 public static void main(String[] args) { 26 Student stu = new Student(); //创建学生对象 27 stu.setName("张三"); //为对象的name 属性赋值 28 stu.setAge(-18); //为对象的age 属性赋值 29 stu.read(); //调用对象的方法 30 } 31 } 在文件3-4中,使用private关键字将name属性和age属性声明为私有变量,并对外界 提供公有的访问方法,其中,getName()方法和getAge()方法用于获取name属性和age属 性的值,setName()方法和setAge()方法用于设置name属性和age属性的值。 文件3-4的运行结果如图3-9所示。 图3-9 文件3-4的运行结果 从图3-9可以看出,当调用setAge()方法传入了一个负数(-18)时,程序提示年龄输入 有误,age显示为初始值0。这是因为setAge()方法对参数age进行了判断,如果age的值 小于0,会打印“您输入的年龄有误!”,age会采用初始值0(在Java中int类型的变量初始值 为0)。 3.4 构造方法 从前面所学的知识可以发现,实例化一个对象后,如果要为这个对象中的属性赋值,则 必须直接访问对象的属性或调用setter方法。如果需要在实例化对象时为这个对象的属性 赋值,可以通过构造方法实现。构造方法(也称为构造器)是类的一个特殊成员方法,在类实 例化对象时自动调用。本节针对构造方法进行详细讲解。 3.4.1 定义构造方法 构造方法是一个特殊的成员方法,在定义时,有以下几点需要注意: (1)构造方法的名称必须与类名一致。 (2)构造方法名称前不能有任何返回值类型的声明。 (3)不能在构造方法中使用return返回一个值,但可以单独写return语句作为方法的 第3章 面向对象(上) 93 结束。下 面通过一个案例演示构造方法的定义,如文件3-5所示。 文件3-5 Example05.java 1 class Student{ 2 public Student() { 3 System.out.println("调用了无参构造方法"); 4 } 5 } 6 public class Example05 { 7 public static void main(String[] args) { 8 System.out.println("声明对象..."); 9 Student stu = null; //声明对象 10 System.out.println("实例化对象..."); 11 stu = new Student(); //实例化对象 12 } 13 } 在文件3-5中,在Student类中定义了无参构造方法。在Example05类的main()方法 中,声明并实例化了stu对象。 文件3-5的运行结果如图3-10所示。 图3-10 文件3-5的运行结果 从图3-10可以看出,当调用关键字new 实例化对象时,程序调用了Student类的无参 构造方法。 在一个类中除了可以定义无参构造方法外,还可以定义有参构造方法,通过有参构造方 法可以实现对属性的赋值。 下面修改文件3-5,演示有参构造方法的定义与调用,如文件3-6所示。 文件3-6 Example06.java 1 class Student{ 2 private String name; 3 private int age; 4 public Student(String n, int a) { 5 name = n; 6 age = a; 7 System.out.println("调用了有参构造方法"); 8 } 9 public void read(){ Java基础入门(第94 3版) 10 System.out.println("我是:"+name+",年龄:"+age); 11 } 12 } 13 public class Example06 { 14 public static void main(String[] args) { 15 Student stu = new Student("张三",18); //实例化Student 对象 16 stu.read(); 17 } 18 } 在文件3-6中,Student类中声明了私有属性name和age,并且定义了有参构造方法。 第15行代码实例化Student对象,并传入参数“张三”和18,分别赋值给name和age,该过 程会调用有参构造方法;第16行代码通过stu对象调用了read()方法。 文件3-6的运行结果如图3-11所示。 图3-11 文件3-6的运行结果 由图3-11可知,name属性已经被赋值为“张三”,age属性已经被赋值为18。 3.4.2 构造方法的重载 与普通方法一样,构造方法也可以重载,在一个类中可以定义多个构造方法,但是要求 每个构造方法的参数类型或参数个数不同。在创建对象时,可以通过调用不同的构造方法 为不同的属性赋值。 下面通过一个案例学习构造方法的重载,如文件3-7所示。 文件3-7 Example07.java 1 class Student{ 2 private String name; 3 private int age; 4 public Student() { } 5 public Student(String n) { 6 name = n; 7 System.out.println("调用了一个参数的构造方法"); 8 } 9 public Student(String n,int a) { 10 name = n; 11 age = a; 12 System.out.println("调用了两个参数的构造方法"); 13 } 14 public void read(){ 第3章 面向对象(上) 95 15 System.out.println("我是:"+name+",年龄:"+age); 16 } 17 } 18 public class Example07 { 19 public static void main(String[] args) { 20 Student stu1 = new Student("张三"); 21 Student stu2 = new Student("张三",18); //实例化Student 对象 22 stu1.read(); 23 stu2.read(); 24 } 25 } 在文件3-7中,Student类中定义了一个无参构造方法和两个有参构造方法。在main()方 法中,第20~23行代码实例化stu1对象和stu2对象时,根据实例化对象时传入参数个数 的不同,stu1对象调用了只有一个参数的构造方法,stu2对象调用了有两个参数的构造 方法。 文件3-7的运行结果如图3-12所示。 图3-12 文件3-7的运行结果 多学一招:默认构造方法 在Java中的每个类都至少有一个构造方法。如果在一个类中没有定义构造方法,系统 会自动为这个类创建一个默认的构造方法,这个默认的构造方法没有参数,方法体中没有任 何代码,所以Java中默认的构造方法在程序运行时什么也不做。 下面的程序中,Student类的两种写法效果是完全一样的。 第一种写法: class Student { } 第二种写法: class Student { public Student(){ } } 对于第一种写法,类中虽然没有声明构造方法,但仍然可以用newStudent()语句创建 Student类的实例对象,在实例化对象时调用默认构造方法。 Java基础入门(第96 3版) 由于系统提供的默认构造方法往往不能满足需求,因此,通常需要程序员自己在类中定 义构造方法,一旦为类定义了构造方法,系统就不再提供默认的构造方法了,具体代码如下 所示: class Student { int age; public Student(int n) { age = n; } } 上面的Student类中定义了一个有参构造方法,这时系统就不再提供默认的构造方法。 下面再编写一个测试程序调用上面的Student类,如文件3-8所示。 文件3-8 Example08.java 1 public class Example08 { 2 public static void main(String[] args) { 3 Student stu = new Student(); //实例化Student 对象 4 } 5 } 运行文件3-8,编译器会报错,错误信息如图3-13所示。 图3-13 文件3-8编译错误信息 从图3-13可以看出,编译器提示“无法将类com.itheima.Student中的构造器Student 应用到给定类型”,原因是:使用newStudent()创建Student类的实例对象时需要调用无 参构造方法,而Student类中定义了一个有参构造方法,系统不再提供无参构造方法。为 了避免上面的错误,在一个类中如果定义了有参构造方法,最好再定义一个无参构造 方法。 需要注意的是,构造方法通常使用public进行修饰。 3.5 this关键字 在Java开发中,当成员变量与局部变量发生重名问题时,需要使用this关键字分辨成 员变量与局部变量。Java中的this关键字语法比较灵活,其作用主要有以下3个: 第3章 面向对象(上) 97 (1)使用this关键字调用本类中的属性。 (2)使用this关键字调用成员方法。 (3)使用this关键字调用构造方法。 本节将详细讲解this关键字的这3种用法。 3.5.1 使用this关键字调用本类中的属性 在文件3-6中,Student类定义中的成员变量age表示年龄,而构造方法中表示年龄的 参数是a,这样的程序可读性很差。这时需要对一个类中表示年龄的变量进行统一命名,例 如都声明为age。但是这样做会导致成员变量和局部变量的名称冲突。下面通过一个案例 进行验证,如文件3-9所示。 文件3-9 Example09.java 1 class Student { 2 private String name; 3 private int age; 4 //定义构造方法 5 public Student(String name,int age) { 6 name = name; 7 age = age; 8 } 9 public String read(){ 10 return "我是:"+name+",年龄:"+age; 11 } 12 } 13 public class Example09 { 14 public static void main(String[] args) { 15 Student stu = new Student("张三", 18); 16 System.out.println(stu.read()); 17 } 18 } 文件3-9的运行结果如图3-14所示。 图3-14 文件3-9的运行结果 由图3-14可知,stu对象姓名为null,年龄为0,这表明构造方法中的赋值并没有成 功,这是因为构造方法参数名称与对象成员变量名称相同,编译器无法确定哪个名称是 当前对象的属性。为了解决这个问题,Java提供了关键字this指代当前对象,通过this可 以访问当前对象的成员。修改文件3-9,使用this关键字指定当前对象属性,如文件3-10 所示。 Java基础入门(第98 3版) 文件3-10 Example10.java 1 class Student { 2 private String name; 3 private int age; 4 //定义构造方法 5 public Student(String name,int age) { 6 this.name = name; 7 this.age = age; 8 } 9 public String read(){ 10 return "我是:"+name+",年龄:"+age; 11 } 12 } 13 public class Example10 { 14 public static void main(String[] args) { 15 Student stu = new Student("张三", 18); 16 System.out.println(stu.read()); 17 } 18 } 文件3-10的运行结果如图3-15所示。 图3-15 文件3-10的运行结果 从图3-15可以看出,文件3-10成功调用构造方法完成了stu对象的初始化。这是因为 在构造方法中,使用this关键字明确标识了类中的两个属性this.name和this.age,所以在 进行赋值操作时不会产生歧义。 3.5.2 使用this关键字调用成员方法 可以通过this关键字调用成员方法,具体示例代码如下: class Student { public void openMouth() { … } public void read() { this.openMouth(); } } 上述代码中,在read()方法中使用this关键字调用了openMouth()方法。需要注意的 是此处的this关键字也可以省略不写。 第3章 面向对象(上) 99 3.5.3 使用this关键字调用构造方法 构造方法在实例化对象时被Java虚拟机自动调用。在程序中不能像调用其他成员方 法一样调用构造方法,但可以在一个构造方法中使用“this(参数1,参数2,…)”的形式调用 其他的构造方法。 下面通过一个案例演示如何使用this关键字调用构造方法,如文件3-11所示。 文件3-11 Example11.java 1 class Student { 2 private String name; 3 private int age; 4 public Student () { 5 System.out.println("调用了无参构造方法"); 6 } 7 public Student (String name,int age) { 8 this(); //调用无参构造方法 9 this.name = name; 10 this.age = age; 11 } 12 public String read(){ 13 return "我是:"+name+",年龄:"+age; 14 } 15 } 16 public class Example11 { 17 public static void main(String[] args) { 18 Student stu = new Student("张三",18); //实例化Student 对象 19 System.out.println(stu.read()); 20 } 21 } 在文件3-11中,Student类中定义了一个无参构造方法和一个有参构造方法,并在有参 构造方法中使用this()的形式调用本类中的无参构造方法。 文件3-11的运行结果如图3-16所示。 图3-16 文件3-11的运行结果 使用this调用类的构造方法时,应注意以下3点: (1)只能在构造方法中使用this调用其他的构造方法,不能在成员方法中通过this调 用构造方法。 (2)在构造方法中,使用this调用其他构造方法的语句必须位于第一行,且只能出现 Java基础入门(第1 00 3版) 一次。下 面程序的写法是错误的: public Student(String name) { System.out.println("有参构造方法被调用了。"); this(name); //不在第一行,编译错误! } (3)不能在一个类的两个构造方法中使用this互相调用。下面程序的写法是错误的: class Student { public Student () { this("张三"); //调用有参构造方法 System.out.println("有参构造方法被调用了。"); } public Student (String name) { this(); //调用无参构造方法 System.out.println("无参构造方法被调用了。"); } } 3.6 代码块 代码块,简单来讲,就是用{}括起来的一段代码。根据位置及声明关键字的不同,代码 块可以分为4种:普通代码块、构造块、静态代码块、同步代码块。本节将针对普通代码块 和构造块进行讲解。静态代码块将在3.7节中进行讲解,同步代码块将在多线程部分进行 讲解。 3.6.1 普通代码块 普通代码块就是直接在方法或语句中定义的代码块,具体示例如下: public class Test { public static void main(String[] args) { { int age = 18; System.out.println("这是普通代码块。age:"+age); } int age = 30; System.out.println("age:"+age); } } 在上述代码中,每一对“{}”括起来的代码都称为一个代码块。Test是一个大的代码 块,在Test代码块中包含了main()方法代码块,在main()方法中又定义了一个局部代码 块,局部代码块对main()方法进行了“分隔”,起到了限定作用域的作用。 第3章 面向对象(上) 1 01 上述代码的局部代码块中定义了变量age,main()方法代码块中也定义了变量age,但 由于两个变量处在不同的代码块中,作用域不同,因此并不相互影响。 3.6.2 构造块 构造块是直接在类中定义的代码块。下面通过一个案例演示构造块的使用,如文件3-12 所示。 文件3-12 Example12.java 1 class Student{ 2 String name; //成员属性 3 { 4 System.out.println("我是构造块"); //与构造方法同级 5 } 6 //构造方法 7 public Student(){ 8 System.out.println("我是Student 类的构造方法"); 9 } 10 } 11 public class Example12 { 12 public static void main(String[] args) { 13 Student stu1 = new Student(); 14 Student stu2 = new Student(); 15 } 16 } 在文件3-12中,第3~5行代码定义的代码块与构造方法、成员属性同级,这样的代码 块就是构造块。 文件3-12的运行结果如图3-17所示。 图3-17 文件3-12的运行结果 由图3-17可以得出以下两点结论: (1)在实例化Student类对象stu1、stu2时,构造块先于构造方法执行(这里和构造块 写在前面还是后面没有关系)。 (2)每当实例化一个Student类对象时,都会在执行构造方法之前执行构造块。