Java第八课(多态,内部类,引用类型用法总结)

Java第七课回顾
权限修饰符: public -> protected -> (default) ->private
重写:
1.方法名和参数列表一致
2.子类中重写的方法, 返回值类型[小于等于]父类返回值类型
3.权限修饰符,[大于等于]父类方法的权限
final:
类: 类不能被继承
方法: 方法不能被重写
变量: 常量, 不能被修改, 必须初始化
常见的final类:
String Math
String不可变的: CharSequence - 字符序列 - 字符数组
底层实现: 字符数组 -> 字节数组 byte[] value
final value = 其他值
String str = “hello”;
str = “hi”;
接口:
1.公共的抽象方法
2.公共的静态常量 public static final
3.公共的默认方法
4.公共的静态方法[私有的]
5.私有的方法
使用接口:
实现类 implements 接口
实现所有的抽象方法, 默认方法[可以选择性重写]
创建实现类对象, 调用方法
类和接口叫做多实现, 要重写/覆盖[实现]所有的接口的所有抽象方法
和 重复的默认方法
类和类 叫做单继承, 父类中的方法和父接口中的默认方法重复了, 优先调用父类中的方法
Zi extends Fu implements InterfaceA

Java第八课(多态,内部类,引用类型用法总结)

多态

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

前提【重点】

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

多态的实现
多态体现的格式:

⽗类类型 变量名 = new ⼦类对象;
变量名.⽅法名();

⽗类类型:指⼦类对象继承的⽗类类型,或者实现的⽗接⼝类型。
代码如下:

Fu f = new Zi();
f.method();

当使⽤多态⽅式调⽤⽅法时,⾸先检查⽗类中是否有该⽅法,如果没有,则编译错误;如果有,
执⾏的是⼦类重写后⽅法。
代码如下:

定义父类

public abstract class Animal {
 public abstract void eat();
}

定义⼦类:

class Cat extends Animal {
 public void eat() {
 System.out.println("吃⻥");
 }
}
class Dog extends Animal {
 public void eat() {
 System.out.println("吃⻣头");
 }
}

定义测试类:

public class Test {
 public static void main(String[] args) {
 // 多态形式,创建对象
 Animal a1 = new Cat();
 // 调⽤的是 Cat 的 eat
 a1.eat();
 // 多态形式,创建对象
 Animal a2 = new Dog();
 // 调⽤的是 Dog 的 eat
 a2.eat();
 }
}

多态的好处
实际开发的过程中,⽗类类型作为⽅法形式参数,传递⼦类对象给⽅法,进⾏⽅法的调⽤,更能
体现出多态的扩展性与便利。代码如下:
定义⽗类:

public abstract class Animal {
 public abstract void eat();
}

定义⼦类:

class Cat extends Animal {
 public void eat() {
 System.out.println("吃⻥");
 }
}
class Dog extends Animal {
 public void eat() {
 System.out.println("吃⻣头");
 }
}

定义测试类:

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 都可以完成。
所以,多态的好处,体现在,可以使程序编写的更简单,并有良好的扩展。

引⽤类型转换

多态的转型分为向上转型与向下转型两种:
向上转型
向上转型:多态本身是⼦类类型向⽗类类型向上转换的过程,这个过程是默认的。
当⽗类引⽤指向⼀个⼦类对象时,便是向上转型。
使⽤格式:

⽗类类型 变量名 = new ⼦类类型();
如:Animal a = new Cat();

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

⼦类类型 变量名 = (⼦类类型) ⽗类变量名;
如: Cat c =(Cat) a;

为什么要转型
当使⽤多态⽅式调⽤⽅法时,⾸先检查⽗类中是否有该⽅法,如果没有,则编译错误。也就是
说,不能调⽤⼦类拥有,⽽⽗类没有的⽅法。编译都错误,更别说运⾏了。这也是多态给我们带
来的⼀点"⼩麻烦"。所以,想要调⽤⼦ 类特有的⽅法,必须做向下转型。
转型演示,代码如下:
31 }
⽗类类型 变量名 = new ⼦类类型();
如:Animal a = new Cat();
1
2
⼦类类型 变量名 = (⼦类类型) ⽗类变量名;
如: Cat c =(Cat) a;
1
2
定义类:

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 关键字,给引⽤变量做类型的校
验,格式如下:

变量名 instanceof 数据类型
如果变量属于该数据类型,返回true。
如果变量不属于该数据类型,返回false。

所以,转换前,我们最好先做⼀个判断,代码如下:

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
 }
 }
}

匿名内部类【重点】

匿名内部类:是内部类的简化写法。它的本质是⼀个 带具体实现的 ⽗类或者⽗接⼝的 匿名
的 ⼦类对象。
开发中,最常⽤到的内部类就是匿名内部类了。以接⼝举例,当你使⽤⼀个接⼝时,似乎得做如
下⼏步操作:

  1. 定义⼦类
  2. 重写接⼝中的⽅法
  3. 创建⼦类对象
  4. 调⽤重写后的⽅法
    我们的⽬的,最终只是为了调⽤⽅法,那么能不能简化⼀下,把以上四步合成⼀步呢?匿名内部
    类就是做这样的快捷⽅式。

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

格式

new ⽗类名或者接⼝名() {
 // ⽅法重写
 @Override
 public void method() {
 // 执⾏语句
 }
};

使⽤⽅式
以接⼝为例,匿名内部类的使⽤,代码如下:
定义接⼝:

public abstract class FlyAble {
 public abstract void fly();
}

创建匿名内部类,并调⽤:

public class InnerDemo {
 public static void main(String[] args) {
 /*
 * 1.等号右边:是匿名内部类,定义并创建该接⼝的⼦类对象
 * 2.等号左边:是多态赋值,接⼝类型引⽤指向⼦类对象
 */
 FlyAble f = new FlyAble() {
 public void fly() {
 System.out.println("我⻜了~~~");
 }
 };

 // 调⽤fly⽅法,执⾏重写后的⽅法
 f.fly();
 }
}

通常在⽅法的形式参数是接⼝或者抽象类时,也可以将匿名内部类作为参数传递。代码如下:

public class InnerDemo2 {
 public static void main(String[] args) {
 /*
 * 1.等号右边:定义并创建该接⼝的⼦类对象
 * 2.等号左边:是多态,接⼝类型引⽤指向⼦类对象
 */
 FlyAble f = new FlyAble() {
 public void fly() {
 System.out.println("我⻜了~~~");
 }
 };
 // 将f传递给showFly⽅法中
 showFly(f);
 }
 public static void showFly(FlyAble f) {
 f.fly();
 }
}

以上两步,也可以简化为⼀步,代码如下:

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();
 }
}

引⽤类型⽤法总结

class作为成员变量
在定义⼀个类Role(游戏⻆⾊)时,代码如下:

class Role {
 int id; // ⻆⾊id
 int blood; // ⽣命值
 String name; // ⻆⾊名称
}

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

class Weapon {
 String name; // 武器名称
 int hurt; // 伤害值
}

定义穿戴盔甲类,将增加防御能⼒,也就是提升⽣命值:

class Armour {
 String name; // 装备名称
 int protect; // 防御值
}

定义⻆⾊类:

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

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

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();
 }
}
输出结果:
发动法术攻击:纵横天下
攻击完毕
发动法术攻击:逆转乾坤
攻击完毕

我们使⽤⼀个接⼝,作为成员变量,以便随时更换技能,这样的设计更为灵活,增强了程
序的扩展性。
接⼝作为成员变量时,对它进⾏赋值的操作,实际上,是赋给它该接⼝的⼀个⼦类对象。

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、付费专栏及课程。

余额充值