第5章 继承

5.1 类、超类和子类

5.1.1 定义子类

//例:继承 Employye 类来定义 Manger类,使用关键字 extends 表示继承;
public class Manger extends Employye{
}

​ 关键字 extends指示正在构造的新类派生于一个已存在的类。已存在的类称为超类(superclass)、基类(base class)或父类(parent class);新类称为子类(subclass/child class)或派生类(derived class)。

​ Java语言规范指出:“声明为私有类成员不会被这个类的子类继承”。不能扩展记录,且记录也不能扩展其它类。

5.1.2 方法覆盖

​ 超类中有些方法对子类Manager并不一定适用,可以通过一个新的方法来覆盖(override)超类中的方法。

​ 方法覆盖(override):如果在子类中定义一个方法,其名称、返回类型及参数签名正好与父类中某个方法的名称、返回类型及参数签名相匹配,子类方法不能低于超类方法的可见性,那么可以说,子类的方法覆盖了父类的方法。

//例
public class Manger extends Employye{
    public double getSalry(){
        double baseSalary = super.getSalary();
        return baseSalary + this.bonus;
    }
}

​ super与this引用不是一个概念;super不是一个对象的引用,只是一个指示编译器调用超类的特殊关键字。

5.1.3 子类构造器

关键字this:

  • 指示隐式参数的引用
  • 调用该类的其它构造器

关键super:

  • 调用超类的方法
  • 调用超类构造器

在 Java 中,子类继承父类后,其构造器总是会调用父类的构造器:

  • 如果子类构造器中没有显式使用 super(…),Java 编译器会在子类构造器的第一行自动插入 super(),即调用父类的无参构造器。
  • 如果父类没有无参构造器(只有带参数的构造器),则子类构造器中必须显式使用 super(…) 来调用父类的一个带参构造器,并传入相应参数。
  • 一旦在子类构造器中手动调用了父类构造器(如 super(“参数”)),编译器就不会再自动添加默认的 super()。
  • 如果一个构造器 显式地调用了另一个构造器(通过 this(...)super(...)),那么编译器就不会再自动插入 super()

一个对象变量可以指示多种实际类型,称为多态(polymorphism)

在运行时能自动地选择适当的方法,称为动态绑定(dynamic binding)

5.1.4 继承层次结构

​ 继承并不仅限于一个层次。由一个公共超类派生出来的所有类的集合称为继承层次继承(inheritance hierarchy),在层次结构中,从某个特定的类到其祖先的路径称为该类的继承承链(inheritance chain),一个祖先可以有多个子孙链。

在这里插入图片描述

5.1.5多态

​ 在Java程序中设计语言中,对象变量是多态的(polymorphic)。

//例
基类 对象名 = new 派生类();

5.1.6 方法调用

例 x.f(args),隐式参数x声明为类C的一个对象,过程描述:

  • 编译器查看对象的声明类型和方法名。
    • 可能存在多个 f 但参数类型不同的方法。
    • 编译器将会一一列出C类中所有名为f的方法和其超类中所有名为可访问的方法。
    • 编译器一致所有可能调用的候选方法
  • 编译器确定方法中提供的参数类型
    • 如果在所有名为f的方法中存在一个与所提供参数类型完全匹配的方法,就选择此方法。
      • 此过程称为重载解析
    • 如果编译器没有找到与参数类型匹配的方法,或发现经过类型转换后有多个方法匹配,编译器就会报告一个错误。
      • 方法的名字和参数列表称为方法的签名(signature)。
        • 如在子类中定义一个与超类签名相同的方法,那子类这个方法就会覆盖(override)超类中有相同签名的方法。
      • 返回类型不是签名的一部分。
        • 在覆盖一个方法时,需要保证返回类型的兼容性。
        • 允许子类将覆盖方法的返回类型改为原返回的子类型。
  • 如果是private方法、static方法、final方法或者构造器,那么编译器可以准确的知道调用哪个方法,称为静态绑定(static binding)。
    • 如果调用的方法依赖于隐式参数的实际类型,那么必须在运行时使用动态绑定。
  • 程序运行时并采用动态绑定调用方法时,虚拟机必须调用与x所引用对象的实际类型对应的那个方法。
    • 因为调用方法都要完成所有,时间开销大,所以虚拟机预先为每个类计算了一个方法表(method table),列出了所有方法的签名和要调用的实际方法。
      • 虚拟机加载一个类之后可以构建这个方法表,为此要结合它在类文件中找到的方法以及超类的方法表。
        • 如 super.f(param),那么编译器将搜索超类的方法表。
基类 对象名 = new 派生类();

如果 对象名.方法()

  • 首先,虚拟机获取 实际类型的方法表。
  • 虚拟机查找定义 方法()签名的类。
  • 虚拟机调用此方法。

动态绑定:无须修改就可以对程序进行扩展。

5.1.7 阻止继承:final类和方法

​ 阻止定义某个类的子类,可以使用在类定义中使用final修饰符,表名这个类是final类。

​ 不允许扩展的类被称为final类。

​ final类中的所有方法自动地称为 final方法,不包括字段。

​ 枚举和记录重视final,它们不允许扩展。

5.1.8 强制类型转换

​ 将一个类型强制转换为另外一个类型的过程称为强制转换(casting)。

​ 完成对象应用的强制转换,转换语法与数值表达式的强制类型转化类型类似。

​ 进行强制转换的原因:要在暂时忘记对象的实际类型之后使用对象的全部功能。

​ 在Java中,每个对象变量都有一个类型,类型描述了这个变量引用哪种对象以及它能做什么。

  • 将一个值存入一个变量是,编译器将检查你是否承诺过多。
    • 如果将一个子类引用赋给一个超类变量,承诺较少,编译器允许。
    • 如果将一个超类引用赋给一个子类变量,承诺过多,必须强制类型转换,才能通过运行时的检查。
      • 如果在继承链上进行向下的强制类型转换,并且“谎报”对象包含的内容
        • Java运行时系统将注意到承诺不符,并产生 ClassCastExeption异常。
      • 可使用 instanceof操作符进行验证。

5.1.9 instanceof 模式匹配

//例
Fruit fruit = new Apple(); 
if (fruit instanceof Apple) { 
    Apple apple = (Apple) fruit;
}

此写法过于冗余

在Java16中,可以直接在instanceof测试中声明子类变量

Fruit fruit = new Apple(); 
if (fruit instanceof Apple apple) { 
}

​ 在使用instanceof的情况下,需要应用一个子类方法,可以使用 instanceof 的“模式匹配” ,而不是强制类型转换。

5.1.10 受保护访问

本包和所有子类可以访问 -使用关键字 protected

不推荐使用,违反了OOP(面向对象程序设计)提倡的数据封装精神。

5.2 Object:所有类的超类

​ Object类是Java所有类的十足,Java中每个类都扩展了Object。如果没有明确指出超类,那么Object就是这个类的超类。

5.2.1 Object类型的变量

​ Object类型的变量可以引用任何类型的对象。Object类型变量只能用于作为任意值的一个泛型容器。

​ 在Java中,只用基本类型不是对象。

5.2.2 equals方法

​ obje类中的equals方法用于检测一个对象是否等于另一个对象。

​ 记录是一种特殊形式的不可变类,其状态完全由“标准”构造器设计的字段定义。记录会自动定义一个比较字段的equals方法。两个记录实例中相应字段值相等时,这两个记录实例就相等。

5.2.3 equals性质

Java语言规范要求equals方式具有以下性质:

  • 自反性:对于任何非null引用x,x.equals(x)应该返回 true。
  • 对称性:对于任何引用x 和y,当且仅当y.equals(x)返回true是,x,equals(y)也为null
  • 传递性:对于任何引用x、y和z,如果x.equals(y)返回 ture,y.equals(z)返回true,x.equals(z)也应该返回true
  • 一致性:如果 x和y 引用对象没有发生变化,反复强调 x.equals(y)应该返回同样的结果
  • 对于任意非null引用x, x.equals(null)应该返回 false。

5.2.4 hashCode方法

​ 散列码(hash code)是由对象导出的一个整型值。散列码没有规律的。如果x和y是两个不同的对象,哪个x.hashCode() 与 y.hashCode() 基本上相同

​ 如果有数组类型的字段,可以使用静态的Arrays.hashCode方法计算一个散列码,这个散列码有数组元素的散列码组成。

​ record类型会自动提供一个hashCode方法,它会有字段值的散列码得出一个散列码。

5.2.5 toString方法

​ toString方法,返回一个字符串,表示这个对象的值。

​ 只要对象与一个字符串通过操作符 “+” 拼接起来,Java编译就会自动地调用toString方法来获取这个对象的字符串描述。

5.3 泛型数组列表

​ 在Java中可以动态修改数组容量,ArrayList类,它与数组类型,但在添加或删除元素是,它能过自动地调整容量,而不需要为此而外编写代码。

​ ArrayL是一个有类型参数(type parameter)的泛型类型(generic class)。指定数组列表保存的元素对象的类型,需要用一对尖括号将类名追加到ArrayList后面。

5.3.1 声明数组列表

//例:声明一个类型为 Employee的动态数组
ArrayList<Employee> staff = new ArrayList<Employee>();

//右边菱形中可以省略
ArrayList<Employee> staff = new ArrayList<>();

//在Java 10 ,可以使用 var 关键字
var staff = new ArrayList<Employee>();

​ 此语法称为 “菱形”语法,因为空尖括号<>就像一个菱形。可以结合new操作符使用菱形语法。

使用add方法可以将元素添加到数组列表中:

staff.add(value)

​ 数组列表管理着一个内部的对象引用数组。最终,这个数组的空间有可能全部用尽。

  • 如果调用 add 而内部数组已经满了,数组列表就会自动创建一个更大的数组,并将所有对象从较小的数组拷贝到较大的数组中。

size方法返回数组列表包含的实际元素个数。

  • 一旦能确定数组列表的大小将保持恒定,就可以调用 trimToSize 方法。
    • 此方法将内存块的大小调整为保存当前元素数量所需要的存储空间。
    • 垃圾回收器将回收多余的存储空间。
  • 一旦削减了数组列表大小,添加元素就就需要再次移动内存块,十分耗费时间。
    • 所以应当只有确认不会再向数组列表添加任何元素是才调用 trimToSize。

5.3.2 访问数组列表元素

​ ArrayLIst动态数组遍历元素,要用一个复杂的语法访问元素。

​ 使用 set、get 方法修改和访问数据,下标从0开始。

public E get(int index)

 public E set(int index, E element)

5.4对象包装器与自动装箱

每个基本数据类型都对应的一个 包装器(wrapper)。包装器类是不可变的,即一旦构建包装器,就不允许更改在其中的值,包装器类还是 final,不能派生它们的子类。

基本数据类型包装器
intInteger
longLong
floatFloat
doubleDouble
shortShort
byteByte
charCharacter
booleanBoolean
//ArrayList<Integer> 效率远远低于 int[] 数组
var list = new ArrayList<Integer>;

//自动装箱 : 自动转换list.add(Integer.valueOf(3));
list.add(3);

//自动拆箱:自动转换 list.get(0).intValue();
int n = list.get(0);

自动包装(autowrapping)与包装器更一致

自动装箱规范要求 boolean、byte、char(<= 127),介于 -128 和 127 之间的 short 和 int 包装到固定的对象中,

不要依赖包装器对象的同一性。不用 == 比较包装类对象,不要将包装类对象作为锁。

包装类的构造器以弃用,并被完全删除。

装箱和拆箱是编译器做的工作。编译器生成类的字节码时 会插入必要的方法调用,虚拟机执行字节码

5.5 参数个数可变的方法

可以提供参数个数可变的方法(有时,这些方法被成称为 “变参”(varargs)方法)。

//例:
System.out.printf("%d",n);
System.out.printf("%d %s",n,"qwe");


//printf方法定义
public PrintStream printf(String format, Object ... args) {
  return format(format, args);
}

​ 省略号 … 的Java代码的一部分,它表名这个方法可以接受任意数量的对象(除 format 参数以外)。

​ printf方法接收两个参数,一个是格式字符,另一个是 Object[] 数组,其中保存着所有其他参数(如果调用者提供的是整数或者其他基本类型的值,则会把它们自动装箱为对象)。扫描 format字符串,并将第i个格式说明符与args[i]的值匹配。

​ 对于printf的实现者来说,Object…参数类型与Object[] 完全一样。编译器需要转换每个printf调用,将参数打包到一个数组中,并根据需要自动装箱。

 System.out.printf("%d %s",new Object[]{Integer.valueOf(n),"qwe"});

5.6 抽象类

  • 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
  • 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
  • 由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
  • 父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
  • 关键字:abstract
  • 不含抽象方法,也可以类声明abstract
  • 抽象类不能实例化。
    • 可以创建抽象类的对象变量(object variable),但只能引用非抽象子类的对象。

​ 抽象方法相当于子类中实现的具体方法的占位符。

​ 扩展一个抽象类,可以有两种选择:

  • 一种是在子类中保留抽象类中的部分或所有或所有抽象方法仍未定义,这样就必须将子类也标记为抽象类;
  • 一种做法是定义全部方法,子类就不是抽象的。

5.7 枚举类

  • 枚举的构造器总是私有的。可以省略private修饰符,如果声明一个 enum构造器为public 或 protected,会出现语法错误。

  • 枚举类型都是抽象类Enum的子类。继承了这个类的许多方法。

    • toString:返回枚举常量名。
    • valueOf:是toString的逆方法,返回一个包含全部枚举值的数组
    • ordinal:返回枚举常量在enum声明中的位置,位置从 0 开始计数
    • cmpareTo(E other):枚举常量出现在 other之前,则返回一个负数;this==ohter,则返回0;否则,则返回一个整数
  • Enum类有一个类型参数。

5.8 密封类

​ 密封类(Sealed Classes)是java17引入的一个特性,允许更精确地控制那些类可以扩展或实现类或接口。使用密封类,可以声明一个类、接口只能被特定的类继承或实现。

关键字:

  • sealed:用于修饰类或接口,表示它是密封的。
  • permits:指定哪些类可以继承这个密封了或实现密封接口。
  • final、sealed或non-sealed:密封类的直接子类必须用这个三个关键字之一进行修饰
    • final:表示该子类不能被进一步继承
    • sealed:表示子类也是密封的,并且需要继续指定可以继承它的类
    • non-sealed:表示该子类不是密封的,它可以被任何其他类继承,即使它的父类是密封的

5.9 反射

​ 反射库(reflection library)提供了一个工具集,可以用来编写动态操纵Java代码的程序。使用反射,Java可以支持用户页面生成器、对象关系映射器以及很多其他需要动态查询类功能的开发工具。

​ 可反射(reflection):能过分析类的能力的称为。Java反射的API主要为于java.lang.reflect包中

利用反射机制的功能可以用来:

  • 在运行时分析类的能力
  • 在运行时检查对象
  • 实现泛型数组操作代码
  • 利用Method对象

5.9.1 Class类

​ 在程序运行期间,Java运行时系统始终为所有对象维护一个运行时类型标识(runtime type identification)。这个信息会跟踪每个对象所属的类。虚拟机利用运行时类型信息选择要执行的正确方法。

​ Java有个特殊类可以访问这些信息。保存这些信息的类名为 Class。Object类中的getClass() 方法将会返回一个Class类型的实例。

​ Class类提供了类的结构信息以及动态操作类的能力。每个加载到JVM中的类都有一个对应的Class对象,这个对象包含了该类的所有元数据,如类名、父类、接口、构造函数、方法和字段等。可以在运行时动态地获取类的相关信息或操作对象。

获取Class对象

  • 使用 .calss 语法

    Class<String> clazz = String.class;
    
  • 使用 Object 类的 getClass()方法

    String str = "Hello,World!";
    Class<?> clazz = str.getClass();
    
  • 使用 Class.forName()方法

    • 当知道类的完全限定名(fully qualified name)使用
    try {
        Class<?> clazz = Class.forName("java.lang.String");
    } catch (ClassNotFoundException e) {
         e.printStackTrace();
    }
    

主要方法:

  • 获取类的信息
    • getName():返回类的全限定名。
    • getSimpleName():返回类的简短名称,不包含包名。
    • getPackage():返回该类所属的包
    • getSuperclass():返回表示此 class 所表示的实体(类、接口、基本类型或void)的超类的 Class 对象
    • getInterfaces():确定此对象所表示的类或接口实现的接口
    • isInterface():判断是否为接口
    • isEnum():判断是否为枚举
    • isArray():判断是否为数组
    • isPrimitive():判断是否为基础类型
  • 创建实例
    • Constructor constructor = clazz.getDeclaredConstructor().newInstance();:使用无参构造创建新的实例
    • getDeclaredConstructor(Class<?>… parameterTypes) 和 newInstance(Object… initargs):创建具有特定参数的构造器的新实例。
  • 获取成员
    • getFields():返回所有公共字段(包括继承来的)
    • getDeclaredFields():返回本类中定义的所有字段(不包括继承),但包括私有字段
    • getMethods():返回所有公共方法(包括继承)
    • getDeclaredMethods():返回本类中定义的所有方法(不包括继承),但包括私有方法
    • getConstructors():返回所有公共构造函数
    • getDeclaredConstructors():返回本类中的定义的所有构造函数,包括私有构造函数。

5.9.2 资源

​ 在java中,关键的文件被称为资源(resource)

类通常一些关联的数据文件:

  • 图像和声音文件
  • 包含消息字符串和按钮标签被称为资源(resource)

Class类提供了一个服务可以查找资源文件:

  • 获得拥有资源的Class对象

  • 有些方法(如 ImageIcon 了的 getImage方法)接受模式资源位置的URL。可以调用

    URL url = cl.getResource("about.gif");
    
  • 否则,使用 getResourceAsStream方法得到一个输入流读取文件中的数据。

  • public URL getResource(String name)

    • 找到与类同一位置的资源,返回一个 URL或 输入流
  • public InputStream getResourceAsStream(String name)

    • 是Java 中用于从类路径(classpath)中加载资源文件为输入流(InputStream) 的方法
    • 如果没有找到资源,则返回null

5.9.3 反射分析类的能力

​ java.lang.reflect包中有三个类 Field、Method和Constructor,分别用于描述的字段、方法和构造器。这三个类都有一个名为 getName的方法,用来返回字段、方法或构造器的名字。

​ Filed类有 getType方法,用来返回描述字段类型的一个对象,这个对象的类型为 Class。

​ Method和Constructor类有报告参数类型的一个方法,Method类还有一个报告返回类型的方法。

​ 这个三个类有个 getModifiers的方法,将返回一个整数,用不同的 0/1 位描述使用的修饰符,在利用 java.lang.reflect包中 Modifier类的静态方法分析 getModifiers返回的这个整数。例:isPublic、isPrivate 或 isFinal 判断一个方法或构造器的修饰符。还可以使用 Modifier.toString方法打印修饰符。

​ Class类中getFields、getMethods和getConstructors方法将分别返回这个类支持的公共字段、方法和构造器的组件,其中包括超类的公共成员。

​ Class类的getDeclaredFields、getDeclaredMethods和getDeclaredConstructors方法将分别返回这类中声明的全部字段、方法和构造器组成的数组,其中包括私有成员、包成员和受保护成员,以及有包访问权限的成员,但不包括超类的成员。

  • Filed的常用方法(字段)
方法名返回类型功能说明
getName()String获取字段名称
getType()Class<?>获取字段的数据类型
get(Object obj)Object获取指定对象 obj 中该字段的值
set(Object obj, Object value)void设置指定对象 obj 中该字段的值
getModifiers()int获取字段的修饰符(如 public、private 等)
setAccessible(boolean flag)void设置是否忽略访问权限限制(用于访问 private 字段)
getDeclaringClass()Class<?>获取声明该字段的类
  • Method类常用方法(方法)
方法名返回类型功能说明
getName()String获取方法名称
getParameterTypes()Class<?>[]获取方法参数类型的数组
getReturnType()Class<?>获取方法的返回类型
invoke(Object obj, Object... args)Object调用方法,传入对象和参数
getModifiers()int获取方法的修饰符(如 public、private 等)
setAccessible(boolean flag)void设置是否忽略访问权限限制(用于访问 private 方法)
getDeclaringClass()Class<?>获取声明该方法的类
getExceptionTypes()Class<?>[]获取该方法声明的异常类型
  • Constructor类常用方法(构造器)
方法名返回类型功能说明
getName()String获取构造器所属类的名字(不是构造器名)
getParameterTypes()Class<?>[]获取构造器参数类型的数组
<T> T newInstance(Object... initargs)T使用构造器创建一个新实例
getModifiers()int获取构造器的修饰符(如 public、private 等)
setAccessible(boolean flag)void设置是否忽略访问权限限制(用于调用 private 构造器)
getDeclaringClass()Class<?>获取声明该构造器的类
getExceptionTypes()Class<?>[]获取该构造器声明的异常类型
  • Modifier工具类(解析修饰符)
方法名参数功能说明
isPublic(int mod)mod 是 getModifiers() 返回的整数判断是否是 public
isPrivate(int mod)mod 是 getModifiers() 返回的整数判断是否是 private
isProtected(int mod)mod 是 getModifiers() 返回的整数判断是否是 protected
isStatic(int mod)mod 是 getModifiers() 返回的整数判断是否是 static
isFinal(int mod)mod 是 getModifiers() 返回的整数判断是否是 final
toString(int mod)mod 是 getModifiers() 返回的整数将修饰符转换为字符串形式(如 “public static”)
  • Class类反射相关方法
方法返回类型功能说明
getFields()Field[]获取所有 public 字段(包括继承来的)
getMethods()Method[]获取所有 public 方法(包括继承来的)
getConstructors()Constructor[]获取所有 public 构造器
  • 获取本类中声明的所有成员(不包括继承)
方法返回类型功能说明
getDeclaredFields()Field[]获取本类中定义的所有字段(包括 private、protected、default)
getDeclaredMethods()Method[]获取本类中定义的所有方法(包括 private、protected、default)
getDeclaredConstructors()Constructor[]获取本类中定义的所有构造器(包括 private、protected、default)

5.10 继承的设计技巧

  • 将公共操作和字段放在超类中
  • 不要使用受保护的字段
  • 使用继承实现 “is-a” 关系
  • 除非所有继承的方法都有意义,否则不要使用继承
  • 覆盖方法时,不要改变预期的行为
  • 使用多态,而不要使用类型信息
  • 不滥用反射
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫魂魂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值