一、继承
继承是 Java 中实现代码复用的重要手段,它允许一个类获取另一个类的属性和方法。通过extends
关键字,建立类之间的父子关系。
(一)继承的定义与作用
例如,在员工管理系统的场景下,Teacher
类和Consultant
类都有name
属性,为了减少重复代码,可以创建一个父类People
,将name
属性提取到People
类中。
java
typescript
class People {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Teacher extends People {
private String skill;
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
}
class Consultant extends People {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
在上述代码中,Teacher
和Consultant
类继承自People
类,它们可以使用People
类中的getName
和setName
方法,提高了代码的重用性。
(二)权限修饰符
权限修饰符用于限制类中成员(成员变量、成员方法、构造器)的访问范围,Java 中有private
、缺省(默认不写修饰符)、protected
和public
四种权限修饰符。
修饰符 | 本类里 | 同一个包中的其他类 | 不同包的子孙类 | 不同包的任意类 |
---|---|---|---|---|
private |
√ | |||
缺省(默认不写修饰符) | √ | √ | ||
protected |
√ | √ | √ | |
public |
√ | √ | √ | √ |
Java
class AccessModifierExample {
private int privateVar;
int defaultVar;
protected int protectedVar;
public int publicVar;
private void privateMethod() {}
void defaultMethod() {}
protected void protectedMethod() {}
public void publicMethod() {}
}
在这个类中,privateVar
和privateMethod
只能在本类中访问;defaultVar
和defaultMethod
在同一个包中的其他类可以访问;protectedVar
和protectedMethod
在不同包的子孙类中也能访问;publicVar
和publicMethod
在任何地方都可以访问 。
(三)继承的特点
- 单继承模式:Java 是单继承的,一个类只能继承一个直接父类,这避免了多继承带来的冲突。例如:
java
kotlin
class A {}
class B {}
// 以下代码会报错,因为Java不支持多继承
// class C extends A, B {}
- 多层继承 :Java 支持多层继承,所有类最终都直接或间接继承自
Object
类。例如:
java
scala
class Grandparent {}
class Parent extends Grandparent {}
class Child extends Parent {}
在这个例子中,Child
类间接继承了Grandparent
类的非私有成员,并且Object
类是所有类的祖宗类。
- 子类访问成员特点 :子类访问成员遵循就近原则,先在子类局部范围找,然后到子类成员范围找,最后到父类成员范围找,找不到则报错。如果子父类中出现重名成员,优先使用子类的,若要访问父类成员,可通过
super
关键字。例如:
java
scala
class ParentClass {
int value = 10;
}
class ChildClass extends ParentClass {
int value = 20;
void printValue() {
// 输出子类的value
System.out.println("子类的value: " + value);
// 输出父类的value
System.out.println("父类的value: " + super.value);
}
}
在ChildClass
的printValue
方法中,通过super.value
访问父类的value
成员变量。
(四)方法重写
当子类觉得父类中的某个方法不好用或无法满足自身需求时,可以重写该方法。方法重写需要满足方法名称、参数列表一样,并且要注意一些规则。
java
scala
class Animal {
public void sound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
// 使用@Override注解校验方法重写格式
@Override
public void sound() {
System.out.println("狗汪汪叫");
}
}
在上述代码中,Dog
类重写了Animal
类的sound
方法。重写时需注意:
- 子类重写父类方法时,访问权限必须大于或者等于父类该方法的权限(
public > protected > 缺省
)。 - 重写的方法返回值类型,必须与被重写方法的返回值类型一样,或者范围更小。
- 私有方法、静态方法不能被重写,如果重写会报错。
(五)子类构造器
子类的全部构造器,都会先调用父类的构造器,再执行自己。默认情况下,子类构造器的第一行代码是super()
(写不写都有),它会调用父类的无参数构造器;如果父类没有无参数构造器,则必须在子类构造器的第一行手写super(...)
,指定调用父类的有参数构造器。
java
arduino
class People {
private String name;
private int age;
public People() {}
public People(String name, int age) {
this.name = name;
this.age = age;
}
}
class Teacher extends People {
private String skill;
public Teacher() {}
public Teacher(String name, int age, String skill) {
// 调用父类有参数构造器
super(name, age);
this.skill = skill;
}
}
在Teacher
类的有参数构造器中,通过super(name, age)
调用父类的有参数构造器,先初始化从父类继承的属性,再初始化自身的skill
属性。
(六)this
与super
this
代表本类对象的引用,super
代表父类存储空间的标识,它们在访问成员变量、成员方法和构造方法时有不同的用法。
java
csharp
class Parent {
int value = 10;
void print() {
System.out.println("父类的print方法");
}
Parent() {
System.out.println("父类的无参构造器");
}
Parent(int value) {
this.value = value;
System.out.println("父类的有参构造器,value: " + value);
}
}
class Child extends Parent {
int value = 20;
void print() {
// 访问本类的value
System.out.println("本类的value: " + this.value);
// 访问父类的value
System.out.println("父类的value: " + super.value);
// 调用本类的print方法
this.print();
// 调用父类的print方法
super.print();
}
Child() {
// 调用父类的无参构造器
super();
System.out.println("子类的无参构造器");
}
Child(int value) {
// 调用父类的有参构造器
super(value);
this.value = value;
System.out.println("子类的有参构造器,value: " + value);
}
}
在Child
类中,通过this
和super
可以清晰地区分访问本类和父类的成员。需要注意的是,this(...)
和super(...)
都只能放在构造器的第一行,且二者不能同时出现。
二、多态
多态是在继承 / 实现情况下的一种现象,表现为对象多态和行为多态,它使程序更加灵活和可扩展。
(一)多态的定义与前提
多态的前提是有继承 / 实现关系、存在父类引用子类对象、存在方法重写。例如:
java
scala
class Animal {
public void move() {
System.out.println("动物在移动");
}
}
class Dog extends Animal {
@Override
public void move() {
System.out.println("狗在跑");
}
}
class Cat extends Animal {
@Override
public void move() {
System.out.println("猫在走");
}
}
使用时:
java
arduino
public class Main {
public static void main(String[] args) {
// 父类引用指向子类对象,体现对象多态
Animal animal1 = new Dog();
Animal animal2 = new Cat();
// 调用重写的方法,体现行为多态
animal1.move();
animal2.move();
}
}
在上述代码中,Animal
是父类,Dog
和Cat
是子类,它们重写了父类的move
方法。通过父类引用指向不同的子类对象,调用move
方法时会执行子类中重写后的方法。需要注意的是,Java 中的属性(成员变量)不谈多态。
(二)多态的好处
-
解耦合与便于扩展维护:在多态形式下,右边对象是解耦合的,更便于扩展和维护。例如,当需要添加新的动物子类时,只需要创建新的子类并实现相应的方法,而不需要修改使用多态的代码。
-
增强方法扩展性:定义方法时,使用父类类型的形参,可以接收一切子类对象,扩展性更强、更便利。例如:
java
typescript
class Animal {
public void eat() {
System.out.println("动物在进食");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗在啃骨头");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫在吃鱼");
}
}
public class Main {
// 使用父类类型作为形参
public static void feed(Animal animal) {
animal.eat();
}
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
// 可以传入不同的子类对象
feed(dog);
feed(cat);
}
}
在feed
方法中,使用Animal
类型的形参,可以接收Dog
和Cat
等子类对象,提高了代码的扩展性。但多态下不能直接使用子类的独有功能,例如Dog
类可能有bark
方法,但通过Animal
引用无法直接调用bark
方法。
(三)多态下的类型转换
-
自动类型转换 :
父类 变量名 = new 子类();
,例如Animal animal = new Dog();
,这是将子类对象自动转换为父类类型,也称为向上转型。在向上转型过程中,子类对象会失去子类特有的方法,只能调用父类中定义的方法。 -
强制类型转换 :
子类 变量名 = (子类) 父类变量
,例如Dog dog = (Dog) animal;
,这是将父类引用强制转换为子类类型,也称为向下转型。但在进行强制类型转换前,建议使用instanceof
关键字判断当前对象的真实类型,以避免运行时的ClassCastException
异常。例如:
java
java
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.bark();
}
}
}
在上述代码中,通过instanceof
判断animal
对象是否是Dog
类型,如果是,则进行强制类型转换并调用Dog
类特有的bark
方法。需要注意的是,存在继承 / 实现关系就可以在编译阶段进行强制类型转换,但运行时如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常。