【Java核心计算 基础知识(第9版)】第5章 继承

本章要点
- 类、超类和子类
- Object:所有类的超类
- 泛型数组列表
- 对象包装器和自动装箱
- 参数数量可变的方法
- 枚举类
- 反射
- 继承设计的技巧


5.1 类、超类和子类

  • 关键字extends表明正在构造的新类派生于一个已存在的类。已存在的类称为超类(superclass)、基类(base class)、父类(parent class);新类称为子类(subclass)、派生类(derived class)、孩子类(child class)。
  • 设计类时,通用方法声明在超类中,子类特有的方法声明在子类中。
  • 对于超类中不适用于子类的方法,子类可以将其覆盖(override)。
  • 如上一篇博文所指出的:Java Language Specification(JavaSE 8)-8.5. Member Type Declarations明确指出,超类的私有成员不被继承
  • super关键字可指示调用超类(超类,而不是父类对象)成员和构造器。
  • 子类构造器必须首先调用超类构造器。如果子类构造器没有显式调用超类构造器,则将自动调用超类默认(无参)的构造器。注意此时如果超类没有无参构造器,编译器将报错。

5.1.1 继承层次

  • 由一个公共超类派生出来的所有类的集合被称为继承层次(inheritance hierarchy)。在继承层次中,从某个特定的类到其祖先类的路径被称为该类的继承链(inheritance chain)。

5.1.2 多态

  • 置换法则:程序中出现超类对象的任何地方都可以用子类对象置换。即,子类的每个对象也是超类的对象。
  • 多态:父类变量既可以引用父类对象,也可以引用任一子类对象(父类引用指向子类对象)。
  • 注意:由于多态的存在,数组是类型不安全的,需要时刻牢记创建时的元素类型。
    public static void main(String[] args) {
        Integer[] irr = new Integer[10];
        Object[] orr = irr; 
        orr[0] = new Object();// java.lang.ArrayStoreException: java.lang.Object
    }

5.1.3 动态绑定

对象方法的调用过程
1. 编译器查看对象的声明类型和方法名。编译器会获取对象内所有可访问的同名方法
2. 编译器查看调用方法时提供的参数列表。如果存在一个与提供的参数类型完全匹配,就选择这个方法,这个过程被称为重载解析(overriding resolution)。如果参数类型存在父子关系,子类型比父类型更匹配。如果都不匹配,或匹配不止一个,则报错。
3. 如果是private,static,final方法或者构造器,那么编译器可以准确的知道应该调用哪个方法,这种方式称为静态绑定(static binding)。其他方法的调用依赖于隐式参数(this)的运行时类型,称为动态绑定(dynamic binding)。
4. 动态绑定的调用,虚拟机在程序运行时确定与this的实际类型最匹配的方法。虚拟机会预先为每个类创建一个方法表,列明所有的方法签名和实际调用的方法。如果方法通过super调用,编译器会通过超类进行确定。

动态绑定的一个重要特性:当为方法的调用增加新的可能性时(例如,提供一个新的实现),无需修改、也无需重新编译调用的代码。

5.1.4 阻止继承:final类和方法

  • 被final修饰的类不可以被继承。
  • 被final修饰的方法不可以被覆盖。
  • 被final修饰的变量只能赋值一次。

5.1.5 强制类型转换

  • 将一个类型强制转换为另一个类型的过程被称为强制类型转换。
  • (Type) object
  • 进行类型转换的唯一原因是:在暂时忽视对象的实际类型之后(向上转型),使用对象的全部功能(强制向下转型)。
  • 转换失败:Cannot cast from ** to **(继承层次外,编译期),ClassCastException(继承层次内,运行期)。
  • 只能在继承层次内进行类型转换。
  • 在将超类转换为子类之前,应该使用instanceof进行检查。

5.1.6 抽象类

  • 用abstract修饰的方法为抽象方法,用abstract修饰的类为抽象类。
  • 抽象类不一定有抽象方法,有抽象方法的类一定是抽象类或接口。
  • 抽象类不能实例化。(可以声明抽象类型的变量引用其实现类对象。)
  • 抽象类的子类必须实现抽象类内所有的抽象方法,或者声明为抽象类。
  • abstract不可以和static,final,private修饰同一个方法,因无意义。

5.2 Object:所有类的超类

  • Object类是Java中所有类的最终超类,如果没有明确声明超类,则Object类是该类的直接超类。
  • 基本类型不是Object的子类,基本类型字面值不是对象。

5.2.1 equals方法

  • equals()用于检测一个对象是否等于另一个对象。
  • 在Object类中,引用相同(即引用了同一个对象)返回true,否则返回false。
  • 在定义类时,通常重写equals(),状态相同(域的值相等)的两个对象即相等。

5.2.2 相等测试与继承

  • Java语言规范要求equals()具有一下特性:

    • 自反性:对于任何非空引用x,x.equals(x)应该返回true。
    • 对称性:对于任何引用x和y,当且仅当x.equals(y)返回true时,y.equals(x)返回true。
    • 传递性:对于任何引用x、y和z,当x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)返回true。
    • 一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果。
    • 对于任意非空引用x,x.equals(null)应该返回false
  • 建议:在比较x与y是否属于同一个类时,如果equals的语义在每个子类中都一样(父类中将equals()声明为final),则用instanceof判断;如果equals的语义在每个子类中可能不同,则用getClass()判断。

    • 这是因为,sub instanceof SuperType返回true,super instanceof SubType返回false,违反了对称性要求

5.2.3 hasCode方法

  • 散列码(hash code)是由对象导出的一个无规律的整型值。不同对象的散列码一般不相等。
  • Object类中,hashCode()返回对象的地址值。
  • 如果重写equals(),一般同时重写hasCode(),以便将对象的状态反映到散列码中。在重写的equals()中,先比较散列码。
  • equals()与hashCode()必须一致:equals()返回true的两个对象要求返回相等的散列码。但具有相等的散列码的两个对象equals()不一定返回true。

5.2.4 toString方法

  • toString()返回表示对象值的字符串
  • Object类中,toString()返回对象的字符串格式为”类名@十六进制散列码”
  • 一般重写toString()方法,返回”类名[field=value,field=value…]”格式的字符串。

5.3 泛型数组列表

  • ArrayList类

5.4 对象包装器与自动装箱

  • 每个基本类型都有一个与之对应的类,称为它的包装器(wrapper)。Byte,Short,Integer,Long,Float,Double,Character,Boolean,Void.
  • Number类是Byte,Short,Integer,Long,Float,Double的公共超类。
  • 对象包装器类是不可变的,即一旦构造类包装器对象,就不允许更改包装纸其中的值。
  • 当有需要时,基本类型可以自动装箱(autoboxing)为包装器,包装器也可以自动拆箱为基本类型。
  • 介于-128~127之间的值会被包装到固定的对象中
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2);   // true

Integer l1 = new Integer(127);
Integer l2 = new Integer(127);
System.out.println(l1 == l2);   // false
  • 自动装箱和自动拆箱由编译器实现,而不是由虚拟机实现。

5.5 参数数量可变的方法

  • 可以用可变的参数数量调用的方法
  • (Type1 name, Type1 name,Type3… names)中,…表示这个方法可以接收任意数量Type3类型的参数。
  • Type3…实际上是一个Type3[]数组,因此也可以直接接收一个数组对象(前提是该可变参数位于参数列表的末尾)。

5.6 枚举类

  • 声明了有限个实例的类。
    public static void main(String[] args) {
        System.out.println(Size.SMALL); // SMALL
        System.out.println(Size.MEDIUM);    // MEDIUM
        System.out.println(Size.LARGE); // LAGRE

        System.out.println(Size.SMALL.getValue());  // 1
        System.out.println(Size.MEDIUM.getValue()); // 2
        System.out.println(Size.LARGE.getValue());  // 3

        System.out.println(Enum.valueOf(Size.class, "SMALL"));  // SMALL
        System.out.println(Enum.valueOf(Size.class, "MEDIUM")); // MEDIUM
        System.out.println(Enum.valueOf(Size.class, "LARGE"));  // LAGRE

        System.out.println(Size.SMALL.ordinal());   // 0
        System.out.println(Size.MEDIUM.ordinal());  // 1
        System.out.println(Size.LARGE.ordinal());   // 2
    }

    enum Size{
        SMALL(1),MEDIUM(2),LARGE(3);

        private int value;
        private Size(int value){
            this.value = value;
        }

        public int getValue(){
            return value;
        }
    }

5.7 反射

5.7.1 Class类

  • 在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。
  • 可以通过Class类访问这些信息。有三种方法可以获取Class类型实例。
    • Object类的方法getClass()(通过对象获取);
    • Object类的静态方法forName()(通过类名获取);
    • 任意类型T,T.class(通过类型获取);
  • 一个Class对象表示的是一个类型,而不是一个类。如int.class,void.class。
  • Class类的newInstance()方法调用默认的无参构造器构造一个对象。

5.7.3 利用反射分析类的能力

  • java.lang.reflect包下的三个类:Field,Method,Constructor。
  • Class类的getFields(),getMethods(),getConstructor()分别返回对象运行时类型的public域,方法,构造器,包括父类的public域和方法,不包括父类的public构造器。
  • Class类的getDeclaredFields(),getDeclaredMethods(),getDeclaredConstructors()分别返回全部域,方法,构造器,但不包括父类的域,方法和构造器
  • Modifier类(getModifiers())和Parameter类(getParameterTypes())。
  • getExceptionTypes(),getReturnType()。

5.7.4 在运行时使用反射分析对象

  • AccessibleObjet类的setAccessible()方法。
  • Field类的get()和set()方法。
    • get()和set()方法返回和接收基本类型是通过包装器实现的,如果要直接返回/接收基本类型,应调用getInt()等方法。
  • Method类的invoke()方法。
  • Constructor类的newInstance()方法。

5.7.5 使用反射编写泛型数组代码

  • Class类的getComponentType()方法:返回数组元素类型,当对象不是数组时返回null。
  • java.lang.reflect.Array类的newInstance()方法。
  • 数组是对象,基本类型不是对象;所基本类型数组可以转换为Object,但不能转换为Object[]。

5.8 继承设计的技巧

  • 将公共操作和域放在超类
  • 不要使用protected域。
  • 使用继承实现”is-a”关系。
  • 除非所有继承的方法都有意义,否则不要使用继承。
  • 在重写方法时,不要改变预期的行为。
  • 使用多态,而非类型信息。
  • 不要过多地使用反射。
posted @ 2017-08-28 23:07  李崎荷  阅读(115)  评论(0编辑  收藏  举报