文章目录
接口
接口就像是一种约定,我们约定某些英雄是物理系英雄,那么他们就一定能够进行物理攻击。
在设计LOL的时候,进攻类英雄有两种,一种是进行物理系攻击,一种是进行魔法系攻击
这时候,就可以使用接口来实现这个效果
创建接口
创建一个接口 File->New->Interface
AD
,声明一个方法 physicAttack
物理攻击,但是没有方法体,是一个“空”
方法
package charactor;
public interface AD {
//物理伤害
public void physicAttack();
}
设计一类英雄,能够使用物理攻击
设计一类英雄,能够使用物理攻击,这类英雄在LOL中被叫做AD
类:ADHero
继承了Hero
类,所以继承了name,hp,armor
等属性
实现某个接口,就相当于承诺了某种约定。所以,实现了AD这个接口,就必须提供AD接口中声明的方法physicAttack()
实现在语法上使用关键字 implements
package j2se;
public class ADHero extends Hero implements AD{
//父类Hero只有有参构造方法,继承类就需要显式声明重写父类构造函数
public ADHero(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public void physicAttack(){
System.out.println("进行了物理攻击");
}
}
魔法攻击接口
package charactor;
public interface AP {
public void magicAttack();
}
设计一类英雄,只能使用魔法攻击
继承了Hero
类,所以继承了name,hp,armor
等属性
同时,实现了AP这个接口,就必须提供AP接口中声明的方法magicAttack()
。实现在语法上使用关键字 implement
package j2se;
public class APHero extends Hero implements AP {
public APHero(String name){
super(name);
}
@Override
public void magicAttack(){
System.out.println("进行了魔法攻击");
}
}
设计一类英雄,既能进行物理攻击,又能进行魔法攻击
package j2se;
public class ADAPHero extends Hero implements AD,AP{
public ADAPHero(String name){
super(name);
}
@Override
public void physicAttack(){
System.out.println("进行了物理攻击");
}
@Override
public void magicAttack(){
System.out.println("进行了魔法攻击");
}
}
使用接口
与类不同,接口不能new
,不能直接创建一个借接口对象,对象只能通过类来创建。但是可以声明接口类型的变量,引用实现了接口的类对象:
AD hero1 = new ADHero("hanbing");
System.out.println(hero1.physicAttack());
hero1是AD的类型变量,但是引用了ADHero类型的对象,之所以能赋值,是因为ADHero实现了AD接口,如果一个类型实现了多个接口,那么这种类型的对象就可以被赋值给任意接口类型的变量。
hero1可以调用AD接口的方法,也只能调用AD接口的方法(不能调用ADHero类中的其他实例或者类方法),实际执行时,执行的是具体实现类的代码。如下:
package lianxi;
public class ADHero implements AD{
protected String name;
public ADHero(String name) {
this.name = name;
}
public static void test(){
System.out.println("ADHero test");
}
@Override
public void physicAttack(){
System.out.println("进行了物理攻击");
}
public static void main(String[] args) {
AD hero1 = new ADHero("hb");
hero1.physicAttack();//执行ADHero中的physicAttack方法
hero1.test();//错误
}
}
使用接口的目的是:降低耦合,提高灵活性
什么样的情况下该使用接口?
如上的例子,似乎要接口,不要接口,都一样的,那么接口的意义是什么呢
学习一个知识点,是由浅入深得进行的。 这里呢,只是引入了接口的概念,要真正理解接口的好处,需要更多的实践,以及在较为复杂的系统中进行大量运用之后,才能够真正理解,比如在学习了多态之后就能进一步加深理解。
练习-接口
设计一个治疗者接口:Healer
该接口声明有方法: heal()
设计一个Support
类,代表辅助英雄,继承Hero
类,同时实现了Healer
这个接口
package j2se;
public class Supper extends Hero implements Healer {
public Supper(String name){
super(name);
}
@Override
public void heal(){
System.out.println(this.name+"加了点血");
}
public static void main(String[] args) {
Supper ad=new Supper("teemo");//实例化对象,调用构造方法
ad.heal();
}
}
对象转型
父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的属性或方法,父类引用无法调用。子类重定义父类已有的属性,父类引用调用的还是本类的属性值。子类重定义父类已有的方法时,父类引用调用的是子类重写过的方法(这里只能调用实例方法,因为其实动态绑定的)。
明确引用类型与对象类型的概念
首先,明确引用类型与对象类型的概念
在这个例子里,有一个对象 new ADHero(),
同时也有一个引用ad
对象是有类型的, 是ADHero
引用也是有类型的,是ADHero
通常情况下,引用类型和对象类型是一样的
接下来要讨论的类型转换的问题,指的是引用类型和对象类型不一致的情况下的转换
package charactor;
public class Hero {
public String name;
protected float hp;
public static void main(String[] args) {
ADHero ad = new ADHero();
}
}
所谓的转型,是指当引用类型和对象类型不一致的时候,才需要进行类型转换。类型转换有时候会成功,有时候会失败
判别到底能否转换成功?
把右边的当做左边来用,看能否说的通
子类转父类(向上转型)
Hero h = new Hero();
ADHero ad = new ADHero();
h = ad;
右边ad
引用所指向的对象的类型是 物理攻击英雄
左边h
引用的类型是 普通英雄
把物理攻击英雄 当做 普通英雄,说不说得通? 说得通,就可以转
所有的子类转换为父类,都是说得通的
父类转子类(向下转型)
总结:子类先指向父类,然后再把这个子类转子类就可以转
父类转子类,有的时候行,有的时候不行,所以必须进行强制转换。
强制转换的意思就是 转换有风险,风险自担。
什么时候可以?
1. Hero h =new Hero();
2. ADHero ad = new ADHero();
3. h = ad;
4. ad = (ADHero) h;
5. 'ad是ADHero类型的,也是Hero类型的'
6. "因为类型转换,所以h是ADHero类型的,也是Hero类型的。如果没有第三句 h就不是ADHero类型的"
第3行,是子类转父类,一定可以的
第4行,就是父类转子类,所以要进行强转。
h
这个引用,所指向的对象是ADHero
, 所以第4行,就会把ADHero
转换为ADHero
,就能转换成功。
什么时候转换不行呢?
Base();//父类
Child();//子类
Base b = new Base();
Child c = Child(b);//这样是不行的
语法上java不会报错,但是运行时会抛出出错,错误为类型转换异常。
一个父类的变量能不能转换为一个子类的变量,取决于这个父类变量的动态类型(即引用的对象类型)是不是这个子类或者这个子类的
总结
可以通过instanceof关键字判断给的一个父类变量能不能转换为一个子类的变量,从而安全地进行类型转换。
package charactor;
public class Hero {
public String name;
protected float hp;
public boolean equals(Object o){//这里是传入Object类型或其子类型的对象(new name(),把o引用指向object对象或者子类对象)
if(o instanceof Hero){
Hero h = (Hero) o;
return this.hp == h.hp;
}
return false;
}
public static void main(String[] args) {
Hero h1= new Hero();
h1.hp = 300;
Hero h2= new Hero();
h2.hp = 400;
Hero h3= new Hero();
h3.hp = 300;
System.out.println(h1.equals(h2));
System.out.println(h1.equals(h3));
}
}
public boolean equals(Object o){//这里是传入Object类型或其子类型的对象(new name(),把o引用指向object对象或者子类对象)
if(o instanceof Hero){
Hero h = (Hero) o;
return this.hp == h.hp;
}
return false;
}
其中:
public boolean equals(Object o)
这里是传入Object类型或其子类型的对象(new name(),把o引用指向object对象或者子类对象),object o = new name()
,子类转父类一定可以 。设置为object
,就不会出现传入错误的情况。如果是(ADHero o)
,则传入new Hero()
就会出错if(o instanceof Hero)
判断传入的对象是不是Hero类型,或其子类型的Hero h = (Hero) o;
(Hero) o
是强制 父类转子类,其之所以成功。是应为object
的引用o
指向了传入的对象new name()
,而传入的对象通过上一步的判断,其是Hero类或Hero类的子类return this.hp == h.hp;
,h.hp
是访问的传入对象的hp
,父类引用指向子类对象,引用访问方法时,如果子类有重写方法,则访问子类的方法。但是不能访问子类中新加的属性和方法,也就是说在父类中没有定义的属性和方法。
没有继承关系的两个类,互相转换,一定会失败
虽然ADHero
和APHero
都继承了Hero
,但是彼此没有互相继承关系
比如:“把魔法英雄当做物理英雄来用”,在语义上也是说不通的
实现类转换成接口(向上转型)
总结:也就是说有实现接口方法的类类型对象才能转换为接口,接口直接转换为类类型会失败,需要先转换
引用ad指向的对象是ADHero类型,这个类型实现了AD接口
10行: 把一个ADHero类型转换为AD接口
从语义上来讲,把一个ADHero当做AD来使用,而AD接口只有一个physicAttack
方法,这就意味着转换后就有可能要调用physicAttack
方法,而ADHero
一定是有physicAttack
方法的,所以转换是能成功的。(转换为AP接口就不行)
package charactor;
public class Hero {
public String name;
protected float hp;
public static void main(String[] args) {
ADHero ad = new ADHero();
AD adi = ad;
}
}
接口转换成实现类(向下转型)
10行:
ad
引用指向ADHero
, 而adi
引用是接口类型:AD
,实现类转换为接口,是向上转型,所以无需强制转换,并且一定能成功
12行:
adi
实际上是指向一个ADHero
的,所以能够转换成功
14行:
adi
引用所指向的对象是一个ADHero
,要转换为ADAPHero
就会失败。(假设能够转换成功,那么就可以使用magicAttack方法,而adi引用所指向的对象ADHero是没有magicAttack方法的。)
instanceof
instanceof Hero
判断一个引用所指向的对象,是否是Hero类型,或者Hero的子类
System.out.println(h1 instanceof ADHero);
Hero h = ad 这一行是没错 是由子转父,
AD adi = (AD) h; 这一行是因为h指向ADHero,强行转为AD也是可行的,最后达到父转子
APHero ap = (APHero) adi; 这个adi是指向AD的,不能强行转向APhero,所以这里有问题
重写
子类可以继承父类的对象方法
在继承后,重复提供该方法,就叫做方法的重写
又叫覆盖 override
比如:父类Item
有个方法
package j2se;
public class Item {
String name;
int price;
public void buy(){
System.out.println("购买");
}
public void effect(){
System.out.println("物品使用后,可以有效果");
}
public static void main(String[] args) {
Item bloodBottle = new Item();
bloodBottle.name = "血瓶";
bloodBottle.price = 50;
Item shoots = new Item();
shoots.name = "草鞋";
shoots.price = 300;
Item sword = new Item();
sword.name = "长剑";
sword.price = 350;
bloodBottle.effect();
'子类调用effect方法,就会执行重写的方法,而不是从父类的方法'
LifePotion lp = new LifePotion();
lp.effect();
}
}
package j2se;
public class LifePotion extends Item{
'重写父类的effect方法'
public void effect(){
System.out.println("血瓶使用后,可以回血");
}
}
如果没有重写这样的机制,也就是说LifePotion这个类,一旦继承了Item,所有方法都不能修改了。
但是LifePotion又希望提供一点不同的功能,为了达到这个目的,只能放弃继承Item,重新编写所有的属性和方法,然后在编写effect的时候,做一点小改动.
这样就增加了开发时间和维护成本
多态
操作符的多态
+
可以作为算数运算,也可以作为字符串连接
类的多态
父类引用指向子类对象
操作符的多态
同一个操作符在不同情境下,具备不同的作用
如果+
号两侧都是整型,那么+
代表 数字相加
如果+
号两侧,任意一个是字符串,那么+
代表字符串连接
类的多态
观察类的多态现象
观察类的多态现象:
i1
和i2
都是Item类型- 都调用effect方法
- 输出不同的结果
多态: 都是同一个类型,调用同一个方法,却能呈现不同的状态
package property;
public class Item {
String name;
int price;
public void buy(){
System.out.println("购买");
}
public void effect() {
System.out.println("物品使用后,可以有效果 ");
}
public static void main(String[] args) {
Item i1= new LifePotion();
Item i2 = new MagicPotion();
System.out.print("i1 是Item类型,执行effect打印:");
i1.effect();
System.out.print("i2也是Item类型,执行effect打印:");
i2.effect();
}
}
类的多态条件以及作用
要实现类的多态,需要如下条件
- 父类(接口)引用指向子类对象
- 调用的方法有重写
那么多态有什么作用是什么?
不使用多态时:
如果不使用多态,
假设英雄要使用血瓶和魔瓶,就需要为Hero设计两个方法
useLifePotion
、useMagicPotion
除了血瓶和魔瓶还有很多种物品,那么就需要设计很多很多个方法,比如
usePurityPotion
净化药水
useGuard
守卫
useInvisiblePotion
使用隐形药水
等等等等
package charactor;
import property.LifePotion;
import property.MagicPotion;
public class Hero {
public String name;
protected float hp;
public void useLifePotion(LifePotion lp){
lp.effect();
}
public void useMagicPotion(MagicPotion mp){
mp.effect();
}
public static void main(String[] args) {
Hero garen = new Hero();
garen.name = "盖伦";
LifePotion lp =new LifePotion();
MagicPotion mp =new MagicPotion();
garen.useLifePotion(lp);
garen.useMagicPotion(mp);
}
}
使用多态
如果物品的种类特别多,那么就需要设计很多的方法
比如useArmor
,useWeapon
等等
这个时候采用多态来解决这个问题
设计一个方法叫做useItem
,其参数类型是Item
(是为了让父引用指向子类对象)
如果是使用血瓶,调用该方法
如果是使用魔瓶,还是调用该方法
无论英雄要使用什么样的物品,只需要一个方法即可
package charactor;
import property.Item;
import property.LifePotion;
import property.MagicPotion;
public class Hero {
public String name;
protected float hp;
public void useItem(Item i){
i.effect();
}
public static void main(String[] args) {
Hero garen = new Hero();
garen.name = "盖伦";
LifePotion lp =new LifePotion();
MagicPotion mp =new MagicPotion();
garen.useItem(lp);'这里有一个子类型向父类转换Item i = lp'
garen.useItem(mp);
}
}
练习:
immortal是不朽的,不死的意思
mortal就是终有一死的,凡人的意思
- 设计一个接口
接口叫做Mortal,其中有一个方法叫做die - 实现接口
分别让ADHero,APHero,ADAPHero这三个类,实现Mortal接口,不同的类实现die方法的时候,都打印出不一样的字符串 - 为Hero类,添加一个方法,在这个方法中调用 m的die方法。
public void kill(Mortal m)
- 在主方法中
首先实例化出一个Hero对象:盖伦
然后实例化出3个对象,分别是ADHero,APHero,ADAPHero的实例
然后让盖伦 kill 这3个对象
public class Hero {
String name; //姓名
float hp; //血量
float armor; //护甲
int moveSpeed; //移动速度
//带一个参数的构造方法
public Hero(String name){
System.out.println("创建"+ name);
this.name = name;
}
public void kill(mortal m){
m.die();
}
//带两个参数的构造方法
/* public Hero(String name,float hp){
this(name);
System.out.println("两个参数的构造方法");
this.hp = hp;
}*/
public static void main(String[] args) {
// Hero teemo = new Hero("提莫",383);
Hero teemo = new Hero("提莫");
ADHero hanbing = new ADHero("寒冰");
APHero Karsatin = new APHero("卡萨丁");
ADAPHero EZ = new ADAPHero("EZ");
// System.out.println(teemo.name);
teemo.kill(hanbing);
teemo.kill(Karsatin);
teemo.kill(EZ);
}
package j2se;
public class ADHero extends Hero implements AD,mortal{
public ADHero(String name) {
super(name);
// TODO Auto-generated constructor stub
}
@Override
public void physicAttack(){
System.out.println("进行了物理攻击");
}
@Override
public void die(){
System.out.println(this.name +"被杀死了");
}
}
多态和继承的区别
看上例中继承和多态的掺杂,就弄混了。
继承是子类使用父类的方法和属性(除了private),而多态是,通过子类重写父类的方法,在父类中通过父类引用指向子类实例调用子类的方法。
继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。
多态与继承、方法重写密切相关,我们在方法中接收父类类型作为参数,在方法实现中调用父类类型的各种方法。当把子类作为参数传递给这个方法时,java虚拟机会根据实际创建的对象类型,调用子类中相应的方法(存在方法重写时)。
多态性是指允许不同类bai的对象对同一消息作出响应。多态性du包括参数化多态性和包含zhi多态性。多态性语言具有灵活、dao抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
多态的表现形式有重载和覆盖两种形式。
重载(overload),是发生在同一类中。与什么父类子类、继承毫无关系。
标识一个函数除了函数名外,还有函数的参数(个数和类型)。也就是说,一个类中可以有两个或更多的函数,叫同一个名字而他们的参数不同。
他们之间毫无关系,是不同的函数,只是可能他们的功能类似,所以才命名一样,增加可读性,仅此而已!
再说覆盖(override),是发生在子类中!也就是说必须有继承的情况下才有覆盖发生。
我们知道继承一个类,也就有了父类了全部方法,如果你感到哪个方法不爽,功能要变,那就把那个函数在子类中重新实现一遍。
这样再调用这个方法的时候,就是执行子类中的过程了。父类中的函数就被覆盖了。(当然,覆盖的时候函数名和参数要和父类中完全一样,不然你的方法对父类中的方法就不起任何作用,因为两者是两个函数,毫不关系)
在java中
高内聚,低耦合。
继承就可以降低代码的耦合,这样编写代码,在你以后修改一处时,就不会牵扯很多的累,方便以后的修改和升级。
隐藏
与重写类似,方法的重写是子类覆盖父类的对象方法
隐藏,就是子类覆盖父类的类方法
父类有一个类方法 :battleWin
package charactor;
public class Hero {
public String name;
protected float hp;
//类方法,静态方法
//通过类就可以直接调用
public static void battleWin(){
System.out.println("hero battle win");
}
}
子类隐藏父类的类方法
package charactor;
public class ADHero extends Hero implements AD{
@Override
public void physicAttack() {
System.out.println("进行物理攻击");
}
//隐藏父类的battleWin方法
public static void battleWin(){
System.out.println("ad hero battle win");
}
public static void main(String[] args) {
Hero.battleWin();
ADHero.battleWin();
}
}