Java——面向对象三大特性2(继承)

简介: Java——面向对象三大特性2(继承)

2 继承


2.1 继承的基本概念


我们让 Cat 类和 Dog 类继承自 Animal 这个类。这样,Animal 类与 Cat 和 Dog类之间,就形成了继承关系。而被继承的类 Animal,被称为“父类”;而 Cat 与 Dog 这两个类继承自 Animal,被称为“子类”。


image.png



由于父类是子类的共性的抽象,是一个一般的类,因此,我们在进行继承关系设计的时候,应当尽量把子类的共性放在父类,特性放在子类。


image.png



这是一个非常典型的继承关系。生物,是一个很宽泛的概念,包括植物、动物等,都可以看做是生物。而动物,就是一种特殊的生物,因此,动物类是生物类的子类。同样的,哺乳动物是特殊的动物,而人又是特殊的哺乳动物。



父类是对子类共性的抽象,父类和子类的关系,是由一般到特殊的关系。在设计类的继承关系时,应当把共性放在父类,特性放在子类。


2.2 继承的基本语法


从语法上说,继承使用关键字:extends。在定义子类的时候,可以用 extends 关键字说明这个类的父类是哪一个类。代码如下:

class Animal{
    int age;
    boolean sex;
    public void eat(){
        System.out.println("Animal eat");
    }
    public void sleep(){
        System.out.println("sleep 8 hours");
    }
}
class Dog extends Animal{
    public void lookAfterHouse(){
        System.out.println("look after house");
    }
}
class Cat extends Animal{
    public void catchMouse(){
        System.out.println("catch mouse");
    }
}
public class TestDog {
    public static void main(String args[]){
        Dog d = new Dog();
        d.sex = true;
        d.age = 3;
        d.eat();
        d.lookAfterHouse();
    }
}


父类中的属性和方法,被子类继承之后,相当于子类中也有了相应的属性和方法。



在父类中定义了属性和方法之后,子类中就能够直接继承,这样,就让父类中的代码得到了重用,从而提高了代码的可重用性。同时,子类也能够写一些子类的特性,这样,就在父类的基础上增加一些功能,体现了面向对象的可扩展性。



2.3 什么能被继承?

class Parent{
    int value1;
    private int value2;
}
class Child extends Parent{
    public void method(){
        value1 = 100; // 编译正确 Child 类从父类中继承到 value1 属性
        value2 = 200; // 编译错误,Child 没有继承到 value2 属性
    }
}

image.png


那么 Child 类中有没有 value2 属性呢?从空间上来说,在创建 Child 对象的时侯,会在 Child 对象的内部,包含着一个 Parent 对象。



只有子类能够访问的属性和方法,才能够被子类继承。



2.4 访问权限修饰符


在 Java 中,总共有四种访问修饰符。除了我们之前介绍的 private 和 public 两个修饰符之外,还有两个跟访问权限相关的修饰符:default 以及 protected。要注意的是,default修饰符指的是:如果在属性或方法前面,不加任何的访问修饰符(即不加 private、public、protected),则访问权限是 default 权限。



要注意的是,要把一个属性设为 default 时,千万不能写上“default”这个单词。



访问权限为 default 的属性或者方法,只能被本类或者同包的其他类访问。


对于 default 的属性和方法而言,只有同包的类才能够访问。

package p1;
public class Parent{
    int value = 20;
}
package p1; //与 Parent 同一个包
public class Child1 extends Parent{
    public void m1(){
        System.out.println(value); //继承了 Parent 的 value 属性
    }
}
package p2; //与 Parent 不同包
public class Child2 extends p1.Parent{
    public void m2(){
    //编译错误!Child2 类和 Parent 不同包,没有继承 value 属性
    System.out.println(value); 
    }
}


用 protected 修饰符修饰的属性和方法,能够被本类内部、同包的类以及非同包的子类访问。

package p1;
public class Parent{
    protected int value;
}
package p1;
public class SamePackage{
    public void m1(){
        Parent p = new Parent();
        System.out.println(p.value); //同包的类可以访问
    }
}
package p2;
public class Child extends p1.Parent{
    public void m2(){
    //非同包的子类可以访问父类的 value 属性
        System.out.println(value);
    }
}
package p2;
public class Other {
    public void m2(){
        p1.Parent p = new p1.Parent();
        //编译错误!Other 类和 Parent 类并不同包,也没有继承关系,不能访问
        System.out.println(p.value); 
    }
}


image.png


private  default  protected  public


访问权限依次变宽



2.5 方法覆盖

class Dog extends Animal{
    public void lookAfterHouse(){
        System.out.println("Dog can look after house");
    }
    public void sleep(){
        System.out.println("sleep 6 hours");
    }
}
public class TestDog {
    public static void main(String args[]){
        Dog d = new Dog();
        d.sleep();
    }
}


子类中用一个特殊实现,来替换从父类中继承到的一般实现,这种语法叫做“方法覆盖”。



从语法上说,方法覆盖对方法声明的五个部分都有要求。



1. 访问修饰符相同或更宽


例如,父类的方法如果是 protected 方法,子类如果想要覆盖这个方法,则修饰符至少是 protected,也可以是 public,但是不能是 default 或者 private 的。



2. 返回值类型相同。


如果返回值类型不同,则会产生一个编译错误。


3. 方法名相同。


4. 参数表相同。



2.6 对象创建的过程


在有了继承关系之后,对象创建过程如下:



1. 分配空间。要注意的是,分配空间不光是指分配子类的空间,子类对象中包含的父类对象所需要的空间,一样在这一步统一分配。在分配空间的时候,会把所有的属性值都设为默认值。


2. 递归的构造父类对象。


3. 初始化本类属性。


4. 调用本类的构造方法。

class A{
    int valueA = 100;
    public A(){ valueA=150; }
}
class B extends A{
    int valueB = 200;
    public B(){ valueB=250; }
}
class C extends B{
    int valueC = 300;
    public C(){ valueC=350; }
}
public class TestInherit{
    public static void main(String args[]){
        C c = new C();
    }
}

我们在主方法中创建了一个 C 对象,则创建时的过程如下。


1. 分配空间。在分配空间时,会把 C、B、A 这三个对象的空间一次性都分配完毕,然后把这三个对象的属性都设为默认值。这样,value1,value2,value3 这三个属性都被设置为 0


2. 递归构造 C 对象的父类对象。在这里,要 C 对象的父类对象,就是 B 对象。因此,在这一步需要创建一个 B 对象。


3. 初始化 C 的属性,即把 valueC 赋值为 300


4. 调用 C 的构造方法。



其中,第 2 步,C 对象的父类为 B 对象,因此必须要先创建一个 B 对象。创建 B 对象不用重新分配空间,需要以下几步:


2.1 递归的构造 B 对象的父类对象


2.2 初始化 B 属性:把 vauleB 赋值为 200


2.3 调用 B 的构造方法。



在 2.1 这个步骤中,递归的创建 B 对象的父类对象,也就是创建 A 对象。创建 A对象不需要分配空间,因此,A 对象的创建有这样几步:


2.1.1 创建 A 对象的父类对象。这一步在运行时,没有任何的输出。


2.1.2 初始化 A 的属性,把 valueA 赋值为 100


2.1.3 调用 A 的构造方法。




总结一下:创建 C 对象的步骤一共有 7 步:


1. 分配空间。在一次分配空间时,会把整个继承关系中涉及到的类所需要的空间,都分配完毕,并把所有属性都设为默认值 0。


image.png


2. 初始化 A 类的属性。把 valueA 赋值为 100。


image.png


3. 调用 A 类的构造方法。会把 valueA 的值设为 150。


image.png


4. 初始化 B 类的属性,把 vauleB 赋值为 200。


image.png


5. 调用 B 类的构造方法,会把 valueB 的值设为 250。

image.png



6. 初始化 C 类的属性,把 valueC 赋值为 300。


image.png


7. 调用 C 类的构造方法,会把 valueC 的值设为 350。


image.png



2.7 super 关键字


在默认情况下,创建子类对象时,都会调用父类的无参构造方法。


super 关键字用法一:super 用在构造方法上


super 关键字的第一种用法,就是可以指定在递归构造父类对象的时候,调用父类的哪一个构造方法。


要格外注意的是,super 用在构造方法中时,只能作为构造方法的第一句。


然而,我们曾经介绍过,this 关键字可以在构造方法中,指明调用本类的其他构造方法。并且,对 this()来说,这个语句也只能作为构造方法的第一个语句。这样,在构造方法中,就不能够既使用 this(),又使用 super()。


这样,我们构造方法的第一个语句,就有了三种可能


1. super (参数) 指明调用父类哪个构造方法


2. this (参数) 指明调用本类哪个构造方法


3. 既不是 this(参数)又不是 super(参数)。


编译器会自动在这个构造方法中增加一个语句:“super();” ,即调用父类的无参构造方法。


super 关键字用法二:super 用作引用


最典型的用途是,使用 super在子类中,调用父类被覆盖的方法。

class Parent{
    public void m(){ 
        System.out.println(“m in Parent”);
    }
}
class Child extends Parent{
    public void m (){
        System.out.println(“m in Child”);
    }
    public void m1 (){
           this.m();
    }
    public void m2(){
        super.m();
    }
}
public class TestSuper{
    public static void main(String args[]){
        Child c = new Child();
        c.m1();
        c.m2();
    }
}

super 可以调用父类被覆盖的方法,这种特性在实际编程中有着广泛的应用。


2.8 单继承


Java 语言规定,每一个类只能有一个直接父类。


因此,Java 语言中单继承的特性, 被认为是 Java 语言相对于 C++语言,“简单性”的一个重要体现。


相关文章
|
3天前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
14 3
|
7天前
|
安全 Java API
Java 17新特性让你的代码起飞!
【10月更文挑战第4天】自Java 8发布以来,Java语言经历了多次重大更新,每一次都引入了令人兴奋的新特性,极大地提升了开发效率和代码质量。本文将带你从Java 8一路走到Java 17,探索那些能让你的代码起飞的关键特性。
32 1
|
15天前
|
编解码 Oracle Java
java9到java17的新特性学习--github新项目
本文宣布了一个名为"JavaLearnNote"的新GitHub项目,该项目旨在帮助Java开发者深入理解和掌握从Java 9到Java 17的每个版本的关键新特性,并通过实战演示、社区支持和持续更新来促进学习。
53 3
|
1天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
9 3
|
4天前
|
Java 开发者
在Java的集合世界里,Set以其独特的特性脱颖而出,它通过“哈希魔法”和“红黑树防御”两大绝技
【10月更文挑战第13天】在Java的集合世界里,Set以其独特的特性脱颖而出。它通过“哈希魔法”和“红黑树防御”两大绝技,有效抵御重复元素的侵扰,确保集合的纯洁性和有序性。无论是“人海战术”还是“偷梁换柱”,Set都能从容应对,成为开发者手中不可或缺的利器。
16 6
|
1天前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
7 2
|
1天前
|
Java 开发者
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素。通过哈希表和红黑树两种模式,Set能够高效地识别并拒绝重复元素的入侵,确保集合的纯净。无论是HashSet还是TreeSet,都能在不同的场景下发挥出色的表现,成为开发者手中的利器。
10 2
|
1天前
|
Java
Java Set以其“不重复”的特性,为我们提供了一个高效、简洁的处理唯一性约束数据的方式。
【10月更文挑战第16天】在Java编程中,Set接口确保集合中没有重复元素,每个元素都是独一无二的。HashSet基于哈希表实现,提供高效的添加、删除和查找操作;TreeSet则基于红黑树实现,不仅去重还能自动排序。通过这两个实现类,我们可以轻松处理需要唯一性约束的数据,提升代码质量和效率。
9 2
|
3天前
|
存储 Java 数据处理
在Java集合框架中,Set接口以其独特的“不重复”特性脱颖而出
【10月更文挑战第14天】在Java集合框架中,Set接口以其独特的“不重复”特性脱颖而出。本文通过两个案例展示了Set的实用性和高效性:快速去重和高效查找。通过将列表转换为HashSet,可以轻松实现去重;而Set的contains方法则提供了快速的元素查找功能。这些特性使Set成为处理大量数据时的利器。
11 4
|
4天前
|
存储 Java 数据处理
Java中的Set接口以其独特的“不重复”特性,在集合框架中占据重要地位。
【10月更文挑战第13天】Java中的Set接口以其独特的“不重复”特性,在集合框架中占据重要地位。本文通过两个案例展示了Set的实用性和高效性:快速去重和高效查找。通过将列表转换为HashSet,可以轻松实现去重;而Set的contains方法则提供了高效的元素查找功能。这些特性使Set在处理大量数据时表现出色,值得我们在日常编程中充分利用。
16 3