一、接口
在设计LOL英雄时,有两类进攻性英雄,一类ADC 一类APC
可以使用 接口 实现这一功能
接口就像是一种约定,
约定好的ADC 就打物理输出,不会打别的。
IDea如何创建接口?
点开后,选择,并输入名字即可
1、物理攻击接口(魔法攻击接口)
两种接口创建编码基本一致,此处合在一起记录。
在接口AD中需要声明出,我们会在类中使用的方法。只是声明一个方法,并没有方法体,也就是“空方法”。
package charactor;
public interface AD {
public void physicAttack();//声明一个方法,但没有方法体,是一个“空方法”
}
package charactor;
public interface AP {
public void magicAttack();
}
2、创建一个物理(魔法和同时有物理魔法)的英雄类
重新创建一个英雄类,需要继承Hero类,(使用extends)
也就拥有了Hero中的属性。
然后需要使用相应的方法(已经在接口中声明。若为声明,需要重新声明),使用implements
上述第二步 被称为 实现(在语法上使用implements)
实现一个接口,就相当于承认了这个接口的约束。
同样,实现了这个接口,就必须提供该接口声明的方法(是不是意味着,必须提供方法体。)
package charactor;
public class APHeroCarry extends Hero implements AP{
@Override
public void magicAttack() {
System.out.println("进行魔法伤害!");
}
}
package charactor;
public class ADAPHeroCarry extends Hero implements AD,AP{
//当接口不止一个时,用逗号隔开。
@Override
public void magicAttack() {
System.out.println("可以进行魔法攻击!");
}
@Override
public void physicAttack() {
System.out.println("也可以进行物理攻击!");
}
}
二、对象转型
1、明确引用类型和对象类型的关系
在这个例子中,
有一个对象 new ADHero()
也有一个引用 ad
两者都有类型,都是ADHero
通常情况下,两者时一样的。
现在讲的对象转型,指的是两者之间不一致的情况。
package charactor;
public class Hero {
public String name;
protected float hp;
public static void main(String[] args) {
ADHero ad = new ADHero();
}
}
2、子类转父类(向上转型)
转型指的是 引用类型和对象类型不一致时使用的方法
类型转换有时候会成功,但有时候也会失败的。
判断方法:
把右边当左边看,如果可以就说明转型成功了。
Hero h = new Hero();
ADHero ad = new ADHero();
h = ad;
这个例子就是:转换类型
右边是物理攻击英雄
左边是普通英雄
把物理攻击英雄当作普通英雄,行得通,所以可以转换。
其实,所有的子类转父类都是行的通的。毕竟子类继承的父类。
苹果手机 继承了 手机,把苹果手机当做普通手机使用
怡宝纯净水 继承了 饮品, 把怡宝纯净水 当做饮品来使用
苍老师 继承了动物, 把苍老师 。。。
package charactor;
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、父类转子类
父类转子类,有时候成功,有时候失败。
所以必须强制转换。
什么时候行?
1. Hero h =new Hero();
2. ADHero ad = new ADHero();
3. h = ad;
4. ad = (ADHero) h;
第三行是子类转父类,可行。h现在指向ADHero;
第四行是父类转子类,要强制执行,此时(h现在指向ADHero),这一步就是ADHero转换为ADHero不变,也可以成功。
什么时候不行?
1. Hero h =new Hero();
2. ADHero ad = new ADHero();
3. Support s =new Support();
4. h = s;
5. ad = (ADHero)h;
这里引用了三个对象。
第四行,子类转父类,可行。此时h指向Support这个对象。
第五行,想把指向Support的h转换为ADHero。从语义上讲,把物理攻击英雄变为辅助英雄是不可以的,并且系统会抛出异常。(同为子类,不行)
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;
}
}
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 类型转换异常
总结:(右边放左边,看成立不)
(1)子转父,一定行;
(2)父转子,不一定,需强转。(强转的结果:强转等号两边类型一样或有构成子转父,可以转;等号两边变为同级,不可以转)
5、实现类转换为接口(向上转型)
package charactor;
public class Hero {
public String name;
protected float hp;
public static void main(String[] args) {
ADHero ad = new ADHero();
AD adi = ad;
}
}
引用ad指向的对象是ADHero类型的。这个类型已经实现了AD接口。
AD adi = ad;
这一句讲ADHero类型的引用ad,转换为接口类型AD。
而AD接口只有一个方法physicAttack,那么ad也可能会调用该方法,由于已知ad已实现了AD接口,所以转换时成功的。
6、接口转换为实现类(向下转型)
package charactor;
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();
}
}
AD adi = ad;
可以实现,就是类转换为接口。
ADHero adHero = (ADHero) adi;
此时adi已经指向了一个ADHero对象,所以也可以转换为ADHero
ADAPHero adapHero = (ADAPHero) adi;
adi指向ADHero,但是不可以转换为ADAPHero。
因为,如果要转换为ADAPHero,就可能会调用magicAttack方法,但是adi引用的对象ADHero并没有该方法。
总结:(从右边看,是右边要转换为什么)
(1)类转接口(向上转型),如该类已经实现过接口,即当转换为接口时,可以调用接口的方法,则成功。
(2)接口转类(向下转型),须先看等号左边“转化成的类”已经实现了哪些接口。再看“要转化的接口”是否有这些方法,如果有,则成功,反之,则失败。
7、instanceof
X instancof Hero 来判断一个引用所指的对象,是否是Hero类型或者是Hero的子类
package charactor;
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);
}
}
三、重写
子类可以继承父类的对象方法,继承之后对方法进行重新编码--重写。
又称 覆盖override
1、父类
package property;
public class Item {
public String name;
public int price;
//创建了物品类
int i = 1;
public void method1( int i){
System.out.println(i); i = 5;
}
public void buy(){
System.out.println("购买");
}
public void effect(){
System.out.println("物品使用后,可以有效果");
}
}
2、子类
package property;
public class LifePotion extends Item{
public void effect(){
System.out.println("血瓶使用后,可以回血!");
}
}
3、main
package property;
public class Item {
public String name;
public int price;
//创建了物品类
int i = 1;
public void method1( int i){
System.out.println(i); i = 5;
}
public void buy(){
System.out.println("购买");
}
public void effect(){
System.out.println("物品使用后,可以有效果");
}
class Armor extends Item{
int ac; //护甲等级
}
public static void main(String[] args) {
Item i = new Item();
i.effect();
LifePotion ip = new LifePotion();
ip.effect();
}
}
可以看见,当我们在子类里重写了父类方法后,方法体改变了。
四、多态
操作符的多态,
+可以作为 运算符,也可以作为字符串连接
类的多态
父类引用指向子类对象
1、操作符的多态
+ 两边为整型,运算符而已
+ 两边为 任意一个为字符串,就是把字符串拼接。
int i = 5;
int j = 6;
int s = i + j;
System.out.println(s);
//字符串运算
int a = 7;
String b = "5";
String d = "5";
String c = d + b;
System.out.println(c);
String e = a + b;
System.out.println(e);
后两个为字符串拼接。
2、类的多态
父类
package property;
public class Item {
public String name;
public 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.println("i1 是 Item类型,执行effect");
i1.effect();
System.out.println("i2 是 Item类型,执行effect");
i2.effect();
}
}
两个子类:
package property;
public class LifePotion extends Item{
public void effect(){
System.out.println("血瓶使用后,可以回血!");
}
}
package property;
public class MagicPotion extends Item{
public void effect(){
System.out.println("蓝瓶使用后,可以恢复魔法!");
}
}
输出结果:
观察可知:
(1)i1和i2类型都是为Item;
(2)但是指向了两个不同的子类对象,都执行了effect(重写过);
(3)输出的是重写过的 结果。
3、类的多态条件
(1)父类(接口)引用指向子类对象
(2)子类对继承的方法有重写
4、使用类的多态和不使用有什么区别?
(1)不使用类的多态
假如不使用多态,英雄要使用蓝瓶和血瓶。就需要设定两个方法。
useLifePotion
useMagicPotion
去调用不同物品子类里的effect方法。
但是游戏里还有很多的物品,这样对每一个物品都需要有一个方法,就会使英雄类的main函数显得很复杂。
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);
}
}
虽然结果一样,但是这里多更新了两个对象。对于更多的物品就会有更多的方法需要声明。
(2)使用类的多态
假如使用类的多态,就不用给每个物品创建一个新的方法
只需要一个方法就可以让每个子类对象的重写方法被调用。
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);
garen.useItem(mp);
}
}
这里就只使用了一个方法useItem(),根据参数的不同,就可以调用不同子类的方法。
由于在声明时,参数类型是 Item,
根据类的多态,就可以使用该类Item下的子类。
5、练习
immortal是不朽的,不死的意思
mortal就是终有一死的,凡人的意思
1. 设计一个接口
接口叫做Mortal,其中有一个方法叫做die
2. 实现接口
分别让ADHero,APHero,ADAPHero这三个类,实现Mortal接口,不同的类实现die方法的时候,都打印出不一样的字符串
3. 为Hero类,添加一个方法,在这个方法中调用 m的die方法。
public void kill(Mortal m)
4. 在主方法中
首先实例化出一个Hero对象:盖伦
然后实例化出3个对象,分别是ADHero,APHero,ADAPHero的实例
然后让盖伦 kill 这3个对象
(1)首先是接口Mortal
package charactor;
public interface Mortal {
public void die();
}
(2)三个子类
package charactor;
public class ADAPHeroCarry extends Hero implements AD,AP,Mortal{
//当接口不止一个时,用逗号隔开。
@Override
public void magicAttack() {
System.out.println("可以进行魔法攻击!");
}
@Override
public void physicAttack() {
System.out.println("也可以进行物理攻击!");
}
public void die(){
System.out.println("魔剑士英雄死亡!");
}
}
package charactor;
public class ADHeroCarry extends Hero implements AD,Mortal{
public void physicAttack(){
System.out.println("进行物理攻击!");
}
public void die(){
System.out.println("物理英雄死亡!");
}
}
package charactor;
public class APHeroCarry extends Hero implements AP,Mortal{
@Override
public void magicAttack() {
System.out.println("进行魔法伤害!");
}
public void die(){
System.out.println("魔法英雄死亡!");
}
}
(3)主函数及输出
package charactor;
import property.*;
public class Hero {
static String copyRight = "Riot"; //声明该属性时初始化
static {
copyRight = "YSKM"; //静态初始化块
}
protected int id;
{
id = 10001; //初始化块
}
public String name;
float hp;
float armor;
int moveSpeed;
//类方法
public void useItem(Item i){
i.effect();
}
public void kill(Mortal m){
m.die();
}
public static void main(String[] args) {
Hero h1 = new Hero();
h1.name = "凯瑟琳";
ADAPHeroCarry a1 = new ADAPHeroCarry();
ADHeroCarry a2 = new ADHeroCarry();
APHeroCarry a3 = new APHeroCarry();
h1.kill(a1);
h1.kill(a2);
h1.kill(a3);
}
}
五、隐藏
与重写类似,
重写是 子类覆盖父类的方法;
隐藏是子类覆盖父类的类方法。
(方法的调用需要有创建的有对象,而类方法不需要具体对象就可以使用
当需要调用某个属性时,必须有对象;但只是无属性方法,就可以用类方法)
1、父类(包含一个类方法)
package charactor;
import property.*;
public class Hero {
static String copyRight = "Riot"; //声明该属性时初始化
static {
copyRight = "YSKM"; //静态初始化块
}
protected int id;
{
id = 10001; //初始化块
}
public String name;
float hp;
float armor;
int moveSpeed;
//类方法()
public static void battleWin(){
System.out.println("战斗胜利!");
}
}
}
2、子类隐藏父类的类方法
package charactor;
public class ADHeroCarry extends Hero implements AD,Mortal{
public void physicAttack(){
System.out.println("进行物理攻击!");
}
public void die(){
System.out.println("物理英雄死亡!");
}
public static void battleWin(){
System.out.println("物理英雄战斗胜利!");
}//子类的类方法 覆盖掉父类的类方法
public static void main(String[] args) {
Hero.battleWin();
ADHeroCarry.battleWin();
}
}
不同类调用该方法,就会有不同结果。(类方法已经被隐藏)
3、练习
Hero h =new ADHero();
h.battleWin(); //battleWin是一个类方法
h是父类类型的引用
但是指向一个子类对象
h.battleWin(); 会调用父类的方法?还是子类的方法?
可见输出的结果还是 Hero的结果,表明,虽然静态方法(类方法可以被实例化对象调用,但是和h指向哪个对象无关,只和h的【引用类型】有关系,这里仍是父类引用)
六、super关键字
1、准备一个显示的无参构造方法;
package charactor;
import property.*;
public class Hero {
static String copyRight = "Riot"; //声明该属性时初始化
static {
copyRight = "YSKM"; //静态初始化块
}
protected int id;
{
id = 10001; //初始化块
}
public String name;
float hp;
float armor;
int moveSpeed;
//父类的类方法
public Hero(){//父类的构造方法
System.out.println("Hero的构造方法 ");
}
public static void main(String[] args) {
new Hero();
new ADHeroCarry();
}
}
此时实例化父类对象或者子类对象,都会打印这句话。
2、实例化子类,父类的构造方法一定会被调用
实例化一个子类对象,其构造方法(子类)会调用。
同时也会调用其父类的显示构造方法(隐式也会,只是没有显示出来)
且,父类构造方法会先于子类调用。
package charactor;
import property.*;
public class Hero {
static String copyRight = "Riot"; //声明该属性时初始化
static {
copyRight = "YSKM"; //静态初始化块
}
protected int id;
{
id = 10001; //初始化块
}
public String name;
float hp;
float armor;
int moveSpeed;
//父类的类方法
public Hero(){//父类的构造方法
System.out.println("Hero的构造方法 ");
}
public static void main(String[] args) {
//new Hero();
new ADHeroCarry();
}
}
public ADHeroCarry(){
System.out.println("AD Hero的构造方法");
}
3、父类显示的设置两个构造函数
package charactor;
import property.*;
public class Hero {
static String copyRight = "Riot"; //声明该属性时初始化
static {
copyRight = "YSKM"; //静态初始化块
}
protected int id;
{
id = 10001; //初始化块
}
public String name;
float hp;
float armor;
int moveSpeed;
//父类的类方法
public Hero(){//父类的构造方法
System.out.println("Hero的无参构造方法 ");
}
public Hero(String name){//父类的构造方法
System.out.println("Hero的含一个参数的构造方法 ");
this.name = name;
}
public static void main(String[] args) {
new Hero();
new Hero("亚索");
//new ADHeroCarry();
}
}
4、子类显示调用父类的构造方法。
如果只是在子类里构造一个有一参数的构造方法,在调用该方法时,会先调用父类的显示无参构造方法。
package charactor;
public class ADHeroCarry extends Hero implements AD,Mortal{
public void physicAttack(){
System.out.println("进行物理攻击!");
}
public void die(){
System.out.println("物理英雄死亡!");
}
public ADHeroCarry(){
System.out.println("AD Hero的构造方法");
}
public ADHeroCarry(String name){
System.out.println("AD Hero的一参数构造方法");
}
public static void main(String[] args) {
Hero h = new ADHeroCarry("凯瑟莉");
}
}
如果使用super关键字来调用的话。
public ADHeroCarry(String name){
super(name);
System.out.println("AD Hero的一参数构造方法");
}
就会调用,父类的含参构造方法。
5、调用父类属性
通过super调用父类的mobeSpeed属性
ADHeroCarry也提供了moveSpeed属性
package charactor;
public class ADHeroCarry extends Hero implements AD,Mortal{
int moveSpeed = 350;
public void physicAttack(){
System.out.println("进行物理攻击!");
}
public void die(){
System.out.println("物理英雄死亡!");
}
public ADHeroCarry(){
System.out.println("AD Hero的构造方法");
}
public int getMoveSpeed1(){
return this.moveSpeed;
}
public int getMoveSpeed2(){
return super.moveSpeed;
}
public ADHeroCarry(String name){
super(name);//调用父类
System.out.println("AD Hero的一参数构造方法");
}
public static void main(String[] args) {
ADHeroCarry h = new ADHeroCarry();
System.out.println(h.getMoveSpeed1());
System.out.println(h.getMoveSpeed2());
//Hero h = new ADHeroCarry("凯瑟莉");
}
}
根据不同 的方法可以调用(this)本子类的速度,也可以得到(super)父类的速度。
6、调用父类的方法
不使用super,
且不在子类里重写方法。调用时会调用父类的方法。
package charactor;
import property.Item;
import property.LifePotion;
public class ADHeroCarry extends Hero implements AD,Mortal{
int moveSpeed = 350;
public void physicAttack(){
System.out.println("进行物理攻击!");
}
public void die(){
System.out.println("物理英雄死亡!");
}
public ADHeroCarry(){
System.out.println("AD Hero的无参数构造方法");
}
public int getMoveSpeed1(){
return this.moveSpeed;
}
public int getMoveSpeed2(){
return super.moveSpeed;
}
public ADHeroCarry(String name){
super(name);//调用父类
System.out.println("AD Hero的一参数构造方法");
}
public static void main(String[] args) {
ADHeroCarry h = new ADHeroCarry();
LifePotion lp = new LifePotion();
h.useItem(lp);
}
}
重写并 加上super后
public void useItem(Item i){
super.useItem(i);
System.out.println("ADHero use Item!");
}
七、Object类
Object类时所有子类的父类
1、Object类时所有子类的父类
声明一个类的同时,默认继承了Object这个类
public class Hero extends Object
2、toString()
Object类提供了一个toString()方法,也就是所有类都可以调用该方法。
该方法的作用就是返回当前对象的字符串表达
sout返回的作用就时toString()
public static void main(String[] args) {
new Hero();
Hero h =new Hero("亚索");
System.out.println(h);
System.out.println(h.toString());
//new ADHeroCarry();
}
可以观察到两个打印方式的结果是一样的。
3、finalize
当一个对象没有任何引用指向时,就满足垃圾回收的条件。
当他被垃圾回收时,就会调用它的finalize()方法。
该方法不是开发人员主动调用的,而是JVM虚拟机自带的。
public void finalize(){
System.out.println("该英雄正在被回收!");
}
public void useItem(Item i){
System.out.println("hero use Item!");
}
public static void main(String[] args) {
Hero h;//只有引用 没有对象
for (int i = 0; i < 9999; i++){
h = new Hero();
//不断创建新的对象, 并让h指向该对象;
// 那么前一个对象就会失去它的引用,当这样的对象堆积多了。
// 就会触发垃圾回收
}
new Hero();
//new ADHeroCarry();
}
每次调用一次finalize()方法就会打印该语句一次。
4、equals()
equalis()的作用是,判断两个对象的内容是否一致。
假设,我们认为两个英雄血量hp一致时,该两个英雄就是一样的。
public boolean equals(Object o){//返回类型是boolean,因为时判断是否相等。
if (o instanceof Hero){//interface的作用是判断,o是否是Hero类型或者是不是其子类。
Hero h = (Hero) o;//强制转换,如果是同类型,直接转;如果是子类转父类,也可以转。此处不存在失败的问题。
return this.hp == h.hp;//这里返回的是(true或者false)
}
else return false;
}
public static void main(String[] args) {
Hero h1 = new Hero();
h1.hp = 200;
h1.name = "的";
Hero h2 = new Hero();
h2.hp = 300;
Hero h3 = new Hero();
h3.hp = 400;
System.out.println(h1.equals(h3));
System.out.println(h2.equals(h3));
System.out.println(h1.equals(h2));
}
可以看见,血量不一样时,就会显示false
但是修改数据后,就会判断true。
根据不同的equals()方法,来通过不同条件,判断。
5、==
这不是Object的方法,但是可以用来判断两个对象是否相同,
准确的来说,是判断两个引用是否指向同一个对象。
System.out.println(h1 == h3);
System.out.println(h1.equals(h2));
System.out.println(h1 == h2);
可以看见 h1 h2 的对象的hp是一样的,但是并不代表着两个引用指向同一个对象。
6、hashCode()
hashCode()返回一个哈希值。
7、线程同步相关方法
Object还提供线程同步相关方法
八、final
final修饰类、方法、基本类型变量,引用的时候分别有不同的意思。
1、final修饰类
当Hero被修饰为final时,表示其不能被继承。
其子类会报错误。
public final class Hero extends Object{
public class ADHeroCarry extends Hero implements AD,Mortal{
2、final修饰方法
当父类的方法被修饰为fianl时,子类无法对该方法进行重写。
public final void useItem(Item i){
System.out.println("hero use Item!");
}
子类的
public void useItem(Item i){
super.useItem(i);
System.out.println("ADHero use Item!");
}
编译时会报错
3、final修饰基本类型变量
表示该变量只有一次赋值机会
final int a ;
a = 1 ;
a=2;
System.out.println(a);
4、final修饰引用
h引用被修饰为final,表示为该引用只有一次指向机会。当其完成指向后,不能再赋予新对象。
但是可以修改该对象的属性,因为其属性并没有被final修饰。
final Hero h;
h =new Hero();
h =new Hero();
h.hp = 5;
5、常量
常量指的是可以公开,直接访问,不会变化的值
比如 itemTotalNumber 物品栏的数量是6个
public class Hero extends Object {
public static final int itemTotalNumber = 6;//物品栏的数量
String name; //姓名
float hp; //血量
float armor; //护甲
int moveSpeed; //移动速度
九、抽象类
当一个类声明了一个没有方法体的方法时,称这个“空”方法为抽象方法,使用abstract修饰
当一个类有抽象方法时,该类也要被声明为抽象类。
1、抽象类
为Hero增加一个attack的抽象方法,用abstract修饰,并且用abstract修饰Hero类
Hero的各个类都会继承其属性和方法,但对于该方法应该是有不同的输出方法的。 (而且是必须具备该方法)
父类部分代码:
public abstract class Hero extends Object{
static String copyRight = "Riot"; //声明该属性时初始化
static {
copyRight = "YSKM"; //静态初始化块
}
public String name;
float hp;
float armor;
int moveSpeed = 375;
public Hero(){
}
public abstract void attack();
子类必须具备的代码:
public void physicAttack(){
System.out.println("进行物理攻击!");
}
public void attack(){
physicAttack();
}
public void magicAttack() {
System.out.println("进行魔法伤害!");
}
public void attack(){
magicAttack();
}
public void attack() {
System.out.println("既可以进行物理攻击,也可以进行魔法攻击");
}
但是此时会有异常显示,因为Hero是抽象abstract的,所以不能创建
2、抽象类没有抽象方法
当一个抽象类,没有声明抽象方法时。(可以)
此时的抽象类不能被实例化
3、抽象类与接口的区别
区别1 :
子类只能继承一个抽象类,
但是可以实现多个接口。
区别2:
抽象类可以定义:
public、private、protected、package
静态和非静态属性
final和非final属性
但接口声明的属性只能是:
public
静态的
final
即便没有显式的声明,也会默认显示
注:接口和抽象类都可以有实体方法。接口中的有实体的方法,叫做默认方法
接口里需要先声明“空”方法,其实也都是抽象方法。
package charactor;
public interface AP {
public static final int resistPhyical = 100;
int restistMagic = 0;
public void magicAttack();
}
其中 int resistPhysical即使没有显式的声明为public static final,
也会默认为该类型;
总结:
(1)抽象类可以被定义,(类似普通类),抽象方法并不是必须的。(若有,则必须再子类中进行重写)
(2)接口声明的空方法,也是抽象方法。接口被实现时,不一定需要重写该方法。
十、内部类
分为四种:
非静态内部类
静态内部类
匿名类
本地类
1、非静态内部类
非静态内部类 BattleScore“战斗成绩”
非静态内部类可以直接在一个类里面定义
比如:
战斗成绩只有在一个战斗英雄存在时才有意义
所有,非静态内部类,只有在外部类实例化后才有意义。
语法:new 外部类().内部类()
由于BattleScore是Hero 的非静态内部类,所以是可以调用Hero的私有属性name
public class Hero extends Object{
private String name1;
class BattleScore{
int kill;
int die;
int assist;
public void legendary(){
if (kill > 8)
System.out.println(name1 + "超神!");
else
System.out.println(name1 + "原来是小瘪三");
}
}
public static void main(String[] args) {
Hero h = new Hero();
h.name1 = "张辽";
//首先实例化外部类Hero
//接下来实例化内部类
//由于BattleScore的实例化必须建立在一个已有对象上,才有意义
//战斗成绩只有在英雄存在时才有用
BattleScore b = h.new BattleScore();
b.kill = 10;
b.legendary();
}
}
可以看见在实例化非静态内部类时,
BattleScore b = h.new BattleScore();
必须是英雄(外部类对象存在才有意义)
2、静态内部类
在一个类里声明一个静态内部类;
比如敌方水晶,当敌方水晶被摧毁时,己方所有英雄都会获得胜利,并不是具体谁取得胜利。
因此,静态内部类的实现,并不需要外部类的实例化。可以直接实例化;
语法:new 外部类.静态内部类()
因为没有外部类的实例,所以静态内部类的实例化,不能访问外部类的属性和方法。
除了可以访问外部类的私有静态成员外,静态内部类和普通类没有区别。
私有静态成员
public class Hero extends Object{
static String copyRight = "Riot"; //声明该属性时初始化
static {
copyRight = "YSKM"; //静态初始化块
}
public String name;
float hp;
float armor;
int moveSpeed = 375;
private static void battleWin(){//私有静态方法
System.out.println("battle win");
}
static class EnemyCrystal{//静态内部类
int hp = 50;
public void checkIfVictory(){
if(hp ==0){
Hero.battleWin();
//System.out.println(name + "win the game!");//静态内部类,不能访问外部属性,因为没有实例化
}
else {
System.out.println("The Enemy Crystal is still alive!");
}
}
}
public static void main(String[] args) {
//静态内部对象,不需要提前实例化外部对象
Hero.EnemyCrystal c = new Hero.EnemyCrystal();
c.checkIfVictory();
}
}
静态内部对象,不需要提前实例化外部对象,直接创建了静态内部对象。
Hero.EnemyCrystal c = new Hero.EnemyCrystal();
实际上就是 外部类.静态内部类 引用名 = new 外部类.静态内部类
3、匿名类
匿名类指的是,声明一个类的同时九实例化它,简化代码。
通常情况下,要实现一个接口或者使用一个抽象类,都必须要创建一个子类。
但有时候,为了方便使用,我们直接实例化抽象类,并在其中 实现其抽象方法
因为,实现的抽象方法的必是类,所以此处的也是一个类,只是没有命名,
即,匿名类。
public abstract class Hero {//加上abstract就变为了抽象类
String name; //姓名
float hp; //血量
float armor; //护甲
int moveSpeed; // 移动速度
int killNumber; //击杀数
int budaoNumber; // 补刀数
int money; //金钱
int deadNumber; // 死亡次数
int assistantNumber; //助攻次数
float attackSpeed; // 攻击速度
// 这里就是创建了一个英雄类,设定了英雄的属性。
public abstract void attack();//父类声明主函数
public static void main(String[] args) {
ADHero adHero = new ADHero();
adHero.attack();
System.out.println( "看看adHero属于哪个类\n"+ adHero);
Hero h = new Hero() {
@Override
public void attack() {
System.out.println("新的进攻手段!");
}
}; //此处需要一个分号,是因为这里属于实例化的一部分
h.attack();
//这样是直接实例化对象,并且实现了抽象方法,说明这也是一个类。
System.out.println( "看看h属于哪个类\n"+ h);
}
}
可以观察到,adHero属于ADHero类,但是h的类是由JVM随机生成的一个类。
PS:为什么这个实例化,和抽象方法的重写,在main函数里?
(1)实例化本身就应该在main函数里。
(2) 实际上,抽象类本身是不可以实例化的,只有通过转化为匿名类,才可以。并且是看做一个新的类,并对抽象方法重写。
(3)就算将这部分代码放在main函数外,也不会报错。但是由于此函数没有命名,所以不可以在main中实例化,也就无法调用attack()方法。而原方法可行。
4、本地类
本地类就是 有名字的匿名类。
内部类、匿名类、本地类区别:
(1)内部类只能声明在成员位置,即与属性和方法同一等级;
(2)匿名类和本地类,直接声明在代码块中,可以是主方法,也可以是for循环等等;
package charactor;
public abstract class Hero {//加上abstract就变为了抽象类
String name; //姓名
float hp; //血量
float armor; //护甲
int moveSpeed; // 移动速度
int killNumber; //击杀数
int budaoNumber; // 补刀数
int money; //金钱
int deadNumber; // 死亡次数
int assistantNumber; //助攻次数
float attackSpeed; // 攻击速度
// 这里就是创建了一个英雄类,设定了英雄的属性。
public abstract void attack();//父类声明主函数
public static void main(String[] args) {
//与匿名类的区别在于,本地类有了自定义的类名;
class SomeHero extends Hero{
public void attack(){
System.out.println(name + "(本地类)刹那月华!");
}
}
SomeHero someHero = new SomeHero();
someHero.name = "张队长";
someHero.attack();
System.out.println(someHero);
}
}
本地类的对象,既有系统分配的类,也有自定义的类。
(我以为继承了父类,所以会有两个,但其他子类,也只有一个,见ADHero)
5、在匿名类中使用外部的局部变量
在匿名类里使用外部局部变量,必须修饰为final,
注:在jdk8中,已经不需要强制修饰成final了,如果没有写final,不会报错,因为编译器偷偷的帮你加上了看不见的final
package charactor;
public abstract class Hero {//加上abstract就变为了抽象类
String name; //姓名
float hp; //血量
float armor; //护甲
int moveSpeed; // 移动速度
int killNumber; //击杀数
int budaoNumber; // 补刀数
int money; //金钱
int deadNumber; // 死亡次数
int assistantNumber; //助攻次数
float attackSpeed; // 攻击速度
// 这里就是创建了一个英雄类,设定了英雄的属性。
public abstract void attack();//父类声明主函数
public static void main(String[] args) {
final int damage =5;
Hero h = new Hero() {
@Override
public void attack() {
System.out.println("新的进攻手段,造成"+damage+"点伤害");
}
}; //此处需要一个分号,是因为这里属于实例化的一部分
h.attack();
//这样是直接实例化对象,并且实现了抽象方法,说明这也是一个类。
}
}
为什么要声明为final?
因为,在使用外部局部变量时,匿名类里也会声明一个同名变量,并且用构造方法初始化这个值。
若不用final,就意味着,可以对该变量进行多次赋值。
当调用attack()方法时,实际上是调用的这个匿名类内部的值,而非外部变量。
当在attack中,修改damage的值,就会被暗示修改了外部damage 的值,但实际上,这两个变量有不同存储地址,并不是同一个变量,也就无法同步修改。
为避免这种误差,就使用fianl来修饰外部damage,让其看起来像是不能被修改一样。
6、练习
创建一个Item的匿名类
package property;
public abstract class Item {
public String name;
public int price;
//创建了物品类
public void buy(){
System.out.println("购买");
}
public void effect(){
System.out.println("物品使用后,可以有效果");
}
public abstract void disposable();
public static void main(String[] args) {
Item i1 = new LifePotion();//创建子类的方法
Item i2 = new MagicPotion();
//System.out.println("i1 是 Item类型,执行effect");
//i1.effect();
//System.out.println("i2 是 Item类型,执行effect");
// i2.effect();
Item item =new Item() {
@Override
public void disposable() {
System.out.println("全新的物品!");
}
};
item.disposable();
System.out.println(item);
}
}
系统分配的类名。
十一、默认方法
1、什么是默认方法
默认方法指的是 接口可以提供具体的方法了,而不是只声明一个空方法。
Mortal这个接口,提供了一个有实体的方法revive,并且声明为default;
package charactor;
public interface Mortal {
public void die();
default public void revive(){
System.out.println("此英雄复活了!");
}
}
2、为什么有默认方法
假设没有默认方法的机制,那么若想给接口Mortal新添加一个功能,所有已实现该接口的类,都要对该方法进行重写,工作量繁杂。
但如果能直接在接口里实现默认方法(有实体的方法),那么对于所有实现该接口的类,都不用再重新修改此方法了,并可以直接使用。
3、练习
为AD接口增加一个默认方法 attack()
为AP接口也增加一个默认方法 attack()
问: ADAPHero同时实现了AD,AP接口,那么 ADAPHero 对象调用attack()的时候,是调用哪个接口的attack()?
答:
系统会报错。此时应该在ADAPHero中重写该方法,直接会调用他自身的attack()。
或者使用ADHero.super.attack()
APHero.super.attack()
来分别调用。
十二、综合练习
1、UML图-----类之间的关系
UML-Unified Module Language
统一建模语言,可以很方便的用于描述类的属性,方法,以及类和类之间的关系
2、解释UML-类图
3、解释UML-接口图
4、解释UML-继承关系
带箭头的实线,表示 Spider,Cat, Fish都继承于Animal这个父类
5、 解释UML-实现关系
表示 Fish实现了 Pet这个接口虚线
6、练习-Animal类
1. 创建Animal类,它是所有动物的抽象父类。
2. 声明一个受保护的整数类型属性legs,它记录动物的腿的数目。
3. 定义一个受保护的构造器,用来初始化legs属性。
4. 声明抽象方法eat。
5. 声明具体方法walk来打印动物是如何行走的(包括腿的数目)。
package Animals;
public abstract class Animal {//抽象类
protected int legs; //记录动物的推的数目
public abstract void eat();//抽象方法
public void walk(){
System.out.println("动物都会运动,且该动物有%d条腿"+this.legs);
}
protected void Animal(int legs){
this.legs = legs;//初始化腿数
}
}
7、练习-Spider类
1. Spider继承Animal类。
2. 定义默认构造器,它调用父类构造器来指明所有蜘蛛都是8条腿。
3. 实现eat方法
package Animals;
public class Spider extends Animal{
public Spider(int legs){
super(legs);// 调用了父类的构造方法
}
public void eat() {
System.out.println("蜘蛛会吃鱼!");
}
}
8、练习-Pet接口
根据UML类创建pet(宠物)接口
1. 提供getName() 返回该宠物的名字
2. 提供setName(String name) 为该宠物命名
3. 提供 play()方法
package Animals;
public interface Pet {
public String getName();
public String setName(String name);
public void play();
}
9、练习-Cat类
1. 该类必须包含String属性来存宠物的名字。
2. 定义一个构造器,它使用String参数指定猫的名字;该构造器必须调用超类构造器来指明所有的猫都是四条腿。
3. 另定义一个无参的构造器。该构造器调用前一个构造器(用this关键字)并传递一个空字符串作为参数
4. 实现Pet接口方法。
5. 实现eat方法。
package Animals;
import charactor.Hero;
public class Cat extends Animal implements Pet {
String name;//1
public Cat(String name){
super(4);2 这里用的是父类的构造器
}
public Cat(){
this(" ");//3 这里调用了前一个构造器
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}//4
public void eat(){
System.out.println("猫也吃鱼!");
}//5
public void play(){}
}
10、练习-Fish类
1. 创建Fish类,它继承Animal类
2. 重写Animal的walk方法以表明鱼不能走且没有腿。
3. 实现Pet接口
4. 无参构造方法
5. 提供一个private 的name属性
package Animals;
public class Fish extends Animal implements Pet{
private String name;//5
public void walk(){
System.out.println("鱼没有脚!不能走路");
}///1
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
public void play(){}//3
public Fish(){
System.out.println("一条鱼诞生了!");
}//4
public void eat(){
System.out.println("鱼吃虾米·");
}
}