JavaSE 多态、final、权限、内部类

1 多态

1.1 概述

引⼊

多态是继封装、继承之后,⾯向对象的第三⼤特性。

⽣活中,⽐如跑的动作,⼩猫、⼩狗和⼤象,跑起来是不⼀样的。再⽐如⻜的动作,昆⾍、⻦类和⻜机,⻜起来也是不⼀样的。可⻅,同⼀⾏为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是这样的状态。

定义

多态:是指同⼀⾏为,具有多个不同表现形式 。

前提【重点】

  1. 继承或者实现【⼆选⼀】
  2. ⽅法的重写【意义体现:不重写,⽆意义】
  3. ⽗类引⽤指向⼦类对象【格式体现】

1.2 多态的实现

多态体现的格式:
在这里插入图片描述
当使⽤多态⽅式调⽤⽅法时,⾸先检查⽗类中是否有该⽅法,如果没有,则编译错误;如果有,执⾏的是⼦类重写后⽅法。
在这里插入图片描述
在这里插入图片描述

1.3 多态的好处

实际开发的过程中,⽗类类型作为⽅法形式参数,传递⼦类对象给⽅法,进⾏⽅法的调⽤,更能体现出多态的扩展性与便利。代码如下:
在这里插入图片描述
定义测试类:

public class Test {
 public static void main(String[] args) {
 // 多态形式,创建对象
 Cat c = new Cat();
 Dog d = new Dog();
 
 // 调⽤showCatEat
 showCatEat(c);
 // 调⽤showDogEat
 showDogEat(d);
 
 /*
 * 以上两个⽅法,均可以被showAnimalEat(Animal a)⽅法所替代
 * ⽽执⾏效果⼀致
 */
 showAnimalEat(c);
 showAnimalEat(d);
 }
 
 public static void showCatEat(Cat c) {
 c.eat();
 }
 
 public static void showDogEat(Dog d) {
 d.eat();
 }
 
 public static void showAnimalEat(Animal a) {
 a.eat();
 }
}

由于多态特性的⽀持, showAnimalEat ⽅法的 Animal 类型,是 Cat 和 Dog 的⽗类类型,⽗类类型接收⼦类对象,当然可以把 Cat 对象和 Dog 对象,传递给⽅法。

当 eat ⽅法执⾏时,多态规定,执⾏的是⼦类重写的⽅法,那么效果⾃然与 showCatEa t、 showDogEat ⽅法⼀致, 所以 showAnimalEat 完全可以替代以上两⽅法。

不仅仅是替代,在扩展性⽅⾯,⽆论之后再多的⼦类出现,我们都不需要编写 showXxxEat ⽅法了,直接使⽤ showAnimalEat 都可以完成。

所以,多态的好处,体现在,可以使程序编写的更简单,并有良好的扩展。

1.4 引⽤类型转换

多态的转型分为向上转型与向下转型两种:

向上转型

向上转型:多态本身是⼦类类型向⽗类类型向上转换的过程,这个过程是默认的。
当⽗类引⽤指向⼀个⼦类对象时,便是向上转型。
在这里插入图片描述

向下转型

向下转型:⽗类类型向⼦类类型向下转换的过程,这个过程是强制的。
⼀个已经向上转型的⼦类对象,将⽗类引⽤转为⼦类引⽤,可以使⽤强制类型转换的格式,便是向下转型。
在这里插入图片描述

为什么要转型

当使⽤多态⽅式调⽤⽅法时,⾸先检查⽗类中是否有该⽅法,如果没有,则编译错误。
也就是说,不能调⽤⼦类拥有,⽽⽗类没有的⽅法。编译都错误,更别说运⾏了。这也是多态给我们带来的⼀点"⼩麻烦"。所以,想要调⽤⼦ 类特有的⽅法,必须做向下转型。

转型演示,代码如下:
定义类:

abstract class Animal {
 abstract void eat();
}
class Cat extends Animal {
 public void eat() {
 System.out.println("吃⻥");
 }
 public void catchMouse() {
 System.out.println("抓⽼⿏");
 }
}
class Dog extends Animal {
 public void eat() {
 System.out.println("吃⻣头");
 }
 public void watchHouse() {
 System.out.println("看家");
 }
}

定义测试类:

public class Test {
 public static void main(String[] args) {
 // 向上转型
 Animal a = new Cat();
 a.eat(); // 调⽤的是 Cat 的 eat
 
 // 向下转型
 Cat c = (Cat)a;
 c.catchMouse(); // 调⽤的是 Cat 的 catchMouse
 }
}

转型的异常

转型的过程中,⼀不⼩⼼就会遇到这样的问题,请看如下代码:

public class Test {
 public static void main(String[] args) {
 // 向上转型
 Animal a = new Cat();
 a.eat(); // 调⽤的是 Cat 的 eat
 
 // 向下转型
 Dog d = (Dog) a;
 d.watchHouse(); // 调⽤的是 Dog 的 watchHouse 【运⾏报错】
 }
}

这段代码可以通过编译,但是运⾏时,却报出了 ClassCastException ,类型转换异常!这是因为,明明创建了Cat类型对象,运⾏时,当然不能转换成Dog对象的。这两个类型并没有任何继承关系,不符合类型转换的定义。

为了避免 ClassCastException 的发⽣,Java提供了
instanceof 关键字,给引⽤变量做类型的校验,格式如下:
在这里插入图片描述
所以,转换前,我们最好先做⼀个判断,代码如下:

public class Test {
 public static void main(String[] args) {
 // 向上转型
 Animal a = new Cat();
 a.eat(); // 调⽤的是 Cat 的 eat
 // 向下转型
 if (a instanceof Cat) {
 Cat c = (Cat) a;
 c.catchMouse(); // 调⽤的是 Cat 的 catchMouse
 } else if (a instanceof Dog) {
 Dog d = (Dog) a;
 d.watchHouse(); // 调⽤的是 Dog 的 watchHouse
 }
 }
}

2 接⼝多态的综合案例

2.1 笔记本电脑

笔记本电脑(laptop)通常具备使⽤USB设备的功能。在⽣产时,笔记本都预留了可以插⼊USB设备的USB接⼝,但具体是什么USB设备,笔记本⼚商并不关⼼,只要符合USB规格的设备都可以。

定义USB接⼝,具备最基本的开启功能和关闭功能。⿏标和键盘要想能在电脑上使⽤,那么⿏标和键盘也必须遵守USB规范,实现USB接⼝,否则⿏标和键盘的⽣产出来也⽆法使⽤。

2.2 案例分析

进⾏描述笔记本类,实现笔记本使⽤USB⿏标、USB键盘

USB接⼝,包含开启功能、关闭功能
笔记本类,包含运⾏功能、关机功能、使⽤USB设备功能
⿏标类,要实现USB接⼝,并具备点击的⽅法
键盘类,要实现USB接⼝,具备敲击的⽅法

2.3 案例实现

定义USB接⼝:

interface USB {
 void open(); // 开启功能
 void close(); // 关闭功能
}

定义⿏标类:

class Mouse implements USB {
 public void open() {
 System.out.println("⿏标开启,红灯闪⼀闪");
 }
 public void close() {
 System.out.println("⿏标关闭,红灯熄灭");
 }
 public void click() {
 System.out.println("⿏标单击");
 }
}

定义键盘类:

class KeyBoard implements USB {
 public void open() {
 System.out.println("键盘开启,绿灯闪⼀闪");
 }
 public void close() {
 System.out.println("键盘关闭,绿灯熄灭");
 }
 public void type() {
 System.out.println("键盘打字");
 }
}

定义笔记本类:

class Laptop {
 // 笔记本开启运⾏功能
 public void run() {
 System.out.println("笔记本运⾏");
 }
 // 笔记本使⽤usb设备,这时当笔记本对象调⽤这个功能时,必须给其传递⼀个符合USB规
则的USB设备
 public void useUSB(USB usb) {
 // 判断是否有USB设备
 if (usb != null) {
 usb.open();
 // 类型转换,调⽤特有⽅法
 if(usb instanceof Mouse) {
 Mouse m = (Mouse) usb;
 m.click();
 } else if (usb instanceof KeyBoard) {
 KeyBoard kb = (KeyBoard) usb;
 kb.type();
 }
 usb.close();
 }
 }
 public void shutDown() {
 System.out.println("笔记本关闭");
 }
}

测试类,代码如下:

public class Test {
 public static void main(String[] args) {
 // 创建笔记本实体对象
 Laptop lt = new Laptop();
 // 笔记本开启
 lt.run();
 
 // 创建⿏标实体对象
 Usb u = new Mouse();
 // 笔记本使⽤⿏标
 lt.useUSB(u);
 
 // 创建键盘实体对象
 KeyBoard kb = new KeyBoard();
 // 笔记本使⽤键盘
 lt.useUSB(kb);
 
 // 笔记本关闭
 lt.shutDown();
 }
}

3 final关键字

3.1 概述

学习了继承后,我们知道,⼦类可以在⽗类的基础上改写⽗类内容,⽐如,⽅法重写。那么我们能不能随意的继承API中提供的类,改写其内容呢?显然这是不合适的。为了避免这种随意改写的情况,Java提供了 final 关键字, ⽤于修饰不可改变内容。

final:不可改变。可以⽤于修饰类、⽅法和变量。
类:被修饰的类,不能被继承。
⽅法:被修饰的⽅法,不能被重写。
变量:被修饰的变量,不能被重新赋值。

3.2 使⽤⽅式

修饰类

在这里插入图片描述
查询API发现像 public final class String 、 public final class Math 、 public final class Scanner 等,很多我们学习过的类,都是被 final 修饰的,⽬的就是供我们使⽤,⽽不让我们所以改变其内容。

修饰⽅法

在这里插入图片描述

重写被 final 修饰的⽅法,编译时就会报错。

修饰变量

1. 局部变量 – 基本类型

基本类型的局部变量,被 final 修饰后,只能赋值⼀次,不能再更改。代码如下:

public class FinalDemo1 {
 public static void main(String[] args) {
 // 声明变量,使⽤final修饰
 final int a;
 // 第⼀次赋值
 a = 10;
 // 第⼆次赋值
 a = 20; // 报错,不可重新赋值
 // 声明变量,直接赋值,使⽤final修饰
 final int b = 10;
 // 第⼆次赋值
 b = 20; // 报错,不可重新赋值
 } }

在这里插入图片描述
根据 final 的定义,写法1报错!写法2,为什么通过编译呢?因为每次循环,都是⼀次新的变量c。这也是⼤家需要注意的地⽅。

2. 局部变量 – 引⽤类型

引⽤类型的局部变量,被 final 修饰后,只能指向⼀个对象,地址不能再更改。但是不影响对象内部的成员变量值的修改,代码如下:
在这里插入图片描述

3. 成员变量

成员变量涉及到初始化的问题,初始化⽅式有两种,只能⼆选⼀:
在这里插入图片描述
在这里插入图片描述

被final 修饰的常量名称,⼀般都有书写规范,所有字⺟都⼤写。

4 权限修饰符

4.1 概述

在Java中提供了四种访问权限,使⽤不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限:
在这里插入图片描述

4.2 不同权限的访问能⼒

在这里插入图片描述

5 内部类

5.1 概述

什么是内部类

将⼀个类A定义在另⼀个类B⾥⾯,⾥⾯的那个类A就称为内部类,B则称为外部类。

成员内部类

成员内部类:定义在类中⽅法外的类。
在这里插入图片描述

访问特点

内部类可以直接访问外部类的成员,包括私有成员。
外部类要访问内部类的成员,必须要建⽴内部类的对象。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

内部类仍然是⼀个独⽴的类,在编译之后会内部类会被编译成独⽴的.class⽂件,但是前⾯冠以外部类的类名和$符号 。

⽐如,Person$Heart.class

5.2 匿名内部类【重点】

匿名内部类:是内部类的简化写法。它的本质是⼀个 带具体实现的 ⽗类或者⽗接⼝的 匿名的 ⼦类对象。

开发中,最常⽤到的内部类就是匿名内部类了。以接⼝举例,当你使⽤⼀个接⼝时,似乎得做如下⼏步操作:

  1. 定义⼦类
  2. 重写接⼝中的⽅法
  3. 创建⼦类对象
  4. 调⽤重写后的⽅法

我们的⽬的,最终只是为了调⽤⽅法,那么能不能简化⼀下,把以上四步合成⼀步呢?匿名内部类就是做这样的快捷⽅式。

前提

匿名内部类必须继承⼀个⽗类或者实现⼀个⽗接⼝。

格式

在这里插入图片描述

使⽤⽅式

以接⼝为例,匿名内部类的使⽤,代码如下:
在这里插入图片描述
创建匿名内部类,并调⽤:
在这里插入图片描述
在这里插入图片描述
以上两步,也可以简化为⼀步,代码如下:

public class InnerDemo3 {
 public static void main(String[] args) {
 /*
 * 创建匿名内部类,直接传递给showFly(FlyAble f)
 */
 showFly(new FlyAble() {
 public void fly() {
 System.out.println("我⻜了~~~");
 }
 });
 }
 public static void showFly(FlyAble f) {
 f.fly();
 }
}

6 引⽤类型⽤法总结

实际的开发中,引⽤类型的使⽤⾮常重要,也是⾮常普遍的。我们可以在理解基本类型的使⽤⽅式基础上,进⼀步去掌握引⽤类型的使⽤⽅式。基本类型可以作为成员变量、作为⽅法的参数、作为⽅法的返回值,那么当然引⽤类型也是可以的。

6.1 class作为成员变量

在这里插入图片描述
使⽤ int 类型表示⻆⾊id和⽣命值,使⽤ String 类型表示名。此时, String 本身就是引⽤类型,由于使⽤的⽅式类似常量,所以往往忽略了它是引⽤类型的存在。如果我们继续丰富这个类的定义,给 Role 增加武器,穿戴装备等属性,我们将如何编写呢?
在这里插入图片描述
定义⻆⾊类:

class Role {
 int id;
 int blood;
 String name;
 // 添加武器属性
 Weapon wp;
 // 添加盔甲属性
 Armour ar;
 // 提供get/set⽅法
 public Weapon getWp() {
 return wp;
 }
 public void setWeapon(Weapon wp) {
 this.wp = wp;
 }
 public Armour getArmour() {
 return ar;
 }
 public void setArmour(Armour ar) {
 this.ar = ar;
 }
 // 攻击⽅法
public void attack() {
 System.out.println("使⽤" + wp.getName() + ", 造成" + wp.getHurt() + "点
伤害");
}
 // 穿戴盔甲
public void wear() {
 // 增加防御,就是增加blood值
 this.blood += ar.getProtect();
 System.out.println("穿上" + ar.getName() + ", ⽣命值增加" +
ar.getProtect());
 }
}

测试类:

public class Test {
 public static void main(String[] args) {
 // 创建Weapon 对象
 Weapon wp = new Weapon("屠⻰⼑", 999999);
 // 创建Armour 对象
 Armour ar = new Armour("麒麟甲", 10000);
 // 创建Role 对象
 Role r = new Role();
 // 设置武器属性
 r.setWeapon(wp);
 
 // 设置盔甲属性
 r.setArmour(ar);
 // 攻击
 r.attack();
 // 穿戴盔甲
 r.wear();
 }
}

输出结果:
使⽤屠⻰⼑,造成999999点伤害
穿上麒麟甲,⽣命值增加10000

类作为成员变量时,对它进⾏赋值的操作,实际上,是赋给
它该类的⼀个对象。

6.2 interface作为成员变量

接⼝是对⽅法的封装,对应游戏当中,可以看作是扩展游戏⻆⾊的技能。所以,如果想扩展更强⼤技能,我们在 Role 中,可以增加接⼝作为成员变量,来设置不同的技能。
定义接⼝:

// 法术攻击
public interface FaShuSkill {
 public abstract void faShuAttack();
}
定义⻆⾊类:
public class Role {
 FaShuSkill fs;
 public void setFaShuSkill(FaShuSkill fs) {
 this.fs = fs;
 }
 // 法术攻击
 public void faShuSkillAttack() {
 System.out.print("发动法术攻击:");
 fs.faShuAttack();
 System.out.println("攻击完毕");
 }
}

定义测试类:

public class Test {
 public static void main(String[] args) {
 // 创建游戏⻆⾊
 Role role = new Role();
 // 设置⻆⾊法术技能
 role.setFaShuSkill(new FaShuSkill() {
 @Override
 public void faShuAttack() {
 System.out.println("纵横天下");
 }
 });
 // 发动法术攻击
 role.faShuSkillAttack();
 // 更换技能
 role.setFaShuSkill(new FaShuSkill() {
 @Override
 public void faShuAttack() {
 System.out.println("逆转乾坤");
 }
 });
 // 发动法术攻击
 role.faShuSkillAttack();
 }
}
输出结果:
发动法术攻击:纵横天下
攻击完毕
发动法术攻击:逆转乾坤
攻击完毕

我们使⽤⼀个接⼝,作为成员变量,以便随时更换技能,这
样的设计更为灵活,增强了程序的扩展性。

接⼝作为成员变量时,对它进⾏赋值的操作,实际上,是赋
给它该接⼝的⼀个⼦类对象。

6.3 interface作为⽅法参数和返回值类型

当接⼝作为⽅法的参数时,需要传递什么呢?当接⼝作为⽅法的返回值类型时,需要返回什么呢?对,其实都是它的⼦类对象。 ArrayList 类我们并不陌⽣,查看API我们发现,实际上,它是 java.util.List 接⼝的实现类。所以,当我们看⻅ List 接⼝作为参数或者返回值类型时,当然可以将 ArrayList 的对象进⾏传递或返回。

请观察如下⽅法:获取某集合中所有的偶数。

定义⽅法:

public static List<Integer> getEvenNum(List<Integer> list) {
 // 创建保存偶数的集合
 ArrayList<Integer> evenList = new ArrayList<>();
 // 遍历集合list,判断元素为偶数,就添加到evenList中
 for (int i = 0; i < list.size(); i++) {
 Integer integer = list.get(i);
 if (integer % 2 == 0) {
 evenList.add(integer);
 }
 }
 /*
 * 返回偶数集合
 * 因为getEvenNum⽅法的返回值类型是List,⽽ArrayList是List的⼦类,
 * 所以evenList可以返回
 */
 return evenList; }

调⽤⽅法:

public class Test {
 public static void main(String[] args) {
 // 创建ArrayList集合,并添加数字
 ArrayList<Integer> srcList = new ArrayList<>();
 for (int i = 0; i < 10; i++) {
 srcList.add(i);
 }
 
 /*
 * 获取偶数集合
 * 因为getEvenNum⽅法的参数是List,⽽ArrayList是List的⼦类,
 * 所以srcList可以传递
 */
 List list = getEvenNum(srcList);
 System.out.println(list);
 }
}

接⼝作为参数时,传递它的⼦类对象。
接⼝作为返回值类型时,返回它的⼦类对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值