记录学习Java基础中有关继承、方法重写、构造器调用的基础知识,学习继承之前建议学习static关键字的内容【自学笔记】01Java基础-09Java关键字详解
1 继承概述
1.1 什么是继承?
1.2 继承的特点
- 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器。
- Java是单继承模式:一个类只能继承一个直接父类。
- Java不支持多继承、但是支持多层继承。
- Java中所有的类都是Object类的子类。
1.3 继承的重要问题
1.3.1 子类是否可以继承父类的构造器?
不可以的,子类有自己的构造器,父类构造器用于初始化父类对象。
1.3.2 子类是否可以继承父类的私有成员(方法,变量)?
可以的,只是不能直接访问。
具体表现如下:
- 子类不能直接引用父类的私有字段(成员变量)。
- 子类不能覆盖(override)父类的私有方法。
- 子类可以通过调用父类提供的公共(public)或受保护(protected)方法来间接访问或者修改私有字段的值,前提是父类提供了这样的接口。
例如,在父类中有一个私有变量,并且提供了一个公共的getter和setter方法,那么子类就可以通过调用这些方法来读取和设置该私有变量的值。
1.3.3 子类是否可以继承父类的静态成员?
有争议的知识点。
子类可以直接使用父类的静态成员(共享)
但个人认为:子类不能继承父类的静态成员。(共享并非继承)
Java中,静态成员(包括静态变量和静态方法)是与类关联的,而不是与对象实例关联。因此子类可以直接访问父类的静态成员,但不会重新创建或覆盖这些成员。
public class Parent {
public static int count = 0;
// 静态方法
public static void incrementCount() {
count++;
}
}
public class Child extends Parent {
// 子类无需定义count,直接可以使用Parent.count
public static void main(String[] args) {
// 直接通过类名访问父类的静态成员
System.out.println(Parent.count); // 输出:0
Parent.incrementCount();
System.out.println(Parent.count); // 输出:1
System.out.println(Child.count); // 输出:1,因为Child和Parent共享同一个静态变量count
}
}
在这个例子中,Child
类和 Parent
类共享同一个 count
变量。同样,Child
类也可以调用 Parent
中的public静态方法 incrementCount()
。但是,子类不能重写父类的静态方法,只能重新声明一个同名的静态方法,这将被视为一个新的方法而非覆盖。
1.4 继承后方法、变量的访问优先级
满足就近原则。
- 先子类局部范围找
- 然后子类成员范围找
- 然后父类成员范围找,如果父类范围还没有找到则报错。
当子类中方法或变量与父类重名时,会优先使用子类的,此时如何指定使用父类的方法或变量?
可以通过super.父类成员变量/父类成员方法
,指定访问父类的成员。了解super关键字
1.5 子类如何声明与父类同名的变量的两种方式
子类中想声明与父类同名的变量时有两种情况:
- 对于父类中static修饰的静态变量(类变量),子类必须声明为static类型。子类和父类的static同名变量各自独立,前面学过了子类中使用父类中static变量直接
类名.变量
,可以查看了解static关键字
public class Parent {
public static int count = 10;
}
public class Child extends Parent {
public static int count = 20;
public static void main(String[] args) {
System.out.println(Parent.count); // 输出:10
System.out.println(Child.count); // 输出:20
}
}
- 对于父类中非static修饰的实例变量,子类可以声明与父类同名的非static变量,可以使用
this
关键字来获取当前作用域内的非静态变量,用super
来获取父类的非静态变量。
public class Parent {
public int count = 10;
}
public class Child extends Parent {
public int count = 20; // 子类中声明了与父类同名的实例变量
public void displayCounts() {
System.out.println("Parent count: " + super.count);
System.out.println("Child count: " + this.count);
}
public static void main(String[] args) {
Child child = new Child();
child.displayCounts(); // 输出:
// Parent count: 10
// Child count: 20
}
}
2 方法重写
public class Animal {
public void makeSound() {
System.out.println("动物发出叫声");
}
}
public class Dog extends Animal {
@Override // 标注此方法是重写父类Animal的makeSound方法
public void makeSound() {
System.out.println("狗叫汪汪汪");
}
}
2.1 @Override重写注解的作用
在上方示例代码中,重写的方法makeSound()
上方出现了@Override注解。
- Override注解放在重写后的方法上,作为重写是否正确的校验注解。
- 加上该注解后如果重写错误,编译阶段会出现错误提示。
- 建议重写方法都加@Override注解,代码安全,优雅!也并非一定要加。
2.2 方法重写的注意事项
- 子类必须继承自父类。
- 重写方法的名称、形参列表、返回类型必须与被重写方法的一致。
- private私有方法和static静态方法不能被重写。
- 子类重写父类方法时,访问权限必须大于或者等于父类 (权限范围:缺省 < protected < public)
- 重写的方法抛出的异常类型不能大于父类对应方法允许抛出的异常类型,也就是说,子类重写的方法可以不抛出异常,或者抛出父类方法所声明异常的子类异常。
3 子类构造器
复习构造器的概念 > 封装-构造方法
子类并不继承父类的构造器,而是在自己构造器调用父类构造器来初始化从父类继承而来的成员变量。
- 子类可以通过
super
关键字在构造器内部明确调用父类的一个构造器,从而初始化父类的状态。 - 调用语句
super();
必须是子类构造器中第一行,并且一个构造器中只能出现一次。如果显示写明就是显式调用,如果没写也会存在,为隐式调用。 super()
的"()"
中可以填入参数,以此调用父类有参构造器,将会通过指定的参数名称、数量、排序来判断调用的父类中的哪个有参构造器。
显式调用:
public class Parent {
public int value;
// 父类构造器
public Parent(int value) {
this.value = value;
}
}
public class Child extends Parent {
public String name;
// 显式调用父类构造器
public Child(int parentValue, String childName) {
super(parentValue); // 调用Parent类的构造器
this.name = childName;
}
- 即使没有显式地调用父类构造器,子类构造器第一行也会自动插入一个父类无参构造器的隐式调用:
“super();”
。 - 如果父类没有无参构造器,那么子类必须显式调用一个带有参数的父类构造器;反之如果没有显式调用父类有参构造器,则父类必须存在无参构造能被默认隐式调用。
隐式调用:
// 若不显式调用,则会默认调用父类无参构造器(若存在)
// 如果Parent类没有无参构造器,则此处会出错,需要显示调用带参构造器
public Child(String childName) {
//super();//第一句默认调用父类无参构造器,即使不写也默认存在
this.name = childName;
}
}
3.1 子类构造器调用父类有哪些情况?
前言:
我们知道在实体类中没有定义任何构造器时,系统会自动提供一个默认(无参)构造器。
一旦定义了至少一个构造器(无论是否有参数),Java编译器将不再自动提供无参构造器。这时若需无参构造器来创建对象,那么必须手动提供一个无参构造器,否则将会报错。
所以子类构造器调用父类有以下情况:
- 父类没有显式定义任何构造器,此时默认有无参构造器,子类会默认隐式调用父类无参构造器
- 父类显式定义了无参构造器,此时子类会默认隐式调用父类无参构造器。
- 父类显式定义了有参构造器,此时子类必须显式调用
super(...)
至少一个有参构造器,否则因为无法找到默认无参构造器报错。 - 父类显式定义了有参构造器和无参构造器,此时子类会默认隐式调用无参构造器,也可以显式调用任一构造器。
3.2 子类为什么必须调用父类构造器?
- 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。
- 子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化。
4 this和super关键字使用总结
-
this 关键字:
- 表示当前对象引用,它总是指向调用该方法或构造器的对象实例。
- 在方法内部,
this
可以用来引用当前对象的属性或方法:class Person { String name; public Person(String name) { this.name = name; // 使用 this 引用当前实例对象的 name 属性,并将传入的形参name赋值给它 } public void showName() { System.out.println(this.name); // 使用 this 引用当前对象的 name 属性 } }
- 当成员变量与局部变量同名时,
this
用于区分二者:class MyClass { int value; public void setValue(int value) { this.value = value; // 设置的是成员变量 value,而不是方法参数 } }
this
还可以作为方法或构造器的参数传递当前对象引用:class AnotherClass { public void process(MyClass obj) { //... } public void callProcess() { process(this); // 将当前对象自身作为参数传递给 process 方法 }
-
super 关键字:
super
通常在子类中使用,用于访问父类(超类)的成员,包括属性、方法和构造器。- 在构造器中,
super()
用于调用父类的构造器:class Child extends Parent { public Child() { super(); // 调用父类无参构造器 } public Child(String name) { super(name); // 如果父类有一个接受 String 参数的构造器,则调用该构造器 } }
- 在子类的方法中,
super
用于调用父类被覆盖(重写)的方法:class Animal { public void makeSound() { System.out.println("动物发出叫声"); } } class Dog extends Animal { @Override public void makeSound() { super.makeSound(); // 调用父类的 makeSound 方法 System.out.println("狗叫汪汪汪"); } }
- 同样,
super
也可以用于访问父类中被隐藏的静态字段。