继承

在Java中,继承是一种重要的面向对象编程概念,允许一个类(子类)从另一个类(父类)继承属性和方法。子类可以继承父类的非私有属性和方法,同时可以添加自己的属性和方法。

子类继承父类:private 构造方法 是不能被继承的。但是子类可以显示的调用父类的构造super

语法

public class ParentClass {
    // 父类的属性和方法
}

public class ChildClass extends ParentClass {
    // 子类的属性和方法
}

示例代码

// 父类
public class Animal {
    String name;

    public Animal(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println(name + " is eating.");
    }
}

// 子类
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    public void bark() {
        System.out.println(name + " is barking.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog myDog = new Dog("Buddy");
        myDog.eat();
        myDog.bark();
    }
}

在上面的示例中,Animal类是父类,Dog类是子类。Dog类继承了Animal类的属性和方法,并添加了自己的bark方法。在Main类中,创建了一个Dog对象并调用了父类和子类的方法。

在上述代码中,Animal类是超类(super class),也被称为父类(parent class)或基类(base class)。它是被其他类继承的类。

Dog类是子类(subclass),也被称为扩展类(extended class)。它继承了Animal类的属性和方法,并且可以添加自己的属性和方法。

Dog类的定义中,使用了extends关键字来指定Animal类作为父类。这意味着Dog类继承了Animal类的所有非私有属性和方法。通过使用super关键字,Dog类可以访问父类的构造方法和成员变量。

Main类中,创建了一个Dog对象myDog,并调用了父类Animaleat方法和子类Dogbark方法。这是因为子类继承了父类的方法,可以直接使用。

继承树

继承树是指通过继承关系组织起来的类的层次结构。在继承树中,顶部是最通用的类,也称为根类或基类,而底部是更具体的类,也称为子类。子类可以继承父类的属性和方法,同时可以添加自己的属性和方法,形成了一种层级关系。

在一个继承树中,一个类可以有多个子类,但通常只有一个父类。父类可以有多个子类,这种关系被称为单继承和多继承。在Java中,类是单继承的,即一个类只能有一个直接父类。

通过继承树,我们可以更好地组织和管理代码,实现代码的重用和扩展。父类中定义的通用属性和方法可以被多个子类共享,避免了重复编写相同的代码。同时,子类可以根据自身的需求进行扩展和定制,实现了代码的灵活性和可维护性。

继承树的顶部是Object类,它是所有类的根类。所有类都直接或间接地继承自Object类。在Java中,如果一个类没有显式指定父类,则默认继承Object类。

继承树的设计要遵循面向对象编程的原则,如单一职责原则、开闭原则等,以确保代码的可扩展性和可维护性。通过合理设计和使用继承,可以更好地组织代码结构,提高代码的复用性和可读性。

protected 关键字

在 Java 中,protected 是一种访问修饰符,用于限制类的成员(属性和方法)的访问范围。使用 protected 修饰的成员可以被同一包内的类访问,也可以被子类访问,但不能被其他包中的类访问。

语法示例

public class ParentClass {
    protected String protectedField;

    protected void protectedMethod() {
        // 方法实现
    }
}

public class ChildClass extends ParentClass {
    public void accessProtectedMember() {
        System.out.println(protectedField);
        protectedMethod();
    }
}

在上面的示例中,ParentClass 类中的 protectedFieldprotectedMethod 都被修饰为 protected。这意味着这两个成员可以被 ChildClass 类中的方法访问。

ChildClass 类继承了 ParentClass 类,因此可以访问 ParentClass 中被 protected 修饰的成员。在 ChildClass 类的 accessProtectedMember 方法中,可以直接访问 protectedField 和调用 protectedMethod

代码示例

// 父类
public class ParentClass {
    protected String parentName;

    protected void parentMethod() {
        System.out.println("This is a method in ParentClass.");
    }
}

// 子类
public class ChildClass extends ParentClass {
    public void displayParentInfo() {
        parentName = "Parent";
        System.out.println("Parent's name: " + parentName);
        parentMethod();
    }
}

public class Main {
    public static void main(String[] args) {
        ChildClass child = new ChildClass();
        child.displayParentInfo();
    }
}

在上述示例中,ParentClass 类中的 parentNameparentMethod 被修饰为 protectedChildClass 类继承了 ParentClass 类,并在 displayParentInfo 方法中访问了父类的 protected 成员。

Main 类中,创建了一个 ChildClass 对象 child,并调用了 displayParentInfo 方法。该方法中通过子类对象访问了父类的 protected 成员,实现了对父类成员的访问和调用。

通过 protected 关键字,子类可以访问父类的 protected 成员,实现了在继承关系中对成员的保护和共享。

super 关键字

在 Java 中,super 是一个关键字,用于引用父类的构造方法、成员变量和方法。通过 super 关键字,子类可以访问父类的构造方法、成员变量和方法,实现对父类的调用和重用。

示例代码

// 父类
public class ParentClass {
    String parentName;

    public ParentClass(String name) {
        this.parentName = name;
    }

    public void displayParentInfo() {
        System.out.println("Parent's name: " + parentName);
    }
}

// 子类
public class ChildClass extends ParentClass {
    String childName;

    public ChildClass(String parentName, String childName) {
        super(parentName); // 调用父类的构造方法
        this.childName = childName;
    }

    public void displayChildInfo() {
        System.out.println("Child's name: " + childName);
    }

    public void displayParentInfo() {
        super.displayParentInfo(); // 调用父类的方法
        System.out.println("Child's parent name: " + parentName);
    }
}

public class Main {
    public static void main(String[] args) {
        ChildClass child = new ChildClass("Parent", "Child");
        child.displayParentInfo(); // 调用子类的方法
        child.displayChildInfo(); // 调用子类的方法
    }
}

在上述示例中,ParentClass 类有一个构造方法和一个方法用于显示父类信息。ChildClass 类继承了 ParentClass 类,并添加了自己的构造方法和方法用于显示子类信息。

ChildClass 类的构造方法中,通过 super(parentName) 调用了父类 ParentClass 的构造方法,初始化了父类的成员变量。这样子类就可以继承父类的属性。

ChildClass 类的 displayParentInfo 方法中,通过 super.displayParentInfo() 调用了父类 ParentClass 的方法,实现了对父类方法的重用。同时,子类也可以添加自己的方法,如 displayChildInfo 方法用于显示子类信息。

Main 类中,创建了一个 ChildClass 对象 child,并调用了子类和父类的方法,展示了 super 关键字的使用方式。

通过 super 关键字,子类可以访问父类的构造方法、成员变量和方法,实现了对父类的调用和重用,同时也可以在子类中添加自己的属性和方法,实现了代码的扩展和灵活性。

当父类没有默认的构造方法时,子类必须显式调用父类的构造方法,并提供合适的参数。这是因为子类的构造方法需要初始化父类的成员变量,而没有默认的构造方法时,编译器无法自动调用父类的构造方法。

子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。如果父类有多个构造方法,子类必须选择一个合适的构造方法来调用。

下面是一个示例代码:

// 父类
public class ParentClass {
    private int age;

    public ParentClass(int age) {
        this.age = age;
    }

    public void displayAge() {
        System.out.println("Age: " + age);
    }
}

// 子类
public class ChildClass extends ParentClass {
    private String name;

    public ChildClass(int age, String name) {
        super(age); // 调用父类的构造方法
        this.name = name;
    }

    public void displayName() {
        System.out.println("Name: " + name);
    }
}

public class Main {
    public static void main(String[] args) {
        ChildClass child = new ChildClass(10, "John");
        child.displayAge(); // 调用父类的方法
        child.displayName(); // 调用子类的方法
    }
}

在上述示例中,父类 ParentClass 有一个带有参数的构造方法,用于初始化父类的成员变量 age。子类 ChildClass 继承了父类,并在自己的构造方法中调用了父类的构造方法 super(age),以初始化父类的成员变量。子类也添加了自己的成员变量 name 和方法 displayName

Main 类中,创建了一个 ChildClass 对象 child,并调用了子类和父类的方法,展示了子类调用父类构造方法的过程。

通过显式调用父类的构造方法,子类可以初始化父类的成员变量,并在子类中添加自己的成员变量和方法,实现了代码的扩展和灵活性。

一般建议:如果给类中编写构造方法,则手动编写一个无参构造,防止报错

阻止继承

有时候,我们希望某个类不被其他类继承,即不允许创建该类的子类。在 Java 中,可以通过使用 final 关键字来阻止继承。当一个类被声明为 final 时,它不能被其他类继承。

示例代码

// final 类
final class FinalClass {
    // 类的成员和方法
}

// 试图继承 final 类,会导致编译错误
// class ChildClass extends FinalClass {
//     // 子类的成员和方法
// }

在上面的示例中,FinalClass 类被声明为 final,因此不能被其他类继承。如果尝试创建一个子类 ChildClass 继承 FinalClass,编译器会报错,因为 FinalClass 是一个 final 类,不允许被继承。

通过将类声明为 final,可以有效地阻止其他类对该类的继承,确保该类的独立性和完整性。这在某些情况下可以提高代码的安全性和稳定性。

阻止继承的类通常是一些工具类或者具有特定功能的类,不希望被修改或继承的情况下使用。通过使用 final 关键字,可以明确表明该类不应被继承,从而避免意外的继承行为。

总结

通过 final 关键字可以阻止类的继承,确保类的独立性和完整性。声明为 final 的类不能被其他类继承,从而避免意外的继承行为。这种做法适用于一些特定场景,如工具类或具有特定功能的类。

向上转型

向上转型是指将一个子类的实例赋值给其父类类型的变量。通过向上转型,可以实现将子类对象当做父类对象来处理,从而实现多态性。在 Java 中,向上转型是自动进行的,无需显式转换。

示例代码

// 父类
class Animal {
    public void eat() {
        System.out.println("Animal is eating.");
    }
}

// 子类
class Dog extends Animal {
    public void bark() {
        System.out.println("Dog is barking.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog(); // 向上转型
        animal.eat(); // 调用父类方法
        // animal.bark(); // 编译错误,无法调用子类方法
    }
}

在上面的示例中,Dog 类是 Animal 类的子类,通过 new Dog() 创建了一个 Dog 类的实例,并将其赋值给 Animal 类型的变量 animal,这就是向上转型。通过向上转型,Dog 类的实例可以当做 Animal 类的实例来处理。

Main 类中,通过 animal.eat() 调用了 Animal 类的 eat 方法,这是因为 animal 变量的类型是 Animal 类型。但是无法通过 animal.bark() 调用 Dog 类的 bark 方法,因为 animal 变量的类型是 Animal 类型,无法访问子类特有的方法。

通过向上转型,可以实现对父类和子类的统一处理,提高代码的灵活性和可扩展性。在实际开发中,向上转型经常用于方法参数的传递和返回值的处理,以实现多态的效果。

总之,向上转型是将子类对象赋值给父类类型的变量,实现多态性和统一处理的一种机制。

向下转型

向下转型是指将一个父类类型的变量转换为其子类类型的变量。在 Java 中,向下转型需要显式进行类型转换,并且需要注意转换的安全性,因为并非所有的父类类型都可以成功转换为子类类型。如果尝试将一个父类类型的变量转换为其非对应的子类类型,会导致编译错误或运行时异常。

示例代码

// 父类
class Animal {
    public void eat() {
        System.out.println("Animal is eating.");
    }
}

// 子类
class Dog extends Animal {
    public void bark() {
        System.out.println("Dog is barking.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog(); // 向上转型
        animal.eat(); // 调用父类方法

        // 向下转型
        Dog dog = (Dog) animal;
        dog.bark(); // 调用子类方法
    }
}

在上面的示例中,首先通过向上转型将 Dog 类的实例赋值给 Animal 类型的变量 animal。然后通过向下转型,将 animal 变量转换为 Dog 类型的变量 dog。通过向下转型,可以调用 Dog 类特有的方法 bark()

需要注意的是,在进行向下转型时,需要确保原始对象的实际类型是目标类型或其子类类型,否则会导致 ClassCastException 运行时异常。因此,在进行向下转型时,通常会使用 instanceof 运算符来先进行类型检查,确保安全进行转型。

示例代码(带类型检查)

// 父类
class Animal {
    public void eat() {
        System.out.println("Animal is eating.");
    }
}

// 子类
class Dog extends Animal {
    public void bark() {
        System.out.println("Dog is barking.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog(); // 向上转型
        animal.eat(); // 调用父类方法

        // 向下转型
        if (animal instanceof Dog) {
            Dog dog = (Dog) animal;
            dog.bark(); // 调用子类方法
        } else {
            System.out.println("Cannot cast to Dog.");
        }
    }
}

在上述示例中,通过使用 instanceof 运算符进行类型检查,确保了向下转型的安全性。如果 animal 变量的实际类型不是 Dog 类型或其子类类型,就会输出提示信息,避免了可能的运行时异常。

通过向下转型,可以实现对父类对象的具体子类方法的调用,实现了多态性和灵活性。在实际开发中,向下转型通常用于需要调用子类特有方法的场景,但需要注意安全性和类型检查,以避免潜在的异常情况。

总之,向下转型是将父类类型的变量转换为其子类类型的变量,需要显式进行类型转换,并通过 instanceof 运算符进行类型检查,确保转换的安全性。

区分继承和组合

继承

  • 定义:继承是一种面向对象编程的概念,允许一个类(子类)从另一个类(父类)继承属性和方法。

  • 特点:子类继承父类的属性和方法,可以添加自己的属性和方法,形成一种层级关系。

  • 示例代码

// 父类
class Animal {
    String name;

    public Animal(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println(name + " is eating.");
    }
}

// 子类
class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    public void bark() {
        System.out.println(name + " is barking.");
    }
}

组合

  • 定义:组合是一种对象关系,一个类包含另一个类的对象作为自己的成员变量。

  • 特点:通过组合,一个类可以使用其他类的功能,但不继承其属性和方法。

  • 示例代码

// 组合示例
class Engine {
    public void start() {
        System.out.println("Engine started.");
    }
}

class Car {
    private String model;
    private Engine engine;

    public Car(String model) {
        this.model = model;
        this.engine = new Engine();
    }

    public void startCar() {
        System.out.println("Car model: " + model);
        engine.start();
    }
}

在上述示例中,Car 类通过组合的方式包含了 Engine 类的对象作为自己的成员变量,从而实现了使用 Engine 类的功能。与继承不同的是,Car 类并没有继承 Engine 类的属性和方法,而是通过组合来使用 Engine 类的功能。

通过继承,子类可以获得父类的属性和方法,形成一种层级关系;而通过组合,一个类可以包含其他类的对象,实现代码的复用和灵活性。在设计中,需要根据实际需求来选择使用继承还是组合,以实现代码的结构清晰和可维护性。

方法重载

方法重载是指在同一个类中可以定义多个方法,这些方法具有相同的名称但参数列表不同(参数类型、参数个数或参数顺序)。通过方法重载,可以让同名方法具有不同的行为,提高代码的灵活性和可读性。这种方法名相同,但各自的参数不同,称为方法重载(Overload)。

示例代码

public class Calculator {
    // 方法重载:两个整数相加
    public int add(int a, int b) {
        return a + b;
    }

    // 方法重载:三个整数相加
    public int add(int a, int b, int c) {
        return a + b + c;
    }

    // 方法重载:两个浮点数相加
    public double add(double a, double b) {
        return a + b;
    }

    // 方法重载:字符串拼接
    public String add(String a, String b) {
        return a + b;
    }
}

public class Main {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();

        // 调用不同版本的 add 方法
        System.out.println(calculator.add(1, 2)); // 调用两个整数相加的方法
        System.out.println(calculator.add(1, 2, 3)); // 调用三个整数相加的方法
        System.out.println(calculator.add(1.5, 2.5)); // 调用两个浮点数相加的方法
        System.out.println(calculator.add("Hello, ", "World!")); // 调用字符串拼接的方法
    }
}

在上面的示例中,Calculator 类定义了多个同名方法 add,它们具有不同的参数列表。通过方法重载,可以根据传入的参数类型、数量或顺序来调用不同的方法。

Main 类中,创建了一个 Calculator 对象 calculator,并分别调用了不同版本的 add 方法。根据传入的参数类型和数量,编译器会自动匹配调用对应的方法,实现了方法重载的效果。

通过方法重载,可以根据不同的需求定义多个同名方法,提高代码的灵活性和可读性。在实际开发中,方法重载经常用于实现相似功能但参数类型、数量或顺序不同的方法。

总之,方法重载是指在同一个类中可以定义多个同名方法,它们具有不同的参数列表,通过方法重载可以实现同名方法的多态性和灵活性。

注意:方法重载的返回值类型通常都是相同的。

方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。

举个例子,String类提供了多个重载方法indexOf(),可以查找子串:

  • int indexOf(int ch):根据字符的Unicode码查找;

  • int indexOf(String str):根据字符串查找;

  • int indexOf(int ch, int fromIndex):根据字符查找,但指定起始位置;

  • int indexOf(String str, int fromIndex)根据字符串查找,但指定起始位置。

方法重写

方法重写是面向对象编程中的一个重要概念,也称为覆写(override)。当子类继承父类后,子类可以对父类的方法进行重写,即在子类中重新定义一个与父类方法签名(方法名、参数列表、返回类型)相同的方法。通过方法重写,子类可以根据自身的需求来重新实现父类的方法,从而实现对方法的定制和扩展。

特点

  • 方法名、参数列表、返回类型必须与父类方法一致。

  • 访问修饰符不能比父类方法的访问修饰符更严格。

  • 子类方法不能比父类方法抛出更多的异常。

  • 重写的方法不能是 staticfinal 类型。

示例

// 父类
class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound.");
    }
}

// 子类
class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.makeSound(); // 调用子类重写的方法
    }
}

在上面的示例中,Animal 类有一个 makeSound 方法,Dog 类继承了 Animal 类并重写了 makeSound 方法。在 Main 类中,创建了一个 Dog 类的实例,并将其赋值给 Animal 类型的变量 animal,然后调用 animal.makeSound() 方法。由于方法重写,实际调用的是 Dog 类中重写的 makeSound 方法,输出结果为 "Dog barks."。

通过方法重写,子类可以根据自身的特性和需求来重新实现父类的方法,实现了对方法的个性化定制。方法重写是实现多态性的一种方式,提高了代码的灵活性和可扩展性。

调用父类方法

在子类中重写父类方法后,有时候我们需要在子类方法中调用父类的方法。可以通过 super 关键字来实现调用父类方法。

// 父类
class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound.");
    }
}

// 子类
class Dog extends Animal {
    @Override
    public void makeSound() {
        super.makeSound(); // 调用父类方法
        System.out.println("Dog barks.");
    }
}

在上面的示例中,Dog 类重写了 makeSound 方法,并通过 super.makeSound() 调用了父类 AnimalmakeSound 方法。这样可以在子类方法中保留父类方法的功能,并在其基础上添加新的功能。

注意事项

  • 方法重写是子类对父类方法的重新实现,要求方法名、参数列表、返回类型必须一致。

  • 访问修饰符不能比父类方法的访问修饰符更严格,如父类方法为 public,子类方法不能为 private

  • 重写的方法不能比父类方法抛出更多的异常,可以抛出相同的异常或其子类异常。

  • 重写的方法不能是 staticfinal 类型,因为 static 方法属于类,不是实例方法,而 final 方法不能被重写。

通过方法重写,子类可以根据自身的需求来重新实现父类的方法,实现对方法的个性化定制和扩展。方法重写是面向对象编程中实现多态性的重要手段,提高了代码的灵活性和可维护性。