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++语言,“简单性”的一个重要体现。


相关文章
|
28天前
|
存储 安全 Java
Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
【10月更文挑战第17天】Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
57 2
|
29天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
38 3
|
29天前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
32 2
|
1月前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
30 3
|
11天前
|
分布式计算 Java API
Java 8引入了流处理和函数式编程两大新特性
Java 8引入了流处理和函数式编程两大新特性。流处理提供了一种声明式的数据处理方式,使代码更简洁易读;函数式编程通过Lambda表达式和函数式接口,简化了代码书写,提高了灵活性。此外,Java 8还引入了Optional类、新的日期时间API等,进一步增强了编程能力。这些新特性使开发者能够编写更高效、更清晰的代码。
25 4
|
20天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
13 2
|
25天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3
|
25天前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
33 2
|
25天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2
|
26天前
|
存储 Java API
优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。
【10月更文挑战第19天】本文介绍了如何优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。内容包括Map的初始化、使用Stream API处理Map、利用merge方法、使用ComputeIfAbsent和ComputeIfPresent,以及Map的默认方法。这些技巧不仅提高了代码的可读性和维护性,还提升了开发效率。
50 3