码迷,mamicode.com
首页 > 其他好文 > 详细

第5章 继承

时间:2020-03-25 00:55:13      阅读:71      评论:0      收藏:0      [点我收藏+]

标签:强制   abs   ons   限制   print   bst   枚举   java   管理   

继承(inheritance)的基本思想是,可以基于已有的类创建新的类。继承已存在的类就是复用(继承)这些类的方法,而且可以增加一些新的方法和字段,使新类能够适应新的情况。

反射(reflection)是指在程序运行期间更多地了解类及其属性的能力。

5.1 类、超类和子类

  1. Java中,使用关键字extends表示继承。

    public class Manager extends Employee {
        // added methods and fields
    }
    
  2. 这个已存在的类称为超类(superclass)、基类(base class)或父类(parent class),新类称为子类(subclass)、派生类(derived class)或孩子类(child clsaa)

  3. 应该将最一般的方法放在超类中,将更特殊的方法放在子类中。在子类中可以增加字段、增加方法或覆盖超类的方法,不过继承绝对不会删除任何字段或者方法。

  4. 子类不能直接访问超类的私有字段,必须使用关键字super来调用超类的公共接口。

  5. 子类可以提供一个新的方法来覆盖超类中同名的方法:

    public double getSalary () {
        double baseSalary = super.getSalary();
        return baseSalary + bonus;
    }
    
  6. 子类构造器:

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

    使用super调用构造器的语句必须是子类构造器的第一条语句。

  7. 如果子类的构造器没有显式的调用超类的构造器,将自动的调用超类的无参数构造器。

  8. 关键字this的两种含义:

    • 指示隐式参数的引用
    • 调用该类的其他构造器

    关键字super的两种含义:

    • 调用超类的方法
    • 调用超类的构造器
  9. 一个对象变量(例如,变量e)可以指示多种实际类型的现象称为多态(polymorphism)。在运行时能够自动地选择适当的方法,称为动态绑定(dynamic binding)

    Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
    boss.setBonus(5000);
    
    var staff = new Employee[3];
    
    staff[0] = boss;
    staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
    staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);
    
    for (Employee e : staff)
        System.out.println(e.getName + " " + e.getSalary);
    
  10. 继承并不仅限于一个层次。由一个公共超类派生出来的所有类的集合称为继承层次(inheritance hierarchy)。在继承层次中,从某个特定的类到其祖先的路径称为该类的继承链(inheritance chain)

  11. "is-a"规则的另一种表述是替换原则,它指出程序中出现超类对象的任何地方都可以使用子类对象替换。在Java程序设计语言中,对象变量是多态的。不过,不能将超类的引用赋给子类变量。

  12. 假设要调用x.f(args),隐式参数x声明为类C的一个对象。方法调用的过程如下:

    • 编译器查看对象的声明类型和方法名。
    • 编译器要确定方法调用中提供的参数类型。
    • 如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确的知道应该调用哪个方法。这称为静态绑定(static binding)
    • 程序运行并且采用动态绑定方法时,虚拟机必须调用与x所引用对象的实际类型对应的那个方法。
  13. 动态绑定有一个非常重要的特性:无须对现有的代码进行修改就可以对程序进行扩展。

  14. 运行子类将覆盖方法的返回类型修改为原返回类型的子类型。

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

  16. 不允许被扩展的类被称为final类。

    public final class Executive extends Manager {
        ...
    }
    
  17. 如果类中的某个特定方法声明为final,那么子类就不能覆盖这个方法。

  18. 如果将一个类声明为final,其中的方法会自动地成为final,字段不会。

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

  20. 将一个子类的引用赋给一个超类变量,编译器是允许的。但是将一个超类的引用赋给一个子类变量时,就承诺过多了,必须进行强制类型转换。

  21. 强制类型转换的原则:

    • 只能在继承层次内进行强制类型转换
    • 在将超类强制转换成子类之前,应该使用instanceof进行检查
    if (staff[1] instanceof Manager) {
        boss = (Manager) staff[1];
        ...
    }
    
  22. 包含一个或多个抽象方法的类本身必须被声明为抽象的。

    public abstract class Person {
        ...
        public abstract String getDescription() {};
    }
    

    除了抽象方法之外,抽象类还可以包含字段和具体方法。

    即使不含抽象方法,也可以将类声明为抽象类。

    抽象类不能实例化,即不能创建这个类的对象。

    可以定义一个抽象类的对象变量,但是这个变量只能引用非抽象子类的对象。

  23. 抽象方法充当占位方法的角色,它们在子类中具体实现。

    扩展抽象类有两种选择:

    • 在子类中保留抽象类中的部分或所有抽象方法仍未定义,这样子类也必须标记为抽象类。
    • 定义全部方法,子类就不是抽象类了。
  24. Java中的4个访问控制修饰符:

    • 仅对本类可见 —— private
    • 对外部完全可见 —— public
    • 对本包和所有子类可见 —— protected
    • 对本包可见 —— 默认(很遗憾),不需要修饰符

5.2 Object类:所有类的超类

  1. Object类是Java中所有类的始祖,在Java中每个类都扩展了Object

  2. 可以使用Object类型的变量引用任何类型的对象。在Java中,只有基本类型不是对象,例如,数值、字符和布尔类型的值。

  3. 所有的数组类型,不管是对象数组还是基本类型的数组都扩展了Object类。

    Object obj = new Employee("Harry Hacker", 50000);
    
    Employee[] staff = new Employee[10];
    obj = staff;    // OK
    obj = new int[10];  // OK
    
  4. Object类中实现的equals方法可以确定两个对象引用是否相等。如果两个对象引用是相等的,这两个对象就相等。如果对象可能为null,可以使用Objects.equals方法进行判断。

  5. 在子类中定义equals方法时,可以首先调用超类的equals,然后再比较子类中新增的实例字段。

  6. 编写一个完美的equals方法的建议:

    • 显式参数命名为otherObject,稍后需要将它强制转换成另一个名为other的变量。
    • 检测thisotherObject是否相等:
      if (this == otherObject) return false;
      
    • 检测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;
      
    • otherObject强制转换为相应类类型的变量:
      ClassName other = (ClassName) otherObject;
      
    • 现在根据相等性概念的要求来比较字段。使用==比较基本类型字段,使用Objects.equals比较对象字段。如果所有的字段都匹配,就返回true;否则返回false
      return field1 == other.field1
        && Objects.equals(field2, other.field2)
        && ...;
      
  7. 散列码(hash code)是由对象导出的一个整型值。散列码是没有规律的。

    由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列码,其值由对象的存储地址得出。

    字符串的散列码是由内容导出的。

  8. Employee类的hashCode方法:

    public class Empoyee {
        public int hashCode() {
            return 7 * name.hashCode()
              + 11 * new Double(salary).hashCode()
              + 13 * hireDay.hashCode();
        }
        ...
    }
    

    进阶版:

    • 使用null安全的方法Objects.hashCode,如果参数为null,这个方法会返回0
    • 使用静态方法Double.hashCode来避免创建Double对象。
    public int hashCode() {
        return 7 * Objects.hashCode(name)
          + 11 * Double.hashCode(salary)
          + 13 * Objects.hashCode(hireDay);
    }
    

    再次进阶版:

    • 使用Objects.hash方法
    public int hashCode() {
        return Objects.hash(name, salary, hireDay);
    }
    
  9. 两个相等的对象要求返回相等的散列码。

  10. Object类有toString方法,绝大多数(但不是全部)的toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的字段值。

  11. 设计的子类应该定义自己的toString方法,并加入子类的字段。

  12. 随处可见toString方法的主要原因是:只要对象与一个字符串通过操作符"+"连接起来,Java编译器就会自动地调用toString方法来获得这个对象的字符串描述。

  13. 如果x是一个任意对象,并调用:

    System.out.println(x);
    

    println方法就会简单地调用x.toString(),并打印输出得到的字符串。

  14. 打印数组可以调用静态方法:Arrays.toString。打印多维数组,需要调用Arrays.deepToString静态方法。

5.3 泛型数组列表

  1. ArrayList是一个有类型参数的泛型类。ArrayList类类似于数组,但在添加或删除元素时,可以自动调整大小。

  2. 声明数组列表:

    ArrayList<Employee> staff = new ArrayList<Employee>();
    

    Java 10中,可以使用var关键字,以避免重复写类名:

    var staff = new ArrayList<Employee>();
    

    如果没有使用var关键字,可以省去右边的类型参数:

    ArrayList<Employee> staff = new ArrayList<>();
    

    这称为“菱形”语法。

  3. add方法可以将元素添加到数组列表中,如果内部数组已经满了,数组列表就会自动创建一个更大更大的数组,并将所有的对象从较小的数组中拷贝到较大的数组中。

  4. 如果已经知道或能够估计除数组可能存储的元素数量,就可以在填充数组之前调用ensureCapacity方法。

  5. size方法将返回数组列表中包含的实际元素个数。

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

  7. add方法为数组列表增加新元素,get方法获取第i个元素,set方法可以替换数组中已经加入的元素,remove方法从数组列表中删除一个元素。使用toArray方法可以将数组元素拷贝到一个数组中。

  8. 可以使用for each循环遍历数组列表的内容。

  9. 出于兼容性的考虑,编译器检查到没有发现违反规则的现象之后,就将所有的类型化数组列表转换成原始ArrayList对象。

5.4 对象包装器与自动装箱

  1. 所有的基本类型都有一个与之对应的类:IntegerL、Long、Float、Double、Short、Byte、Character、Boolean(前6个类派生于公共的超类Number),这些类称为包装器。包装器类是不可变的,一旦构造了包装器,就不允许更改包装在其中的值。同时,包装器类还是final,因此不能派生它们的子类。

  2. 加入定义一个整型数组列表,尖括号中的类型参数不允许是基本类型,但是可以使用包装器类。

    var list = new ArrayList<Integer>();
    
  3. 自动装箱:

    list.add(3);
    

    会自动地变换成:

    list.add(Integer.valueOf(3));
    
  4. 当将一个Integer对象赋给一个int值时,将会自动地拆箱:

    int n = list.get(i);
    

    会转换成:

    int n = list.get(i).intValue();
    
  5. 自动装箱和拆箱也适用于算数表达式。

    Integer n = 3;
    n++;
    
  6. 比较两个包装器对象时要调用equals方法。

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

  8. 装箱和拆箱是编译器要做的工作,而不是虚拟机。

  9. 使用静态方法parseInt可以将字符串转换成整型。

    int x = Integer.parseInt(s);
    

5.5 参数数量可变的方法

  1. printf方法的定义:

    public class PrintStream {
        public PrintfStream printf(String fmt, Object... args) {return format(fmt, args)};
    }
    

    printf方法接收两个参数,一个是格式字符串,另一个是Object[]数组,其中保存着所有其他参数。

  2. 如果一个已有方法的最后一个参数是数组,可以把它重新定义为有可变参数的方法,而不会破坏任何已有的代码。

5.6 枚举

  1. 一个典型的例子:

    public enum Size {SMALL, MEDIUM, LARGE, EXTRA_LARGE};
    

    这个声明定义的类型是一个类,它刚好有4个实例,不可能构造新的对象。

  2. 枚举的构造器总是私有的。可以省略private修饰符。如果声明一个enum构造器为publicprotected,会出现语法错误。

5.7 反射

  1. 能够分析类能力的程序称为反射(reflective),反射可以用来:

    • 在运行时分析类的能力
    • 在运行时检查对象
    • 实现泛型数组操作代码
    • 利用Method对象
  2. 在程序运行期间,Java运行时系统始终为所有对象维护一个运行时类型标识。这个信息会跟踪每个对象所属的类。保存这些信息的类名为Class

  3. 获得Class类对象的三种方法:

    • Object类中的getClass()方法将会返回一个Class类型的实例。
      Employee e;
      ...
      Class cl = e.getClass();
      
    • 可以使用静态方法forName获得类名对应的Class对象。
      String className = "java.util.Random";
      Class cl = Class.forName(className);
      
      无论何时使用forName方法,都应该提供一个异常处理器。
    • 如果T是任意类型的Java类型(或void关键字),T.class将代表匹配的类对象。
      Class cl1 = Random.class;
      Class cl2 = int.class;
      Class cl3 = Double[].class;
      
      一个Class对象实际上表示的是一个类型,这可能是类,也可能不是类。
      Class类实际上是一个泛型类。例如,Employee.class的类型是Class<Employee>
  4. 虚拟机为每个类型管理一个唯一的Class对象。因此,可以利用==运算符实现两个类对象的比较。

    if (e.getClass == Employee.class) ...
    
  5. 如果有一个Class类型的对象,可以用它构造类的实例。调用getConstructor方法将得到一个Constructor类型的对象,然后使用newInstance方法来构造一个实例。

    String className = "java.util.Random";  // or any other name of a class with 
                                            // a no-arg constructor
    Class cl = Class.forName(className);
    Object obj = cl.getConstructor().newInstance();
    

    如果这个类没有无参数的构造器,getConstructor方法会抛出一个异常。

  6. 异常有两种类型:

    • 检查型(checked)异常:编译器将会检查程序员是否知道这个异常并做好准备来处理后果。
    • 非检查型(unchecked)异常:越界错误或访问null引用等。编译器并不期望程序员为这些异常提供处理器,而是应该集中精力避免这些错误的发生。
  7. 异常处理最简单的一个策略:

    如果一个方法包含一条可能抛出检查型异常的语句,则在方法名上增加一个throws子句。调用这个方法的任何方法也都需要一个throws声明。这也包括main方法。如果一个异常确实出现,main方法将终止并提供一个堆栈轨迹。

    public static void doSomethingWithClass(String name) throws ReflectiveOperationException {
        Class cl = Class.forName(name); // might throw exception
        do something with cl
    }
    
  8. Java中,与类有关联的文件被称为资源。Class类提供了一个很有用的服务可以查找资源文件:

    • 获得拥有资源的类的Class对象,例如,ResourceTest.class
    • 有些方法,如ImageIcon类的getImage方法,接受描述资源位置的URL。则要调用
      URL url = cl.getResource("about.gif");
      
    • 否则,使用getResourceAsStream方法得到一个输入流来读取文件中的数据。
  9. 利用反射分析类的能力

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

    • Field类的常用方法:getName、getType、getModifiers
    • Constructor类的常用方法:getName、getModifiers、getParameterTypes
    • Method类的常用方法:getName、getModifiers、getReturnType、getParameterTypes

    可以利用java.lang.reflect包中的Modifier类的静态方法分析getModifiers返回的整数。如isPublic、isPrivate、isFinal,也可以使用Modifier.toString方法。

  10. Class类中的getFields、getConstructors、getMethods方法将分别返回这个类支持的公共字段、构造器和方法的数组,其中包括超类的公共成员。

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

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

    如果f是一个Field类型的对象,obj是某个包含f字段的类的对象,f.get(obj)将返回一个对象,其值为obj的当前字段值。

    var harry = new Employee("Harry Hacker", 50000, 10, 1, 1989);
    Class cl = harry.getClass();    // the class object representing Employee
    Field f = cl.getDeclaredField("name");  // the name field of the Employee class
    Object v = f.get(harry);    // the value of the name field of the harry object, i.e., the String object "Harry Hacker"
    

    调用f.set(obj, value)将把对象objf表示的字段设置为新值。

  12. 只能对可以访问的字段使用getset方法。反射机制的默认行为受限于Java的访问控制。

  13. 可以调用FieldMethodConstructor对象的setAccessible方法覆盖Java的访问控制。setAccessible方式是AccessibleObject类中的一个方法,它是FieldMethodConstructor类的公共超类。

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

    java.lang.reflect包中Array类的静态方法newInstance,能够构造一个新数组,调用此方法时必须提供的参数,一是数组的元素类型,二是数组的长度。

    数组的长度可以通过Array类的静态方法getLength方法获得。

    获得新数组元素类型的步骤:

    • 首先获得a数组的类对象
    • 确认它确实是一个数组
    • 使用Class类的getComponentType方法(只为表示数组的类对象定义了这个方法)确定数组的正确类型
    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, length);
        System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
    }
    

    这个方法可以用来扩展任意类型的数组,而不仅是对象数组。

    该方法的参数应该声明为Object类型,而不要声明为对象型数组(Object[])。整型数组类型int[]可以转换成Object,但是不能转换成对象数组。

  15. 调用任意方法和构造器

    Method类有一个invoke方法,允许调用包装在当前Method对象中的方法。invoke方法的签名为:

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

    第一个参数是隐式参数,其余的对象提供了显式参数。对于静态方法,第一个参数可以忽略,即可以将它设置为null

    invoke方法的返回类型是Object对象,必须相应地完成强制类型转换再使用。如果返回类型是基本类型,invoke方法会返回其包装器类型。

    获得Method对象的方法:

    • 调用getDeclareMethods方法
    • 调用Class类的getMethod方法。由于有可能存在若干个同名的方法,调用该方法时必须提供想要的方法的参数类型。getMethod方法的签名为:
    Method getMethod(String name, Class... parameterTypes)
    

    可以使用类似的方法调用任意的构造器。将构造器的参数类型提供给Class.getConstructor方法,并把参数值提供给Constructor.newInstance方法。

    Class cl = Random.class;    // or any other class with a constructor that 
                                // accepts a long parameter
    Constructor cons = cl.getConstructor(long.class);
    Object obj = cons.newInstance(42L);
    

5.8 继承的设计技巧

  1. 将公共操作和字段放在超类中。

  2. 不要使用受保护的字段。

    • 子类集合是无限制的,任何人都能够由超类派生出子类,然后直接访问超类的protected字段,从而破坏了封装性。
    • 同一个包中的所有类都可以访问protected字段,不管它们是否为这个类的子类。
  3. 使用继承实现"is-a"关系。

  4. 除非所有继承的方法都有意义,否则不要使用继承。

  5. 在覆盖方法时,不要改变预期的行为。

  6. 使用多态,而不要使用类型信息。

  7. 不要滥用反射。

第5章 继承

标签:强制   abs   ons   限制   print   bst   枚举   java   管理   

原文地址:https://www.cnblogs.com/harr/p/12563281.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!