美文网首页
JAVA--继承

JAVA--继承

作者: DiDu小梁 | 来源:发表于2019-06-30 18:17 被阅读0次

概述

利用继承,人们可以基于已存在的类构造一个新类。继承已存在的类就是复用(继承)这些类的方法和域。 在此基础上,还可以添加一些新的方法和域,以满足新的需求。这是 Java 程序设计中的一项核心技术。"is-a" 关系是继承的一个明显特征。

初始化

public class Manager extends Employee {
    private double bonus;

    public Manager(String name, double salary, int year, int month, int day) {
        super(name, salary, year, month, day); 
        bonus = 0;
    }
    
    public Manager(String name, double salary, int year, int month, int day, int bonus) {
        this(name, salary, year, month, day);
        this.bonus = bonus;
    }
    public void setBonos(double bonus) {
        this.bonus = bonus;
    } 
}
  • 使用super 调用构造器的语句必须是子类构造器的第一条语句。super 关键字有两个用途: 调用超类的方法,、调用超类的构造器。
  • 关键字 this 有两个用途:引用隐式参数 、调用该类其他的构造器 。
  • 在调用构造器的时候,this和super关键字的使用方式很相似。调用构造器的语句只能作为另一个构造器的第一条语句出现。构造参数既可以传递给本类(this) 的其他构造器,也可以传递给超类(super) 的构造器。

方法调用

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

方法调用过程详解
  1. 编译器査看对象的声明类型和方法名。假设调用 x.f(param) 且隐式参数 x 声明为 C 类的对象。需要注意的是: 有可能存在多个名字为f, 但参数类型不一样的方法。例如,可能存在方法 f(int) 和方法f(String), 编译器将会一一列举所有 C 类中名为 f 的方法和其超类中访问属性为 public 且名为 f 的方法 (超类的私有方法不可访问)。至此,编译器已获得所有可能被调用的候选方法。
  2. 接下来, 编译器将査看调用方法时提供的参数类型。 如果在所有名为 f 的方法中存在一个与提供的参数类型完全匹配, 就选择这个方法。这个过程被称为重载解析(overloading resolution),PS:方法的名字和参数列表称为方法的签名。 例如, f(int) 和 f(String) 是两个具有相同名字,不同签名的方法。 如果在子类中定义了一个与超类签名相同的方法, 那么子类中的这个方法就覆盖了超类中的这个相同签名的方法。不过, 返回类型不是签名的一部分
  3. 如果是private方法、static方法、final方法或者构造器, 那么编译器将可以准确地知道应该调用哪个方法, 我们将这种调用方式称为静态绑定(static binding)。 与此对应的是, 调用的方法依赖于隐式参数的实际类型, 并且在运行时实现动态绑定
  4. 当程序运行,并且采用动态绑定调用方法时, 虚拟机一定调用与 x 所引用对象的实际类型最合适的那个类的方法。 假设 x 的实际类型是 D, 它是 C 类的子类。 如果 D 类定义了方法 f(String), 就直接调用它; 否则,将在 D 类的超类中寻找 f(String), 以此类推。每次调用方法都要进行搜索,时间开销相当大。因此, 虚拟机预先为每个类创建了一个方法表(method table),其中列出了所有方法的签名和实际调用的方法。这样一来,在真正调用方法的时候, 虚拟机仅查找这个表就行了。在前面的例子中, 虚拟机搜索 D 类的方法表, 以便寻找与调用 f(Sting) 相配的方法。这个方法既有可能是 D.f(String), 也有可能是 X.f(String), 这里的X是D的超类。这里需要提醒一点,如果调用super.f(param), 编译器将对隐式参数超类的方法表进行搜索。

阻止继承: final 类和方法

public final class A extends B {
    ...
}

public class C extends B {
    ...
    public final getName() {
     return name;
    }
    ...
}

有时候,可能希望阻止人们利用某个类定义子类。不允许扩展的类被称为 final 类。如果在定义类的时候使用了 final 修饰符就表明这个类是 final 类。 类中的特定方法也可以被声明为 final。 如果这样做,子类就不能覆盖这个方法,类将方法或类声明为 final 主要目的是: 确保它们不会在子类中改变语义。

强制类型转换

将一个类型强制转换成另外一个类型的过程被称为类型转换。进行类型转换的唯一原因是: 在暂时忽视对象的实际类型之后, 使用对象的全部功能。

//success  x 的值转换成整数类型, 舍弃了小数部分
double x = 3.405;
int nx = (int) x;

//前置条件
Manager boss = new Manager(...);
boss.setBonus(5000) ;
Employee[] staff = new Employee[3];
staff[0] = boss;
staff[1] = new Employee(...); 
staff[2] = new Employee(...);
---------------------------------
//success
Employee staff = new Manager(...);
//success
Manager boss = (Manager) staff[0];
//error  抛出 ClassCastException 异常
Manager boss = (Manager) staff[1];
//推荐用法
if (staff[1] instanceof Manager){
    boss = (Manager) staff[1]:
    ...
}

将一个值存入变量时, 编译器将检查是否允许该操作。将一个子类的引用赋给一个超类变量,编译器是允许的。但将一个超类的引用赋给一个子类变量, 必须进行类型转换,这样才能够通过运行时的检査。由上可知:

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

在一般情况下,应该尽量少用类型转换和 instanceof 运算符。

抽象类(abstract)

// 抽象类
public abstract class Person {
    private String name; 

    public Person(String name) {
        this.name = name; 
    }

    //抽象方法
    public abstract String getDescriptionO;

    public String getName() {
        return name;
    }
}

//实现子类
public class Student extends Person {
    private String major;

    public Student(String name, String major) {
        super(name) ;
        this.major=major;
    }
 
    public String getDescription() {
        return "a student majoring in " + major; 
    }
}

--------------------------

//error
Person p = new Person(...);

//success
Person p = new Student(...);
  • 为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。除了抽象方法之外,抽象类还可以包含具体数据和具体方法。
  • 抽象方法充当着占位的角色,它们的具体实现在子类中。
  • 扩展抽象类可以有两种选择。 一种是在抽象类中定义部分抽象类方法或不定义抽象类方法, 这样就必须将子类也标记为抽 象类; 另一种是定义全部的抽象方法, 这子类就不是抽象的了。
  • 类即使不含抽象方法, 也可以将类声明为抽象类。
  • 抽象类不能被实例化。如果将一个类声明为 abstract, 就不能创建这个类的对象。
  • 可以定义一个抽象类的对象变量, 但是它只能引用非抽象子类的对象。

4个访问修饰符

  • 仅对本类可见 private。
  • 对所有类可见 public。
  • 对本包和所有子类可见 protected。
  • 对本包可见(默认),不需要修饰符。

所有类的超类(Object)

可以使用 Object 类型的变量引用任何类型的对象。当然,Object 类型的变量只能用于作为各种值的通用持有者。 要想对其中的内容进行具体的操作, 还需要清楚对象的原始类型,并进行相应的类型转换。

equals 方法

Object 类中的 equals 方法用于检测一个对象是否等于另外一个对象。 在 Object 类中, 这个方法将判断两个对象是否具有相同的引用。== 运算符检测的也是对象是否指向同一个存储区域。如果两个对象具有相同的引用,它们一定是相等的。 从这点上看, 将其作为默认操作也是合乎情理的。 然而, 对于多数类来说, 这种判断并没有什么意义。
Java 语言规范要求 equals 方法具有下面的特性:

  • 自反性: 对于任何非空引用 x, x.equals(x) 应该返回 true。
  • 对称性: 对于任何引用 x 和 y, 当且仅当 y.equals(x) 返回 true, x.equals(y) 也应该返回 true。
  • 传递性: 对于任何引用 x、 y 和 z, 如果 x.equals(y) 返回 true, y.equals(z)返回 true, x.equals(z) 也应该返回 true。
  • 一致性: 如果 x 和 y 引用的对象没有发生变化, 反复调用 x.equals(y) 应该返回同样的结果。
  • 对于任意非空引用 x, x.equals(null) 应该返回 false。
    equas方法编码建议:
  1. 显式参数命名为 otherObject, 稍后需要将它转换成另一个叫做 other 的变量。
  2. 检测 this 与 otherObject 是否引用同一个对象。这条语句只是一个优化。 实际上, 这是一种经常采用的形式。 因为计算这个等式要比一 个一个地比较类中的域所付出的代价小得多。
if (this = otherObject) 
    return true;
  1. 检测 otherObject 是否为 null , 如 果 为 null , 返 回 false。 这项检测是很必要的。
 if (otherObject = null) 
    return false;
  1. 比较 this 与 otherObject 是否属于同一个类。
    如果 equals 的语义在每个子类中有所改变,就使用 getClass 检测:
if (getClass() != otherObject.getCIassO)  
    return false;

如果所有的子类都拥有统一的语义, 就使用 instanceof 检测:

if (!(otherObject instanceof ClassName)) 
    return false;
  1. 将 otherObject 转换为相应的类类型变量:
ClassName other = (ClassName) otherObject
  1. 现在开始对所有需要比较的域进行比较了。使用 = 比较基本类型域,使用equals 比较对象域。如果所有的域都匹配,就返回true; 否则返回false。
return field1 == other.field1 && Objects.equals(field2, other.field2) && ...
hashCode 方法(集合章节详解)
  • 散列码(hashcode) 是由对象导出的一个整型值。散列码是没有规律的。如果x和y是两个不同的对象, x.hashCode( ) 与 y.hashCode( ) 基本上不会相同。
  • hashCode 方法定义在 Object 类中, 因此每个对象都有一个默认的散列码, 其值为对象的存储地址。如果重新定义 equals 方法, 就必须重新定义 hashCode 方法, 以便用户可以将对象插人到散列表中。
  • equals 与 hashCode 的定义必须一致: 如果 x.equals(y) 返回 true, 那么 x.hashCode( ) 就必须与 y.hashCode( ) 具有相同的值。
toString 方法

在 Object 中还有一个重要的方法,就是 toString 方法,它用于返回表示对象值的字符串。Object 类定义了 toString 方法, 用来打印输出对象所属的类名和散列码。
toString 方法是一种非常有用的调试工具。 在标准类库中, 许多类都定义了 toString 方法, 以便用户能够获得一些有关对象状态的必要信息。强烈建议为自定义的每一个类增加toString方法。这样做不仅自己受益,而且所有使用这个类的程序员也会从这个日志记录支持中受益匪浅。

对象包装器与自动装箱

ArrayList<Integer> list = new ArrayList<>();
//当将一个 int 对象赋给一个 Integer 值时,将会自动地装箱(autoboxing).
list.add(3); -> list .add(Integer.value0f(3));
//当将一个 Integer 对象赋给一个 int 值时,将会自动地拆箱。 
int n = list.get(i); -> int n = list.get(i).intValue();

有时, 需要将 int 这样的基本类型转换为对象。 所有的基本类型都有一个与之对应的类。 例如,Integer 类对应基本类型 int。 通常, 这些类称为包装器 ( wrapper ) 。这些对象包装器类拥有很明显的名字: Integer、Long、Float、Double、Short、Byte、Character、Void 和 Boolean ( 前 6 个类派生于公共的超类 Number)。 对象包装器类是不可变的, 即一旦构造了包装器, 就不允许更改包装在其中的值。同时,对象包装器类还是final, 因此不能定义它们的子类。tips: 由于每个值分别包装在对象中, 所以 ArrayList<lnteger> 的效率远远低于 int[ ] 数组。 因此,应该用它构造小型集合,其原因是此时程序员操作的方便性要比执行效率更加重要。

Integer a = 1000; 
Integer b = 1000; 
a == b     // it is false

Integer a = 100; 
Integer b = 100; 
a == b     // it is true

自动装箱规范要求 boolean、byte、char <=127, 介于 -128 ~ 127 之间的 short 和 int 被包装到固定的对象中。介于上述原因,建议两个包装器对象比较时调用 equals 方法。

继承的设计技巧

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

相关文章

  • JAVA--继承

    概述 利用继承,人们可以基于已存在的类构造一个新类。继承已存在的类就是复用(继承)这些类的方法和域。 在此基础上,...

  • Java--继承

      继承让我们更加容易实现类的扩展。比如,我们定义了人类,再定义Boy类就只需要扩展人类即可。实现了代码的重用,不...

  • Java--继承使用要点

    1.父类也称作超类、基类、派生类等。2.Java中只有单继承,没有像C++那样的多继承。多继承会引起混乱,使得继承...

  • Java--接口要点与多继承

    接口的要点 子类通过implements来实现接口中的规范。 接口不能创建实例,但是可用于声明引用变量类型。 一个...

  • Eclipse 开发基本配置与快捷键

    自动提示Window -->> Preferences -->> Java-->> Editor-->> Cont...

  • Eclipse自动补全

    Windows——>Preferences——>Java-->Editor-->Content Asist,在Au...

  • B入门指南-----eclipse代码格式化

    打开eclipse 选择 window-->Preferences-->JAVA-->Code-->Code St...

  • 001—Myeclipse常用编程设置

    设置工程通用JDK 选择Windows-->preference--->java-->Installed JREs...

  • java启动jvm配置详解

    /usr/lib/jvm/java-1.8.0-openjdk/bin/java-- 资源分配-server -X...

  • maven 项目install时 报错

    错误代码节选: 解决步骤: Eclipse-->Window-->preferences-->Java-->Ins...

网友评论

      本文标题:JAVA--继承

      本文链接:https://www.haomeiwen.com/subject/gaivcctx.html