zoukankan      html  css  js  c++  java
  • 【阅读笔记】Java核心技术卷一 #3.Chapter5

    5 继承

    5.1 类、超类和子类

    5.1.1 定义子类

    超类(superclass)和子类(subclass),
    基类(base class)和派生类(derived class),
    父类(parent class)和孩子类(child class)

    在 Java 中,所有的继承都是公有继承。

    5.1.2 覆盖方法(略)

    5.1.3 子类构造器

    this是当前对象的引用
    super不是一个对象的引用,不能将super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。

    我们可以通过 super 实现对超类构造器的调用。使用 super 调用构造器的语句必须是子类构造器的第一条语句。
    如果子类的构造器没有显式地调用超类的构造器,则将自动地调用超类默认(没有参数)的构造器。如果超类没有不带参数的构造器,并且在子类的构造器中又没有显式地调用超类的其他构造器,则 Java 编译器将报告错误。

    5.1.4 继承层次

    继承层次继承链

    Java 不支持多继承。

    5.1.5 多态

    一个对象变量可以指示多种实际类型的现象被称为多态(polymorphism)。在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamic binding)
    在 Java 中,不需要将方法声明为虚函数(C++)。动态绑定是默认的处理方式。如果不希望让一个方法具有虚拟特征,可以将它标记为 final 。

    5.1.6 理解方法调用

    覆盖方法时,一定要保证返回类型的兼容性。允许子类将覆盖方法的返回类型定义为原返回类型的子类型。(协变返回类型)
    重载可以有不同的返回类型。
    在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。

    虚拟机预先为每个类创建了一个方法表(method table),其中列出了所有方法的签名和实际调用的方法。

    private 方法、static 方法、final 方法或者构造器,是静态绑定

    详见P155

    5.1.7 阻止继承:final类和方法

    final 类不允许extends,final 方法不允许Override;
    final 类中的所有方法自动称为 final 方法,而域不会。

    如果方法很简短、被频繁调用且没有被覆盖,那么即时编译器就会将这个方法进行内联处理。
    例如,内联调用 e.getName() 将被替换为访问 e.name域。

    如果虚拟机加载了另外一个子类,而在这个子类中包含了对内联方法的覆盖,那么优化器将取消对覆盖方法的内联。这个过程很慢,不过很少发生。

    5.1.8 强制类型转换

    如果试图在继承链上进行向下的类型转换,并且“谎报”有关对象包含的内容,运行这个程序时,Java runtime system 将报告这个错误,并产生一个 ClassCastException 异常。

    Manager boss = (Manager)staff[1]; // Error
    // 在进行类型转换之前,先查看一下是否能够成功地转换
    if (staff[1] instanceof Manager)
    {
        boss = (Manager)staff[1];
    }
    // 将会产生编译错误,这是因为 String 不是 Employee 的子类
    String c = (String)staff[1];
    
    • 只能在继承层次内进行类型转换。
    • 在将超类转换成子类之前,应该使用 instanceof 进行检查

    5.1.9 抽象类

    • 包含一个或多个抽象方法的类本身必须被声明为抽象的;
    • 除了抽象方法之外,抽象类还可以包含具体数据和具体方法;
    • 类即使不含抽象方法,也可以将类声明为抽象类。
    • 抽象类不能被实例化。
    • 可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。

    5.1.10 受保护访问

    人们希望超类中的某些方法允许被子类访问,或允许子类的方法访问超类的某个域。
    为此,需要将这些方法或域声明为 protected。

    例如,如果将超类 Employee 中的 hireDay 声明为proteced,而不是私有的,Manager 中的方法就可以直接地访问它。
    不过,Manager 类中的方法只能够访问 Manager 对象中的 hireDay 域,而不能访问其他 Employee 对象中的这个域 。

    事实上,Java 中的受保护部分对所有子类及同一个包中的所有其他类都可见。

    包访问级别(package-private):没有访问修饰符的类的访问级别是包级,其public方法也会变成包级。

    Java 用于控制可见性的 4 个访问修饰符:

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

    5.2 Object:所有类的超类

    在 Java 中,只有基本类型不是对象。
    所有的数组类型,不管是对象数组还是基本类型的数组都扩展了 Object 类。

    5.2.1 equals方法

    在 Object 类中,这个方法将判断两个对象是否具有相同的引用。

    如果两个参数都为 null,Objects.equals(a, b) 调用将返回 true;
    如果其中一个参数为 null,则返回 false;
    否则,如果两个参数都不为 null,则调用 a.equals(b)。

    在子类中定义 equals 方法时,首先调用超类的 equals。如果检测失败,对象就不可能相等。
    如果超类中的域都相等,就需要比较子类中的实例域。

    5.2.2 相等测试与继承

    getClass() or instanceOf ?

    1. 显式参数命名为 otherObject , 稍后需要将它转换成另一个叫做 other 的变量 。

    2. 检测 this 与 otherObject 是否引用同一个对象:

      if (this == otherObject) return true;

    3. 检测 otherObject 是否为 null , 如果为 null , 返回 false:

      if (otherObject == null) return false;

    4. 比较 this 与 otherObject 是否属于同一个类

      1. 如果 equals 的语义在每个子类中有所改变,就使用 getClass 检测:

        if (getClass() != otherObject.getClass()) return false;

      2. 如果所有的子类都拥有统一的语义,就使用 instanceof 检测:

        if (!(otherObject instanceof ClassName)) return false;

    5. 将 otherObject 转换为相应的类类型变量:

      ClassName other = (ClassName) otherObject;

    6. 现在开始对所有需要比较的域进行比较了。使用 == 比较基本类型域,使用 equals 比较对象域。
      如果所有的域都匹配,就返回true;否则返回 false。

      return field1 == other.field1
      && Objects.equals(field2, other.field2)
      && . . .;

      如果在子类中重新定义 equals,就要在其中包含调用 super.equals(other)。

    • 对于数组类型的域,可以使用静态的 Arrays.equals 方法检测相应的数组元素是否相等。
    • 覆盖 Object 类中的equals方法时,显示参数类型应该是 Object。否则其结果并没有覆盖 Object 类的 equals 方法,而是定义了一个完全无关的方法。

    5.2.3 hashCode方法

    Object 类中的 hashCode 方法,每个对象都有一个默认的散列码,其值为对象的存储地址。

    如果重新定义 equals 方法,就应该重新定义 hashCode 方法;
    equals 与 hashCode 的定义必须一致:如果 x.equals(y) 返回 true,那么 x.hashCode() 就必须与 y.hashCode() 具有相同的值。

    Objects.hashCode,如果其参数为 null,这个方法会返回 0,否则返回对参数调用 hashCode 的结果;
    需要组合多个散列值时,可以调用Objects.hash并提供多个参数。这个方法会对各个参数调用 Objects.hashCode,并组合这些散列值。

    public int hashCode()
    {
        return 7 * Objects.hashCode(name)
        + 11 * Double.hashCode(salary)
        + 13 * Objects.hashCode(hireDay);
    }
    public int hashCode()
    {
        return Objects.hash(name, salary, hireDay);
    }
    

    可以使用静态方法 Double.hashCode 来避免创建 Double 对象;
    如果存在数组类型的域,那么可以使用静态的 Arrays.hashCode 方法计算一个散列码,这个散列码由数组元素的散列码组成。

    5.2.4 toString方法

    Object 类定义了 toString 方法,用来打印输出对象所属的类名和散列码。

    int[] luckyNumbers = { 2, 3, 5, 7, 11, 13 };
    String s = "" + luckyNumbers; // s = "[I@1a46e30", [I 表示类型为整型数组
    String s = Arrays.toString(luckyNumbers); // s = "[2, 3, 5, 7, 11, 13]"
    

    ① class A extends B;
    ② B.toString(){ getClass().getName(); }
    ③ A.toString(){ super.toString() + "[this is A]"; }
    调用 A.toString 时调用的 “getClass.getName” 会是 A 而不是 B

    5.3 泛型数组列表(略)

    5.4 对象包装器与自动装箱

    对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。
    同时,对象包装器类还是 final,因此不能定义它们的子类 。

    Integer 对象是不可变的:包含在包装器中的内容不会改变。这些包装器类不能用来实现修改数值参数的方法(像 C++ 的引用那样)。

    如果想编写一个修改数值参数值的方法,就需要使用在 org.omg.CORBA 包中定义的持有者(holder)类型,
    包括 IntHolder、 BooleanHolder 等。每个持有者类型都包含一个公有域值,通过它可以访问存储在其中的值。

    public static void triple(IntHolder x)
    {
        x.value = 3 * x.value;
    }
    

    java.lang.Integer

    /// API
    int value();
    static String toString(int i);
    static String toString(int i, int radix);
    static int parseInt(String s);
    static int parseInt(String s, int radix);
    static Integer valueOf(String s);
    static Integer valueOf(String s, int radix);
    

    java.text.NumberFormat

    ///API
    Number parse(String s);
    

    5.5 参数数量可变的方法

    参数里Object[] argsObject... args等价。

    System.out.printf("%d %s", new Object[] { new Integer(n), "widgets" } );
    public static double max(double... values){}
    public static void main(String... args){}
    

    例如调用double m = max(3.1, 40.4, -5);,编译器会传递new double[] { 3.1, 40.4, -5 }进去。

    5.6 枚举类

    public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE };
    /// 添加构造器、方法和域
    public enum Size {
        SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");
    
        private Size(String abbreviation) {
            this.abbreviation = abbreviation;
        }
    
        public String getAbbreviation() {
            return abbreviation;
        }
    
        private String abbreviation;
    }
    

    5.7 反射

    主要是一些类及其API

    java.lang.Class

    /// API
    static Class forName(String className);
    Object newInstance();
    

    java.lang.reflect.Field

    java.lang.reflect.Method

    java.lang.reflect.Constructor

    /// API
    Object newInstance(Object[] args);
    

    java.lang.reflect.Modifier

    • 使用java.lang.reflect.Array来实现通用的数组拷贝
    public static Object goodCopyOf(Object a, int newLength)
    {
        Class cl = a.getClass();
        if (!cl.isArray()) return null;
        Class componentType = cl.getComponentType();
        int length = Array.getLength(a);
        Object newArray = Array.newInstance(componentType, newLength);
        System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
        return newArray;
    }
    
    • 使用java.lang.reflect.Method调用任意方法:
      public Object invoke(Object implicitParameter, Object[] explicitParameters)
      建议仅在必要的时候才使用 Method 对象。
      特别要重申:建议 Java 开发者不要使用 Method 对象的回调功能。使用接口进行回调会使得代码的执行速度更快,更易于维护。

    5.8 继承的设计技巧(略)

  • 相关阅读:
    AJAX异步传输——以php文件传输为例
    js控制json生成菜单——自制菜单(一)
    vs2010中关于HTML控件与服务器控件分别和js函数混合使用的问题
    SQL数据库连接到服务器出错——无法连接到XXX
    PHP错误:Namespace declaration statement has to be the very first statement in the script
    【LeetCode】19. Remove Nth Node From End of List
    【LeetCode】14. Longest Common Prefix
    【LeetCode】38. Count and Say
    【LeetCode】242. Valid Anagram
    【LeetCode】387. First Unique Character in a String
  • 原文地址:https://www.cnblogs.com/caophoenix/p/12498787.html
Copyright © 2011-2022 走看看