Java lecture 7 常量、抽象类和抽象方法、绘制对象的图片在窗口中、成员内部类和匿名内部类

一、常量

定义:用static、final修饰的变量
也就意味着结合了这两个关键字的特性

特性:

  1. 通过类名打点访问
  2. 不能被二次修改,且声明的时候就需要初始化了(但实际上是可以修改的)

常量的命名:
建议纯大写,有多个单词的时候,用下划线隔开

什么时候用:
当有一份数据,不需要变化,且经常使用。
就是说,牵一发而动全身的类就需要。
方便访问,保护数据的安全性。

语法:
static final 数据类型+常量的名字 = 常量的值

访问常量的时候,会在编译期间直接将常量的内容转换成直接量,效率更高。(对比起下图的方式)
注意!使用这种方法访问常量的时候,并不会调用到类的静态化代码,所以效率更高。

请添加图片描述
比如在这个潜艇大战的项目中,因为我们的游戏窗口大小是固定的,而后续很多地方都需要使用该窗口的宽或高,如果一旦窗口大小发生改变,那很多地方都需要改变,所以可以将窗口的大小设计为常量,方便使用。

所以,在GameWorld上方加上两个常量。因为常量需要被访问,所以设计为public。

    public static final int WIDTH = 641;/** 窗口的宽 */
    public static final int HEIGHT = 479;/** 窗口的高 */

将目前项目中用到的具体的宽高数据改成使用常量.
所以后面frame.setSize的参数就能更改为(WIDTH, HEIGHT)了,而不是具体的数字。(其他类调用的时候要记得类名打点调用)

二、抽象方法和抽象类

总结设计规则(套路):

1.将所有概念统一的类,它们共性属性和行为 提取到一个父类中.过程叫做泛化.也是优化代码的冗余.

2.提取到父类中的共性行为里面的实现逻辑,若子类都能够或多个子类可以使用,则可以设计为普通方法.

若所有子类不能够使用父类提供的行为内的实现逻辑时,则应该设计为抽象方法!

3.接口…后续讲.

从设计的角度来讲,先有抽象方法,才推理出的抽象类.

从代码的角度来讲,抽象方法是需要存在于抽象类中的.

抽象方法:

1.用 abstract关键字修饰的方法为抽象方法. 写到返回值前方.
比如abstract void step{}

2.抽象方法只能放在抽象类中.

3.Abstract methods cannot have a body :抽象方法不能有方法体!(想一想,反正本来就无法复用,写它干嘛)

4.抽象方法是必须要被重写实现的!

抽象类:

1.用abstract修饰的类 为抽象类.

2.抽象类只是在原有的普通类的基础上,添加内容多了抽象方法的部分而已.

3.若一个类,为抽象类,是需要子类继承并实现内部的抽象方法的!

4.抽象类是不能够被创建对象的!(因为它本身就无法使用,创建个对象有啥用?)但是可以创造个数组。

//      SeaObject s = new SeaObject();//抽象类不能被创建对象!
        SeaObject[] s1 = new SeaObject[3];//创建SeaObject类型数组的对象 开了三块地.

所以抽象类的意义在于:
1.封装子类共有的属性和行为 -------代码父类.

2.为子类提供向上造型 -----调用父执行子

当前7个子类移动行为是一样的,但是每个子类具体移动逻辑不一样,在使用向上造型时,父类中的移动行为,相当于提供一个统一的入口.

几个常见的疑问:

1.抽象方法还有意义吗?为什么不直接用普通方法呢?: 遵循面向对象的设计原则,其次做成抽象方法原因,
   1是因为子类对应这个方法都有不同的实现逻辑,(所以需要继承抽象方法后进行重写)
   2是做成抽象方法我们可以达到代码的约束,要求子类必须重写否则报错!(就是说,如果不用父类的方法的话,那就得把每个子类的方法写完之后单独调用一遍。)
​
2.子类既然都重写的话,那么父类中的抽象方法还有存在的必要?: 父类中存在抽象方法的意义,就是为了实现向上造型后,父类可以提供对应行为的一个入口,统一管理子类的行为,具体执行时.执行的则是不同子类的实现逻辑.**达到入口统一.**    

三、画对象图片

绘制的方法 java已经提供了,所以我们直接使用就可以了。
但是需要重写。
输入paint回车之后,就能自动输入方法。

只需要2步骤:

1.对象的图片
2.绘制的(X,Y)坐标

例:
假如要绘制一个战舰

public void paint(Graphics g){
1. 获取要绘制的战舰图
ImageIcon ship = ImageIcon.battleship(获取战舰图片,存到ship这个变量里。
2. 给定绘制的坐标
ship.paintIcon(null,g,240,124)
//这个方法里面需要4个内容,要按顺序,第一个填null(不需要理解,只需要填null就好),第二个填g(可以理解为是画笔的意思),第三个填x值,第四个填y值(表示这张图片绘制在哪个(x,y))
}

如果想要贴切到项目中画对象的话(就是根据项目的逻辑去绘制的话):

需要5个步骤
第一个步骤:

**每个对象都要获取图片,意味着每个类都要有获取图片的方法,**如果每个类都写一个获取图片方法的话,则每个类都有共性的行为,此时可以将绘制方法干脆提取到父类中.

提取到父类的方法是做普通方法还是抽象方法? 因为每个子类获取图片的内容是不一样的,所以应该设计为抽象方法.

    abstract ImageIcon getImage();

这里之所以返回值设计为ImageIcon,是因为绘制完图片之后需要返回获取到的图片。

第二个步骤

绘制图片的条件:
每个对象都是有状态,例如 活着的和死亡的 两个状态。在获取图片之前,应该先判断对象的状态,若对象是活着的状态,则返回图片,否则返回null。 所有对象都有活着的和死亡的两个状态,且这个状态是固定的,我们可以将该两个状态 做成常量放在父类种去写。 还应该设计一个变量,表示当前状态
可以这么理解:
请添加图片描述

//这段代码写在父类里
    protected static final int LIVE = 0;/** 活着的状态 */
    protected static final int DEAD = 1;/** 死亡的状态 *///这里因为其他类不需要访问这个类这两个常量,所以只需要写protected或者private就可以了;
    public int currentState = LIVE;//当前状态 默认为活着的状态。

所以应该怎么去判断对象的状态?写一个父类的通用方法,直接添加两个普通方法(因为所有的子类对象都能用,不需要修改),用来判断当前的对象是否是活着的还是死亡的状态。

  /**
     * 判断当前调用这个方法的对象 是否为死亡状态。
     */
    protected boolean isDead(){
        return currentState == DEAD;//返回 当前对象的状态是否等于死亡状态。
    }/**
     *判断当前调用这个方法的对象 是否为活着状态。
     */
    protected boolean isLive(){
        return currentState == LIVE;//返回 当前对象的状态是否等于活着状态。
    }

第三个步骤:

子类重写实现父类提供的getImage抽象方法。
注意:下面的代码是分别写在各个子类里的

战舰类:
  @Override  //重写获取图片的方法
    ImageIcon getImage() { //战舰比较特殊,并不是一打就死亡。
        return ImageResources.battleship;//返回战舰图片}
深水炸弹类:
     @Override
    ImageIcon getImage() {
        //1.判断当前对象是否是活着的状态,
        if(this.isLive()){
            return ImageResources.bomb;//是则返回对应类型图片
        }
        //2.否则返回 null
        return null;//如果代码能执行到这一行,说明if没成立,则返回null代表没有图片给当前对象使用
    }
水雷类:
      @Override
    ImageIcon getImage() {
        if(this.isLive()){//判断当前水雷对象是否为活着的状态
            return ImageResources.mine;
        }
        return null;//如果代码能走到这一行,说明当前对象死亡 返回null即可。
    }
水雷潜艇类:
       @Override
    ImageIcon getImage() {
        if(this.isLive()){
            return ImageResources.minesubm;//返回水雷潜艇图片
        }
        return null;
    }
侦察潜艇类:
 @Override
    ImageIcon getImage() {
        if(this.isLive()){//判断当前侦察潜艇对象是否为活着的状态
            return ImageResources.obsersubm;
        }
        return null;//如果代码能走到这一行,说明当前对象死亡 返回null即可。
    }
鱼雷类:
      @Override
    ImageIcon getImage() {
        if(this.isLive()){//判断当前鱼雷对象是否为活着的状态
            return ImageResources.torpedo;
        }
        return null;//如果代码能走到这一行,说明当前对象死亡 返回null即可。
    }
鱼雷潜艇类:
      @Override
    ImageIcon getImage() {
        if(this.isLive()){//判断当前鱼雷潜艇对象是否为活着的状态
            return ImageResources.torpesubm;
        }
        return null;//如果代码能走到这一行,说明当前对象死亡 返回null即可。
    }
    

以上的这三个步骤,都是为了去对获取要绘制的战舰图进行方法优化。即代替原来的在测试类里一个一个实例化,一个一个调用方法。从而实现,在父类里写一个通用的获取图像的方法,子类重写,并且根据存活条件进行判断。

所以接下来,是要进行图片的绘制。

第四个步骤:

获取对象图片的功能有了,但是每个对象需要绘制,7个类若有绘制的行为是重复的,所以可以将绘制的行为,提取到父类中复用。

绘制的方法做普通的还是抽象的方法?

  /**
     * 因为每个子类都需要进行绘制,那么将绘制行为提取到父类中.
     * 因为每个子类的绘制逻辑是一样的,而绘制的数据我们可以通过对象来区分
     * 所以做成普通方法即可.
     * 当前方法需要一个画笔,所以做成形参,让调用的地方传递进入即可.
     */
    public void paintImage(Graphics g){
        //1.获取要绘制的图片
        ImageIcon icon =this.getImage();//获取当前对象图片资源 存储到 图片类型的变量icon里面
        //2.给定绘制的坐标
        //paintIcon方法里面要4个内容 按顺序    null,g ,x , y
        icon.paintIcon(null, g, this.x+100, this.y);
                                     //+100只是为了能看得到潜艇而已 后续还要删掉}

记住,paintImage()方法是写在seaobject这个类里,getImage()方法是写在各个子类里,所以就是,在gameworld类里使用paint,然后具体的对象打点调用paintImage()方法,paintImage方法里调用getImage方法,存储图像并输出。

第五个步骤
在GameWorld类的paint中测试:

 //绘制的方法 Java已经提供了 我们直接使用就可以了
    @Override               //g --- 画笔
    public void paint(Graphics g) {
        ImageResources.sea.paintIcon(null,g,0,0);//绘制背景图片
        ship.paintImage(g);
        for (int i = 0; i < submarines.length; i++) {
            submarines[i].paintImage(g);
        }
        for (int i = 0; i < thunders.length; i++) {
            thunders[i].paintImage(g);
        }
        for (int i = 0; i < bombs.length; i++) {
            bombs[i].paintImage(g);
        }}

注意:
这里有一个优化:
就是把原本在gameworld里的action方法里的对象实例化更改一下,在属性里面就声明并初始化,并且对于炸弹和潜艇类和鱼类类的声明需要声明成数组,并且不是固定容量的。因为一场游戏你不知道会有多少个炸弹和潜艇出现,所以先不声明具体的内存容量,以免报错,具体语法如下:

Bomb[] bombs = {};
SeaObject[] submarines = {};
//这是静态初始化的语法
//意味着当前数组空间为空
//变成花括号代表创建的数组对象长度为0

问题:运行游戏以后,潜艇能够绘制出来,但是不能移动.而潜艇的移动应当自动发生.

实现自动发生的操作,需要学习:

三、成员内部类(应用率比较低)

类中套个类,内部的类称之为内部类,外部的类称之为外部类.

1.内部类对其它类不具备可见性
也就是说,其他类无法调用一个类的内部类

2.内部类对象可以在外部类直接创建
就是在外部类那里设置个创建的方法

3.内部类共享外部类的属性和行为.
也就是说,内部类可以直接使用外部类的成员属性和方法。

4.内部类访问外部类 默认隐式写法 外部类名.this.XXX.

class Aoo{//外部类
    
    
    class Boo{//内部类
        
    }
    
}
----------------------------------------------------
package oo.day04;/**
 * 成员内部类的使用演示类:
 */
public class TestDemo {public static void main(String[] args) {
//        Baby b = new Baby(); 内部类对外不具备可见性
        Mama m = new Mama();
    }
}class Mama{ //外部类
    private int a = 10;//外部类的成员变量
    void action(){
        Baby b = new Baby();
    }
    class Baby{//内部类
        private int a = 1;
        void test(){
            Mama.this.a = 100;//可以访问外部类的成员(实例变量,方法等)
            Baby.this.a =20;
        }
    }
}

四、匿名内部类(常用):

没有类名的内部类称之为匿名内部类.

好处:在只想要重写方法的时候,比起做个子类,再继承,再重写,这样更简洁。

适用性:应用在子类只是为了重写父类/(接口)的某个方法时,可以以更加简洁的语法,来实现.

但前提条件是,这个子类/(实现类)在别的地方不用.(因为是匿名的,其他类无法使用)

1.匿名内部类,只会存在于要重写父类(普通/抽象)或接口方法时才会用到.
2.内部类默认认为外部类的数据为final修饰的.匿名内部类,无法修改外部类的值的,如果一旦外部类的数据发生修改,则报错.

package oo.day04;/**
 * 匿名内部类的使用演示类:
 */
public class NSInnerClassDemo {
    public static void main(String[] args) {
//        Boo b = new Boo();
//        b.show();int a = 10;
        a = 100;
        //使用匿名内部类的方式 来实现重写Aoo类中show方法.
        //1.创建Aoo的子类,只不过没有名字.
        //2.将当前子类对象的地址赋值给 sub这个变量
        //3.花括号代表的这是该子类类体.
        Aoo sub = new Aoo(){ //创建的类 是NSInnerClassDemo的匿名内部类
            @Override        //创建的匿名内部类 是 Aoo 的子类.
            public void show() {
//                System.out.println(a); 匿名内部类 默认认为使用的外部类的数据都是final修饰的
                System.out.println("匿名内部类重写Aoo类中的show方法");
            }
        };
        sub.show();}
}class Aoo{ //父类public void show(){
        System.out.println("Aoo这个类的show方法");
    }
}//常规重写
//1.做个子类  2. 实现继承Aoo   3.实现重写
class Boo extends Aoo{
    @Override
    public void show() {
        System.out.println("Boo这个类重写后的show方法");
    }
}

匿名内部类的格式:父类/接口 对象 = new 父类/接口(){ 重写父类/接口中的方法 };

面试题:

问:内部类有独立的.class字节码文件吗?

答: 成员内部类和匿名内部类都有.

外部类 字节码文件 成员内部类 内部类字节码文件

Mama Mama.class Baby Mama$Baby.class

匿名内部类字节码文件

NSInnerclassDemo NSInnerclassDemo.class NSInnerclassDemo$1.class

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq030928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值