目录
软件中的继承:继承(复用)代码,代码不用自己写,继承过来也能用。
一、适用性
当多个类模板中存在一些共有的属性和行为时,且它们在概念上是(is a)一种的关系时,才可以使用继承来去优化冗余的代码。
二、语法
通过 extends 来实现继承。
子类 extends 父类{
}
//子类此时拥有父类的一切内容
三、类的名称
- 父类/超类:存放所有子类共有的属性和行为。
- 子类/派生类:存放自己特有的属性和行为。
- 创建子类对象后,子类对象不仅仅可以访问自己类中定义内容,也可以访问继承得来的内容。因为继承代表自己有!
- 父类对象,只能访问自己的内容。
- 继承具有单一性,传递性。
传递性:
class 爷爷{
传家宝();
}
class 儿子 extends 爷爷{
传家宝();
}
class 孙子 extends 儿子{
传家宝();
}
四、super关键字
1. Java规定,每个类都有自己的构造方法,构造方法是不可以继承的!
2. 而且一个父类在被继承时,要有自己的构造方法,默认的也可以,否则子类会报错!
3. 如果实现了继承,在创建子类对象时,子类构造方法中会先去执行父类的构造方法,然后再执行子类构造方法的内容,若没有明确为父类提供构造方法,系统则执行父类默认的构造方法.
4. 现象原因:子类构造方法中,有一个隐式的写法------super();
5. 若明确写了子类的构造方法,调用父类构造方法的代码要放到子类构造方法中第一行.
6. 若父类中写了有参数的构造方法,而没有写无参的构造方法,那么子类实现继承时,需要明确调用父类的有参的构造方法,否则子类会报错!
this表示当前类的...
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 子的语法就叫做向上造型。
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。
两小:子类重写父类方法时,返回值类型(指的是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循环的次数。
对雷来说也是这样的。
接下来的问题问题:遍历海洋对象数组时,对象的信息不明确,就是打印出来的语句都是一样的。
战舰类:
@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
,子类方法可以为public
或protected
,但不能是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);//设置是否可见
}
记得!要先绘制窗口,再调用实例化创建那些东西。不然会导致窗口出现延迟等等情况。