杰瑞科技汇

Java类间继承关系是怎样的?

什么是继承?

在Java中,继承 是面向对象编程的四大基本特性(封装、继承、多态、抽象)之一,它允许一个类(称为子类派生类)获取另一个类(称为父类基类超类)的属性(字段)和方法。

继承的核心思想是 “代码复用”“建立类之间的层次关系”,通过继承,我们可以创建一个新类,该类不仅拥有自己的新特性,还自动拥有父类的所有特性,从而避免了重复编写相同的代码。


继承的语法

在Java中,使用 extends 关键字来声明一个类继承自另一个类。

// 父类 (基类 / 超类)
class ParentClass {
    // 父类的属性和方法
    public void parentMethod() {
        System.out.println("这是父类的方法");
    }
}
// 子类 (派生类)
class ChildClass extends ParentClass {
    // 子类自己的属性和方法
    public void childMethod() {
        System.out.println("这是子类自己的方法");
    }
}

关键点:

  • extends 的英文意思是 “扩展”,这非常形象地描述了继承——子类在父类的基础上进行了扩展。
  • Java 不支持多继承,即一个类不能同时继承自多个父类,这主要是为了避免“菱形问题”(Diamond Problem),即当多个父类拥有相同的方法时,子类会不知道该继承哪一个,Java通过单继承和接口(implements)来解决这个问题。
  • Java支持多层继承,即一个类可以继承自另一个类,而这个类又可以再继承自第三个类,形成一条继承链。
class Animal {}
class Mammal extends Animal {} // Mammal 继承自 Animal
class Dog extends Mammal {}    // Dog 继承自 Mammal,Dog 间接继承了 Animal

继承的特性与规则

理解继承的规则对于正确使用它至关重要。

a) 子类继承父类的什么?

子类会继承父类中非私有的成员(包括字段和方法)。

  • public 成员:子类完全继承,可以直接访问。
  • protected 成员:子类继承,并且可以在子类内部直接访问。
  • 默认(包私有)成员:如果子类和父类在同一个包中,子类可以继承并访问;如果不在同一个包中,则不能访问。
  • private 成员不继承,父类的私有成员对子类是不可见的,子类无法直接访问父类的私有字段或方法。

示例:

class Father {
    public String publicName = "Public Name";
    protected String protectedName = "Protected Name";
    String defaultName = "Default Name"; // 包私有
    private String privateName = "Private Name"; // 私有
    public void publicMethod() {
        System.out.println("Father's public method");
    }
    protected void protectedMethod() {
        System.out.println("Father's protected method");
    }
    void defaultMethod() {
        System.out.println("Father's default method");
    }
    private void privateMethod() {
        System.out.println("Father's private method");
    }
}
class Son extends Father {
    public void testInheritance() {
        System.out.println(this.publicName);      // 可以访问
        System.out.println(this.protectedName);   // 可以访问
        System.out.println(this.defaultName);     // 如果Son和Father在同一个包,可以访问
        // System.out.println(this.privateName); // 编译错误!无法访问private成员
        this.publicMethod();      // 可以调用
        this.protectedMethod();   // 可以调用
        this.defaultMethod();     // 如果在同一个包,可以调用
        // this.privateMethod();  // 编译错误!无法调用private方法
    }
}

b) 方法重写

这是继承中一个非常重要的概念,如果子类认为从父类继承来的某个方法不能满足自己的需求,可以在子类中提供一个具有相同方法名、相同参数列表、相同返回值类型(或其子类)的新实现,这就叫做方法重写

  • 目的:实现多态,让子类的行为更具体化。
  • 语法:在子类中重新定义父类的方法。
  • @Override 注解:强烈建议在重写方法上使用 @Override 注解,它不是必需的,但它可以帮助编译器检查你是否正确地重写了方法(比如方法名写错了),可以有效避免很多低级错误。

示例:

class Animal {
    public void makeSound() {
        System.out.println("动物发出声音");
    }
}
class Dog extends Animal {
    // 重写父类的 makeSound 方法
    @Override
    public void makeSound() {
        System.out.println("汪汪汪!");
    }
}
class Cat extends Animal {
    // 重写父类的 makeSound 方法
    @Override
    public void makeSound() {
        System.out.println("喵喵喵!");
    }
}
public class Test {
    public static void main(String[] args) {
        Animal myDog = new Dog();
        Animal myCat = new Cat();
        myDog.makeSound(); // 输出: 汪汪汪!
        myCat.makeSound(); // 输出: 喵喵喵!
    }
}

c) super 关键字

super 关键字用于在子类中引用父类的成员(字段、方法或构造函数)。

  • 访问父类被重写的方法

    class Dog extends Animal {
        @Override
        public void makeSound() {
            super.makeSound(); // 调用父类 Animal 的 makeSound 方法
            System.out.println("汪汪汪!");
        }
    }
    // 输出:
    // 动物发出声音
    // 汪汪汪!
  • 访问父类的隐藏字段(如果子类定义了与父类同名的字段):

    class Father {
        String name = "Father";
    }
    class Son extends Father {
        String name = "Son";
        public void printNames() {
            System.out.println(this.name);      // 输出: Son
            System.out.println(super.name);     // 输出: Father
        }
    }
  • 调用父类的构造函数: 这是非常重要的一点,子类在创建对象时,会隐式或显式地调用父类的构造函数,构造函数不能被继承

    • 隐式调用:如果子类构造函数的第一行没有使用 super(...),那么默认会调用父类的无参构造函数
    • 显式调用:如果子类构造函数的第一行使用了 super(...),则会调用父类对应的有参构造函数
    • 规则super(...) 必须是子类构造函数中的第一条语句。
    class Father {
        public Father() {
            System.out.println("Father's no-arg constructor");
        }
        public Father(String name) {
            System.out.println("Father's constructor with name: " + name);
        }
    }
    class Son extends Father {
        public Son() {
            // super(); // 这是隐式调用的,可以省略不写
            System.out.println("Son's no-arg constructor");
        }
        public Son(String name) {
            super(name); // 显式调用父类的有参构造函数
            System.out.println("Son's constructor with name: " + name);
        }
    }
    public class Test {
        public static void main(String[] args) {
            System.out.println("--- 创建 Son() 对象 ---");
            Son son1 = new Son();
            // 输出:
            // Father's no-arg constructor
            // Son's no-arg constructor
            System.out.println("\n--- 创建 Son(\"Tom\") 对象 ---");
            Son son2 = new Son("Tom");
            // 输出:
            // Father's constructor with name: Tom
            // Son's constructor with name: Tom
        }
    }

继承的访问权限和可见性

修饰符 本类 同包 不同包的子类 其他类
public
protected
默认 (无)
private

这个表格清晰地展示了,子类能直接访问父类的 publicprotected 成员,但不能访问 private 成员。


Object 类:所有类的祖先

在Java中,所有类都直接或间接地继承自 java.lang.Object 类,如果你没有使用 extends 关键字来指定父类,那么你的类就会默认继承 Object

这意味着:

  • 任何Java对象都可以调用 Object 类中的方法,toString(), equals(), hashCode() 等。
  • 这就是为什么你可以直接在任何对象上调用 .toString() 的原因。

继承的优缺点

优点

  1. 代码复用:这是最主要的好处,子类可以复用父类的代码,减少了冗余。
  2. 建立类层次结构:使得代码结构清晰,逻辑关系明确。Animal -> Mammal -> Dog 的层次结构。
  3. 为多态奠定基础:继承是实现多态的前提,没有继承,就没有方法重写,也就没有多态。

缺点

  1. 破坏封装性:子类对父类有很强的依赖关系,如果父类的实现发生了改变(比如修改了方法签名),可能会影响到所有子类。
  2. 灵活性差:继承关系在编译时就确定了,是静态的,它非常僵硬,不够灵活,如果设计不当,会导致一个庞大而脆弱的继承体系。
  3. “is-a” 关系滥用:继承应该用于描述 “is-a” (是一个) 的关系。Dog is an Animal,如果错误地用于 “has-a” (有一个) 的关系,Car has an Engine,使用继承会导致设计错误,此时应该使用组合

Java的继承关系是一个强大但需要谨慎使用的特性,它通过 extends 关键字建立单层次的类结构,核心目的是实现代码复用和为多态提供支持,子类继承父类的非私有成员,并可以重写父类的方法来定义自己的行为。super 关键字是子类访问父类成员的桥梁,虽然继承带来了诸多好处,但也可能引入耦合度高、灵活性差等问题,因此在设计时需要严格遵循 “is-a” 原则,并考虑是否组合是更优的选择。

分享:
扫描分享到社交APP
上一篇
下一篇