专业编程基础技术教程

网站首页 > 基础教程 正文

Java路径-23-Java的继承(java面向对象 - 封装、继承和多态)

ccvgpt 2024-07-28 12:23:01 基础教程 10 ℃

1 继承的概念

继承是面向对象的三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,追加属性和方法。

  • 继承是指在原有类的基础上,进行功能扩展,创建新的类型。
  • 继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。
  • JAVA中类只有单继承,没有多继承!
  • 继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。
  • 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示。
  • 子类和父类之间,从意义上讲应该具有"is a"的关系。
  • extends的意思是“扩展”,子类是父类的扩展。

2 为什么要继承

  1. 代码重用:继承允许子类继承父类的属性和方法,避免了重复编写相同的代码,提高了代码的复用性。
  2. 继承层次结构:通过继承可以建立对象之间的层次结构,可以更好地组织和管理代码。
  3. 扩展性:通过继承可以在已有的类的基础上进行扩展,添加新的功能或修改已有的功能,而不影响原有的代码。
  4. 多态性:继承是实现Java中多态性的基础。子类对象可以赋值给父类引用,通过父类引用调用子类对象的方法,实现了不同对象的统一调用接口。
  5. 方法的重写:子类可以重写父类的方法,通过方法的重写可以实现对父类方法的定制化需求。
  6. 代码的可维护性:通过继承可以将代码分解为多个类,每个类专注于不同的功能,提高了代码的可读性和可维护性。
  7. 统一的接口:通过继承可以定义一个统一的接口,子类可以根据自身的需要实现该接口,实现了代码的规范化和标准化。

3 继承的特性

  • 子类拥有父类非 private 的属性、方法。
  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
  • 子类可以用自己的方式实现父类的方法,例如重写。
  • Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
  • 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。

4 类的继承格式

在Java中,使用extends关键字来实现继承。

Java路径-23-Java的继承(java面向对象 - 封装、继承和多态)

class Animal {
    protected String name;
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void speak() {
        System.out.println("I am an animal.");
    }
}

class Dog extends Animal {
    public void speak() {
        System.out.println("I am a dog.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setName("Tom");
        dog.speak(); // 输出:I am a dog.
    }
}

5 继承关键字 extends,implements,super,this,final

(1)extends关键字

在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。

public class Animal{ 
    private String name; 
    private int id; 
    
    public Animal(String myName, int myid) { 
        //初始化属性值 
    } 
    public void eat() { 
        //吃东西方法的具体实现 
    } 
    public void sleep() { 
        //睡觉方法的具体实现 
    } 
}

public class Penguin extends Animal{ }

(2)implements关键字

使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。

public interface A { 
    public void eat(); 
    public void sleep(); 
}

public interface B { 
    public void show(); 
}

public class C implements A,B { 

}

(3)super 与 this 关键字

super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。

this关键字:指向自己的引用。

class Animal {
  void eat() {
    System.out.println("animal : eat");
  }
}
 
class Dog extends Animal {
  void eat() {
    System.out.println("dog : eat");
  }
  void eatTest() {
    this.eat();   // this 调用自己的方法
    super.eat();  // super 调用父类方法
  }
}
 
public class Test {
  public static void main(String[] args) {
    Animal a = new Animal();
    a.eat();
    Dog d = new Dog();
    d.eatTest();
  }
}

输出结果:

animal : eat(animal对象)
dog : eat(子类的对象)
animal : eat

(3)final 关键字

  • final 表示不能被继承 可以用来修饰变量(包括类属性、对象属性、局部变量和形参)、方法(包括类方法和对象方法)和类。

final 含义为 "最终的"。

使用 final 关键字声明类,就是把类定义定义为最终类,不能被继承,或者用于修饰方法,该方法不能被子类重写:

6 构造器

在Java编程语言中,构造器(构造函数)扮演着至关重要的角色:它们负责初始化新创建的对象。当涉及到继承时,子类对象的创建和初始化过程中会涉及到对父类构造器的调用。

6.1 隐式调用父类构造器

当创建一个类的子类对象时,默认情况下,子类构造器会自动隐式地调用父类无参数的构造器。这意味着在子类构造器的实现中不需要明确写出对父类构造器的调用。如果父类中不存在无参数的构造器,并且子类构造器也没有显式地调用父类的有参数构造器,编译器将会报错,因为它无法自行完成对象的正确初始化。

下面的代码展示了隐式调用父类构造器的简单例子:

class Parent {
    public Parent() {
        System.out.println("Parent Constructor");
    }
}
 
class Child extends Parent {
    public Child() {
        // 这里隐式地调用了父类无参数的构造器
        System.out.println("Child Constructor");
    }
}
 
public class Test {
    public static void main(String[] args) {
        Child c = new Child(); // 输出:Parent Constructor
                               //       Child Constructor
    }
}

6.2 显式调用父类构造器

在某些情况下,你可能需要调用父类的有参数构造器,或者父类根本就没有无参数构造器。在这种情况下,你必须在子类构造器中使用super关键字来显式调用父类的构造器。这个调用必须位于子类构造器的第一行。

这里有一个显式调用父类构造器的例子:

class Parent {
    public Parent(String message) {
        System.out.println("Parent Constructor with message: " + message);
    }
}
 
class Child extends Parent {
    public Child() {
        super("Hello from Child"); // 显式地调用父类带参数的构造器
        System.out.println("Child Constructor");
    }
}
 
public class Test {
    public static void main(String[] args) {
        Child c = new Child(); // 输出:Parent Constructor with message: Hello from Child
                               //       Child Constructor
    }
}

6.3 匿名内部类中调用父类构造器

在使用匿名内部类时,如果父类有构造器,无论是默认的还是带参数的,你都需要在匿名类中对应地进行调用。匿名内部类允许你"临时"创建一个没有名称的类,并立即实例化它。当创建匿名内部类的实例时,你必须提供一个与父类构造器匹配的参数列表。

下面是一个在匿名内部类中调用父类构造器的例子:

abstract class Shape {
    private String color;
 
    public Shape(String color) {
        this.color = color;
        System.out.println("Shape constructor called with color: " + color);
    }
 
    public abstract void draw();
}
 
public class Test {
    public static void main(String[] args) {
        Shape circle = new Shape("Red") {
            @Override
            public void draw() {
                System.out.println("Drawing a red circle");
            }
        };
 
        circle.draw(); // 输出:Shape constructor called with color: Red
                       //       Drawing a red circle
    }
}

在本例中,通过new Shape("Red") {...}语句,我们不仅实例化了一个Shape的匿名子类,还显式调用了Shape类的带参数构造器,并传递了字符串"Red"作为参数。这之后,我们覆盖了draw`方法以提供具体的实现。

这样的机制确保了即使在使用匿名内部类时,父类也能被正确地初始化。对于那些设计为被继承的类,这种做法尤为重要,因为它允许父类在构造子类实例时施加控制,确保所有必要的初始化步骤都能执行。

6.4 结论

1. 创建子类对象时,优先调用父类的构造器

2. 子类构造器的第一行,默认隐含语句super(); 用于调用父类的默认无参构造

3. 父类默认含有无参构造。但是,如果你在父类中只定义了有参构造,父类就没有了无参构造,这时在子类构造器中,可以用super(参数) 的形式来访问父类的某个指定的带参构造。继承关系中,对象的初始化顺序为 : 先初始化父类内容,后初始化子类内容。

4. 注意 :this() 和 super() 不可以同时出现在同一构造器中。因为——this() 和 super() 语句都必须位于构造器的首句!

Tags:

最近发表
标签列表