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

继承是指在原有类的基础上,进行功能扩展,创建新的类型。

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

继承的格式:

  • 格式: public class 子类名 extends 父类名{}
  • 例如: public class Zi extends Fu {}
  • Fu:是父类,也被称为基类、超类
  • Zi: 是子类,也被称为派生类

继承中子类的特点:

子类可以有父类的内容,子类还可以有自己特有的内容。

// 父类
class Animal {
    String name;

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

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

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

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

    // 重写eat方法
    void eat() {
        System.out.println(name + " is eating dog food.");
    }

    // 添加新方法
    void bark() {
        System.out.println(name + " says bark!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal("Generic Animal");
        Dog dog = new Dog("Buddy");

        animal.eat();
        dog.eat();
        dog.bark();
    }
}

//父类
public class Father {
public void doSome1(){
System.out.println("父类doSome1");
}
public void doSome2(){
System.out.println("父类doSome2");
}
}
//子类
public class Son extends Father{
@Override
public void doSome1() {
System.out.println("子类重写doSome1");
}
public void doOther(){
System.out.println("子类doOther");
}
}
//测试类
public class TestExtend {
public static void main(String[] args) {
Son son = new Son();
son.doSome1();//子类重写
son.doSome2();//继承自父类
son.doOther();//子类扩展
}
}
运行结果
子类重写父类doSome1
父类doSome2
子类doOther

Java中的Object类是所有Java类的根类,位于类继承层次结构的顶端。这意味着Java中的每个类都隐式地继承了Object类,因此Object类中定义的方法可以在任何Java对象上使用。以下是Object类的一些常用方法:

  1. equals(Object obj)

    • 用于比较两个对象的等价性。默认实现比较的是对象的内存地址,但通常需要重写这个方法来提供实际内容的比较逻辑。
  2. hashCode()

    • 返回对象的哈希码值,通常用于哈希表中。如果重写了equals()方法,也应该重写hashCode()方法,以维护equals()hashCode()的一致性。
  3. toString()

    • 返回对象的字符串表示。默认实现返回对象的类名和哈希码的无符号十六进制表示。通常重写这个方法以提供更有意义的信息。
  4. getClass():反射

    • 返回对象的Class对象,表示对象的运行时类。
  5. wait()wait(long timeout):多线程

    • 导致当前线程等待,直到另一个线程调用该对象的notify()notifyAll()方法,或者超过指定的超时时间。
  6. notify():多线程

    • 唤醒在此对象监视器上等待的单个线程。
  7. notifyAll()

    • 唤醒在此对象监视器上等待的所有线程。
  8. clone()

    • 创建并返回对象的一个副本。这个方法在Object类中是受保护的,并且需要在子类中进行重写并声明为public,以实现对象的克隆。
    9.finalize():
    • 释放GC无法回收的资源

下面是一个简单的例子,演示了如何重写Object类的一些方法:

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类的其他方法...
}

public class Main {
    public static void main(String[] args) {
        Person person1 = new Person("Alice", 30);
        Person person2 = new Person("Alice", 30);

        System.out.println("person1 == person2: " + person1.equals(person2)); // 输出 true
        System.out.println("person1 toString: " + person1.toString()); // 输出 Person 的字符串表示
    }
}
在这个例子中,Person类重写了equals()、hashCode()和toString()方法,以提供基于Person对象的实际内容的比较和字符串表示。

在子类中,创建了一个与父类中名称、返回值类型、参数列表都完全相同的方法,只是方法体的功能实现不同,。这是多态性的一个重要体现。当父类中的方法无法满足子类的需求,或者子类需要有特殊功能时,就可以进行方法重写。

方法重写规则

● 父类的成员方法只能被它的子类重写,即不能继承一个方法,就不能重写这个方法;

● 被final修饰的方法不能被重写;

● 被static修饰的方法不能被重写,但可以再次声明;

● 构造方法不能被重写;

● 子类和父类在同一个包中时,子类可以重写父类中除了被private和final修饰的其他所有方法;

● 子类和父类不在同一个包中时,子类只能重写父类被public和protected修饰的非final方法;

● 重写的方法建议使用@Override注解来标识。

方法签名要相同:重写的方法和被重写的方法,在方法名、参数上都要相同;

● 返回值类型一致:JDK 1.5之前重写方法的返回值类型必须一样,但之后的Java版本放宽了限制,返回值类型必须小于或等于父类方法的返回值类型;

● 访问修饰符要更宽泛:子类重写父类的方法时,子类方法中的访问修饰符不能比父类中的更严格(public>protected>default>private)。比如父类方法的修饰符是protected,则子类的同名方法其修饰符可以是protected或public,但不能是默认的或private;

● 声明的异常类型要一致:重写的方法一定不能抛出新的检査异常,或者比被重写方法声明更宽泛的检査型异常。例如,父类的方法声明了IOException,重写该方法时就不能抛出Exception,只能拋出IOException或其子类异常。但可以抛出非检査异常

学习网址Java方法重写(Override)与方法重载(Overlode)的区别,方法重写的详细介绍_快速java中怎么重写已有的方法-CSDN博客

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

方法重写:在子类中,创建了一个与父类中名称、返回值类型、参数列表都完全相同的方法,只是方法体的功能实现不同

方法重载:允许在同一个类中存在多个同名的方法,只要它们的参数列表不同即可。方法重载有助于提供更清晰和灵活的接口。

方法重写(Method Overriding)和方法重载(Method Overloading)是Java中两种不同的概念,它们在面向对象编程中都非常重要,但用途和规则不同。下面是它们之间的主要区别:

方法重写(Method Overriding)

  1. 目的:改变父类方法的行为。
  2. 继承性:只能在子类中进行。
  3. 方法签名:必须与被重写的方法完全一致(相同的方法名、参数列表和返回类型)。
  4. 访问控制:不能比父类中的方法访问级别更严格。
  5. 返回类型:可以与父类中的方法相同,或为协变返回类型(即返回类型是父类返回类型的子类)。
  6. 异常:可以抛出与父类方法相同的异常或其子集,但不能抛出新的检查型异常或更广泛的异常集合。
  7. 使用场景:实现多态性,允许子类根据需要提供特定于其自己的行为。

方法重载(Method Overloading)

  1. 目的:在一个类中定义多个同名方法,但参数不同。
  2. 继承性:可以在同一个类中或继承体系中进行。
  3. 方法签名:必须不同,可以通过参数的数量、类型或顺序来区分。
  4. 访问控制:可以定义不同访问级别的重载方法。
  5. 返回类型:可以相同也可以不同,但这不影响方法重载的判断。
  6. 异常:可以抛出不同类型的异常。
  7. 使用场景:提供相同功能但接受不同参数的方法,增加代码的灵活性和可读性。
    super 关键字的用法和 this 关键字用法相似QQ图片20240801112537
  • this:代表本类对象的引用(this关键字指向调用该方法的对象一般我们是在当前类中使用this关键字所以我们常说this代表本类对象的引用)
  • super:代表父类存储空间的标识(可以理解为父类对象引用)指向当前对象的直接父类实例。使用 super 可以访问父类中定义的属性、方法和构造函数

用途:

  1. 访问父类构造函数:在子类的构造函数中,可以使用 super() 调用父类的构造函数,以确保父类的初始化逻辑被执行。

    javaclass Parent {
        public Parent() {
            // 初始化代码
        }
    }
    
    class Child extends Parent {
        public Child() {
            super(); // 调用父类的构造函数
            // 子类自己的初始化代码
        }
    }
    
  2. 访问父类方法:当子类重写了父类的方法,但仍然需要调用父类中的方法实现时,可以使用 super.methodName()

    javaclass Parent {
        void show() {
            System.out.println("Parent show()");
        }
    }
    
    class Child extends Parent {
        @Override
        void show() {
            super.show(); // 调用父类的方法
            System.out.println("Child show()");
        }
    }
    
  3. 访问父类属性:如果子类需要访问父类中定义的属性,可以使用 super

    javaclass Parent {
        int value = 10;
    }
    
    class Child extends Parent {
        void printValue() {
            System.out.println(super.value); // 访问父类的属性
        }
    }
    
  4. 调用父类方法的另一个版本:当父类中有多态方法时,使用 super 可以调用父类中定义的版本,而不是子类重写后的版本。

  5. 在静态上下文中不能使用:由于 super 引用的是当前对象的实例,它不能在静态方法中使用,因为静态方法不与任何特定实例关联。

  6. 在接口的实现中使用:如果一个类实现了多个接口,且这些接口中定义了同名方法,可以使用 super 来调用另一个接口中的方法。

    javainterface A {
        void doSomething();
    }
    
    interface B {
        void doSomething();
    }
    
    class Implementor implements A, B {
        public void doSomething() {
            A.super.doSomething(); // 调用接口 A 中的方法
            B.super.doSomething(); // 调用接口 B 中的方法
        }
    }
    
  7. superclass 和 subclass 关系super 关键字总是指向直接父类,即使存在更高层次的父类。

使用 super 关键字可以保持代码的清晰和正确性,尤其是在涉及继承和多态性的场景中。

final是 最终 的意思。
在java这门编程语言中,final是一个关键字,它可以被用来修饰类,变量以及成员方法。final 关键字可以用于多个上下文,具有不同的含义,但核心概念是表示不可改变性。
被final修饰的变量,又叫被称为 自定义常量。

在java中,final关键字有三个用法,修饰类(不能继承),修饰方法(不能被子类重写),以及修饰成员方法(常量不能修改)。

final修饰变量(成员变量和局部变量)
变量分为成员变量和局部变量(成员变量和局部变量介绍传送门),他们被final修饰时有不同的注意事项。

(1) final修饰成员变量:该成员变量必须在其所在类对象创建之前被初始化(且只能被初始化一次)。

这句话的意思是: 被final修饰的成员变量,一定要被赋值且只能被赋值一次,且必须是在这个成员变量所在的类对象创建之前被赋值。

final修饰局部变量,在定义时该变量可以不被直接初始化,但是在使用该变量之前,该变量必须完成初始化,否则报错!

用法

  1. 最终变量:当一个变量被声明为final,这意味着它的值在初始化之后不能被再次修改。

    java
    final int CONSTANT = 10; // 常量
    
  2. 最终方法:当一个方法被声明为final,这意味着子类不能重写(Override)这个方法。

    javaclass Parent {
        final void show() {
            System.out.println("I can't be overridden");
        }
    }
    // Child class cannot override show() method
    
  3. 最终类:当一个类被声明为final,这意味着这个类不能被继承。没有其他类可以成为这个类的子类。

    javafinal class UtilityClass {
        // class implementation
    }
    // Cannot create a subclass of UtilityClass
    
  4. 匿名内部类使用final成员:在匿名内部类中,如果需要使用外部类的局部变量,该变量必须被声明为final,即使实际上没有打算修改它。

    javafinal int value = 5;
    new Thread(new Runnable() {
        public void run() {
            System.out.println(value);
        }
    }).start();
    
  5. 不可变对象:使用final关键字可以帮助创建不可变对象。不可变对象一旦创建,其状态就不能被改变,这有助于保证线程安全性和简化对象的同步。

  6. 重写final方法的限制:如果父类中的方法被声明为final,则不能被子类重写。

  7. 性能优化final方法可以被编译器优化,因为它们不能被子类覆盖。这有时可以提高执行效率。

  8. 接口中的默认方法:在Java 8及以后的版本中,接口可以包含默认方法。如果一个方法在接口中是default的,那么在实现该接口的类中可以将其声明为final,以防止被子类覆盖。

    javainterface MyInterface {
        default void myMethod() {
            // default implementation
        }
    }
    
    class MyClass implements MyInterface {
        @Override
        final void myMethod() {
            // specific implementation
        }
    }
    

使用final关键字可以增强代码的清晰度和健壮性,同时在某些情况下还可以提供性能优势。

总结:
final修饰成员变量,该变量必须在 其所在类对象 创建之前完成初始化且只能被初始化一次(我的理解:对象被创建,说明要使用这个对象,所以身为这个对象属性之一的成员变量就必须要被赋值)
final修饰局部变量,该变量在定义时可以不被初始化,但是使用之前,必须完成初始化且只能初始化一次!

总而言之一句话:
final修饰的成员变量在定义时必须初始化(三种方法),final修饰的局部变量定义时可以不被初始化,但是使用之前必须完成初始化!