继承

何为继承


继承(Inheritance)是面向对象编程(OOP)中的一个基本概念,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。继承是一种机制,通过这种机制,可以创建一个新类,这个新类是现有类的修改版或扩展版。

继承的特点:

  1. 代码复用:继承支持代码复用。子类可以继承父类的代码,无需重新编写相同的代码。

  2. 层次结构:继承支持创建类之间的层次结构,这有助于组织和管理大型软件项目。

  3. 扩展性:子类可以扩展父类的功能,通过添加新的方法或覆盖现有方法来实现。

  4. 多态性:继承是实现多态性的一个基础。通过继承,子类可以被看作是父类的一种特殊类型,这允许将子类对象视为父类对象使用。

  5. 访问控制:子类可以访问父类的公共(public)和受保护(protected)成员,但不能直接访问私有(private)成员。

  6. 方法重写:子类可以重写(Override)父类的方法,提供特定的实现。

  7. 构造器:子类继承父类的属性,但需要调用父类的构造器来初始化这些属性。

  8. super 关键字:在子类中,可以使用 super 关键字来引用父类的成员,包括调用父类的构造器或方法。

  9. 类型兼容性:在Java中,子类是其父类的一个子类型。因此,子类的对象可以在任何期望父类对象的地方使用。

  10. 最终类:如果一个类被声明为 final,则它不能被其他类继承。

示例:

// 父类
public class Animal {
    protected String name;

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

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

    public void sleep() {
        System.out.println(name + " is sleeping.");
    }
}

// 子类
public class Dog extends Animal { // Dog 继承自 Animal
    public Dog(String name) {
        super(name); // 调用父类的构造器
    }

    @Override // 注解表示我们正在重写一个方法
    public void eat() {
        System.out.println(name + " is eating dog food.");
    }

    public void bark() {
        System.out.println(name + " says bark!");
    }
}

// 使用继承
public class Main {
    public static void main(String[] args) {
        Dog myDog = new Dog("Buddy");
        myDog.eat();  // 使用继承并重写的方法
        myDog.bark(); // Dog类特有的方法
    }
}

在这个示例中,Dog 类通过 extends 关键字继承自 Animal 类。Dog 类继承了 Animal 类的 name 属性和 eat()sleep() 方法,并且重写了 eat() 方法,添加了 bark() 方法。这展示了继承如何用于代码复用和功能扩展。

继承的优缺点


继承作为面向对象编程的一个核心概念,提供了代码复用和建立层次结构的能力,但同时也带来了一些设计上的挑战。

优点:

  1. 代码复用:继承允许子类继承父类的属性和方法,减少了代码的重复编写,提高了开发效率。

  2. 层次结构:继承支持创建清晰的类层次结构,有助于组织和管理大型软件项目。

  3. 多态性:继承是实现多态性的基础,允许将子类对象视为父类对象使用,提高了程序的灵活性。

  4. 扩展性:子类可以扩展父类的功能,通过添加新的方法或覆盖现有方法来实现特定的行为。

  5. 简化性:使用继承可以简化复杂的系统设计,将通用功能抽象到父类中,子类专注于特定功能。

  6. 维护性:当父类的方法需要修改时,所有继承自该父类的子类都会自动获得更新,简化了维护工作。

缺点:

  1. 紧密耦合:继承创建了类之间的强关联,父类的改变可能会影响到所有子类,这可能导致代码难以维护。

  2. 脆弱性:高度的继承结构可能使得系统变得脆弱,一个小的修改可能会在意想不到的地方引起问题。

  3. 限制性:继承是静态的,一旦定义了继承关系,就很难改变。这限制了类的灵活性。

  4. 继承深度:过度使用继承可能导致继承层次过深,使得代码难以理解和跟踪。

  5. 不灵活:继承不支持多重继承,这限制了从多个父类继承不同功能的能力。

  6. 滥用:继承有时被滥用,例如,为了获得一些工具方法而继承一个不相关的类,这违反了OOP的基本原则。

  7. 隐藏细节:继承可能会隐藏实现细节,使得代码阅读者难以理解代码的真正行为。

  8. 修改限制:如果父类的方法被声明为final,则子类无法重写该方法,这限制了子类的扩展性。

  9. 继承而非组合:有时开发者可能会优先使用继承而不是组合,这可能导致设计问题,因为继承并不总是实现代码复用的最好方式。

总的来说,继承是一个强大的工具,但应该谨慎使用。在某些情况下,组合或其他设计模式可能是更好的选择。正确使用继承可以带来代码复用和清晰的层次结构,但也需要考虑到它可能引入的耦合性和维护挑战。

Object类


在Java中,Object 类是所有Java类的根类,位于类继承层次结构的顶端。这意味着Java中的每个类都隐式地继承了Object 类,除非你使用final关键字明确地阻止进一步继承。Object 类位于java.lang包中,它是Java语言的基础。

Object类的特点:

  1. 通用性:作为根类,Object 类定义了所有Java对象共有的基本行为。

  2. 方法

    • equals(Object obj): 用于比较两个对象的等价性,默认实现比较对象的内存地址,但通常被重写以提供有意义的等价性比较。
    • hashCode(): 返回对象的哈希码,通常与equals方法一起重写,以确保相等的对象有相同的哈希码。
    • toString(): 返回对象的字符串表示,默认实现返回对象的类名和内存地址的无符号十六进制表示,但通常被重写以提供更有意义的信息。
    • clone(): 创建并返回对象的一个副本,具体行为依赖于Cloneable接口的实现。
    • finalize(): 在对象被垃圾回收器回收前进行清理操作,但不推荐使用,因为Java 9开始已被标记为过时,并在Java 14中被移除。
  3. 序列化Object 类实现了Serializable接口,这意味着所有Java对象都可以被序列化和反序列化。

  4. 等待和通知机制

    • wait(): 导致当前线程等待,直到另一个线程调用对象的notify()notifyAll()方法。
    • notify(): 唤醒在此对象上等待的单个线程。
    • notifyAll(): 唤醒在此对象上等待的所有线程。
  5. 内存管理Object 类的方法支持Java的内存管理机制,特别是垃圾回收。

  6. 根类:由于Object类是所有Java类的父类,它为所有对象提供了一个公共的类型。

  7. 反射Object 类是反射的起点,因为所有类都隐式地继承自Object,所以Object类的属性和方法在反射API中是可用的。

  8. 实例创建:使用new关键字创建对象时,所有对象都会隐式地调用Object类的构造器。

示例:

public class MyClass {
    private int value;

    public MyClass(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "MyClass{" + "value=" + value + '}';
    }

    public static void main(String[] args) {
        MyClass myObject = new MyClass(42);
        System.out.println(myObject.toString()); // 重写Object类的toString()方法
    }
}

在这个例子中,MyClass 隐式地继承了Object 类,因此可以使用Object 类的toString()方法。MyClass 重写了toString()方法,以提供更具体的字符串表示。

作为根类,Object 类为Java语言提供了一个统一的基础,确保了所有对象都具有一些基本的行为和特性。

常用方法有哪些

Java的Object类定义了一些常用方法,这些方法在Java编程中非常普遍,经常被重写以提供特定功能。

  1. equals(Object obj)

    • 用于比较两个对象的等价性。默认实现检查对象的内存地址是否相同,但通常被重写以提供基于属性的比较。
  2. hashCode()

    • 返回对象的哈希码,用于在哈希表中定位对象。如果equals方法被重写,通常也需要重写hashCode方法,以维护equalshashCode的一致性。
  3. toString()

    • 返回对象的字符串表示。默认实现返回对象的类名和内存地址的无符号十六进制表示,但通常被重写以提供更有意义的信息。
  4. `clone()``

    • 创建并返回对象的一个副本。这个方法是受保护的,需要在子类中被重写为public,并且通常还要实现Cloneable接口。
  5. `finalize()``(已在Java 14中移除):

    • 允许对象在被垃圾回收器回收前进行清理操作。由于不可预测性和替代机制的存在,此方法不推荐使用。
  6. `getClass()``

    • 返回对象的Class对象,表示对象的运行时类。
  7. `wait()``

    • 导致当前线程等待,直到另一个线程调用对象的notify()notifyAll()方法。
  8. `notify()``

    • 唤醒在此对象上等待的单个线程,选择是任意的。
  9. `notifyAll()``

    • 唤醒在此对象上等待的所有线程。

示例:

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && (name != null ? name.equals(person.name) : person.name == null);
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    // 其他方法...
}

在这个Person类的例子中,我们重写了equalshashCodetoString方法。equals方法用于比较两个Person对象是否相等,基于它们的nameage属性。hashCode方法返回一个基于属性的哈希码。toString方法提供了Person对象的字符串表示,这对于调试和记录日志非常有用。

方法重写


方法重写(Method Overriding)是面向对象编程中的一个概念,它发生在子类中,子类提供一个已在父类中定义的方法的新实现。方法重写使得子类可以改变从父类继承来的方法的行为。

方法重写的特点:

  1. 方法签名相同:被重写的方法必须具有与父类中相同的方法签名,即方法名、参数列表和返回类型(协变返回类型除外)必须一致。

  2. 访问级别:子类中重写的方法不能有比父类更严格的访问级别。

  3. 非静态方法:只能重写父类中的非静态方法。静态方法不能被子类重写。

  4. 重写注解:在Java中,可以使用@Override注解来明确地表示一个方法是重写自父类的方法。如果方法没有正确地重写父类方法,编译器会报错。

  5. 多态性:方法重写是多态性的一个重要方面,它允许通过父类引用调用子类重写的方法。

  6. 调用super方法:在子类的方法中,可以使用super关键字调用父类中被重写的方法。

  7. 方法的可见性:如果子类重写的方法使用了更宽松的访问修饰符,那么子类的这个方法可以被子类的子类访问。

  8. 抽象类和接口:如果父类是一个抽象类或实现了接口,子类必须重写所有抽象方法。

示例:

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

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myAnimal = new Dog(); // 多态性
        myAnimal.sound(); // 输出: Dog barks
    }
}

在这个示例中,Dog 类重写了 Animal 类的 sound 方法。Dog 类的 sound 方法提供了一个新的实现,当通过 Animal 类型的引用调用时,实际执行的是 Dog 类中重写的方法。这展示了多态性,即同一个接口可以有不同的实现。

方法重写规则

  1. 方法重写(Method Overriding)在面向对象编程中遵循一系列规则,确保继承体系中方法的正确调用和行为。

    1. 继承:方法重写发生在子类与其父类之间。子类必须继承父类的方法才能重写它。

    2. 方法签名:重写的方法必须具有与被重写方法相同的方法签名,即相同的方法名、参数列表和返回类型(对于返回类型,子类的方法可以返回父类方法返回类型的子类型,这称为协变返回类型)。

    3. 访问级别:重写的方法不能缩小访问级别。例如,如果父类中的方法声明为public,则子类中的重写方法不能声明为protectedprivate

    4. 非静态方法:只能重写父类中的非静态方法。静态方法不能被子类重写,但可以被子类隐藏(Hide)。

    5. 重写注解:使用@Override注解可以帮助编译器检查方法是否正确重写了父类的方法。如果方法没有正确重写父类中的方法,编译器将报错。

    6. final关键字:如果父类的方法被声明为final,则不能被子类重写。

    7. super调用:在子类的重写方法中,可以使用super关键字来调用父类中被重写的方法。

    8. 多态性:方法重写是多态性的一个重要方面。当通过父类的引用调用重写的方法时,实际执行的是子类中的方法。

    9. 抽象方法:如果父类是一个抽象类,子类必须重写所有抽象方法,除非子类也是抽象类。

    10. 方法的可见性:如果子类重写的方法使用了比父类更宽松的访问修饰符,那么子类的这个方法可以被子类的子类访问。

    11. 接口方法:如果父类是一个接口,那么实现该接口的类必须提供接口中所有方法的具体实现。

    12. 构造器:构造器不能被重写,但可以被子类通过调用父类的构造器来扩展。

    13. 私有方法:私有方法不能被子类重写,因为它们在类的外部不可见。

方法重写与方法重载的区别

方法重写(Method Overriding)和方法重载(Method Overloading)是面向对象编程中的两个不同的概念,它们在Java等语言中有着不同的应用和规则:

方法重写(Method Overriding):

  1. 定义:发生在子类和父类之间,子类提供一个已在父类中定义的方法的新实现。

  2. 目的:改变继承自父类的行为。

  3. 方法签名:必须与被重写的方法具有相同的方法签名(方法名、参数列表和返回类型)。

  4. 访问控制:子类重写的方法不能有比父类更严格的访问控制。

  5. 使用场景:实现多态性,允许通过父类引用调用子类的方法。

  6. 例子

    class Animal {
        void sound() {
            System.out.println("Animal makes a sound");
        }
    }
    class Dog extends Animal {
        @Override
        void sound() {
            System.out.println("Dog barks");
        }
    }
    

方法重载(Method Overloading):

  1. 定义:在同一个类中,存在多个同名方法,但它们的参数列表不同(参数数量、类型或顺序至少有一项不同)。

  2. 目的:提供相同功能的多个变体,允许调用者根据参数的不同选择不同的方法。

  3. 方法签名:必须有所不同,至少在一个方面(参数的数量、类型或顺序)。

  4. 访问控制:各个重载方法可以有不同的访问控制修饰符。

  5. 使用场景:提供多种方法调用选项,使得调用者可以根据需要选择最合适的方法。

  6. 例子

    class Calculator {
        int add(int a, int b) {
            return a + b;
        }
        
        double add(double a, double b) {
            return a + b;
        }
    }
    

主要区别:

  • 继承:方法重写需要继承关系,发生在子类和父类之间;方法重载发生在同一个类中。
  • 方法签名:方法重写要求方法签名相同;方法重载要求方法签名不同。
  • 返回类型:方法重写要求返回类型与被重写的方法一致或为其子类型;方法重载的方法可以有不同的返回类型。
  • 访问级别:方法重写不能缩小访问级别;方法重载的各个方法可以独立设置访问级别。
  • 多态性:方法重写是实现多态性的一种方式;方法重载不是多态性。

super关键字


在Java中,super 关键字是一种特殊的引用,它指向当前对象的直接父类实例。

super 的作用:

  1. 访问父类的成员
    使用 super 可以访问直接父类中的成员变量和方法,特别是当子类中有同名成员时。

    public class Parent {
        protected int value = 10;
        
        public void printValue() {
            System.out.println(value);
        }
    }
    
    public class Child extends Parent {
        private int value = 20;
        
        public void printValue() {
            super.printValue(); // 访问父类的方法
            System.out.println(value); // 访问子类的变量
        }
    }
    
  2. 调用父类的构造器
    使用 super() 可以显式调用父类的构造器,这通常在子类的构造器中进行。

    public class Parent {
        public Parent() {
            // 父类的构造代码
        }
    }
    
    public class Child extends Parent {
        public Child() {
            super(); // 调用父类的构造器
            // 子类的构造代码
        }
    }
    
  3. 在重写的方法中调用父类的方法
    使用 super 可以在子类重写的方法中调用父类被重写的方法。

    public class Parent {
        public void show() {
            System.out.println("Parent's show()");
        }
    }
    
    public class Child extends Parent {
        @Override
        public void show() {
            super.show(); // 在重写的方法中调用父类的方法
            System.out.println("Child's show()");
        }
    }
    
  4. 区分成员变量和参数
    当方法参数与类的成员变量同名时,super 可以用来引用父类的成员变量。

    public class Parent {
        protected int value;
    }
    
    public class Child extends Parent {
        public Child(int value) {
            super.value = value; // 将传入的参数赋值给父类的成员变量
        }
    }
    
  5. 静态上下文中使用
    在静态方法中,super 关键字不能用来引用非静态成员,因为静态方法不属于类的实例。但是,它可以用于调用父类的静态方法。

    public class Parent {
        public static void staticMethod() {
            System.out.println("Parent's static method");
        }
    }
    
    public class Child extends Parent {
        public static void staticMethod() {
            super.staticMethod(); // 调用父类的静态方法
        }
    }
    

super 关键字是Java中实现继承和多态的重要工具,它允许子类访问和调用父类的成员,即使在子类中存在同名的成员或方法。正确使用 super 可以提高代码的可读性和维护性。

final关键字


final 关键字的作用:

  1. 创建不可继承的类

    • 将类声明为 final 可以阻止其他类继承它。这通常用于工具类或者那些不应该被继承的类。
    public final class UtilityClass {
        // 工具类方法
    }
    
  2. 定义不可重写的方法

    • 将方法声明为 final 可以防止子类重写该方法。这有助于保护方法的核心行为不被改变。
    public final void criticalMethod() {
        // 重要的方法实现
    }
    
  3. 声明常量

    • 将变量声明为 final 并结合 static 关键字,可以创建类级别的常量。这些常量的值在初始化后不能被改变。
    public static final int MAX_VALUE = 100;
    
  4. 保证变量不可变

    • 对于实例变量,声明为 final 意味着它们只能在对象构造时被初始化一次,之后不能被重新赋值。这有助于创建不可变对象。
    public final int instanceVariable;
    
  5. 优化性能

    • final 关键字可以作为编译器优化的提示,因为 final 变量的值是固定的,编译器可以在某些情况下进行更有效的优化。
  6. 限制匿名内部类对外部局部变量的访问

    • 在匿名内部类中,只有被声明为 final 或不可变的局部变量才能被访问。
    final int value = 10;
    new Thread(() -> {
        System.out.println(value);
    }).start();
    
  7. Lambda表达式中的变量捕获

    • 在Lambda表达式中,如果外部的局部变量被声明为 final 或事实上不可修改(effectively final),则它可以在Lambda表达式中被捕获。
    final int value = 10;
    Runnable runnable = () -> System.out.println(value);
    
  8. 限制方法的覆盖

    • 当一个方法被声明为 final 时,它不能被子类覆盖,这可以用来实现特定的方法行为不被改变。

使用 final 关键字有助于明确代码的意图,提高代码的可读性和可维护性,同时也为编译器提供了优化的机会。

posted @ 2024-08-01 11:31  墨澜  阅读(8)  评论(0编辑  收藏  举报