Java Lecture 5:面向对象中的继承、super关键字、向上造型、重写与重载与绘制窗口

目录

一、适用性

二、语法

三、类的名称

四、super关键字

五、向上造型

六、重写(Override)

七、重写和重载的区别

八、如何绘制窗口


软件中的继承:继承(复用)代码,代码不用自己写,继承过来也能用。

一、适用性

当多个类模板中存在一些共有的属性和行为时,且它们在概念上是(is a)一种的关系时,才可以使用继承来去优化冗余的代码。

二、语法

通过 extends 来实现继承。

子类 extends 父类{
  
}
//子类此时拥有父类的一切内容

泛化:从多个类中,提取冗余属性和行为的过程,称之为泛化。

三、类的名称

  1. 父类/超类:存放所有子类共有的属性和行为。
  2. 子类/派生类:存放自己特有的属性和行为。
  3. 创建子类对象后,子类对象不仅仅可以访问自己类中定义内容,也可以访问继承得来的内容。因为继承代表自己有!
  4. 父类对象,只能访问自己的内容。
  5. 继承具有单一性,传递性。

传递性:

class 爷爷{
    传家宝();
}
class 儿子 extends 爷爷{
    传家宝();
}
class 孙子 extends 儿子{
    传家宝();
}

四、super关键字

1. Java规定,每个类都有自己的构造方法,构造方法是不可以继承的!

2. 而且一个父类在被继承时,要有自己的构造方法,默认的也可以,否则子类会报错!

3. 如果实现了继承,在创建子类对象时,子类构造方法中会先去执行父类的构造方法,然后再执行子类构造方法的内容,若没有明确为父类提供构造方法,系统则执行父类默认的构造方法.

4. 现象原因:子类构造方法中,有一个隐式的写法------super();

5. 若明确写了子类的构造方法,调用父类构造方法的代码要放到子类构造方法中第一行.

6. 若父类中写了有参数的构造方法,而没有写无参的构造方法,那么子类实现继承时,需要明确调用父类的有参的构造方法,否则子类会报错!

this表示当前类的...

super表示父类的...

super.成员变量 ------------访问父类的成员变量(应用率低)

super.方法 ------------访问父类的方法

super() ------------访问父类的构造方法,如果括号中写了某个参数,那则表示访问父类 对应的有参构造方法.

问题:当前侦察潜艇/水雷潜艇/鱼雷潜艇的构造方法逻辑冗余重复!

解决:在创建子类对象时,会先去执行父类的构造方法,我们可以将这3个构造方法重复多份的代码,提取到父类的构造方法中,达到代码的复用.

在SeaObject类中做一个专门给潜艇类提供的初始化构造方法:

  /**
     * 此构造方法是专门为三种潜艇提供的初始化的构造方法
     * 因为潜艇的宽高不同,所以宽高不能写死,做成形参,由具体的子类使用时传递他的宽高.
     */
    SeaObject(int width,int height){
        this.width = width;
        this.height = height;
        x = -width;
        //随机数公式: Math.random()*(最大值 - 最小值) + 最小值
        y = (int) (Math.random() * (479 - height - 150) + 150);
        speed = (int) (Math.random() * (3 - 1) + 1);
    }

三个潜艇类构造方法代码修改如下:

/**
 * 水雷潜艇类
 */
public class MineSubmarine extends SeaObject{
​
    MineSubmarine() {
        super(63,19);
    }
}
--------------------------------------------
    /**
 * 侦察潜艇类
 */
public class ObserverSubmarine extends SeaObject{
​
    ObserverSubmarine(){
       super(63,19);
    }
​
}
---------------------------------------------
    /**
 * 鱼雷潜艇类
 */
public class TorpedoSubmarine extends SeaObject {
​
    TorpedoSubmarine() {
        super(64, 20);
    }
​
}
剩下4个子类,战舰/深水炸弹/鱼雷/水雷 的构造方法 赋值的过程是重复的,所以可以在父类中提供一个为这个4个类初始化赋值的构造方法


  /**
     *此构造方法是为战舰/深水炸弹/鱼雷/水雷 提供的初始化的构造方法
     * 因为这4个类的具体数据都不同,所以做成5个形式参数,具体由子类来决定赋值的数据内容
     */
    SeaObject(int x, int y, int width, int height, int speed) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.speed = speed;
    }

五、向上造型

存在于继承关系中

1. 声明父 new 子的语法就叫做向上造型。

2. 父大 子小

3.引用类型的遍历,能打点调出什么,取决于当前类型中有什么。

也就是说,比如Person pp = new Student(stuID)

pp.stuID是会报错的,因为person类里没有stuID这个属性。

要访问 stuID 属性,我们需要将 pp 强制转换为 Student 类型,以便在运行时访问 Student 类中定义的属性和方法。

if (pp instanceof Student) {
    Student student = (Student) pp;
    int studentID = student.getStuID();
    // 使用 studentID 进行操作
}

向上造型的好处就是可以通过父类型来代表不同的子类型

语法:
 

class Animal{ //动物类
    
}
class Tiger extends Animal{ //老虎类
    
}
​
main{
      类型           对象    
     Animal a = new Animal();//动物 是 动物  如果代码放到程序中不会报错!语义通
     
     Animal a1 = new Tiger();//老虎 是 动物  语义通
     
     Tiger t2 = new Tiger();//老虎 是 老虎   语义通
    
     Tiger  t1 = new Animal();//动物 是 老虎 语义不通
    
    
}
​
​

问题:运行测试后,当前打印输出的信息不明确.不清楚到底是老师的信息还是学生的信息,还是医生的信息...

解决:可以通过方法的重写来解决.

六、重写(Override)

1.适用性:当实现继承后,子类无法复用父类的方法时,子类可以通过重写方法,来实现自己的逻辑!

2.若子类重写实现了父类的方法,在编译期间,调用的是父类的,运行时,则执行具体子类重写后的那个方法 . 现象:调父 执行子

就是说,写的时候显示是pp.sayhi(),看起来调的是父类的sayhi, 实际上运行的时候是用的pp重写后的sayhi。

3.重写的实现,遵循两同两小一大

两同: 方法名相同,参数列表相同.

两小:子类重写父类方法时,返回值类型(指的是int, byte, long这些返回值类型的大小,不是具体的值的大小),需要等于或者小于父类中的那个方法。大于的话就会报错。

异常要等于或小于父类中的那个方法。

一大:子类重写父类方法时,访问权限要等于或大于父类中的那个方法。

4. 并且,如果父类的方法本身没有返回值的话,子类重写的时候也不能有返回值,不然一定会报错。

学生类重写:
  void sayHi() {
        System.out.println("大家好,我叫:" + name + "我的年龄是:" + age + "我的学号是:" + stuID);
    } 
老师类重写:
      @Override
    void sayHi() {
        System.out.println("大家好,我叫:" + name + "我的年龄是:" + age + "我的工资是:" + salary);
​
    }
医生类重写:
       @Override
    void sayHi() {
        System.out.println("大家好,我叫:" + name + "我的年龄是:" + age + "我的职级是:" + level);
​
    }
​
父类:
    void sayHi() {
        System.out.println("大家好,我叫:" + name + "我的年龄是:" + age);
    }
​
​

问题:在Gameworld类中,由于三种潜艇用三个数组来表示,二种雷用两个数组来表示,那么后期测试时,需要通过很多for循环来遍历对应的数组。比较麻烦。

解决:目前在项目中,因为三种潜艇的行为是一样的,两种雷的行为是一样的,通过分析划分了两个父类型的数组来表示潜艇和雷 。

   Battleship ship; //声明一个战舰类型的变量
    Bomb[] bombs;//声明一个深水炸弹数组类型的变量
    SeaObject[] submarines;//代表三种潜艇类型(侦察潜艇,水雷潜艇,鱼雷潜艇)
    SeaObject[] thunders;//代表二中雷类型(水雷,鱼雷)

也就是说,一个数组里面可以装几种具有类似属性、行为的数组对象。

好处在于:

1. 简洁,不繁琐

2. 调用的时候更容易

但是千万注意!不能乱放,要根据类似的属性和方法和对象进行归类,不然后面会很乱。

所以现在实例化的代码可以改成:
 

    void action() {
        ship = new Battleship();//为战舰类创建战舰对象 并存储在ship这个变量里
        submarines = new SeaObject[9];
        submarines[0] = new ObserverSubmarine();
        submarines[1] = new ObserverSubmarine();
        submarines[2] = new ObserverSubmarine();
        submarines[3] = new MineSubmarine();
        submarines[4] = new MineSubmarine();
        submarines[5] = new MineSubmarine();
        submarines[6] = new TorpedoSubmarine();
        submarines[7] = new TorpedoSubmarine();
        submarines[8] = new TorpedoSubmarine();
        for (int i = 0; i < submarines.length; i++) {
            submarines[i].step();
        }

而原先的情况是:
 

        os = new ObserverSubmarine[3];//创建了侦察潜艇数组对象 并开了3块地 ,这个对象给 os
        os[0] = new ObserverSubmarine();//为数组下标为0 的区域创建对象
        os[1] = new ObserverSubmarine();//为数组下标为1 的区域创建对象
        os[2] = new ObserverSubmarine();//为数组下标为2 的区域创建对象
        for (int i = 0; i < os.length; i++) {
            os[i].step();
            //被攻击的行为
        }
        ms = new MineSubmarine[3];//创建了水雷潜艇数组对象 并开了3块地 ,这个对象给 ms
        ms[0] = new MineSubmarine();//为数组下标为0 的区域创建对象
        ms[1] = new MineSubmarine();//为数组下标为1 的区域创建对象
        ms[2] = new MineSubmarine();//为数组下标为2 的区域创建对象
        for (int i = 0; i < ms.length; i++) {
            ms[i].step();
            //被攻击的行为
        }
        ts = new TorpedoSubmarine[3];//创建了鱼雷潜艇数组对象 并开了3块地 ,这个对象给 ts
        ts[0] = new TorpedoSubmarine();//为数组下标为0 的区域创建对象
        ts[1] = new TorpedoSubmarine();//为数组下标为1 的区域创建对象
        ts[2] = new TorpedoSubmarine();//为数组下标为2 的区域创建对象
        for (int i = 0; i < ts.length; i++) {
            ts[i].step();
            //被攻击的行为
        }

这样的话,减少了代码的繁琐性,也减少了for循环的次数。

对雷来说也是这样的。

接下来的问题问题:遍历海洋对象数组时,对象的信息不明确,就是打印出来的语句都是一样的。

解决:7个子类分别重写父类的step方法

 战舰类: 
@Override
    void step() {
        System.out.println("战舰对象通过键盘左右移动");
    }
深水炸弹类:
     @Override
    void step() {
        System.out.println("深水炸弹y向下运动");
    }
水雷类:
     @Override
    void step() {
        System.out.println("水雷y向上运动...");
    }
鱼雷类:
      @Override
    void step() {
        System.out.println("鱼雷Y向上运动..");
    }
水雷潜艇类:
     @Override
    void step() {
        System.out.println("水雷潜艇X向右运动");
    }
侦察潜艇类:
     @Override
    void step() {
        System.out.println("侦察潜艇X向右运动");
    }
鱼雷潜艇类:
  @Override
    void step() {
        System.out.println("鱼雷潜艇X向右运动");
    }

这里讲一下重写的情况分类

重写的情况分类:
             情况一: Boo子类 只想吃西餐 ----不需要重写
             情况二: Boo子类 只想喝果汁 ----需要重写
             情况三: Boo子类 又想吃西餐,又想喝果汁 -----需要重写
​
class Aoo{//父
    void eat(){
        System.out.println("吃西餐");
    }
}
class Boo extends Aoo{//子
    void eat(){
          super.eat();//调用父类的eat方法.
          System.out.println("喝果汁");
    }
}
Boo b = new Boo();
b.eat();

所以要根据具体情况分析什么时候要重写。

七、重写和重载的区别

这可能是考面试题

重写(override/overrideing):发生父子关系中,方法参数个数和类型要相同. 方法名相同

就是一模一样才能重写。

  • 方法名、参数列表和返回类型与父类方法相同。
  • 访问修饰符不能比父类方法的访问修饰符更严格(例如,父类方法为public,子类方法可以为publicprotected,但不能是private)。
  • 子类方法不能抛出比父类方法更宽泛的异常类型(可以抛出相同的异常或其子类型)。
  • 子类方法必须在相同的类层次结构中声明。

重载(overload/overloading):发生在同类中(继承过来也代表自己有,就是说继承了父类,它也有这个方法) ,方法参数个数或类型要不同. 方法名相同

  • 方法名相同。
  • 参数列表必须不同,可以是参数的数量不同、参数的类型不同或参数的顺序不同。
  • 方法的返回类型可以相同也可以不同。
  • 重载方法可以在同一个类中定义,也可以在父类和子类之间定义。

示例代码:

class Aoo{
    void show(){
        
    }
}
class Boo extends Aoo{
     void show(){  //------发生了重写
        
    }
}
--------------------------------------
  class Aoo{
    void show(){
        
    }
}
class Boo extends Aoo{
     void show(int a){  //------发生了重载
        
    }
}  

重载就是没有覆盖原有的方法,而重写会覆盖,可以这么理解。

而且!重写需要继承,重载不需要。

八、如何绘制窗口

需要用到界面绘制相关的功能,swing包下的内容.

不过现在工作中基本不用了.

java很少用来写游戏了,所以这里了解一下就可以了,只需要知道怎么用就好了,不需要知道原理。

第一步:在GameWorld类上方导入 画框和底板

import javax.swing.JFrame;// 导入画框功能
import javax.swing.JPanel;//导入底板功能

第二步:GameWorld类继承JPanle

public class GameWorld extends JPanel

第三步:为当前类添加绘制窗口的方法,并在main中调用

   /**
     * 绘制窗口的方法
     */
    void paintWorld() {
        //1.做个画框
        JFrame frame = new JFrame();
        this.setFocusable(true);
        //2.为画框添加底板
        frame.add(this);//this代表当前类
        //3.设置画框的相关功能
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置默认关闭操作的状态--- 当点击退出则退出程序(不设置这个的话就没办法退出)
        frame.setSize(641 + 16, 479 + 39);//设置大小
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);//设置是否可见
    }

记得!要先绘制窗口,再调用实例化创建那些东西。不然会导致窗口出现延迟等等情况。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

qq030928

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

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

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

打赏作者

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

抵扣说明:

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

余额充值