如何使用继承
java
public class 子类名 extends 父类名 {
}
先用一个例子理解
java
class Animal {
void eat() {
System.out.println("动物在吃东西");
}
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("狗在吃骨头");
}
void bark() {
System.out.println("狗在叫");
}
}
1. 什么是"父类引用指向子类对象"?
java
Animal a = new Dog();
这里:
new Dog():真正创建的是一个 Dog 对象;Animal a:这个变量的类型是 Animal;- 所以
a是一个 父类类型的引用,指向子类对象。
这就像说:
这只动物其实是一条狗,但我现在只把它当作"动物"来看待。
2. 为什么不能直接调用子类特有方法?
java
a.eat(); // 可以
a.bark(); // 编译报错
因为编译器看的是 a 的声明类型,也就是 Animal。
Animal 里没有 bark(),所以编译器会认为这个方法不存在。
虽然运行时 a 指向的是 Dog,但编译器不会自动去查子类有没有额外方法。
3. 强制类型转换是什么意思?
java
Dog d = (Dog) a;
d.bark(); // 可以调用
(Dog) a 就是 强制类型转换 ,也叫 向下转型。
意思是:
我知道
a虽然声明成Animal,但它实际指向的是一个Dog,现在请把它当成Dog来用。
转成 Dog 之后,就可以调用 Dog 特有的 bark() 方法了。
4. 为什么要"强制"?
因为从父类类型转回子类类型,不一定安全。
比如下面这种就会报错:
java
Animal a = new Animal();
Dog d = (Dog) a; // 运行时报 ClassCastException
这里 a 实际指向的是 Animal,不是 Dog,所以不能转成 Dog。
5. 比较安全的写法
转换前先用 instanceof 判断一下:
java
if (a instanceof Dog) {
Dog d = (Dog) a;
d.bark();
}
这样只有当 a 真正指向的是 Dog 时,才会转换,避免 ClassCastException。
简单记忆
| 操作 | 写法 | 说明 |
|---|---|---|
| 向上转型 | Animal a = new Dog(); |
自动转换,安全,但只能调用父类声明的方法 |
| 向下转型 | Dog d = (Dog) a; |
强制转换,可以调用子类特有方法,但可能出错 |
| 安全判断 | a instanceof Dog |
先判断再转,更安全 |
所以,"如果一个对象赋值给其父类的引用,此时想要调用该对象特有的方法,必须进行强制类型转换"的意思就是:
父类引用会"遮住"子类特有的方法。想调用这些特有方法,就必须把它强制转回子类类型。
override 是什么?
override 叫"方法重写",意思是:
子类重新定义父类里已经有的方法。
基本写法
java
class Animal {
void speak() {
System.out.println("动物在叫");
}
}
class Dog extends Animal {
@Override
void speak() {
System.out.println("狗在汪汪叫");
}
}
这里:
Animal里有speak();Dog继承Animal;Dog里又写了一个同样的speak();- 这就是 重写。
@Override 是一个注解,用来告诉编译器:
这个方法是在重写父类的方法。
它不是必须的,但 强烈建议写,因为如果你写错了方法名、参数,编译器会提醒你。
怎么调用?
java
Animal a = new Dog();
a.speak(); // 输出:狗在汪汪叫
虽然 a 的类型是 Animal,但它实际指向的是 Dog 对象,所以会调用 Dog 里重写后的 speak()。
这就是多态。
重写的要求
子类重写父类方法时,要注意:
| 要求 | 说明 |
|---|---|
| 方法名必须相同 | 比如都是 speak |
| 参数列表必须相同 | 参数类型、个数、顺序都要一样 |
| 返回值类型一般相同 | 或兼容的子类型 |
| 访问权限不能更严格 | 父类是 public,子类不能改成 private |
不能重写 static 方法 |
static 方法属于类,不属于对象 |
不能重写 private 方法 |
子类根本看不到父类的 private 方法 |
重写和重载的区别
很多人会混淆这两个:
| 对比 | 重写 override | 重载 overload |
|---|---|---|
| 发生位置 | 子类和父类之间 | 同一个类里 |
| 方法名 | 相同 | 相同 |
| 参数列表 | 必须相同 | 必须不同 |
| 作用 | 改变父类方法的行为 | 提供多个同名方法 |
例如:
java
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}
这是 重载 overload,不是重写。
简单记忆
- override:子类把父类的方法"换一种实现";
- @Override:写在方法上面,表示"我在重写父类方法";
- 调用时看 实际对象类型,不是看引用类型。
super 就是 "去父类那里找东西" 。
子类继承父类后,如果子类自己也有同名的方法、变量,或者想调用父类的构造方法,就可以用 super 明确指定:我要用父类的那个版本。
1. 调用父类构造方法:super(...)
java
class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
Dog(String name) {
super(name); // 调用父类 Animal(String name) 构造方法
}
}
注意:
super(...)必须写在子类构造方法的 第一行;- 如果子类构造方法里没有写
super(...),Java 会默认调用父类的 无参构造方法; - 如果父类没有无参构造方法,子类就必须显式写
super(...)。
2. 访问父类成员变量:super.变量名
java
class Father {
String name = "爸爸";
}
class Son extends Father {
String name = "儿子";
void show() {
System.out.println(name); // 儿子
System.out.println(super.name); // 爸爸
}
}
当子类和父类有同名变量时,直接写 name 会访问子类自己的;写 super.name 才能访问父类的。
3. 调用父类方法:super.方法名()
java
class Animal {
void speak() {
System.out.println("动物在叫");
}
}
class Dog extends Animal {
@Override
void speak() {
super.speak(); // 先执行父类版本
System.out.println("狗在汪汪叫");
}
}
调用时:
java
Dog dog = new Dog();
dog.speak();
输出:
text
动物在叫
狗在汪汪叫
这里 super.speak() 就是:虽然子类重写了 speak(),但我还想保留父类原来的行为。
和 this 对比一下
| 关键字 | 含义 | 常见用法 |
|---|---|---|
this |
当前对象自己 | this.name、this() 调用本类构造 |
super |
当前对象的父类部分 | super.name、super() 调用父类构造、super.method() 调用父类方法 |
可以这样记:
this:找 自己;super:找 父类。
常见错误
java
class Dog extends Animal {
Dog(String name) {
System.out.println("准备创建狗");
super(name); // 错误:super() 必须在第一行
}
}
还有,super 不能在 static 方法里使用,因为静态方法不属于某个对象。
一句话总结
super 就是让子类在需要时能访问父类的构造方法、变量和方法,尤其是 子类重写或隐藏了父类内容时,用 super 找回父类版本。
不完全一样,它们有重叠,但不是同一个东西。
可以这么理解:
| 对比点 | Java final |
C++ const |
|---|---|---|
| 修饰变量 | 表示只能赋值一次,之后不能重新赋值 | 表示变量是只读的,不能修改 |
| 修饰对象/引用 | final 只保证引用不变,对象内部仍可改 |
const 可以限制对象本身不能被修改 |
| 修饰方法 | final 表示方法不能被重写 |
const 放在成员函数后面,表示该方法不会修改对象 |
| 修饰类 | final 表示类不能被继承 |
const 不能用来修饰类 |
| 修饰指针/引用 | Java 没有指针,所以没有这种用法 | const 可以修饰指针本身或指针指向的内容 |
变量层面:最像
java
final int x = 10;
x = 20; // 报错
cpp
const int x = 10;
x = 20; // 报错
这里它们很像,都是"初始化后不能改"。
但对象层面差别很大
Java 里:
java
final List<String> list = new ArrayList<>();
list.add("hello"); // 可以
list = new ArrayList<>(); // 不行
final 只是说 list 这个引用不能指向别的对象,但对象里面的内容可以改。
C++ 里:
cpp
const MyClass obj;
obj.modify(); // 如果 modify 不是 const 成员函数,会报错
const 能限制对象本身不能被修改,这是 Java final 做不到的。
修饰方法时也不一样
Java:
java
public final void show() { }
表示子类不能重写这个方法。
C++:
cpp
void show() const { }
表示这个成员函数不会修改当前对象。
简单记
- Java
final:不能重新赋值、不能重写、不能继承。 - C++
const:强调"只读",能修饰变量、指针、引用、对象和成员函数。 - Java 的
const:只是保留关键字,目前没用。
所以如果你是在学 Java,看到 final 不要直接把它当成 C++ 的 const,变量上有点像,但对象和方法上的语义差别挺大。