继承是面向对象的三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,追加属性和方法。
继承是指在原有类的基础上,进行功能扩展,创建新的类型。
- 继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。
- 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
类的一些常用方法:
-
equals(Object obj)
:- 用于比较两个对象的等价性。默认实现比较的是对象的内存地址,但通常需要重写这个方法来提供实际内容的比较逻辑。
-
hashCode()
:- 返回对象的哈希码值,通常用于哈希表中。如果重写了
equals()
方法,也应该重写hashCode()
方法,以维护equals()
和hashCode()
的一致性。
- 返回对象的哈希码值,通常用于哈希表中。如果重写了
-
toString()
:- 返回对象的字符串表示。默认实现返回对象的类名和哈希码的无符号十六进制表示。通常重写这个方法以提供更有意义的信息。
-
getClass()
:反射- 返回对象的
Class
对象,表示对象的运行时类。
- 返回对象的
-
wait()
和wait(long timeout)
:多线程- 导致当前线程等待,直到另一个线程调用该对象的
notify()
或notifyAll()
方法,或者超过指定的超时时间。
- 导致当前线程等待,直到另一个线程调用该对象的
-
notify()
:多线程- 唤醒在此对象监视器上等待的单个线程。
-
notifyAll()
:- 唤醒在此对象监视器上等待的所有线程。
-
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)
- 目的:改变父类方法的行为。
- 继承性:只能在子类中进行。
- 方法签名:必须与被重写的方法完全一致(相同的方法名、参数列表和返回类型)。
- 访问控制:不能比父类中的方法访问级别更严格。
- 返回类型:可以与父类中的方法相同,或为协变返回类型(即返回类型是父类返回类型的子类)。
- 异常:可以抛出与父类方法相同的异常或其子集,但不能抛出新的检查型异常或更广泛的异常集合。
- 使用场景:实现多态性,允许子类根据需要提供特定于其自己的行为。
方法重载(Method Overloading)
- 目的:在一个类中定义多个同名方法,但参数不同。
- 继承性:可以在同一个类中或继承体系中进行。
- 方法签名:必须不同,可以通过参数的数量、类型或顺序来区分。
- 访问控制:可以定义不同访问级别的重载方法。
- 返回类型:可以相同也可以不同,但这不影响方法重载的判断。
- 异常:可以抛出不同类型的异常。
- 使用场景:提供相同功能但接受不同参数的方法,增加代码的灵活性和可读性。
super 关键字的用法和 this 关键字用法相似
- this:代表本类对象的引用(this关键字指向调用该方法的对象一般我们是在当前类中使用this关键字所以我们常说this代表本类对象的引用)
- super:代表父类存储空间的标识(可以理解为父类对象引用)指向当前对象的直接父类实例。使用
super
可以访问父类中定义的属性、方法和构造函数
用途:
-
访问父类构造函数:在子类的构造函数中,可以使用
super()
调用父类的构造函数,以确保父类的初始化逻辑被执行。javaclass Parent { public Parent() { // 初始化代码 } } class Child extends Parent { public Child() { super(); // 调用父类的构造函数 // 子类自己的初始化代码 } }
-
访问父类方法:当子类重写了父类的方法,但仍然需要调用父类中的方法实现时,可以使用
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()"); } }
-
访问父类属性:如果子类需要访问父类中定义的属性,可以使用
super
。javaclass Parent { int value = 10; } class Child extends Parent { void printValue() { System.out.println(super.value); // 访问父类的属性 } }
-
调用父类方法的另一个版本:当父类中有多态方法时,使用
super
可以调用父类中定义的版本,而不是子类重写后的版本。 -
在静态上下文中不能使用:由于
super
引用的是当前对象的实例,它不能在静态方法中使用,因为静态方法不与任何特定实例关联。 -
在接口的实现中使用:如果一个类实现了多个接口,且这些接口中定义了同名方法,可以使用
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 中的方法 } }
-
superclass 和 subclass 关系:
super
关键字总是指向直接父类,即使存在更高层次的父类。
使用 super
关键字可以保持代码的清晰和正确性,尤其是在涉及继承和多态性的场景中。
final是 最终 的意思。
在java这门编程语言中,final是一个关键字,它可以被用来修饰类,变量以及成员方法。final
关键字可以用于多个上下文,具有不同的含义,但核心概念是表示不可改变性。
被final修饰的变量,又叫被称为 自定义常量。
在java中,final关键字有三个用法,修饰类(不能继承),修饰方法(不能被子类重写),以及修饰成员方法(常量不能修改)。
final修饰变量(成员变量和局部变量)
变量分为成员变量和局部变量(成员变量和局部变量介绍传送门),他们被final修饰时有不同的注意事项。
(1) final修饰成员变量:该成员变量必须在其所在类对象创建之前被初始化(且只能被初始化一次)。
这句话的意思是: 被final修饰的成员变量,一定要被赋值且只能被赋值一次,且必须是在这个成员变量所在的类对象创建之前被赋值。
final修饰局部变量,在定义时该变量可以不被直接初始化,但是在使用该变量之前,该变量必须完成初始化,否则报错!
用法
-
最终变量:当一个变量被声明为
final
,这意味着它的值在初始化之后不能被再次修改。java final int CONSTANT = 10; // 常量
-
最终方法:当一个方法被声明为
final
,这意味着子类不能重写(Override)这个方法。javaclass Parent { final void show() { System.out.println("I can't be overridden"); } } // Child class cannot override show() method
-
最终类:当一个类被声明为
final
,这意味着这个类不能被继承。没有其他类可以成为这个类的子类。javafinal class UtilityClass { // class implementation } // Cannot create a subclass of UtilityClass
-
匿名内部类使用
final
成员:在匿名内部类中,如果需要使用外部类的局部变量,该变量必须被声明为final
,即使实际上没有打算修改它。javafinal int value = 5; new Thread(new Runnable() { public void run() { System.out.println(value); } }).start();
-
不可变对象:使用
final
关键字可以帮助创建不可变对象。不可变对象一旦创建,其状态就不能被改变,这有助于保证线程安全性和简化对象的同步。 -
重写final方法的限制:如果父类中的方法被声明为
final
,则不能被子类重写。 -
性能优化:
final
方法可以被编译器优化,因为它们不能被子类覆盖。这有时可以提高执行效率。 -
接口中的默认方法:在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修饰的局部变量定义时可以不被初始化,但是使用之前必须完成初始化!