八.接口与继承
1.接口的定义为的关键字为interface 2.接口中只能有方法和变量,变量为public static final类型,方法只能被可以为public(默认)和protected修饰,方法不能有具体的实现 3.一个类可以实现多个接口 4.一个类实现接口,必须要实现接口的所有的方法
1.接口
接口就像是一种约定,我们约定某些英雄是物理系英雄,那么他们就一定能够进行物理攻击。 实现某个接口,就相当于承诺了某种约定 什么样的情况下该使用接口? 如上的例子,似乎要接口,不要接口,都一样的,那么接口的意义是什么呢 学习一个知识点,是由浅入深得进行的。 这里呢,只是引入了接口的概念,要真正理解接口的好处,需要更多的实践,以及在较为复杂的系统中进行大量运用之后,才能够真正理解,比如在学习了多态之后就能进一步加深理解 代码示例: // 接口 public interface AP { public void magicAttack(); } // 实现 public class APHero extends Hero implements AP{ @Override public void magicAttack() { System.out.println("进行魔法攻击"); } }
2.对象转型
1.明确引用类与对象类型的概念
首先,明确引用类型与对象类型的概念 在这个例子里,有一个对象 new ADHero(), 同时也有一个引用ad 对象是有类型的, 是ADHero 引用也是有类型的,是ADHero 通常情况下,引用类型和对象类型是一样的 接下来要讨论的类型转换的问题,指的是引用类型和对象类型不一致的情况下的转换问题
[图片上传失败...(image-6ce689-1585128357168)]
public class Hero { public String name; protected float hp; public static void main(String[] args) { ADHero ad = new ADHero(); } } ``` #### 2.子类转父类(向上转型)
所谓的转型,是指当引用类型和对象类型不一致的时候,才需要进行类型转换
类型转换有时候会成功,有时候会失败(参考基本类型的类型转换)
到底能否转换成功? 教大家一个很简单的判别办法
把右边的当做左边来用,看说得通不
右边ad引用所指向的对象的类型是 物理攻击英雄
左边h引用的类型是 普通英雄
把物理攻击英雄 当做 普通英雄,说不说得通? 说得通,就可以转
所有的子类转换为父类,都是说得通的。比如你身边的例子
苹果手机 继承了 手机,把苹果手机当做普通手机使用
怡宝纯净水 继承了 饮品, 把怡宝纯净水 当做饮品来使用[图片上传失败...(image-d96d98-1585128357168)] ```java public class Hero { public String name; protected float hp; public static void main(String[] args) { Hero h = new Hero(); ADHero ad = new ADHero(); //类型转换指的是把一个引用所指向的对象的类型,转换为另一个引用的类型 //把ad引用所指向的对象的类型是ADHero //h引用的类型是Hero //把ADHero当做Hero使用,一定可以 h = ad; } } ``` #### 3.父类转子类(向下转型) 父类转子类,有的时候行,有的时候不行,所以必须进行强制转换。 强制转换的意思就是 转换有风险,风险自担。 什么时候行? ```java 1. Hero h =new Hero(); 2. ADHero ad = new ADHero(); 3. h = ad; 4. ad = (ADHero) h;
第3行,是子类转父类,一定可以的
第4行,就是父类转子类,所以要进行强转。
h这个引用,所指向的对象是ADHero, 所以第4行,就会把ADHero转换为ADHero,就能转换成功。什么时候不行?
1. Hero h =new Hero(); 2. ADHero ad = new ADHero(); 3. Support s =new Support(); 4. h = s; 5. ad = (ADHero)h;
第4行,是子类转父类,是可以转换成功的
第5行,是把h引用所指向的对象 Support,转换为ad引用的类型ADHero。 从语义上讲,把物理攻击英雄,当成辅助英雄来用,说不通,所以会强制转换失败,并且抛出异常以下是对完整的代码的关键行分析
14行: 把ad当做Hero使用,一定可以
转换之后,h引用指向一个ad对象
15行: h引用有可能指向一个ad对象,也有可能指向一个support对象
所以把h引用转换成AD类型的时候,就有可能成功,有可能失败
因此要进行强制转换,换句话说转换后果自负
到底能不能转换成功,要看引用h到底指向的是哪种对象
在这个例子里,h指向的是一个ad对象,所以转换成ADHero类型,是可以的
16行:把一个support对象当做Hero使用,一定可以
转换之后,h引用指向一个support对象
17行:这个时候,h指向的是一个support对象,所以转换成ADHero类型,会失败。
失败的表现形式是抛出异常 ClassCastException 类型转换异常[图片上传失败...(image-d4f480-1585128357168)]
package charactor; import charactor1.Support; public class Hero { public String name; protected float hp; public static void main(String[] args) { Hero h =new Hero(); ADHero ad = new ADHero(); Support s =new Support(); h = ad; ad = (ADHero) h; h = s; ad = (ADHero)h; } }
4.没有继承关系的两个类,互相转换
没有继承关系的两个类,互相转换,一定会失败
虽然ADHero和APHero都继承了Hero,但是彼此没有互相继承关系
"把魔法英雄当做物理英雄来用",在语义上也是说不通的[图片上传失败...(image-1b916e-1585128357168)]
public class Hero { public String name; protected float hp; public static void main(String[] args) { ADHero ad = new ADHero(); APHero ap = new APHero(); // 没有继承关系的类型进行互相转换一定会失败,所以会出现编译错误 ad = (ADHero) ap; } }
#### 5.实现类转换成接口(向上转型) 引用ad指向的对象是ADHero类型,这个类型实现了AD接口
10行: 把一个ADHero类型转换为AD接口
从语义上来讲,把一个ADHero当做AD来使用,而AD接口只有一个physicAttack方法,这就意味着转换后就有可能要调用physicAttack方法,而ADHero一定是有physicAttack方法的,所以转换是能成功的。[图片上传失败...(image-422689-1585128357168)] ```java public class Hero {
public String name;
protected float hp;public static void main(String[] args) {
ADHero ad = new ADHero();
AD adi = ad;
}
}#### 6.接口转换成实现类(向下转型) 10行: ad引用指向ADHero, 而adi引用是接口类型:AD,实现类转换为接口,是向上转型,所以无需强制转换,并且一定能成功 12行: adi实际上是指向一个ADHero的,所以能够转换成功 14行: adi引用所指向的对象是一个ADHero,要转换为ADAPHero就会失败。 假设能够转换成功,那么就可以使用magicAttack方法,而adi引用所指向的对象ADHero是没有magicAttack方法的。 [图片上传失败...(image-cd4e55-1585128357168)] ```java public class Hero { public String name; protected float hp; public static void main(String[] args) { ADHero ad = new ADHero(); AD adi = ad; ADHero adHero = (ADHero) adi; ADAPHero adapHero = (ADAPHero) adi; adapHero.magicAttack(); } }
#### 7.instanceof instanceof Hero 判断一个引用所指向的对象,是否是Hero类型,或者Hero的子类 ```java public class Hero { public String name; protected float hp; public static void main(String[] args) { ADHero ad = new ADHero(); APHero ap = new APHero(); Hero h1= ad; Hero h2= ap; //判断引用h1指向的对象,是否是ADHero类型 System.out.println(h1 instanceof ADHero); //判断引用h2指向的对象,是否是APHero类型 System.out.println(h2 instanceof APHero); //判断引用h1指向的对象,是否是Hero的子类型 System.out.println(h1 instanceof Hero); }
}
```
3.重写
子类可以继承父类的对象方法
在继承后,重复提供该方法,就叫做方法的重写
又叫覆盖 override
子类继承了父类,即子类也继承了父类的方法,子类可以重写父类的方法,来达到自己的目的, 如果没有重写这样的机制,也就是说LifePotion这个类,一旦继承了Item,所有方法都不能修改了。 ,这样增加了开发时间和维护成本
// 父类 public class Item { String name; int price; public void buy(){ System.out.println("购买"); } public void effect() { System.out.println("物品使用后,可以有效果"); } } // 子类 public class LifePotion extends Item{ // 重写父类的方法 public void effect(){ System.out.println("血瓶使用后,可以回血"); } }
4.多态
操作符的多态:
+可以作为算数运算,也可以作为字符串连接
类的多态:
父类引用指向子类对象(核心)
示例1:操作符的多态
同一个操作符在不同情境下,具备不同的作用 如果+号两侧都是整型,那么+代表 数字相加 如果+号两侧,任意一个是字符串,那么+代表字符串连接 public class Hero { public String name; protected float hp; public static void main(String[] args) { int i = 5; int j = 6; int k = i+j; //如果+号两侧都是整型,那么+代表 数字相加 System.out.println(k); int a = 5; String b = "5"; String c = a+b; //如果+号两侧,任意一个是字符串,那么+代表字符串连接 System.out.println(c); } }
示例2:观察类的多态现象
观察类的多态现象: 1. i1和i2都是Item类型 2. 都调用effect方法 3. 输出不同的结果 多态: 都是同一个类型,调用同一个方法,却能呈现不同的状态
//------------------父类------------- 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(); } } // ---------------------血瓶--------- public class LifePotion extends Item { public void effect(){ System.out.println("血瓶使用后,可以回血"); } } // ----------------------蓝瓶--------- public class MagicPotion extends Item{ public void effect(){ System.out.println("蓝瓶使用后,可以回魔法"); } }
示例3:类的多态条件
要实现类的多态,需要如下条件 1. 父类(接口)引用指向子类对象 2. 调用的方法有重写
示例4:类的多态比较-不使用多态
如果不使用多态, 假设英雄要使用血瓶和魔瓶,就需要为Hero设计两个方法 useLifePotion useMagicPotion 除了血瓶和魔瓶还有很多种物品,那么就需要设计很多很多个方法,比如 usePurityPotion 净化药水 useGuard 守卫 useInvisiblePotion 使用隐形药水 等等等等 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); } }
示例5:类的多态比较-使用多态
如果物品的种类特别多,那么就需要设计很多的方法 比如useArmor,useWeapon等等 这个时候采用多态来解决这个问题 设计一个方法叫做useItem,其参数类型是Item 如果是使用血瓶,调用该方法 如果是使用魔瓶,还是调用该方法 无论英雄要使用什么样的物品,只需要一个方法即可 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); garen.useItem(mp); } }
5.隐藏
与重写类似,方法的重写是子类覆盖父类的对象方法
隐藏,就是子类覆盖父类的类方法
// 父类有一个类方法 :battleWin public class Hero { public String name; protected float hp; //类方法,静态方法 //通过类就可以直接调用 public static void battleWin(){ System.out.println("hero battle win"); } } // 子类隐藏父类的类方法 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(); } }
6.super
super关键字调用父类的构造方法,如果super()里面什么都没有则调用父类的无参构造方法,如果其中有参数,则调用父类对应的构造方法,
当一个类继承一个父类的时候,那这个子类实例化时会默认调用父类的无参构造方法,若有参数,则调用自己本身的对应构造方法和父类的无参构造方法,若想调用父类的有参构造方法时,则需要在自身有参构造方法中加入super("参数"),即可完成调用
super.属性:调用父类属性
super():调用父类的构造方法
super.方法名;调用父类的方法
this.属性:调用自己的属性
this() :调用自身的构造方法
this.方法名:调用自身的方法
public class Hero { // 无参构造方法 public Hero() { System.out.println("这是父类无参构造方法"); } // 含参构造方法 public Hero(String name) { System.out.println("这是父类含参构造方法!"+name); } public void attack() { } }
public class Keyword_Super extends Hero{ public Keyword_Super() { System.out.println("子类无参构造方法"); } public Keyword_Super(String name) { super(name); // this(); // 调用自身的无参构造方法 System.out.println("子类含参构造方法"); } public static void main(String args[]) { new Keyword_Super(); new Keyword_Super("mhq"); } }
7.Object()
// 步骤1:Object是所有类的父类 声明一个类的时候,默认是继承Object eg:public class Hero extends Object // 步骤2:toString() Object类提供一个toString方法,所以所有的类都有toString方法 toString()的意思是返回当前对象的字符串表达 通过 System.out.println 打印对象就是打印该对象的toString()返回值 // 步骤3:finalize() 当一个对象没有任何引用指向的时候,它就满足垃圾回收的条件 当它被垃圾回收的时候,它的finalize() 方法就会被调用。 finalize() 不是开发人员主动调用的方法,而是由虚拟机JVM调用的。 注意:这个一定不是说,有垃圾就会回收,而是多到一定的量的时候才会回收 代码实现: public class Hero { public String name; protected float hp; public String toString(){ return name; } public void finalize(){ System.out.println("这个英雄正在被回收"); } public static void main(String[] args) { //只有一引用 Hero h; for (int i = 0; i < 100000; i++) { //不断生成新的对象 //每创建一个对象,前一个对象,就没有引用指向了 //那些对象,就满足垃圾回收的条件 //当,垃圾堆积的比较多的时候,就会触发垃圾回收 //一旦这个对象被回收,它的finalize()方法就会被调用 h = new Hero(); } } } // 步骤4:equals() equals() 用于判断两个对象的内容是否相同,比如两个人的钱一样多,那就可以相等 // 步骤5:== 这不是Object的方法,但是用于判断两个对象是否相同 更准确的讲,用于判断两个引用,是否指向了同一个对象 代码实现: public class Test { public String name; protected float hp; public boolean equals(Object o){ if(o instanceof Test){ Test h = (Test) o; return this.hp == h.hp; } return false; } public static void main(String[] args) { Test h1= new Test(); h1.hp = 300; Test h2= new Test(); h2.hp = 400; Test h3= new Test(); h3.hp = 300; System.out.println(h1.equals(h2)); System.out.println(h1.equals(h3)); System.out.println("------------------"); System.out.println(h1==h2); System.out.println(h1==h3); } } // 步骤6:hashCode() hashCode方法返回一个对象的哈希值 // 步骤7:线程同步相关方法 Object还提供线程同步相关方法 wait() notify() notifyAll() // 步骤8:getClass() getClass()会返回一个对象的类对象,属于高级内容,不适合初学者过早接触,关于 类对象的详细内容请参考反射机制
8.final
示例1:final修饰类
当Hero被修饰成final的时候,表示Hero不能够被继承
示例2:final修饰方法
方法被修饰成final,那么该方法在其子类中,不能够被修改重写(不能体现多态性)
示例3:final修饰基本类型变量
final修饰基本类型变量,表示该变量只有一次赋值机会
示例4:final修饰引用
final修饰引用 h引用被修饰成final,表示该引用只有1次指向对象的机会 public class Hero extends Object { String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 public static void main(String[] args) { final Hero h; h =new Hero(); // 可以被引用 h =new Hero(); // 被final修饰不能有第二次引用 h.hp = 5; } }
示例5:常量
常量指的是可以公开,直接访问,不会变化的值 比如 itemTotalNumber 物品栏的数量是6个 public static final int itemTotalNumber = 6; public class Hero extends Object { public static final int itemTotalNumber = 6;//物品栏的数量 String name; //姓名 float hp; //血量 float armor; //护甲 int moveSpeed; //移动速度 public static void main(String[] args) { final Hero h; h =new Hero(); h.hp = 5; } }
9.抽象类
步骤1:抽象类
/* 当一个类中有抽象方法,那么这个类就一定要变成抽象类即(abstract). 抽象方法和接口有点类似也可以没有实现体,但是继承这个抽象类的子类,必须去重写,实现父类的抽象方法 */ 代码示例: // 父类: public abstract class Hero { String name; float hp; float armor; int moveSpeed; public static void main(String[] args) { } // 抽象方法attack // Hero的子类会被要求实现attack方法 public abstract void attack(); } // 子类: public class ADHero extends Hero implements AD { public void physicAttack() { System.out.println("进行物理攻击"); } @Override public void attack() { physicAttack(); } }
步骤2:抽象类可以没有抽象方法
类可以在不提供抽象方法的前提下,声明为抽象类 一旦一个类被声明为抽象类,就不能够被直接实例化 public abstract class Hero { String name; float hp; float armor; int moveSpeed; public static void main(String[] args) { //虽然没有抽象方法,但是一旦被声明为了抽象类,就不能够直接被实例化 Hero h= new Hero(); } }
步骤3:抽象类和接口的区别
// 区别1: 子类只能继承一个抽象类,不能继承多个 子类可以实现多个接口 // 区别2: 抽象类可以定义 public,protected,package,private 静态和非静态属性 final和非final属性 但是接口中声明的属性,只能是 public 静态 final的 即便没有显式的声明 注: 抽象类和接口都可以有实体方法。 接口中的实体方法,叫做默认方法 public interface AP { public static final int resistPhysic = 100; //resistMagic即便没有显式的声明为 public static final //但依然默认为public static final int resistMagic = 0; public void magicAttack(); // 以下便是默认方法的格式 public default void apattack(){ } }
步骤4:
目的: 有的物品使用之后就消失了,比如血瓶 有的物品使用了之后还会继续存在,比如武器 为Item类设计一个抽象方法 public abstract boolean disposable() 不同的子类,实现disposable后,会返回不同的值。 比如LifePotion就会返回true,因为是会消失了。 而Weapon,Armor 就会返回false,因为是不会消失了
/** 答案: * 有的物品使用之后就消失了,比如血瓶 有的物品使用了之后还会继续存在,比如武器 为Item类设计一个抽象方法 public abstract boolean disposable() 不同的子类,实现disposable后,会返回不同的值。 比如LifePotion就会返回true,因为是会消失了。 而Weapon,Armor 就会返回false,因为是不会消失了 * */ public abstract class Item { String name; int price; public abstract boolean disposable();//是否是一次性的判断类型抽象方法 public static void main(String[] args) { LifePotion lp = new LifePotion(); Weapon wp = new Weapon(); Armor ar = new Armor(); System.out.println(lp.disposable()); System.out.println(wp.disposable()); System.out.println(ar.disposable()); } } //------------------血瓶子类-------------------- public class LifePotion extends Item{ @Override public boolean disposable() { return true; } } //-------------------武器子类------------------- public class Weapon extends Item{ @Override public boolean disposable() { return false; } } //-------------------护甲子类------------------- public class Armor extends Item { @Override public boolean disposable() { return false; } }