java-继承和多态

注意
本文档属于个人笔记,个人水平有限,请酌情采纳,有任何错误可在评论区指出

继承满足“is-a”规则,即Manager is a Employee

/images/all/image-20221113161049769.png

如果子类的构造方法没有显式地调用超类的构造方法,则将自动地调用超类的无参构造,如果没有超类没有定义无参构造方法,编译报错。

this关键字的用途:

  • 引用隐式参数
  • 调用该类其他的构造方法

super关键字的用途:

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

调用其他构造方法的语句只能出现在构造方法中的第一行

 1import java.util.Date;
 2import java.util.GregorianCalendar;
 3
 4public class ManagerTest {
 5    public static void main(String[] args) {
 6        Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
 7        boss.setBonus(5000);
 8
 9        Employee[] staff = new Employee[3];
10
11		// 这里实际上使用了多态
12        staff[0] = boss;
13        staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
14        staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);
15
16 
17        for (Employee e : staff) {
18            System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
19        }
20    }
21}
22
23class Employee {
24
25    private String name;
26    private double salary;
27    private Date hireDay;
28
29    /**
30     * @param n     the employee's name
31     * @param s     the salary
32     * @param year  the hire year
33     * @param month the hire month
34     * @param day   the hire day
35     */
36    public Employee(String n, double s, int year, int month, int day) {
37        name = n;
38        salary = s;
39        GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
40        hireDay = calendar.getTime();
41    }
42
43    public String getName() {
44        return name;
45    }
46
47    public double getSalary() {
48        return salary;
49    }
50
51    public Date getHireDay() {
52        return hireDay;
53    }
54
55    public void raiseSalary(double byPercent) {
56        double raise = salary * byPercent / 100;
57        salary += raise;
58    }
59}
60
61class Manager extends Employee {
62    private double bonus;
63
64    /**
65     * @param n     the manager's name
66     * @param s     the salary
67     * @param year  the hire year
68     * @param month the hire month
69     * @param day   the hire day
70     */
71    public Manager(String n, double s, int year, int month, int day) {
72        // 调用父类的构造方法必须出现在子类子类构造方法的第一行
73        super(n, s, year, month, day);
74        bonus = 0;
75    }
76
77    @Override
78    public double getSalary() {
79        double baseSalary = super.getSalary();
80        return baseSalary + bonus;
81    }
82
83    public void setBonus(double b) {
84        bonus = b;
85    }
86
87}

“is-a”规则的另一种表述法是置换法则:程序中出现超类对象的任何地方都可以用子类对象置换

Java中的对象变量都是多态的,Employee变量既可以引用一个Employee对象,也可以引用Employee的任何一个子类(比如Manager)的对象

当把子类的对象赋给父类的变量时,就发生了向上造型

1// Employee是声明类型
2Employee[] staff = new Employee[3];
3// 等号右边是动态类型(可能是Employee对象,也可能是它的子类对象)
4staff[0] = boss;
5staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
6staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);

注意: 不能将一个超类的引用赋给子类变量(不是所有雇员都是经理)

  • 当通过对象变量调用函数的时候,调用哪个函数这件事情叫做绑定

    • 静态绑定:根据变量的声明类型来决定

    • 动态绑定:根据变量的动态类型来决定

java中默认绑定都是动态绑定

如果想让一个类无法被继承,可以在class关键字前加上final关键字,这个类的所有方法也将自动加上final关键字

如果想让某个类的方法不能被重写,可以在方法名前加上final关键字

将方法或类声明为final主要目的是确保它们不会在子类中改变。

有时候希望将超类转换为子类,这样就能调用子类的方法。但这一般是超类的设计问题。应该避免这种转换。

将超类转换为子类之前,应该使用instanceof进行检查, 避免出现类型转换异常(ClassCastException)

 1Employee[] staff = new Employee[3];
 2// staff[0]引用的是Manager对象, staff[1]和staff[2]引用的是Employee对象
 3staff[0] = boss;
 4staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
 5staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);
 6
 7// 假设要将Employee对象转换为Manager对象
 8Manager ma = (Manager) staff[0];
 9// Error, staff[1]引用的是Employee对象,会报ClassCastException异常
10Manager mb = (Manager) staff[1];
11// 正确做法: 转换前需要检查该对象是否属于转化后的对象类型
12if (staff[1] instanceof Manager) {
13    Manager mb = (Manager) staff[1];
14}
15
16// 注意null不会抛出异常
17System.out.println(null instanceof  Manager); //输出false

抽象类天然支持多态性,因为抽象类不能实例化,只能引用非抽象子类的对象

  • 抽象类不一定包含抽象方法,有抽象方法一定要定义为抽象类
  • 抽象类可以包含具体数据(比如name)和具体方法(比如getName)
  • 抽象类也有构造方法
  • 继承抽象类的子类必须实现所有的抽象方法
  • 抽象类永远不能实例化,所有只能通过子类对象调用对应实现的抽象方法。
 1import java.util.Date;
 2import java.util.GregorianCalendar;
 3
 4public class PersonTest
 5{
 6   public static void main(String[] args)
 7   {
 8      Person[] people = new Person[2];
 9
10      // fill the people array with Student and Employee objects
11      people[0] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
12      people[1] = new Student("Maria Morris", "computer science");
13
14      // print out names and descriptions of all Person objects
15      for (Person p : people)
16         System.out.println(p.getName() + ", " + p.getDescription());
17   }
18}
19
20abstract class Person
21{
22   private String name;
23
24   public Person(String n)
25   {
26      name = n;
27   }
28
29   public abstract String getDescription();
30
31   public String getName()
32   {
33      return name;
34   }
35
36}
37
38class Employee extends Person
39{
40   public Employee(String n, double s, int year, int month, int day)
41   {
42      // 调用抽象类的构造方法
43      super(n);
44      salary = s;
45      GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
46      hireDay = calendar.getTime();
47   }
48
49   public double getSalary()
50   {
51      return salary;
52   }
53
54   public Date getHireDay()
55   {
56      return hireDay;
57   }
58
59   public String getDescription()
60   {
61      return String.format("an employee with a salary of $%.2f", salary);
62   }
63
64   public void raiseSalary(double byPercent)
65   {
66      double raise = salary * byPercent / 100;
67      salary += raise;
68   }
69
70   private double salary;
71   private Date hireDay;
72}
73
74class Student extends Person
75{
76   /**
77    * @param n the student's name
78    * @param m the student's major
79    */
80   public Student(String n, String m)
81   {
82      // pass n to superclass constructor
83      super(n);
84      major = m;
85   }
86
87   public String getDescription()
88   {
89      return "a student majoring in " + major;
90   }
91
92   private String major;
93}

所有class默认继承自Object

除基本类型之外,所有的对象数组和基本类型数组都继承了Object

1System.out.println(new Object() instanceof Object); // true
2System.out.println(new int[2] instanceof Object); // true
3System.out.println(new Person[2] instanceof Object); // true

重写object.equals

  1package EqualsTest;
  2
  3import java.util.Date;
  4import java.util.GregorianCalendar;
  5
  6/**
  7 * This program demonstrates the equals method.
  8 * @version 1.11 2004-02-21
  9 * @author Cay Horstmann
 10 */
 11public class EqualsTest
 12{
 13   public static void main(String[] args)
 14   {
 15      Employee alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15);
 16      Employee alice2 = alice1;
 17      Employee alice3 = new Employee("Alice Adams", 75000, 1987, 12, 15);
 18      Employee bob = new Employee("Bob Brandson", 50000, 1989, 10, 1);
 19
 20      System.out.println("alice1 == alice2: " + (alice1 == alice2));
 21
 22      System.out.println("alice1 == alice3: " + (alice1 == alice3));
 23
 24      System.out.println("alice1.equals(alice3): " + alice1.equals(alice3));
 25
 26      System.out.println("alice1.equals(bob): " + alice1.equals(bob));
 27
 28      System.out.println("bob.toString(): " + bob);
 29
 30      Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15);
 31      Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
 32      boss.setBonus(5000);
 33      // System.out.println(对象),会自动调用该对象所属类的toString()方法
 34      System.out.println("boss.toString(): " + boss);
 35      System.out.println("carl.equals(boss): " + carl.equals(boss));
 36      System.out.println("alice1.hashCode(): " + alice1.hashCode());
 37      System.out.println("alice3.hashCode(): " + alice3.hashCode());
 38      System.out.println("bob.hashCode(): " + bob.hashCode());
 39      System.out.println("carl.hashCode(): " + carl.hashCode());
 40   }
 41}
 42
 43class Employee
 44{
 45   public Employee(String n, double s, int year, int month, int day)
 46   {
 47      name = n;
 48      salary = s;
 49      GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
 50      hireDay = calendar.getTime();
 51   }
 52
 53   public String getName()
 54   {
 55      return name;
 56   }
 57
 58   public double getSalary()
 59   {
 60      return salary;
 61   }
 62
 63   public Date getHireDay()
 64   {
 65      return hireDay;
 66   }
 67
 68   public void raiseSalary(double byPercent)
 69   {
 70      double raise = salary * byPercent / 100;
 71      salary += raise;
 72   }
 73
 74   @Override
 75   public boolean equals(Object otherObject)
 76   {
 77      // 1.检查是否引用自同一个对象(实际上就是检查两个对象的地址是否相同)
 78      // 注意我们的目的是判断两个对象的对应数据域是否相同,而不是只对比对象地址
 79      // 每个对象对应不同的hashCode,hashCode值即该对象的地址
 80      // 只比较相同对象的话意义不大
 81      if (this == otherObject) return true;
 82
 83      // 2.比较的对象为null,返回false
 84      if (otherObject == null) return false;
 85
 86      // 3.判断是否是同一个类的对象
 87      if (getClass() != otherObject.getClass()) return false;
 88
 89      // 4.这里我们已经知道比较的同一个class的非空对象
 90      Employee other = (Employee) otherObject;
 91
 92      // 5.检查它们的数据域是否相等
 93      // 注: 如果要检查两个数组是否相等,可以使用Arrays.equals()
 94      return name.equals(other.name) && salary == other.salary && hireDay.equals(other.hireDay);
 95   }
 96
 97   // 如果重写了equals方法,必须重写hashCode方法
 98   // 这样就能保证equals返回true时,两个对象的hashCode也一样
 99   public int hashCode()
100   {
101      return 7 * name.hashCode() + 11 * new Double(salary).hashCode() + 13 * hireDay.hashCode();
102      // 组合多个散列值时,可以使用下面这种方式
103      //       return Objects.hash(name,salary,hireDay);
104   }
105
106   public String toString()
107   {
108      return getClass().getName() + "[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay
109            + "]"; 
110   }
111
112   private String name;
113   private double salary;
114   private Date hireDay;
115}
116
117class Manager extends Employee
118{
119   public Manager(String n, double s, int year, int month, int day)
120   {
121      super(n, s, year, month, day);
122      bonus = 0;
123   }
124
125   public double getSalary()
126   {
127      double baseSalary = super.getSalary();
128      return baseSalary + bonus;
129   }
130
131   public void setBonus(double b)
132   {
133      bonus = b;
134   }
135
136   public boolean equals(Object otherObject)
137   {
138      if (!super.equals(otherObject)) return false;
139      Manager other = (Manager) otherObject;
140      // super.equals checked that this and other belong to the same class
141      return bonus == other.bonus;
142   }
143
144   public int hashCode()
145   {
146      return super.hashCode() + 17 * new Double(bonus).hashCode();
147   }
148
149   public String toString()
150   {
151      return super.toString() + "[bonus=" + bonus + "]";
152   }
153
154   private double bonus;
155}

包装器类是不可变的

如果在一个条件表达式中混合使用IntegerDouble类型,Integer值就会拆箱,提升为double, 再装箱为Double

由于包装类引用可以为null,所以可能会抛出NullPointerException异常

1Integer i1 = 1;
2Double d1 = 2.0;
3System.out.println(true ? i1 : d1); // 输出1.0
4
5Integer i2 = null;
6// error, 会抛出NullPointerException异常
7System.out.println(i2 * 3);

找出数组中的最大值

 1public class Main {
 2    public static void main(String[] args) {
 3        System.out.println(max(1,2,8.8,9.9)); // 9.9
 4    }
 5
 6    public static double max(double... values) {
 7        // 负无穷
 8        double largest = Double.NEGATIVE_INFINITY;
 9        for (int i = 0; i < values.length; i++) {
10            if (values[i] > largest) largest = values[i];
11        }
12        return largest;
13    }
14
15}
 1import java.util.Scanner;
 2
 3public class EnumTest {
 4    public static void main(String[] args) {
 5      Scanner in = new Scanner(System.in);
 6      System.out.print("Enter a size: (SMALL, MEDIUM, LARGE, EXTRA_LARGE) ");
 7      String input = in.next().toUpperCase();
 8      // 创建指定名字和类的枚举常量
 9      Size size = Enum.valueOf(Size.class, input);
10      // toString()方法返回枚举常量名
11      System.out.println("size=" + size);
12      System.out.println("abbreviation=" + size.getAbbreviation());
13      // 判断枚举常量时直接使用"=="
14      if (size == Size.EXTRA_LARGE)
15         System.out.println("Good job--you paid attention to the _.");
16    }
17}
18
19enum Size {
20    SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");
21
22    private Size(String abbreviation) {
23        this.abbreviation = abbreviation;
24    }
25
26    public String getAbbreviation() {
27        return abbreviation;
28    }
29
30    private String abbreviation;
31}

获取class对象的三种方式:

  • 使用Object类的getClass()
  • 使用Class类的静态方法forName
  • 直接使用.class

jvm为每一种类型管理一个Class对象

 1// 第一种方式
 2Employee e = new Manager("Carl Cracker", 80000, 1987, 12, 1);
 3System.out.println(e.getClass());
 4
 5// 第二种方式(注意要捕获异常)
 6// 动态修改要加载的类名
 7String className = "ManagerTest.Employee";
 8try {
 9    System.out.println(Class.forName(className));
10} catch (ClassNotFoundException ex) {
11    throw new RuntimeException(ex);
12}
13
14// 第三种方式
15// Class类实际上是一种泛型类
16Class<Employee> employeeClass = Employee.class;
17// 一个Class 对象实际上表示的是一个类型, 而这个类型未必一定是一种类
18System.out.println(int.class); // 输出int

利用反射创建一个有参构造的对象

注:new newInstance()使用的是无参构造方法

 1try {
 2    Class<?> aClass = Class.forName("ManagerTest.Employee");
 3    // 如果要使用有参构造方法,需要先获取一个构造方法
 4    Constructor<?> c = aClass.getConstructor(String.class, double.class, int.class, int.class, int.class);
 5    // 利用上面的构造方法创建对象
 6    Employee e = (Employee)c.newInstance("Carl Cracker", 80000, 1987, 12, 15);
 7    System.out.println(e.getName());
 8} catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException ex) {
 9    throw new RuntimeException(ex);
10}

通过反射机制获取类的成员变量,方法,构造方法等

 1package cc.bnblogs;
 2
 3import java.lang.reflect.Field;
 4import java.lang.reflect.InvocationTargetException;
 5import java.lang.reflect.Method;
 6import java.util.Arrays;
 7
 8public class Main {
 9    public static void main(String[] args) throws
10            ClassNotFoundException, InstantiationException,
11            IllegalAccessException, NoSuchFieldException {
12        Class<?> aClass = Class.forName("cc.bnblogs.Point");
13        Point p = (Point)aClass.newInstance();
14
15        // 获取类的成员变量
16        // 只能获取和修改public型的值
17        Field x = aClass.getDeclaredField("x");
18        Field y = aClass.getDeclaredField("y");
19        Field str = aClass.getDeclaredField("color");
20                
21        // 要获取private变量的值,需要使用setAccessible(true)
22        x.setAccessible(true);
23        Object o = x.get(p);
24      	System.out.println(o);
25        
26
27        // 获取Point类的所有成员变量
28        Field[] fields = aClass.getDeclaredFields();
29        System.out.println(Arrays.toString(fields));
30
31        // error,获取对象的私有成员变量
32        // System.out.println(x.get(p));
33        System.out.println(str.get(p));
34
35
36        try {
37            // 获取Point类的setColor方法
38            Method mSetColor = aClass.getMethod("setColor", String.class);
39            // 修改point的颜色
40            mSetColor.invoke(p,"black");
41
42            System.out.println(p.getColor());
43
44            // 获取所有声明的方法
45            Method[] methods = aClass.getDeclaredMethods();
46            System.out.println(Arrays.toString(methods));
47            
48            // 使用Modifier类获取权限修饰符的名称
49            System.out.println(Modifier.toString(aClass.getModifiers()));
50
51        } catch (NoSuchMethodException | InvocationTargetException e) {
52            throw new RuntimeException(e);
53        }
54    }
55}
56
57
58class Point {
59    private int x;
60    private int y;
61
62    public String color;
63
64    Point() {
65
66    }
67
68    Point(int x,int y) {
69        this.x = x;
70        this.y = y;
71    }
72
73
74    public String getColor() {
75        return color;
76    }
77
78    public void setColor(String color) {
79        this.color = color;
80    }
81
82    public int getX() {
83        return x;
84    }
85
86    public void setX(int x) {
87        this.x = x;
88    }
89
90    public int getY() {
91        return y;
92    }
93
94    public void setY(int y) {
95        this.y = y;
96    }
97
98}

反射机制实现访问父类的成员变量

 1Person e = new Employee("Harry Hacker", 50000, 1989, 10, 1);
 2// 获取Employee的父类
 3Class<?> aClass = Class.forName("PersonTest.Employee").getSuperclass();
 4// 不能这样获取
 5// e.getClass(); 父类引用子类对象,e实际上还是Employee类
 6System.out.println(aClass);
 7// 获取父类的成员变量name
 8Field name = aClass.getDeclaredField("name");
 9name.setAccessible(true);
10Object o = name.get(e);
11System.out.println(o);

利用反射创建泛型数组

java.lang.reflect包中的Array类允许动态地创建数组。例如, 将这个特性应用到Array 类中的copyOf方法实现中,实现数组的动态拓展

 1import java.lang.reflect.Array;
 2import java.util.Arrays;
 3
 4public class CopyOfTest {
 5    public static void main(String[] args) {
 6        int[] a = {1, 2, 3};
 7        a = (int[]) goodCopyOf(a,10);
 8        System.out.println(Arrays.toString(a));
 9        
10        String[] b = {"Tom", "Dick", "Harry"};
11        b = (String[]) goodCopyOf(b,10);
12        System.out.println(Arrays.toString(b));
13
14        System.out.println("The following call will generate an exception.");
15        // Object不能强制转换为String
16        // b = (String[]) badCopyOf(b,10);
17    }
18
19    public static Object[] badCopyOf(Object[] a, int newLength) {
20        Object[] newArray = new Object[newLength];
21        System.arraycopy(a,0,newArray,0,Math.min(a.length,newLength));
22        return newArray;
23    }
24
25    public static Object goodCopyOf(Object a, int newLength) {
26        Class cl = a.getClass();
27        if (!cl.isArray()) return null;
28        Class componentType = cl.getComponentType();
29        int length = Array.getLength(a);
30        Object newArray = Array.newInstance(componentType,newLength);
31        System.arraycopy(a,0,newArray,0,Math.min(length,newLength));
32        return newArray;
33    }
34}

利用反射调用任意方法

  • 调用静态方法,不需要获取类对象
  • 调用非静态方法,需要获取类对象
  • 反射调用私有静态方法(getDeclaredMethod,setAccessible)
  • 反射调用私有非静态方法(getDeclaredMethod,setAccessible)
 1package CopyofTest;
 2
 3import java.lang.reflect.Constructor;
 4import java.lang.reflect.InvocationTargetException;
 5import java.lang.reflect.Method;
 6
 7public class CopyOfTest {
 8    public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
 9        Class<?> aClass = Class.forName("CopyofTest.Point");
10        // 有参构造器
11        Constructor<Point> constructor = Point.class.getConstructor(int.class, int.class);
12        Point p = constructor.newInstance(1, 2);
13
14        // 调用get和set方法
15        Method getX = aClass.getMethod("getX");
16        System.out.println(getX.invoke(p));
17
18        Method setX = aClass.getMethod("setX",int.class);
19        setX.invoke(p,10);
20        System.out.println(getX.invoke(p));
21
22        // 调用private非静态方法
23        Method privateMethod = aClass.getDeclaredMethod("printStr");
24        privateMethod.setAccessible(true);
25        String str = (String) privateMethod.invoke(p);
26        System.out.println(str);
27
28        // 调用private静态方法
29        Method privateStaticMethod = aClass.getDeclaredMethod("print");
30        privateStaticMethod.setAccessible(true);
31
32        // 调用静态函数, 第一个对象参数设置为null
33        privateStaticMethod.invoke(null);
34    }
35}
36
37
38class Point {
39    private int x;
40    private int y;
41
42    public Point(int x,int y) {
43        this.x = x;
44        this.y = y;
45    }
46
47    public void setX(int x) {
48        this.x = x;
49    }
50
51    public int getX() {
52        return x;
53    }
54
55    public void setY(int y) {
56        this.y = y;
57    }
58
59    public int getY() {
60        return y;
61    }
62
63    private String printStr() {
64        return "point";
65    }
66
67    private static void print() {
68        System.out.println("print");
69    }
70}