第八章 异常处理
try {
// do risky thing
} catch (Exception ex) {
// try to recover
}
大部分时间都在处理异常,而不是抛异常,那怎么抛呢。
1、Risky , exception-throwing code:
public void takeRisk
() throws BadException {
if (abandonAllHope) {
throw new BadException();
//抛异常的时候,新建一个异常对象,抛
}
}
注意:方法上,必须声明,我要抛异常。
注意:声明的是throws,不是throw,真正抛的时候用throw。
注意:声明的throws在方法的那个括号之后!一定小心,稍不留神就忘了。
2、Your code that calls the risky method:
public void crossFingers() {
try {
anObject.takeRisk();
}
catch (BadException ex) {
systempout.println("Aaargh!!!");
ex.printStackTrace();
}
}
注意:如果你搞不定catch住的异常,至少打印下StackTrack。
注意:
异常处理有两种方式:1、是try catch块包围;2、是继续抛出,即躲开异常;
注意:编译器会检查除了RuntimeException以外的异常。这里当然还包括所有从RuntimeException继承下来的所有异常。编译器都会检查,即必须保证:
1、如果你抛异常,那必须在方法上声明要抛异常,抛什么异常。
2、如果调用了个会抛异常的方法,必须说服编译器你知道他会抛异常。即有try catch块包围,或者躲开它(下面讲呢)。
为什么不检查RuntimeException?
答:因为RuntimeException基本都是你自己代码逻辑的问题,不像别的异常,如IO异常,读取一个文件,这个文件在不在不是代码能保证的,所以这种异常可以检测。
注意:一个方法,不是只能抛一种异常的,可以抛很多,都要声明出来。当然使用这个方法的时候,所有种类异常都得要接住。
try catch finally
try中,遇到出异常的语句后,即使该语句后还有内容,也不再执行了,直接跳到catch去执行。
finally是不管try内有没有异常抛出,都必须在最后执行。即,如果try内有异常出现,则先去执行catch,执行完catch后,执行finally;如果try内没异常出现,则try执行完后,直接去final执行。
注意:如果try 内 或者 catch内 有return语句,finally还是要执行!先把final执行完了,再return。
注意:是finally,不是final。
异常的多态
前面说了,异常都是在一条继承链上的,最上面的是Exception类。那么也即是说,异常也是能够使用多态的,如下:
我又一大堆异常类要抛,PantsException TeeShirtException LingerieException DressShirtException,我可以直接写一个它们共同的父类异常,这样把它们都囊获了。
然后接异常的时候,我可以写:
注意:我抛的时候是ClothingException,即父类异常,接的时候也得是父类异常,不能变成了子类异常了。但抛的时候是许多子类异常,那接的时候变成只有父类异常是可以的。实测过。
于是,注意里就引出来个问题,就是如果有很多异常要接,那接的时候是有顺序问题的。即,
父类异常要最后接,子类的要先接,兄弟异常之间没有先后顺序,都可以。
duck 躲开异常
如果发现调用一个方法会抛异常,然后自己又处理不了,还一个处理方式,就是继续将这个异常向外抛。
看到那个大大的throws了吗?而且,
注意,
该方法内部没有throw。这就是异常是自己原创,还是接别人再往外抛出的一个区别。
当一个类内的方法risky,抛出异常的时候,这个方法立即出栈,异常抛给栈上的最顶端函数,即刚才的caller。然后这个caller要么try catch,要么继续抛异常,如果选择继续抛,它这个函数(方法)也立即出栈,异常继续向栈底走。
这么连续的一直抛,谁也不接异常,那最终得到该异常的就是JVM,这时候就再没地方抛了,JVM直接结束。
注意:main函数也是不可以duck的,过不了编译,这里仅仅举例。
异常四大规则
1、不能只有catch 或 finally,而没有try。
void go() {
Foo f = new Foo();
f.foof();
catch (FooException ex) { }
}
NOT LEGAL!
2、不能在try catch块 之间 添加代码。
try {
x.doStuff();
}
int y = 43;
catch (Exception ex) {}
NOT LEGAL!!
3、try后,必须跟catch 或者 finally。
try {
x.doStuff();
} finally {
//cleanup
}
LEGAL! 没catch都行呢,但不能没try。
4、如果没catch,只有个finally,那该方法声明的时候必须声明异常,即躲避异常。
void go()
throws FooException {
try {
x.doStuff();
} finally { }
}
看那个throws。
第九章 GUI
JFrame是最基本的,就是窗口,不同的JVM,也就是操作系统,JFrame会有不同的样子。
JFrame上,能够放各种小东西。在javax.swing 包中找,常用的主要有:JButton JRadioButton JCheckBox JLabel JList JScrollPane JSlider JtextArea JTextField JTable。
都很方便,JTable稍微麻烦一点。
制作GUI典型步骤:
1、创建frame( a JFrame)
JFrame frame = new JFrame();
2、设置默认关闭方法
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
3、创建widget (button text field 等)
JButton button = new JButton("Click Me");
4、添加widget到frame
frame.getContentPane().add(button);
5、显示它 ,设置大小,和可见性
frame.setSize(300,300); // 单位为像素
frame.setVisible(true);
注意:第二步很容易忘,如果没有写,那关闭时,点击关闭,但是窗口实际没有关,依然存在。
注意:widget不是直接添加到frame上的,frame只是个窗口,你添加东西是加到窗口的 面板上!pane上。
事件监听
举个例子,添加button 按下事件的监听:
1、准备响应button事件的类,要实现 ActionListener 接口。
2、向button注册响应button事件的对象。
3、准备响应button事件的类,要设置好响应处理函数。
import javax.swing.*
import java.awt.event.*;
//一定要记得引包,包含监听接口的包
public class SimpleGuilB
implements ActionListener {
//第一步,实现接口
JButton button;
public static void main (String[] args) {
SimpleGuilB gui = new SimpleGuilB();
gui.go();
}
public void go() {
JFrame frame = new JFrame();
button = new JButton("click me");
button.addActionListener(this); //第二步 给button添加接收事件的对象
frame.getContentPane().add(button);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300,300);
frame.setVisible(true);
}
public void actionPerformed(ActionEvent event) { //第三步 完整写出事件处理函数
button.setText("I've been clicked!");
}
}
事件处理函数的参数,例如上面的ActionEvent event,这个对象就是个小的数据携带器,有时候,比如鼠标点击和键盘按下,不但要知道有鼠标点击和键盘按下,很多时候还要知道是点的哪里,按的什么键。还有,如果一个Panel上,有多个按钮,那都用的是actionPerformed()函数,这时候,就要用ActionEvent event引用变量,来携带信息,用于区分各个按钮。
多个按钮的区分 inner class 内嵌类
为了区分多个按钮发出的事件,可以这么干:
public void actionPerformed(ActionEvent event) {
if (
event.getSource() == colorButton) {
//doStuff
} else if (event.getSource() == labelButton){
//doSthOther
}
}
上面的colorButton lavelButton,就是你前面注册时候用的Button.
colorButton = new JButton();
colorButton.addActionListener(this);
注意:但是这么干如果你真的有很多按钮,那所有按钮的功能都要在这一个方法中实现,那这个方法可能变得非常长,实现也不优雅。
或者,可以这么干:
class MyGui {
JFrame frame;
JLabel label;
void gui() {
//初始化两个监听者类的对象,并注册到各自按钮上。ColorButton和LabelButton
}
}
class ColorButtonListener implements ActionListener {
public void actionPerformed (ActionEvent event) {
frame.repaint();
//注意,这实现不了,仅给个例子
}
}
class LabelButtonListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
label.setText("That hurt!");
//注意,这实现也不对,给个例子
}
}
即,有一个按钮,就添加一个类,然后在MyGui类中,分别创建两个监听者类的对象,实例化后,注册到各自的按钮上。
注意:上面两个注意处,这么写都不对,两个监听类既使用不了frame对象,也用不了label对象。但上面就是个例子,主要做示意。要真想像上面那样实现,需要改动很多地方,比如要给MyGui类添加getters函数,还要给监听类添加setters,以便传递MyGui的引用去这两个类。这样会变得非常麻烦,类变得复杂,而且也不是好的封装。
上面两个实现都不好,于是inner class来了:
内嵌类声明很简单
inner class可以使用outer class的所有的方法和变量,即使是private的。
注意:内嵌类的一个对象,能访问它外部类的任意一个对象的成员方法或成员变量吗?这句话刚读肯定会不知道在说撒。就是说,我一个class里面有个内嵌类,有个成员变量aaa,然后这个类有好几个实例,比如instance1 instance2 instance3,然后这个class里面的内嵌类的一个实例insInner,能既随便访问instance1的成员变量或函数,又随便访问instance2的成员变量和成员函数,又能随便访问instantce3的成员变量和函数吗?
肯定不行。
于是,就引出个结论,就是内嵌类的对象,需要和外部类的一个对象进行绑定,才能建立特殊联系,才能访问这一个被绑定的外部类的所有成员函数或变量。
注意:内嵌类 和 外部类的对象绑定后,内嵌类的对象可以随意访问外部类对象的成员函数和成员变量,and vice-versa,
反过来也是可以的,外部类也可以随意访问内部类成员!
注意:这提到个知识点,就是不绑定好像也行,就是inner class要定义在一个static的方法中,但这个知识点批注说估计永远见不到。大概提一下。
那么怎么将一个内嵌类 和外部类的对象绑定呢?
就是把 内嵌类 实例化在外部类的一个方法中,或者直接将这个内嵌类的一个对象当做成员变量。基本就是说,把内嵌类的对象,在外部类中实例化。
比如:
class MyOuter {
private int x;
MyInner inner = new MyInner(); //直接将内嵌类对象当做成员变量
public void doStuff() {
inner.go();
}
class MyInner {
void go() {
x = 42;
}
}
}
或者:
class MyOuter {
private int x;
public void doStuff() {
MyInner inner = new MyInner(); //成员函数中实例化内嵌类
inner.go();
}
class MyInner {
void go() {
x = 42;
}
}
}
上面两种方法,都可以将inner class 和 outer class绑定起来。
注意:还有一个方法,是内嵌类在外部类的外面实例化,然后将外部类和内部类绑在一起的方法。
这个方法也是说基本不可能遇见。
class Foo {
public static void main(String[] args) {
MyOuter outerObj = new MyOuter();
MyOuter.MyInner innerObj = outerObj.new MyInner();
}
}
这样可以在一个不相干的类中,方法中,实例化内嵌类和外部类,然后将两者连接。
于是,上面的监听函数可以写成:
public class TwoButtons { //注意这个主GUI类不实现ActionListener!
JFrame frame;
JLabel label;
public static void main (String[] args) {
TwoButtons gui = new TwoButtons();
gui.go();
}
public void go() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton labelButton = new JButton("Change Label");
labelButton.addActionListener(new LabelListener());
JButton colorButton = new JButton("Change Circle");
c
olorButton.addActionListener(new ColorListener());
label = new JLabel("I'm a label.");
MyDrawPanel drawPanel = new MyDrawPanel();
frame.getContentPane().add(BorderLayout.SOUTH,colorButton);
frame.getContentPane().add(BorderLayout.CENTER, drawPanel);
frame.getContentPane().add(BorderLayout.EAST,labelButton);
frame.getContentPane().add(BorderLayout.WEST,label);
frame.setSize(300, 300);
frame.setVisible(true);
}
class LabelListener implements ActionListener {
public void actionPerformed(ActionEvent event) {
label.setText("Ouch!");
}
}
class ColorListener implements ActionListener {
public void actionPerformed (ActionEvent event) {
frame.repaint();
}
}
}
这实现就好多了,优雅多了。里面稍微带了一点布局相关内容。
inner class有什么用?
答:
1、Inner class主要是给一个了多次实现同一个方法的机会。或说提供同一个接口,但不同实现的机会。重载的方法可叫同一个接口,返回值,参数 名称完全相同的方法,叫相同的接口。
2、Inner Class的存在,也是因为不能多继承,即如果我一个Dog类,继承自Animal,但是可能有时候,我需要通过Button类的IS-A测试,那我可以建立一个Inner class的Button类,然后以这个Button类作为我的代表,去通过这个IS-A测试。
画图
如果想要在Panel上,画图,那么需要你的类,继承于JPanel类,并且重写paintComponent()方法。
注意:画图的时候可不能直接在Frame上画,要先创建自己的画布,即上面说的自己的那个继承于JPanel的类,然后先把这个类的对象,添加到JFrame上,然后在这个对象上画。
JFrame frame = new Frame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyDrawPanel drawPanel = new MyDrawPanel();
frame.getContentPanel().add(drawPanel); //要先加载Panel,再画
.............
注意:这个paintComponent()方法,不是自己调用的!它的参数你创造不出来,屏幕刷新的时候自动调用,有点像Onpaint()之类的函数,MFC上。
paintComponent除普通话图外,能做的事情:
1、显示JPEG:
public void paintComponent(Graphics g) {
Image image = new ImageIcon("catzilla.jpg").getImage();
g.drawImage(image, 3 , 4 , this);
}
那个3,4就是坐标,现对于this,即当前这个widget,左上角,图片该显示到哪里。
2、画图,随机颜色的圆
public void paintComponent(Graphics g) {
g.fillRect(0, 0, this.getWidth(), this.getHeight()); //这是在涂背景色
int red = (int)(Math.random() * 255); //设置随机的颜色
int green = (int)(Math.random() * 255);
int blue = (int)(Math.random() * 255);
Color randomColor = new Color(red, green, blue);
g.setColor(randomColor);
g.fillOval(70, 70, 100, 100);
}
frame.repaint(); 可以重绘整个frame内的所有component。调用各个component的paintComponent()函数。
Graphics2D
前面看到了paintComponent的参数类型为Graphics,所以在
public void paintComponent(Graphics g) {} 中,那个g,是Graphics类型,
或者是Graphics的子类的类型。
实际上,上面传的那个g的实际类型,确实是Graphics2D,也就是说,这里g所能调用的函数数量变少了,即只能调用Graphics父类内的方法,自己衍伸出来的新方法,并不能调用。
所以需要:
Graphics2D g2d = (Graphics2D)g; 强制转换。
Graphics 能调用的函数:
Graphics2D能调用的新函数:
当然只是部分,没全写。
画一个颜色渐变的圆:
规定渐变色,情况如下
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
GradientPaint gradient = new GradientPaint(80,80,Color.blue, 100,100,Color.orange); //设置渐变颜色的起点,终点坐标,和颜色
g2d.setPaint(gradient);
g2d.fillOval(80,80, 100,100)
}
随机渐变色,情况如下
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
int red = (int)(Math.random() * 255);
int green = (int)(Math.random() * 255);
int blue = (int)(Math.random() * 255);
Color startColor = new Color(red,green,blue);
//设置起点的随机的颜色
int red = (int)(Math.random() * 255);
int green = (int)(Math.random() * 255);
int blue = (int)(Math.random() * 255);
Color endColor = new Color(red,green,blue); //设置终点的随机的颜色
GradientPaint gradient = new GradientPaint(80,80,startColor, 100,100,endColor); //设置渐变颜色的起点,终点坐标,和颜色
g2d.setPaint(gradient);
g2d.fillOval(80,80, 100,100)
}