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 继承的设计技巧(略)

【阅读笔记】Java核心技术卷一 #3.Chapter5的更多相关文章

  1. 【阅读笔记】Java核心技术卷一 #0

    这是一篇备忘性质的读书笔记,仅记录个人觉得有用的知识点 本文作为一个目录索引,部分章节跳过 吐槽:此书中文翻译有不少地方不太通顺,这种情况我要把英文版对应的部分也读一遍才能明白(说实话,英文里的从句表 ...

  2. java核心技术卷一

    java核心技术卷一 java基础类型 整型 数据类型 字节数 取值范围 int 4 +_2^4*8-1 short 2 +_2^2*8-1 long 8 +_2^8*8-1 byte 1 -128- ...

  3. 对《Java核心技术卷一》读者的一些建议

    <Java核心技术卷一>是唯一可以和<Java编程思想>媲美的一本 Java 入门书.单从技术的角度来看,前者更好一些.但上升到思想层面嘛,自然后者更好,两者的偏重点不同. 思 ...

  4. 读《java核心技术卷一》有感

    过去一个多月了吧.才囫囵吞枣地把这书过了一遍.话说这书也够长的,一共706页.我从来不是个喜欢记录的人,一直以来看什么书都是看完了就扔一边去,可能有时候有那么一点想记录下来的冲动,但算算时间太紧,很多 ...

  5. 【阅读笔记】Java核心技术卷一 #6.Chapter8

    8 泛型程序设计 8.1 为什么要使用泛型程序设计 类型参数(type parameters)(E.T.S...) 通配符类型(wildcard type)(?) 注意这两者用法用处并不同. 8.2 ...

  6. 【阅读笔记】Java核心技术卷一 #5.Chapter7

    7 异常.断言和日志 在 Java 中,如果某个方法不能够采用正常的途径完整它的任务,就可以通过另外一个路径退出方法. 在这种情况下,将会立刻退出,并不返回任何值,而是抛出(throw)一个封装了错误 ...

  7. 【阅读笔记】Java核心技术卷一 #4.Chapter6

    6 接口.lambda 表达式与内部类 6.1 接口 6.1.1 接口概念 接口绝不能含有实例域:但在接口中可以定义常量,被自动设为 public static final 接口中的所有方法自动地属于 ...

  8. 【阅读笔记】Java核心技术卷一 #2.Chapter4

    4 对象和类 4.1 面向对象程序设计概述(略) 4.2 使用预定义类 java.time.LocalDate static LocalDate now(); static LocalDate of( ...

  9. 【阅读笔记】Java核心技术卷一 #1.Chapter3

    3 Java的基本程序设计结构 3.1 一个简单的 Java 应用程序(略) 3.2 注释(略) 3.3 数据类型 8种基本类型 byte,short,int,long float,double ch ...

随机推荐

  1. Golang去除字符串前后空格

    Golang去除字符串前后空格 实现Demo package main import "fmt" func DeletePreAndSufSpace(str string) str ...

  2. Java并发编程中的锁

    synchronized 使用synchronized实现同步有2种方式: 同步方法(静态与非静态) 同步代码块 任何Java对象均可作为锁使用,其中,使用的锁对象有以下3种: 静态同步方法中,锁是当 ...

  3. python用random模块模拟抽奖逻辑(print修改end参数使打印结果不分行)

    import random   #引入random模块,运用random函数list_one=["10081","10082","10083" ...

  4. 详解C++中的多态和虚函数

    一.将子类赋值给父类 在C++中经常会出现数据类型的转换,比如 int-float等,这种转换的前提是编译器知道如何对数据进行取舍.类其实也是一种数据类型,也可以发生数据转换,但是这种转换只有在 子类 ...

  5. Gym 101308D Database 枚举

    大致题意: 给出一张表,n行m列,每一行的列用逗号分隔.判断这个表是否有冗余元素.如果一张表中有两行两列对应的的元素相同,那么这个表就有冗余元素. 分析: 先枚举要排序的列,然后枚举行,如果相邻两行相 ...

  6. Linux基础 -02

    3.4 虚拟机快照技术 什么是快照 3.5 虚拟机克隆技术 什么是克隆 4.Bash Shell快速入门 4.1 什么是Bash Shell bash shell是一个命令解释器,主要负责用户程序与内 ...

  7. centos 8 安装 PostgreSQL-10

    下载 PostgreSQL-10软件包 官网地址:https://www.postgresql.org/ 选择自己的版本 此处已postgresql-10.16-2-linux-x64.run安装为例 ...

  8. 安卓手机改造服务器——基本环境配置(CentOS7 arm32)

    安装好CentOS系统之后,我们需要对环境进行一些基本的配置,让Linux更好用 写在前面 注意:本文章是针对arm32的CentOS7进行配置的,其他系统或不同架构不要尝试. 配置yum镜像源 1. ...

  9. 【重学Java】可变参数

    可变参数 可变参数[应用] 可变参数介绍 可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了 方法的参数类型已经确定,个数不确定,我们可以使用可变参数 可变参数定义格式 修饰符 ...

  10. Linux:Linux操作防火墙命令

    首先查看Linux的防火墙是否关闭 firewall-cmd Linux上新用的防火墙软件,跟iptables差不多的工具. firewall-cmd --state # 显示防火墙状态 system ...