Java学习第八项——接口与继承

一、接口

在设计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("鱼吃虾米·");
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值