Java核心技术 卷一
5.1 类、超类和子类
- 定义子类
下面是由继承Employee类来定义Manager类的格式,关键词extends表示继承。
例如:
public class Manager extends Employee
{
添加方法和域
}
关键词extends表明正在构造新类派生于一个已存在的类。已存在的类称为超类、基类或父类,新类称为子类、派生类或孩子类。
在Manager类中,增加一个用于存储奖金信息的域,以及一个用于设置这个域的新方法:
public class Manager extends Employee
{
private double bonus;
...
public void setBonos(double bonus)
{
this.bonus = bonus;
}
}
//方法使用:
Manager boss = . . .;
boss.setBonus(5000);
//父类不能使用子类方法,而子类可以使用父类方法。
- 覆盖方法
超类中的有些方法对子类Manager并不一定适用。具体来说,Manager类中的getSalary方法应该返回薪水和奖金的总和。,此时,就需要覆盖父类方法。
例如:
public class Manager extends Employee
{
...
public double getSalary()
{
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
...
}
- 子类构造器
例如:
public Manager(String name, double salary, int year, int month, int day)
{
super(name, salary, year, month, day);
bonus = 0;
}
语句super(name, salary, year, month, day)是“调用超类Employee中含有n、s、year、month和day参数的构造器”的简写形式。
由于Manager类的构造器不能访问Employee类的私有域,所以必须利用Employee类的构造器对这些部分私有域进行初始化。,可以通过super实现对超类构造器的调用。
- 继承层次
由一个公共超类派生出来的所有类的集合称为继承层次。
在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链。
- 多态
例如:
Employee e;
e = new Employee(. . .); // Employee object expected
e = new Manager(. . .); // OK, Manager can be used as well
- 理解方法调用
下面假设要调用x.f(args),隐式参数x声明为类c的一个对象。
1.编译器查看对象的声明类型和方法名。
2.接下来,编译器将查看调用方法时提供的参数类型。
3.如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,其称为静态绑定。否则为动态绑定。
4.当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所应用对象的实际类型最合适的那个类的方法。
例如:
Manager:
getName() -> Employee.getName()
getSalary() -> Manager.getSalary()
getHireDay() -> Employee.getHireDay()
raiseSalary(double) -> Employee.raiseSalary(double)
setBonus(double) -> Manager.setBonus(double)
在运行时,调用e.getSalary()的解析过程为:
1.首先,虚拟机提取e的实际类型的方法表。
2.接下来,虚拟机搜索定义getSalary签名的类。
3.最后,虚拟机调用方法。
动态绑定有一个非常重要的特性:无需对现存的代码进行修改,就可以对程序进行拓展。假设增加一个新类Executive,并且变量e有可能引用这个类的对象,我们不需要对包含调用e.getSalary()的代码进行重新编译。
- 阻止继承:final类和方法
不允许拓展的类被称为final类。
例如:
public final class Executive extends Manager//子类就不能覆盖这个方法,final类中的所有方法自动地称为final方法。
{...}
将方法或类声明为final主要目的是:确保它们不会再子类改变语义。
- 强制类型转换
Java程序语言设计提供了一个专门用于进行类型转换地表示法。
例如:
double x = 3.405;
int nx = (int) x ;
只能在继承层次内进行类型转换。在将超类转换成子类之前,应该使用instanceof进行检查。
- 抽象类
如果自下面上在类的继承层次结构中上移,位于上层的类更具有通用性,甚至可能更加抽象
拓展抽象类可以有两种选择:
1.在抽象类中定义部分抽象类方法或不定义抽象类方法,就必须将子类也便极为抽象类。
2.定义全部的抽象方法,此时,子类就不是抽象的了。
- 受保护访问
Java用于控制可见性的4个访问修饰符:
1.仅对本类可见——private
2.对所有类可见——public
3.仅本包和所有子类可见——protected
4.对本包可见——默认,不需要修饰符
5.2 Object:所有类的超类
Object类是Java中所有类的始祖,在Java中每个类都是由他拓展而来的。
可以使用Object类型的变量引用任何类型的对象:
Object obj = new Employee("Harry Hacker", 35000);
当然,Object类型的变量只能用于作为各种值的通用持有者。否则使用强制转换。
- equals方法
Object类中的equals方法用于检测一个对象是否等于另一个对象。
public class Employee
{
...
public boolean equals(Object otherObject)
{
// a quick test to see if the objects are identical
if (this == otherObject) return true;
// must return false if the explicit parameter is null
if (otherObject == null) return false;
// if the classes don't match, they can't be equal
if (getClassO != otherObject.getClass())
return false;
// now we know otherObject is a non-null Employee
Employee other = (Employee) otherObject;
// test whether the fields have identical values
return name.equals(other. name)
&& salary = other,sal ary
&& hi reDay. equals(other,hi reDay):
}
}
- 相等测试与继承
Java语言规范要求equals方法具有以下特性:
1.自反性:对于任何非空引用x,x.equals(x)应该返回true
2.对称性:对于任何引用x与y,当且仅当y.equals(x)返回true,x.equals(y)也应该赶回true
3.传递性:对于任何引用x,y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也返回true。
4.一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回通用的结果
5.队医任意非空引用x,x.equals(null)应该返回false
-
hashCode方法
散列码(hash Code)是由对象导出的一个整型值。
由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列值,其值为对象的存储地址。
例如:
String s = "Ok";
StringBuilder sb = new StringBuilder(s);
System.out.println(s.hashCode() + " "+sb.hashCode());
String t = new String("Ok");
StringBuilder tb = new StringBuilder(1);
System.out.println(t.hashCode() + " " +tb.hashCode());
- toString方法
用于返回表示对象值的字符串。
例如:
public String toStringO
{
return "Employee[name=" + name
+ ",salary:"+salary
+",hireDay="+hireDay
+ "]";
}
5.3 泛型数组列表
为了指定数组列表保存的元素对象类型,需要用一对尖括号将类名括起来加在后面。
例如:
ArrayList<Employee> staff = new ArrayList<Eniployee>();
- 访问数组列表元素
数组列表自动扩展容量的遍历增加了访问元素语法的复杂程度。其原因是ArrayList类并不是Java程序设计语言的一部分。
使用get和set方法实现访问或改变数组元素的操作,而不使用人们喜欢的[]语法格式。
例如:
staff.set(i , harry):
等价于
a[i] = harry;
可以使用“for each”循环遍历数组列表:
for (Employee e : staff)
do something with e
例如:
for (int i = 0; i < staff .size(); i ++)
{
Employee e = staff.get(i);
do something with e
}
- 类型化与原始数组列表的兼容性
例如:
public class EmployeeDB
{
public void update(ArrayList list) { . . . }
public ArrayList find(String query) {. . . }
}
可以将一个类型化的数组列表传递给update方法,而并不需要进行任何类型转换。
ArrayList<Employee> staff = . . .;
employeeDB.update(staff);
5.4 对象包装器与自动装箱
有时,需要将int这样的基本数据类型转为对象。例如,Integer类对应的数据类型int,这些类称为包装器。其包括:Integer、Long、Float、Double、Short、Byte、Character 、Void 和 Boolean。
例如:
ArrayList<Integer> list = new ArrayList<>();
list.add(3);//list.add (Integer.value0f(3));
这称为自动装箱。
int n = list.get(i);//int n = list.get(i).intValue();
其称为自动拆箱。
5.5 参数数量可变的方法
在Java SE 5.0以前的版本中,每个Java方法都有固定数量的参数。然而,现在的版本提供了可以用可变的参数数量调用的方法。
例如:
public class PrintStream
{
public PrintStream printf(String fmt , Object... args) { return format(fmt, args); }
}
这里的省略号是Java的一部分,它表明这个方法可以接受任意数量的对象
5.6 枚举类
例如:
public enum Size { SMALL , MEDIUM, LARGE, EXTRAJARGE };
如果需要的话,可以在枚举类型中添加一些构造器、方法和域。
public enum Size
{
SMALL("S"), MEDIUMC("M"), LARGE("L"), EXTRA_LARGE("XL");
private String abbreviation;
private Size(String abbreviation) { this.abbreviation = abbreviation; }
public String getAbbreviation() { return abbreviation; }
}
所有的枚举类型都是Enum类的子类。(toString)
5.7 反射
反射库提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序。
能够具有分析类能力的程序称为反射。
作用如下:
1.在运行时分析类的能力。
2.在运行时查看对象,例如,编写一个toString方法供所有类使用。
3.实现通用的数组操作代码。
4.利用Method对象,这个对象很像C++中的函数指针。
- Class类
在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。
这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。
虚拟机为每一个类型管理一个Class对象。因此,可以利用==运算符实现两个类对象比较的操作。
例如:
if (e.getClass()==Employee.class) . . .
- 捕获异常
当程序运行过程中发生错误时,就会“抛出异常”。
异常处理:未检查异常和已检查异常。对于已检查异常,编译器将会检查是否提供了处理器。然而,很多常见的异常,例如:访问null引用,都属一未检查异常。
- 利用反射分析类的能力
反射机制最重要的内容:检查类的结构。
在 java.lang.reflect 包中有三个类 Field、 Method 和 Constructor 分别用于描述类的域、 方法和构造器。
Class类中的 getFields、 getMethods 和 getConstructors 方 法 将 分 别 返 回 类 提 供 的public 域、 方法和构造器数组, 其中包括超类的公有成员。Class 类的 getDeclareFields、getDeclareMethods 和 getDeclaredConstructors 方法将分别返回类中声明的全部域、 方法和构造器, 其中包括私有和受保护成员,但不包括超类的成员。
- 在运行时使用反射分析对象
查看对象域的关键方法是Filed类中的get方法。
例如:
Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989);
Class cl = harry.getClass();
// the class object representing Employee
Field f = cl .getDeclaredField("name");
// the name field of the Employee class
Object v = f.get(harry);
// the value of the name field of the harry object , i .e., the String object "Harry Hacker"
由于name是一个私有域,所以利用get方法才能得到可访问域的值。
反射机制的默认行为受限于Java的访问控制。
- 使用反射编写泛型数组代码
java.lang.reflect包中的Array类允许动态地创建数组。
例如:
Employee[] a = new Employee[100]:
// array is full
a = Arrays.copyOf(a, 2 * a.length);
- 调用任意方法
反射机制允许你调用任意方法。
在 Method 类中有一个 invoke 方法, 它允许调用包装在当前 Method 对象中的方法。invoke 方法的签名是:
Object invoke(Object obj, Object... args)
第一个参数是隐式参数, 其余的对象提供了显式参数。
5.8 继承的设计技巧
建议:
1.将公共操作和域放在超类。
2.不要使用受保护的类。
3.使用继承实现“is-a”关系。
4.除非所有继承的方法都有意义,否则不要使用继承。
5.在覆盖方法时,不要改变预期行为。
6.使用多态,而非类型信息。
7.不要过多使用反射。