对象生存周期管理与内存泄漏
对象生存周期管理与内存泄漏
1 飞机生存周期
一个对象创建到使用直到最终销毁的过程称为对象的生命周期。
1.1 定义飞机状态
计算机只能处理数据,只有将飞机的生命周期数量化才能使用利用程序进行处理:
- LIVING(1):活着状态,飞机刚刚创建的状态,其生命值是满的
- DEAD(0):死亡状态,飞机被打击很多次,生命值为0的时候,进入死亡状态,死亡状态时候播放爆炸动画帧,动画帧播放结束以后进入僵尸状态
- ZOMBIE(-1):僵尸状态,飞机的动画播放结束以后进入僵尸状态,僵尸状态的飞机不绘制了,等待被从内存中删除和垃圾回收
利用算法实现状态的切换:
- new计算调用构造器用于创建飞机,创建飞机时候设计飞机的生命值
- 每次飞机和弹击发生碰撞执行飞机的hit方法表示飞机被击中,在hit方法中减少生命值
- 当生命值减少到0的时候,就进入到DEAD状态
- 为飞机增加goDead(去死)方法,用于处理与英雄机,活着碰到炸弹时候,一次消除全部生命值,直接进入DEAD状态
- 更新nextimage方法
- LIVING状态时候播放动画图片
- DEAD状态播放爆炸效果图片,如果播放完毕就转到ZOMBIE状态
这些算法和数据都在FlyingObject中定义。
1.2 管理活着状态
package demo01;
import java,awt.Graphics;
import javax.swing.ImageIcon;
/*
* 父类中定义从子类抽取的属性和方法
* 这种抽取方式称为“泛化”
*/
public abstract class FlyingObject {
public static final int LIVING=1;
public static final int DEAD=0;
public static final int ZOMBIE=-1;//定义活着,死亡,僵尸
protected int state=LIVING;//定义开始飞机都为活着
protected int life=1;//定义生命值都为一条命
protected double x;
protected double y;
protected double width;
protected double height;
protected double step;
protected ImageIcon image;
//当前对象动画帧,如果没有:如子弹、天空,则此属性保持null
protected ImageIcon[] images;
//爆炸效果动画帧,如果没有保持null
protected ImageIcon[] bom;
//动画帧播放计数器,%数组长度得到播放动画帧的位置
protected int index=0;
//无参数构造器,减少子类的编译错误
public FlyingObject(){
}
//根据位置初始化x,y,image,images,bom
public FlyingObject(double x, double y,
ImageIcon image, ImageIcon[] Images, ImageIcon[] bom) {
//当前图片 全部动画帧 爆炸图片
this.x=x;
this.y=y;
this.image=image;
this.images=Images;
this.bom=bom;
width=image.getIconWidth();
height=image.getIconHeight();
}
public abstract void move();
/*
* 打击方法,每打击一次,减少一次生命值,返回true
* 如果生命值为0则进入死亡状态,如果再次调用这个方法将返回false
*/
public boolean hit(){
if(life>0){
life--;
if(life==0){
state=DEAD;
}
return true;
}
return false;
}
//动画帧播放方法
public void nextImage(){
//没有动画帧时候,不播放动画帧图片
if(images==null){
return;
}
//System.out.println(index +","+(index % images.length));//打桩
image=images[(index++/30) % images.length];//除以30等于调慢30倍
}
public void paint(Graphics g){
nextImage();//换动画帧,然后再绘制
image.paintIcon(null,g,(int)x, (int)y);
}
@Override
public String toString() {
String className=getClass().getName();
return className+" [x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]";
}
/*
*检测碰撞方法:
*1.在父类型中定义的duang方法可以被任何子类继承,所有子类都获得了碰撞检测功能
*2.将方法中的数据类型定义为FlyingObject,就可以处理各种多态参数
*/
public boolean duang(FlyingObject bu) {
FlyingObject p=this;
//计算小飞机的内切圆数据
double r1=Math.min(p.width, p.height)/2;
double x1=p.x+p.width/2;
double y1=p.y+p.height/2;
//计算子弹的内切圆数据
double r2=Math.min(bu.width, bu.height)/2;
double x2=bu.x+bu.width/2;
double y2=bu.y+bu.height/2;
//利用勾股定理计算圆心距离
double a=y2-y1;
double b=x2-x1;
double c=Math.sqrt(a*a + b*b);
//如果圆心距离小于半径和就表示两个圆相交 就是发生了碰撞
//System.out.println(c+","+(r1+r2));
return c<r1+r2;
}
}
测试案例:
package demo01;
public class HitDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
/*
* 测试打击方法
*/
Airplane plane=new Airplane();
System.out.println(plane.state);//1
System.out.println(plane.hit());//true
System.out.println(plane.state);//0
System.out.println(plane.hit());//false
}
}
1.3 设定大飞机生命值
package demo01;
public class Bigplane extends Plane{
//飞机从屏幕上方随机位置出场
public Bigplane(){
super(Images.bigplane[0], Images.bigplane, Images.bom);
step=Math.random()*3+0.5;
lift=5;//大飞机生命值
}
//从自定位置出场
public Bigplane(double x, double y, double step) {
//利用super()调用父类有参数构造器,复用了父类中构造器算法
super(x, y, Images.bigplane[0], Images.bigplane, Images.bom);
this.step=step;
lift=5;//大飞机生命值
}
}
测试:
package demo01;
public class BigplaneDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
/*
* 测试大飞机打五次为死亡
*/
Bigplane plane=new Bigplane();
System.out.println(plane.state);//1
System.out.println(plane.hit());//true
System.out.println(plane.state);//1
System.out.println(plane.hit());//true
System.out.println(plane.state);//1
System.out.println(plane.hit());//true
System.out.println(plane.state);//1
System.out.println(plane.hit());//true
System.out.println(plane.state);//1
System.out.println(plane.hit());//true
System.out.println(plane.state);//0
System.out.println(plane.hit());//false
}
}
1.4 去死方法
为FlyingObject设定GoDead(去死)方法,这个方法的目的在遇到严重撞击时候,可以立即将飞机状态设置为DEAD。
/*
* 狗待方法
*/
public boolean goDead() {
if(state==LIVING) {//如果当前状态为活着
life=0;//把生命值设成0
state=DEAD;//把当前状态设为死亡的
return true;//死亡返回true
}else {
return false;//没死亡返回false
}
}
测试案例:
package demo01;
public class GoDeadDemo {
public static void main(String[] args) {
/*
* 测试去死
*/
Bigplane plane=new Bigplane();
System.out.println(plane.state);//1
System.out.println(plane.goDead());//true
System.out.println(plane.state);//0
System.out.println(plane.goDead());//false
}
}
1.5 播放爆炸图片
/*
* 动画帧的播放方法
*/
private int i=0;
public void nextImage() {
switch(state) {//设置当前状态
case LIVING://第一种情况,活着,注意符号
//没有动画帧时候,不播放动画帧图片
if(images==null) {
return;
}
//System.out.println(index +","+(index % images.length));//打桩
image=images[(index++/30) % images.length];//除以30等于调慢30倍
break;//switch语句要写break
case DEAD://另一种情况 死了,注意符号
int index=i++/30;
if(bom==null) {//如果没有这个照片,不做处理
return;
}
if(index==bom.length) {//照片如果播放到头,成僵尸状态
state=ZOMBIE;
return;
}
image=bom[index];//不是僵尸状态,正常播放取照片交给当前照片
}
}
测试:
package demo01;
public class NextImageDemo {
public static void main(String[] args) {
Bigplane plane=new Bigplane();
//活着的时候播放正常图片
//执行nextImage方法30次会切换一次图片
System.out.println(plane.state);
for(int i=0; i<50; i++) {
plane.nextImage();//每次调用此方法
System.out.println(plane.image);
}
System.out.println(plane.hit());
System.out.println(plane.hit());
System.out.println(plane.hit());
System.out.println(plane.hit());
System.out.println(plane.hit());
System.out.println(plane.hit());
System.out.println(plane.state);
//死掉以后播放死亡动画
//执行nextImage方法30次会切换一次图片
for(int i=0; i<121; i++) {
plane.nextImage();
System.out.println(plane.image);
}
//播放完以后进入僵尸状态
System.out.println(plane.state);
}
}
测试结果:
...
images/bigairplane1.png
images/bigairplane1.png
true
true
true
true
true
false
0
images/bom1.png
images/bom1.png
...
2 打掉飞机
飞机消失就是将飞机数组缩容,将飞机从数组中删除。
2.1 检测飞机状态
为了方便检测当前飞机和子弹的状态,重构FIyingObject增加检测飞行物状态的方法:
- isLiving 检测飞行物是否是活的
- isDead 检测飞行物是否是死的
- isZombie检测飞行物是否是僵尸
public boolean isLiving(){
return state=LIVING;
}
public boolean isDead(){
return state=DEAD;
}
public boolean isZombie(){
return state=ZOMBIE;
}
测试案例:
package demo01;
public class StateDemo {
public static void main(String[] args) {
Airplane plane=new Airplane();
System.out.println(plane.state==FlyingObject.LIVING);//检测飞机状态是不是活着状态
System.out.println(plane.isLiving());//同上
System.out.println(plane.isDead());//没死
System.out.println(plane.isZombie());//非僵尸状态
plane.goDead();//死亡
System.out.println(plane.isLiving());
System.out.println(plane.isDead());
System.out.println(plane.isZombie());
}
}
2.2 重构Word类中的hitDetection方法
public void hitDetection() {
//拿到每个子弹
for(int i=0; i<bullets.length; i++) {
if(!bullets[i].isLiving()) {
continue;
}
for(int j=0; j<planes.length; j++) {
if(!planes[j].isLiving()) {
continue;
}
if(planes[j].duang(bullets[i])) {
//System.out.println(planes[j]+"撞到:"+bullets[i]);
bullets[i].goDead();//子弹去死
planes[j].hit();//飞机被打一下
}
}
}
}
2.3 重构定时任务
private class PaintTask extends TimerTask{
public void run(){
index++;
fireAction();
createPlane();
//执行飞机移动方法;是多态的移动方法,每个飞机都不同
for(int i=0; i<planes.length;i++){
if(planes[i].isLiving()){
planes[i].move();
}
}
for(int i=0; i<bullets.length;i++){
if(bullets[i].isLiving()){
bullets[i].move();
}
}
hitDetection();
sky.move();
repaint(); //调用重写绘制方法,这个方法会自动执行paint
}
}
2.4 清除僵尸飞机、子弹
缩容实现,具体步骤:
- 创建一个新数组
- 将原数组中的非僵尸飞机复制到新数组
- 对新数组复制缩容并且替换原数组
在World类中编写clean方法:
public void clean(){
FlyingObject[] living=new FlyingObject[planes.length];
int index=0;
for(int i=0; i<planes.length; i++){
if(planes[i].isZombie()){
continue;
}
living[index++]=planes[i];
}
planes=Arrays.conpyOf(living, index);
}
由于planes数组是私有的,则在World类中编写测试方法:
public void testClean(){
System.out.println(planes.length);
planes[0].state=FlyingObject.ZOMBIE;
planes[3].state=FlyingObject.ZOMBIE;
clean();
System.out.println(planes.length);
}
测试案例:
package demo01;
public class TestClean{
public static void main(String[] args){
//测试清除方法 结果10 8
World world=new World();
world.testClean();
}
}
重构clean方法增加删除子弹的算法:
public void clean() {
FlyingObject[] living=new FlyingObject[planes.length];
int index=0;
for(int i=0; i<planes.length; i++) {
if(planes[i].isZombie()) {//死亡走下一步
continue;
}
living[index++]=planes[i];
}
planes=Arrays.copyOf(living, index);
Bullet[]arr=new Bullet[bullets.length];
index=0;
for(int i=0; i<bullets.length; i++) {
if(bullets[i].isDead()) {//死亡走下一步
continue;
}
arr[index++]=bullets[i];
}
bullets=Arrays.copyOf(arr,index);
}
2.5 定时清除僵尸机,子弹
由于定时器中移动飞机和子弹的算法比较臃肿,可以进行重构,将移动算法抽取成objectMove方法。
由于定时器任务不仅用于绘制界面,而且还负担着软件的业务循环功能,所以可以将类名从PaintTask重构为LoopTask。
private class LoopTask extends TimerTask{//需要隐藏的属性和方法定义为private
public void run() {
index++;
fireAction();
createPlane();//如果还想创建飞机直接调用方法
sky.move();//不进行move方法。paint方法重写。进行单独处理将无法呈现连续的天空(sky)
//调用重写绘制方法,这个方法会自动执行paint
objectMove();
hitDetection();
clean();//勿忘调用
repaint();
}
}
private void objectMove() {//把内部类移动至当前的外部类
for(int i=0; i<planes.length; i++) {//执行飞机移动方法;是多态的移动方法,每个飞机都不同
if(planes[i].isLiving()) {//如果活着就动,死了便不动了
planes[i].move();//利用for循环简化各项飞行代码
}
}
for(int i=0; i<bullets.length; i++) {
if(bullets[i].isLiving()) {//如果活着就动,死了便不动了
bullets[i].move();
}
}
}
启动定时器的代码也需要修改:
Timer timer=new Timer();
LoopTask task=new LoopTask();
timer.schedule(task,1000, 1000/100);
3 内存泄漏与内存溢出
3.1 Java垃圾回收(GC)
垃圾回收器工作时候:
- 垃圾回收会在适当时候自动启动
- 定时自动启动
- 利用System.gc()方法通知启动
- 利用算法检测对象,是否是内存垃圾
- 对象不被引用判断为垃圾
- 将标记为垃圾的对象回收,释放内存空间
- 回收对象时,会调用其finalize方法,如果JVM很快退出可能来不及执行此方法。
案例:
package demo02;
public class GCDemo{
public static void main(String[] args){
//GC测试
//创建两个对象
Foo f1=new Foo();
Foo f2=new Foo();
//第一个创建的对象不被引用,称为内存垃圾
f1=null;
//f2=null;
//通知启动GC
System.gc();
//输出语句可以延长Java VM结束时间
System.out.println("OK");
}
}
3.2 内存泄漏,内存溢出
被引用的对象非垃圾对象:
- 正在使用的子弹,被bullets数组引用,不是垃圾对象
- 击中飞机死亡的子弹,从bullets数组中删除,称为垃圾对象
- 飞出屏幕的无用子弹,被bullets数组引用,不是垃圾对象
3.3 飞行物出界检测
定义:FlyingObject类
在FlyingObject中添加检测出界算法outOfBounds方法,返回值是boolean,true表示出界,flase表示没有出界
public boolean outOfBounds(){
if(y<(-height-50)){
return true;
}else if(y>700+50){
return true;
}else{
return false;
}
}
测试:
public class outOfBoundsDemo {
public static void main(String[] args) {
//测试边界检测
Airplane plane=new Airplane();
for(int i=0; i<500; i++) {
System.out.println(plane);
System.out.println(plane.outOfBounds());
plane.move();
}
Bullet bullet=new Bullet(200,500);
for(int i=0; i<500; i++) {
System.out.println(bullet);
System.out.println(bullet.outOfBounds());
bullet.move();
}
}
}
3.4 清除出界的飞机
将出界的飞机和子弹从数组中移除。
重构clean方法,将出界的子弹和飞机一并清除:
public void clean() {
FlyingObject[] living=new FlyingObject[planes.length];
int index=0;
for(int i=0; i<planes.length; i++) {
if(planes[i].isZombie()||planes[i].outOfBounds()) {//死亡并且解决内存泄漏走下一步
continue;
}
living[index++]=planes[i];
}
planes=Arrays.copyOf(living, index);
Bullet[]arr=new Bullet[bullets.length];
index=0;
for(int i=0; i<bullets.length; i++) {
if(bullets[i].isDead()||bullets[i].outOfBounds()) {//死亡并且解决内存泄漏走下一步
continue;
}
arr[index++]=bullets[i];
}
bullets=Arrays.copyOf(arr,index);
//System.out.println(planes.length+","+bullets.length);
}
每天对着镜子里的自己说一句:今天的你也很棒!