Java继承是面向对象编程中的一个重要概念,它允许一个类(称为子类或派生类)从另一个类(称为父类或基类)继承属性和行为。通过继承,子类可以重用父类已有的代码,并且可以在此基础上添加新的功能。【子类是父类的一种】
Java继承
什么是继承
在Java中,使用关键字extends
实现继承。子类声明时使用extends
关键字后跟父类的名称。子类会继承父类的非私有成员变量和方法,包括字段、构造函数和方法。
下面是一个简单的例子来说明Java继承的基本概念:
class Animal { // 父类
protected String name;
public void eat() {
System.out.println("Animal is eating.");
}
}
class Dog extends Animal { // 子类
public void bark() {
System.out.println("Dog is barking.");
}
}
在上面的例子中,Animal
类是一个基类,其中定义了一个name
字段和一个eat()
方法。Dog
类是一个派生类,它通过extends
关键字继承了Animal
类的属性和行为,并新增了一个bark()
方法。
我们可以创建Dog
类的实例并调用其继承自Animal
类的方法和字段:
Dog dog = new Dog();
dog.name = "Fido";
dog.eat(); // 调用继承自Animal类的eat()方法
dog.bark(); // 调用Dog类的bark()方法
在这个例子中,我们能够使用dog.name
访问继承自Animal
类的name
字段,并使用dog.eat()
调用继承自Animal
类的eat()
方法。同时,我们还可以使用dog.bark()
调用Dog
类新增的bark()
方法。
需要注意的是,Java中的继承是单继承的,一个子类只能继承一个父类。但是,Java支持多层继承,即一个类可以直接继承另一个类,而后者又可以继承其他类,形成继承层级。
此外,Java还提供了接口(interface
)来实现接口的继承,允许类通过实现接口来继承一组抽象方法。与类的继承不同,一个类可以实现多个接口,从而获得更大的灵活性和重用性。
总结一下,Java继承是面向对象编程的一个关键概念,它允许子类从父类继承属性和行为。通过继承,我们可以重用已有代码并扩展功能。使用关键字extends
来声明一个子类继承自父类,并通过实例化子类来访问继承的字段和方法。
格式
在Java中,实现继承的语法格式如下:
class 父类 {
// 父类的属性和方法
}
class 子类 extends 父类 {
// 子类的属性和方法
}
其中:
父类
是已经存在的一个类,称为基类或超类。子类
是要创建的新类,称为派生类或子类。extends
关键字用于表示子类继承自父类。
子类通过关键字extends
后跟父类的名称来实现继承。继承意味着子类继承了父类的非私有属性和方法,并且可以在子类中添加新的属性和方法,或者重写父类的方法。
Java继承是面向对象编程的重要概念,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。继承在Java中具有以下好处和特点:
Java只支持单继承,不支持多继承,但支持多层继承
Java是一种只支持单继承的编程语言,这意味着一个类只能直接继承自一个父类。这样的设计是为了避免多继承带来的复杂性和潜在的冲突问题。
然而,Java支持多层继承,这意味着一个类可以继承自另一个类,而后者可以再次继承自另一个类,形成一个继承层次结构。这种继承关系可以一直延续,形成一个继承链。
例如,假设我们有类A和类B,类B继承自类A,然后再有类C继承自类B。这样,类C就间接继承了类A的属性和方法。这是Java中常见的继承形式:
class A {
// A's properties and methods
}
class B extends A {
// B's properties and methods
}
class C extends B {
// C's properties and methods
}
这种多层继承的设计使得代码的复用更加灵活,同时保持了单继承带来的简洁和易于管理。
为什么java不能多继承
1.命名冲突:如果两个父类中有相同名称的方法或属性,子类在继承时将不知道应该选择哪个父类的方法或属性。这种冲突称为"菱形继承"(钻石继承)问题,可能会导致代码混乱和难以维护。
什么是菱形和钻石继承
:菱形继承和钻石继承是多继承模型下的两种继承问题,它们可以在某些编程语言中出现,尤其是支持多继承的语言。虽然Java不支持多继承,但其他一些编程语言(如C++)可能会面临这些问题。
- 菱形继承(Diamond Inheritance):
菱形继承发生在一个类继承自两个间接共同父类的情况下。简单来说,如果有一个类A,两个类B和C都直接继承自A,然后又有一个类D继承自B和C,这就形成了一个菱形继承结构。因为B和C都继承自A,所以D将间接继承A的成员,导致A中的成员在D中存在两份,可能导致重复定义和冗余。
A
/ \
B C
\ /
D
- 钻石继承(Diamond Inheritance):
钻石继承是菱形继承的一个特例,它发生在一个类继承自两个直接共同父类的情况下。简单来说,如果有一个类A,两个类B和C都直接继承自A,然后又有一个类D同时继承自B和C,这就形成了一个钻石继承结构。因为B和C都继承自A,而D又同时继承自B和C,所以D将直接继承自A的成员两次,可能导致方法调用的二义性和冲突。
A
/ \
B C
\ /
D
在支持多继承的语言中,如果没有正确处理菱形继承和钻石继承,可能会导致编程中的困扰和错误。因此,一些语言通过采用接口(Interface)的概念来替代多继承,以避免这些问题。接口只定义方法签名,而不包含实现,类可以实现多个接口,从而达到类似多继承的效果,但避免了冲突和二义性。
2.复杂性增加:多继承会增加代码的复杂性,因为子类可能继承来自多个父类的不同行为,而不是简单地继承一个父类的行为。
3.破坏封装:多继承可能会破坏类的封装性,因为一个子类可以访问多个父类的实现细节,这可能会导致代码耦合性增加。
虽然Java本身不支持多继承,但它通过接口(Interface)提供了一种类似多继承的机制。类可以实现多个接口,从而获得多个接口中定义的方法,这样可以在一定程度上弥补了单继承的限制。使用接口的方式还可以避免菱形继承问题,因为接口只定义方法的签名而不包含实现。
// 定义接口
interface Interface1 {
void method1();
}
interface Interface2 {
void method2();
}
// 实现接口
class MyClass implements Interface1, Interface2 {
@Override
public void method1() {
// 实现method1的具体逻辑
}
@Override
public void method2() {
// 实现method2的具体逻辑
}
}
当谈论继承的好处时,我们通常会提及以下几个方面:
-
代码重用:
继承是面向对象编程的一个重要概念,它允许子类继承父类已经定义的属性和方法。这样一来,子类就可以直接使用父类的功能,无需重复编写相同的代码。通过继承,子类可以在不破坏原有代码的情况下,拥有父类的所有功能,并且可以根据需要进行扩展和定制。代码重用大大提高了开发效率,减少了代码冗余,同时也有利于代码的维护和升级。 -
扩展性:
继承使得代码更加灵活和可扩展。子类可以通过添加新的属性和方法来扩展父类的功能,以满足特定需求。这种扩展是在原有代码基础上的增量改进,不会影响已经存在的代码。例如,假设有一个通用的图形类,子类可以通过继承它来创建圆形、正方形、三角形等特定类型的图形,并且可以为每种图形增加特定的属性和方法,而不需要重新编写通用的图形操作代码。 -
统一性:
继承可以帮助提高代码的统一性。当多个类拥有共同的属性和方法时,可以将这些共同部分抽象到一个父类中,而子类只需要专注于实现各自特定的功能。通过这种方式,代码更加清晰、简洁,易于理解和维护。继承可以实现代码的模块化,让代码结构更加合理和组织有序。
尽管继承在一定程度上提供了代码重用、扩展性和统一性的好处,但也应该注意过度使用继承可能导致继承层次复杂,使得代码理解和调试变得困难。因此,在设计中,应该权衡使用继承的场景,避免滥用,而是选择合适的抽象和设计模式来优化代码结构。
特点:
-
单一继承:
Java中的类只支持单一继承,即一个子类只能继承一个父类。这种设计是为了避免多继承可能带来的复杂性和冲突。如果一个类可以同时继承多个父类,当这些父类中有同名方法或属性时,编译器就无法确定应该使用哪一个,可能导致歧义。为了保持代码的清晰性和简洁性,Java选择了只支持单一继承的模型。 -
多层继承:
尽管Java只支持单一继承,但它支持多层继承,也称为层次继承。这意味着一个类可以继承另一个类,而后者又可以继续继承另一个类,形成一个继承的层级结构。这样的继承链可以一直延伸下去,从而实现代码的复用和扩展。多层继承的例子可以是类A继承自类B,类B继承自类C,依次类推,形成一个继承链。
A (subclass)
|
B (subclass, superclass)
|
C (superclass)
- 访问控制:
子类继承了父类的属性和方法后,可以根据父类成员的访问修饰符来决定自己对这些成员的访问权限。在Java中,有四种访问修饰符:
- public: 公共访问修饰符,被声明为public的成员可以被任何类访问。
- protected: 受保护访问修饰符,被声明为protected的成员可以被子类访问,以及同一包中的其他类访问。
- default(默认): 如果没有明确指定访问修饰符,成员会具有默认访问权限,可以被同一包中的其他类访问。
- private: 私有访问修饰符,被声明为private的成员只能被本类的方法访问,其他类无法直接访问。
继承后,子类可以访问从父类继承的所有非私有成员(即public、protected和默认访问权限的成员),但无法直接访问父类的私有成员。子类可以通过继承和访问控制机制,实现对父类成员的复用和定制。
继承后的子类的特点:子类可以直接使用且可以添加新的功能
-
子类继承了父类的非私有属性和方法,可以直接使用这些属性和方法,无需重新实现。
-
子类可以通过方法的重写(覆盖)来改变从父类继承的方法的行为。如果子类定义了与父类相同名称和参数列表的方法,那么子类对象在调用该方法时将执行子类的方法而非父类的方法。
-
子类可以拥有自己的独特属性和方法,以满足其特定的需求。
-
如果子类的构造方法没有显式调用父类的构造方法,默认情况下,Java编译器会自动在子类构造方法中插入对父类无参构造方法的调用(前提是父类有无参构造方法)。如果父类没有无参构造方法,子类必须在构造方法中显式调用父类的其他构造方法。
过度的继承可能导致类之间的紧密耦合,降低代码的可维护性和可读性。因此,在实践中,应优先考虑组合(Composition)等其他设计模式,以更好地实现代码的灵活性和可维护性。
【1】每一个类都直接或者间接的继承于object:
在 Java 中,每一个类都直接或间接地继承自 java.lang.Object
,这是 Java 语言的基本设计原则。即使你在定义类时没有显式地指定一个父类,Java 会自动将其继承自 Object
类。
java.lang.Object
是 Java 类继承层次结构中的根类(Root Class),它位于类继承层次结构的最顶层。这意味着所有的 Java 类都可以调用 Object
类中的方法,因为这些方法在所有类中都是可用的。
Object
类中包含了一些常用的方法,如 toString()
, equals()
, hashCode()
, getClass()
等等。这些方法对于所有的 Java 对象都是有效的,因为所有类都是 Object
的子类。
示例代码:
class MyClass {
private int value;
public MyClass(int value) {
this.value = value;
}
// MyClass类继承自Object,即使没有显式声明
// 以下的方法在所有的类中都可用
@Override
public String toString() {
return "MyClass[value=" + value + "]";
}
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass(42);
// 使用Object类的toString方法
System.out.println(obj.toString()); // 输出:MyClass[value=42]
}
}
在上面的例子中,我们定义了一个名为 MyClass
的类,并创建了一个对象 obj
。尽管我们没有显式声明 MyClass
继承自 Object
类,但它会自动继承 Object
类,因此可以在 MyClass
中使用 Object
类的方法,如 toString()
。
总结:
每一个类在 Java 中都直接或间接地继承自 java.lang.Object
类。这使得 Object
类的方法对所有的 Java 对象都是有效的,同时也使得 Java 语言的继承层次结构保持一致性。
【2】单一继承:Java中的类只支持单一继承,一个子类只能继承一个父类。这意味着一个类不能同时继承多个父类。
可以分支多个,但是父类智能有一个
【3】多层继承:继承层次结构应该合理设计,避免出现过于复杂的继承关系 ----> 子类 A 继承父类 B ,父类B 可以 继承父类 C:
在 Java 中,类的继承可以分为两种类型:直接继承和间接继承。
当一个类在Java中继承另一个类,它获得了被继承类的属性和方法,这种继承方式称为子类继承父类。在Java中,一个类可以有一个直接父类,但可以通过继承链间接继承其他类的特性,从而形成更加复杂的继承体系。
1. 直接继承(Direct Inheritance):
直接继承是指一个类明确地通过 extends
关键字继承另一个类。当一个类直接继承另一个类时,它就可以访问父类中的所有非私有成员(属性和方法),这些成员可以是 public
、protected
或默认(package-private)访问修饰符。直接继承允许子类重用父类的代码,并且可以在子类中添加新的特性或重写父类的方法。
示例代码:
class Animal {
void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
void bark() {
System.out.println("狗在汪汪叫");
}
}
在上面的例子中,Dog
类直接继承自 Animal
类,它是 Animal
类的子类。
2. 间接继承(Indirect Inheritance):
间接继承是指一个类通过继承链间接地继承其他类的属性和方法。由于Java只支持单一继承,一个类只能有一个直接父类,但可以通过间接继承从多个类中获得特性。这种继承体系形成了继承层次结构,其中顶层类是 Object
类,它是所有类的根类。所有类在继承链上都是 Object
类的子类。
例如,考虑以下类继承关系:
Object
|
+-- Animal
|
+-- Mammal
|
+-- Dog
- `Animal` 类是 `Object` 类的子类。它可以直接访问 `Object` 类中的公共方法(如 `toString()`、`equals()` 等)。
- `Mammal` 类是 `Animal` 类的子类。因此,它不仅继承了 `Object` 类的方法,还继承了 `Animal` 类中的方法。
- `Dog` 类是 `Mammal` 类的子类。因此,它继承了 `Object`、`Animal` 和 `Mammal` 类中的方法。
- 通过这种继承层次结构,`Dog` 类间接地继承了 `Object` 类中定义的方法,而无需在 `Dog` 类中显式声明继承自 `Object`。
示例代码:
class Animal {
void makeSound() {
System.out.println("动物发出声音");
}
}
class Mammal extends Animal {
// Mammal类间接继承了Animal类的makeSound方法
}
class Dog extends Mammal {
void bark() {
System.out.println("狗在汪汪叫");
}
}
在上面的例子中,Mammal
类间接继承自 Animal
类,而 Dog
类又直接继承自 Mammal
类。因此,Dog
类通过继承链间接继承了 Animal
类的 makeSound()
方法。
总结:
- 直接继承是指一个类直接继承自另一个类,使用
extends
关键字实现。 - 间接继承是指一个类通过继承链继承了其他类的属性和方法,通过多层次的继承实现。
- Java中支持单一继承,一个类只能有一个直接父类,但可以通过间接继承获得更多的特性。
其他说明
继承的优点:
- 代码重用: 子类可以继承父类的属性和方法,从而避免在子类中重复编写相同的代码,提高代码的复用性。
- 扩展性: 子类可以在继承父类的基础上添加新的特性或行为,使得程序可以轻松扩展和修改。
- 层次性: 继承形成了类的层次结构,使得代码更加组织化和易于理解。
Java不支持多继承、但支持多层继承:由于Java只允许单一继承,因此一个类不能同时继承多个直接父类。然而,Java支持多层继承,即通过继承链间接继承其他类的特性。子类可以继承父类的父类,从而在继承链上形成多层继承体系。
继承的注意事项:
- 单一继承: Java只支持单一继承,即一个类只能有一个直接父类。这是为了避免多继承可能带来的复杂性和冲突。 一个类在Java中只能继承一个直接父类,这是Java语言设计的单继承原则。因此,每个类只能有一个直接父类,这有助于保持代码的简洁性和继承关系的清晰。
- 继承层次: 继承层次结构应该合理设计,避免出现过于复杂的继承关系,保持继承链的清晰和简洁。
- 访问控制: 子类可以访问父类中的
public
和protected
成员,但不能访问父类中的private
成员。 - Java中所有的类都直接或者间接继承于Object类:在Java中,如果一个类没有显式地指定一个直接父类,那么它将默认继承自java.lang.Object类。因此,所有的Java类都可以通过继承链直接或间接地访问Object类中定义的方法(如toString()、equals()等)。
子类继承了什么:类的成员:构造函数、成员变量、成员方法。
在面向对象编程中,子类继承了父类的成员,包括构造函数、成员变量(也称为属性或字段)、成员方法(也称为函数或方法)。继承是面向对象编程的一个重要特性,它允许子类从父类继承已有的属性和行为,使得代码可以更加灵活、重用和易于维护。
具体来说,当一个子类继承一个父类时,子类将获得父类中定义的所有非私有(private)的成员。以下是继承过程中子类可以继承的三种主要成员:
-
构造函数(Constructor):
构造函数是用于初始化对象的特殊方法。当创建一个对象时,构造函数会被调用来初始化对象的状态。子类继承了父类的构造函数,这意味着子类可以使用父类的构造函数来创建对象并进行初始化操作。通常在子类的构造函数中,可以调用父类的构造函数以确保父类中定义的初始化逻辑也能得到执行。 -
成员变量(属性或字段):
成员变量是类中用于存储数据的变量,它们代表对象的状态。子类继承了父类的成员变量,这意味着子类可以直接访问并使用父类中定义的成员变量,无需重新声明。 -
成员方法(函数或方法):
成员方法是类中定义的用于执行操作的函数。子类继承了父类的成员方法,这意味着子类可以直接调用父类中定义的方法,无需重新定义。子类还可以重写(Override)父类的方法,即在子类中重新定义一个和父类方法签名相同的方法,从而改变或扩展父类方法的行为。
需要注意的是,继承并不会继承父类的私有成员(private members),私有成员只能在父类内部访问,子类无法直接访问它们。但子类可以通过父类提供的公共方法来间接地访问和操作私有成员。
构造方法是否能被继承下来
1. 构造函数:
没有继承关系直接说明时,就自动加载object字节码文件
- 私有构造函数:子类无法继承父类的私有构造函数,因为私有构造函数只能在父类内部访问,==子类无法直接调用或继承。==这意味着子类不能通过继承来复用父类的私有构造函数。
- 非私有构造函数:子类也不能继承父类的非私有构造函数。尽管子类会默认调用父类的无参构造函数(如果没有显式调用其他构造方法),但子类并不真正继承父类的构造方法定义。子类的构造方法只是在执行时调用父类的构造方法来初始化父类的成员变量,子类自己并没有得到这些构造方法的实际定义。
为什么不管构造方法用什么修饰,子类都无法继承—>违背了类名= =构造方法名= =文件名,需要自己复写一遍
构造函数在Java中是用来创建对象的特殊方法。它们具有以下特点:
- 构造函数的名称必须与类名完全相同,包括大小写。
- 构造函数没有返回类型,连void也没有。
- 构造函数不能被直接调用,而是在创建对象时由"new"关键字自动调用。
- 构造函数用于初始化对象的状态,包括设置成员变量的初始值和执行其他必要的初始化操作。
由于构造函数用于创建对象并进行初始化,它们的主要目的是创建新的实例,并确保对象的正确初始化。因此,构造函数不具有继承性。
当我们创建一个子类时,编译器会自动为子类生成一个默认的构造函数,该构造函数调用父类的无参构造函数(如果有的话)来初始化父类的部分。这样,父类的构造函数在创建子类对象时会被间接调用,但并不是真正的继承。
然而,如果父类定义了带参数的构造函数,并且没有提供无参构造函数,那么子类必须显式调用父类的带参数的构造函数来初始化父类的部分。这时候,我们使用"super"关键字来在子类的构造函数中调用父类的构造函数。
总结:
构造函数没有继承性,因为它们是用于对象初始化的特殊方法。子类会默认调用父类的无参构造函数(如果有的话),但这并不是继承构造函数本身,而是子类对象初始化时通过间接调用父类构造函数实现的。如果需要在子类中进行初始化,必须显式调用父类的构造函数,通常通过使用"super"关键字来实现。
代码举例:
public class Test {
public static void main(String[] args) {
ChildClass child1 = new ChildClass();
ChildClass child2 = (ChildClass) new ParentClass("John Doe", 30);
ParentClass parent3 = new ParentClass("Jane Smith", 25);
ChildClass child4 = (ChildClass) new ChildClass("Alice Johnson", 28);
}
}
class ParentClass {
String name;
int age;
public ParentClass() {
}
public ParentClass(String name, int age) {
this.name = name;
this.age = age;
}
}
class ChildClass extends ParentClass {
public ChildClass(String name, int age) {
super(name, age);
}
public ChildClass() {
super();
}
}
逐行解释
Paragraph 1:
// 利用空参构造创建子类对象,Java 编译器会为子类生成一个默认的空参数构造方法。
ChildClass child1 = new ChildClass();
这段说明了使用默认构造函数(没有参数的空构造函数)来创建子类对象(ChildClass)。它提到如果没有显式定义构造函数,Java 编译器会自动生成一个默认的构造函数供子类使用。
Paragraph 2:
// 利用带参构造创建子类对象
// 使用父类的带参构造函数来创建子类对象child2,这是一个不推荐的写法,虽然合法但会导致对象类型和实际初始化方式不一致。
ChildClass child2 = (ChildClass) new ParentClass("John Doe", 30);
这段讨论了使用带参数构造函数来创建子类对象(ChildClass)。它指出,在这种情况下,使用父类的构造函数(带有参数的ParentClass构造函数)来创建ChildClass对象(child2)。虽然这是一种有效的方法,但不推荐使用,因为它导致对象的类型(ChildClass)与实际的初始化方式(使用父类的构造函数)不一致。
Paragraph 3:
ParentClass parent3 = new ParentClass("Jane Smith", 25);
// 使用父类的带参构造函数来创建父类对象parent3。
// 创建一个 ParentClass 类的对象 parent3,通过调用带参数构造函数 ParentClass(String, int) 来完成初始化。
这段描述了使用带参数构造函数创建父类对象(parent3)。它简单地解释了通过使用构造函数ParentClass(String, int)
并传入参数"Jane Smith"和25来创建一个ParentClass对象。
Paragraph 4:
ChildClass child4 = (ChildClass) new ChildClass("Alice Johnson", 28);
// 使用子类的带参构造函数来创建子类对象child4,这是推荐的方式。
// 创建一个 ChildClass 类的对象 child4,通过调用子类的带参数构造函数 ChildClass(String, int) 来完成初始化。
// 直接使用子类构造函数来创建子类对象是符合面向对象设计原则的。
这段解释了使用子类的带参数构造函数来创建子类对象(child4)的推荐方式。强调直接使用子类构造函数来创建子类对象符合面向对象设计原则。构造函数ChildClass(String, int)
被用来初始化对象,并传入参数"Alice Johnson"和28。
Paragraph 5:
class ParentClass {
String name;
int age;
// 父类的空参构造函数
public ParentClass() {
}
// 父类的带参构造函数
public ParentClass(String name, int age) {
this.name = name;
this.age = age;
}
}
class ChildClass extends ParentClass {
// 子类的带参构造函数
public ChildClass(String name, int age) {
// 这里可以添加子类特有的初始化逻辑
super(name, age); // 调用父类的带参构造函数来完成初始化
}
// 子类的空参构造函数
public ChildClass() {
// 这里可以添加子类特有的初始化逻辑
super(); // 调用父类的空参构造函数来完成初始化
}
// 可以在这里添加子类的其他代码。
}
2. 成员变量:都可以继承,但得通过get、set方法获取
- 私有成员变量:==子类无法直接继承父类的私有成员变量,因为私有成员变量只能在父类内部访问。==子类只能通过父类提供的公有/受保护方法间接访问这些私有成员变量,或者在子类中重新声明相同名称的成员变量来实现类似的功能。
- 非私有成员变量:子类可以继承父类的非私有成员变量,这意味着子类将拥有与父类相同名称和类型的成员变量。子类可以直接访问继承的成员变量,并根据需要进行修改或重写。
[1]非私有的变量继承情况:自己继承到的martialArt,和父类无关,这是一个新的类
package work0729;
public class TTest {
public static void main(String[] args) {
Child c = new Child();
c.favouriteGame = "堡垒之夜"; // 将变量名更改为 'favouriteGame',其值改为 "堡垒之夜"
System.out.println(c);
c.martialArt = "功夫熊猫"; // 将变量名更改为 'martialArt',其值改为 "功夫熊猫"
System.out.println(c.favouriteGame + " " + c.martialArt);
}
}
class Child extends People {
String favouriteGame; // 将变量名更改为 'favouriteGame'
}
class People {
String martialArt; // 将变量名更改为 'martialArt'
}
尽管在 Child
类中没有显式地定义 martialArt
变量,但它仍然可以访问 martialArt
变量,因为它继承了父类 People
的所有实例变量和成员(非私有的)。
在Java中,如果子类没有显式地定义构造函数,它将隐式继承父类的无参构造函数(默认构造函数),前提是父类有一个无参构造函数可供继承。在这种情况下,父类 people
有一个无参构造函数,因此子类 Child
会继承这个无参构造函数。
子类 Child
中有两个构造函数:一个是无参构造函数(默认构造函数),另一个是带参数的构造函数。在带参数的构造函数中,您可以设置子类自己的实例变量 favourite
的值。而在默认构造函数中,子类将隐式调用父类的无参构造函数。
在 TTest
类中,您可以创建 Child
类的对象并调用其构造函数,而不需要显式提供 martialArt
变量的值。这是因为 Child
类继承了 people
类的无参构造函数,该构造函数可以初始化 martialArt
变量,因此在 Child
对象创建时,martialArt
变量会被赋予默认值(例如null,对于对象类型的变量)。
下面是相关代码:
package work0729;
public class TTest {
public static void main(String[] args) {
Child c1 = new Child(); // 使用无参构造函数
c1.favouritegame = "堡垒之夜";
System.out.println(c1.favourite + " " + c1.martialArt); // martialArt 变量会有默认值
Child c2 = new Child("英雄联盟"); // 使用带参数的构造函数
System.out.println(c2.favouritegame + " " + c2.martialArt); // martialArt 变量会有默认值
}
}
class Child extends People {
String favouritegame;
public Child() {
// 隐式调用父类 People 的无参构造函数
}
public Child(String favouritegame) {
this.favouritegame = favouritegame;
}
}
class People {
String martialArt;
}
请注意,尽管在 Child
类中没有显式地定义 martialArt
变量,但它仍然可以访问 martialArt
变量,因为它继承了父类 People
的所有实例变量和成员(非私有的)。
[2] 私有的如何间接访问:下面的get和set方法以及扩展getset的使用方法
package work0729;
public class People {
private String martialArt;
public People() {
}
public People(String martialArt) {
this.martialArt = martialArt;
}
public String getMartialArt() {
return this.martialArt;
}
public void setMartialArt(String martialArt) {
this.martialArt = martialArt;
}
}
public class Child extends People {
String favoriteGame;
String specialSkill = getMartialArt();
public Child() {
}
public Child(String favoriteGame, String specialSkill) {
this.favoriteGame = favoriteGame;
this.specialSkill = specialSkill;
}
public String getFavoriteGame() {
return this.favoriteGame;
}
public void setFavoriteGame(String favoriteGame) {
this.favoriteGame = favoriteGame;
}
public String getSpecialSkill() {
return this.specialSkill;
}
public void setSpecialSkill(String specialSkill) {
this.specialSkill = specialSkill;
}
}
public class TTest {
public static void main(String[] args) {
Child child = new Child("Chess", "Kung Fu");
child.setMartialArt("Tai Chi");
System.out.println(child.favoriteGame + " " + child.getMartialArt());
}
}
在这段代码中,涉及了Java中的面向对象编程中的get和set方法,以及扩展了get和set方法的使用。
-
get
方法:
get
方法用于获取类中的成员变量的值。在这里,getmartialArt()
是people
类中的一个get方法,用于获取martialArt
成员变量的值。 -
set
方法:
set
方法用于设置类中的成员变量的值。在这里,setmartialArt(String martialArt)
是people
类中的一个set方法,用于设置martialArt
成员变量的值。 -
扩展get和set方法的使用:
在Child
类中,除了继承了people
类的getmartialArt()
方法外,它还定义了自己的getGame()
和getSs()
方法以及setGame(String game)
和setSs(String ss)
方法。这些方法分别用于获取和设置favoriteGame
和ss
成员变量的值。
在TTest
类的main
方法中,通过创建Child
类的实例c
并传入favoriteGame
和ss
的值,然后调用c.setmartialArt("fsdfdsf")
方法设置martialArt
的值,并通过c.favoriteGame()
和c.getmartialArt()
分别获取favoriteGame
和martialArt
的值并输出到控制台。
请注意代码中的一个问题:
在Child
类的构造函数中,已经有一个对ss
的初始化语句String ss = getmartialArt();
,然后又通过另一个构造函数public Child(String favoriteGame, String ss)
设置了ss
的值,这样会导致构造函数的初始化被覆盖。应该选择使用一个方式来初始化ss
,例如在构造函数中统一设置,而不是混合使用。
3. 成员方法:
- 私有成员方法:==子类无法直接继承父类的私有成员方法,因为私有成员方法只能在父类内部调用。==子类只能通过父类提供的公有/受保护方法间接调用这些私有成员方法,或者在子类中定义相同名称和参数列表的方法来实现类似的功能。
- 非私有成员方法:子类可以继承父类的非私有成员方法,包括公有、受保护和默认访问权限的方法。子类可以直接调用这些方法,并在子类中重写这些方法以改变其行为。
Java的虚方法表是什么:当一个类继承自另一个类,它可以重写(override)父类的方法
Java的虚方法表(Virtual Method Table,简称VMT)是用于实现动态分派(Dynamic Dispatch)的一种机制,是一种数据结构,用来存储类及其继承层次结构中的虚方法的实际地址。。它是Java中实现多态(Polymorphism)的基础。
在Java中,==当一个类继承自另一个类,它可以重写(override)父类的方法。==当通过父类的引用调用这个方法时,实际上会根据对象的实际类型来决定调用哪个方法。这种根据对象实际类型来确定方法调用的机制称为动态分派。
当使用父类的引用调用方法时,根据引用的实际类型找到对应的虚方法表,
然后根据方法的索引或名称来查找要调用的方法的实际地址,从而实现多态性。
虚方法表是为了实现动态分派而引入的一种数据结构。每个Java类都有一个虚方法表,其中记录了该类及其继承层次结构中所有的虚方法。虚方法表是一个数组,数组中的每个元素都是一个函数指针,指向对应方法的实际实现。
与虚方法表对应的是实例对象的方法区(Method Area),其中保存了类的结构信息,包括方法代码等。
当创建对象实例时,对象并不包含自己的方法表。
相反,对象存储了一个指向类的虚方法表的指针,以便可以在运行时通过该指针查找正确的方法地址。
当通过一个引用调用方法时,Java虚拟机(JVM)会根据引用的实际类型找到对应的虚方法表,然后根据方法的索引或名称来查找要调用的方法的实际地址。这样就可以在运行时动态地确定要调用的方法,实现了多态性。
需要注意的是,虚方法表只与类的类型有关,而与具体对象的实例无关。每个类只有一个对应的虚方法表,而类的实例可以有多个,它们共享同一个虚方法表。
虚方法表是Java中实现多态的重要机制之一,它使得方法的调用在运行时可以根据对象的实际类型而动态确定,从而增加了程序的灵活性和扩展性。
总结:
- 子类无法继承父类的私有构造函数、私有成员变量和私有成员方法。
- 子类可以继承父类的非私有构造函数、非私有成员变量和非私有成员方法。
- 对于私有的构造函数、成员变量和成员方法,子类无法直接访问,但可以通过父类提供的公有/受保护方法间接访问和使用这些私有成员。
继承机制是面向对象编程的重要特性,它允许子类复用父类的功能并扩展其行为,提高了代码的重用性和可维护性。同时,子类还可以通过方法的重写来实现多态性,进一步增强了面向对象编程的灵活性和扩展性。