Chapter12 GUI Basics
Swing的组件不依赖于本地的GUI,称为轻量级组件;而AWT的组件比较依赖本地的GUI,称为重量级组件。
为了使Swing中的新组件类区别于AWT的组件,Swing GUI 组件类的命名加上前缀J。
GUI API包含的类可以分为三类:组件(component)类,容器(container)类和帮助(helper)类。
Compent和JCompent类都是抽象类。
P407 Table12.1 GUI Container Classes
帮助类在java.awt包。Swing组件并没有全部取代AWT,AWT中的帮助类在GUI编程中依然有用。
为了创建用户界面, 我们需要创建一个frame或者applet去容纳用户界面组件。
在调用frame.setVisible(true)之前frame都不会显示出来。
如果frame没有调用setSize(width, height)方法,则frame会只显示标题栏。
调用setLocationRelativeTo(null)方法可以使frame在屏幕中居中,使用setLocationRelativeTo(null)方法前必须先调用setSize(width,height)方法设定窗口大小。
调用setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)可以使窗口关闭时终结程序。
每一个JFrame包含一个content pane,一个content pane是java.awt.Container的实例。一个GUI组件如按钮是放置在JFrame的content pane中的,以前必须先使用getContentPane方法获取frame的content pane,然后调用content pane中的add方法去放置组件,在java5之后允许我们直接使用JFrame的实例的add方法添加组件(如如frame.add(Btn))。
一个GUI组件(如一个按钮)只能被添加到唯一的一个容器并且只能在容器中添加一次。如果多次添加与只添加一次的效果是相同的。
容器是通过布局管理器来规划组件位置,为容器设定布局管理器类型的方法如下:
LayoutManager layoutManager = new XLayout();
container.setLayout(layoutManager);
或写为
container.setLayout(new XLayout());
GridLayout布局管理器可以指定行列,行与列可以是零,但不能两个都是零。如果指定行为零,GridLayout会根据组件数与指定的列数去计算行数;如果指定列为零则会自动计算列数。
如果行和列都不为零,则行为主参数,即如果添加的组件数大于row*column,则行固定,而列会自动计算。例如我们指定三行和三列但包含十个组件,则GridLayout会创建固定的三行,自动计算出四列,而排放是最后一行包含两个组件。
BorderLayout中如果没有指定组件的位置则会默认是中间,如果多个组件包含在同一个位置中,则只有后边添加的一个会显示。
Component类是GUI的组件和容器的最根部,JComponent继承Container, Container继承Component。
所有的Swing GUI 组件(除了JFrame,JApplet和JDialog)都是JComponent的子类。
Java目前支持三种图像格式: GIF、JPEG和PNG。
要显示一张图片,首先要使用new javax.swing.ImageIcon(filename)创建一个ImageIcon对象。
Windows中文件名是不分大小写而Unix则大小写敏感,所以为了使程序能运行于所有的平台,我们命名图像文件时统一使用小写。
Chapter13 Exception Handling
在Java中,运行时错误是由异常(exception)导致。
整数被0除会产生异常,而浮点数被0除不会导致异常。
要抛出一个异常可以在try块中直接使用throw语句,或者调用会抛出异常的方法。
使用异常的好处是可以使方法向该方法的调用者抛出异常,让调用者去处理异常。
异常处理机制的本质好处是可以把发现一个错误(在被调用的方法中完成)和把处理一个错误(在执行调用方法的方法中完成)分开。
异常的继承关系如下:
Throwable类是异常类的根。
我们可以通过继承Exception或Exception的子类来创建自己的异常类。
异常类可以分为三种主要的类型:系统错误(system error), 异常(exception)和运行时异常(runtime exception)。
运行时异常和错误以及它们的子类都是不检查的异常,而其他的异常都是检查的异常,意思是编译器会强制程序检查和处理它们。
大多情况下,不检查的异常都反映程序的不可补偿的逻辑错误(logic errors that are unrecoverale)。例如如果数组越界会抛出一个IndexOutOfBoundsException异常,这个错误应该在编写程序时更正,所以这是不检查的异常(可以理解为编译时不检查)。一般发生不检查的异常,程序都会意外退出。
Java的异常处理模型基于三个操作:
声明一个异常
抛出一个异常
捕获一个异常
Java不要求为错误和运行时异常(不检查的错误)在方法中显式的声明,然而方法中如果抛出其他异常,则必须在方法的开头显示声明,这样才能使该方法的调用者被通知有异常抛出。
声明异常的格式如下:
public void myMethod() throws IOException
如果有多个异常可以声明如下:
public void myMethod() throws Exception1, Exception2, …, ExceptioN
如果一个方法在父类中没有声明异常,我们在子类中重写时也不能声明异常。
Java API中的异常类最少拥有两个构造器:一个无参构造器和一个拥有一个用来描述异常的字符串参数的构造器,该字符串参数被称作异常信息,可以使用getMessage()获得该参数。
声明一个异常使用的关键字是throws,而抛出一个异常使用的关键字是throw
捕获异常的格式如下:
try {
statement;
}
catch (Exception1 exVar1) {
handle for exception1;
}
catch (Exception2 exVar2) {
handle for exception2;
}
catch (ExceptionN exVarN) {
handler for exceptionN;
}
如果在try块中的一条语句抛出了异常,Java会跳过try块中剩余的语句然后转向catch语句寻找对应的异常处理代码。Java搜索对应的异常处理代码时会搜索方法链,即该方法的调用者中没有对应的异常处理代码,则会继续搜索调用该调用者的调用者中有没有对应的异常处理代码,如果到最后都没有异常处理代码则程序被终结并在控制台中打印错误信息。
许多异常类都源于一个共同的父类,如果在catch语句中捕获一个异常对象的父类,则此时该catch语句能捕获到所有由该父类产生的子类的异常对象。
捕获异常的顺序是重要的,catch语句中父类类型比子类类型先出现会出现编译错误。
Java强制我们处理检查的异常。如果一个方法声明了检查的异常,我们调用该方法时必须添加try-catch块或者使调用该方法的方法声明异常。如p2是声明为一个检查的异常的方法,则我们在p1方法中调用p2时必须写成如下之一:
有时我们会希望一些代码无论异常是否发生或被捕获都被执行,这时候我们可以使用finally语句。格式如下:
try {
statements;
}
catch(TheException ex) {
handling ex;
}
finally {
finalStatement;
}
如果try块中没有发生异常,finalStatements 会执行,然后再执行try块后边的语句。
如果发生了异常但没有被任何的catch语句捕获,则try块中剩下的所有语句被忽略,跳转到finally语句去执行,并且该异常会被传递到该方法的调用者。
finally块始终会执行即使在其前边有return语句。
使用finally的话是可以没有catch块的。
使用异常处理机制通常需要更多的时间和资源,因为它需要对异常对象实例化,回滚和调用栈,并需要在方法链中传递异常。
一个异常发生在方法中,如果我们希望异常由调用者处理则我们应该创建一个异常对象并抛出该异常,如果我们能够更好地在方法中处理该异常的话,我们不需要使用异常处理机制。
哪些情况下应该使用异常处理机制有时是难以决定的,原则上是对于简单的逻辑测试我们不应该滥用异常处理机制。
Java允许重新抛出一个异常,如
try {
statements;
}
catch(TheException ex) {
perform operations before exits;
throw ex;
}
重新抛出一个异常可以使调用者对该异常的其他异常处理代码有可能得到执行。
有时候我们希望在异常处理代码中抛出另一个异常,这叫做异常链。如下列代码:
public Class ChainedExceptionDemo{
public static void main(String[] args) {
try {
method1();
}
catch (Exception ex) {
ex.printStackTrace();
}
}
public static void method1() throws Exception {
try {
method2();
}
catch(Exception ex) {
throw new Exception(“New info from method1”, ex);
}
}
public static void method2() throws Exception {
throw new Exception(“New info from method2”);
}
}
method1中新抛出的异常会先显示,接着是method2抛出的源异常。(显示结果见P448)
Chapter14 Abstract Classes and Interfaces
抽象类像普通类一样,但是不能使用new关键字创建抽象类的实例。
抽象方法没有实现内容,它的实现由它的子类提供。
一个包含抽象方法的类必须定义为抽象类。
抽象类的构造器定义为protected,因为它只能被子类使用。
在非抽象的子类中必须实现父类中所有的抽象方法。抽象方法都不是static的。
抽象类中可以没有抽象方法。
即使父类是非抽象类,它的子类也可以定义为抽象类。
子类重写父类的方法时可以定义为abstract,这虽然不常用但是很有用。
抽象类不能使用new关键字创建实例,但它可以作为数据类型。
定义接口的格式:
modifier interface InterfaceName {
}
每个接口都会编译成一个分开的二进制代码文件,像一般的类一样。
接口的数据域都是定义为public final static的,方法都是定义为public abstract。Java允许忽略接口数据域和方法的修饰符,所以下边的定义是等价的。
要为组件实现一个监听器,必须实现以下两点:
1 该监听器对象必须是XListener接口(代表一种监听器接口,如该接口可以为ActionListener)的实例。
2 这个XListener的对象listener必须使用source.addXListener(listener)与source绑定。
ActionListener接口包含一个为了处理事件的方法actionPerFormed,自己写的继承ActionListener接口的listener类必须重写该方法去响应事件。
一个为按钮添加监听的简单例子:
import javax.swing.*
import java.awt.event.*
public class HandelEvent extends JFrame {
public HandleEvent() {
JButton jbtOK = new JButton(“OK”);
JPanel panel = new JPanel();
panel.add(jbtOK);
OKListenerClass listener1 = new OKListenerClass();
jbtOK. addActionListener(listener1);
}
public static void main(String[] args) {
JFrame frame = new HandleEvent();
frame.setTitle(“Handle Event”);
frame.setSize(200, 150);
frame. setLocation(200, 100);
frame.setDefaultCloseOperation(JFrame.Exit_ON_CLOSE);
frame.setVisible(true);
}
}
class OKListenerClass implements ActionListener {
public void acctionPerformed(ActionEvent e) {
System.out.println(“OK button clicked”);
}
}
Java只允许继承一个类但是允许使用接口实现多继承。
类的命名使用名词,而接口使用形容词或名词。
在比较使用抽象类还是接口时,一般地,具有很强的is-a关系应该使用抽象类,而具有较弱的is-a关系,或者称为is-kind-of关系应该使用接口。
一般地,接口比抽象类更好是因为接口能为没有关系的类定义一个共同的父类型。
对int、double这些原子类型java都设有对应的类,这些类被称为包装类(wrapper class)。
每个数字的包装类都继承Number类,包含doubleValue(), floateValue(), intValue()……这些方法,这些方法能把对象转换为原子数据类型的值。
每个数字的包装类都包含常量MAX_VALUE和MIN_VALUE。
每个数字的包装类都拥有两个重载的转换方法,能够把数字的字符串按10进制或指定进制转换为对应的数字。
在java.util.Arrays中提供了一个静态方法sort,可以用来为java.util.Arrays类的任意对象数组进行排序。
数组是对象,一个数组也是Object类的对象。如果A是B的子类型,A[]的每个实例都是B[]的实例。下边的语句的结果都是正确的:
new int[10] instanceof Object
new Integer[10] instanceof Object
new Integer[10] instanceof Comparable[]
new Integer[10] instanceof Number[]
new Number[10] instanceof Object[]
尽管int的值可以赋值到double类型的变量,但是int[]和double[]是两个不兼容类型。所以不能把int[]数组赋值到double[]或Object[]类型。
Java允许包装类和原子类型自动转换,例如下面的语句是等同的:
如果需要计算非常大的数值可以使用java.math包中的BigInteger和BigDecimal类。
Chapter15 Graphics
Java中的图形坐标系统是以左上角为坐标原点,水平向右为x坐标正方向,垂直向下为y坐标正方向,如下图:
为了在组件上画东西,需要把类定义为继承JPanel并重写它的paintComponent方法去指定要画什么,paintComponent方法会自动被调用(参考P500的Listing15.1)。
有些书定义画布类为JComponent的子类,但是这样的话如果想设置背景颜色必须自己写代码,一个简单的setBackground(Color color)方法不会改变Jcomponent的背景颜色。
paintComponent方法永远不应该直接调用,它是在视图区域变化时或者调用repaint方法时由JVM调用。
我们应该重写paintComponent方法去告诉系统应该怎么绘画视图区域,但永远不要重写repaint方法。
repaint方法是请求更新视图区域并立即响应。但是它是异步的,就是说它的响应需要JVM在单独的线程中执行paintComponent方法。
getPreferredSize()方法定义在Component中,布局管理器有时考虑该方法有时却忽略,在GridLayout布局管理器中则需要考虑该方法,我们应该重写该方法,因为JPanel中默认的preferred size是(0, 0);
drawstring(String s, int x, int y) //画字符串
drawLine(int x1, int y1, int x2, int y2) //画直线
drawRect(int x, int y, int w, int h) //画矩形
fillRect(int x, int y, int w, int h) //画填充矩形
drawRoundRect(int x, int y, int w, int h, int aw, int ah) //画圆角矩形
drawOval(int , x, int y, int w, int h) //画椭圆
drawArc(int x, int y, int w, int h, int startAngle, int arcAngle) //画弧线
fillArc(int x, int y, int w, int h, int startAngle, int arcAngle) //画填充扇形
drawPolygon(Polygon polygon)
drawPolygon(int [] xpoints, int[] ypoints, int npoints); //画多边形
fillPolygon(Polygon polygon)
fillPolygon(int[] xpoints, int[] ypoints, int npoints) //画填充多边形
g.drawPolyline(int[] x, int[] y, int numberOfLine) //画多边
Compent类中有setBackground, setForeground, seFont方法,这些方法用来设置整个组件中的颜色和字体,如果我们想使一些信息拥有不同的颜色和字体,我们必须使用Graphics类中的setColor和setFont方法去设置当前的绘画。
使用label作为显示图片的区域是简单与方便的,但我们不能对图片的显示有太多的控制,如果要更灵活的显示图片可以使用Graphics类中的drawImage方法。
Chapter16 Event-Driven Programming
产生一个事件和触发它的组件称为源对象(source object)或源组件(source component)。如一个按钮是一个button-clicking动作事件的源对象。
P535 Table16.1 用户动作,源对象和事件类型
如果一个组件能触发一个事件,则该组件的任何子类都能触发同样的事件类型。
大部分事件类在java.awt.event包中,但也有些是在java.swing.event包中。
Java的事件处理使用一个delegation-based模型:一个源对象触发一个事件,一个关注该事件的对象处理它。后边的对象称为监听器。要使一个对象成为一个事件的监听器,需要做两点:1、监听器是一个监听器接口的实例;2、使用source.addXListener(listener)方法与源对象绑定。
监听器对象必须是对应的event-listener接口的实例,以确定该监听器有正确的方法去处理事件。
P537 Table16.2 事件,监听器接口,监听器方法
通常事件命名为XEvent,而对应的监听器接口命名为XListener。如ActionEvent对应的监听器接口为ActionListener。
一个内部类,或称为嵌套类,是一个定义在另外一个类中的类。
一个内部类被编译成一个名为OuterClassName$InnerClassName.class。如一个Test类中有一个内部类A,则A编译为Test$A.class。
一个内部类可以引用嵌套它的外部类的数据和方法,因此,使用内部类能使编程更加简便和简洁。
内部类可以使用与类成员的可视化规则一样的可视化修饰符定义。
内部类可以定义为public。
内部类可以定义为static。一个static内部类能够通过使用外部类名字访问。一个static的内部类不能访问外部类中非static的成员。
一个内部类的对象通常在外部类中创建,但是我们也能在其它类中创建该内部类的对象。如果一个内部类不是static的,则必须先创建外部类的实例,然后使用下面的方法创建内部类的对象:
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
如果内部类是static的,使用下面的方法创建对象:
OuterClass.InnerClass innerObject = new OuterClass.InnerClass();
匿名内部类的语法如下:
new SuperClassName/InterfaceNmae() {
// Implement or override methods in superclass or interface
// Other methods if necessary
}
一个匿名内部类必须继承一个父类或者implement一个接口,但是不能直接使用extends 或 implements子句;
一个匿名内部类必须实现父类或接口中所有的抽象方法;
一个匿名内部类通常是使用其父类的无参构造器去创建一个实例。如果一个匿名内部类是implement一个接口,则使用构造器Object();
一个匿名内部类编译成的类名为OuterClassName$n.class。如一个外部类Test中有两个内部类,则他们编译为Test$1.class和Test$2.class;
作匿名内部类的监听器示例:
public ControlCircle() {
jbtEnlarg.addActionListener(
new ActionListener() { //直接写接口名ActionListener
//而没有implements关键字和多余的内容
public void actionPerformed(ActrionEvent e) {
canvas.enlarge();
}
});
}
因为接口中的方法是抽象的,我们必须实现所有的抽象方法,为了方便使用,Java提供了一些监听器接口的简便类,称为简便适配器,参见P551 Table16.3
MouseEvent继承InputEvent,所以MouseEvent的对象可以使用定义在InputEvent中的方法。
KeyPressed中有三个方法keyPressed,keyReleased和keyTyped,当按下按键时keyPressed会被调用,当松开按键时keyReleased会被调用,当按下的按键时Unicode字符时keyTyped会被调用。如果按键不拥有Unicode码(如control键)则keyTyped不会被调用。
每一个键盘事件都可以通过KeyEvent中的getKeyChar()或getKeyCode()获取关联的键字符或键码。键码是一个常量,对于Unicode的字符的码与Unicode的值是相同的,参见P555 Table16.4。
一个Timer对象被看作ActionEvent的源,它的监听器必须是ActionListener的实例并与Timer对象绑定。
Chapter17 Creating Graphical User Interfaces
一个常规的按钮拥有一个default icon,一个pressed icon和一个rollover icon。正常情况下显示的是default icon,当按下按钮时显示的是pressed icon,当鼠标放在按钮上而没按下时显示的是rollover icon。
horizontal alignment指明按钮上的图标和文字的水平位置。要指定水平位置可以使用setHorizontalAlignment(int)方法设置,其中参数可以传递五个常量LEADING,LEFT,CENTER,RIGHT,TRAILING中的一个。其中LEADING和LEFT是相同的,而RIGHT和TRAILING是相同的。
vertical alignment指明图标和文字的垂直位置。可以使用setVerticalAlignment(int)方法指定垂直位置,参数可以为三个常量TOP,CENTER,BOTTOM中的一个。
一个JButton能够触发许多类型的事件,但是通常我们只需使监听器添加ActionEvent的响应。当一个按钮按下时,它激活的是ActionEvent。
check按钮可以多选,而radio按钮则只能选一个。
(通常check按钮是空心正方形外形,而radio按钮是空心圆形外形)
为了让radio按钮称为一组,我们需要创建一个java.swing.ButtonGroup实例并使用add方法把radio按钮添加进去。如有radio按钮jrb1和jrb2,则代码为:
ButtonGroup group = ew ButtonGroup();
group.add(jrb1);
group.add(jrb2);
ButtonGroup不是java.awt.Component的子类,所以ButtonGroup对象不能添加到container中。ButtonGroup只是把radio按钮归成一组,但还是要把radio按钮逐个地添加到container中。
一个label可以显示一个短文本,一个图片或者两者一起。
一个text field可以用来输入或显示一个字符串。
如果要显示多行文本,可以使用JTextArea。
JTextArea不处理滚动条,我们可以先创建一个JScrollPane对象然后向其添加JTextArea对象,由JScrollPane处理JTextArea的滚动。
一个combo box是一个下拉选择列表,包含供用户选择的项目。
一个list与combo box具有相同的功能,但它可以多选。
JScrollBar是一个让用户选择范围内的值得组件。
JSlider与JScrollBar相像,不过JSlider拥有更多的性质和能够展现多种形态。
拖动条的值默认地是从上往下增加,从左向右增加。
Chapter18 Applets and Multimedia
applets没有main方法,它们依靠浏览器运行。
Chapter19 Binary I/O
一般地,要阅读一个用文本编辑器编辑的文本文件或作一个文本输出程序时使用文本输入输出,要阅读一个由Java二进制输出程序创建的文件时使用二进制输入。
几乎所有I/O类中的方法都抛出java.io.IOException异常,所以使用时必须声明方法为抛出java.io.IOException异常或至于try-catch块中。
文件的根目录是类的目录,例如类放在c:\book时,下面的语句
FileOutputStream output = new FileInputStream(“temp.dat”);访问的文件绝对路径为c:\book\temp.dat。如果要指定文件的路径可以把语句替换为:
FileOutputStream output = new FileOutputStream(“directory/temp.dat”);
UTF-8是一个允许系统操作ASCII和Unicode编码的编码系统。
UTF-8编码系统可以指定编码使用一个字节,两个字节或三个字节(ASCII是一个字节,Unicode是两个字节)。
读取数据时必须使用与其存储方式相同的序列和格式,例如对于UTF-8的数据必须使用writeUTF去写,使用readUTF去读。
我们应该总是使用缓冲IO为输入输出提速,对于小文件我们不会注意到执行质量的提高,但对于大于100MB的文件使用缓冲IO的话,我们会看到速度明显的提高。
Chapter20 Recursion
完。