读后笔记 -- Java核心技术(第11版 卷I ) Chapter5 继承

5.1 类、超类和子类

  • 继承:基于已存在的类构造一个新类。继承已存在的类:1)复用(继承)这些类的方法;2)增加一些新的方法和字段;
  • 反射:在程序运行期间发现更多的类及其属性的能力

OOP语言的三大特性:继续、封装、多态

解释:https://blog.csdn.net/qq_39188150/article/details/105860221

 

5.1.1. 定义子类

public class Manager extends Employee {
    添加的方法和域    // Java 所有的继承都是公有继承
}

Employee:超类(superclass)、基类(base class)、       父类(parent class)

Manager:  子类(subclass)、   派生类(derived class)、孩子类(child class) 

 

5.1.2. 子类覆盖方法

public class Manager extends Employee {
    private double bonus;          // 子类新增字段

@Override public double getSalary() { double baseSalary = super.getSalary(); // 调用父类 Employee 的 getSalary()方法 return baseSalary + bonus; } }

 子类中可 增加字段、增加方法 或 覆盖超类,但绝不能删除继承的任何字段或方法

 

5.1.3. 子类构造器

public class Manager extends Employee {
    private double bonus;

    public Manager(String name, double salary, int year, int month, int day) { 
        super(name, salary, year, month, day);        // super 实现对超类构造器的调用。!!!但必须是子类构造器的第一条语句。
        bonus = 0;             // 新增字段在构造器中的初始化
    }
    ...
}
  • 如果子类的构造器没有显式地调用超类的构造器, 则将自动地调用超类的 无参数构造器。
  • 如果超类没有无参数构造器, 并且在子类的构造器中又没有显式地调用超类的其他构造器, 则 Java 编译器将报告错误。
this 关键字的用途

1)指示 隐式参数 的引用;

2)调用该类其他的构造器;

super 关键字的用途

1)调用超类的方法;

2)调用超类的构造器;

    构造器参数可以传递给当前(this)的另一个构造器,也可以传递给超类(super)的构造器,案例如下:

public class Employee {
    private static int nextId;

    private final int id;
    private String name = "";
    private double salary;

    ...public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public Employee(double salary) {
        this("Employee #" + nextId, salary);      // calls the Employee(String, double) constructor
    }
    ...
}    

 

5.1.4. 继承层次

   继承层次:由一个公共超类派生出来的所有类的集合。一个祖先类可以有多个子孙链。

        

 

5.1.5. 多态 Polymorphism

 1)是否应设计为继承关系的原则:

  • “is-a”规则,它指出子类的每个对象也是超类的对象;
  • "is-a"规则的另一表述是 替换原则,即 程序中出现超类对象的任何地方都可以使用子类对象替换。

 2)Java中,对象变量是多态(一个变量可以指示多个类型):Employee 变量 e 可以引用 Employee 类对象, 也可以引用 Employee 类的任何一个子类的对象;

----- 多态 -----
Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
boss.setBonus(5000);

Employee[] staff = new Employee[3];
staff[0] = boss;
staff[1] = new Employee("Harry Cracker", 50000, 1989, 10, 1);
staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);

for (Employee e : staff) {
    // e 为 staff[1]/ staff[2] 时,指示 Employee 类,调用 Employee 的 getSalary()
    // e 为 staff[0] 时,指示 Manager 类,调用 Manager 的 getSalary()
    System.out.println("name=" + e.getName() + ", salary=" + e.getSalary());
}
Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
Employee[] staff = new Employee[3];

// 1) 变量 staff[0] 与 boss 引用同一个对象,但编译器只将 staff[0] 看成是 Employee 对象
staff[0] = boss;
// 这将意味着:
boss.setBonus(5000);       // OK
staff[0].setBonus(5000);   // Error,因为 staff[0] 是 Employee 类,而 setBonus 不是 Employee 的方法

// 2)不能将一个超类的引用赋给子类变量
Manager m = staff[i];      // Error,因为 不是所有的雇员都是经理

 

5.1.6. 方法调用

------- More about Method Calss --------
Suppose x is declared to be of type C. Conside a method call: x.f(args)
1. The compiler finds all accessible methods called f in C and its superclasses.
2. The compiler selects method whose parameter types match the argument types (overloading resolution).
3. If the method is private, static, or final, then the compiler knows exactly which methods to call (static binding).
4. Otherwise, the exact method is found at runtime (dynamic binding).
方法覆盖(Overloading)
  • 要求参数类型完全匹配
  • 允许有协变(covariant)的返回类型(返回类型是覆盖类型的子类型);
----- Employee.java ------
public class Employee {
    public void setBoss(Employee boss) {
        System.out.println("Employee setBoss");
    }
    public Employee getBoss() {
        return Employee.this;
    }
}

----- Manager.java -----
public class Manager extends Employee {
    @Override
    public void setBoss(Employee boss) {    // 因为要覆盖超类的方法,此处的 类型必须使用 Employee 类,其方法的参数必须完全匹配
        System.out.println("Manager setBoss");
    }

    @Override
    public Manager getBoss() {              // 1)此处的getBoss()无参数,所以会覆盖 Employee 的 getBoss() -- 方法参数一致;2)返回类型是 Manager,是Employee的子类,所以允许有协变的返回类型 -- 返回类型可修改
        return Manager.this;
    }
}

----- 调用 ------
for (Employee e : staff) {
    System.out.println("name=" + e.getName() + ", salary=" + e.getSalary());
    e.setBoss(e);
}
动态绑定的一个重要特征:无须对现有的代码进行修改就可以对程序进行扩展。
  如增加一新类 Executive,并且变量 e 有可能引用这个类的对象,我们不需要对包含调用 e.getSalary() 的代码进行重新编译。
 
覆盖一个方法时,子类方法不能低于超类方法的可见性。特别是, 如超类方法是 public, 子类方法一定为 public
 

5.17. 阻止继承:final 类和方法

  将 方法 或 类 声明为 final 主要目的确保不会在子类中改变语义。如 String 类
       如: public final class Executive extends Manager {
                    ...
               }
  • a)类被定义为 final,表示阻止 定义其子类;
  • b)类中的方法被定义为 final,表示 子类不能覆盖这个方法。

   类被声明为 final =>

  • 1)类中的所有方法自动地成为 final 方法;
  • 2)(!!!)不包含字段

 

5.1.8. 强制类型转换

  1)数据类型转换: double x = 3.405;   int nx = (int) x ;
  2)对象引用强转换:  
  对象引用强转换的唯一原因:暂时忽视对象的实际类型之后,使用对象的全部功能。
  • 将一个子类的引用赋给一个超类变量, 编译器是允许的。
  • 但将一个超类的引用赋给一个子类变量, 必须进行类型转换, 才能通过运行时的检査(因为赋予了更多的变量)。
综上所述
  • 只能在继承层次内进行类型转换;
  • 在将超类转换成子类之前,应该使用 instanceof  进行检查;   a instanceof A => 判断 对象a 是否是 类A 的实例
Employee harry = new Employee(...);
Manager boss = new Manager(....);      // 其中,Manager 类继承自 Employee

if (harry instanceof Employee)  => true
if (boss instanceof Employee)   => true 
if (harry instanceof Manager)  => false
 在一般情况下,应尽量少用类型转换和 instanceof 运算符。
Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
boss.setBonus(5000);

Employee[] staff = new Employee[3];
staff[0] = boss;
staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);

for (Employee e : staff) {
    System.out.println("name= " + e.getName() + ", salary= " + e.getSalary() + ", hireDay=" + e.getHireDay());
}

Manager boss2 = (Manager) staff[1];    // Error [java.lang.ClassCastException: inheritance.Employee cannot be cast to inheritance.Manager],可以改成 staff[0]

if (staff[1] instanceof Manager) {     // 为避免上面的 ClassCastException,可以通过 instanceof 预先判断一下
    Manager boss3 = (Manager) staff[1];
    System.out.println(boss3);
}

 

5.1.9. 抽象类

OOP中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。

父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。

在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,但,可以实现多个接口
  • 1)抽象方法充当着占位的角色, 它们的具体实现在子类中;
  • 2)扩展抽象类可以有两种选择
  •        (1)子类中保留抽象类中的部分或所有抽象方法仍未定义 => 子类 也为 抽象类;
  •        (2)定义全部的抽象方法 => 子类 不抽象;
  •  3)抽象类不能被实例化;
  •  4)可以定义一个抽象类的对象变量, 但只能引用非抽象子类的对象。
// Person 类是抽象类,Student 类是非抽象的派生子类
Person p = new Student("Vince Wu", "Economics);

 

抽象类的目的:抽取出来的公共行为

抽象类可以包含:

  • 1)抽象方法
  • 2)变量;
  • 3)具体方法;
public abstract class AbstractPerson {
    // 1) 抽象类的抽象方法
    public abstract String getDescription();
    // 2) 抽象类的变量
    private final String name;public AbstractPerson(String name) { this.name = name; }   // 抽象类的构造器
    // 3) 抽象类的具体方法
    public String getName() { return name; }
}

 

5.1.10. 受保护访问 protected(现实中比较少用)

子类也不能访问超类的 private 字段。 

 1)超类的方法 或 字段 设置 protected 的目的:超类中的某些方法或字段允许被子类访问

  2)protected 的实际意义:限制某个方法的使用

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

  • 仅对本类可见                 --- private
  • 对所有类可见                 --- public
  • 对本包和所有子类可见  --- protected (较少使用)
  • 对本包可见                    --- 默认, 不需要修饰符 (较少使用)

 


5.2 Object:所有类的超类

5.2.1 Object 类型的变量

1. Object 类型的变量只能作为各种值的一个泛型容器,如果想对其中的内容进行具体操作,要进行相应的强制类型转换。
 
2. Java 中,只有 8 种基本类型 (primitive types) 不是对象。
    所有的数组类型(包括:对象数组、基本类型的数组)都扩展了 Object 类。
// 1. 可以使用 Object 类型的变量引用任何类型的对象
Object obj = new Employee("Harry Hacker", 35000);

// 2. 对其中的内容进行具体的操作, 要清楚对象的原始类型, 并进行相应的类型转换
Employee e = (Employee) obj;

// 3. obj 可以存放 对象数组 或 基本类型数组 
Employee[] staff = new Employee[10];
obj = staff; // OK,对象数组
obj = new int[10]; // OK,基本类型 int 数组

 

5.2.2-5. Object 类的几个主要方法

1)equals:(默认)判断两个对象是否具有相同的引用

什么是 ==?
   == 等于比较运算符,
   1)数值类型比较:即使他们的数据类型不相同,只要他们的值相等,也都将返回true;
   2)引用类型比较:只有当两个引用变量的类型具有父子关系时才可以比较,而且这两个引用必须指向同一个对象,才会返回true. (在这里我们可以理解成 == 比较的是两个变量的内存地址)。通过 System.identityHashCode(obj) 可以打印对象 obj 的内存地址。

什么是 equals()?
   equals()是 Object 类的方法,实际上返回的就是 == 比较的结果。但所有的类都继承 Object,而且 Object 中的 equals() 没有使用 final 关键字修饰, 那么使用 equals() 比较的时候,需要注意这个类有没有重写 Object 中的 equals() 方法.

区别
   == 是java提供的等于比较运算符,用来比较两个变量指向的内存地址是否相同.而 equals() 是 Object 提供的一个方法. Object 中 equals() 的默认实现就是返回两个对象 == 的比较结果.
但 equals() 可以被重写, 所以我们在具体使用的时候需要关注equals()方法有没有被重写.

 

2)相等测试与继承

  Java 要求 equals() 具有下列特性:

  • (1)自反性 Reflexive:对任何非空引用 x, x.equals(x) = true;
  • (2)对称性 Symmetric:对任何引用 x 和 y, 当且仅当 y.equals(x) = true 时, x.equals(y) = true;
  • (3)传递性 Transitive:对任何引用 x、y 和 z, 如 x.equals(y) = true, y.equals(z) = true,x.equals(z) 也应= true;
  • (4)一致性 Consistent:如果 x 和 y 引用的对象没有发生变化,反复调用 x.equals(y) 应返回同样的结果;
  • (5)对于任意非空引用 x, x.equals(null) 返回 false;

 完美的 equals 方法

---- 实例 超类 Employee 的 equals(),覆盖 Object 类的 equals() ----
@Override
public boolean equals(Object otherObject) {      // 要覆盖 Object 类的 equals(Object obj),所以这里必须定义为 Object
    // step 1: a quick test to see if the objects are identical
    if (this == otherObject) { return true; }

    // step 2: must return false if the explicit parameter is null。
    if (otherObject == null) { return false; }

    // step3: if the classes don't match, they can't be equal。1)如果 equals 的语义可以在子类中改变,需要使用 getClass
// 2)如果所有的子类都都有相同的相等性语义,使用 instanceof: if (!(otherObject instanceof ClassName)) return false; if (getClass() != otherObject.getClass()) { return false; } // 4) 将 otherObject 强制转换为相应类型的变量; Employee other = (Employee) otherObject; // 5) 根据相等性比较字段。a) Objects.equals() 可以处理 null 对象的情况; b) name 和 hireDay 是对象,所以用 equals() 比较,salary 是数值型,所以用 "==" 比较 return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay); } ---- 实例 子类 Manager 的 equals() ---- @Override public boolean equals(Object otherObject) { if (!super.equals(otherObject)) { return false; }
Manager other
= (Manager) otherObject; // super.equals checked that this and other belong to the same class return bonus == other.bonus; }

对于数组类型的字段,可以使用静态方法 Arrays.equals()  检测相应的数组元素是否相等。

 

3)hashCode

  • 对象导出的一个整型值,如果x, y 是两个不同的对象,则 x.hashCode() 和 y.hashCode() 基本不同;
  • 如果重新定义 equals 方法,必须重新定义 hashCode 方法;
  • equals 与 hashCode 的定义必须一致;
String s = "Ok";
StringBuilder sb = new StringBuilder(s);
System.out.println(s.hashCode() + " " + sb.hashCode());

String t = "Ok";
StringBuilder tb = new StringBuilder(t);
System.out.println(t.hashCode() + " " + tb.hashCode());

--------- Output ---------
2556 225534817
2556 1878246837
// 字符串的 hashCode 是由内容导出的。s、t 具有相同内容,所以拥有相同的 hashCode // StringBuilder 类中没有定义 hashCode 方法,使用 Object 类的默认 hashCode 方法,即从 对象的存储地址 得出 hashCode。sb、tb 是两个创建的对象,拥有不同的地址,所以 hashCode 不同
/**
 * hashCode 的几种方法“”
 */
// 第一种:采用 7,11,13 让 hashCode 更加均匀;
public int hashCode() { return 7 * name.hashCode() + 11 * new Double(salary).hashCode() + 13 * hireDay.hashCode(); }

// 第二种: better,避免 name 和 hirdDay 为空,使用 null 安全的 Objects.hashCode() 会做处理
public int hashCode() { return 7 * Objects.hashCode(name) + 11 * Double.hashCode(salary) + 13 * Objects.hashCode(hireDay); }

// 第三种: best,更简单的办法,Objects.hash() 对所有参数一致处理
public int hashCode() { return Objects.hash(name, salary, hireDay);}

对于数组类型的字段,可以使用静态方法 Arrays.hashCode()  检测相应的数组元素的 hashCode。

 

4)toString

  • 对象 “ +” 字符串,编译器会自动调用 toString();
  • 如没有 重写 toString 方法,调用 System.out.println(x);  将输出 "java.io.PrintStream@2f6684";即 ClassName@hashCode
  • 如超类使用了 getClass().getName(),那么子类只要调用 super.toString() 就可以了;
  • 数组使用用 Arrrays.toString,如  int[] luckyNumbers = { 2, 3, 5, 7 , 11, 13 } ;    String s = Arrays.toString(luckyNumbers);
  • 多维数组使用 Arrays.deepToString 方法;

    强烈建议为自定义的每一个类增加 toString 方法

// 针对第三小点,子类覆盖超类的 toString() 方法
public class Manager extends Employee {
    ...
    public String toString() {
        return super.toString() + "[bonus=" + bonus + "]";
     }
}

 

 


5.3 泛型数组列表

1. 数组定义的方式:
// 1. 定义一个静态初始化数组,固定数组大小
int[] array1 = new int[] {10, 20, 30, 40, 50};

// 2. 动态初始化一个数组,固定数组大小
var staff1 = new Employee[6];

// 3. 使用泛型数组 ArrayList,自动调整数组容量
var staff2 = new ArrayList<Employee>();

ArrayList<>:

  • 如果赋值给一个变量,或传递到某个方法,或从某个方法返回,编译器会检査这个变量、参数或方法的泛型类型,然后将类型放在 <> 中;
  • 如果调用 add() 且内部数组已经满了,数组列表就将自动地创建一个更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中;
  • !!! ArrayList can only hold objects, not primitive types (ArrayList 仅存放对象,不能存基本数据类型);
  •        => 所以,ArrayList 采用了 包装器来处理 基本数据类型的存放。如 ArrayList<Interger> list = New ArrayList<>;   (chapter 5.4)

 

// 数组列表 与 数组的区别
new ArrayList<>(100);  // capacity is 100
=> 拥有分配 100 个元素的能力(超过时将重新分配,超过 100);最初甚至初始化构造之后,数组列表根本不含任何元素,需要通过 .add() 或其他方式填充元素

new Employee[100];    // size is 100
=> 为数组分配 100 个元素的存储空间。超过时报 “java.lang.ArrayIndexOutOfBoundsException”,不能扩展
 
ArrayList<Employee> staff = new ArrayList<>();
staff.set(0, new Employee("Albert Hammer", 75000, 1987, 12, 15));   // // set 可理解为替换的操作。不能对空数组列表操作,否则报错 “IndexOutOfBoundsException”

 

*** ArrayList 的注意事项 ***1)原始的 ArrayList 存在一定的危险性,它可以接受任意类型的对象,如
ArrayList<Employee> staff = new ArrayLis<>(100);
Employee harry = new Employee(.....);
...; staff.set(i, harry);
// Ok,此时 staff 数组已经非空 staff.set(i, "Harry Hacker"); // 1)编译不会报警,仅在检索对象并试图进行类型转换时,才发现问题。 2)如使用到 ArrayList<Employee>,编辑器就会报错 2)灵活的操作方式:创建一个已知类型的数组,然后将该类型赋给新数组; // step1:创建一个数组列表,并添加所有的元素 var list = new ArrayList<X>(); while (...) { x=...; list.add(x); } // step2:使用 toArray() copy 到一个数组中 var a = new X[list.size()]; list.toArray(a);

 

 


5.4 对象包装器与自动装箱

1. 对象包装器
        ArrayList 只能存储对象,不能存基本数据类型。所以基本类型要通过包装器进行包装。如 int 包装成 Integer。
  • 对象包装器:所有的基本类型都有一个与之对应的类(如 int 对应 Integer)。包含:Integer、Long、Float、Double、Short、Byte (这 6 个类派生于公共的超类 Number)、Character 和 Boolean; 
  • 对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装器类还是 final , 因此不能派生它们的子类;
  • ArrayList<Integer> 的效率远远低于 int[],使用场景:方便 + 较小的集合;
ArrayList<int> list;     // 错误定义,类型不能为基本类型

// 应该改成:
var list = new ArrayList<Integer>;

 

2. 自动装箱与拆箱 => 编译器的工作

 int -> Integer:自动装箱; Integer -> int :自动拆箱

Caution: == doesn't work with wrappers.

Integer a = n + 1;
Integer b = n + 1;
System.out.println(a == b);   // May be false,因为现在 a,b 都是对象了
System.out.println(a.equals(b));   // True

Caution: Wrappers can be null.
Integer n = null;
System.out.println(n + 1);   // Null pointer exception
// 1)自动装箱,如 添加 int 类型的元素到 ArrayList<lnteger>
list.add(3);         // 自动变换成:list.add(Integer.valueOf(3));

// 2)自动拆箱,如 将一个 Integer 对象赋给一个 int 值
int n = list.get(i);   // 自动转换成: int n = list.get(i).intValue();
1. 自动装箱规范要求: booleanbytechar <= 127, -128~127 之间的 shortint 被包装到固定的对象中。
Integer a = 1000;
Integer b = 1000;

// 所以,下面一定返回 true
if (a == b) { return true; }

2. 如果一个条件表达式混用了 Integer 和 Double 类型, Integer 会拆箱,提升到 Double,再装箱成 Double
Integer n = 1;
Double x = 2.0;
System.out.println(true ? n : x); // prints 1.0

 

 


5.5. 参数数量可变的方法(变参 varargs 方法)

// "..." 表示若干参数,参数将接受 double[] 数组
public static double max (double... values) {
    double largest = Double.NEGATIVE_INFINITY;
    for (double v : values) {
        if (v > largest)  { largest = v; }
    return largest; 
}

// 调用
double m = max(3.1, 40.4, -5);    
// 普通做法。对比起来,变参方式更灵活double[] values = new double[] {3.1, 40.4, -5};
double m = max(values);public static double max(double[] values) {
    double largest = Double.NEGATIVE_INFINITY;
    for (double v : values) {
        if (v > largest) largest = v;
    }
    return largest;
}

 


5.6 枚举类

public enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE }      // 定义 枚举类型
  • 比较两个枚举类型的值,直接 “==” 即可,不需要 equals。因为枚举只有几个对象,不存在具有相同值的 不同对象;
  • 枚举的构造器总是 private 的;
  • 所有的枚举类型 都是 Enum 类的子类;

枚举类 (是一个类)的几个常用的方法

方法 说明 案例 注释
toString() 返回枚举常量名  Size.SMALL.toString( ); 返回字符串“ SMALL”
valueOf() toString 的逆方法 Size s = Enum.valueOf(Size,class, "SMALL"); 将 s 设置成 Size.SMALL
ordinal() 返回枚举常量在 enum 中的位置,从0开始 System.out.println(size.ordinal()); small -> 0, large -> 2
values() 返回包含全部枚举值的数组 Size[] values = Size.values();  返回 [SMALL, MEDIUM, LARGE, EXTRA_LARGE]

 


5.7 反射

反射库:提供了一个工具集,以便编写能够动态操纵 Java 代码的程序。被大量地应用于 JavaBeans 中,是 Java 组件的体系结构;

反射:能够分析类能力的程序(一般工具构造者使用);

反射机制(一般用于 JDK、开源框架),可以用来:

  • 在运行时分析类的能力;
  • 在运行时检查对象, 如 编写一个适用于所有类的 toString();
  • 实现泛型数组操作代码;
  • 利用 Method 对象, 这个对象很像 C++ 的函数指针;

 

5.7.1. Class 类

// 1. Given an object, get its Class object:
Object obj = ...;
Class cl = obj.getClass();

// 2. Find the class name:
System.out.println(cl.getName());

// 3. Find the methods that you can apply:
System.out.println(Arrays.toString(cl.getMethods());

// 4. newInstance yields an instance constructed with no-arg constructor:
Object newObj = cl.newInstance();

 

获取 Class 的三种方式:

public class ReflectTest {
    public static void main(String[] args) throws ClassNotFoundException {
//method1: getClass() 返回一个 Class 类型的实例
        Employee e = new Employee("Hack", 50000, 1986, 2, 1);

        Class c1 = e.getClass();            // get its class object
        System.out.println(e.getClass().getName() + ", " + e.getName());
        // inheritance.Employee, Hack
        System.out.println(c1);
        // class inheritance.Employee,其中 inheritance 是 Employee 类所在的包名 

        //method2:forName():从一个字符串,获得类名对应的 Class 对象 【需要增加一个 异常处理】。应用场景:手工强制加载其他类
        String className = "java.util.Random";
        Class c2 = Class.forName(className);
        System.out.println(c2);
        // class java.util.Random

        //method3: 如果 T 是任意的 Java 类(或 void 关键字), T.class 代表匹配的类对象
// 一个 Class 对象实际表示的是一个类型,可能是类,也可能不是类
Class cl1 = Random.class; // 获得 类 Class cl2 = int.class; // int 不是类,是基本数据类型,但 int.class 是一个 Class 类型的对象 Class cl3 = Double[].class; // 获得类。数组类的返回有点奇怪,Double[]带[L,int[]带[I System.out.println(cl1 + ", " + cl2 + ", " + cl3); // class java.util.Random, int, class [Ljava.lang.Double; } }

 

两个有用的方法

  • 1) == 运算符:类对象比较,如 if (e.getClass() == Employee.class) ...               // 虚拟机中,每个类型都对应唯一的 Class 对象,所以可以用 “==” 比较;
  • 2)newInstance:创建一个相同类型的实例。newInstance 方法调用默认的构造器(无参数的构造器)初始化新建的对象;
// forName + newInstance 结合使用实现:已知一个 Class 类型的对象,构造出一个实例
String className = "java.util.Random";
Class cl = Class.forName(className);
Object obj = cl.getConstructor().newInstance();   // getConstructor() 将得到一个 Consturctor 类型的对象,然后用 newInstance() 来构造一个实例

 

5.7.2. 声明异常

异常的两个分类:

  • 1)非检查异常:编译器不会检查是否提供了处理器。如 访问 null 引用 或 越界错误;
  • 2)检查异常    :编译器会检查是否提供了处理器;
try {
    statements that might throw exceptions
}
catch (Exception e) {
    handler action
}

 ** 如果调用了一个抛出已检查异常的方法,而又没有提供处理器,编译器就会给出错误

关于两者间详细的介绍,参考第 7 章节:https://www.cnblogs.com/bruce-he/p/16285503.html

 

5.7.4. 利用反射分析类的能力

    java.lang.reflect 包中有三个类 Field、Method 和 Constructor 分别描述 类的 字段、方法 和 构造器。

Method Function
Field[] getFields()
返回包含 Field 对象的数组。 这些对象对应这个类或其超类的公有字段
Field[] getDeclaredFields()
返回包含 Field 对象的数组。 这些对象对应这个类的全部字段。如果类中没有字段, 或 Class 对象描述的是基本类型 或 数组类型,将返回一个长度为 0 的数组
Method[] getMethods()
返回包含 Method 对象的数组。返回所有的公有方法, 包括从超类继承来的公共方法
Method[] getDeclaredMethods() 返回包含 Method 对象的数组。返回这个类或接口的全部方法, 但不包括由超类继承了的方法
Constructor[] getConstructors()
返回包含 Constructor 对象的数组。 其中包含 Class 对象所描述的类的所有公共构造器
Constructor[] getDeclaredConstructors() 返回包含 Constructor 对象的数组。 其中包含 Class 对象所描述的类的全部构造器
Class getDeclaringClass()
返冋一个 Class 对象,标识定义了这个 构造器、 方法 或 字段的类
String getPackageName() (java 9)
得到包含这个类型的包的包名。 如数组类型,则返回元素类型所属的包;  如基本类型,则返回“java.lang”
Class[] getExceptionTypes() (在 Constructor 和 Method 类中)
返回一个Class 对象数组,其中各个对象标识这个方法抛出的异常类型
int getModifiers() 

返回一个整数,描述 构造器、方法 或 字段 的修饰符。使用 Modifier 类中的方法来分析这个返回值。用不同的位开关描述 public 和 static 这样的修饰符使用状况

String getName() 返冋一个表示 构造器、 方法 或 字段名的字符串
Class[] getParameterTypes() (在 Constructor 和 Method 类中) 返回一个Class 对象数组,其中各个对象标识参数的类型
Class getReturnType()  ( 在 Method 类中)  返回一个用于表示返回类型的 Class 对象

 

5.7.5. 在运行时使用反射分析对象

查看任意对象的数据域名称和类型:

  • 获得对应的 Class 对象;
  • 通过 Class 对象调用 getDeclaredFields;

  反射机制的默认行为受限于 Java 的访问控制,可调用 Field、Method、Constructor 对象的 setAccessible() 来覆盖。 

import java.lang.reflect.Field;

public class OATest {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Employee harry = new Employee("Harry Hacker", 50000, 1982, 7, 9);
        Class cl = harry.getClass();  // cl: class inheritance.Employee

        Field f = cl.getDeclaredField("name");   // f: private final java.lang.String Employee.name
        f.setAccessible(true);     // name is private field in Employee Class, must set it to accessible
        Object v = f.get(harry);   // v: Harry Hacker
    }
}

 

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

 构造新数组:

Object newArray = Array.newInstance(componentType, newLength);    // 需要提供2个参数:数组的参数类型 和 数组的长度
// !!! 不能直接创建 Object[] 数组,然后进行类型转换

1. 通过 Array.getLength() 获取数组长度;

2. 通过如下方法获取新数组的类型:

  • 1)获取原数组的类的对象;
  • 2)确认它确实是数组;
  • 3)使用 Class 类的 getComponentType() 确定数组的正确类型;
// 具体的实现代码
public
static Object goodCopyOf(Object a, int newLength) { // 此处使用的 Object,不是 Object[]。目的是可以扩展任意类型的数组,不仅是对象数组。整数数组 int[] 可以转换成 Object,但不能转成 Object[](ClassCastException) 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; }

 


5.8 继承的设计技巧

1. 将 公共操作 和 字段 放在超类(Place common operations and fields in the superclass);
 
2. 不要使用 protected 字段(Don't use protected fields),原因是:
  • 1)子类集合是无限制的,任何人都可由类派生一个子类,并直接访问 protected 的实例字段,从而破坏封装性;
  • 2)同一包中的任何类都可访问 protected 字段,不管是否为这个类的子类;
 
3. 使用继续实现 “is-a” 关系(Use inheritance to model the “is-a” relationship);
    如 钟点工就不应该继承 Employee 类,如继承就有两个字段表示薪水,一个工资,一个时薪
 
4. 除非所有继承的方法都有意义,否则不要使用继承(Don't use inheritance unless all inherited methods make sense);
 
5. 在覆盖方法时,不要改变预期的行为(Don't change the expected behavior when you override a method);
     替换原则包含:语法 + 行为
 
6. 使用多态,而非 类型信息(Use polymorphism, not type information);
   
 
7. 不要 滥用 反射(Don't overuse reflection);
 
posted on 2021-12-27 22:53  bruce_he  阅读(26)  评论(0编辑  收藏  举报