woojean的博客 模仿、熟练、超越

《Java核心技术:卷1》读书笔记

2017-04-05

第1章 Java简介[2]

1.Java的体系结构中立实现:Java编译器能够生成与特定计算机体系结构无关的字节码指令,这些字节码既可以容易地在任何机器上由Java解释器解释执行,也能够在运行时很容易的被翻译成本地机器代码。

2.即时编译(JIT):把字节码编译为本地代码后,缓存得到的结果,在需要的时候重新调用。该方法由于只需要做一次解释,极大地提高了经常使用的代码的速度。

第2章 Java编程环境[1]

1.使用命令行解释和执行Java程序 javac Welcome.java java Welcome javac程序是Java的编译器,它把Welcome.java文件编译成Welcome.class文件。java程序是Java解释器,它负责解释执行编译器生成到class文件中的字节码。注意大小写。

第3章 Java基本编程结构[18]

1.源代码的文件名必须同公有类的名字相同,且需以.java作为扩展名。

2.和C不同,Java的main方法并不向操作系统返回“退出代码”,Java中,如果main方法正常退出,Java程序的退出代码为0,表示成功地执行完毕程序。若要用不同的退出代码终止程序,则使用System.exit方法。

3.回车并不代表语句的结束,所以语句可以跨越多行。

4.Java共有八种基本类型,四种是整型,两种浮点型,一种字符型以及用于表示真假的布尔类型。 1)整型 long 8字节、int 4字节、short 2字节、byte 1字节; 表示float类型数据时需要添加后缀F。没有后缀F的浮点数据总是被认为是double类型的。 有三种特殊的浮点值:正无穷大、负无穷大、NaN(非数字)用于表示溢出和出错。 不能用if(x == Double.NaN)的形式来判断x是否为数字,正确方法是:if( Double.isNaN(x) )。 如果需要进行不产生舍入误差的精确数字计算,需要使用BigDecimal类。 2)字符型 char 2字节; Unicode编码表中前256个字符与ASCII码相等。 尽管理论上可以在Java应用程序和applet中使用任意的Unicode字符,但实际上能不能看到它们 还取决于所使用的浏览器以及操作系统。 3)布尔类型 boolean 不能和整数相互转换,强制类型转换也不行。 5.const是被保留的Java关键字,但目前还未被定义。必须使用final来表示常量。

6.字符串 1)在标准Java库中包含一个名为String的预定义类。每个被双括号引起来的字符串都是String类的实例。 2)当用+号连接一个字符串和一个非字符串时,后者将被转换成字符串。 3)String类的对象是不可改变的,编译器把字符串设置为共享的。 4)对于直接操作字符串的情况,Java提供了单独的StringBuffer类。 5)不要使用==操作符来检测两个字符串是否相等,它只能判定两个串是否存储在同一个位置。 6)如果虚拟机总是把相等的串设为共享,那么可以使用==来测试它们是否相等。但实际上只有字符串常量会被共享,而+或substring等操作产生的结果串并不共享。

7.不管什么时候调用了JOptionPane.showInputDialog,都必须以System.exit(0)调用结束程序。显式对话框启动了一个新的控制线程。当main方法结束时,新线程并不自动终止。结束所有的线程需要调用System.exit方法。

8.可以利用NumberFormat类来实现对数字、货币、百分数等的格式化输出。

9.Java控制流结构与C/C++的不同只有两处:Java中没有goto语句,但有一个标签化版本的break。

10.在Java中不允许在嵌套块中重新定义一个变量,如 int n { int n; //错误 …

11.标签化的break:标签必须在要跳出的最外层循环的前面,并且标签后必须有一个冒号。 read_data: while(…) { for(…) { … if(x) break read_data; … } } //break之后从这里开始执行 if(x){…//处理错误情况} else{…//执行正常处理} 如果发生错误,则加标签的break跳转到加标签块的末尾。对任何使用break语句的情况都需要测试循环结束的原因。

12.事实上,可以把标签用于任何语句,甚至是if语句和块语句。

13.大数字:如果基本的整型和浮点型数据无法达到要求的精度,可以使用BigInteger和BigDecimal。这两个类可以操作任意长的数字。前者实现任意精度的整数运算,后者实现任意精度的浮点数运算。使用普通方法可以将普通数字转换成大数字:BigInteger a = BigInteger.valueOf(100);

14.Java中无法编程实现重载操作符,Java语言设计者为字符串的连接重载了+操作符,但没有对其他操作符进行重载。

15.在Java中长度为0的数组是合法的,其与null是不同的。

16.可以把一个数组变量拷贝给另一个,这时两个变量都指向相同的数组。如果实际上是想将一个数组中的值拷贝给另一个变量,则需要使用System类中的arraycopy方法。

17.对数组中的数字排序,可以使用Arrays类的sort方法。

18.Math.random方法返回一个在0(包含)到1(不包含)间的随机浮点数。

第4章 对象和类[21]

1.类之间最常见关系 1)依赖(“use-a”)一个类的方法操作了另一个类的对象 2)聚合(“has-a”) 3)继承(“is-a”)

2.对象变量并不包含对象,它只是指向一个对象。在Java中,任何对象变量的值都是指向存储在别处的对象的一个引用。new运算符的返回值也是一个引用。

3.本地对象变量并不会被自动初始化为null,必须对它们进行初始化:或者通过调用new,或者将它们设为null。

4.所有的Java对象都存储在堆中,当一个对象包含另一个对象变量时,这个变量包含的只是指向堆中另一个对象的指针。

5.Date类用来表示当前的时间点,GregorianCalendar类用我们熟悉的日历符号表示日期,是对更一般的Calendar类的扩展。

6.方法不仅可以访问调用该方法的对象中的私有数据,还可以访问其所在类的所有对象的私有数据。

7.如果一个类的所有字段都是final,那么此类是不可变的,它的对象在被构造之后永远不变。不可变的类有一个重要的优点:它们不会被共享引用。

8.Java语言总是使用传值调用,也即方法得到的只是所有参数值的拷贝,因此方法不能修改传递给它的任何参数变量的内容。

9.对于对象参数来说,方法得到的是对对象引用的一个拷贝,原来的对象变量和这个拷贝所指向的是同一个对象。也即并不向对象使用引用调用,对象引用是通过值来传递的。

10.Java中利用方法参数可以做到和不能做到的几种情况: 1)方法不能修改基本类型的参数 2)方法可以修改对象参数的状态 3)方法不能让对象参数指向新的对象

11.如果在构造器中没有显式给某个字段赋值,那么它会被自动赋为默认值:数字变量为0,布尔变量为false,对象引用为null。

12.如果编写的类没有构造器,系统会提供默认构造器,它会把所有的实例字段设为默认值。如果一个类提供至少一个构造器,但没有提供默认构造器,那么构造没有构造参数的对象是非法的。

13.显式字段初始化:可以在类的定义中简单地把值赋给任何字段,构造器执行之前,赋值会先被执行。当类的所有构造器要把某个特定实例字段赋以相同的值时,这种语法特别有用。初始值未必一定要是常量。

14.调用其他构造器:如果构造器的第一个语句具有形式this(…),那么这个构造器将调用同一类中的其他构造器。

15.初始化块:不管用哪个构造器构造对象,初始化块都首先被运行,然后才是构造器的主题部分被执行。

16.调用构造器后详细的执行过程: 1)初始化所有数据字段为默认值 2)按照在类声明中出现的次序依次执行所有字段初始化语句和初始化块 3)如果构造器的第一行代码调用了另一个构造器,则执行被调用的构造器主体 4)执行构造器主体

17.如果静态字段需要复杂的初始化代码,则可以使用静态初始化块: static int nextId=1; static { Random ran=new Random(); nextId=ran.nextInt(1000); } 在类第一次被加载时,会进行静态初始化。 可利用这一点实现不用main方法就写出一个“Hello world”程序: public class Hello { static { System.out.println(“Hello,World”); } System.exit(0); }

18.Java使用包把类聚集起来,所有的标准Java包都处于java与javax包层次中(java与javax不是包)。从编译器的角度看,嵌套的包之间没有任何关系,例如包java.util和java.util.jar就是彼此无关的,它们每一个都有自己独立的类集合。

19.import语句应该放在源文件的顶部,但是要在所有package语句下面。

20.只能使用符号引入一个包。不能使用import java.或是import java..来引入所有以java为前缀的包。

21.要把类放入包中,必须把包的名字放在源文件顶部,即放在对包中的类进行定义的代码之前。如果在源文件中没有加入package语句,那么源文件中的类将属于默认包,默认包没有包名。包中的文件被放在与完整包名匹配的子目录中。

第5章 继承[22]

1.子类无法直接访问其超类中的私有字段,要访问这些字段必须和其他方法一样使用公有接口。

2.super不同于this,它不是一个对对象的引用(不能把super赋给另一个对象变量),而是指示编译器调用超类方法的专用关键字。

3.一个对象变量可以指向多种实际类型的现象被称为“多态”。而在运行时自动选择正确的方法进行调用的现象称为“动态绑定”。动态绑定有一个非常重要的特性:它使程序无需重新编译已有代码就能获得可扩展性。

4.在Java中不需要把方法声明为虚拟的,默认情况就是动态绑定。如果不想让一个方法称为虚拟方法,可以把它标记为final。

5.不允许扩展的类称为final类,final class xxx extends…final类中的所有方法都自动是final的(但字段不是)。把方法或类设为final主要有两个原因:效率、安全。

6.在进行类型转换(从超类到子类类型的转换)之前最好先用instanceof操作符判断它是否能成功。

7.具有一个或多个抽象方法的类本身也必须声明为抽象的。除了抽象方法外,抽象类还可以有具体数据和具体方法。即使不含抽象方法,类也可以被声明为抽象类。

8.抽象类不能被实例化,但是仍然可以创建抽象类的对象变量,只是这个变量必须指向它的非抽象子类的对象。

9.Java中的受保护字段和方法既可以被子类访问,还可以被同一包中的其他类访问。

10.访问修饰符小结: 1)private:只对本类可见; 2)public:对一切可见; 3)protected:对所有子类和本包可见; 4)默认值:对本包可见。

11.Object类中的equals方法用于测试某个对象是否同另一个对象相等。在Object中的实现是判断两个对象是否指向同一块内存区域。如果想测试对象是否相等则需要覆盖equals方法进行更有意义的比较。

12.按照Java语言规范要求编写equals方法的建议: 1)显式参数命名为otherObject; 2)测试this同otherObject是否是同一对象: if(this==otherObject) return true; 3)测试otherObject是否为null,如果是,就返回false,这项测试是必须的。 if(otherObject==null) return false; 4)测试this和otherObject是否属于同一个类,这项测试是“对称性规则”所要求的。 if(getClass()!=otherObject.getClass()) return false; 5)把otherObject的类型转换成所需的类型: ClassName other=(ClassName)otherObject; 6)比较所有的字段,使用==比较基本类型字段,使用equals比较对象字段。如果所有字段都匹配,则返回true,否则返回false。 遵循以上规则定义一个类的equals方法,则在定义其子类的equals方法时,首先调用超类的equals方法。如果这项测试不能通过,对象也就不可能相等。如果超类字段相等,那么需要比较子类的实例字段。

13.无论何时用+操作符将对象和字符串进行相连接Java编译器都会自动调用toString方法获得对象的字符串表示。

14.所有的数组类型,不管它是对象数组还是基本类型数组,都是从Object派生出来的类类型。只能把对象数组转换成Object[]数组,而不能把int[]数组转换成Object[]数组,不过两种数组都可以转换成Object。

15.所有基本类型都有着与之对应的类,这种类通常被称为对象包装器。包装器类是final的,也不能改变存储在对象包装器内的值。

16.程序运行时,Java运行时系统一直对所有的对象进行运行时类型识别。这项信息记录了每个对象所属的类。虚拟机通常使用运行时类型信息选择正确的方法去执行。用来保存这些信息的类就是Class类。

17.获取Class类型对象的三种方法: 1)调用Object类中的getClass方法; 2)使用Class类的静态方法forName获得与字符串对应的Class对象: String className=”Manager”; Class c1=Class.forName(className); 3)如果T是一个Java类型,那么T.class就代表了匹配的类对象: Class cl1=Manager.class; Class cl2=int.class; Class cl3=Double[].class; 注意的是,Class对象实际上描述的只是类型,而这类型未必是类。

18.可以利用newInstance()方法为类创建一个实例,如e.getClass().newInstance();创建了一个同e一个类型的新实例。newInstance方法调用默认构造器(无参数构造器)初始化新建对象。

19.使用反射分析类的功能:java.lang.reflect包中的三个类Field、Method、Constructor类分别描述类的字段、方法和构造器。它们都有一个getName方法。Field类有个getType方法,返回一个用来描述字段类型的Class对象。而Method和Constructor类都有报告这些方法的返回类型和参数类型的方法(一系列get方法)。三个类都有一个getModifiers方法,它返回一个整数,其不同位的设置描述了所使用的修饰符。可以使用Modifier类的静态方法来分析getModifiers返回的整数。

20.在运行时使用反射分析对象:如果f是一个Field类型的对象,且obj是f字段所属类的对象,那么f.get(obj)会返回一个对象,它的值是obj中该字段的当前值。也可以通过f.set(obj,value)把obj对象的f字段设为新值。但是,只能使用get方法获取可访问字段的值。除非具有访问权限,Java安全机制允许你找出任意对象有哪些字段,但它不会允许你读取那些字段的值。(可以通过setAccessible方法屏蔽访问控制)

21.方法指针:Method类有一个invoke方法允许调用包装在当前Method对象中的方法。方法原型: Object invoke(Object obj,Object[] args) 第一个参数是隐式参数,对象数组提供了显式参数。对于静态方法来说,第一个参数会被忽略,可以设为null。如果方法没有显式参数,则可以为args参数传递一个null或一个长度为0的数组。

22.不要滥用反射:反射机制通过在运行时探查字段和方法,从而可以帮助写出通用性很好的程序。这项能力对系统编程来说特别有用,但它不适合于应用编程。而且反射是脆弱的,编译器不能帮助你发现编程错误,任何错误在运行时被发现都会导致异常。

第6章 接口和内部类[27]

1.接口中的任何方法都自动是public类型的,无需提供public关键字。不能在接口中放置静态方法,接口中也绝不会去实现方法。在实现接口时必须把方法声明为public。

2.接口中可以提供常量。

3.接口中决不能有实例字段。接口中的字段总是默认为public static final的,无需提供关键字说明。

4.接口不是类,不能构造接口对象,但还是可以声明接口变量。

5.也可以用instanceof来检查对象是否实现了某个接口。

  1. 使用=号拷贝一个变量时,原始值和拷贝指向同一个对象。如果需要copy成一个新对象,需要使用clone方法。clone方法只是定义在Object中的一个protect方法,它只会按照字段逐一拷贝。

  2. 如果源对象和克隆对象共享的子对象是不可改变的,则浅拷贝没有问题。有两种情况可能发生子对象不可改变的现象:子对象属于不能改变的类,如String。或者子对象在其生命周期内只保存一些常量,在其上没有更改方法,也没有方法生成对它的引用。

8.当子对象是可变的时候,必须重新定义clone方法进行深拷贝以同时克隆子对象。

9.Cloneable接口是一个标记接口,跟clone方法没有关系(该方法是在Object类中定义的)。如果对象要求克隆,但没有实现这个接口,那么会产生一个已检查异常。

10.建立深拷贝的clone方法: Class Employee implements Cloneable{ public Object clone(){ try{ //调用Object.clone() //clone方法总是返回Object,需要进行类型转换。 Employee cloned = (Employee)super.clone(); cloned.hireDay=(Date)hireDay.clone(); return cloned; } catch(ClonNotSupportedException e) { return null;} } }

11.使用内部类的原因主要有四个: 1)内部类对象能够访问创建它的对象的实现,包括其私有数据; 2)内部类能够隐藏起来,不为同一包中的其他类所见; 3)匿名内部类可以方便地定义运行时回调; 4)使用内部类在编写事件驱动的程序时很方便

12.内部类的对象实例含有一个隐式引用,指向那个实例化它的外部类对象。通过这个指针,内部类对象可以访问外部对象的全部状态。

13.内部类对象并不是外部类的实例字段,而是外部类方法中的局部变量;

14.只有内部类才是私有的,普通类总是具有包可见性或者公有可见性。

15.局部内部类不会使用访问修饰符来指示,它们的范围总是限定在声明它们的程序块中。它们能够对外部世界完全隐藏起来,除了定义局部内部类所在的方法,没有方法知道其存在。它不仅能访问外部类中的字段,甚至还能访问局部变量,不过那些局部变量必须被声明为final的: public class BankAccount{ … public void start(final double rate){ class InterestAdder implements ActionListener{ public void actionPerformed(ActionEvent event){ double interest = balance*rate/100; balance+=interest; … } } ActionListener adder = new InterestAdder(); Timer t= new Timer(1000,adder); t.start(); } } 为了让actionPerformed方法中的代码能够工作,InterestAdder类必然在释放start方法的局部变量rate之前给它做了一份拷贝。

16.匿名内部类:创建一个实现了ActionListener接口的类的新对象。 public class BankAccount{ … public void start(final double rate){ ActionListener adder = new ActionListener (){ public void actionPerformed(ActionEvent event){ double interest = balance*rate/100; balance+=interest; … } }; Timer t= new Timer(1000,adder); t.start(); } }

17.由于构造器名必须和类名相同,而匿名类没有名字,所以匿名内部类不能有构造器。取而代之的是,构造器参数被送到超类的构造器中,用于构造对象的任何参数都要放在超类型名字后的括号中,一般情况下语法如下: new SuperType(construction parameters) { 内部类方法和数据 } 这里,超类型可以是接口,那么内部类实现该接口;也可以是类,那么内部类扩展这个类。例如: Person queen = new Person(“Mary”); //一个Person对象 Person count = new Person(“Dracula”){…}; //一个扩展了Person的内部类的对象 如果内部类实现的是接口,那么该内部类没有任何构造参数,不仅如此,还必须按照如下语法提供一组括号: new InterfaceType(){methods and data}

18.有时候使用内部类只是为了把一个类隐藏在另一个类中,并不需要内部类具有对外部类对象的引用。在这种情况下可以把内部类声明为static以去掉生成的引用。如果内部类是在一个静态方法中构造的,则必须使用静态内部类。

19.代理类能够在运行时创建新的类,这样的代理类能够实现你指定的接口,尤其是代理类具有:指定接口所要求的所有方法,Object类定义的所有方法。但是不能在运行时为这些方法定义新的代码,必须提供一个调用处理器。

20.调用处理器:是实现了InvocationHandler接口的任意类的对象,该接口只有一个方法: Object invoke(Object proxy,Method method,Object[] args) 只要调用了代理对象上的任意一个方法,调用处理器的invoke方法就会带着Method对象和原调用的参数被调用,随后调用处理器必须指出如何处理调用。

21.要创建一个代理对象,需要使用Proxy类中的newProxyInstance方法。该方法有三个参数: 1)一个类加载器; 2)一个Class对象数组,每个元素都是需要实现的接口; 3)一个调用处理器;

22.使用代理和调用处理器来跟踪方法调用,打印被调用方法的名字和参数,然后使用包装过的对象作为隐含参数来调用方法: import java.lang.reflect.; import java.util.; public class ProxyTest {
public static void main(String[] args) {
Object[] elements = new Object[1000]; // fill elements with proxies for the integers 1 … 1000 for (int i = 0; i < elements.length; i++) { Integer value = new Integer(i + 1);
InvocationHandler handler = new TraceHandler(value); //为所有接口构造代理 Class[] interfaces = value.getClass().getInterfaces(); Object proxy = Proxy.newProxyInstance(null, interfaces, handler); elements[i] = proxy; } // construct a random integer Random generator = new Random(); int r = generator.nextInt(elements.length); Integer key = new Integer(r + 1);

  // binarySearch方法这样调用:if(elements[i].compareTo(key)<0)…
  int result = Arrays.binarySearch(elements, key);

  // print match if found
  if (result >= 0)
     System.out.println(elements[result]);    } }

class TraceHandler implements InvocationHandler { public TraceHandler(Object t) {
target = t; }

public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
// 在实际调用之前添加操作:打印调用对象及方法的信息 System.out.print(target); System.out.print(“.” + m.getName() + “(“); if (args != null) { for (int i = 0; i < args.length; i++) {
System.out.print(args[i]); if (i < args.length - 1) System.out.print(“, “); } } System.out.println(“)”);

  // invoke actual method
  return m.invoke(target, args);    }

private Object target; //包装的对象 }

23.代理类是在程序运行时创建的,然而一旦被创建,也就成了常规类,同虚拟机中的任何其他类没有区别;

24.所有的代理类都扩展了Proxy类。一个代理类只有一个实例变量:定义在Proxy超类中的调用处理器。执行代理对象的任务所需的任何附加数据必须存储在调用处理器中。

25.代理类的名字是没有定义的,Java SDK中的proxy类会自动生成类名,这些名字以字符串$开始。

26.对于一个特定的类加载器和预设的接口组来说,只有唯一的一个代理类。如果使用同一个类加载器和接口组对newInstance方法进行两次调用的话,将会得到同一个类的两个对象。可以通过getProxyClass方法得到该类: Class proxyClass = proxy.getProxyClass(null,interfaces);

27.代理类总是public和final的。如果代理类实现的所有接口都是public的,那么此代理类不属于任何特定的包。否则,所有的非公有的接口必须属于同一个包,代理类也属于那个包。

第7章 图形编程[15]

1.Java中的顶层窗口被称为框架,在Swing中为JFrame,它从AWT中的Frame类扩展而来。JFrame是少数几个不绘制在画布上的Swing组件之一。因此它的修饰部件(按钮、标题栏、图标等)是通过用户的窗口系统,而非Swing绘制的。

2.框架在建立时是不可见的,这使得程序员有机会在框架初次显式前为其增添一些组件。为了显式框架需要调用其show方法。

3.默认情况下,当用户关闭一个框架时,该框架会隐藏起来,但程序不会终止。可以定义用户关闭框架时的响应动作,如让程序退出:frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

4.设定框架位置:setLocation(x,y) 重新设定位置及大小:setBounds(x,y,width,height) 把框架设为最大:frame.setExtendedState(Frame.MAXIMIZED_BOTH)

5.得到屏幕大小: Toolkit kit = Toolkit.getDefaultToolkit(); Dimension screenSize = kit.getScreenSize(); int w= screenSize.width; int h= screenSize.height; 设定框架图标: Image img=kit.getImage(“icon.gif”); setIconImage(img);

6.添加组件至容器内容窗格 Container contentPane = frame.getContentPane(); Component c=…; contentPane.add(c); 如果只需在框架中显式一个Swing组件,可以这样: JComponent c=…; frame.setContentPane(c);

7.把一个普通的JPanel添加到内容窗格中是没有什么意义的,它什么也不做。要使它变得有意义必须使用继承来创建一个新类,然后通过覆盖或者添加方法的手段来获得所需的额外功能。特别是,为了能在面板中进行绘制,则需要: 1)定义一个扩展了JPanel的新类; 2)覆盖paintComponent方法;该方法定义在所有Swing组件的父类JComponent类中,它含有一个Graphics类型的参数。为了确保超类完成自己的那份工作,必须在进行自己的绘制工作之前先调用super.paintComponent方法。 只要窗口需要重绘,不管是什么原因,事件处理器都会通知组件,它会引起所有组件中的paintComponent方法被执行。绝不要自己调用这个方法,不应干涉这个自动的过程。

8.如果需要强制性重绘屏幕,可以调用repaint方法,这个方法会使用正确配置了的Graphics参数引起对所有组件的paintComponent调用。

9.Graphics2D类从Graphics扩展而来,用来代替后者。Java 2D库使用面向对象的方式来组织几何形状,为了绘制形状,首先需要创建一个实现了Shape接口的类的对象,然后调用Graphics2D类的draw方法: public void paintComponent(Graphics g){ Graphics2D g2=(Graphics2D)g; Rectangle2D rect=…; g.draw(rect); }

10.设定颜色: g2.setPaint(Color.RED);或者g2.setPaint(new Color(0,128,128)); 如果使用的是Graphics对象而非Graphics2D,那么需要使用setColor方法设置颜色。

11.设置框架的背景颜色为用户桌面上所有窗口使用的默认值: frame.setBackground(SystemColor.window)

12.填充闭合形状的内部区域只需用fill代替draw: Graphics2D g2=(Graphics2D)g; Rectangle2D rect=…; g2.setPaint(Color.RED); g.fill(rect);

13.设置字体: Font f=new Font(“Serif”,Font.BOLD,36); g2.setFont(f);

14.从本地读取图像: String fileName=…; Image image = ImageIO.read(new File(filename)); 从网络获取图像: String urlName=…; Image image = ImageIO.read(new URL(filename)); 显示图像:g.drawImage(image,x,y,null);

15.拷贝屏幕的一块区域:void copyArea(int x,int y,int width,int heigh,int dx,int dy)

第8章 事件处理[42]

1.AWT中事件处理机制概览: 1)一个监听器对象是一个实现了专门的监听器接口的类的实例; 2)一个事件源是一个能够注册监听器对象并向它们发送事件对象的对象; 3)事件发生时,事件源会把事件对象发送给所有的注册监听器; 4)监听器对象随后会使用事件对象中的信息来决定对事件的反应。

2.习惯使用内部类: button.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent event){ String command=event.getActionCommand(); … } }

3.适配器类:每个具有不止一个方法的AWT监听器接口都有一个实现其所有方法,但方法中什么也不做的适配器类。如WindowListener接口对应有WindowAdapter类。这意味着适配器类自动满足了Java的实现相关监听器接口的技术要求。可以通过扩展适配器类为接口中的特定事件类型指定所希望的反应,而不是去实现接口中的全部方法。

4.尽管javax.Swing.event包中包含很多Swing组件专用的监听器接口,但是对于通常的事件处理,它仍然使用基本的AWT监听器接口。

5.AWT明确区分了语义事件和低层事件,语义事件是用于表达用户操作的事件,低层事件是使这些成为可能的事件。java.awt.event包中有四个语义事件类:ActionEvent、AdjustmentEvent、ItemEvent、TextEvent; 有六个低层事件类:ComponentEvent、KeyEvent、MouseEvent、MouseWheelEvent、FocusEvent、WindowEvent、ContainerEvent。

6.Java明确区分了字符和虚拟键代码。虚拟键代码用VK_来表示,比如VK_A、VK_SHIFT。假设用户按下shift键的同时再去按A键,那么相应这个用户动作Java将会生成5个事件: 1)按SHIFT键(为VK_SHIFT调用keyPressed) 2)按A键(为VK_A调用keyPressed) 3)键入“A”(为“A”调用keyTyped) 4)释放A键(为VK_A调用keyReleased) 5)释放SHIFT(为VK_SHIFT调用keyReleaseed) keyTyped将keyPressed和keyReleased结合起来,它报告用户击键所生成的字符,而keyPressed和keyReleased则报告用户实际按下的键。

7.测试用户是否按下了SHIFT和右箭头键: puclic void keyPressed(KeyEvent event) { int keyCode = event.getKeyCode(); if(keyCode == KeyEvent.VK_RIGHT && event.isShiftDown()) { … } }

8.并不是每次击键都会keyTyped方法捕获,只有那些能够生成Unicode字符的击键动作才会被捕获。在keyTyped方法中可以通过调用getKeyChar方法得到实际键入的字符。

9.鼠标操作会被不同的用户界面组件内部处理并翻译成相应的语义事件。

10.当用户点击了鼠标按钮时会调用下面三个监听器方法:mousePressed、mouseReleased、mouseClicked。如果只对完成点击感兴趣,可以忽略前两个方法,通过在MouseEvent参数上调用getX和getY能够得到鼠标点击时的坐标;

11.如果要区分单击、双击或者三击,需要使用getClickCount方法。

12.getModifiersEx方法能够准确报告鼠标事件中鼠标按键和键盘修饰符的值,在Windows下鼠标右键的掩码为:BUTTON3_DOWN_MASK,所以可以这样来监测鼠标右键是否按下: if( (event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) !=0 )

13.改变鼠标指针形状: setCursor(Cursor.getDefaultCursor()); setCursor(Cursor.getPredefinedCursor(Cursor.CRODDHAIR_CURSOR));

14.定义自己的光标类型: Toolkit tk = Toolkit.getDefaultToolkit(); Image img = tk.getImage(“cursor.gif”); Cousor newCousor = tk.createCustomCursor(img,new Point(10,10), “new cousor”);

15.如果用户在移动鼠标时按下一个鼠标按钮,则会调用mouseDragged而非mouseClicked方法。只有鼠标停留在组件中mouseMoved方法才会被调用,而mouseDragged方法在即使鼠标被拖动到组件之外时还是会被调用。

16.同一时刻,一个窗口中最多只能有一个组件拥有焦点。组件获得焦点的方法有两个:用户使用鼠标点击该组件;或者用户使用TAB键在各个组件间轮流切换焦点。

17.有些组件,如面板,在默认情况下是不接受焦点的,但默认情况可以覆盖进行修改,如:panel.setFocusable(true);

18.焦点窗口和活动窗口通常是相同的,两者不同的情况只发生在焦点拥有者处于没有框架修饰的顶层窗口(如弹出菜单)的时候。

19.得到焦点拥有者、焦点窗口、活动窗口: KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); Component owner= manager.getFocusOwner(); Window focused= manager.getFocusedWindow(); Window active= manager.getActiveWindow();

20.为了在焦点改变时获得通知,需要在组件或者窗口中安装焦点监听器。组件焦点监听器必须实现FocusListener接口,这个接口有两个方法:focusGained和focusLost。两个方法都有一个FocusEvent参数。

  1. FocusEvent的isTemporary方法在发生临时性焦点改变时会返回true,当一个组件暂时失去控制但会自动重新获得时,就会发生临时性的焦点改变。比如用户选择了其他的活动窗口,一旦用户重新选择了当前窗口,原来那个组件将重新获得焦点。

22.要获得窗口焦点事件,需要为窗口安装WindowFocusListener,并实windowGainedFocus和windowLostFocus方法。

23.当组件或窗口丢失焦点时,对端指的是获得焦点的组件或窗口;在组件或窗口获得焦点时,对端则是指丢失焦点的组件或窗口。FocusEvent类的getOppositeComponent方法用于报告对端组件,WindowEvent类的getOppositeWindow用于报告对端窗口。

24.可以使用Component类的requestFocus或requestFocusInWindow方法来把焦点移动到其他组件上。后者只在组件位于焦点窗口中时才成功,而前者对于没有包含在当前焦点窗口中的组件执行成功与否取决于平台。

25.不应该在requestFocus或requestFocusInWindow方法返回true的时候就认定组件已经获得了焦点,而是应该等待FOCUS_GAINED事件的发送。

26.Action接口是Swing包提供的一种机制,用来封装命令并把它们连接到多个事件源。Action接口扩展了ActionListener接口,Action接口具有如下方法: void addPropertyChangeListener(PropertyChangeListener listener) 添加一个 PropertyChange 侦听器。 Object getValue(String key) 使用关联的键获取此对象的一个属性。 boolean isEnabled() 返回 Action 的启用状态。 void putValue(String key, Object value) 使用关联的键设置此对象的一个属性。 void removePropertyChangeListener(PropertyChangeListener listener)移除一个 PropertyChange 侦听器。 void setEnabled(boolean b) 设置 Action 的启用状态。

  1. 一个动作就是一个对象,它封装了:命令的说明、执行命令需要的参数。当一个动作被连接到菜单项或工具栏按钮,但是动作被禁止时,那么菜单项或者按钮会变为灰色。

28.用预定义动作名把动作名字和图标存储到动作对象中: action.putValue(Action.NAME,”blue”); action.putValue(Action.SMALL_ICON,new ImageIcon(“blue-ball.gif”)); 如果动作对象被添加到菜单或者工具栏中,那么它的名字和图标会自动地显示在菜单项或者工具栏按钮上。

29.有一个类实现了Action接口中除了第一个方法以外的所有方法,即AbstractAction类。

30.JButton类有一个构造器使用Action对象作为参数: public class ColorAction extends AbstractAction… Action blueAction = new ColorAction(“Blue”,new ImageIcon(“blue-ball.gif”),Color.BLUE); JButton blueButton = new JButton(blueAction); 构造器将读取动作中对命令的说明(字符串或可选图标)并进行相应的设置。

31.要把动作关联到击键,首先需要生成一个KeyStroke类的对象。KeyStroke类封装了对键的说明,可以使用KeyStroke类的静态方法getKeyStroke来生成一个KeyStroke对象。 可以指定虚拟键代码和标志来生成: KeyStroke ctrlBKey = KeyStroke.getKeyStroke(KeyEvent.VK_B,Event.CTRL_MASK); 或者使用字符串来说明一次击键: KeyStroke ctrlBKey = KeyStroke.getKeyStroke(“ctrl B”);

32.每个JComponent都有三个输入映射来把KeyStroke对象映射到动作,这三个输入映射对应着三个不同的条件: WHEN_ANCESTOR_OF_FOCUSED_COMPONENT :用于 registerKeyboardAction 的常量,意味着当接收组件是获得焦点的组件的祖先或者其本身就是获得焦点的组件时,应该调用命令。 WHEN_FOCUSED :用于 registerKeyboardAction 的常量,意味着在组件获得焦点时应该调用命令。 WHEN_IN_FOCUSED_WINDOW:用于 registerKeyboardAction 的常量,意味着当接收组件处于获得焦点的窗口内或者其本身就是获得焦点的组件时,应该调用命令。 击键处理需要按照以下顺序来检查映射: 1)检查具有输入焦点的组件的WHEN_FOCUSED映射,如果该击键存在,那么执行相应的动作。如果该动作已启动,那么停止处理; 2)从具有输入焦点的组件开始,检查它父组件的WHEN_ANCESTOR_OF_FOCUSED_COMPONENT映射,如果找到击键对应的映射,则执行相应的动作,如果动作已启用,那么停止处理; 3)遍历具有输入焦点的窗口中的所有可视和已启用的组件,它们把这个击键注册到WHEN_IN_FOCUSED_WINDOW映射中。给这些组件(按照它们的击键注册顺序)一个机会来执行相应的动作。只要第一个启用的动作被执行了,那么停止处理。如果一个击键在多个WHEN_IN_FOCUSED_WINDOW映射出现,处理过程的这一部分会有些脆弱。

33.可以使用getInputMap方法从组件中得到输入映射,如: InputMap imap = panel.getInputMap(JComponent.WHEN_FOCUSED);

34.InputMap不直接把KeyStroke对映射到Action对象上,而是将其映射到一个临时对象上,然后第二个对象(由ActionMap类实现)把临时对象映射到动作。这使得很容易让来自不同输入映射的击键可以共享一个动作。

35.把一个键绑定到一个动作: InputMap imap = panel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); Action yellowAction = new ColorAction(“yellow”,new ImageIcon(“yellow-ball.gif”),Color.YELLOW); imap.put(KeyStroke.getKeyStroke(“ctrl Y”),”panel.yellow”); ActionMap amap = panel.getActionMap(); amap.put(“panel.yellow”,yellowAction); 空动作可以使用字符串”none”来表示,所以很容易取消一个键的动作: imap.put(KeyStroke.getKeyStroke(“ctrl Y”),”none”);

36.InputMap将一个KeyStroke对应到一个对象,ActionMap将一个对象对应到一个行为。通常InputMap中KeyStroke所对应的对象是一个字符串,通过这个字符串可以在ActionMap中查找到相应的行为。 InputMap:void put(KeyStroke keyStroke, Object actionMapKey),将 keyStroke 的一个绑定添加到 actionMapKey。 ActionMap:void put(Object key, Action action) ,添加一个 key 到 action 的绑定。

37.总结如何执行同一动作来响应按钮、菜单项或者击键: 1)创建一个类扩展AbstractAction类; 2)创建那个动作类的对象; 3)使用动作对象构造按钮或者菜单项。构造器从动作对象读取标签文本和图标; 4)对于能够通过键盘触发的动作来说,还要多做一步。先要定位窗口的顶层组件,如包含了其他所有组件的面板。 5)得到顶层组件的WHEN_ANCESTOR_OF_FOCUSED_COMPONENT输入映射。为需要的击键创建一个KeyStroke对象。创建动作键对象,比如字符串来描述动作,增加(击键、动作键)对到输入映射中去。 6)最后得到顶层组件的动作映射。把(动作键、动作对象)对添加进该映射中。

38.所有AWT事件源都支持一种对应监听器的多点传送模型。也即同一事件能够被传送到不止一个监听器对象。只要把多个监听器添加到一个事件源中就能够让所有注册的监听器都能够对事件作出响应。

39.得到表示事件队列的对象: EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue(); 将新事件添加到事件队列中: queue.postEvent(new ActionEvent(this,ActionEvent.ACTION_PERFORMED,”Blue”)); 使用getNextEvent方法可以删除一个事件。而peekEvent方法则返回队列中的下一个事件,但是该方法并不删除该事件。

40.定制事件的三要素: 1)事件类型,AWT事件队列中的所有事件都必须为AWTEvent或其子类型; 自定义事件类的实现中需要给超类一个事件ID号,程序选择的事件ID值应该大于整数常量 AWTEvent.RESERVED_ID_MAX,如下: class TimerEvent extends AWTEvent {
public TimerEvent(Timer t) { super(t, TIMER_EVENT); } public static final int TIMER_EVENT= AWTEvent.RESERVED_ID_MAX + 5555; } 2)事件监听器接口 interface TimerListener extends EventListener {
public void timeElapsed(TimerEvent event); } 3)事件源,AWT事件机制要求事件源扩展Component类。

41.事件源的责任: 1)管理它生成的事件监听器; 2)把事件分发给向它注册的监听器; Swing提供类EventListenerList来简化用于添加、删除和激发事件的方法的实现。这个类会处理多线程同时添加、删除、分发事件时可能引发的细节问题。因为有些事件源接收多种类型的监听器,所以事件监听器列表中的每一个监听器都同特定的类关联起来。add和remove方法是用于实现addXXXListener及removeXXXListener方法的,例如: private EventListenerList listeners; public void addTimerListener(TimerListener listener) {
listenerList.add(TimerListener.class, listener); } public void removeTimerListener(TimerListener listener) {
listenerList.remove(TimerListener.class, listener); }

42.当AWT把事件从队列中删除时,它调用processEvent方法, Component:protected void processEvent(AWTEvent e) 处理组件上发生的事件。 例如: public void processEvent(AWTEvent event) {
if (event instanceof TimerEvent) { EventListener[] listeners = listenerList.getListeners(TimerListener.class); for (int i = 0; i < listeners.length; i++) ((TimerListener)listeners[i]).timeElapsed( (TimerEvent)event); } else super.processEvent(event); }

第9章 Swing用户界面组件[47]

1.MVC设计模式: 1)模型:存储内容,实现改变和发现内容的方法。模型是不可见的。 2)视图:显示内容,一个模型可以有多个视图,其中每个视图可以显示完整内容的不同部分或不同方面。 3)控制器:处理用户输入,决定是否把用户输入事件转化成对模型或视图的改变。

2.对于大多数控件,模型类实现了名字结尾为Model的接口,例如ButtonModel接口。一般来说,每个Swing组件都有一个后缀为UI的相关视图对象,但并不是所有的Swing组件都有专门的控制器对象。

3.容器内的所有组件都由一个布局管理器进行管理,面板的默认布局管理器为流布局管理器,其特点是在一行上水平排列组件,直到该行没有足够的空间,则另起一行。当用户缩放容器时,布局管理器会自动回流组件使其填充可用空间。可以指定对其方式,如:setLayout(new FlowLayout(FlowLayout.LEFT));

4.JFrame的内容窗格的默认布局管理器为边界布局,可以选择把组件放在窗格中的中部、北部、南部、东部或者西部。如:add(yellowButton,BorderLayout.SOUTH);边界布局会增大所有组件来填充可用空间。

5.网格布局按行列来排列所有的组件,每个单元总是一样的大小。可以指定行数和列数及水平和垂直间距: panel.setLayout(new GridLayout(5,4,3,3)); 添加组件时,从第一行的第一列开始,然后是第一行的第二列,这样持续下去。

6.对应单行文本输入的类为JTextField,对应多行文本的类为JTextArea,这两个类都继承自抽象类JTextComponent类。

7.可以在JTextField的构造器中设定其宽度,如JTextField textField=new JTextField(“Default String”,20);但该宽度并不是用户能够输入的字符个数的上限。如果需要在运行时重新设置列数,可以使用setColumns方法,如: textField.setColumns(10); panel.validate(); validate方法会重新计算容器内所有组件的大小,并且对它们重新布局。

8.所有文本组件的模型都由Document接口说明,既包括无格式的文本,也包括格式化的文本。当数据改变后,可以要求文档(而非文本组件)发出通知,这需要安装一个文档监听器: textField.getDocument().addDocumentListener(listener); 当文本发生改变后,下面三个方法中的一个会被调用: void insertUpdate(DocumentEvent e) void removeUpdate(DocumentEvent e) void changedUpdate(DocumentEvent e)

9.Java提供JpasswordField类来实现密码框,其setEchoChar方法用来设置回显字符,getPassword方法用来获取包含在密码框内的文本。

10.JFormattedTextField类用来实现格式化输入。如创建一个整型输入的文本框: JFormattedTextField intField = new JFormattedTextField( NumberFormat.getIntegerInstance(); ) 可以用setValue的方法来设置默认值,该方法参数为Object类型,如:intField.setValue(new Integer(100));可以用getValue方法得到结果,该方法返回类型为Object,需要将它转化为适当的类型。如果用户没有编辑数值,它返回原来的Integer对象,如果用户编辑了数值,则返回类型为Long的对象。所以应该把返回值转化为通用的超类Number: Number value = (Number)intField.getValue(); int v = value.intValue();

11.当格式化文本框失去焦点时,格式器检测用户输入的文本字符串。如果格式器知道怎么把文本字符串转化为对象,那么该文本就是有效的,否则就是无效的。可以用isEditValid方法进行检测。

12.失去焦点的默认行为被称为“提交或者恢复”。如果文本字符串有效,那么就提交它,格式器把它转化为对象,该对象变为当前文本框的值。然后该值转化为字符串,成为文本框内可见的文本字符串。如果文本字符串无效,那么当前值不会改变,文本框恢复代表原来值的字符串。如果文本字符串以一个整数开始,那么整型格式器就认为该字符串是有效的。可以用setFocusLostBehavior方法来设置其他行为。

13.任何时候控制器处理一个命令使文本插入到文档中,这被称为“插入字符串”命令。被插入的字符串可以是单独的一个字符也可以是粘贴缓冲区的内容。文档过滤器可以中途截取该命令,并且修改字符串或者取消插入,例如,实现一个过滤器分析要插入的字符并且只插入是数字或者-符号的字符: class IntFilter extends DocumentFilter { public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException { StringBuffer buffer = new StringBuffer(); for(int i=buffer.length()-1;i>=0;i–) { char ch = buffer.charAt(i); if(!Character.isDigit(ch) && ch!=’-‘) buffer.deleteChar(i); } super.insertString(fb,offset,buffer.toString(),attr); } } 也可以覆盖DocumentFilter类的replace方法,当选择并替换文本时,该方法被调用。

14.安装文本过滤器需要覆盖格式器类的getDocumentFilter方法,并传递格式器类的对象到JFormattedTextField,如: JFormattedTextField intField = new JFormattedTextField(new InternationalFormatter(NumberFormat.getIntegerInstance()) { protected DocumentFilter getDocumentFilter() { return filter; } private DocumentFilter filter=new IntFilter(); });

15.可以给任何JComponent附加检验器。如果组件失去焦点,则询问检验器,如果检验器报告说组件的内容无效,该组件立刻重新获得焦点,用户在提供其他输入前被强迫修改内容。检验器必须继承抽象类InputVerifier,并且定义verify方法。如: class FormattedTextFieldVerifier extends InputVerifier { public boolean verify(JComponent component) { JFormattedTextField field=(JFormattedTextField)component; return field.isEditValid(); } } 安装检验器:intField.setInputVerfier(new FormattedTextFieldVerifier());

16.NumberFormat类有静态方法:getNumberInstance、getCurrencyInstance、getPercentInstance用来产生浮点数、金额和百分数的格式器。编辑日期和时间可以使用DateFormat类的静态方法之一。

17.DefaultFormatter可以格式化任何类的对象,这些类都有一个以一个字符串为参数的构造器和匹配的toString方法。该格式器针对文本框值调用toString来初始化文本框的文本。当文本框失去焦点时,格式器使用带有String参数的构造器构造相同类的新对象作为当前值。如果构造器抛出异常,那么编辑就是无效的。

18.MaskFormatter对处理一些常量和一些变量字符的固定尺寸样式很有用。如,new MaskFormatter(“##-###-##”);

19.自定义格式器需要继承DefaultFormatter类并且覆盖以下方法: String valueToString(Object value),把值转化为字符串显式在文本框中; Object stringToValue(String text),分析用户输入的文本,并把它转回为对象。如果其中任一方法检测到错误,将抛出ParseException异常。如自定义IP地址格式器: class IPAddressFormatter extends DefaultFormatter { public String valueToString(Object value) throws ParseException { if (!(value instanceof byte[])) throw new ParseException(“Not a byte[]”, 0); byte[] a = (byte[])value; if (a.length != 4) throw new ParseException(“Length != 4”, 0); StringBuffer buffer = new StringBuffer(); for (int i = 0; i < 4; i++) { int b = a[i]; if (b < 0) b += 256; buffer.append(String.valueOf(b)); if (i < 3) buffer.append(‘.’); } return buffer.toString(); }

public Object stringToValue(String text) throws ParseException { StringTokenizer tokenizer = new StringTokenizer(text, “.”); byte[] a = new byte[4]; for (int i = 0; i < 4; i++) { int b = 0; try { b = Integer.parseInt(tokenizer.nextToken()); } catch (NumberFormatException e) { throw new ParseException(“Not an integer”, 0); } if (b < 0 || b >= 256) throw new ParseException(“Byte out of range”, 0); a[i] = (byte)b; } return a; } }

20.如果文本区中的文本超出显示范围,那么剩下的文本就会被修剪掉。可以通过使用换行来避免修剪长行:textArea.setLineWrap(true);换行只是视觉效果,文档中的文本并没有改变,并没有’\n’字符被插入到文本中。

21.文本区没有滚动条,如果需要滚动条,必须把文本区插入到一个滚动窗格中,例如: JScrollPane scrollPane=new JScrollPane(textArea); 滚动是由滚动窗格内部处理的,无需处理滚动事件。

22.JtextArea组件只显示无格式的文本,如果要显示格式化文本,需要使用JEditorPane和JTextPane

23.可以使用SwingConstants接口中的常量来指定对其方式,JLabel是实现了此接口的Swing类之一,因此可以如下构造一个左对齐的标签: JLabel label = new JLabel(“TEXT”,SwingConstants.LEFT); 可以在按钮、标签、菜单项中直接使用HTML文本,如: label = new JLabel(“<html>Requiredentry:</html>”);

24.文本框类和文本区类都从超类JTextComponent中继承了方法来选择组件包含的文本,这两个类也都能够检查当前所选文本。 selectAll方法用来选中所有文本;select方法用来选中文本的一部分,其参数和subString完全一样;getSelectionStart和getSelectionEnd方法返回当前选择的起始和结束位置,getSelectedText返回选中的文本。

25.实现单选按钮组:首先为按钮组构造一个ButtonGroup类型的对象,接着把JRadioButton类型的对象添加到该按钮组中,在按钮组中,当点击一个新按钮时,前一个被选中的按钮自动取消选定,如: ButtonGroup group = new ButtonGroup(); JRadioButton smallButton=new JRadioButton(“Small”,false); group.add(smallButton);

26.只有通过setActionCommand命令明确设定了所有单选按钮的动作命令,才能通过方法:buttonGroup.getSelection().getActionCommand()获得当前选中的按钮的动作命令(也可以为每个单选按钮添加独立的动作监听器)。

27.可以为任何扩展了JComponent的组件提供一种边界,最常见的用法是在一个面板周围设置一种边界,然后用其他用户界面元素来填充该面板。有几种不同的边界可供选择,但是使用它们的步骤完全一样: 1)调用BorderFactory的一个静态方法来创建边界,Java自定义了几种不同的边界风格; 2)可以为边界添加一个标题,使用BorderFactory.creatTitledBorder实现; 3)可以把几种边界组合起来,使用BorderFactory。createCompoundBorder实现; 4)将结果添加到组件中去,通过调用JComponent的setBorder方法实现: Border etched=BorderFactory.createEtchedBorder(); Border title=BorderFactory.createTitledBorder(etched,”A Title”); panel.setBorder(titled);

28.当用户从组合框中选择一个选项时,该组合框就会生成一个动作事件。为了判断哪个选项被选择,可以在事件参数上调用getSource方法来得到发送事件的组合框的一个引用,接着调用getSelectedItem方法来提取当前被选择的项,如: faceCombo.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { label.setFont(new Font( (String)faceCombo.getSelectedItem(),Font.PLAIN, DEFAULT_SIZE));
} });

29.构造一个滑块:JSlider slider=new JSlider(min,max,initialValue); 当用户滑动滑块时,一个ChangeEvent事件就会被发送到所有的改变监听器上。为了得到这些改变的通知,需要调用addChangeListener方法,并安装实现了ChangeListener接口的对象,该接口只有一个方法stateChanged,在该方法中,可以提取滑块的值: public void stateChange(ChangeEvent event) { JSlider slider =(JSlider)event.getSource(); int value = slider.getValue(); }

30.可以通过显示标尺来修饰滑块,如: slider.setMajorTickSpacing(20);//大标尺 slider.setMinorTickSpacing(5);//小标尺 要把标尺实际显示出来还须调用:slider.setPaintTicks(true); 可以强制滑块对其标尺:slider.setSnapToTicks(true);

31.也可以提供其他标尺标记,如字符串或者图标:首先填充一个键值为new Integer(tickValue),而值为Component类型的哈希表,然后调用setLabelTable方法,组件就会放置在标尺标记下,如把标尺的标签设为A,B,C,D,E,F: Hashtable labelTable=new Hashtable(); labelTable.put(new Integer(0),new JLabel(“A”)); labelTable.put(new Integer(0),new JLabel(“A”)); … labelTable.put(new Integer(0),new JLabel(“F”)); slider.setLabelTable(labelTable);

32.填充滑块:slider.putClientProperty(“JSlider.isFilled”,Boolean.TRUE); 逆向填充:Slider.setInverted(true);

33.JSpinner也是文本框,它在一边带有两个小按钮,可以增加或者减少存储在文本框内的值。

34.创建一个菜单栏:JMenuBar menuBar=new JMenuBar(); 在框架中添加菜单栏:frame.setJMenuBar(menuBar); 创建一个菜单对象并添加到菜单栏中: JMenu editMenu=new JMenu(“Edit”); menuBar.add(editMenu); 在菜单对象中添加菜单项、分隔符及子菜单: JMenuItem pasteItem=new JMenuItem(“Paste”); editMenu.add(pasteItem); editMenu.addSeparator(); JMenu optionsMenu=…;//submenu editMenu.add(optionsMenu); 利用JMenu.add(String s)可以把一个菜单项添加到菜单的结尾,并返回创建的子菜单项,例如 JMenuItem pasteItem=editMenu.add(“Paste”); pasteItem.addActionListener(listener);

35.JMenuItem类继承了AbstractButton类,因此同按钮一样,菜单也可以具有文本标签、图标或者二者兼有。

36.复选框和单选按钮菜单项会在文本旁边显示一个复选框或一个单选按钮。当用户选择该菜单项时,该菜单项会自动在选择和未选择之间切换。如创建一个复选框菜单项: JCheckBoxMenuItem checkMenuItem=new JCheckBoxMenuItem(“ReadOnly”); 创建单选按钮菜单项使用JRadioButtonMenuItem。单选按钮菜单项同常规单选按钮一样,必须添加到一个按钮组中。

37.创建弹出菜单同创建常规菜单很相似,但是弹出菜单没有标题: JPopupMenu popup=new JPopupMenu(); 必须明确调用show方法来显示,且需要指定其父组件与位置: popup.show(panel,x,y);

38.用户点击弹出触发器弹出菜单的步骤: 1)安装一个鼠标监听器; 2)在鼠标监听器中安装如下代码: public void mousePressed(MouseEvent event) { if(event.isPopupTrigger()) popup.show(event.getComponent(),event.getX(),event.getY()); }

39.可以通过在菜单项构造器中指定一个快捷字母来为菜单项设置快捷键: JMenuItem cutItem = new JMenuItem(“Cut”,”T”); 若要为一个菜单设置快捷键,需要调用setMnemonic方法: JMenu helpMenu = new JMenu(“Help”); helpMenu.setMnemonic(‘H’);

40.快捷键用来从当前打开的菜单中选择一个子菜单或者菜单项,而加速器是在不打开菜单的情况下选择菜单项的快捷键,它直接激活同菜单关联的动作事件,如把加速器CTRL+O关联到openItem菜单项: openItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,InputEvent.CTRL_MASK));

41.只有工具栏(JToolBar)在一个具有边界布局或者其他任何支持North、East、South、West约束布局的容器内,那么它才能够被拖动。

42.三种复杂布局管理: 1)箱式布局:BoxLayout 2)网格组布局:GridBagLayout,使用GridBagConstraints约束布局,该约束包含一系列字段、参数; 3)弹簧布局:SpringLayout

43.当一个窗口初次显示时,便利顺序中的第一个组件会具有键盘焦点。每次用户按下TAB键,那么下一个组件就会得到焦点。Swing的遍历顺序是从左至右,从上到下的。如果一个容器中还包含了其他的容器,当焦点被设到其他容器时,该容器左上角的组件会得到焦点,并且依次在该容器内传递焦点,最后焦点被设到跟着该容器的组件上。

44.要改变遍历顺序,可以把相关组件组合到面板中,如果这样不奏效,那么就要安装一个比较器对组件进行不同排序,要么完全替换遍历策略。

45.JOptionPane有四个静态方法来显示简单对话框: 1)showMessageDialog,无返回值; 2)showConfirmDialog,代表选择项的一个整数; 3)showOptionDialog,代表选择项的一个整数; 4)showInputDialog,用户选择或输入的字符串;

46.简单对话框总结: 1)选择对话框类型:消息、确认、选项、输入; 2)选择图标:错误、信息、警告、问题、无、自定义; 3)选择消息:字符串、图标、定制组件、它们的集合; 4)对于确认对话框,选择选项类型:默认、Yes/No、Yes/NoCancel、OK/Cancel; 5)对于选项对话框,选择选项(字符串、图标、定制组件)和默认选项; 6)对于输入对话框,选择输入组件(文本框或者组合框); 底部按钮取决于对话框类型和选项类型。

47.文件选择对话框JFileChooser及颜色选择对话框JColorChooser其实都是组件而非对话框,也即它们并不是继承于JDialog;

第10章部署applet和应用程序[21]

1.applet仅仅是一个扩展了java.applet.Applet类的Java类,如果applet包含Swing组件就必须扩展JApplet类。applet中使用init函数来替代main函数,因为applet是在网页中运行的,所以没有必要指定退出方法。

2.applet嵌入Html中的格式: <applet code=”HelloWorld.class” width=”300” height=”300”> </applet>

3.将应用程序转换成applet的详细步骤: 1)创建一个HTML页面,它有合适的标记来装载applet代码; 2)创建一个JApplet的子类,将该类标记为public; 3)删除应用程序中的main方法,因为应用程序会在浏览器中显示,所以不要构造框架窗口; 4)将所有的初始化代码从框架窗口的构造器中移到applet的init方法中。不需要显式地构造applet对象,浏览器会实例化一个该对象并且调用init方法; 5)删除对setSize的调用,浏览器会通过html中的width和height来指定大小; 6)删除对setDefaultCloseOperation的调用,applet不可能被用户关闭,当浏览器退出时applet才会终止执行; 7)如果应用程序调用了setTitle方法,删除该调用,applet没有标题栏; 8)不要调用show方法,applet会自动显示;

4.applet的生命周期:applet类提供四个方法,它们构造了创建任何applet的框架: 1)init:当Java第一次运行applet时,系统会自动调用该方法,applet可以有构造器,但是通常是在init方法中而不是在默认的构造器中进行全部的初始化工作。 2)start:当init方法被调用后该方法会被自动调用,而当用户从其他页面返回到包含applet的页面时,该方法也被调用,也即start方法会被多次调用,而init方法仅仅会被调用一次。start方法是applet重新启动线程的地方,如果applet在离开当前页面时没有什么需要挂起,则没有必要实现该方法。 3)stop:该方法在用户离开包含applet的页面时会被自动调用。该方法是当用户不再关注applet时停止那些耗时操作。 4)destroy:仅仅在浏览器正常退出时,该方法会被调用。

5.不同于应用程序,applet的行为受限,当applet视图违反任何一条访问规则时,applet管理器都会抛出异常。限制applet的执行环境常常被称为“沙箱”,在“沙箱”中运行的applet不能修改或者探测用户系统。

6.Java通过三个独立的机制来加强安全性: 1)程序代码是在Java虚拟机中解释执行的,而不是直接运行; 2)安全管理器会检查Java运行库中的所有敏感操作; 3)applet可以通过签名技术来表明其来源。

7.想applet传递数据 <applet code=”FontParamApplet.class” width=”200” height=”200”> <param name=”font” value=”Helvetica”/> </applet>

public class FontParamApplet extends JApplet
{
	public void init()
	{
		String fontName=getParameter(“font”);
	}
}
只能在applet的init方法中调用getParameter方法,而不能在构造器中调用。

8.获得URL的常见方法是询问applet的来源,特别是: 1)当前调用它的页面的URL:使用getDocumentBase方法; 2)当前applet自己的URL:使用getCodeBase方法;

9.获取图像和声音文件: Image cat=getImage(getCodeBase(),”images/cat.gif”); AudioClip meow=getAudioClip(getDocumentBase(),”audio/meow.au”); 图像和声音文件必须放在同一个服务器上,而且applet代码也是存放在同一个服务器上,出于安全原因,applet不能访问其他服务器上的文件。 为了提高下载速度,可以将多媒体对象存储在JAR文件中,getImage和getAudioClip/play方法会自动搜索applet的jar文件。如果图像或者声音文件包含在一个jar文件内,它会被立刻从该JAR文件中加载,否则浏览器向服务器请求下载该声音或图像文件。

10.applet上下文:为了与浏览器通信,applet应该调用getAppletContext方法,该方法返回一个实现了AppletContext接口的对象。AppletContext接口的具体内部实现就是applet和浏览器之间的通信途径。

11.一个网页可以包含多个applet。如果一个网页中包含的多个applet都来自同一个codebase,则它们之间可以通信。如果HTML文件中的每个applet都被赋予了name属性,则可以使用AppletContext接口中的getApplet(String)方法来获得对applet的引用;无论有没有name属性,都可以列出网页上全部的applet,通过getApplets方法可以得到一个枚举对象,如下打印出当前页面包含的全部applet的类名: Enumeration e=getAppletContext().getApplets(); while(e.hasMoreElements()) { Object a=e.nextElement(); System.out.println(a.getClass().getName()); } 一个applet不能和其他网页上的applet通信。

12.修改浏览器状态栏: getAppletContext().showStatus(“…”;

13.打开新的网页: URL url=new URL(“http://www.baidu.com”); getAppletContext().showDocument(u,”_blank”);

14.Java允许将所有需要的类文件甚至是图像及声音文件打包成一个单一的文件。只要向服务器发出一个单一的http请求,就可以下载这个文件,这样的存档Java类文件的文件被称为Java存档文件(JAR)。JAR文件使用的是ZIP压缩格式的压缩文件。在applet标记中标记jar文件如下: <applet archive=”xxx.jar” … 每次需要类文件、图像或者声音文件时,浏览器首先在archive属性中指定的JAR文件中寻找,当文件不在这些JAR文件中时,才会到Web服务器上去取。

15.JAR文件是一个简单的ZIP格式文件,它包含类文件、程序需要的其他文件以及描述该JAR文件特性的清单文件。可以将应用程序所需要的类文件和其他资源打包成一个JAR文件,然后在清单文件中增加一项来指定该程序的主类(清单文件的最后一行必须是以换行符结束)。一旦程序打包后,就可以通过一个简单的命令来加载它,如果正确的配置了系统的话,还可以通过双击JAR文件来打开它。

16.Java中的资源定位机制 1)获得一个具有资源的类的Class对象; 2)调用getResource(filename)来获取资源的位置,返回值是URL; 3)如果资源是图像或者声音文件,则直接通过getImage或者getAudioClip方法读取它; 4)否则,就对URL使用openStream方法读取文件中的数据,要“从找到AboutPanel.class类的地方找到一个名为about.txt的文本文件“: URL url=AboutPanel.class.getResource(“about.txt”); InputStream in=url.openStream(); 也可以将这两步结合起来: InputStream in= AboutPanel.class.getResourceAsStream(“about.txt”);

17.Java Web Start是一项新技术,目的是改善用户通过Internet发布Java程序的经验,其与applet的主要区别如下: 1)Java Web Start用来发布普通的Java应用程序,这些应用程序是通过调用某一个类的main方法来启动的,它们不需要从Applet继承; 2)Java Web Start不在浏览器中运行,它在浏览器外显示,尽管它可以通过浏览器启动,但是后台的机制完全不同于applet的启动; 3)一旦一个Java Web Start应用程序被下载,它可以在浏览器外面运行; 4)Java Web Start的“沙箱“相对比较宽松,它允许未签名的应用程序访问一些本地资源;

18.对于一个要使用Java Web Start发布的应用程序,应该将其打包成一个或多个JAR文件,然后创建一个JNLP格式的描述符文件,并且将这些文件放在web服务器上。接着要确保web服务器对于具有.jnlp后缀的文件会返回application/x-java-jnlp-file的MIME类型描述,浏览器会使用MIME类型来确定使用哪一种辅助应用程序。

19.JNLP API允许应用程序开发者在一些限定的条件下访问某些本地资源,它提供如下服务: 1)打开和保存文件 2)访问剪贴板 3)下载文件 4)打印 5)保存和获取持久性的配置信息 6)在默认的浏览器中打开一个文档 要使用任何一种服务都需要使用ServiceManager,如: FileSaveService service=(FileSaveService)ServiceManager.lookup(“javax.jnlp.FileSaveService”); 如果服务不可用,则会抛出UnavailableServiceException异常。

20.属性集是存储键值对的数据结构,常常用来存放配置信息。在Java中实现属性集的类为Properties,例如: Properties settings=new Properties(); settings.put(“font”,”Courier”); setting.put(“size”,”10”);

21.Preferences类提供了一个与平台无关的中心存储器,它具有树状结构。在Windows系统上它就是使用注册表来存放信息。

第11章 异常和调试[13]

1.一个异常对象总是Throwable子类的实例。Throwable的子类演变成两个分支:Error和Exception。Error类体系描述了Java运行系统中的内部错误以及资源耗尽的情况,应用程序不应该抛出这种类型的对象。Exception自身也演变成两个分支:一个是RuntimeException的子类,以及不从它衍生的其他异常。由编程导致的错误会导致RuntimeException异常,而其他错误原因导致的异常,例如因为IO错误导致曾经运行正确的程序出错,都不会导致RuntimeException异常。

2.Java语言规范将任何Error的子类以及RuntimeException的子类都称为未检查异常,而其他的异常被称为已检查异常。

3.一个方法不仅仅要告诉编译器它返回什么样的值,还要告诉编译器什么样的错误可能发生。方法在其方法头中声明它可能会抛出的异常,这样定义的方法头反映了该方法会抛出哪些“已检查”异常。

4.仅仅在如下4种情况下才会抛出异常: 1)调用了一个会抛出“已检查异常”的方法; 2)程序运行过程中发生了错误,并且用throw语句抛出一个“已检查异常”; 3)程序错误,如数组下标越界; 4)Java虚拟机或者运行时库出现了内部错误; 如果是出现头两种情况,就必须告诉那些会调用该方法的程序员:如果调用该方法,可能会抛出异常。因为任何抛出异常的方法都可能是一个死亡陷阱,如果没有合适的异常处理器来捕获这些异常,则当前线程会被终止。

5.如果一个方法要抛出多于一个的已检查异常,则应该在该方法头中声明所有的异常,如: Class MyAnimation { public Image loadImage(String s) throws EOFException,MalformedURLException { … } }

6.不需要声明Java的内部错误,也就是那些从Error继承来的错误,任何代码都可以抛出这样的异常,但是我们对其没有控制权;也不应该声明从RuntimeException继承来的那些未检查异常,因为这样的异常通常是可以避免的。

7.除了声明异常以外,还可以捕获异常。通过捕获异常,就可以使方法不会将异常抛出,从而不需要使用throws子句。

8.如果在自己的子类中覆盖了一个来自父类的方法,则子类方法所能抛出的“已检查异常”不能超过其父类方法(只可以抛出更少的异常)。如果父类方法根本没有抛出任何“已检查异常”,则子类也只能如此,此时只能在子类方法的代码中捕捉每一个“已检查异常”。

9.假如方法内的任何代码抛出了一个异常,同时它的类型没有在catch从句中指定,则该方法会立即退出。通常应该捕捉并处理那些已知如何处理的异常,而传递那些不知如何处理的异常。

10.在三种可能的情况下,程序会执行finally子句: 1)代码不抛出异常:执行完try块内的所有代码,随后(哪怕try块中执行了return语句)会执行finally中的代码; 2)代码抛出的异常在catch中捕获:try块中发生异常剩下的语句将被忽略,随后执行相应catch块中的代码,然后执行finally从句的代码;如果catch块抛出了异常,则这个异常会返回到该方法的调用者; 3)代码抛出异常,但未在任何catch块中捕获:try块内剩余代码被跳过,随后执行finally从句的代码,再将异常“抛回”该方法的调用者;

11.因为在方法返回前,finally子句中的语句会被执行。因此从具有return语句的try块中退出,finally子句可能会导致非预期的控制流。如果finally块中也包含return语句,则该值可能会屏蔽try块中的原始返回值。

12.断言机制允许在测试阶段加入一种检查,而在发布阶段自动删去检查,它有两种形式: 1)assert 条件 2)assert 条件:表达式 这两种形式都会对条件进行评估,如果结果为假,则抛出AssertionError,在第二种形式中,表达式会传入AssertionError的构造器并转换成一个消息字符串。如断言x是否大于等于0,并将x的值传递给AssertionError对象,从而可以在以后显示: assert x >= 0:x;

13.断言失败是致命的,不可恢复的错误。断言检查仅仅用在程序开发和测试阶段。因此不应该用断言作为信号来通知程序的另外部分发生了可恢复错误,或者将断言作为通知程序使用者的方式。断言仅仅应该在测试阶段用来定位程序内部错误。

第12章 流与文件[17]

1.保存在文件中的信息,或者从一个网络连接中获取的信息,其处理方式在本质上是完全一样的。可以从中读出一系列字节的对象称为输入流,能向其中写入一系列字节的对象称为输出流。这两种对象分别用抽象类InputStream和OutputStream来实现。对于以Unicode格式来保存的信息来说,用以字节为基础的流来处理不是很方便,因此,专门处理Unicode的类都从抽象类Reader和Writer继承而来。

2.InputStream类提供了一个抽象方法:abstract int read(),该方法会读取一个字节并返回,假如遇到输入流的结尾,就返回-1.类似的,OutputStream类定义了抽象方法:abstract void write(int b)用来将一个字节写到指定的输出位置。无论read方法还是write方法都会阻塞线程的运行,直到字节被实际读出或写入为止。可以调用System.in.available()方法检查目前能够读取的字节数,再进行读取操作以避免线程挂起。InputStream类也提供了非抽象方法,这些方法会调用抽象的read方法,这样其子类只需要覆盖一个方法就可以了。

3.完成了对流的读取或写入操作后,要使用close方法将其关闭,该方法会释放流所占得资源,同时也会刷新输出流使用的缓冲区。如果没有关闭一个文件,很可能最后一个字节包永远你不会被投递出去,当然也可以用flush方法来主动刷新输出缓冲。

4.Java提供了总数超过60种的流类,均是从基本的InputStream和OutputStream类衍生出来的。利用这些类可以操作常见格式的数据,而不必操作那些低级别的字节流。

5.流过滤器的分层:对某些流来说,可以从文件或者其他地方读入字节,而对另一些流来说,它们则可将字节组装成更有用的数据类型。Java程序员通过将一个现成的流传递给另一个流的构造器来综合运用这两种流,将其合并成所谓“过滤流”。例如为了能从文件中读取数字,首先要创建一个FileInputStream,然后将其传递给一个DataInputStream的构造器: FileInputStream fin=new FileInputStream(“employee.dat”); DataInputStream din=new DataInputStream(fin); double s=din.readDouble();

6.有时候将多个中间流串连到一起时,需要对它们进行跟踪,例如在读取输入时提前检查下一个字节,看看是否是希望的值,为此可利用PushbackInputStream来实现: PushbackInputstream pbin=new PushbackInputstream(new BufferedInputStream(new FileInputStream(“employee.dat”))); int b=pbin.read(); if(b!’<’) pbin.unread(b); 如果想在“向前看”的同时也能读入数字,就同时需要一个pushback输入流以及一个数据输入引用流: PushbackInputstream pbin; DataInputStream din=new DataInputStream( pbin= new PushbackInputstream(new BufferedInputStream(new FileInputStream(“employee.dat”))));

7.数据流:数据流因为实现了DataOutput、DataInput接口,因此可以读取或写入全部基本Java类型。如DataOutput接口提供下列方法:writeChars、writeByte…

8.随机存取文件流:RandomAccessFile类可以在文件的任何地方查找或写入数据,它同时实现了DataOutput和DataInput接口。其seek方法用来设置文件指针位置,getFilePointer方法用来返回文件指针当前位置。如打开一个可同时进行读写操作的文件: RandomAccessFile inOut=new RandomAccessFile(“employee.dat”,”rw”);

9.文本输出:要想以二进制格式写入数据,使用DataOutputstream;要想以文本格式写入则使用PrintWriter;但是这两个类尽管提供了有用的输出方法,却没有定义目的地,因此PrintWriter必须同目标writer合并到一起,如: PrintWriter out=new PrintWriter(new FileWriter(“employee.txt”)); 也可以将其同目标流合并到一起,如: PrintWriter out=new PrintWriter(new FileOutputStream(“employee.txt”)); PrintWriter(OutputStream)构造器会自动增加一个OutputStreamWriter以便将Unicode字符转换成流内的字节。为了向PrintWriter进行写操作,应使用print方法或者println方法。

10.读取文本:在Java中唯一用来处理文本输入的是BufferedReader,它包含一个ReadLine方法,可用来读取整行文本,需要将一个BufferedReader同一个输入源合并起来: BufferedReader in=new BufferedReader(new FileReader(“employee.txt”)); 一个典型的输入循环如下: String line; while((line=in.readline())!=null){ … }

11.字符串分割器类StringTokenizer类是专门用来处理带分隔符的字符串的,在构造函数中指定欲处理的字符串及一个或多个分隔符;

12.如果想在对象流中保存和恢复任何一个类,则该类必须实现Serializable接口。Serializable接口没有方法。对象序列化机制使得可以如同存储文本或者数据字段一样简单地存储对象。

13.保存对象数据需要使用ObjectOutputStream,如: ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(“employee.dat”)); Employee harry=…; Manager boss=…; out.writeObject(harry); out.writeObject(boss);

14.读回对象需要使用ObjectInputStream对象,如: ObjectInputStream in=new ObjectInputStream(new FileInputStream(“employee.dat”)); Employee e1=(Employee)in.readObject(); Employee e2=(Employee)in.readObject(); 读回对象时必须对已保存对象的数量,它们的顺序以及它们的类型做到心中有数。

15.因为当一个对象被重新装载后,它可能占据一个和原来那个截然不同的内存地址,所以对于对象内部的对象引用,不能保存和恢复它们的内存地址。为此,Java采用“序列化”方式,具体算法为: 1)保存到磁盘的所有对象都获得一个序列号; 2)当要保存一个对象时,先检查该对象是否已经被保存; 3)如果以前保存过,只需写入“与已保存的具有序列号x的对象相同”标记;否则,保存它的所有数据; 当要读回对象时,将上述过程简单地逆转即可。对于载入的每个对象,都要注意它的序列号,并记住它在内存中的位置。如果遇到与已经保存的具有序列号x的对象相同标记,就根据序列号来获取该对象的位置,并设置对象引用,令其指向那个内存地址。 当使用对象流时,这些过程都会自动完成,对象流会分配序列号,并跟踪重复的对象。

16.有些数据字段是不应该被序列化的,这可以通过将其标志为transient来取消对该字段的序列化。

17.File类用来实现文件管理功能,它关心的是文件在磁盘上的存储,而流关心的是文件的内容。


公众号
文章目录