第五章 继承 | 学习笔记

Java核心技术 卷一

5.1 类、超类和子类

  • 定义子类

下面是由继承Employee类来定义Manager类的格式,关键词extends表示继承。

例如:
public class Manager extends Employee
{
    添加方法和域
}

关键词extends表明正在构造新类派生于一个已存在的类。已存在的类称为超类、基类或父类,新类称为子类、派生类或孩子类。
在Manager类中,增加一个用于存储奖金信息的域,以及一个用于设置这个域的新方法:

public class Manager extends Employee
{
    private double bonus;
    ...
    public void setBonos(double bonus)
    {
        this.bonus = bonus;
    }
}
//方法使用:
    Manager boss = . . .;
    boss.setBonus(5000);
//父类不能使用子类方法,而子类可以使用父类方法。
  • 覆盖方法

超类中的有些方法对子类Manager并不一定适用。具体来说,Manager类中的getSalary方法应该返回薪水和奖金的总和。,此时,就需要覆盖父类方法。

例如:
public class Manager extends Employee
{
    ...
    public double getSalary()
    {
         double baseSalary = super.getSalary();
         return baseSalary + bonus;
    }
    ...
}
  • 子类构造器
例如:
public Manager(String name, double salary, int year, int month, int day)
{
    super(name, salary, year, month, day);
    bonus = 0;
}

语句super(name, salary, year, month, day)是“调用超类Employee中含有n、s、year、month和day参数的构造器”的简写形式。
由于Manager类的构造器不能访问Employee类的私有域,所以必须利用Employee类的构造器对这些部分私有域进行初始化。,可以通过super实现对超类构造器的调用。

  • 继承层次

由一个公共超类派生出来的所有类的集合称为继承层次。
在继承层次中,从某个特定的类到其祖先的路径被称为该类的继承链。
在这里插入图片描述

  • 多态
例如:
Employee e;
e = new Employee(. . .); // Employee object expected
e = new Manager(. . .); // OK, Manager can be used as well
  • 理解方法调用

下面假设要调用x.f(args),隐式参数x声明为类c的一个对象。
1.编译器查看对象的声明类型和方法名。
2.接下来,编译器将查看调用方法时提供的参数类型。
3.如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,其称为静态绑定。否则为动态绑定。
4.当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所应用对象的实际类型最合适的那个类的方法。

例如:
Manager:
    getName() -> Employee.getName()
    getSalary() -> Manager.getSalary()
    getHireDay() -> Employee.getHireDay()
    raiseSalary(double) -> Employee.raiseSalary(double)
    setBonus(double) -> Manager.setBonus(double)
在运行时,调用e.getSalary()的解析过程为:
     1.首先,虚拟机提取e的实际类型的方法表。
    2.接下来,虚拟机搜索定义getSalary签名的类。
    3.最后,虚拟机调用方法。

动态绑定有一个非常重要的特性:无需对现存的代码进行修改,就可以对程序进行拓展。假设增加一个新类Executive,并且变量e有可能引用这个类的对象,我们不需要对包含调用e.getSalary()的代码进行重新编译。

  • 阻止继承:final类和方法

不允许拓展的类被称为final类。

例如:
public final class Executive extends Manager//子类就不能覆盖这个方法,final类中的所有方法自动地称为final方法。
{...}    

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

  • 强制类型转换

Java程序语言设计提供了一个专门用于进行类型转换地表示法。

扫描二维码关注公众号,回复: 8631402 查看本文章
例如:
    double x = 3.405;
    int nx = (int) x ;

只能在继承层次内进行类型转换。在将超类转换成子类之前,应该使用instanceof进行检查。

  • 抽象类

如果自下面上在类的继承层次结构中上移,位于上层的类更具有通用性,甚至可能更加抽象
拓展抽象类可以有两种选择:
1.在抽象类中定义部分抽象类方法或不定义抽象类方法,就必须将子类也便极为抽象类。
2.定义全部的抽象方法,此时,子类就不是抽象的了。

  • 受保护访问

Java用于控制可见性的4个访问修饰符:
1.仅对本类可见——private
2.对所有类可见——public
3.仅本包和所有子类可见——protected
4.对本包可见——默认,不需要修饰符

5.2 Object:所有类的超类

Object类是Java中所有类的始祖,在Java中每个类都是由他拓展而来的。
可以使用Object类型的变量引用任何类型的对象:

Object obj = new Employee("Harry Hacker", 35000);

当然,Object类型的变量只能用于作为各种值的通用持有者。否则使用强制转换。

  • equals方法

Object类中的equals方法用于检测一个对象是否等于另一个对象。

public class Employee
{
    ...
    public boolean equals(Object otherObject)
    {
        // a quick test to see if the objects are identical
        if (this == otherObject) return true;
        // must return false if the explicit parameter is null
        if (otherObject == null) return false;
        // if the classes don't match, they can't be equal
        if (getClassO != otherObject.getClass())
        return false;
        // now we know otherObject is a non-null Employee
        Employee other = (Employee) otherObject;
        // test whether the fields have identical values
        return name.equals(other. name)
        && salary = other,sal ary
        && hi reDay. equals(other,hi reDay):
     }
}            
  • 相等测试与继承

Java语言规范要求equals方法具有以下特性:
1.自反性:对于任何非空引用x,x.equals(x)应该返回true
2.对称性:对于任何引用x与y,当且仅当y.equals(x)返回true,x.equals(y)也应该赶回true
3.传递性:对于任何引用x,y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也返回true。
4.一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回通用的结果
5.队医任意非空引用x,x.equals(null)应该返回false

  • hashCode方法

    散列码(hash Code)是由对象导出的一个整型值。
    由于hashCode方法定义在Object类中,因此每个对象都有一个默认的散列值,其值为对象的存储地址。

例如:
    String s = "Ok";
    StringBuilder sb = new StringBuilder(s);
    System.out.println(s.hashCode() + " "+sb.hashCode());
    String t = new String("Ok");
    StringBuilder tb = new StringBuilder(1);
    System.out.println(t.hashCode() + " " +tb.hashCode());

在这里插入图片描述

  • toString方法

用于返回表示对象值的字符串。

例如:
public String toStringO
{
    return "Employee[name=" + name
        + ",salary:"+salary
        +",hireDay="+hireDay
        + "]";
}    

5.3 泛型数组列表

为了指定数组列表保存的元素对象类型,需要用一对尖括号将类名括起来加在后面。

例如:
    ArrayList<Employee> staff = new ArrayList<Eniployee>();
  • 访问数组列表元素

数组列表自动扩展容量的遍历增加了访问元素语法的复杂程度。其原因是ArrayList类并不是Java程序设计语言的一部分。
使用get和set方法实现访问或改变数组元素的操作,而不使用人们喜欢的[]语法格式。

例如:
    staff.set(i , harry):
        等价于
    a[i] = harry;    

可以使用“for each”循环遍历数组列表:

for (Employee e : staff)
    do something with e
例如:
for (int i = 0; i < staff .size(); i ++)
{
    Employee e = staff.get(i);
        do something with e
}
  • 类型化与原始数组列表的兼容性
例如:
    public class EmployeeDB
    {
        public void update(ArrayList list) { . . . }
        public ArrayList find(String query) {. . . }
    }
可以将一个类型化的数组列表传递给update方法,而并不需要进行任何类型转换。
    ArrayList<Employee> staff = . . .;
    employeeDB.update(staff);

5.4 对象包装器与自动装箱

有时,需要将int这样的基本数据类型转为对象。例如,Integer类对应的数据类型int,这些类称为包装器。其包括:Integer、Long、Float、Double、Short、Byte、Character 、Void 和 Boolean。

例如:
    ArrayList<Integer> list = new ArrayList<>();
    list.add(3);//list.add (Integer.value0f(3));
    这称为自动装箱。
    int n = list.get(i);//int n = list.get(i).intValue();
    其称为自动拆箱。

5.5 参数数量可变的方法

在Java SE 5.0以前的版本中,每个Java方法都有固定数量的参数。然而,现在的版本提供了可以用可变的参数数量调用的方法。

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

这里的省略号是Java的一部分,它表明这个方法可以接受任意数量的对象

5.6 枚举类

例如:
    public enum Size { SMALL , MEDIUM, LARGE, EXTRAJARGE };
如果需要的话,可以在枚举类型中添加一些构造器、方法和域。
    public enum Size
    {
        SMALL("S"), MEDIUMC("M"), LARGE("L"), EXTRA_LARGE("XL");
        private String abbreviation;
        private Size(String abbreviation) { this.abbreviation = abbreviation; }
        public String getAbbreviation() { return abbreviation; }
    }

所有的枚举类型都是Enum类的子类。(toString)

5.7 反射

反射库提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序。
能够具有分析类能力的程序称为反射。
作用如下:
1.在运行时分析类的能力。
2.在运行时查看对象,例如,编写一个toString方法供所有类使用。
3.实现通用的数组操作代码。
4.利用Method对象,这个对象很像C++中的函数指针。

  • Class类

在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。
这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。
虚拟机为每一个类型管理一个Class对象。因此,可以利用==运算符实现两个类对象比较的操作。

例如:
    if (e.getClass()==Employee.class) . . .
  • 捕获异常

当程序运行过程中发生错误时,就会“抛出异常”。
异常处理:未检查异常和已检查异常。对于已检查异常,编译器将会检查是否提供了处理器。然而,很多常见的异常,例如:访问null引用,都属一未检查异常。

  • 利用反射分析类的能力

反射机制最重要的内容:检查类的结构。
在 java.lang.reflect 包中有三个类 Field、 Method 和 Constructor 分别用于描述类的域、 方法和构造器。
Class类中的 getFields、 getMethods 和 getConstructors 方 法 将 分 别 返 回 类 提 供 的public 域、 方法和构造器数组, 其中包括超类的公有成员。Class 类的 getDeclareFields、getDeclareMethods 和 getDeclaredConstructors 方法将分别返回类中声明的全部域、 方法和构造器, 其中包括私有和受保护成员,但不包括超类的成员。

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

查看对象域的关键方法是Filed类中的get方法。

例如:
Employee harry = new Employee("Harry Hacker", 35000, 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"

由于name是一个私有域,所以利用get方法才能得到可访问域的值。
反射机制的默认行为受限于Java的访问控制。

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

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

例如:
Employee[] a = new Employee[100]:
// array is full
a = Arrays.copyOf(a, 2 * a.length);
  • 调用任意方法

反射机制允许你调用任意方法。
在 Method 类中有一个 invoke 方法, 它允许调用包装在当前 Method 对象中的方法。invoke 方法的签名是:

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

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

5.8 继承的设计技巧

建议:
1.将公共操作和域放在超类。
2.不要使用受保护的类。
3.使用继承实现“is-a”关系。
4.除非所有继承的方法都有意义,否则不要使用继承。
5.在覆盖方法时,不要改变预期行为。
6.使用多态,而非类型信息。
7.不要过多使用反射。

发布了14 篇原创文章 · 获赞 22 · 访问量 567

猜你喜欢

转载自blog.csdn.net/qq_41550921/article/details/103968128