##24. final关键字
变量变为常量,不能被修改值
- 被修饰的变量必须初始化
- final修饰类的时候,此类不可以被继承
- final修饰方法后,表示该方法不能被重写
25. 重写:
1. 重写基本概念
1.1 什么是重写?
- 重写是指在子类在继承了父类之后,将父类中的方法,经过修改表现出与父类不同的行为。
- 相当于覆盖掉了父类原有的行为,所以重写又叫做覆盖,
1.2 重写的核心目的
重新定义父类原有的行为
- 为后续实现多态作准备
1.3 重写的实现条件
两个类之间要有继承关系 / 实现关系,成为父子类
2. 重写的语法规则与严格约束:
2.1 方法名/参数完全相同
- 子类重写的方法必须和父类被重写的方法拥有相同的名称和相同的参数列表(参数个数、类型、顺序都必须一致)。
- 错误示例:父类是 public void shout(String name),子类写成 public void shout() 或 public void shout(String name, int age) 这都不是重写,而是重载(Overload)。
2.2 访问权限(修饰符)
1. 子类方法的访问权>=父类方法的访问权限。
权限等级:public > protected > 默认(包访问权限) > private
- 正确:父类是 protected,子类可以是 public。
- 错误:父类是 public,子类改成 protected(会报错,因为权限缩小了)。
2.3 返回值类型必须兼容
2.3.1 父子类返回类型相同都是Computer(最基本的)
2.3.2 或者返回类型还可以是协变返回类型
- 子类重写方法的返回值类型可以是父类返回值类型的子类(这叫协变返回类型)
- 正确:父类返回 Computer,子类返回 Lenovo(因为 Lenovo 是 Computer 的子类)。
- 错误:父类返回 Computer,子类返回 String(报错,类型不兼容)
2.4 被private/final/static 修饰的父类方法不能在写子类中重写
父类中标记为 final 的方法是“最终方法”,目的是防止子类篡改。
- 父类的static静态方法属于类本身,不属于对象。
- 父类的 private 方法对子类是不可见的(无法继承),所以子类中定义同名方法只是一个新方法,不算重写。即使加上 @Override 注解也会报错。
2.5 构造方法 不可以被重写
- 父子类名都不同,而重写要求两个方法名必须一致,谈不上重写。
- 构造方法也不可以被继承,构造方法名要求与类名一致,父类的名字怎么可能和子类的名字一致?Dog类中出现一个Animal()?
3. 注解@override
只是编译器帮助我看写没写错,不写@override 他也知道这是重写的方法
26. 重载
26.1.1 实现条件
方法名一样
- 参数列表不一样
- 返回值无关
func(int a,int b)
func(string name)
//构成重载
26.1.2 核心目的
- 让一个方法可以实现多个功能。
- 比如math()方法,可以用于实现➕➖✖️➗四个不同功能。
26.1.3 重载实现条件
- 可以在同一个类中,也可以不在同一个类中。
- 也可以发生在父子类中,但本质判断仍看参数列表变化。
27. 如何区分重写与重载?
27.1 看参数列表
- 参数列表一样是重写
- 参数列表不一样是重载
- 为什么主要看参数列表?
- 因为重载和重写的方法名都要求一致,返回值也不好看,所以只能看参数列表。
示例对比
- 重载
class math {
public int add(int a, int b) {
return a + b;
}
public int add(int a, int b, int c) {
return (a - b - c);
}
}
2. 重写
class Animal {
public void move() {
System.out.println("动物在吃饭");
}
}
class Bird extends Animal {
@Override
public void move() {
System.out.println("鸟在吃种子");
}
}
27.2 绑定机制
27.2.1 重写绑定机制(动态绑定机制)
- 重写的绑定是在运行时绑定的
- 重写是在运行的时候看堆内存里面的实例对象是谁,来确定调用谁的方法的。
- 编译时看左边,运行时看实例对象
- 重写绑定机制在编译和运行的时候看的对象不一样所以叫做动态绑定。
Animal myPet = new Cat();
- 编译过程:JVM在编译的时候,发现myPet是Animal类,会检查Animal类中是否有eat方法,发现有eat方法则编译通过。
- 运行过程:编译结束后发现实际的对象是Cat对象则会调用Cat对象的eat方法
//动物类(父类)
class Animal {
public void eat() {
System.out.println("动物在吃食物");
}
}
//猫类(子类),重写 eat 方法
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫在吃鱼");
}
}
//测试类
public class Zoo {
public static void main(String[] args) {
Animal myPet = new Cat();
// 多态:声明为 Animal,实际对象是 Cat,向上转型
System.out.println("调用 myPet.eat():");
myPet.eat(); // 此处调用的是 Cat 的 eat()
}
}
调用 myPet.eat():
猫在吃鱼
27.2.2 重载绑定机制
- 重载的绑定是在编译的时候确定
- 重载方法主要是在编译的时候看参数列表确定调用哪个方法的
- 重载在编译和运行的时候通过参数列表来确定调用哪个方法,并且在运行的时候调用的还是那个方法,所以叫做静态绑定
class Math {
public int add(int a, int b) {
System.out.println("调用 add(int, int)");
return a + b;
}
public int add(int a, int b, int c) {
System.out.println("调用 add(int, int, int)");
return a - b - c;
}
}
public class Test {
public static void main(String[] args) {
Math m = new Math();
// 调用两个参数的
m.add(1, 2);
// 调用三个参数的
m.add(1, 2, 3);
// 展示编译时绑定:即使引用类型改变,参数决定调用
}
}
28. 向上转型
28.1 父类引用指向子类对象
虽然类型不一致,但必须是父子类,继承了之后才可以
- 向上转型之后,父类只能调用被子类重写的方法,不能调用子类自己有的方法,想调用子类自己的方法,所以产生了向下转型。
Animal animal = new Dog();
29. 向下转型
29.1 作用
当父类引用指向子类对象时,父类类型无法调用子类自己新增的方法(比如 Mac有 bootWithTouchID(),但 Computer 没有)。
29.2 如何转型?
需要强转:子类引用指向父类对象
class Computer {
double price; // 默认值 0.0
}
class Mac extends Computer {
// 子类独有的方法:指纹解锁(父类没有)
public void bootWithTouchID() {
System.out.println("Mac 正在使用 Touch ID 开机...");
}
}
public class Main {
public static void main(String[] args) {
// 向上转型
Computer myComputer = new Mac();
myComputer.price = 12999;
myComputer.bootWithTouchID();
// 错误!父类引用无法直接调用子类独有方法
// 编译报错
// 解决方案:向下转型,将 Computer 转回 Mac
if (myComputer instanceof Mac) {
Mac myMac = (Mac) myComputer; // 向下转型
myMac.bootWithTouchID(); // 成功调用!输出:Mac 正在使用 Touch ID 开机...
}
}
}
- 当子类被向上转型后,无法调用子类自身独有的方法时,使用向下转型可以实现对子类自身独有方法的调用。
29.3 instanceof关键字
作用:向下转型的时候检查转型的子类是否正确
if (myComputer instanceof Mac) {
Mac myMac = (Mac) myComputer; // 向下转型
myMac.bootWithTo uchID(); // 成功调用!输出:Mac 正在使用 Touch ID 开机...
}
否则,如果转型错误(比如 Computer 实际指向的是 Lenovo,你却强转成 Mac),程序会在运行时抛出
ClassCastException(类转换异常),导致程序崩溃
Computer c = new Lenovo();
Mac m = (Mac) c;
// 编译没问题,运行时报错!因为 c 根本不是 Mac
30. 动态绑定(调用重写)
调用方法的时候,编译的时候的方法和运行时的方法不一样,所以叫做动态绑定,因为向上转型。 动态绑定含义:动态绑定 = 向上转型 + 重写
- 调用重写方法的时候
1.编译时:检查 Animal 类有 eat(),通过。
2.运行时:JVM 动态绑定到实际传入对象的 eat()。
3.当程序运行时,animal 指向谁,JVM 就动态绑定到谁的方法- 主题:Java中的动态绑定。
- 具体场景:父类引用指向子类对象(Animal a = new Dog();)。(向上转型)
- 条件:子类重写了父类的方法(Dog 重写了 Animal 的 eat())。(重写)
- 结论:父类引用 animal 可以调用子类的重写方法。
class Animal {
public void eat() {
System.out.println("动物吃饭");
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗啃骨头");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog(); // 向上转型
animal.eat();
//1.输出结果是:狗啃骨头
//2.编译时:Animal有eat(),通过。
//3.运行时:JVM发现实际是Dog,执行Dog的eat()。
}
}
31. 静态绑定(调用重载)
调用重载方法时候编译的时候和运行的时候调用的方法不会发生改变
class Computer {
double price;
// 静多态:下面是三个 重载(Overload) 的 start 方法
// 它们方法名相同,但参数列表不同 —— 编译器在编译时就确定调用哪一个
public void start() {
System.out.println("电脑开机了(默认方式)");
}
public void start(String password) {
System.out.println("输入密码:" + password + ",电脑开机了");
}
public void start(int fingerprintId) {
System.out.println("扫描指纹 ID:" + fingerprintId + ",电脑开机了");
}
}
public class Main {
public static void main(String[] args) {
Computer c = new Computer();
// 编译器看到 c.start() 没参数,直接绑定到第一个方法
c.start();
// 编译器看到参数是 String,直接绑定到第二个方法
c.start("123456");
// 编译器看到参数是 int,直接绑定到第三个方法
c.start(10001);
}
}
33. 多态
33.1 定义:
多态就是同一个行为(方法)在不同的对象上表现出不同的形态。
- 父类引用指向子类对象,调用方法时实际执行的是子类的方法。
33.2 如何实现多态?
需要满足以下几点:继承+重写+向上转型
- 一个父类引用,接收不同的子类对象的时候(向上转型),父类引用调用子类重写后的方法时,因为不同子类重写的方法体的不同而输出不同的结果叫做多态。
- 使用父类引用调用被子类重写的方法会发生多态
- 描述动态绑定表现出不同行为的这整个过程叫做多态
- 如果你有 10 个子类(Dog、Cat、Duck),分别重写了 父类的eat()。
- 当程序运行时,animal 指向谁,JVM 就动态绑定到谁的方法。
- 这个过程是“一对多”的映射关系(一个父类引用,对应多个子类对象)。动态绑定本身是“运行时查表”的那一个动作,而多态是描述这个动作带来的代码表现效果。
// 1. 父类
class Animal {
public void eat() {
System.out.println("动物在进食");
}
}
// 2. 三个子类,分别重写 eat()
class Dog extends Animal {
@Override
public void eat() { System.out.println("狗在啃骨头 🦴");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫在吃鱼 🐟");
}
}
class Duck extends Animal {
@Override
public void eat() {
System.out.println("鸭子在吃菜叶 🥬");
}
}
// 3. 测试类
public class Test {
1.参数是父类引用
public static void feedAnimal(Animal animal) {
System.out.print("动物开始吃饭:");
animal.eat();
}
public static void main(String[] args) {
// 传入三个不同的子类对象,同一个方法表现出三种形态
4.传参就相当于进行了向上转型。
feedAnimal(new Dog()); // 实际是 Dog
feedAnimal(new Cat()); // 实际是 Cat
feedAnimal(new Duck()); // 实际是 Duck
}
}
动物开始吃饭:狗在啃骨头 🦴
动物开始吃饭:猫在吃鱼 🐟
动物开始吃饭:鸭子在吃菜叶 🥬
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2205_76017107/article/details/163311694




