java核心技术卷1---第5章 继承

继承(is - a)

基本思想:基于已有的类创建新的类。

增加新的方法和字段,使新类适应新的情况

类,子类,超类

原则上最一般的方法放在超类,特殊方法放在子类

覆盖方法

子类中提供一个新的方法覆盖超类中的方法

子类访问超类中的方法,使用super关键字

子类构造器不能访问超类中的私有字段,用一个构造器初始化私有字段,利用super语法调用构造器,必须是子类构造器第一条内容

class Manager{
	super.(name,age,salary);
}
this的含义:
1. 指示隐式参数的引用
2. 调用该类中的其他构造器

继承层次

由一个公共超类派生出所有类的集合称为继承层次

多态

多态:一个对象变量可以指示多种实际类型的现象

动态绑定:运行时自动选择适当的方法

子类每个对象也是超类的对象,is-a指的是程序中出现超类对象的任何地方都可以用子类对象替代

不能把超类的引用赋给子类变量

子类 对象 = 超类 (×)

子类引用数组可以转换为超类引用的数组

​ 对于private方法,static方法,final方法,构造器,编译器可以准确知道应该调用哪个方法

调用方法过程:

1. 虚拟机获取e的实际类型方法表
2. 虚拟机查找有该方法的类
3. 虚拟机调用这个方法

阻止继承 final和方法

我们希望阻止人们利用某个类定义子类,很简单,把他变成final类

强制类型转换

某个类的对象引用转换或另外一个类的对象引用

进行强制类型转换,才能通过运行时的检查

一个良好的程序设计习惯:在进行强制类型转换之前,查看是否能够成功的转换

  • 只有在继承层次内进行强转
  • 将超类强转成子类之前用instanceof检查
 Manager a = new Manager("a", 1, 1, 1, 1);    
Employee b = new Employee("b", 2, 2, 2, 2);    
if (a instanceof Employee){        System.out.println("√");    }}

抽象类

祖先类拥有一般性,将它作为派生其他类的基类

受保护访问

把类中的字段标记为private ,方法标记为public

需求:限制超类中的某个方法只允许子类访问,希望允许子类的方法访问超类的某个字段

protected 保护字段

private 仅对本类可见
public 对外部完全可见
protected 对本包和所有子类可见
默认 对本包可见

Object:所有类的超类

Object类型变量

Object 类型变量只能用于作为各种值的一个泛型容器

清楚对象的原始类型,进行相应的强制类型转换

equals方法

equals方法用于检测一个对象是否等于另外一个对象

equals方法确定两个对象引用是否相等,如果两个对象引用相等,两个对象肯定就相等

只有两个对象属于同一个类时,才可能相等

相等测试与继承

编写一个完美的equals方法:

  1. 显式参数命名为otherObject,稍后强转为一个名为other的变量

  2. 检测this和otherObject是否相等

  3. 检测otherObject是否为null,如果是返回false

  4. 比较this与otherObject的类

  5. 将otherObject强转为相应类型变量

  6. 根据相等性概念的要求比较字段,用==比较基本类型字段,用equals比较对象字段,如果所有字段都匹配,返回true,否则 false

  7. 子类重新定义equals,其中包含一个super.equals(other)调用

public boolean equals(Object otherObject){
    if (this == otherObject){
        return true;
    }
    if (otherObject == null){
        return false;
    }
    if (getClass() != otherObject.getClass()){
        return false;
    }
    Employee other = (Employee)otherObject;
    return Objects.equals(name,other.name) &&salary == other.salary && Objects.equals(hireDay,other.hireDay);

}

hashCode方法

散列码是由对象导出的一个整型值,每个对象都有一个默认的散列码,其值由对象存储地址得出,通过hashCode码判断地址是否相同

toString方法

返回表示对象值的一个字符串,可以由子类调用,随处可见

toString 方法是一种非常有用的调试工具

强烈建议自定义的每一个类添加toString方法,自己和他人都收益
class EqualsTest{
    public static void main(String[] args) {
        Employee a_a1 = new Employee("A A", 75000, 1987, 12, 15);
        Employee a_a2 = a_a1;
        Employee a_a3 = new Employee("A_A", 75000, 1987, 12, 15);
        Employee b_b = new Employee("B_B", 50000, 1989, 10, 1);

        System.out.println("a_a1 == a_a2" + (a_a1 == a_a2));
        System.out.println("a_a1 == a_a3" + (a_a1 == a_a3));
        System.out.println("a_a1.equals(a_a3)" + (a_a1.equals(a_a3)));
        System.out.println("a_a1.equals(b_b)" + (a_a1.equals(b_b)));

        Manager c_c = new Manager("C_C", 80000, 1987, 12, 15);
        Manager boss = new Manager("C_C", 80000, 1987, 12, 15);
        boss.setBonus(5000);

        System.out.println("boss.toString():" + boss);
        System.out.println("c_c.equals(boss):" + c_c.equals(boss));

        System.out.println("a_a1.hashCode()" + a_a1.hashCode());
        System.out.println("a_a3.hashCode()" + a_a3.hashCode());
        System.out.println("b_b.hashCode()" + b_b.hashCode());
        System.out.println("c_c.hashCode()" + c_c.hashCode());


    }



}
class Employee{
    private String name;
    private double salary;
    private LocalDate hireDay;

    public Employee(String name, double salary,int year,int month,int day) {
        this.name = name;
        this.salary = salary;
        hireDay = LocalDate.of(year,month,day);
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public LocalDate getHireDay() {
        return hireDay;
    }

    public void raiseSalary(double byPercent){
        double raise = salary * byPercent / 100;
    }

    public boolean equals(Object otherObject){
        if (this == otherObject){
            return true;
        }
        if (otherObject == null){
            return false;
        }
        if (getClass() != otherObject.getClass()){
            return false;
        }
        Employee other = (Employee)otherObject;
        return Objects.equals(name,other.name) &&salary == other.salary && Objects.equals(hireDay,other.hireDay);

    }

    public int hashCode(){
        return Objects.hash(name,salary,hireDay);
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", salary=" + salary +
                ", hireDay=" + hireDay +
                '}';
    }
}
class Manager1 extends Employee{
    private double bonus;

    public Manager1(String name, double salary, int year, int month, int day) {
        super(name, salary, year, month, day);
        bonus = 0;
    }

    @Override
    public double getSalary() {
        double baseSalary = super.getSalary();
        return baseSalary + bonus;
    }

    public void setBonus(double bonus){
        this.bonus = bonus;
    }

    @Override
    public boolean equals(Object otherObject) {
        if (!super.equals(otherObject))return false;
        Manager1 other = (Manager1) otherObject;
        return bonus == other.bonus;
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), bonus);
    }

    @Override
    public String toString() {
        return super.toString() + "[bonus=" + bonus + "]";
    }
}

泛型数组列表

java 允许运行时确定数组的大小

Arraylist类,自动调整数组容量,不需要为此编写代码

弥补数组短板

访问或改变数组元素用get set 方法,而不是较简单的下标

存在取长补短的方法

既可以灵活扩展数组,又可以方便访问数组下标


ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee("d",3,3,3,3));
        employees.add(new Employee("d",3,3,3,3));
        employees.add(new Employee("d",3,3,3,3));
        employees.add(new Employee("d",3,3,3,3));
        Employee[] employees1 = new Employee[employees.size()];
        employees.toArray(employees1);
       

类型化与原始数组列表的兼容性

public void update(ArrayList list){}
    public ArrayList find(String query){}
    ArrayList<Employee> result = e.find(query);//报错
    ArrayList<Employee> result = (ArrayList<Employee>) e.find(query);//还是报错
    @SuppressWarnings("unchecked")
    ArrayList<Employee> result = (ArrayList<Employee>) e.find(query);//√

//确保代码没问题,再写注解

参数数量可变的方法 (可变参数)

public void print(int a,Object ... args){}

枚举类

构造器私有

public static void main(String[] args) {
        Num[] values = Num.values();
        for (Num value : values) {
            System.out.println(value);
        }


    }
}
enum Num{
    PI(3.14),E(2.7);
    private double d;

    Num(double d) {
        this.d = d;
    }

    public double getD() {
        return d;
    }

    public void setD(double d) {
        this.d = d;
    }

    @Override
    public String toString() {
        return "Num{" +
                "d=" + d +
                '}';
    }
}


反射

反射是 一种功能强大且复杂的机制,适合开发工具的程序员,不适合一般的应用程序员

反射库提供了丰富且精巧的工具集,用来编写能够操纵java代码的程序,使用反射,

java可以支持用户界面生成器,对象关系映射器,需要动态查询类能力的开发工具

能分析类能力的程序叫反射

作用:

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

Class类

获得class类对象的方法:

  1. 对象. getClass();
  2. Class.forName(类名);
  3. T.class;

用 == 运算符实现两个类对象的比较 , instanceof 又可能是父子关系,就不可行

构建对象实例

var className = "java.util.Random";
Class cl = Class.forName(className);
Object obj = cl.getConstructor().newInstance();

声明异常入门

运行发生错误,程序抛出一个异常,没有提供处理器,程序会终止,在控制台打印异常类型

异常分:1. 非检查型异常 2. 检查型异常

不是所有错误都能避免的,需要一个throws声明

使用反射分析类

反射机制最重要的内容—检查类的结构

publicclass java.util.Date
{
   public java.util.Date(int, int, int, int, int, int);
   public java.util.Date(java.lang.String);
   public java.util.Date();
   public java.util.Date(long);
   public java.util.Date(int, int, int);
   public java.util.Date(int, int, int, int, int);

   public boolean after(java.util.Date);
   public boolean before(java.util.Date);
   public boolean equals(java.lang.Object);
   public java.lang.String toString();
   public int hashCode();
   public java.lang.Object clone();
   public int compareTo(java.util.Date);
   public volatile int compareTo(java.lang.Object);
   private void readObject(java.io.ObjectInputStream);
   private void writeObject(java.io.ObjectOutputStream);
   private final sun.util.calendar.BaseCalendar$Date normalize();
   private final sun.util.calendar.BaseCalendar$Date normalize(sun.util.calendar.BaseCalendar$Date);
   public static long parse(java.lang.String);
   public int getDate();
   public static java.util.Date from(java.time.Instant);
   public long getTime();
   public void setTime(long);
   public static long UTC(int, int, int, int, int, int);
   private static final java.lang.StringBuilder convertToAbbr(java.lang.StringBuilder, java.lang.String);
   private final sun.util.calendar.BaseCalendar$Date getCalendarDate();
   private static final sun.util.calendar.BaseCalendar getCalendarSystem(long);
   private static final sun.util.calendar.BaseCalendar getCalendarSystem(sun.util.calendar.BaseCalendar$Date);
   private static final sun.util.calendar.BaseCalendar getCalendarSystem(int);
   public int getDay();
   public int getHours();
   private static final synchronized sun.util.calendar.BaseCalendar getJulianCalendar();
   static final long getMillisOf(java.util.Date);
   public int getMinutes();
   public int getMonth();
   public int getSeconds();
   private final long getTimeImpl();
   public int getTimezoneOffset();
   public int getYear();
   public void setDate(int);
   public void setHours(int);
   public void setMinutes(int);
   public void setMonth(int);
   public void setSeconds(int);
   public void setYear(int);
   public java.lang.String toGMTString();
   public java.time.Instant toInstant();
   public java.lang.String toLocaleString();

   private static final sun.util.calendar.BaseCalendar gcal;
   private static sun.util.calendar.BaseCalendar jcal;
   private transient long fastTime;
   private transient sun.util.calendar.BaseCalendar$Date cdate;
   private static int defaultCenturyStart;
   private static final long serialVersionUID;
   private static final [Ljava.lang.String; wtb;
   private static final [I ttb;
}
D:\Java\jdk8\bin\java.exe "-javaagent:D:\java\IntelliJ IDEA 2021.2.3\lib\idea_rt.jar=62728:D:\java\IntelliJ IDEA 2021.2.3\bin" -Dfile.encoding=UTF-8 -classpath D:\java\jdk8\jre\lib\charsets.jar;D:\java\jdk8\jre\lib\deploy.jar;D:\java\jdk8\jre\lib\ext\access-bridge-64.jar;D:\java\jdk8\jre\lib\ext\cldrdata.jar;D:\java\jdk8\jre\lib\ext\dnsns.jar;D:\java\jdk8\jre\lib\ext\jaccess.jar;D:\java\jdk8\jre\lib\ext\jfxrt.jar;D:\java\jdk8\jre\lib\ext\localedata.jar;D:\java\jdk8\jre\lib\ext\nashorn.jar;D:\java\jdk8\jre\lib\ext\sunec.jar;D:\java\jdk8\jre\lib\ext\sunjce_provider.jar;D:\java\jdk8\jre\lib\ext\sunmscapi.jar;D:\java\jdk8\jre\lib\ext\sunpkcs11.jar;D:\java\jdk8\jre\lib\ext\zipfs.jar;D:\java\jdk8\jre\lib\javaws.jar;D:\java\jdk8\jre\lib\jce.jar;D:\java\jdk8\jre\lib\jfr.jar;D:\java\jdk8\jre\lib\jfxswt.jar;D:\java\jdk8\jre\lib\jsse.jar;D:\java\jdk8\jre\lib\management-agent.jar;D:\java\jdk8\jre\lib\plugin.jar;D:\java\jdk8\jre\lib\resources.jar;D:\java\jdk8\jre\lib\rt.jar;D:\java\projectfu\out\production\projectfu;C:\Users\11021\.m2\repository\junit\junit\4.13.1\junit-4.13.1.jar;C:\Users\11021\.m2\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar com.fu.javatec.chapter05.ReflectionTest
Enter class name (e.g. java.util.Date): 
java.util.Date
java.lang.Object---====
public class java.util.Date
{
   public java.util.Date(int, int, int, int, int, int);
   public java.util.Date(java.lang.String);
   public java.util.Date();
   public java.util.Date(long);
   public java.util.Date(int, int, int);
   public java.util.Date(int, int, int, int, int);

   public boolean after(java.util.Date);
   public boolean before(java.util.Date);
   public boolean equals(java.lang.Object);
   public java.lang.String toString();
   public int hashCode();
   public java.lang.Object clone();
   public int compareTo(java.util.Date);
   public volatile int compareTo(java.lang.Object);
   private void readObject(java.io.ObjectInputStream);
   private void writeObject(java.io.ObjectOutputStream);
   private final sun.util.calendar.BaseCalendar$Date normalize();
   private final sun.util.calendar.BaseCalendar$Date normalize(sun.util.calendar.BaseCalendar$Date);
   public static long parse(java.lang.String);
   public int getDate();
   public static java.util.Date from(java.time.Instant);
   public long getTime();
   public void setTime(long);
   private static final java.lang.StringBuilder convertToAbbr(java.lang.StringBuilder, java.lang.String);
   private final sun.util.calendar.BaseCalendar$Date getCalendarDate();
   private static final sun.util.calendar.BaseCalendar getCalendarSystem(long);
   private static final sun.util.calendar.BaseCalendar getCalendarSystem(int);
   private static final sun.util.calendar.BaseCalendar getCalendarSystem(sun.util.calendar.BaseCalendar$Date);
   public int getDay();
   public int getHours();
   private static final synchronized sun.util.calendar.BaseCalendar getJulianCalendar();
   static final long getMillisOf(java.util.Date);
   public int getMinutes();
   public int getMonth();
   public int getSeconds();
   private final long getTimeImpl();
   public int getTimezoneOffset();
   public int getYear();
   public void setDate(int);
   public void setHours(int);
   public void setMinutes(int);
   public void setMonth(int);
   public void setSeconds(int);
   public void setYear(int);
   public java.lang.String toGMTString();
   public java.time.Instant toInstant();
   public java.lang.String toLocaleString();
   public static long UTC(int, int, int, int, int, int);

   private static final sun.util.calendar.BaseCalendar gcal;
   private static sun.util.calendar.BaseCalendar jcal;
   private transient long fastTime;
   private transient sun.util.calendar.BaseCalendar$Date cdate;
   private static int defaultCenturyStart;
   private static final long serialVersionUID;
   private static final [Ljava.lang.String; wtb;
   private static final [I ttb;
}

Process finished with exit code 0

使用反射分析对象

如何查看任意对象数据字段的名字和类型

  • 获得对应的Class对象
  • 在这个Class对象上调用getDeclaredFields

查看对象指定字段的内容,利用反射可以查看在编译时不知道的对象字段

可以获得值,可以设置值

getDeclaredFileds获得所有的数据字段,使用setAccessible便利方法将所有字段设置为可访问的

package com.fu.javatec.chapter05;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;

public class Demo07 { }
class ObjectAnalyzer{
    private ArrayList<Object> visited = new ArrayList<>();
                                                                                                                                     
    public String toString(Object obj) throws ReflectiveOperationException{
        if (obj == null) return "null";
        if (visited.contains(obj)) return "...";
        visited.add(obj);
        Class cl = obj.getClass();
        if (cl == String.class) return (String)obj;
        if (cl.isArray()){
            String r = cl.getComponentType() + "[]{";
            for (int i = 0; i < Array.getLength(obj); i++) {
                if (i > 0) r += ",";
                Object val = Array.get(obj, i);
                if (cl.getComponentType().isPrimitive()) r += val;
                else r += toString(val);
            }
            return r + "}";
        }
        String r = cl.getName();
        do {
            r += "l";
            Field[] fields = cl.getDeclaredFields();
            AccessibleObject.setAccessible(fields,true);
            for (Field f : fields) {
                if (!Modifier.isStatic(f.getModifiers())){
                    if (!r.endsWith("{")) r += ",";
                    r += f.getName() + "=";
                    Class t = f.getType();
                    Object val = f.get(obj);
                    if (t.isPrimitive()) r += val;
                    else r += toString(val);
                }
            }
            r += "]";
            cl = cl.getSuperclass();
        }
        while(cl != null);
        return r;
    }
}


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

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

要获得新数组元素类型:

  1. 获得a数组的类对象
  2. 确认他是一个数组
  3. 使用Class类的getComponentType方法确认数组的正确类型
package com.fu.javatec.chapter05;

import java.lang.reflect.Array;
import java.util.Arrays;

public class Demo08 {
    public static void main(String[] args) {
        int []a = {1,2,3};
        a = (int[]) goodCopyOf(a,10);
        System.out.println(Arrays.toString(a));

        String[] b = {"Tom","Dick","Harry"};
        b = (String[]) goodCopyOf(b,10);

    }
    public static Object[] badCopyOf(Object[] a,int newLength){
        Object[] newArray = new Object[newLength];
        System.arraycopy(a,0,newArray,0,Math.min(a.length,newLength));
        return newArray;
    }
    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没有提供途径将一个方法的存储地址传给另外一个方法

高斯林:方法指针是很危险的,而且容易出错

Field类中的get方法查看一个对象的字段,Method类有invoke方法,调用包装在当前Method对象中的方法

Object invoke(Object obj,Object... args)

第一个参数是隐式参数,其余的对象提供显式参数

对于静态方法,第一个参数可以省略,把他设置为null

打印数学函数


package com.fu.javatec.chapter05;

import java.lang.reflect.Method;
import java.sql.Ref;

public class Demo09 {
}
class MethodTableTest{
    public static void main(String[] args) throws ReflectiveOperationException{
        Method square = MethodTableTest.class.getMethod("square", double.class);
        Method sqrt = Math.class.getMethod("sqrt", double.class);

        printTable(1,10,10,square);
        printTable(1,10,10,sqrt);

    }
    public static double square(double x){
        return x * x;
    }
    
    
    public static void printTable(double from,double to,int n,Method f) throws ReflectiveOperationException {
        System.out.println(f);
        double dx = (to - from) / (n - 1);
        for (double x = from;x <= to;x += dx){
            double y = (Double) f.invoke(null , x);
            System.out.printf("%10.4f | %10.4f%n",x,y);
        }
    }
}



建议仅在绝对必要的时候才在你自己的程序中使用Method对象,通常更好的做法是使用接口以及java 8 引入的 Lambda 表达式

特别强调:建议 java 开发者不要使用回调函数的Method对象,可以使用回调的接口,这样代码执行速度块,易于维护

继承设计技巧

1. 将公共操作和字段放在超类中
2. 不要使用受保护的字段
3. 使用继承实现 is-a 关系
4. 除非所有继承的方法有意义,否则不用继承
5. 在覆盖方法时,不要改变预期的行为
6. 使用多态,而不要使用类型信息,使用多态或接口更容易维护和发展
7. 不要滥用反射
上一篇:SDUT PTA JAVA12 常用类(日期、数学、封装类、随机数等)习题


下一篇:JAVA---自定义数组的工具类