zoukankan      html  css  js  c++  java
  • Java复习3-类的继承

    前言

    本次学习面向对象设计的另外一个基本概念:继承(inheritance)。这是Java程序设计中的一项核心技术。另外,还要学习反射(reflection)的概念。

    继承

    类、超类、子类

    public class Manager extends Employee{
        //...
    }
    

    关键字extends表明正在构造的新类派生于一个已存在的类。已存在的类称为超类(superclass)基类(base class)父类(parent class); 新类称为子类(sbclass)派生类(derived class)孩子类(childe class).

    覆盖方法

    子类继承父类的字段和方法,但有些方法子类想要修改,可以使用覆盖(override)

    public class Employee {
    
        private String name;
        private int salary;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getSalary() {
            return salary;
        }
    
        public void setSalary(int salary) {
            this.salary = salary;
        }
    }
    public class Manager extends Employee {
    
        private int bonus;
    
        public int getBonus() {
            return bonus;
        }
    
        public void setBonus(int bonus) {
            this.bonus = bonus;
        }
    
        @Override
        public String getName() {
            return "manager: " + super.getName();
        }
    
        @Override
        public int getSalary() {
            return super.getSalary() + bonus;
        }
    }
    
    • extends关键字记得带s
    • 超类的private字段是不能直接在子类中调用的,子类只能调用父类的protected和默认方法
    • 覆盖的要求是完全一致,即子类的方法名,参数类型和顺序,返回值要完全一致
    • 对于要覆盖的方法要添加注解@Overide
    • 想要调用父类的同名方法,使用supper
    • 子类覆盖父类的方法的权限不可以比父类小,父类是public的,子类也只能是public,父类是protected,子类不能是private

    值得关注的是子类不能继承父类的private相关字段和方法

    多态

    Employee manager = new Manager();
    

    可以将子类赋值给父类。那么,我们创建多个子类,都可以赋值给Employee,employee在运行时可以知道具体是哪个子类的实例,但只能执行父类已有的方法。即子类新加的方法不能执行。子类覆盖的方法可以执行。

    一个对象变量可以指示多种实际类型的现象被称为多态(polymorphism)。在运行时能够自动地选择调用哪个方法的现象称为动态绑定(dynamic binding).

    Java不支持多继承,一个类只能继承一个类,而不是多个。要想要实现多个,可以使用接口。

    所有的类都继承Object对象。

    多态可以用关系is-a来描述,表明程序中出现超类的任何地方都可以用子类对象置换。

    理解方法调用

    假设要调用x.f(args), 隐式参数x声明为类C的一个对象。下面是调用过程的详细描述:

    1)编译器查看对象的声明类型和方法名。假设调用x.f(args),且隐士参数x声明为C类对象。需要注意的是:有可能存在多个名字为f,但参数类型不一样的方法。例如,可能存在多个名字为f,但参数类型不一样的方法。例如,可能存在方法f(int)和方法f(String). 编译器会一一列举所有C类中名为f的方法和其超类中访问属性为public且名为f的方法(超类的私有方法不可访问)。

    至此,编译器已获得所有可能被调用的候选方法。

    2)接下来,编译器将查看调用方法时提供的参数类型。如果在所有名为f的方法中存在一个与提供的参数类型完全匹配,将选择这个方法。这个过程被称为重载解析(overloading resolution)。例如,对于调用x.f("Hello"),编译器将会挑选f(String), 而不是f(int). 由于允许类型转换(int可以转double,Manager可以转Employee), 所以这个过程可能很复杂。如果编译器找不到与参数匹配的方法,或发现经过类型转换后有多个方法与之匹配,将会报告一个错误。

    至此,编译已获得需要调用的方法名字和参数类型。

    3)如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,我们将这种调用方式成为静态绑定(static binding)。与之对应,调用的方法依赖于隐士参数的实际类型,并且在运行时实现动态绑定。

    4)当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。假设x的实际类型是D, 它是C类的子类。如果D类定义了方法f(String), 就直接调用它,否则,将在D类的超类中寻找f(String),以此类推。

    每次调用方法都要进行搜索,时间开销相当大。因此,虚拟机预先为每个类创建了一个方法表(method table),其中列出了所有方法的签名和实际调用的方法。这样一来,在真正调用方法的时候,虚拟机仅查找这个表就行了。

    动态绑定有一个非常重要的特性:无需对现存程序进行扩展。

    阻止继承:final类和方法

    有时候,可能希望阻止人们利用某个类定义的子类。不允许扩展的类被成为final类。如果在定义类的时候使用了final修饰符就表明这个类是final类。

    类的特定方法也可以声明为final的。这样,子类就不能覆盖这个方法(final类中的所有方法自动成为final方法)。

    我们将方法声明为final的主要目的是:确保他们不会在子类中改变语义。

    强制转换

    只能在继承层次内进行类型转换。

    在将超类转换成子类之前,应该使用instanceof进行检查。

    抽象类

    用abstract修饰的类是抽象类。用abstract修饰的方法是抽象方法。

    抽象类不能实例化。抽象方法没有方法体。

    受保护访问

    1. 仅本类可见--private
    2. 所有类可见--public
    3. 对本包和所有子类可见--protected
    4. 对本包可见--默认,不需要修饰符

    equals方法

    java.util.Objects#equals

    public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }
    

    java.util.Arrays#equals(long[], long[])

     public static boolean equals(long[] a, long[] a2) {
        if (a==a2)
            return true;
        if (a==null || a2==null)
            return false;
    
        int length = a.length;
        if (a2.length != length)
            return false;
    
        for (int i=0; i<length; i++)
            if (a[i] != a2[i])
                return false;
    
        return true;
    }
    

    hashcode

    java.util.Objects#hashCode

    public static int hashCode(Object o) {
        return o != null ? o.hashCode() : 0;
    }
    

    java.util.Arrays#hashCode(long[])

    
    public static int hashCode(long a[]) {
        if (a == null)
            return 0;
    
        int result = 1;
        for (long element : a) {
            int elementHash = (int)(element ^ (element >>> 32));
            result = 31 * result + elementHash;
        }
    
        return result;
    }
    

    对象包装器与自动装箱

    所有的基本类型都有一个 与之对应的类。Integer对应int。这些类称为包装器(wrapper).

    对象包装器不可变,且是final的。

    int当做Integer叫做自动装箱(autoboxing)

    
    list.add(1) 会被编译器编译成
    
    list.add(Integer.valueOf(3))
    

    Integer当做int叫做自动拆箱。

    装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,插入必要的方法调用。虚拟机只是执行这些字节码。

    反射

    反射库(reflection library)提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序。

    Class类

    Class类保存了Java对象归属类的信息。

    虚拟机为每个类型管理一个Class对象。所以,只有是这个类的Class对象,都是同一个。如何获得这个Class呢?

    //通过类名获取
    Class clazz = Employee.class;
    
    Employee employee = new Employee();
    
    //通过实例获取
    Class<? extends Employee> aClass = employee.getClass();
    String name = aClass.getName();
    assertEquals("com.test.java.clazz.polimophic.entity.Employee", name);
    
    //通过名字加载
    Class<?> fromName = Class.forName("com.test.java.clazz.polimophic.entity.Employee");
    

    另外,数组的class对象有点特殊。

    String doubleArrayName = Double[].class.getName();
    assertEquals("[Ljava.lang.Double;", doubleArrayName);
    
    String intArrayName = int[].class.getName();
    assertEquals("[I", intArrayName);
    

    那么,我拿到Class对象如何确定是不是我需要的呢,用equals比较吗?因为虚拟机为每个类管理一个Class对象,所以可以用==。

    对于上述三种方式获得的Class对象

    assertTrue(clazz == aClass);
    assertEquals(clazz, aClass);
    assertEquals(clazz, fromName);
    

    Class.forName("xxx.xx.xxx")会抛出一个检查性异常,如果找不到class会报ClassNotFoundException.

    利用反射分析类的能力

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

    Field类有个getType方法,返回描述字段所属的Class对象。

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

    这三个类还有一个叫做getModifiers的方法,它将返回一个整型数值,用不同的位开关描述public和static这样的修饰符使用情况。总之Modifiers提供了修饰符的判断方法。

    Class类中的getFields、getMethods和getConstructors方法将分别返回类提供的public字段、方法和构造器组,其中包括超类的共有成员。

    Class类的getDeclareFields、getDeclareMethods和getDeclareConstructors方法将分别返回类中声明的全部字段、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。

    获取Class name, 修饰符,父类

    public void printClazz() {
        Class clazz = Employee.class;
        Class superclass = clazz.getSuperclass();
        String modifiers = Modifier.toString(clazz.getModifiers());
        if (modifiers.length()>0){
            System.out.print(modifiers + " ");
        }
    
        System.out.println("class " + clazz.getName());
    
        if (superclass!=null && superclass != Object.class){
            System.out.print(" extends " + superclass.getName());
        }
    }
    

    打印:

    public class com.test.java.clazz.polimophic.entity.Employee
    

    获取构造器

    @Test
    public void prinConstructor() {
        StringBuilder sb = new StringBuilder();
        Class clazz = Manager.class;
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor declaredConstructor : declaredConstructors) {
            String name = declaredConstructor.getName();
            String modifiers = Modifier.toString(clazz.getModifiers());
            if (modifiers.length()>0){
                sb.append(modifiers).append(" ");
            }
            sb.append(name).append("(");
    
            //打印参数
            Class[] parameterTypes = declaredConstructor.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                if (i>0){
                    sb.append(", ");
                }
                sb.append(parameterTypes[i].getName());
            }
            sb.append(");");
        }
    
        assertEquals("public com.test.java.clazz.polimophic.entity.Manager(java.lang.String, int, int);", sb.toString());
    }
    

    打印声明的方法

    只打印自己的声明的方法,而不包含父类的。

    /**
     *  public java.lang.String getName();
     *  public int getSalary();
     *  public void setBonus(arg0);
     *  public int getBonus();
     */
    @Test
    public void printMethod() {
        StringBuilder sb = new StringBuilder();
        Class clazz = Manager.class;
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            Class<?> returnType = method.getReturnType();
            String name = method.getName();
    
            System.out.print(" ");
            String modifiers = Modifier.toString(method.getModifiers());
            if (modifiers.length()>0){
                System.out.print(modifiers + " ");
            }
    
            System.out.print(returnType.getName() + " " + name + "(");
    
            Parameter[] parameters = method.getParameters();
            for (int i = 0; i < parameters.length; i++) {
                if (i>0){
                    System.out.print(", ");
                }
                System.out.print(parameters[i].getName());
            }
            System.out.print(");
    ");
        }
    }
    

    打印声明的字段

    /**
     *  private int bonus;
     */
    @Test
    public void printFields() {
        Class clazz = Manager.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            Class<?> type = declaredField.getType();
            String name = declaredField.getName();
            System.out.print(" ");
            String modifiers = Modifier.toString(declaredField.getModifiers());
            if (modifiers.length() > 0) {
                System.out.print(modifiers + " ");
            }
            System.out.println(type.getName() + " " + name + ";");
        }
    }
    

    获取某个字段的value

    Field提供了get方法,来获取字段value。

    @Test
    public void getFieldVal() throws NoSuchFieldException, IllegalAccessException {
        Manager manager = new Manager("a", 1, 100);
        Class<? extends Manager> clazz = manager.getClass();
        Field bonus = clazz.getDeclaredField("bonus");
        bonus.setAccessible(true);
        Object bonusVal = bonus.get(manager);
        System.out.println((int)bonusVal);
        System.out.println(bonus.getInt(manager));
    }
    

    有几个需要注意的地方。

    • clazz.getDeclaredField("bonus"); 注意参数的内容要和filed一致
    • 由于该字段是private的,不能直接获取,需要设置访问权限,强制获取,bonus.setAccessible(true);
    • Field.get(instance)这个方法返回Object对象,可以强转,也可以使用其他api、

    修改某个字段的value

    能读就能写

    @Test
    public void writeFiledVal() throws NoSuchFieldException, IllegalAccessException {
        Manager manager = new Manager("a", 1, 100);
        Class<? extends Manager> clazz = manager.getClass();
        Field bonus = clazz.getDeclaredField("bonus");
        bonus.setAccessible(true);
        bonus.set(manager, 1000);
    
        assertEquals(1000, manager.getBonus());
    }
    

    利用反射创建一个对象

    前面获取到constructor之后就可以使用newInstance方法来创建新对象了。

    @Test
    public void newInstanceTest()
        throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        Class clazz = Manager.class;
        Constructor constructor = clazz.getConstructor(String.class, int.class, int.class);
        Manager instance = (Manager) constructor.newInstance("a", 1, 1);
        assertEquals(1, instance.getBonus());
    }
    

    需要注意的是基本类型的class是int.class,而不是Integer.class.
    `

    利用反射创建数组

    数组和普通对象有所不同。下面演示通过反射拷贝数组。

    private Object goodCopyOf(Object a, int newLength) {
        Class cl = a.getClass();
        if (!cl.isArray()) {
            return null;
        }
    
        int length = Array.getLength(a);
        Object newArray = Array.newInstance(cl.getComponentType(), newLength);
        System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
        return newArray;
    }
    @Test
    public void newArray() {
        int[] a = {1,2,3};
        int[] a2 =(int[]) goodCopyOf(a, 2);
        assertEquals(1, a2[0]);
        assertEquals(2, a2[1]);
    
        String[] str = {"a","b","c"};
        String[] str2 = (String[]) goodCopyOf(str, 2);
        assertEquals("a", str2[0]);
        assertEquals("b", str2[1]);
    }
    

    反射执行instance的某个方法

    @Test
    public void invokeMethod()
        throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Manager manager = new Manager("a", 1, 100);
        Class<? extends Manager> clazz = manager.getClass();
        Method method = clazz.getDeclaredMethod("getBonus");
        int rs = (int) method.invoke(manager);
        assertEquals(100, rs);
    
        Method setBonus = clazz.getDeclaredMethod("setBonus", int.class);
        Object invoke = setBonus.invoke(manager, 0);
        assertNull(invoke);
        assertEquals(0, manager.getBonus());
    }
    

    继承的设计技巧

    • 将公共操作和字段放在超类
    • 不要使用受保护的字段,非必须要,不要使用protected,而推荐用private
    • 使用继承实现is-a的关系,不是这样关系的类不应该使用继承
    • 除非所有继承的方法都有意义,否则不要使用继承
    • 在覆盖方法时,不要改变预期的行为
    • 使用多态,而不是类型信息

    来源

    • Java核心技术 卷一 原书第10版
  • 相关阅读:
    leetcode 268. Missing Number
    DBSCAN
    python二维数组初始化
    leetcode 661. Image Smoother
    leetcode 599. Minimum Index Sum of Two Lists
    Python中的sort() key含义
    leetcode 447. Number of Boomerangs
    leetcode 697. Degree of an Array
    滴滴快车奖励政策,高峰奖励,翻倍奖励,按成交率,指派单数分级(1月3日)
    北京Uber优步司机奖励政策(1月2日)
  • 原文地址:https://www.cnblogs.com/woshimrf/p/java-class-inheritance.html
Copyright © 2011-2022 走看看