继承
继承
继承的定义
继承 是面向对象编程中的一个核心概念,它允许我们定义一个类(称为子类或派生类)来继承另一个类(称为父类或基类)的属性和方法。这样,子类就可以复用父类的代码,同时还可以在此基础上添加新的属性和方法或者覆盖(重写)父类的某些方法。
注:子类访问父类
创建一个子类对象的时候,会默认先创建一个父类 ,无论是通过无参构造还是有参构造来创建子类对象,都是通过无参构造来创建父类对象的
(注:在Java中,当创建一个子类对象时,确实会首先创建一个父类对象,这是因为子类继承了父类的属性和方法。以下是详细解释:
-
继承关系:子类继承了父类的所有属性(字段)和方法(除了构造方法和私有成员)。这意味着子类对象包含了父类的所有成员。
-
初始化顺序:在Java中,构造方法的执行顺序是先执行父类的构造方法,然后再执行子类的构造方法。这是因为子类需要父类的属性和方法来正确初始化。
-
构造方法调用:
- 隐式调用:如果子类的构造方法中没有显式调用父类的构造方法(使用
super()
),编译器会自动插入对父类无参构造方法的调用(super()
)。如果父类没有无参构造方法,且子类构造方法没有显式调用父类的有参构造方法,编译将会失败。 - 显式调用:子类可以通过
super()
语法显式调用父类的有参构造方法,这允许子类在创建时向父类传递初始化参数。
- 隐式调用:如果子类的构造方法中没有显式调用父类的构造方法(使用
-
对象创建过程:
- 内存分配:首先为父类分配内存,初始化父类的属性。
- 父类构造:调用父类的构造方法,完成父类部分的初始化。
- 子类构造:然后为子类分配内存,调用子类的构造方法,完成子类部分的初始化。
-
为什么要创建父类对象:
- 功能复用:子类可以复用父类的功能,这是面向对象编程中代码复用的一种体现。
- 多态性:子类可以覆盖父类的方法,实现多态性。这意味着同一个方法调用可以根据对象的实际类型有不同的行为。
- 维护性:通过继承,可以在不修改现有代码的情况下扩展功能,提高代码的可维护性。
-
构造方法的作用:构造方法的主要作用是初始化对象的状态。在子类对象的创建过程中,首先需要确保父类的状态被正确初始化,然后子类可以在此基础上添加或修改状态。
总结来说,创建父类对象是确保子类能够继承并正确使用父类属性和方法的必要步骤。这是面向对象编程中继承特性的基本要求。)
在Java中创建一个子类对象时,默认情况下会先调用父类的无参构造函数来初始化父类部分的状态。这是因为子类构造函数默认包含了对父类无参构造函数的调用。如果父类没有无参构造函数,或者你需要调用父类的某个特定构造函数,那么你必须显式地使用super
关键字来调用父类的构造函数 。
下面是一个简单的示例代码,用来展示这个过程:
// 父类
class Parent {
protected String name;
// 无参构造函数
public Parent() {
System.out.println("调用父构造函数时没有参数");
}
// 有参构造函数
public Parent(String name) {
System.out.println("用一个参数调用父构造函数");
this.name = name;
}
}
// 子类
class Child extends Parent {
private String nickname;
// 无参构造函数
public Child() {
// 显式调用父类无参构造函数
super();
System.out.println("调用子构造函数时没有参数");
}
// 有参构造函数
public Child(String name, String nickname) {
// 显式调用父类有参构造函数
super(name);
System.out.println("使用两个参数调用子构造函数");
this.nickname = nickname;
}
// 输出信息
public void displayInfo() {
System.out.println("Name: " + name + ", Nickname: " + nickname);
}
}
public class Main {
public static void main(String[] args) {
// 创建子类对象,使用无参构造函数
Child child1 = new Child();
child1.displayInfo();
// 创建子类对象,使用有参构造函数
Child child2 = new Child("Alice", "Al");
child2.displayInfo();
}
}
这段代码定义了两个类:Parent
和 Child
,其中 Child
继承自 Parent
。
Parent
类:
- 包含一个受保护的成员变量
name
,这意味着它可以在子类中被访问。 - 有两个构造函数:一个无参构造函数和一个有参构造函数。无参构造函数打印一条消息,有参构造函数打印一条消息并将传入的参数赋值给
name
。
Child
类:
- 继承自
Parent
。 - 包含一个私有成员变量
nickname
。 - 同样有两个构造函数:一个无参构造函数和一个有参构造函数。无参构造函数显式调用父类的无参构造函数,并打印一条消息。有参构造函数显式调用父类的有参构造函数,并打印一条消息,同时将传入的参数赋值给
nickname
。 - 包含一个
displayInfo
方法,用于打印name
和nickname
的值。
Main
类:
- 包含
main
方法,这是程序的入口点。 - 在
main
方法中,创建了两个Child
类的对象:child1
使用无参构造函数创建,因此它将调用Parent
的无参构造函数,然后调用Child
的无参构造函数。child2
使用有参构造函数创建,传入了name
和nickname
的值,因此它将调用Parent
的有参构造函数,然后调用Child
的有参构造函数。
- 然后调用每个
Child
对象的displayInfo
方法来打印它们的信息。
输出:
当运行这段代码时,输出将如下所示:
调用父构造函数时没有参数
调用子构造函数时没有参数
Name: null, Nickname: null
用一个参数调用父构造函数
使用两个参数调用子构造函数
Name: Alice, Nickname: Al
解释:
- 创建
child1
时,首先调用Parent
的无参构造函数,然后调用Child
的无参构造函数。由于child1
没有提供name
的值,所以name
为null
。 - 创建
child2
时,首先调用Parent
的有参构造函数,传入 "Alice" 作为name
的值,然后调用Child
的有参构造函数,传入 "Al" 作为nickname
的值。
注意:在 Child
类中,name
是从 Parent
类继承来的,因此它是受保护的,可以在 Child
类中直接访问。在 displayInfo
方法中,可以直接使用 name
来访问从父类继承的 name
成员变量。
这里可以看出,即使是使用了有参构造函数来创建Child
对象,父类的构造函数也是先被调用的。因此,创建子类对象时确实会先创建父类对象。如果父类没有无参构造函数,那么你必须在子类构造函数中显式地调用父类的某个构造函数,否则编译会失败 。
注:父类访问子类
在Java中,父类直接调用子类的方法有一些限制。通常,父类的对象不能直接调用子类特有的方法,除非通过某种方式获得了对子类对象的引用。但是,通过多态性,父类类型的引用可以指向子类的对象,并且可以通过这样的引用来调用子类的方法,前提是这些方法在父类中已经被声明,或者子类重写了父类的方法。
通过父类引用调用子类方法
在Java中,父类引用指向子类对象是一种常见的方式来实现父类调用子类方法的场景。例如:
abstract class Parent {
public abstract void overriddenMethod();
}
class Child extends Parent {
@Override
public void overriddenMethod() {
System.out.println("这是 Child 中重写的方法");
}
public void childMethod() {
System.out.println("这是 Child 特有的方法");
}
}
public class Main {
public static void main(String[] args) {
Parent parentReference = new Child(); // 父类引用指向子类对象
parentReference.overriddenMethod(); // 调用子类重写的父类方法
// 为了调用子类特有的方法,需要向下转型
if (parentReference instanceof Child) {
((Child)parentReference).childMethod();
}
}
}
这段代码展示了Java中的抽象类和方法重写的概念,以及如何使用父类的引用来调用子类的方法。
Parent
类:
- 是一个抽象类,包含一个抽象方法
overriddenMethod()
。抽象类不能被实例化,它通常作为基类供其他类继承。
Child
类:
- 继承自
Parent
类。 - 实现了父类中的抽象方法
overriddenMethod()
,并打印一条消息。 - 包含一个子类特有的方法
childMethod()
,打印一条消息。
Main
类:
- 包含
main
方法,这是程序的入口点。 - 在
main
方法中,创建了一个Child
类的对象,并将其引用赋值给Parent
类型的引用变量parentReference
。这是多态的一个例子,子类对象可以被视为父类类型。 - 调用
parentReference
的overriddenMethod()
方法,这将调用Child
类中重写的方法。 - 为了调用
Child
类特有的方法childMethod()
,需要先检查parentReference
是否实际上是Child
类的实例。这是通过instanceof
操作符完成的。如果检查通过,那么将parentReference
向下转型为Child
类型,并调用childMethod()
方法。
输出:
当运行这段代码时,输出将如下所示:
这是 Child 中重写的方法
这是 Child 特有的方法
解释:
- 创建
Child
类的对象,并将其赋值给Parent
类型的引用parentReference
。 - 调用
parentReference.overriddenMethod()
,由于多态,这将调用Child
类中重写的方法。 - 使用
instanceof
检查parentReference
是否是Child
类的实例。如果是,向下转型为Child
类型,并调用childMethod()
方法。
注意:向下转型是将父类引用转换为子类类型的过程。在向下转型之前,必须确保引用实际上是子类的实例,否则可能会抛出 ClassCastException
。在这段代码中,通过 instanceof
操作符确保了安全的向下转型。
使用抽象方法
如果父类中定义了抽象方法,子类可以重写这些方法,并且父类可以通过这些抽象方法来间接调用子类的方法实现。例如:
abstract class Parent {
public abstract void overriddenMethod();
public void callOverriddenMethod() {
System.out.println("从 Parent 调用重写的方法");
this.overriddenMethod();
}
}
class Child extends Parent {
@Override
public void overriddenMethod() {
System.out.println("这是 Child 中重写的方法");
}
}
public class Main {
public static void main(String[] args) {
Parent parentReference = new Child();
parentReference.callOverriddenMethod(); // 调用父类中的抽象方法,实际上执行的是子类中的方法实现
}
}
这段代码展示了Java中的抽象类和方法重写的概念,以及如何使用父类的引用来调用子类的方法。
Parent
类:
- 是一个抽象类,包含一个抽象方法
overriddenMethod()
。抽象类不能被实例化,它通常作为基类供其他类继承。
Child
类:
- 继承自
Parent
类。 - 实现了父类中的抽象方法
overriddenMethod()
,并打印一条消息。 - 包含一个子类特有的方法
childMethod()
,打印一条消息。
Main
类:
- 包含
main
方法,这是程序的入口点。 - 在
main
方法中,创建了一个Child
类的对象,并将其引用赋值给Parent
类型的引用变量parentReference
。这是多态的一个例子,子类对象可以被视为父类类型。 - 调用
parentReference
的overriddenMethod()
方法,这将调用Child
类中重写的方法。 - 为了调用
Child
类特有的方法childMethod()
,需要先检查parentReference
是否实际上是Child
类的实例。这是通过instanceof
操作符完成的。如果检查通过,那么将parentReference
向下转型为Child
类型,并调用childMethod()
方法。
输出:
当运行这段代码时,输出将如下所示:
从 Parent 调用重写的方法
这是 Child 中重写的方法
解释:
- 创建
Child
类的对象,并将其赋值给Parent
类型的引用parentReference
。 - 调用
parentReference.overriddenMethod()
,由于多态,这将调用Child
类中重写的方法。 - 使用
instanceof
检查parentReference
是否是Child
类的实例。如果是,向下转型为Child
类型,并调用childMethod()
方法。
注意:向下转型是将父类引用转换为子类类型的过程。在向下转型之前,必须确保引用实际上是子类的实例,否则可能会抛出 ClassCastException
。在这段代码中,通过 instanceof
操作符确保了安全的向下转型。
使用反射
另外,也可以使用Java的反射API来调用子类的方法,但这通常用于非常特殊的情况,并且需要谨慎使用,因为它可能会破坏封装性:
class Parent {
public void callChildMethod(Object obj) throws Exception {
Method method = obj.getClass().getMethod("childMethod", null);
method.invoke(obj, null);
}
}
class Child extends Parent {
public void childMethod() {
System.out.println("This is a method specific to Child.");
}
}
public class Main {
public static void main(String[] args) throws Exception {
Child child = new Child();
new Parent().callChildMethod(child);
}
}
这段代码展示了Java反射(Reflection)API的使用。反射是一种强大的机制,允许程序在运行时访问和操作类、接口、字段和方法。
Parent
类:
- 包含一个方法
callChildMethod
,它接受一个Object
类型的参数。 - 在这个方法中,使用反射API来动态获取并调用传入对象的
childMethod
方法。
Child
类:
- 继承自
Parent
类。 - 包含一个
childMethod
方法,打印一条消息。
Main
类:
- 包含
main
方法,这是程序的入口点。 - 在
main
方法中,创建了一个Child
类的对象。 - 创建一个
Parent
类的实例,并调用它的callChildMethod
方法,传入Child
类的对象。
输出:
当运行这段代码时,输出将如下所示:
This is a method specific to Child.
解释:
- 创建
Child
类的对象child
。 - 创建
Parent
类的实例。 - 调用
Parent
实例的callChildMethod
方法,传入Child
对象child
。 - 在
callChildMethod
方法中,使用反射API来获取Child
对象的childMethod
方法:obj.getClass().getMethod("childMethod", null)
获取childMethod
方法的Method
对象。这里,obj
是Child
类的实例,"childMethod"
是方法名,null
表示没有参数。method.invoke(obj, null)
调用childMethod
方法,obj
是Child
对象,null
表示没有参数。
childMethod
被调用,打印 "This is a method specific to Child."。
注意:
- 反射可以绕过Java的访问控制,因此需要谨慎使用。
- 使用反射可能会影响性能,因为它涉及到动态解析。
- 在调用方法时,如果方法有参数,需要在
getMethod
和invoke
方法中相应地指定参数类型和参数值。 - 在这个例子中,
getMethod
方法的第一个参数是方法名,第二个参数是一个Class
类型的数组,表示方法的参数类型。由于childMethod
没有参数,所以这里使用null
。 invoke
方法的第一个参数是要调用方法的对象,第二个参数是一个Object
类型的数组,表示方法的参数值。由于childMethod
没有参数,所以这里使用null
。
总结来说,虽然父类本身无法直接调用子类的方法,但可以通过多态性的特性,使用父类类型的引用指向子类对象,从而调用子类的方法。此外,还可以通过抽象方法、反射等方式来实现父类对子类方法的调用。
继承的优点
- 代码复用:通过继承,子类可以复用父类的代码,避免了代码的重复编写,提高了开发效率。
- 扩展性:子类可以在父类的基础上增加新的属性和方法,实现功能的扩展。
- 多态性基础:继承是实现多态性的基础,通过继承可以实现接口的多态性(运行时多态性)。
- 单继承或多继承:大部分现代编程语言(如Java)支持单继承,即一个类只能继承一个父类;而有些语言(如C++)支持多继承,即一个类可以继承多个父类。
示例:
public class Animal {
public void eat() {
System.out.println("Animal eats");
}
}
public class Dog extends Animal {
public void bark() {
System.out.println("Dog barks");
}
}
在这个例子中,Dog
类继承了 Animal
类,并添加了新的方法 bark()
。Dog
类也可以覆写 eat()
方法来改变其行为。
继承的缺点
- 耦合度增加:子类与父类之间高度耦合,如果父类发生较大的变化,子类也需要进行相应的调整。
- 破坏封装性:如果子类继承了一些不必要的属性或方法,可能会破坏封装性,因为子类对外暴露了更多细节。
- 层次结构复杂:如果继承层次过深,会导致结构复杂,难以理解和维护。
🔍子类可以只继承父类的一部分属性吗
在面向对象编程中,子类通过继承机制继承父类的所有属性和方法。但是,子类可以选择性地使用这些属性和方法,或者通过添加新的方法和属性来扩展或修改继承的行为。以下是一些关键点:
-
继承所有属性和方法:
- 当一个子类继承一个父类时,它自动继承了父类的所有公有(public)和受保护(protected)属性和方法。私有(private)成员不能被直接访问,但可以通过父类提供的方法进行访问。
-
覆盖(Override):
- 子类可以覆盖父类的方法,提供自己的实现。这允许子类改变继承的方法的行为。
- 子类也可以覆盖父类的属性,通过提供新的getter和setter方法。
-
隐藏:
- 在某些语言中,子类可以隐藏父类的属性和方法,使其在子类中不可访问。这通常是通过在子类中声明同名的属性或方法来实现的。
-
访问控制:
- 子类可以通过访问控制修饰符(如private, protected, public)来限制对继承属性和方法的访问。
-
使用组合:
- 除了继承,子类还可以通过组合(Composition)来包含父类的实例。这允许子类选择性地使用父类的部分功能,同时保持更高的灵活性和可维护性。
-
抽象类和接口:
- 子类可以继承抽象类或实现接口,这允许子类只实现所需的部分功能。抽象类可以定义一些抽象方法,子类必须提供这些方法的具体实现。
-
设计模式:
- 在某些设计模式中,如策略模式、装饰者模式等,子类可以选择性地使用父类的部分功能,或者通过添加新的功能来扩展父类。
总之,虽然子类继承了父类的所有属性和方法,但它们可以通过覆盖、隐藏、访问控制、组合、抽象类和接口等机制来选择性地使用这些属性和方法。这提供了灵活性,允许子类根据需要定制和扩展继承的行为。
🔍 如何选择性地覆盖父类的方法?
在面向对象编程中,选择性地覆盖父类的方法是一种常见的做法,它允许子类改变或扩展从父类继承的方法的行为。以下是如何进行选择性覆盖的步骤和注意事项:
-
理解继承的方法:
- 首先,你需要了解父类中哪些方法是你想要覆盖的。通常,这些方法是公有(public)或受保护(protected)的,因为它们可以在子类中被访问。
-
确定覆盖的目的:
- 明确你为什么要覆盖这个方法。是为了改变行为、增加额外的功能,还是为了修复一个错误。
-
使用正确的访问修饰符:
- 确保子类中覆盖的方法具有与父类中相同或更宽松的访问修饰符。例如,如果父类的方法是
public
,那么子类中覆盖的方法也应该是public
或protected
。
- 确保子类中覆盖的方法具有与父类中相同或更宽松的访问修饰符。例如,如果父类的方法是
-
保持方法签名一致:
- 子类中覆盖的方法必须具有与父类中被覆盖的方法相同的方法签名,包括方法名、参数列表和返回类型。
-
使用
override
关键字(如果适用):- 在某些编程语言中,如Java和C#,你需要使用
override
关键字来明确指出你正在覆盖一个父类的方法。这有助于编译器检查覆盖是否正确。
- 在某些编程语言中,如Java和C#,你需要使用
-
调用父类的方法:
- 如果你需要在子类的方法中调用父类中被覆盖的方法,可以使用
super
关键字(在Java和C#中)或父类的方法调用语法(在其他语言中)。
- 如果你需要在子类的方法中调用父类中被覆盖的方法,可以使用
-
考虑多态性:
- 覆盖方法时,要考虑多态性的影响。确保子类的方法在调用时能够表现出正确的行为,即使它们是通过父类的引用调用的。
-
测试覆盖的方法:
- 覆盖方法后,确保对子类进行测试,以验证覆盖的方法是否按预期工作,并且没有破坏父类的行为。
-
文档和注释:
- 在子类的方法上添加适当的文档和注释,说明为什么覆盖父类的方法,以及它的行为与父类的方法有何不同。
-
考虑设计原则:
- 在覆盖方法时,考虑设计原则,如开闭原则(对扩展开放,对修改封闭)和里氏替换原则(子类对象应该能够替换掉所有使用父类对象的地方而不影响程序的正确性)。
以下是一个简单的Java示例,展示了如何覆盖父类的方法:
class Animal {
public void makeSound() {
System.out.println("Some sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Bark");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
myDog.makeSound(); // 输出 "Bark"
}
}
在这个例子中,Dog
类覆盖了Animal
类的makeSound
方法,以提供特定的实现。当通过Animal
类型的引用调用makeSound
方法时,会执行Dog
类中覆盖的方法。
🔍问题:当通过Animal
类型的引用调用makeSound
方法时,myDog的类型引用为什么是Animal来调用makeSound方法
在Java中,当你使用父类的类型引用来创建一个子类的对象时,这种行为称为向上转型(upcasting)。向上转型是隐式的,也就是说,你不需要显式地进行转换,Java编译器会自动处理这种类型转换。
在你提供的代码示例中:
class Animal {
public void makeSound() {
System.out.println("Some sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Bark");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog(); // 向上转型
myDog.makeSound(); // 输出 "Bark"
}
}
这里,myDog
被声明为Animal
类型,但实际上它指向了一个Dog
对象。当你调用myDog.makeSound()
时,会发生以下步骤:
-
动态绑定(Dynamic Binding):Java使用动态绑定来决定调用哪个方法。这意味着在运行时,Java虚拟机(JVM)会检查对象的实际类型(而不是引用的声明类型),并调用该类型中的方法。
-
多态:由于
Dog
类覆盖了Animal
类的makeSound
方法,所以这里展示了多态性。多态允许你使用父类的引用来调用子类的方法。 -
方法调用:尽管
myDog
的声明类型是Animal
,但由于它实际上指向一个Dog
对象,JVM在运行时会调用Dog
类中的makeSound
方法。这就是为什么输出是"Bark"而不是"Some sound"。
使用父类类型的引用来调用子类对象的方法有几个好处:
- 代码的通用性:你可以编写通用的代码,这些代码可以与父类及其所有子类的实例一起工作。
- 灵活性:你可以在不修改现有代码的情况下引入新的子类。
- 解耦:父类和子类之间的耦合度较低,因为父类不需要知道子类的具体实现。
这种设计模式是面向对象编程中多态和继承的核心概念之一,它使得代码更加灵活和可扩展。
🔍问题扩展:为什么要使用父类的引用来调用子类的方法
使用父类的引用来调用子类的方法是实现多态的一种常见方式,这是面向对象编程(OOP)的一个核心特性。下面详细解释为什么这样做是有益的:
实现多态
多态性允许使用父类的引用变量来引用子类的对象,这样可以通过统一的父类接口来处理不同子类的对象。当一个父类引用变量指向一个子类对象时,可以通过这个父类引用变量调用子类中重写的方法。这种方式可以提高代码的灵活性、可扩展性和可维护性 。
提高代码的灵活性
通过父类引用调用子类的方法,可以让程序更加灵活地处理不同类型的对象。例如,在不知道对象的确切类型时,可以使用相同的代码处理所有类型的对象。这在处理多种类型的对象列表时非常有用,因为你只需处理一个接口,而不需要知道列表中对象的具体类型 。
支持代码的扩展性
多态性使得代码更具有可扩展性。通过继承和重写父类的方法,子类可以在父类的基础上进行功能的扩展,添加新的属性和方法,从而实现了代码的扩展性 。
便于维护
使用父类引用指向子类对象的方式可以减少代码的耦合度,使得代码更容易维护。如果需要增加新的子类,只需要确保新子类实现了必要的接口或继承了必要的父类,并重写相应的方法即可,而无需改动使用这些对象的现有代码 。
示例
假设有如下结构:
abstract class Animal {
public abstract void speak();
}
class Dog extends Animal {
@Override
public void speak() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
@Override
public void speak() {
System.out.println("Meow!");
}
}
这里 Animal
是一个抽象类,Dog
和 Cat
分别是它的子类。尽管我们不知道具体的子类类型,但我们知道所有的 Animal
都可以 speak()
。因此,可以编写如下代码:
public class Main {
public static void main(String[] args) {
List<Animal> animals = new ArrayList<>();
animals.add(new Dog());
animals.add(new Cat());
for (Animal animal : animals) {
animal.speak(); // 通过父类引用调用子类方法
}
}
}
输出:
Woof!
Meow!
在这个例子中,我们通过 Animal
类型的引用调用了 Dog
和 Cat
子类中的 speak()
方法。这就是多态的一个典型应用,它允许我们以一致的方式处理不同的子类对象,而不需要知道具体是哪一个子类的对象 。
总结
使用父类引用调用子类的方法提供了灵活性、可扩展性和可维护性,是实现多态的关键手段。这种方式使得我们能够写出更灵活、更具适应性的代码,尤其是在处理不确定类型对象的情况下。
Object类
Object
类是 Java 中所有类的根类,所有类都直接或间接地继承自 Object
类。Object
类提供了许多对所有对象都适用的方法,包括但不限于:
toString()
:返回对象的字符串表示。equals(Object obj)
:判断两个对象是否相等。hashCode()
:返回对象的哈希码值。clone()
:创建并返回对象的副本。getClass()
:返回对象的运行时类。finalize()
:当垃圾回收器决定回收某对象时,由对象的finalize()
方法负责执行清理操作(已被弃用,不建议使用)。wait()
,notify()
,notifyAll()
:用于线程间的通信。
super关键字
super 关键字用于引用当前对象的直接父类对象(实例变量和方法)。
-
用法:
- 访问父类的属性:
super.属性名
- 调用父类的方法:
super.方法名(参数列表)
- 调用父类的构造器:
super(参数列表)
(注意,它必须是子类构造器的第一条语句)
- 访问父类的属性:
-
调用父类的构造方法:在子类的构造方法中,可以使用
super
来调用父类的构造方法。例如:public class Parent { public Parent() { // 父类构造方法 } } public class Child extends Parent { public Child() { super(); // 调用父类构造方法 } }
-
访问父类的成员变量:当子类和父类有同名成员变量时,可以使用
super
来访问父类的成员变量。例如:public class Parent { private int value = 10; } public class Child extends Parent { private int value = 20; public void displayValues() { System.out.println("Parent's value: " + super.value); // 访问父类的value System.out.println("Child's value: " + this.value); // 访问子类的value } }
-
调用父类的方法:当子类重写了父类的方法时,可以使用
super
来调用父类中被重写的方法。例如:public class Parent { public void method() { System.out.println("Parent's method"); } } public class Child extends Parent { @Override public void method() { super.method(); // 调用父类的method() System.out.println("Child's method"); } }
注:在子类的构造方法中,可以通过super访问父类的构造方法和普通方法
在子类的普通方法中,只能通过super访问父类的普通方法
final关键字
final 关键字在 Java 中有多个用途:
- 修饰类:表示该类不能被继承。
- 修饰方法:表示该方法不能被重写(但可以被重载)。
- 修饰变量:表示该变量的值一旦被初始化后就不能被改变(对于基本数据类型是变量值不变,对于引用数据类型是引用不可变,但对象本身可以变)。
- 用法:
- 修饰类:
final class ClassName {...}
- 修饰方法:
final void methodName() {...}
- 修饰变量:
final int varName = 10;
或final ClassName objName = new ClassName();
(但objName
指向的对象内容可以改变,只是objName
不能再指向其他对象)
- 修饰类:
继承和接口的区别
在编程中,继承和接口是两种不同的面向对象编程(OOP)概念,它们用于实现代码的重用和模块化。以下是它们的主要区别:
-
继承(Inheritance):
- 定义:继承是一种机制,允许一个类(称为子类或派生类)继承另一个类(称为基类或父类)的属性和方法。
- 目的:继承的主要目的是代码重用和实现类的层次结构。子类可以扩展或修改继承的属性和方法。
- 实现方式:子类通过继承父类,可以访问父类的公有属性和方法,也可以添加新的属性和方法。
- 类型:继承是实现多态的一种方式,它允许使用父类的引用来引用子类的对象。
- 访问控制:子类可以访问父类的公有(public)和受保护(protected)成员,但不能访问私有(private)成员。
-
接口(Interface):
- 定义:接口是一种完全抽象的概念,它定义了一组方法规范,但不实现这些方法。任何实现该接口的类必须提供这些方法的具体实现。
- 目的:接口的主要目的是定义规范和确保不同类之间的一致性。它允许不同的类以统一的方式被使用。
- 实现方式:类通过实现接口,承诺将提供接口中定义的所有方法的具体实现。
- 类型:接口是实现多态的另一种方式,它允许不同的类以统一的接口被使用。
- 访问控制:接口中的方法默认都是公有的,它不包含属性,只有方法签名。
-
多继承与单一实现:
- 继承:在某些语言中,一个类可以继承多个父类(多继承),这可能导致复杂性和继承层次结构的混乱。
- 接口:一个类可以实现多个接口,这提供了一种方式来解决多继承可能带来的问题,因为它只定义了方法的规范,不涉及实现。
-
语言支持:
- 并非所有的编程语言都支持这两种机制。例如,Java 支持继承和接口,而 Swift 支持继承和协议(类似于接口)。
-
设计哲学:
- 继承倾向于表示“是一个(is-a)”关系,例如“狗是动物”。
- 接口倾向于表示“能做(can-do)”关系,例如“任何可以飞的东西都应该实现飞的接口”。
在实际编程中,选择使用继承还是接口通常取决于设计需求和编程语言的特性。有时候,为了更好的代码组织和灵活性,推荐使用组合(Composition)和接口而不是继承。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)