5 继承
利用继承,人们可以基于已存在的类构造一个新类,并复用这些类的方法和域。
反射,是指在程序运行期间发现更多的类及属性的能力。
5.1 类、超类和子类
class Manager extends Employee
关键词extends表面正在构造的新类派生于一个已存在的类。已存在的类称为超类superclass,基类base class或父类parent class;新类称为子类subclass、派生类derived class或孩子类child class。
子类比超类拥有的功能更加丰富。
一个变量可以指示多种实际类型的现象被称为多态,在运行时能够自动地选择调用哪个方法的现象称之为动态绑定。
public class Manager extends Employee { private double bonus; public Manager(String n, double s, int year, int month, int day) { super(n, s, year, month, day); bonus = 0; } public double getSalary() { double baseSalary = super.getSalary(); return baseSalary + bonus; } public void setBonus(double b) { bonus = b; } }
5.1.1 继承层次
由一个公共类派生出的所有类的集合被称为继承层次。由某个特定类到其祖先的路径被称为该类的继承链。
5.1.2 多态
is-a,置换法则,超类出现的地方都可以用子类对象去置换。
Java中,对象变量是多态的,一个employee变量可以引用一个 Employee对象,也可以引用一个Employee类的任何一个子类的对象。
但是,不能把一个超类的引用赋给子类变量。
5.1.3 动态绑定
重载解析:在所有同名的方法中查到一个与参数类型完全匹配,就选用这个方法。
如果是private,final,static方法,编译器将可以准确知道应该调用哪个方法,称为静态绑定。
5.1.4 阻止继承:final类和方法
不允许扩展的类称之为final类。
类中特定的方法也可以被声明为final,子类就不能覆盖这个方法。final类中所有方法自动地成为final方法。
将类和方法声明为final的主要目的是,确保它们不在子类中改变语义。
5.1.5 强制类型转换
Manager boss = (Manager) staff[0]
强制类型转换的唯一原因是,在暂时忽略了对象的实际类型之后,使用对象的全部功能。
- 只能在继承层次内进行类型转换
- 在将超类转换成子类之前,应该使用instanceof进行检查。
5.1.6 抽象类
包含一个或多个抽象方法的类必须被声明为抽象类。
abstract class Person{ public abstract String getDescription(); }
除了抽象方法之外,抽象类还可以包含具体数据和具体方法。
类即使不包含抽象方法,也可以将类声明为抽象类。
抽象类不能被实例化。
可以定义一个抽象类的变量对象,但是它只能引用非抽象子类的对象。
5.1.7 受保护方法
超类中的某些方法允许被子类访问,或允许子类的方法访问超类的某个域。为此,需要将这些方法和域声明为protected。
归纳一下Java当中控制可见性的4个访问修饰符:
- 仅对本类可见——private
- 对所有类可见——public
- 对本包和所有子类可见——protected
- 对本包可见——不需要修饰符
5.2 Object:所有类的超类
在Java中,只有基本类型不是对象,所有的数组类型都扩展于Object类。
5.2.1 euals方法
Object类中的equals方法用于检测一个对象是否等于另外一个对象。如果两个对象具有相同的引用,它们一定是相等的。然而,经常需要检测两个对象状态的相等性,如果两个对象状态相等,就认为两个对象是相等的。
5.2.2 相等测试与继承
Java语言规范要求equals方法具有下面的特性
- 自反性
- 对称性
- 传递性
- 一致性
编写一个完美equals方法的建议
- 显式参数命名为otherObject,稍后将它转换为一个叫做other的变量
- 检测this与otherObject是否引用同一个对象
if (this == otherObject) return true;
- 检测otherObject是否为null,如果为null,返回false。这项检测很有必要。
- 比较this与otherObject是否属于同一个类,用getClass检测
- 将otherObject转换为相应的类类型变量。
- 开始对所有需要进行比较的域进行比较了,用==比较基本类型域,用equals比较对象域。
5.2.3 hashCode方法
散列码是一个对象导出的整型值。散列码是没有规律的。
hashCode方法定义在Objcet类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。
字符串的散列码是由其内容导出的,StringBuffer类中没有定义hashCode方法,它的散列码是由Object类默认的hashCode方法导出的对象存储地址。
5.2.4 toString方法
toString方法用于返回对象值的字符串。
public String toString(){ return getClass().getName() + "[name=" + name + ", salary=" + salary + ", hireDay" + hireDay + "]"}
只要一个对象与一个字符串通过“+”连接起来,Java编译会自动调用toString方法,以便获得这个对象的字符串描述。
5.3泛型数组列表
ArrayList类是一个采用类型参数的泛型类。
ArrayList<Employee>staff = new ArrayList<>();
staff.add(new Employee("kate", ...);
staff.add(new Employee("Ann",...);
staff.ensureCapacity(100);
staff.size();
staff.trimToSize();
5.3.1 访问数组列表元素
- 不必指出数组的大小
- 使用add将任意多的元素添加到数组中
- 使用size()替代length计算元素的数目
- 使用a.get(i)替代a[i]访问元素
5.3.2 类型化与原始数组列表的兼容性
可以将类型化数组传递给原始数组列表参数,但是反之则将会得到一个警告。
5.4 对象包装器与自动装箱
自动装箱
list.add(3);
list.add(Integer.valueof(3);
自动拆箱
int n = list.get(i); int n = list.get(i).intValue();
装箱与拆箱是编译器认可的,而不是虚拟机。
int x = Integer.parseInt(s);
5.5 参数数量可变的方法
实际上,printf方法接收两个参数,一个是格式字符串,一个是Object[]数组,其中保存着所有参数。
用户也可以自定义自己的可变参数的方法,实质上是将可变参数转成Object[]数组传递给方法。
5.6 枚举类
比较两个枚举类的时候,永远不要调用equals方法,直接使用==就可以了。
toString的逆方法是静态方法valueof。
5.7 反射
反射库提供了能够动态操作Java代码的程序。这些功能被大量用于JavaBeans当中。
能够分析类能力的程序称为反射。反射机制可以用来
- 在运行中分析类的能力
- 在运行中查看对象
- 实现通用的数组操作代码
- 利用method对象。
5.7.1 Class类
程序运行中,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。保存这些信息的类,被称为Class类。
Employee e; Class cl = e.getClass();
System.out.println(e.getClass().getName()+ "" + e.getName());
String className = "java.util.Date";
class cl = Class.forName(className);
Class cl = int.class;
Class cl1 = Date.class;
Class对象实际上表示的是一种类型,而这个类型未必一定是一个类,比如int不是类,但int.class是一个Class类型的对象。
虚拟机为每个类型管理一个Class对象。可以利用“==”运算符实现两个类对象比较的操作。
if(e.getClass() == Employee.class)... e.getClass().newInstance(); Object m = Class.forName(s).newInstance();
5.7.2 捕获异常
异常有两种类型,已检查异常与未检查异常。对于已检查异常,编译器将会检查是否提供了处理器。ClassforName方法就是一个抛出已检查异常的例子。
try catch
5.7.3 利用反射来分析类的能力
反射机制的最重要的内容——检查类的结构
Field,Method和Constructor分别用于描述类的域,方法和构造器。
Modifier类。
Class类中的方法getFields, getMethods, getConstructors, getDecalredFields, getDeclaredMethods, getDeclaredConstructors。
public class ReflectionTest { public static void main(String[] args) { // read class name from command line args or user input String name; if (args.length > 0) name = args[0]; else { Scanner in = new Scanner(System.in); System.out.println("Enter class name (e.g. java.util.Date): "); name = in.next(); } try { // print class name and superclass name (if != Object) Class cl = Class.forName(name); Class supercl = cl.getSuperclass(); String modifiers = Modifier.toString(cl.getModifiers()); if (modifiers.length() > 0) System.out.print(modifiers + " "); System.out.print("class " + name); if (supercl != null && supercl != Object.class) System.out.print(" extends " + supercl.getName()); System.out.print("\n{\n"); printConstructors(cl); System.out.println(); printMethods(cl); System.out.println(); printFields(cl); System.out.println("}"); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.exit(0); } /** * Prints all constructors of a class * @param cl a class */ public static void printConstructors(Class cl) { Constructor[] constructors = cl.getDeclaredConstructors(); for (Constructor c : constructors) { String name = c.getName(); System.out.print(" "); String modifiers = Modifier.toString(c.getModifiers()); if (modifiers.length() > 0) System.out.print(modifiers + " "); System.out.print(name + "("); // print parameter types Class[] paramTypes = c.getParameterTypes(); for (int j = 0; j < paramTypes.length; j++) { if (j > 0) System.out.print(", "); System.out.print(paramTypes[j].getName()); } System.out.println(");"); } } /** * Prints all methods of a class * @param cl a class */ public static void printMethods(Class cl) { Method[] methods = cl.getDeclaredMethods(); for (Method m : methods) { Class retType = m.getReturnType(); String name = m.getName(); System.out.print(" "); // print modifiers, return type and method name String modifiers = Modifier.toString(m.getModifiers()); if (modifiers.length() > 0) System.out.print(modifiers + " "); System.out.print(retType.getName() + " " + name + "("); // print parameter types Class[] paramTypes = m.getParameterTypes(); for (int j = 0; j < paramTypes.length; j++) { if (j > 0) System.out.print(", "); System.out.print(paramTypes[j].getName()); } System.out.println(");"); } } /** * Prints all fields of a class * @param cl a class */ public static void printFields(Class cl) { Field[] fields = cl.getDeclaredFields(); for (Field f : fields) { Class type = f.getType(); String name = f.getName(); System.out.print(" "); String modifiers = Modifier.toString(f.getModifiers()); if (modifiers.length() > 0) System.out.print(modifiers + " "); System.out.println(type.getName() + " " + name + ";"); } } }
5.7.4 在运行时使用反射分析对象
反射机制的默认行为受限于Java的访问控制,为了覆盖访问控制,可以调用Field, Method, Constructor对象的setAccessible方法。
编写一个可供任意类使用的通用的toString方法。
public String toString(Object obj) { if (obj == null) return "null"; if (visited.contains(obj)) return "..."; visited.add(obj); Class cl = obj.getClass(); if (cl == String.class) return (String) obj; if (cl.isArray()) { String r = cl.getComponentType() + "[]{"; for (int i = 0; i < Array.getLength(obj); i++) { if (i > 0) r += ","; Object val = Array.get(obj, i); if (cl.getComponentType().isPrimitive()) r += val; else r += toString(val); } return r + "}"; } String r = cl.getName(); // inspect the fields of this class and all superclasses do { r += "["; Field[] fields = cl.getDeclaredFields(); AccessibleObject.setAccessible(fields, true); // get the names and values of all fields for (Field f : fields) { if (!Modifier.isStatic(f.getModifiers())) { if (!r.endsWith("[")) r += ","; r += f.getName() + "="; try { Class t = f.getType(); Object val = f.get(obj); if (t.isPrimitive()) r += val; else r += toString(val); } catch (Exception e) { e.printStackTrace(); } } } r += "]"; cl = cl.getSuperclass(); } while (cl != null); return r; } }
5.7.5 使用反射编写泛型数组代码
5.7.6 调用任意方法
Object invoke(Object obj, Object...args);
String n = m1.invoke(harry);
double s = (Double)m2.invoke(harry);
Method m1 = Employee.class.getMethod("getName");
Method m2 = Employee.class.getMethod("getSalary", double.class);
invoke的参数和返回值必须是Object类型的,这就意味着必须进行多次的类型转换,同时要比直接调用慢一点,建议仅在必要时使用Method对象。
public class MethodTableTest { public static void main(String[] args) throws Exception { // get method pointers to the square and sqrt methods Method square = MethodTableTest.class.getMethod("square", double.class); Method sqrt = Math.class.getMethod("sqrt", double.class); // print tables of x- and y-values printTable(1, 10, 10, square); printTable(1, 10, 10, sqrt); } /** * Returns the square of a number * @param x a number * @return x squared */ public static double square(double x) { return x * x; } /** * Prints a table with x- and y-values for a method * @param from the lower bound for the x-values * @param to the upper bound for the x-values * @param n the number of rows in the table * @param f a method with a double parameter and double return value */ public static void printTable(double from, double to, int n, Method f) { // print out the method as table header System.out.println(f); double dx = (to - from) / (n - 1); for (double x = from; x <= to; x += dx) { try { double y = (Double) f.invoke(null, x); System.out.printf("%10.4f | %10.4f%n", x, y); } catch (Exception e) { e.printStackTrace(); } } } }
5.8 继承设计的技巧
- 将公共操作和域放在超类
- 不要使用受保护的域
- 使用基础实现is-a关系
- 除非所有继承都有意义,否则不要使用继承
- 在覆盖方法时,不要改变预期的行为
- 使用多态,而非类型信息
- 不要过多的使用反射

浙公网安备 33010602011771号