Head First Java 八 九

第八章 异常处理

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.*

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)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值