访问修饰符与继承

JavaJavaBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

介绍

在本实验中,你将学习访问修饰符和继承。通过不同的修饰符,访问级别会有所不同。Java 中的继承类似于生物学中的继承,子类可以保留父类的特性,并在某些方面表现出不同的行为。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL java(("`Java`")) -.-> java/ObjectOrientedandAdvancedConceptsGroup(["`Object-Oriented and Advanced Concepts`"]) java/ObjectOrientedandAdvancedConceptsGroup -.-> java/classes_objects("`Classes/Objects`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/class_attributes("`Class Attributes`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/class_methods("`Class Methods`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/constructors("`Constructors`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/modifiers("`Modifiers`") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/inheritance("`Inheritance`") subgraph Lab Skills java/classes_objects -.-> lab-178543{{"`访问修饰符与继承`"}} java/class_attributes -.-> lab-178543{{"`访问修饰符与继承`"}} java/class_methods -.-> lab-178543{{"`访问修饰符与继承`"}} java/constructors -.-> lab-178543{{"`访问修饰符与继承`"}} java/modifiers -.-> lab-178543{{"`访问修饰符与继承`"}} java/inheritance -.-> lab-178543{{"`访问修饰符与继承`"}} end

访问修饰符

到目前为止,我们已经编写了一些代码。在之前的实验中,我们编写了一个类。其中使用了一些修饰符,例如 publicprivate。那么,这些词的含义是什么呢?

Java 提供了多种访问修饰符来为类、变量、方法和构造函数设置访问级别。四种访问级别如下:

  • default(默认): 对包内可见,这是默认的访问级别。不需要任何修饰符。
  • private(私有): 仅对定义类可见。
  • public(公共): 对 Java 宇宙(所有类型)可见。你可以在任何地方访问它们。
  • protected(受保护): 对包内和所有子类(即使在其他包中)可见。
Java 访问修饰符示意图

Java 还提供了多种非访问修饰符,以实现其他行为微调方式:

  • static: 该修饰符用于创建独立于任何类实例的变量或方法。无论创建了多少个实例,类中只存在一份 static 变量的副本。
  • final: final 变量只能显式初始化一次。final 方法不能被任何子类重写。声明为 final 的类不能被继承。
  • abstract: abstract 类永远不能被实例化。abstract 方法是没有实现的方法声明。
  • synchronized/volatile: synchronizedvolatile 修饰符与线程相关。

示例:

/home/labex/project/modifierTest.java 文件中编写以下代码:

public class modifierTest {
    // static 变量在类加载时初始化。
    public static int i = 10;
    public static final int NUM = 5;
    // 非 static 变量在对象创建时初始化。
    public int j = 1;

    /*
     * 静态代码块,类加载时执行
     * 创建新对象不会再次执行该代码块,仅执行一次。
    */
    static{
        System.out.println("this is a class static block.");
    }
    public static void main(String[] args)
    {
        System.out.println("this is in main method");

        // 你可以访问并修改 i
        modifierTest.i = 20;  // 等同于 obj.i = 20
        System.out.println("Class variable i = " + modifierTest.i);
        // 你可以访问 NUM,但不能修改它
        // HelloWorld.NUM = 10;     这会导致错误,NUM 是 final 的,不可变
        System.out.println("Class variable NUM = " + modifierTest.NUM);

        // 创建新对象
        modifierTest obj = new modifierTest();
        // 我们可以使用类或对象访问静态方法和静态属性
        obj.staticMethod();  // 等同于 modifierTest.staticMethod()
        // 你不能这样访问 j: modifierTest.j
        System.out.println("Object variable j = " + obj.j);
    }
    // 构造函数,只有在创建新对象时才会调用。
    public modifierTest(){
        System.out.println("this is in object's constructor.");
    }
    public static void staticMethod(){
        System.out.println("this is a static method");
    }
}

输出:

使用以下命令运行 modifierTest.java 文件:

javac /home/labex/project/modifierTest.java
java modifierTest

查看输出:

this is a class static block.
this is in main method
Class variable i = 20
Class variable NUM = 5
this is in object's constructor.
this is a static method
Object variable j = 1

继承

在许多情况下,我们已经编写了一个类。然后,我们需要编写一个新类,只是为了对前一个类的代码进行少量修改,并且它们在逻辑上存在某种关系。这时我们可以使用继承。我们使用关键字 extends 来实现继承。子类通过继承获得父类所有可访问的属性和方法,同时子类也可以拥有自己特有的属性和方法。在子类中,我们可以访问父类中声明为 publicprotected 的成员,但不能直接访问 private 成员。请看一个示例:继承结构如下图所示。你可以实现多级继承(水平)或层次继承(垂直):

继承结构图

示例:

/home/labex/project/inheritanceTest.java 文件中编写以下代码:

class Animal{
    // 我是什么种类的动物。
    private String species;
    private int age = 8;

    public Animal(){
        System.out.println("Animal's constructor");
    }
    public void grow(){
        // 在这个类中,我们可以直接访问私有属性。
        System.out.println("I'm "+ this.age + " years old, " +"I grow up.");
    }
}
class Dog extends Animal{
    private String color;
    // 在这个类中,我们不能访问父类的私有属性,但可以访问 grow() 方法。
    public Dog(){
        System.out.println("Dog's constructor");
    }
    public void run(){
        this.grow();
        System.out.println("I'm dog, I can run.");
    }
}
class Bird extends Animal{
    private double weight;

    public Bird(){
        // 如果显式调用父类的构造函数,必须在这里的第一行。
        // super();
        System.out.println("Bird's constructor");
    }
    public void fly(){
        this.grow();
        System.out.println("I'm bird, I can fly.");
    }
}
public class inheritanceTest{
    public static void main(String[] args){
        Dog dog = new Dog();
        dog.run();

        Bird bird = new Bird();
        bird.fly();
    }
}

输出:

使用以下命令运行 inheritanceTest.java 文件:

javac /home/labex/project/inheritanceTest.java
java inheritanceTest

查看输出:

Animal's constructor
Dog's constructor
I'm 8 years old, I grow up.
I'm dog, I can run.
Animal's constructor
Bird's constructor
I'm 8 years old, I grow up.
I'm bird, I can fly.

当你看到这个输出时,可能会感到困惑。别担心,我们会解释原因。当我们使用 new 创建子类的对象时,默认情况下会首先从继承树结构的“顶部”开始调用父类的默认构造函数,最后执行自己的构造函数。可以使用 super 关键字显式调用父类的构造函数,但它必须是构造函数中的第一条语句(如果存在)。super 关键字指向调用类在层次结构中紧邻的父类。

总结

通过访问修饰符,我们可以编写安全的代码,隐藏细节,实现访问控制。其他用户无需了解我们如何实现方法的细节。我们为其他调用者提供了一个接口。对于访问修饰符,你可以阅读 Java 源代码库来理解它们之间的区别。继承,记住层次结构,是类之间的关系。

您可能感兴趣的其他 Java 教程