前言 本次学习面向对象设计的另外一个基本概念:继承(inheritance)。这是Java程序设计中的一项核心技术。另外,还要学习反射(reflection)的概念。
继承 类、超类、子类 1 2 3 public class Manager extends Employee { }
关键字extends表明正在构造的新类派生于一个已存在的类。已存在的类称为超类(superclass)**、 基类(base class)或 父类(parent class); 新类称为 子类(sbclass)、 派生类(derived class)、 孩子类(childe class)**.
覆盖方法 子类继承父类的字段和方法,但有些方法子类想要修改,可以使用**覆盖(override)**。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class Employee { private String name; private int salary; public String getName () { return name; } public void setName (String name) { this .name = name; } public int getSalary () { return salary; } public void setSalary (int salary) { this .salary = salary; } } public class Manager extends Employee { private int bonus; public int getBonus () { return bonus; } public void setBonus (int bonus) { this .bonus = bonus; } @Override public String getName () { return "manager: " + super .getName(); } @Override public int getSalary () { return super .getSalary() + bonus; } }
extends关键字记得带s 超类的private字段是不能直接在子类中调用的,子类只能调用父类的protected和默认方法 覆盖的要求是完全一致,即子类的方法名,参数类型和顺序,返回值要完全一致 对于要覆盖的方法要添加注解@Overide
想要调用父类的同名方法,使用supper
子类覆盖父类的方法的权限不可以比父类小,父类是public的,子类也只能是public,父类是protected,子类不能是private 值得关注的是子类不能继承父类的private相关字段和方法 。
多态 1 Employee manager = new Manager();
可以将子类赋值给父类。那么,我们创建多个子类,都可以赋值给Employee,employee在运行时可以知道具体是哪个子类的实例,但只能执行父类已有的方法。即子类新加的方法不能执行。子类覆盖的方法可以执行。
一个对象变量可以指示多种实际类型的现象被称为多态(polymorphism)**。在运行时能够自动地选择调用哪个方法的现象称为 动态绑定(dynamic binding)**.
Java不支持多继承,一个类只能继承一个类,而不是多个。要想要实现多个,可以使用接口。
所有的类都继承Object对象。
多态可以用关系is-a 来描述,表明程序中出现超类的任何地方都可以用子类对象置换。
理解方法调用 假设要调用x.f(args), 隐式参数x声明为类C的一个对象。下面是调用过程的详细描述:
1)编译器查看对象的声明类型和方法名。假设调用x.f(args),且隐士参数x声明为C类对象。需要注意的是:有可能存在多个名字为f,但参数类型不一样的方法。例如,可能存在多个名字为f,但参数类型不一样的方法。例如,可能存在方法f(int)和方法f(String). 编译器会一一列举所有C类中名为f的方法和其超类中访问属性为public且名为f的方法(超类的私有方法不可访问)。
至此,编译器已获得所有可能被调用的候选方法。
2)接下来,编译器将查看调用方法时提供的参数类型。如果在所有名为f的方法中存在一个与提供的参数类型完全匹配,将选择这个方法。这个过程被称为**重载解析(overloading resolution)**。例如,对于调用x.f(“Hello”),编译器将会挑选f(String), 而不是f(int). 由于允许类型转换(int可以转double,Manager可以转Employee), 所以这个过程可能很复杂。如果编译器找不到与参数匹配的方法,或发现经过类型转换后有多个方法与之匹配,将会报告一个错误。
至此,编译已获得需要调用的方法名字和参数类型。
3)如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,我们将这种调用方式成为**静态绑定(static binding)**。与之对应,调用的方法依赖于隐士参数的实际类型,并且在运行时实现动态绑定。
4)当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型 最合适的那个类的方法。假设x的实际类型是D, 它是C类的子类。如果D类定义了方法f(String), 就直接调用它,否则,将在D类的超类中寻找f(String),以此类推。
每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创建了一个**方法表(method table)**,其中列出了所有方法的签名和实际调用的方法。这样一来,在真正调用方法的时候,虚拟机仅查找这个表就行了。
动态绑定有一个非常重要的特性:无需对现存程序进行扩展。
阻止继承:final类和方法 有时候,可能希望阻止人们利用某个类定义的子类。不允许扩展的类被成为final类。如果在定义类的时候使用了final修饰符就表明这个类是final类。
类的特定方法也可以声明为final的。这样,子类就不能覆盖这个方法(final类中的所有方法自动成为final方法)。
我们将方法声明为final的主要目的是:确保他们不会在子类中改变语义。
强制转换 只能在继承层次内进行类型转换。
在将超类转换成子类之前,应该使用instanceof进行检查。
抽象类 用abstract修饰的类是抽象类。用abstract修饰的方法是抽象方法。
抽象类不能实例化。抽象方法没有方法体。
受保护访问 仅本类可见–private 所有类可见–public 对本包和所有子类可见–protected 对本包可见–默认,不需要修饰符 equals方法 java.util.Objects#equals
1 2 3 public static boolean equals (Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }
java.util.Arrays#equals(long[], long[])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static boolean equals (long [] a, long [] a2) { if (a==a2) return true ; if (a==null || a2==null ) return false ; int length = a.length; if (a2.length != length) return false ; for (int i=0 ; i<length; i++) if (a[i] != a2[i]) return false ; return true ; }
hashcode java.util.Objects#hashCode
1 2 3 public static int hashCode (Object o) { return o != null ? o.hashCode() : 0 ; }
java.util.Arrays#hashCode(long[])
1 2 3 4 5 6 7 8 9 10 11 12 13 public static int hashCode (long a[]) { if (a == null ) return 0 ; int result = 1 ; for (long element : a) { int elementHash = (int )(element ^ (element >>> 32 )); result = 31 * result + elementHash; } return result; }
对象包装器与自动装箱 所有的基本类型都有一个 与之对应的类。Integer对应int。这些类称为包装器(wrapper).
对象包装器不可变,且是final的。
int当做Integer叫做自动装箱(autoboxing)
1 2 3 4 list.add(1 ) 会被编译器编译成 list.add(Integer.valueOf(3 ))
Integer当做int叫做自动拆箱。
装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。
反射 反射库(reflection library)提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序。
Class类 Class类保存了Java对象归属类的信息。
虚拟机为每个类型管理一个Class对象。所以,只有是这个类的Class对象,都是同一个。如何获得这个Class呢?
1 2 3 4 5 6 7 8 9 10 11 12 //通过类名获取 Class clazz = Employee.class; Employee employee = new Employee(); //通过实例获取 Class<? extends Employee> aClass = employee.getClass(); String name = aClass.getName(); assertEquals("com.test.java.clazz.polimophic.entity.Employee", name); //通过名字加载 Class<?> fromName = Class.forName("com.test.java.clazz.polimophic.entity.Employee");
另外,数组的class对象有点特殊。
1 2 3 4 5 String doubleArrayName = Double[].class.getName(); assertEquals("[Ljava.lang.Double;" , doubleArrayName); String intArrayName = int [].class.getName(); assertEquals("[I" , intArrayName);
那么,我拿到Class对象如何确定是不是我需要的呢,用equals比较吗?因为虚拟机为每个类管理一个Class对象,所以可以用==。
对于上述三种方式获得的Class对象
1 2 3 assertTrue(clazz == aClass); assertEquals(clazz, aClass); assertEquals(clazz, fromName);
Class.forName(“xxx.xx.xxx”)会抛出一个检查性异常,如果找不到class会报ClassNotFoundException.
利用反射分析类的能力 在java.lang.reflect包中有三个类Field、Method和Constructor分别用户描述类的字段、方法和构造器。这三个类都有一个getName方法,返回名称。
Field类有个getType方法,返回描述字段所属的Class对象。
Method和Constructor类有能够报告参数类型的方法,Method类还有一个可以报告返回类型的方法。
这三个类还有一个叫做getModifiers的方法,它将返回一个整型数值,用不同的位开关描述public和static这样的修饰符使用情况。总之Modifiers提供了修饰符的判断方法。
Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public字段、方法和构造器组,其中包括超类的共有成员。
Class类的getDeclareFields、getDeclareMethods和getDeclareConstructors方法将分别返回类中声明的全部字段、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。
获取Class name, 修饰符,父类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void printClazz () { Class clazz = Employee.class; Class superclass = clazz.getSuperclass(); String modifiers = Modifier.toString(clazz.getModifiers()); if (modifiers.length()>0 ){ System.out.print(modifiers + " " ); } System.out.println("class " + clazz.getName()); if (superclass!=null && superclass != Object.class){ System.out.print(" extends " + superclass.getName()); } }
打印:
1 public class com.test.java.clazz.polimophic.entity.Employee
获取构造器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Test public void prinConstructor () { StringBuilder sb = new StringBuilder(); Class clazz = Manager.class; Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); for (Constructor declaredConstructor : declaredConstructors) { String name = declaredConstructor.getName(); String modifiers = Modifier.toString(clazz.getModifiers()); if (modifiers.length()>0 ){ sb.append(modifiers).append(" " ); } sb.append(name).append("(" ); Class[] parameterTypes = declaredConstructor.getParameterTypes(); for (int i = 0 ; i < parameterTypes.length; i++) { if (i>0 ){ sb.append(", " ); } sb.append(parameterTypes[i].getName()); } sb.append(");" ); } assertEquals("public com.test.java.clazz.polimophic.entity.Manager(java.lang.String, int, int);" , sb.toString()); }
打印声明的方法 只打印自己的声明的方法,而不包含父类的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Test public void printMethod () { StringBuilder sb = new StringBuilder(); Class clazz = Manager.class; Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { Class<?> returnType = method.getReturnType(); String name = method.getName(); System.out.print(" " ); String modifiers = Modifier.toString(method.getModifiers()); if (modifiers.length()>0 ){ System.out.print(modifiers + " " ); } System.out.print(returnType.getName() + " " + name + "(" ); Parameter[] parameters = method.getParameters(); for (int i = 0 ; i < parameters.length; i++) { if (i>0 ){ System.out.print(", " ); } System.out.print(parameters[i].getName()); } System.out.print(");\n" ); } }
打印声明的字段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void printFields () { Class clazz = Manager.class; Field[] declaredFields = clazz.getDeclaredFields(); for (Field declaredField : declaredFields) { Class<?> type = declaredField.getType(); String name = declaredField.getName(); System.out.print(" " ); String modifiers = Modifier.toString(declaredField.getModifiers()); if (modifiers.length() > 0 ) { System.out.print(modifiers + " " ); } System.out.println(type.getName() + " " + name + ";" ); } }
获取某个字段的value Field提供了get方法,来获取字段value。
1 2 3 4 5 6 7 8 9 10 @Test public void getFieldVal () throws NoSuchFieldException, IllegalAccessException { Manager manager = new Manager("a" , 1 , 100 ); Class<? extends Manager> clazz = manager.getClass(); Field bonus = clazz.getDeclaredField("bonus" ); bonus.setAccessible(true ); Object bonusVal = bonus.get(manager); System.out.println((int )bonusVal); System.out.println(bonus.getInt(manager)); }
有几个需要注意的地方。
clazz.getDeclaredField(“bonus”); 注意参数的内容要和filed一致 由于该字段是private的,不能直接获取,需要设置访问权限,强制获取,bonus.setAccessible(true); Field.get(instance)这个方法返回Object对象,可以强转,也可以使用其他api、 修改某个字段的value 能读就能写
1 2 3 4 5 6 7 8 9 10 @Test public void writeFiledVal () throws NoSuchFieldException, IllegalAccessException { Manager manager = new Manager("a" , 1 , 100 ); Class<? extends Manager> clazz = manager.getClass(); Field bonus = clazz.getDeclaredField("bonus" ); bonus.setAccessible(true ); bonus.set(manager, 1000 ); assertEquals(1000 , manager.getBonus()); }
利用反射创建一个对象 前面获取到constructor之后就可以使用newInstance方法来创建新对象了。
1 2 3 4 5 6 7 8 @Test public void newInstanceTest () throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException { Class clazz = Manager.class; Constructor constructor = clazz.getConstructor(String.class, int .class, int .class); Manager instance = (Manager) constructor.newInstance("a" , 1 , 1 ); assertEquals(1 , instance.getBonus()); }
需要注意的是基本类型的class是int.class
,而不是Integer.class
. `
利用反射创建数组 数组和普通对象有所不同。下面演示通过反射拷贝数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private Object goodCopyOf (Object a, int newLength) { Class cl = a.getClass(); if (!cl.isArray()) { return null ; } int length = Array.getLength(a); Object newArray = Array.newInstance(cl.getComponentType(), newLength); System.arraycopy(a, 0 , newArray, 0 , Math.min(length, newLength)); return newArray; } @Test public void newArray () { int [] a = {1 ,2 ,3 }; int [] a2 =(int []) goodCopyOf(a, 2 ); assertEquals(1 , a2[0 ]); assertEquals(2 , a2[1 ]); String[] str = {"a" ,"b" ,"c" }; String[] str2 = (String[]) goodCopyOf(str, 2 ); assertEquals("a" , str2[0 ]); assertEquals("b" , str2[1 ]); }
反射执行instance的某个方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void invokeMethod () throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Manager manager = new Manager("a" , 1 , 100 ); Class<? extends Manager> clazz = manager.getClass(); Method method = clazz.getDeclaredMethod("getBonus" ); int rs = (int ) method.invoke(manager); assertEquals(100 , rs); Method setBonus = clazz.getDeclaredMethod("setBonus" , int .class); Object invoke = setBonus.invoke(manager, 0 ); assertNull(invoke); assertEquals(0 , manager.getBonus()); }
继承的设计技巧 将公共操作和字段放在超类 不要使用受保护的字段,非必须要,不要使用protected,而推荐用private 使用继承实现is-a
的关系,不是这样关系的类不应该使用继承 除非所有继承的方法都有意义,否则不要使用继承 在覆盖方法时,不要改变预期的行为 使用多态,而不是类型信息 来源