本次学习面向对象设计的另外一个基本概念:继承(inheritance)。这是Java程序设计中的一项核心技术。另外,还要学习反射(reflection)的概念。

  1. public class Manager extends Employee{
  2. //...
  3. }

关键字extends表明正在构造的新类派生于一个已存在的类。已存在的类称为超类(superclass)基类(base class)父类(parent class); 新类称为子类(sbclass)派生类(derived class)孩子类(childe class).

子类继承父类的字段和方法,但有些方法子类想要修改,可以使用覆盖(override)

  1. public class Employee {
  2. private String name;
  3. private int salary;
  4. public String getName() {
  5. return name;
  6. }
  7. public void setName(String name) {
  8. this.name = name;
  9. }
  10. public int getSalary() {
  11. return salary;
  12. }
  13. public void setSalary(int salary) {
  14. this.salary = salary;
  15. }
  16. }
  17. public class Manager extends Employee {
  18. private int bonus;
  19. public int getBonus() {
  20. return bonus;
  21. }
  22. public void setBonus(int bonus) {
  23. this.bonus = bonus;
  24. }
  25. @Override
  26. public String getName() {
  27. return "manager: " + super.getName();
  28. }
  29. @Override
  30. public int getSalary() {
  31. return super.getSalary() + bonus;
  32. }
  33. }
  • 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的主要目的是:确保他们不会在子类中改变语义。

只能在继承层次内进行类型转换。

在将超类转换成子类之前,应该使用instanceof进行检查。

用abstract修饰的类是抽象类。用abstract修饰的方法是抽象方法。

抽象类不能实例化。抽象方法没有方法体。

  1. 仅本类可见–private
  2. 所有类可见–public
  3. 对本包和所有子类可见–protected
  4. 对本包可见–默认,不需要修饰符

java.util.Objects#equals

  1. public static boolean equals(Object a, Object b) {
  2. return (a == b) || (a != null && a.equals(b));
  3. }

java.util.Arrays#equals(long[], long[])

  1. public static boolean equals(long[] a, long[] a2) {
  2. if (a==a2)
  3. return true;
  4. if (a==null || a2==null)
  5. return false;
  6. int length = a.length;
  7. if (a2.length != length)
  8. return false;
  9. for (int i=0; i<length; i++)
  10. if (a[i] != a2[i])
  11. return false;
  12. return true;
  13. }

java.util.Objects#hashCode

  1. public static int hashCode(Object o) {
  2. return o != null ? o.hashCode() : 0;
  3. }

java.util.Arrays#hashCode(long[])

  1. public static int hashCode(long a[]) {
  2. if (a == null)
  3. return 0;
  4. int result = 1;
  5. for (long element : a) {
  6. int elementHash = (int)(element ^ (element >>> 32));
  7. result = 31 * result + elementHash;
  8. }
  9. return result;
  10. }

所有的基本类型都有一个 与之对应的类。Integer对应int。这些类称为包装器(wrapper).

对象包装器不可变,且是final的。

int当做Integer叫做自动装箱(autoboxing)

  1. list.add(1) 会被编译器编译成
  2. list.add(Integer.valueOf(3))

Integer当做int叫做自动拆箱。

装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。

反射库(reflection library)提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序。

Class类保存了Java对象归属类的信息。

虚拟机为每个类型管理一个Class对象。所以,只有是这个类的Class对象,都是同一个。如何获得这个Class呢?

  1. //通过类名获取
  2. Class clazz = Employee.class;
  3. Employee employee = new Employee();
  4. //通过实例获取
  5. Class<? extends Employee> aClass = employee.getClass();
  6. String name = aClass.getName();
  7. assertEquals("com.test.java.clazz.polimophic.entity.Employee", name);
  8. //通过名字加载
  9. Class<?> fromName = Class.forName("com.test.java.clazz.polimophic.entity.Employee");

另外,数组的class对象有点特殊。

  1. String doubleArrayName = Double[].class.getName();
  2. assertEquals("[Ljava.lang.Double;", doubleArrayName);
  3. String intArrayName = int[].class.getName();
  4. assertEquals("[I", intArrayName);

那么,我拿到Class对象如何确定是不是我需要的呢,用equals比较吗?因为虚拟机为每个类管理一个Class对象,所以可以用==。

对于上述三种方式获得的Class对象

  1. assertTrue(clazz == aClass);
  2. assertEquals(clazz, aClass);
  3. 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方法将分别返回类中声明的全部字段、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。

  1. public void printClazz() {
  2. Class clazz = Employee.class;
  3. Class superclass = clazz.getSuperclass();
  4. String modifiers = Modifier.toString(clazz.getModifiers());
  5. if (modifiers.length()>0){
  6. System.out.print(modifiers + " ");
  7. }
  8. System.out.println("class " + clazz.getName());
  9. if (superclass!=null && superclass != Object.class){
  10. System.out.print(" extends " + superclass.getName());
  11. }
  12. }

打印:

  1. public class com.test.java.clazz.polimophic.entity.Employee
  1. @Test
  2. public void prinConstructor() {
  3. StringBuilder sb = new StringBuilder();
  4. Class clazz = Manager.class;
  5. Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
  6. for (Constructor declaredConstructor : declaredConstructors) {
  7. String name = declaredConstructor.getName();
  8. String modifiers = Modifier.toString(clazz.getModifiers());
  9. if (modifiers.length()>0){
  10. sb.append(modifiers).append(" ");
  11. }
  12. sb.append(name).append("(");
  13. //打印参数
  14. Class[] parameterTypes = declaredConstructor.getParameterTypes();
  15. for (int i = 0; i < parameterTypes.length; i++) {
  16. if (i>0){
  17. sb.append(", ");
  18. }
  19. sb.append(parameterTypes[i].getName());
  20. }
  21. sb.append(");");
  22. }
  23. assertEquals("public com.test.java.clazz.polimophic.entity.Manager(java.lang.String, int, int);", sb.toString());
  24. }

只打印自己的声明的方法,而不包含父类的。

  1. /**
  2. * public java.lang.String getName();
  3. * public int getSalary();
  4. * public void setBonus(arg0);
  5. * public int getBonus();
  6. */
  7. @Test
  8. public void printMethod() {
  9. StringBuilder sb = new StringBuilder();
  10. Class clazz = Manager.class;
  11. Method[] methods = clazz.getDeclaredMethods();
  12. for (Method method : methods) {
  13. Class<?> returnType = method.getReturnType();
  14. String name = method.getName();
  15. System.out.print(" ");
  16. String modifiers = Modifier.toString(method.getModifiers());
  17. if (modifiers.length()>0){
  18. System.out.print(modifiers + " ");
  19. }
  20. System.out.print(returnType.getName() + " " + name + "(");
  21. Parameter[] parameters = method.getParameters();
  22. for (int i = 0; i < parameters.length; i++) {
  23. if (i>0){
  24. System.out.print(", ");
  25. }
  26. System.out.print(parameters[i].getName());
  27. }
  28. System.out.print(");\n");
  29. }
  30. }
  1. /**
  2. * private int bonus;
  3. */
  4. @Test
  5. public void printFields() {
  6. Class clazz = Manager.class;
  7. Field[] declaredFields = clazz.getDeclaredFields();
  8. for (Field declaredField : declaredFields) {
  9. Class<?> type = declaredField.getType();
  10. String name = declaredField.getName();
  11. System.out.print(" ");
  12. String modifiers = Modifier.toString(declaredField.getModifiers());
  13. if (modifiers.length() > 0) {
  14. System.out.print(modifiers + " ");
  15. }
  16. System.out.println(type.getName() + " " + name + ";");
  17. }
  18. }

Field提供了get方法,来获取字段value。

  1. @Test
  2. public void getFieldVal() throws NoSuchFieldException, IllegalAccessException {
  3. Manager manager = new Manager("a", 1, 100);
  4. Class<? extends Manager> clazz = manager.getClass();
  5. Field bonus = clazz.getDeclaredField("bonus");
  6. bonus.setAccessible(true);
  7. Object bonusVal = bonus.get(manager);
  8. System.out.println((int)bonusVal);
  9. System.out.println(bonus.getInt(manager));
  10. }

有几个需要注意的地方。

  • clazz.getDeclaredField(“bonus”); 注意参数的内容要和filed一致
  • 由于该字段是private的,不能直接获取,需要设置访问权限,强制获取,bonus.setAccessible(true);
  • Field.get(instance)这个方法返回Object对象,可以强转,也可以使用其他api、

能读就能写

  1. @Test
  2. public void writeFiledVal() throws NoSuchFieldException, IllegalAccessException {
  3. Manager manager = new Manager("a", 1, 100);
  4. Class<? extends Manager> clazz = manager.getClass();
  5. Field bonus = clazz.getDeclaredField("bonus");
  6. bonus.setAccessible(true);
  7. bonus.set(manager, 1000);
  8. assertEquals(1000, manager.getBonus());
  9. }

前面获取到constructor之后就可以使用newInstance方法来创建新对象了。

  1. @Test
  2. public void newInstanceTest()
  3. throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
  4. Class clazz = Manager.class;
  5. Constructor constructor = clazz.getConstructor(String.class, int.class, int.class);
  6. Manager instance = (Manager) constructor.newInstance("a", 1, 1);
  7. assertEquals(1, instance.getBonus());
  8. }

需要注意的是基本类型的class是int.class,而不是Integer.class.
`

数组和普通对象有所不同。下面演示通过反射拷贝数组。

  1. private Object goodCopyOf(Object a, int newLength) {
  2. Class cl = a.getClass();
  3. if (!cl.isArray()) {
  4. return null;
  5. }
  6. int length = Array.getLength(a);
  7. Object newArray = Array.newInstance(cl.getComponentType(), newLength);
  8. System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
  9. return newArray;
  10. }
  11. @Test
  12. public void newArray() {
  13. int[] a = {1,2,3};
  14. int[] a2 =(int[]) goodCopyOf(a, 2);
  15. assertEquals(1, a2[0]);
  16. assertEquals(2, a2[1]);
  17. String[] str = {"a","b","c"};
  18. String[] str2 = (String[]) goodCopyOf(str, 2);
  19. assertEquals("a", str2[0]);
  20. assertEquals("b", str2[1]);
  21. }
  1. @Test
  2. public void invokeMethod()
  3. throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
  4. Manager manager = new Manager("a", 1, 100);
  5. Class<? extends Manager> clazz = manager.getClass();
  6. Method method = clazz.getDeclaredMethod("getBonus");
  7. int rs = (int) method.invoke(manager);
  8. assertEquals(100, rs);
  9. Method setBonus = clazz.getDeclaredMethod("setBonus", int.class);
  10. Object invoke = setBonus.invoke(manager, 0);
  11. assertNull(invoke);
  12. assertEquals(0, manager.getBonus());
  13. }
  • 将公共操作和字段放在超类
  • 不要使用受保护的字段,非必须要,不要使用protected,而推荐用private
  • 使用继承实现is-a的关系,不是这样关系的类不应该使用继承
  • 除非所有继承的方法都有意义,否则不要使用继承
  • 在覆盖方法时,不要改变预期的行为
  • 使用多态,而不是类型信息
  • Java核心技术 卷一 原书第10版

版权声明:本文为woshimrf原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/woshimrf/p/java-class-inheritance.html