【阅读笔记】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 继承的设计技巧(略)

原文地址:https://www.cnblogs.com/caophoenix/p/12498787.html