Core Java 读后感 - 第五章 继承

第五章 继承


5.1 类、超类和子类


  • extends 表示 继承,java 中的 所有继承都是 公有继承 (没有 c++ 中的 私有 和 保护继承) 👿 👿 👿

  • 子类 调用 父类方法时 用 super.超类方法名()

  • 子类 没有 显式 调用 超类 的 构造器,自动调用 超类 的 无参构造器,如果 超类 没有,子类 也没调用其他构造器,就会报错。👿👿👿

  • 关键字 this

    • 指示隐式参数的 引用
    • 调用该类的其他构造器
  • 关键词 super (super 不是一个对象引用, 他只是一个 指示 编译器 调用 超类 方法的特殊关键字

    • 调用超类的方法
    • 调用超类的构造器
  • Java 不支持 多重继承(即一个类 有多个超类)

  • 对象变量多态 的。(理解运行时动态绑定的概念)👿👿👿

    • 如果 父类 引用了 子类对象,编译器 只是将 它看作为 父类对象。调用子类方法就可能会报错(如果子类定义,父类没有定义)。
  • 重载方法 在运行时 动态绑定,虚拟机会调用 所引用对象的实际类型对应的那个方法。(说的天花乱坠,其实就是 “运行时的多态”)


    多态 总结 :

    • 重载解析(词如其名,重载过程解析…)

      • 编译器确定方法调用中提供的参数类型,并去相同名字中去找与所提供的参数类型完全匹配的方法,如果存在,就选择这个方法
      • 方法的签名 : 方法名称 + 参数 (不包括方法返回值)
    • 动态绑定静态绑定

      • 静态绑定 : 编译器 准确知道调用哪个方法, private 方法、 static方法、 final方法、构造器
      • 动态绑定: 要调用方法 依赖于 隐式参数实际类型,必须在运行时使用动态绑定 ,采用该方法时,虚拟机必须调用与 该对象变量所引用 对象的实际类型对应的那个方法。(有调用,没有向上找,由于时间开销大,虚拟机是预先会为每个类创建 方法表 方便搜索)
      • java中 动态绑定默认的 , c++ 则需要将成员函数声明为 virtual, 如果不希望这样,在 java 中使用 final 修饰该函数)👿👿👿
    • 多态其实就说了两个问题:👿👿👿

      1. 子类对象可以传给父类引用,但是只能调用父类的方法(如果调用实际类型的方法父类中没有定义会报错)
      2. 如果子类把父类定义的方法覆盖了,虚拟机就会动态绑定,调用子类对应同名方法

  • 在覆盖一个方法的时候,子类方法 不能低于 超类方法的可见性。

  • final类 会阻止继承 ,其中的所有方法都会 自动的 成为 final 方法,而不包括 字段。 eg: String类 就是 final类

  • 内联 : 如果一个方法没有被覆盖并且很短,编译器就能够对他进行优化处理,这个过程称作:内联 。 eg:内联调用 e.getName() 将被替换为访问字段 e.name

  • 进行 强制类型转换 的唯一原因: 要在暂时忽视对象的 实际类型之后,使用对象的全部功能。

    • 只能在 继承层次(一个 公共超类 派生出来的所有类的 集合) 内进行 强制类型转换
      • 在将 超类 强制转换成 子类 之前,应该使用 instanceof 进行检查
  • 包含一个或多个方法 必须声明为 抽象类,抽象类可以包含 字段 和 具体方法。扩展的抽象类还可以定义为抽象类,抽象类不能实例化。(对我来说,这个知识点其实不需要写,我早就记住了,略略略)

  • 访问权限 👿 👿 👿

public(default)protectedprivate
可以由任意类使用仅对本包可见对本包 和 其他包的所有子类可见只能由定义他们的类使用

5.2 Object:所有类的超类


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

    Employee[] staff = new Employee[10];
    obj = staff; // ok
    obj = new int[10]; // ok
    
  • equal方法: 确定两个对象引用是否相等(还有基于状态检测是否相等), 为了防止调用方法的对象为 null,可以采用 Objects.equals() 方法。

    • 子类可以有自己 相等性 概念,则 对称性需求 将强制使用 getClass 检测

    • 如果由超类决定 相等性 概念, 可以使用 instanceof 检测,这样可以在不同子类之间进行相等性比较


      完美 equals 方法建议: 👿 👿 👿

      • 显示参数命名为 otherObject

      • 检测 thisotherObject 是否相等

        if(this == otherObect) return true;
        
      • 检测 otherObject 是否为 null,如果为 null,返回 false。

        if(otherObject == null) return false;
        
      • 比较 thisotherObject

        // 如果 equals 的语义可以在子类中改变,就使用 getClass检测:
        if(getClass() != otherObject.getClass()) return false
        // 如果所有子类都有相同的 相等性 语义,可以在父类中 使用 instanceof 检测
        if(!(otherObject instanceof ClassName) return false // ClassName子类变量名
        
      • otherObject 强制转换为相应类类型变量

        ClassName other = (ClassName)otherObject
        
      • 根据相等的概念来比较字段,使用 == 来比较 基本类型字段,使用 Objects.equals 比较对象字段。

        return field1 == other.field1
        	&& Objects.equals(field2, other.field2)
        	&& ...;
        
      • 在子类重新定义了 equals,就要在其中包含一个 super.equals(other) 调用


  • getClass方法 : 返回这个对象运行时的类( 返回Class类型的对象 )

  • 如果要定义一个 覆盖超类方法的 子类方法,可以使用 Override标记 。如果这个方法并没有覆盖超类的任何方法,就会看到一个错误报告。

  • 散列码(hash code): 由对象导出的一个 整型 值 。 👿 👿 👿

    • Object类 的 hashcode 由 对象的存储地址 导出

    • String 的散列码 hashcode 的由 内容 导出(这就是为什么一样内容的 String字符串 导出的 hashcode值相同) 👿 👿 👿

      // Sting类型 中的 hashcode 定义方式
      int hash = 0
      for(int i = 0; i.length(); i++)
        	hash = 31 * hash + charAt(i);
      
    • 重新定义了 equals方法,就要重新定义 hashcode方法, 两者必须 相容equal方法 返回 true, hashcode方法 必须返回相同的值 ) eg:如果比较 Employee.equals 比较员工 ID, 则 hashcode方法 就需要散列 ID,而不是 姓名 和 存储地址

    • 合理组合实例字段散列码,以便能让不同对象产生的散列码分布更均匀

      • 可用 objects.hash(args1,...,argsn) :对各个参数调用Objects.hashCode(),并组合这些散列码
      • 数组可用 Arrays.hashCode(xxx[] a) , 散列码由数组元素的散列码组成
  • toString方法 :返回表示对象值的一个字符串。

    • 遵循格式一般为:类名 (getClass().getName)+ 一对方括号括起来的字段值。

    • 只要对象与 + 号连接起来。Java编译器就会自动调用 toString 方法 来 获取这个对象的字符串描述

    • Object类 定义的 toString方法 。可以打印对象的 类名 和 散列码 👿 👿 👿

    • 数组继承了Object类toString方法 ,想要格式打印使用:Arrays.toString()

5.3 泛型数组列表 - ArrayList


  • 在老版本中,使用 vector类 实现动态数组,但目前 ArrayList类 更加高效,因舍弃以前的。

  • 如果估计出数组可能存储的 元素数量, 可以调用 ensureCapacity 方法,这样前 n 次add调用不会带来开销很大的重新分配空间。

  • trimToSize方法: 将存储块大小调整为保存当前元素数量所需要的 存储空间,垃圾回收器将回收多余空间

  • 自动扩容的便利,增加了访问元素语法的复杂程度。必须使用 getset 方法访问和设置元素。

  • ArrayList 需要掌握的方法:

    • E set(int index, E obj)
    • E get(int index)
    • void add(int index, E obj)
    • E remove(int index)

    小技巧 : 既可以灵活扩展数组,又可以方便访问数组元素

    	var list = new ArrayList<X>();
    	while(...){
    		x = ...;
    		list.add(x);
    	}
    	
    	var a = new X[list.size()]
    	list.toArray(a)
    

5.4 对象包装器 与 自动装箱


  • 包装器类不可变 的, 一旦构造,不允许更改包装在其中的值。包装器是 final,不能派生他的子类
  • 自动装箱 和拆箱 是 编译器 的工作,不是 虚拟机 的。编译器 在生成类的字节码时就会插入必要的方法调用,虚拟机 只是执行 这些字节码

5.5 参数数量可变的方法


  • 方法参数列表用到 ..., 相当于一个对应数据类型的数组,举个例子,

    // 与 double[] 完全一样
    public static double max(double... values)
    

5.6 枚举类


  • 自定义 枚举类型枚举类型 包括有限个命名的值。

    enum Size {SMALL, MEDIUM, LARGE, EXTRA_LARGE}
    // 声明 这种类型的变量
    Size s = Size.MEDIUM
    // Size类型 只能存储 这个类型声明中给定的某个枚举值, 或者 null
    
  • 实际上声明定义的类型 是 一个类, 他刚好有 4 个对象(枚举常量) ,不能再构造新的对象。因此比较两个枚举类型值 时 用 == 而非 equals 👿 👿 👿

  • 所有的枚举类型 都是 Enum类 的子类,继承了这个类很多方法

    • toString(), 返回枚举常量名 eg: Size.SMALL.toString() 返回字符串 “SMALL”

    • valueOf(), toString() 逆方法, 返回给定类中有指定名字的枚举常量 (枚举对象)

      // 将 是设置为 Size.SMALL
      Size s = Enum.valueOf(Size.class(), "SMALL")
      
    • static values(), 返回一个包含全部枚举值的数组 eg:Size[] values = Size.values();

    • ordinal(): 返回 enum 声明中 枚举常量的位置,从 0 开始计数 eg: Suze.MEDIUM.ordinal() 返回 1

5.7 反射


  • 反射 : 能够分析 类能力 的程序

  • 程序运行期间,Java运行时系统 会始终为 所有对象 维护一个 运行时类型标识, 虚拟机利用这个信息选择要执行的正确的方法。他会跟踪每个对象所属类。可以使用一个特殊类访问这些信息,即 Class类

    • 就像之前 Employee对象 描述一个特定员工的属性一样,Class对象 会描述一个特定类的属性。

    • 获取 Class对象 的方法:

      • 使用 Object类 中的 getClass方法

        Employee e;
        ... 
        Class cl = e.getClass();
        
      • 使用 Class类 的静态方法 forName 获得类名所对应的对象(字符串会变化,使用这个类)

        String className = "java.util.Random";
        Class cl = Class.forName(className);
        
      • 如果 T 是 java 任意类型,使用 T.class 将代表匹配的 类对象。

        Class cl1 = Random.class;
        Class cl2 = int.class;
        Class cl3 = DOuble[].class;
        
    • 一个 Class对象 实际上 **表示的是一个类型,**这可能是 一个类,也可能不是 ,eg: int.class

    • Class类 实际上是一个 泛型类。 eg: Employee.class 的类型是 Class<Employee>

    • 虚拟机为 每个类型 管理 唯一 一个 Class对象。所以上一节中使用 == 实现两个类的比较(言外之意是:判断是否属于同一个类生成的对象): if(e.getClass() == Employee.class) 👿 👿 👿

  • 使用 Class类 对象,构造类的实例。调用 getConstructor 方法将得到一个Constructor 类型对象,然后使用newInstance 方法来构造实例

    var className = "java.util.Random";
    Class cl = Class.forName(className);
    Object = cl.getConstructor().newInstance();
    
  • 异常 的种类:非检查型异常检查型异常

    • 非检查型异常:编译器不希望你对这些异常提供处理器。
    • 检查型异常:编译器将会检查你(程序员)是否知道这个异常并做好准备来处理后果
  • 利用反射分析类的能力:反射机制的重要内容 - 检查类结构

    • java.lang.reflect 包中有三个类: FieldMethodConstructor 分别用于描述类的字段、方法、和构造器。带 Declared 修饰的方法包括从超类继承过来的,不带的只是这个类的。
      • Field[] getFields()
      • Field[] getDeclaredFields()
      • Method[] getMethods()
      • Method[] getDeclaredMethods()
      • Constructor[] getConstructors()
      • Constructor[] getDeclaredConstructors()
    • 打印对应方法、字段、构造器的修饰符,先获取 修饰符 对应的整数值: cl.getModifiers() , 然后利用该类的静态方法打印:Modifier.toStirng(cl.getModifiers())
  • 使用 反射 在运行时分析对象: 可以利用反射机制, 查看在编译时还不知道的对象字段

    • Field类get方法 可以获取对象字段的值,也可以通过 set(obj,value)设置对象字段的值

      • 下面例子有一处不恰当之处,name 是私有属性,会抛出 IllegalAccessException异常Java安全机制 允许查看有哪些字段,如果想读写哪些字段,除非拥有访问权限 。👿 👿 👿

        var harry = new Emptyee("Harry Hacker", 50000, 10, 1, 1989);
        Class cl = harry.getClass();
        Field f = cl.getDeclaredField("name");
        Object v = f.get(harry)
        
    • 反射机制 的 默认行为,受限制于Java的 访问控制 。可以用 FieldMethodConstructorsetAccessible方法 修改权限,但是也可能被拒绝。eg:f.setAccessible(true);

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

    • 使用反射的 核心原因:可以 将得到的 Object泛型数组 转换回对应类型 。

    • java.lang.reflect包 中的 Array类,允许动态地创建数组

      • 举一个 下面例子, 编写一个 通用方法

        var a = new Employee[100];
        ...
        // 数组已满,需要扩充数组
        a = Arrays.copyof(a, 2 * a.length);
        
      • 坏的尝试,由于java数组会记住每个元素的类型,即创建数组时候 new表达式 使用的类型。

        public static Object[] badCopyOf(Object[] a, int newLength)
        {
        	var = newArray = new Object[newLength];
        	System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
        	return newArray;
        }
        
      • 上面的不好的原因是:返回的 Object[]数组 无法转换成 Employee[] 数组 。因此,为了 编写通用代码需要能够创建与原数组类型相同的数组

        Public static Object goodCopyof(Object a, int newLength)
        {
            // 获得 a 数组的类对象
            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(a.length, newLength));
        	return newArray;    
        }
        
        • 这里 用 Object 而不用 Object[] 的原因是:为了使该方法可以扩展 任意类型数组 而不单单是对象数组。

          int[] a ={1, 2, 3, 4, 5};
          a = (int[]) goodCopyof(a.10);
          
  • 可以利用 反射机制 调用 任意的方法构造器

    • d调用任意方法: 1.获得 Method 对象 2. 调用 invoke方法

    • 例如 : 可以利用 Method类 中的 invoke方法 ,调用包装在当前 Method对象 中的方法

      // Object invoke(Object obj, Object...args) 
      // 第一个 参数 是隐式参数(static 为 null), 其余对象提供显式参数
      String n = (String) m1.invoke(harry)
      
    • 如何得到 Method对象 方法, 有下面两种。不过有若干种 同名方法,所以还必须提供方法的参数类型

      • 调用 getDeclaredMethods方法

      • 调用 Class类getMethod `

        // Method getMethod(String name, Class... parameterTypes)
        // 说明了如何获得方法指针
        Method m1 = Employee.class.getMethod("getname");
        Method m2 = Employee.class.getMethod("raiseSalary", double.class);
        
    • 调用 构造器 : 1. 获取 Constructor对象 2. 调用方法 newInstance方法

      • 将 参数类型 提供给 Class.getConstructor方法

      • 把 参数值 提供给 Constructor.newInstance方法

        Class cl = Random.class;
        Constructor cons = cl.getConstructor(long.class);
        Object obj = cons.newInstance(42L);
        
    • 使用反射获得的方法指针比直接调用慢很多(因为来回强制类型转换需要花费大量时间)

  • 反射总结:

    • 什么是 Class类
    • 分析 类的结构: 字段、方法、构造器
    • 在运行时 分析对象:主要是对象的字段
    • 写泛型数组代码
    • 调用 任意方法 和 构造器 :方法指针

5.8 继承的设计技巧


  • 将公共操作和字段放在超类中
  • 不要使用受保护字段
  • 使用 继承实现“is- a” 关系时,避免滥用
  • 除非所有继承方法都有意义,否则不要使用继承
  • 在覆盖方法时,不要改变预期行为
  • 使用多态,而不要使用类型信息
  • 不要滥用反射
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值