接口
在设计LOL的时候,进攻类英雄有两种,一种是进行物理系攻击,一种是进行魔法系攻击
这时候,就可以使用接口来实现这个效果。
接口就像是一种约定,我们约定某些英雄是物理系英雄,那么他们就一定能够进行物理攻击。
1.物理攻击接口
创建一个接口 File->New->Interface
AD ,声明一个方法 physicAttack 物理攻击,但是没有方法体,是一个“空”方法
2.设计一类英雄,能够使用物理攻击
设计一类英雄,能够使用物理攻击,这类英雄在LOL中被叫做AD
类:ADHero
继承了Hero 类,所以继承了name,hp,armor等属性
实现某个接口,就相当于承诺了某种约定
所以,实现了AD这个接口,就必须提供AD接口中声明的方法physicAttack()
实现在语法上使用关键字 implements
@Override
3.魔法攻击接口
创建一个接口 File->New->Interface
AP ,声明一个方法 magicAttack 魔法攻击,但是没有方法体,是一个“空”方法
4.设计一类英雄,只能使用魔法攻击
设计一类英雄,只能使用魔法攻击,这类英雄在LOL中被叫做AP
类:APHero
继承了Hero 类,所以继承了name,hp,armor等属性
同时,实现了AP这个接口,就必须提供AP接口中声明的方法magicAttack()
实现在语法上使用关键字 implements
5.设计一类英雄,既能进行物理攻击,又能进行魔法攻击
package
charactor;
//同时能进行物理和魔法伤害的英雄
public
class
ADAPHero
extends
Hero
implements
AD,AP{
@Override
public
void
magicAttack() {
System.out.println(
"进行魔法攻击"
);
}
@Override
public
void
physicAttack() {
System.out.println(
"进行物理攻击"
);
}
}
6.什么样的情况下使用接口
这里,只是引入了接口的概念,要真正理解接口的好处,需要更多的实践,以及在较为复杂的系统中进行大量运用之后,才能够真正理解,比如在学习了多态之后就能进一步加深理解。
对象转型
1.明确引用类型和对象类型的概念
有一个对象 new ADHero(), 同时也有一个引用ad
对象是有类型的, 是ADHero
引用也是有类型的,是ADHero
通常情况下,引用类型和对象类型是一样的
接下来要讨论的类型转换的问题,指的是引用类型和对象类型不一致的情况下的转换问题
2.子类转父类(向上转型)
所谓的转型,是指当引用类型和对象类型不一致的时候,才需要进行类型转换
类型转换有时候会成功,有时候会失败(参考基本类型的类型转换)
到底能否转换成功?
把右边的当做左边来用,看是否说得通
右边ad 引用所指向的对象的类型 是 物理攻击英雄
左边h 引用的类型 是 普通英雄
把物理攻击英雄 当做 普通英雄,说不说得通? 说得通,就可以转
所有的 子类转换为父类 ,都是说得通的。
3.父类转子类(向下转型)
父类转子类,有的时候行,有的时候不行,所以必须进行强制转换。
强制转换的意思就是 转换有风险,风险自担。
什么时候行呢?
第3行,是子类转父类,一定可以的
第4行,就是父类转子类,所以要进行强转。
h这个引用,所指向的对象是ADHero, 所以第4行,就会把ADHero转换为ADHero,就能转换成功。
什么时候转换不行呢?
第4行,是子类转父类,是可以转换成功的
第5行,是把h引用所指向的对象 Support,转换为ad引用的类型ADHero。 从语义上讲,把物理攻击英雄,当成辅助英雄来用,说不通,所以会强制转换失败,并且抛出 异常
【失败的表现形式是抛出异常 ClassCastException 类型转换异常】
4.没有继承关系的两个类,互相转换
没有继承关系的两个类,互相转换,一定会失败
5.实现类转换成接口(向上转型)
ok--能成功的
6.接口转换成实现类(向下转型)
×--不能成功
7.instanceof
instanceof Hero
判断一个引用所指向的对象,是否是Hero类型,或者Hero的子类
重写方法
子类可以继承父类的对象方法
在继承后,重复提供该方法,就叫做方法的重写
又叫覆盖 override
1.父类Item
父类Item有一个方法,叫做effect
2.子类LifePotion
子类LifePotion继承Item,同时也提供了方法effect
3.调用重写的方法
调用就会执行重写的方法,而不是从父类的方法
4.如果没用重写这样的机制怎么样?
如果没有重写这样的机制,也就是说LifePotion这个类,一旦继承了Item,所有方法都不能修改了。
但是LifePotion又希望提供一点不同的功能,为了达到这个目的,只能放弃继承Item,重新编写所有的属性和方法,然后在编写effect的时候,做一点小改动.
这样就增加了开发时间和维护成本
多态
操作符的多态
+ 可以作为算数运算,也可以作为字符串连接
类的多态
父类引用指向子类对象
1.操作符的多态
同一个操作符在不同情境下,具备不同的作用
如果+号两侧都是整型,那么+代表 数字相加
如果+号两侧,任意一个是字符串,那么+代表字符串连接
2.观察类的多态现象
都是同一个类型,调用同一个方法,却能呈现不同的状态
3.类的多态条件
要实现类的多态,需要如下条件
1. 父类(接口)引用指向子类对象
2. 调用的方法有重写
4.类的多态-不使用多态
如果不使用多态,那么就需要设计很多很多个方法
5.类的多态-使用多态
如果物品的种类特别多,那么就需要设计很多的方法
比如useArmor,useWeapon等等
这个时候采用多态来解决这个问题
设计一个方法叫做useItem,其参数类型是Item
如果是使用血瓶,调用该方法
如果是使用魔瓶,还是调用该方法
无论英雄要使用什么样的物品,只需要一个方法即可
隐藏
与重写类似,方法的重写是子类覆盖父类的对象方法
隐藏,就是子类覆盖父类的类方法
1.父类
父类有一个类方法 :battleWin
System.out.println(
"hero battle win"
);
2.子类隐藏父类的方法
【相当于“对象方法”的“重写”】
子类隐藏父类的方法:
System.out.println(
"ad hero battle win"
);
super
1.准备一个显式提供无参构造方法的父类
在实例化Hero对象的时候,其构造方法会打印
“Hero的构造方法 "
2.实例化子类,父类的构造方法一定会被调用
实例化一个ADHero(), 其构造方法会被调用
其父类的构造方法也会被调用
并且是父类构造方法先调用
子类构造方法会默认调用父类的 无参的构造方法
3.父类显式提供两个构造方法
分别是无参的构造方法和带一个参数的构造方法
public
Hero(){
System.out.println(
"Hero的无参的构造方法 "
);
}
public
Hero(String name){
System.out.println(
"Hero的有一个参数的构造方法 "
);
this
.name = name;
}
4.子类显式调用父类带参构造方法
使用关键字super 显式调用父类带参的构造方法
public
ADHero(String name){
super
(name);
System.out.println(
"AD Hero的构造方法"
);
}
5.调用父类属性
通过super调用父类的moveSpeed属性
ADHero也提供了属性moveSpeed
6.调用父类方法
ADHero重写了useItem方法,并且在useItem中通过super调用父类的useItem方法
练习:
父类Hero提供了一个有参的构造方法:
但是没有提供无参的构造方法
子类应该怎么处理?
答:
作为子类,无论如何 都会调用父类的构造方法。
默认情况下,会调用父类的无参的构造方法。
但是,当父类没有无参构造方法的时候( 提供了有参构造方法,并且不显示提供无参构造方法),子类就会抛出异常,因为它尝试去调用父类的无参构造方法。
这个时候,必须通过super去调用父类声明的,存在的,有参的构造方法
Object类
1.Object类是所有类的父类
声明一个类的时候,默认是继承了Object
public class Hero extends Object
2.toString()
Object类提供一个toString方法,所以所有的类都有toString方法
toString()的意思是返回当前对象的字符串表达
通过 System.out.println 打印对象就是打印该对象的toString()返回值
3.finalize()
当一个对象没有任何引用指向的时候,它就满足垃圾回收的条件
当它被垃圾回收的时候,它的finalize() 方法就会被调用。
finalize() 不是开发人员主动调用的方法,而是由虚拟机JVM调用的。
4.equals()
equals() 用于判断两个对象的内容是否相同
5.==
这不是Object的方法,但是用于判断两个对象是否相同
更准确的讲,用于判断两个引用,是否指向了同一个对象
6.hashcode()
hashCode方法返回一个对象的哈希值
7.线程同步相关方法
wait()
notify()
notifyAll()
8.getClass()
getClass()会返回一个对象的类对象
final
final修饰类,方法,基本类型变量,引用的时候分别有不同的意思。
1.final修饰类
当Hero被修饰成final的时候,表示Hero不能够被继承
其子类会出现编译错误
2.final修饰方法
Hero的useItem方法被修饰成final,那么该方法在ADHero中,不能够被重写
3.final修饰基本类型变量
final修饰基本类型变量,表示该变量只有一次赋值机会 、
4.final修饰引用
h引用被修饰成final,表示该引用只有1次指向对象的机会
5.常量
常量指的是可以公开,直接访问,不会变化的值
练习:
设计一个类SomeString,继承 String类。 能否继承?
答:
String被设计为final是有原因的,因为String这个类使用得实在是太广泛了,如果能够被继承,就意味着其子类可以随意重写其非final的方法,这些方法就有可能变得和期望的不一样,比如toString始终返回空。 这样就给软件逻辑带来了很大的不确定性因素。
为了规避这种不确定性因素,索性让String类不能被继承,间接地就不存在子类重写其方法的问题了。
这样的方法就叫抽象方法,使用修饰符“abstract"
当一个类有抽象方法的时候,该类必须被声明为抽象类
APHero,ADHero,ADAPHero是Hero的子类,继承了Hero的属性和方法。
但是各自的攻击手段是不一样的,所以继承Hero类后,这些子类就必须提供不一样的attack方法实现。
一旦一个类被声明为抽象类,就不能够被直接实例化
子类只能继承一个抽象类,不能继承多个
子类可以实现多个接口
区别2:
抽象类可以定义
public,protected,package,private
静态和非静态属性
final和非final属性
但是接口中声明的属性,只能是
public
静态
final的
即便没有显示的声明
注: 抽象类和接口都可以有实体方法。 接口中的实体方法,叫做默认方法
非静态内部类
静态内部类
匿名类
本地类
非静态内部类可以直接在一个类里面定义
比如:
战斗成绩只有在一个英雄对象存在的时候才有意义
所以实例化BattleScore 的时候,必须建立在一个存在的英雄的基础上
语法: new 外部类().new 内部类()
作为Hero的非静态内部类,是可以直接访问外部类的private实例属性name的
比如敌方水晶,当敌方水晶没有血的时候,己方所有英雄都取得胜利,而不只是某一个具体的英雄取得胜利。
与非静态内部类不同,静态内部类水晶类的实例化 不需要一个外部类的实例为基础,可以直接实例化
语法:new 外部类.静态内部类();
因为没有一个外部类的实例,所以在静态内部类里面不可以访问外部类的实例属性和方法
除了可以访问外部类的私有静态成员外,静态内部类和普通类没什么大的区别
通常情况下,要使用一个接口或者抽象类,都必须创建一个子类
有的时候,为了快速使用,直接实例化一个抽象类,并“ 当场 ”实现其抽象方法。
既然实现了抽象方法,那么就是一个新的类,只是这个类,没有命名。
这样的类,叫做匿名类
与内部类不一样的是,内部类必须声明在成员的位置,即与属性和方法平等的位置。
本地类和匿名类一样,直接声明在代码块里面,可以是主方法,for循环里等等地方
Mortal 这个接口,增加了一个默认方法 revive,这个方法有实现体,并且被声明为了default
但是引入了默认方法后,原来的类,不需要做任何改动,并且还能 得到 这个默认方法
通过这种手段,就能够很好的扩展新的类,并且做到不影响原来的类
为AP接口也增加一个默认方法 attack()
从而免去到底调用哪个接口的attack方法这个模棱两可的问题