一、常量
定义:用static、final修饰的变量
也就意味着结合了这两个关键字的特性
特性:
- 通过类名打点访问
- 不能被二次修改,且声明的时候就需要初始化了(但实际上是可以修改的)
常量的命名:
建议纯大写,有多个单词的时候,用下划线隔开
什么时候用:
当有一份数据,不需要变化,且经常使用。
就是说,牵一发而动全身的类就需要。
方便访问,保护数据的安全性。
语法:
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