目录
4.1 异常处理方式之一:捕获异常(try-catch-finally)
4.1 异常处理方式之二:声明异常(声明异常抛出throws)
5、try-with-resource自动关闭Closable接口的资源
一、异常
1、异常机制
异常机制本质:
当程序出现异常,程序安全的退出、处理完后继续执行的机制
2、异常(Exception)的概念
2.1 异常的分析
package studyweek4;
public class 异常 {
public static void main(String[] args) {
System.out.println("step1");
try {//如果没有try—catch则不会打印step2,有的话就会处理异常,并且继续往下执行程序
int i = 1 / 0;
}catch (Exception e){
e.printStackTrace();//打印错误信息
}
System.out.println("step2");
}
}
3、异常的分类
3.1 Error
Error是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时JVM(Java虚拟机)出现的问题。
例如,Java虚拟机运行错误(VirtualMachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。Error表明系统JVM已经处于不可恢复的崩溃状态中。我们不需要管他。
3.2 Exception
Exception是程序本身能够处理的异常,如:空指针异常(NullPointerException)、数组下标越界异常(ArrayIndexOutOfBoundsException)、类型转换异常(ClassCastException)、算术异常(ArithmeticException)等。
Exception类是所有异常类的父类,其子类对应了各种各样可能出现的异常事件。通常Java的异常可分为:
1.RuntimeException运行时异常
2.CheckedException已检查异常
3.3 RuntimeException
派生于RuntimeException的异常,如被0除、数组下标越界、空指针等,其产生比较频繁,处理麻烦,如果显式的声明或捕获将会对程序可读性和运行效率影响很大。因此由系统自动检测并将它们交给缺省的异常处理程序(用户可不必对其处理)。
这类异常通常是由编程错误导致的,所以在编写程序时,并不要求必须使用异常处理机制来处理这类异常,经常需要通过增加“逻辑处理来避免这些异常”。
最常见的异常——空指针异常:
package studyweek4;
public class 异常 {
public static void main(String[] args){
String str=null;
//遇到空指针加个判断:if(str!=null)
System.out.println(str.charAt(0));
}
}
3.4 CheckedException
4、异常的处理(重点)
4.1 异常处理方式之一:捕获异常(try-catch-finally)
注意:有父子关系的子类放前面
上面过程详细解析:
try:try语句指定了一段代码,该段代码就是异常捕获并处理的范围。在执行过程中,当任意一条语句产生异常时,就会跳过该条语句中后面的代码。代码中可能会产生并抛出一种或几种类型的异常对象,它后面的catch语句要分别对这些异常做相应的处理。
一个try语句必须带有至少一个catch语句块或一个finally语句块。
catch:每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象。
常用方法,这些方法均继承自Throwable类。
toString()方法,显示异常的类名和产生异常的原因
getMessage()方法,只显示产生异常的原因,但不显示类名。
printStackTrace()方法,用来跟踪异常事件发生时堆栈的内容。
catch捕获异常时的捕获顺序:
如果异常类之间有继承关系,在顺序安排上需注意。越是顶层的类,越放在下面,再不然就直接把多余的catch省略掉。也就是先捕获子类异常再捕获父类异常。
finally:有些语句,不管是否发生了异常,都必须要执行,那么就可以把这样的语句放到finally语句块中。
通常在finally中关闭程序块已打开的资源,比如:关闭文件流、释放数据库连接等。
//异常处理的经典代码(捕获异常)a.txt中内容:abc
FileReader reader =null;
try {
reader =new FileReader("D:/a.txt");
char c=(char)reader.read();//读取文件中的一个字符
char c2=(char)reader.read();
System.out.println(""+c+c2);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try{
if(reader!=null){
reader.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
}//输出ab
4.1 异常处理方式之二:声明异常(声明异常抛出throws)
注意:当父类没有进行throw抛出异常的时候,那么子类是不能抛出的,只能try-catch,因为子类声明的异常范围不能超过父类声明的范围。
5、try-with-resource自动关闭Closable接口的资源
JAVA中,JVM的垃圾回收机制可以对内部资源实现自动回收,给开发者带来了极大的便利。但是JVM对外部资源(调用了底层操作系统的资源)的引用却无法自动回收,例如数据库连接,网络连接以及输入输出IO流等。这些连接就需要我们手动去关闭,不然会导致外部资源泄露,连接池溢出以及文件被异常占用等。
JDK7之后,新增了“try-with-reasource”。它可以自动关闭实现了AutoClosable接口的类,实现类需要实现close()方法。”try-with-resources声明”,将try-catch-finally简化为try-catch,这其实是一种语法糖,在编译时仍然会进行转化为try-catch-finally语句。
把要打开的资源放在try()中写,就不用使用finally去关闭,编译器会自动关闭资源
6、自定义异常
在程序中,可能会遇到JDK提供的任何标准异常类都无法充分描述清楚我们想要表达的问题,这种情况下可以创建自己的异常类,即自定义异常类。
自定义异常类只需从Exception类或者它的子类派生一个子类即可。
自定义异常类如果继承Exception类,则为受检查异常,必须对其进行处理;如果不想处理,可以让自定义异常类继承运行时异常RuntimeException类。
习惯上,自定义异常类应该包含2个构造器:一个是默认的构造器,另一个是带有详细信息的构造器。
7、IDEA开发环境的调试试图
进行调试的核心是设置断点。程序执行到断点时,暂时挂起,停止执行。就像看视频按下停止一样,我们可以详细的观看停止处的每一个细节。
7.1 断点 breakpoint
程序运行到此处,暂时挂起,停止执行。我们可以详细在此时观察程序的运行情况,方便做出进一步的判断。
二、项目:飞机大战
1、构建游戏窗口
package game1;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
//Frame是awt里面的一个类,因此要import导入
public class MygameFrame extends Frame {
public void launchFrame(){
//初始化窗口
this.setTitle("飞机大战");//窗口标题
setVisible(true);//窗口是否可见
setSize(500,500);//窗口大小
setLocation(500,300);//窗口打开位置
//增加关闭窗口的动作
this.addWindowListener(new WindowAdapter() {//匿名内部类
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);//-1是异常推出程序,0是正常关闭
}
});
}
public static void main(String[] args) {
MygameFrame mygameFrame=new MygameFrame();
mygameFrame.launchFrame();
}
}
2、使用paint方法画图形
@override
public void paint(Graphics g){
g.setColor(Color.red);
g.drawLine(100,100,400,200);
g.setColor(Color.green);
g.drawRect(100,100,300,200);
g.setColor(Color.YELLOW);
g.drawOval(100,100,300,200);
}
3、自建GameUtil类:加载图片代码
package game1;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
/**
* 游戏的工具类
* */
public class GameUtil {
//构造器私有,防止访问
private GameUtil(){
}
public static Image getImage(String path) {
BufferedImage img=null;
URL u =GameUtil.class.getClassLoader().getResource(path);
try {
img= ImageIO.read(u);
} catch (IOException e) {
e.printStackTrace();
}
return img;
}
public static void main(String[] args) {
Image img=GameUtil.getImage("images/plane.png");
System.out.println(img);
}
}
4、双缓冲解决屏幕闪烁问题
//做了解即可,使用直接复制
private Image offScreenImage=null;
public void update(Graphics g){
if(offScreenImage==null)
offScreenImage=this.createImage(500,500);//这是游戏窗口的宽度和高度
Graphics gOff=offScreenImage.getGraphics();
paint(gOff);
g.drawImage(offScreenImage,0,0,null);
}
5、游戏物体根类的实现
package game1;
import java.awt.*;
/**
*游戏物体的跟类
*/
public class GameObject {
Image img;//图片
double x,y;
int speed;//物体移动的速度
int width,height;//宽度和高度
public GameObject(Image img, double x, double y, int speed, int width, int height) {
this.img = img;
this.x = x;
this.y = y;
this.speed = speed;
this.width = width;
this.height = height;
}
public void drawMyself(Graphics g){
g.drawImage(img,(int)x,(int)y,width,height,null);
}
public GameObject(){
}
//所有的物体都是矩形,当你获得对应的矩形的时候,我们就可以做一些相关的判断操作
public Rectangle geiRec(){
return new Rectangle((int)x,(int)y,width,height);
}
}
//在paint外部声明和调用
GameObject plane2=new GameObject(plane,100,100,3,22,33);
//在paint内部使用
plane2.drawMyself(g);
6、面向对象思想重构飞机类
package game1;
import java.awt.*;
public class Plane extends GameObject{
@Override
public void drawMyself(Graphics g){
super.drawMyself(g);
//飞机飞行的算法可以自行设定
x+=speed;
}
public Plane(Image img, double x, double y, int speed) {
super(img, x, y, speed);
}
}
7、键盘控制飞机移动
//prant中定义且判断
if(left){
p1.x-=p1.speed;
}
if(right){
p1.x+=p1.speed;
}
if(up){
p1.y-=p1.speed;
}
if(down){
p1.y+=p1.speed;
}
//创建内部类实现
class KeyMonitor extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {//获取按下按键
System.out.println("按下"+e.getKeyCode());
switch (e.getKeyCode()){
case KeyEvent.VK_LEFT:
left=true;
break;
case KeyEvent.VK_RIGHT:
right=true;
break;
case KeyEvent.VK_UP:
up=true;
break;
case KeyEvent.VK_DOWN:
down=true;
break;
}
}
@Override
public void keyReleased(KeyEvent e) {//松开按键
System.out.println("抬起"+e.getKeyCode());
switch (e.getKeyCode()){
case KeyEvent.VK_LEFT:
left=false;
break;
case KeyEvent.VK_RIGHT:
right=false;
break;
case KeyEvent.VK_UP:
up=false;
break;
case KeyEvent.VK_DOWN:
down=false;
break;
}
}
}
8、面向对象优化键盘控制
把上述代码放在plane类中,在内部类中直接调用即可。
9、炮弹类设计
9.1 炮弹类的设计
package game1;
import java.awt.*;
/**
* 炮弹类
*/
public class Shell extends GameObject{
double degree;//炮弹沿着指定的角度飞行。
public Shell(){
x=200;
y=200;
degree=Math.random()*Math.PI*2;//0到2Π中随机生成角度
width=10;
height=10;
speed=8;
}
//接下来画自己
@Override
public void drawMyself(Graphics g) {
Color c=g.getColor();//记录原来颜色
g.setColor(Color.yellow);
g.fillOval((int)x,(int)y,width,height);//实心圆形
g.setColor(c);//用完只后还回去颜色
//根据算法指定移动路径
x+=speed*Math.cos(degree);
y+=speed*Math.sin(degree);
//碰到边界改变方向
if(y> Constant.GAME_HEIGHT-this.height||y<30){
degree=-degree;
}
if(x<0||x>Constant.GAME_WIDTH-this.width){
degree=Math.PI-degree;
}
}
}
9.2 炮弹任意角度飞行
9.3 容器对象存储多发炮弹
为了存储多发炮弹,我们通过定义一个容器ArrayList来管理这些对象。在paint方法中遍历容器中所有对象,并画出这些炮弹。
(由于现在没学,先使用数组来创建)
prant类里面调用
for(int i=0;i<shells.length;i++){
shells[i].drawMyself(g);
}
//launchFrame中创建
for(int i=0;i<50;i++){
shells [i]=new Shell();//初始化创建50个对象
}
10、炮弹和飞机的碰撞检测
之前提到过,每一个物体都是一个矩形,因此碰撞检测就是判断每一个炮弹矩形和飞机矩形是否相交。
//画炮弹
for(int i=0;i<shells.length;i++){
shells[i].drawMyself(g);
//实现碰撞检测,将所有的炮弹和飞机进行检测,看有没有碰撞
boolean peng=shells[i].getRec().intersects(p1.getRec());
if(peng){
p1.live=false;
}
11、爆炸类
package game1;
import java.awt.*;
/**
* 爆炸类
*/
public class Explode {
//位置
double x, y;
int count=1;
static Image[] imgs = new Image[16];
static {
for (int i = 0; i < 16; i++) {
imgs[i] = GameUtil.getImage("images/explode/"+(i+1)+".gif");
imgs[i].getWidth(null);//解决懒加载问题
}
}
public void drawMySelf(Graphics g) {
if (count < 16) {
g.drawImage(imgs[count], (int)x, (int)y, null);
count++;
}
}
public Explode(){}
public Explode(double x, double y) {
this.x = x;
this.y = y;
}
}
12、记录时间
//记时
public void drawTime(Graphics g){
Color c=g.getColor();
Font f=g.getFont();
g.setColor(Color.green);
if(p1.live) {
period = (System.currentTimeMillis()-start.getTime())/1000;
g.drawString("坚持:1", 30, 50);
} else{
if(end==null) {
end = new Date();
period=(end.getTime()-start.getTime())/1000;
}
g.setFont(new Font("微软雅黑",Font.BOLD,30));
g.drawString("最终时间"+period,190,250);
}
g.setColor(c);
g.setFont(f);
}
13、飞机大战全代码
利用数组创建炮弹目前会有报错,但是不影响运行,以后如果能改进再说
13.1 MyGameFrame类
package game1;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Date;
//Frame是awt里面的一个类,因此要import导入
public class MyGameFrame extends Frame {
Image planeImg=GameUtil.getImage("images/plane.png");//获取图片
Image bg=GameUtil.getImage("images/bg.jpg");
Plane p1=new Plane(planeImg,100,100,7);
Shell[] shells = new Shell[50];
Explode explode;
Date start=new Date();//游戏开始的时间
Date end;//游戏结束的时间
long period=0;//玩了多少秒
@Override
public void paint(Graphics g){//g就当作是一支画笔
//画背景
g.drawImage(bg,0,0,500,500,null);//把图片画出来
//画飞机
p1.drawMyself(g);
//画时间
drawTime(g);
//画炮弹
for(int i=0;i<shells.length;i++){
if(shells[i]!=null) {
shells[i].drawMyself(g);
}
//实现碰撞检测,将所有的炮弹和飞机进行检测,看有没有碰撞
boolean peng=shells[i].getRec().intersects(p1.getRec());
if(peng){
p1.live=false;
//处理爆炸效果
if(explode==null) {
explode = new Explode((int)p1.x,(int)p1.y);
}
explode.draw(g);
}
}
}
public void launchFrame(){
//初始化窗口
this.setTitle("飞机大战");//窗口标题
setVisible(true);//窗口是否可见
setSize(Constant.GAME_WIDTH,Constant.GAME_HEIGHT);//窗口大小
setLocation(400,400);//窗口打开位置
//增加关闭窗口的动作
this.addWindowListener(new WindowAdapter() {//匿名内部类
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);//-1是异常推出程序,0是正常关闭
}
});
new PaintThread().start(); //启动重画窗口的线程
this.addKeyListener(new KeyMonitor());
//初始化创建50个对象
for(int i=0;i<shells.length;i++){
shells[i]=new Shell();
}
}
/**
* 定义了一个重画窗口的线程类。
* 定义成内部类是为了方便直接使用窗口类的相关方法、
*/
//使用线程内部类实现动画效果
class PaintThread extends Thread{//继承Thread类
@Override
public void run(){
while(true){
repaint(); //内部类可以直接使用外部类的成员
try {
Thread.sleep(50); //1s=1000ms,系统睡眠50ms,1s画20次
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 内部类实现键盘的监听处理
*/
class KeyMonitor extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
//System.out.println("按下"+e.getKeyCode());
p1.addDirection(e);
}
@Override
public void keyReleased(KeyEvent e) {
// System.out.println("抬起"+e.getKeyCode());
p1.minusDirection(e);
}
}
//记时
public void drawTime(Graphics g){
Color c=g.getColor();
Font f=g.getFont();
g.setColor(Color.green);
if(p1.live) {
period = (System.currentTimeMillis()-start.getTime())/1000;
g.drawString("坚持:1", 30, 50);
} else{
if(end==null) {
end = new Date();
period=(end.getTime()-start.getTime())/1000;
}
g.setFont(new Font("微软雅黑",Font.BOLD,30));
g.drawString("最终时间"+period,190,250);
}
g.setColor(c);
g.setFont(f);
}
private Image offScreenImage=null;//清除屏幕闪烁问题
public void update(Graphics g){
if(offScreenImage==null)
offScreenImage=this.createImage(500,500);//这是游戏窗口的宽度和高度
Graphics gOff=offScreenImage.getGraphics();
paint(gOff);
g.drawImage(offScreenImage,0,0,null);
}
public static void main(String[] args) {
MyGameFrame mygameFrame=new MyGameFrame();
mygameFrame.launchFrame();
}
13.2 Plane类
package game1;
import java.awt.*;
import java.awt.event.KeyEvent;
public class Plane extends GameObject {
boolean left, right, up, down;//飞机方向控制
boolean live=true;
@Override
public void drawMyself(Graphics g) {
if(live) {
super.drawMyself(g);
//飞机飞行的算法可以自行设定
//x+=speed;
if (left) {
x -= speed;
}
if (right) {
x += speed;
}
if (up) {
y -= speed;
}
if (down) {
y += speed;
}
}
}
public Plane(Image img, double x, double y, int speed) {
super(img, x, y, speed);
}
public void addDirection(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
left = true;
break;
case KeyEvent.VK_RIGHT:
right = true;
break;
case KeyEvent.VK_UP:
up = true;
break;
case KeyEvent.VK_DOWN:
down = true;
break;
}
}
public void minusDirection(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
left = false;
break;
case KeyEvent.VK_RIGHT:
right = false;
break;
case KeyEvent.VK_UP:
up = false;
break;
case KeyEvent.VK_DOWN:
down = false;
break;
}
}
}
13.3 GameObject类
package game1;
import java.awt.*;
/**
*游戏物体的跟类
*/
public class GameObject {
Image img;//图片
double x,y;
int speed;//物体移动的速度
int width,height;//宽度和高度
public GameObject(Image img, double x, double y, int speed, int width, int height) {
this.img = img;
this.x = x;
this.y = y;
this.speed = speed;
this.width = width;
this.height = height;
}
public GameObject(Image img, double x, double y, int speed) {
this.img = img;
this.x = x;
this.y = y;
this.speed = speed;
this.width=img.getWidth(null);//直接获得图片的宽和高,对比上面的方法更加方便
this.height=img.getHeight(null);
}
public void drawMyself(Graphics g){//将物体在窗口中画出
g.drawImage(img,(int)x,(int)y,width,height,null);
}
public GameObject(){//空构造器
}
//所有的物体都是矩形,当你获得对应的矩形的时候,我们就可以做一些相关的判断操作
public Rectangle getRec(){
return new Rectangle((int)x,(int)y,width,height);
}
}
13.4 GameUtil类
package game1;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
/**
* 游戏的工具类
* */
public class GameUtil {
//构造器私有,防止访问
private GameUtil(){//用来被子类继承时super的
}
public static Image getImage(String path) {
BufferedImage img=null;
try {
URL u =GameUtil.class.getClassLoader().getResource(path);
img= ImageIO.read(u);
} catch (IOException e) {
e.printStackTrace();
}
return img;
}
}
13.5 Shell类
package game1;
import java.awt.*;
/**
* 炮弹类
*/
public class Shell extends GameObject{
double degree;//炮弹沿着指定的角度飞行。
public Shell(){
x=200;
y=200;
degree=Math.random()*Math.PI*2;//0到2Π中随机生成角度
width=5;
height=5;
speed=5;
}
//接下来画自己
@Override
public void drawMyself(Graphics g) {
Color c=g.getColor();//记录原来颜色
g.setColor(Color.yellow);
g.fillOval((int)x,(int)y,width,height);//实心圆形
g.setColor(c);//用完只后还回去颜色
//根据算法指定移动路径
x+=speed*Math.cos(degree);
y+=speed*Math.sin(degree);
//碰到边界改变方向
if(y> Constant.GAME_HEIGHT-this.height||y<40){
degree=-degree;
}
if(x<0||x>Constant.GAME_WIDTH-this.width){
degree=Math.PI-degree;
}
}
}
13.6 Explode类
package game1;
import java.awt.*;
/**
* 爆炸类
*/
public class Explode {
//位置
double x, y;
int count=0;
static Image[] imgs = new Image[16];
static {
for (int i = 0; i < 16; i++) {
imgs[i] = GameUtil.getImage("images/explode/"+(i+1)+".gif");
imgs[i].getWidth(null);//解决懒加载问题
}
}
public void draw(Graphics g) {
if (count < 16) {
g.drawImage(imgs[count], (int)x, (int)y, null);
count++;
}
}
public Explode(){}
public Explode(double x, double y) {
this.x = x;
this.y = y;
}
}
13.7 Constant类
package game1;
/**
* 存放相关的常量
*/
public class Constant {
//游戏窗口的宽度
public static final int GAME_WIDTH = 500;
//游戏窗口的高度
public static final int GAME_HEIGHT = 500;
}