23.继承
23.1 优点:
实现软件重用,避免重复,易于维护,易于理解。
23.2 实现及默认机制
继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承 Object(这个类在 java.lang 包中,所以不需要 import)祖先类。
23.3 具体概念
- 继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
- 子类继承父类的属性和方法
- 子类从它的父类中继承可访问的数据域和方法,也可以添加新的数据域和新的方法。
“父类”,也称为超类、基类;
“子类”,也称为次类、扩展类、派生类。
子类不是父类的子集,子类一般比父类包含**更多的数据域和方法**。父类中的 private 数据域在子类中是不可见的,因此在子类中不能直接使用它们。
使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
23.4 所有类都直接或者间接的继承自Object类
方法 | 作用及扩展 |
---|---|
equals() | 默认比较地址,需要重写 见23.4.1 |
clone() | 浅拷贝和深拷贝,见24.4.2 |
hashCode() | 返回对象的哈希码值,见24.4.3 |
toString() | 返回对象的字符串形式,见24.4.4 |
getClass() | 它会返回一个你的对象所对应的一个Class的对象,这个返回来的对象保存着你的原对象的类信息,见24.4.5 |
finalize() | 见24.4.6 |
23.4.1 关于equals()重写
注意向下转型前的判断,重写后的equals方法大多是对 两个对象成员属性值的判断
public class Animal {
String color="Animal的color";
int age;
public String getColor(){
return color;
}
}
public class Dog extends Animal {
String color ="Dog的color";
String nickName;
public String getColor(){
return color;
}
public String getSuperColor(){
return super.getColor();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Dog)) return false;
Dog dog = (Dog) o;
return Objects.equals(color, dog.color) && Objects.equals(nickName, dog.nickName);
}
}
23.4.2 关于深浅克隆
-
**浅拷贝:**在填充新对象域的时候,进行简单的字段赋值。
代码实现
class Head {
String s;
void set(String s1) {
s = s1;
}
}
public class Person implements Cloneable{class Head { String s; void set(String s1) { s = s1; } } public class Person implements Cloneable{ Head head; Person(Head head) { this.head = head; } protected Object clone() throws CloneNotSupportedException{ return super.clone(); } public static void main(String[] args) throws CloneNotSupportedException{ Person p = new Person(new Head()); Person p1 = (Person)p.clone(); System.out.println("p == p1 " + (p == p1)); //false System.out.println("p.head == p1.head " + (p.head == p1.head)); //true } }
}
-
**深拷贝:**按照惯例,此方法返回的对象应该独立于该对象(正被复制的对象)。要获得此独立性,在
super.clone
返回对象之前,有必要对该对象的一个或多个字段进行修改。这通常意味着要复制包含正在被复制对象的内部“深层结构”的所有可变对象,并使用对副本的引用替换对这些对象的引用。如果一个类只包含基本字段或对不变对象的引用,那么通常不需要修改super.clone
返回的对象中的字段。(简单来说,就是将该对象内部的对象也克隆一份,而不是简单的引用赋值)
class Head implements Cloneable{
String s;
void set(String s1) {
s = s1;
}
protected Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
public class Person implements Cloneable{
Head head;
Person(Head head) {
this.head = head;
}
protected Object clone() throws CloneNotSupportedException{
Person p = (Person)super.clone();
p.head = (Head)head.clone();
return p;
}
public static void main(String[] args) throws CloneNotSupportedException{
Person p = new Person(new Head());
Person p1 = (Person)p.clone();
System.out.println("p == p1 " + (p == p1)); //false
System.out.println("p.head == p1.head " + (p.head == p1.head)); //false
}
}
23.4.3 关于hashCode()
作用是返回对象的哈希码值。是根据对象的地址计算的
补充:
(1) HashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,HashCode经常用于确定对象的存储地址;
(2)如果两个对象相同, equals方法一定返回true,并且这两个对象的HashCode一定相同;
(3)两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能够说明这两个对象在一个散列存储结构中。
(4)如果对象的equals方法被重写,那么对象的HashCode也尽量重写。
23.4.4 关于toString
直接调用或输出某个对象的引用,默认调用Object类提供的toString( ),返回的相当于是对象地址的16进制表达形式 的字符串类型值
JavaBean的定义规范之一,要求重写toString()
23.4.5 关于getClass()
补充 :
-
在Java中,万事万物皆对象,每个类都有一个相应的Class对象。通过Class类,可以获得一个类的基本信息,比如属性、方法和构造方法等。
-
getClass()是Object类的方法,该方法的返回值类型是Class类,通过getClass()方法可以得到一个Class类的对象。而.class返回的也是Class类型的对象。所以,如果getClass()和.class返回的内容相等,说明是同一个对象。
-
既然都可以得到Class的对象,关于getClass()和.class的区别:getClass()方法,有多态能力,运行时可以返回子类的类型信息。.class是没有多态的,是静态解析,编译时可以确定类型信息。
23.4.6 关于finalize()
finalize方法的用途有两个:1.finalize()方法释放本地方法申请的内存;2.作为终结条件
24.Super关键字
24.1 作用
super 表示使用它的类的父类。super 可用于:
- 调用父类的构造方法;
- 调用父类的方法(子类覆盖了父类的方法时);
- 访问父类的数据域(可以这样用但没有必要这样用)。
24.2 注意事项
**super 语句必须是子类构造方法的第一条语句。不能在子类中使用父类构造方法名来调用父类构造方法。 父类的构造方法不被子类继承。调用父类的构造方法的唯一途径是使用 super 关键字,如果子类中没显式调用,则编译器自动将 super(); 作为子类构造方法的第一条语句。这会形成一个构造方法链。
静态方法中不能使用 super 关键字。
如果父类中不含 默认构造函数(就是 类名() ),那么子类中的super()语句就会执行失败,系统就会报错。一般 默认构造函数 编译时会自动添加,但如果类中已经有一个构造函数时,就不会添加。
子类不能直接继承父类中的 private 属性和方法。
/由于属性是私有的,所以子类不能直接继承,
需要通过 有参构造 函数进行继承/
24.3 创建子类对象过程
子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
在构造一个子类的对象时,父类的构造方法也是会被调用的,而且父类的构造方法在子类的构造方法之前被调用。
在程序运行过程中,子类对象的一部分空间存放的是父类对象。因为子类从父类得到继承,在子类对象初始化过程中可能会使用到父类的成员。
所以父类的空间正是要先被初始化的,然后子类的空间才得到初始化。在这个过程中,如果父类的构造方法需要参数,如何传递参数就很重要了。
注意先后顺序
24.4 关于子类的加载问题
子类的加载顺序为:
- 父类静态代码块(包括静态初始化块,静态属性,但不包括静态方法)
- 子类静态代码块(包括静态初始化块,静态属性,但不包括静态方法 )
- 父类非静态代码块( 包括非静态初始化块,非静态属性 )
- 父类构造函数
- 子类非静态代码块 ( 包括非静态初始化块,非静态属性 )
- 子类构造函数