Java lecture 9 监听、优化内存及接口

一、实现雷入场的操作

水雷和鱼雷分别由水雷潜艇和鱼雷潜艇来生成的,因为发射的逻辑是一样的,所以可以将发射雷的行为放到父类中来进行复用。 shootThunder(发射类的行为)

注意:
雷应该要在潜艇的头部出现,所以x应该等于潜艇的x+潜艇的宽
y应该等于潜艇的y-5。
接下来要写一下发射雷的行为:

    /**
     * 生成雷的方法,返回值不能写具体的雷类型,否则方法不能通用
     * 返回值 写父类型   因为父类可以不同的子类型。这个要写在SeaObject里,才能被调用
     * 后续会被 水雷潜艇对象调用,也可能会被鱼雷潜艇调用, 当然也有可能会被侦察潜艇调用。
     * */
    public SeaObject shootThunder(){
        int x = this.x + this.width;//雷坐标 的 x 数据
        int y = this.y - 5;//雷坐标 的 y 数据
        // instanceof  关键字, 判断当前对象是否 是某个类型的
        if(this instanceof TorpedoSubmarine){//判断 当前对象是否是 鱼雷潜艇类型
            return  new Torpedo(x,y);//返回鱼雷对象
        }else if(this instanceof MineSubmarine){判断 当前对象是否是 水雷潜艇类型
            return new Mine(x,y);//返回水雷 对象
        }else {
            return null;  //如果代码能执行到这一行则表示侦察潜艇 不返回雷。
        }

    }

 /**
     * 雷入场的方法     -----------放run中进行调用。
     */
    public void thunderEnterAction() {
        /**
         * 1.循环遍历当前潜艇数组,并调用数组每个对象的shootThunder方法,并接收产生的对象
         * 2.判断当前接收的这个雷对象,是否为null   if(对象 != null)
         * 3.不为null 则对thunders数组扩1个容
         * 4.将雷对象 装载到 扩容后的位置上!
         * */
        for (int i = 0; i < submarines.length; i++) {
            SeaObject obj = submarines[i].shootThunder();
            if (obj != null) {
                //3.扩容
                thunders = Arrays.copyOf(thunders, thunders.length + 1);
                //4.装载
                thunders[thunders.length - 1] = obj;
            }
        }
    }

深水炸弹是通过按下键盘的空格键来进行发射的。是战舰来发射的。

深水炸弹的生成是基于一个事件来发生的。-------当按下了空格键。

什么是事件:发生了一件事情。 (去餐厅点一份餐)

事件处理:发生事件以后要做的事情 (点完餐让服务员通知你取餐)
侦听:用来检测事件处理的条件 有没有达成 (服务员实时侦听有没有出餐)

Java提供了监听的包和功能
在GameWorld类上当 导入两个包

import java.awt.event.KeyEvent;//键盘事件
import java.awt.event.KeyAdapter;//键盘侦听器

因为KeyAdapter里有抽象方法,所以我们要进行重写。
为了重写,节省时间,而且这个重写的方法我们也只是在这里用一次而已,所以在这里我们和重写run方法意义,使用匿名内部类对要使用的方法进行重写。
比如KeyTyped和KeyPressed。

    void action() {
//        KeyAdapter  匿名内部类来实现需要侦听处理的逻辑。
        KeyAdapter adapter = new KeyAdapter() {//创建具体的监听对象
            @Override
            public void keyPressed(KeyEvent e) {//重写按下按键后,要执行的方法
                System.out.println("按下了键盘");
            }
        };
        this.addKeyListener(adapter);//用于添加所监听的监听对象

在action方法中写。
此时是按了任意键都可以监听到,但是目前我们的需求是,按了空格键才会被监听到。
所以我们要加一点限制条件。
这个限制条件就是KeyEvent e,所以我们要对应去寻找KeyEvent里的封装的按键。
里面都是VK_Alt/ VK_space这些,下划线后代表的是具体的按键。
如果不确定自己所想要按的那个键的具体名称是什么,可以在keyPressed方法里加入一个语句:

e.getKeyCode();//返回当前用户按下的按键

所以最终的代码:

    void action() {
//        KeyAdapter  匿名内部类来实现需要侦听处理的逻辑。
        KeyAdapter adapter = new KeyAdapter() {//创建具体的监听对象
            @Override
            public void keyPressed(KeyEvent e) {//重写按下按键后,要执行的方法
                if (e.getKeyCode() == KeyEvent.VK_SPACE) {//判断是否按的是空格键
                    bombEnteraction();//是的话深水炸弹入场
                }
            }
        };
        this.addKeyListener(adapter);//用于添加所监听的监听对象

在此之前,需要在战舰类中添加shootBomb方法:

   /**
     * 当外部的战舰对象,调用发射的深水炸弹方法时,返回深水炸弹对象。
     * */
    public Bomb shootBomb(){
        return new Bomb(this.x,this.y);//返回深水炸弹,坐标跟战舰一样。
    }

深水入场在GameWorld类中添加方法,并在按下空格键的if代码块中调用

 /**
     * 深水炸弹入场的方法      放在当按下键盘的空格键进行调用
     */
    public void bombEnterAction() {
        /**  1.通过战舰对象调用shootBomb方法  并接收
         *   2.为深水炸弹数组扩1个容
         *   3.将接收的对象 装载 到深水炸弹扩容后的位置。
         * */
        Bomb b = ship.shootBomb();//1
        bombs = Arrays.copyOf(bombs, bombs.length + 1);//2
        bombs[bombs.length - 1] = b;//3
    }

目前的问题:
所有的潜艇/雷/深水炸弹,当移动出屏幕外,虽然看不见 但是实际还存储在我们的内存中。
所以此时需要优化内存

二、优化内存

因为内存本身是有限的,并且需要考虑使用的游戏/软件的性能,所以优化内存很有必要。

内存泄漏:指的就是不断在程序中创建对象的过程。

内存溢出:指的就是内存没有空间可用,(报异常错误!)

垃圾回收器:处理的是没有被引用内存对象。

垃圾回收器可以将移出屏幕外的对象,变成不被引用对象,则可以被垃圾回收器回收。
所以想要回收这些屏幕外的对象的话,我们需要把它们变成不被引用的对象。

因为6个类都需要判断自身产生对象是否越界的行为,所以可以提取到父类中,做一个判断是否越界的方法。
在这里,我们通过是否越界来判定对象是否属于不被引用对象。
那如何不被引用?就是把该对象踢出数组,这样的话就无法遍历它,无法引用了。

所以此时我们对潜艇越界的标准:

SeaObject类中:
      /** 判断是否越界的方法:
     * 为什么不做抽象方法而做普通方法,
     *                         因为三种潜艇的判断越界标准是一样的,所以可以写在父类中复用
     *                         其它三个类,自行进行重写自己的判断逻辑即可。
     *
     * */
    public boolean isOutBounds(){
        return this.x >= GameWorld.WIDTH;//判断当前对象是否超出右侧屏幕越界
    }
深水炸弹
       @Override
    public boolean isOutBounds() {
        return this.y >= GameWorld.HEIGHT;//判断深水炸弹的Y是否大于等于窗口的高。
    }
水雷:
    @Override
    public boolean isOutBounds() {
        return this.y <= 150-this.height; //如果水雷的Y小于等于水平面负一个图片的高度 则需要优化。
    }
鱼雷:
       @Override
    public boolean isOutBounds() {
        return this.y <= -this.height;//如果鱼雷的y小于等于 -图片的高度。
    }

删除越界对象的行为,是自动发生的,在GameWorld类中添加一个删除对象越界的方法。并在run中调用。所以在这里写了一个outOfBounds方法用来调用三类物体的边界处理。

    /**
     * 删除越界对象的方法  并放在run中的移动的方法下面调用。
     */
    public void outOfBounds() {
        //1.遍历当前潜艇数组
        submarines = deleteOutOfBounds(submarines);//接受方法处理后的数组对象
        //遍历当前深水炸弹数组处理缩容
        bombs = (Bomb[])deleteOutOfBounds(bombs);//因为方法返回值类型为SeaObject[] 但是接受的是bombs[] ,所以需要强转
        //遍历当前雷数组处理缩容
        thunders = deleteOutOfBounds(thunders);//接收方法处理后的数组对象
    }public SeaObject[] deleteOutOfBounds(SeaObject[] objects) {
        for (int i = 0; i < objects.length; i++) {
            //2.判断潜艇数组中的每个对象是否越界
            if (objects[i].isOutBounds()) {
                objects[i] = objects[objects.length - 1];//将数组最后一个对象赋值覆盖当前越界的对象空间里
                objects = Arrays.copyOf(objects, objects.length - 1);//删除最后一行
            }
        }
        return objects;//需要将当前缩容后的数组对象 返回出去
    }//这里之所以要返回是因为在最后一句那里objects被重新赋值了一个地址,
    //导致原来的objects数组指向的地址被改变了,不再是传入的那个数组
    //所以最后返回这个objects是让原数组接受更改后的数组
   

下一步:
实现战舰的移动:

通过按下了键盘的(左键 ⬅) 或(右键 ➡) 来实现移动。

  public void leftMove(){
        this.x -= speed; //左移
    }
    public void rightMove(){
        this.x += speed; //右移
    }

在action方法中的键盘侦听逻辑内加上后,变成:

    void action() {
//        KeyAdapter  匿名内部类来实现需要侦听处理的逻辑。
        KeyAdapter adapter = new KeyAdapter() { //创建具体侦听对象
            @Override
            public void keyPressed(KeyEvent e) {//重写按下键盘后 需要执行的方法。
                //e.getKeyCode(); 返回当前用户按下的按键
                if (e.getKeyCode() == KeyEvent.VK_SPACE) {//判断用户按下的键是不是键盘的空格键
                    bombEnterAction();//深水炸弹入场
                }
                if (e.getKeyCode() == KeyEvent.VK_LEFT) {//判断用户按下的是否是左移
                    ship.leftMove();
                } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {//判断用户按下的是否是右移
                    ship.rightMove();
                }
            }
        };
        this.addKeyListener(adapter);//键盘自定义的键盘侦听加载检测当中。

至此,潜艇的移动及各战舰的炸弹投放已实现。下一步,要回到游戏结束的加分逻辑。:

当深水炸弹若打到潜艇时,是有不同的处理逻辑.
1.加分: 打到侦查潜艇加10分,鱼雷潜艇加40分.
2.加命: 打到水雷潜艇 加 1条命.

三、接口

为什么要引入接口:
有一些类有共有的行为,但它们并不一定是继承关系,可能只是一样有这个行为。目前在项目中,遇到的问题是,部分潜艇是加分,部分潜艇是加命,所以需要根据这两种可能去进行接口的构建。

如何理解:和继承(概念上的统一父类)不同,可以理解为继承是认亲爹(因为只能继承一个类),而实现接口是认干爹(因为可以有多个)。

定义:接口是一组行为的规范(行为上的统一父类),不关心多个具体实现的类之间的关系,关心的是它们的这个行为是否统一。
举个例子,假设这个黑色的圆圈是炸弹。
请添加图片描述

注意:接口一旦被类实现,那么该类必须重写接口中的所有方法!
所以一般接口只实现一个功能。

  1. 接口是一种引用类型
  2. 接口使用interface+接口名定义
  3. 接口中只能放常量和抽象方法,并且它的成员默认权限为public。而且如果在接口中写了比如int a =10这种写法,java是默认把它变为常量的,也就是static final int a = 10,所以不会报错,但是如果尝试在main方法中修改它时,就会报错。方法也是,不用写abstract和public,因为是默认的。
  4. **接口不能被创建对象.**比如:不能写Interger i = new Interger() 因为接口是需要类来实现它的。
  5. 子类实现接口 需要通过 implements 关键字来实现 比如:class Aoo implements Interger:
  6. 子类若实现了接口,必须重写接口中所有的抽象方法.
  7. 一个类 是可以实现多个接口的! 比如 “class Aoo implements Inter1, Inter2 :”
  8. 若一个子类,又继承了某个类 也实现了某个接口,语法: 子类 extends 父类 implements 接口。先继承后实现关系。
  9. 接口与接口之间是可以继承的 ,例如: B接口 继承了 A接口 , 那么C类如果实现B接口,那么在B接口中需要实现A接口中的抽象内容才可以。

四、接口与抽象类的区别

  1. 抽象类可以放构造方法 , 接口没有构造方法.

  2. 抽象类可以放普通成员(普通的方法,成员变量), 接口不可以.

  3. 抽象类还可以放抽象方法;接口也可以放抽象的方法,JDK1.8版本接口中,是还可以放静态的方法.

  4. 抽象类中的成员(变量/方法),可以任意添加不同的访问修饰符. 接口中访问修饰符默认是public或者修改为protected.

  5. 接口不可以实现接口,但是可以继承接口.

  6. 一个类只能继承一个类,但是可以实现多个接口.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

qq030928

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

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

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

打赏作者

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

抵扣说明:

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

余额充值