JAVA5新特性

来源:互联网 发布:北京网络医生招聘 编辑:程序博客网 时间:2024/05/29 12:11
JAVA5新特性
Java 5.0发布了,许多人都将开始使用这个JDK版本的一些新增特性。从增强的for循环到诸如泛型(generic)之类更复杂的特性,都将很快出现在您所编写的代码中。我们刚刚完成了一个基于Java 5.0的大型任务,而本文就是要介绍我们使用这些新特性的体验。本文不是一篇入门性的文章,而是对这些特性以及它们所产生的影响的深入介绍,同时还给出了一些在项目中更有效地使用这些特性的技巧。
简介
  在JDK 1.5的beta阶段,我们为BEA的Java IDE开发了一个Java 5编译器。因为我们实现了许多新特性,所以人们开始以新的方式利用它们;有些用法很聪明,而有些用法明显应该被列入禁用清单。编译器本身使用了新的语言特性,所以我们也获得了使用这些特性维护代码的直接体验。本文将介绍其中的许多特性和使用它们的体验。
  我们假定您已经熟悉了这些新特性,所以不再全面介绍每个特性,而是谈论一些有趣的、但很可能不太明显的内容和用法。这些技巧出自我们的实际体验,并大致按照语言特性进行了分类。
  我们将从最简单的特性开始,逐步过渡到高级特性。泛型所包含的内容特别丰富,因此占了本文一半的篇幅。
增强的for循环
  为了迭代集合和数组,增强的for循环提供了一个简单、兼容的语法。有两点值得一提:
Init表达式
  在循环中,初始化表达式只计算一次。这意味着您通常可以移除一个变量声明。在这个例子中,我们必须创建一个整型数组来保存computeNumbers()的结果,以防止每一次循环都重新计算该方法。您可以看到,下面的代码要比上面的代码整洁一些,并且没有泄露变量numbers:
未增强的For:
int sum = 0;
Integer[] numbers = computeNumbers();
for (int i=0; i < numbers.length ; i++)
sum += numbers[i];
增强后的For:
int sum = 0;
for ( int number: computeNumbers() )
sum += number;
局限性
有时需要在迭代期间访问迭代器或下标,看起来增强的for循环应该允许该操作,但事实上不是这样,请看下面的例子:
for (int i=0; i < numbers.length ; i++) {
if (i != 0) System.out.print(",");
System.out.print(numbers[i]);
}
  我们希望将数组中的值打印为一个用逗号分隔的清单。我们需要知道目前是否是第一项,以便确定是否应该打印逗号。使用增强的for循环是无法获知这种信息的。我们需要自己保留一个下标或一个布尔值来指示是否经过了第一项。   这是另一个例子:
for (Iterator<integer> it = n.iterator() ; it.hasNext() ; )
if (it.next() < 0)
it.remove();
  在此例中,我们想从整数集合中删除负数项。为此,需要对迭代器调用一个方法,但是当使用增强的for 循环时,迭代器对我们来说是看不到的。因此,我们只能使用Java 5之前版本的迭代方法。   顺便说一下,这里需要注意的是,由于Iterator是泛型,所以其声明是Iterator<Integer>。许多人都忘记了这一点而使用了Iterator的原始格式。
注释
  注释处理是一个很大的话题。因为本文只关注核心的语言特性,所以我们不打算涵盖它所有的可能形式和陷阱。  我们将讨论内置的注释(SuppressWarnings,Deprecated和Override)以及一般注释处理的局限性。
Suppress Warnings
  该注释关闭了类或方法级别的编译器警告。有时候您比编译器更清楚地知道,代码必须使用一个被否决的方法或执行一些无法静态确定是否类型安全的动作,而使用:
@SuppressWarnings("deprecation")
public static void selfDestruct() {
Thread.currentThread().stop();
}
  这可能是内置注释最有用的地方。遗憾的是,1.5.0_04的javac不支持它。但是1.6支持它,并且Sun正在努力将其向后移植到1.5中。
Eclipse 3.1中支持该注释,其他IDE也可能支持它。这允许您把代码彻底地从警告中解脱出来。如果在编译时出现警告,可以确定是您刚刚把它添加进来——以帮助查看那些可能不安全的代码。随着泛型的添加,它使用起来将更趁手。
Deprecated
  遗憾的是,Deprecated没那么有用。它本来旨在替换@deprecated javadoc标签,但是由于它不包含任何字段,所以也就没有方法来建议deprecated类或方法的用户应该使用什么做为替代品。大多数用法都同时需要javadoc标签和这个注释。
Override
  Override表示,它所注释的方法应该重写超类中具有相同签名的方法:
@Override
public int hashCode() {
...
}
  看上面的例子,如果没有在hashCode中将“C”大写,在编译时不会出现错误,但是在运行时将无法像期望的那样调用该方法。通过添加Override标签,编译器会提示它是否真正地执行了重写。
  在超类发生改变的情况中,这也很有帮助。如果向该方法中添加一个新参数,而且方法本身也被重命名了,那么子类将突然不能编译,因为它不再重写超类的任何东西。
其它注释
  注释在其他场景中非常有用。当不是直接修改行为而是增强行为时,特别是在添加样板代码的情况下,注释在诸如EJB和Web services这样的框架中运行得非常好。
注释不能用做预处理器。Sun的设计特别预防了完全因为注释而修改类的字节码。这样可以正确地理解该语言的成果,而且IDE之类的工具也可以执行深入的代码分析和重构之类的功能。
注释不是银弹。第一次遇到的时候,人们试图尝试各种技巧。请看下面这个从别人那里获得的建议:
public class Foo {
 
@Property
private int bar;
 
}
  其思想是为私有字段bar自动创建getter和setter方法。遗憾的是,这个想法有两个失败之处:1)它不能运行,2)它使代码难以阅读和处理。   它是无法实现的,因为前面已经提到了,Sun特别阻止了对出现注释的类进行修改。
  即使是可能的,它也不是一个好主意,因为它使代码可读性差。第一次看到这段代码的人会不知道该注释创建了方法。此外,如果将来您需要在这些方法内部执行一些操作,注释也是没用的。   总之,不要试图用注释去做那些常规代码可以完成的事情。
枚举
  enum非常像public static final int声明,后者作为枚举值已经使用了很多年。对int所做的最大也是最明显的改进是类型安全——您不能错误地用枚举的一种类型代替另一种类型,这一点和int不同,所有的int对编译器来说都是一样的。除去极少数例外的情况,通常都应该用enum实例替换全部的枚举风格的int结构。
  枚举提供了一些附加的特性。EnumMap和EnumSet这两个实用类是专门为枚举优化的标准集合实现。如果知道集合只包含枚举类型,那么应该使用这些专门的集合来代替HashMap或HashSet。
  大部分情况下,可以使用enum对代码中的所有public static final int做插入替换。它们是可比的,并且可以静态导入,所以对它们的引用看起来是等同的,即使是对于内部类(或内部枚举类型)。注意,比较枚举类型的时候,声明它们的指令表明了它们的顺序值。

“隐藏的”静态方法
  两个静态方法出现在所有枚举类型声明中。因为它们是枚举子类上的静态方法,而不是Enum本身的方法,所以它们在java.lang.Enum的javadoc中没有出现。
  第一个是values(),返回一个枚举类型所有可能值的数组。
  第二个是valueOf(),为提供的字符串返回一个枚举类型,该枚举类型必须精确地匹配源代码声明。
方法
  关于枚举类型,我们最喜欢的一个方面是它可以有方法。过去您可能需要编写一些代码,对public static final int进行转换,把它从数据库类型转换为JDBC URL。而现在则可以让枚举类型本身带一个整理代码的方法。下面就是一个例子,包括DatabaseType枚举类型的抽象方法以及每个枚举实例中提供的实现:
  public enum  DatabaseType {
ORACLE {
public String getJdbcUrl() {...}
},
MYSQL {
public String getJdbcUrl() {...}
};
public abstract String getJdbcUrl();
}
  现在枚举类型可以直接提供它的实用方法。例如:

DatabaseType dbType = ...;
String jdbcURL = dbType.getJdbcUrl();

  要获取URL,必须预先知道该实用方法在哪里。

可变参数(Vararg)
  正确地使用可变参数确实可以清理一些垃圾代码。典型的例子是一个带有可变的String参数个数的log方法:
    Log.log(String code)
Log.log(String code,  String arg)
Log.log(String code,  String arg1, String arg2)
Log.log(String code,  String[] args)
  当讨论可变参数时,比较有趣的是,如果用新的可变参数替换前四个例子,将是兼容的:
Log.log(String code, String... args)
  所有的可变参数都是源兼容的——那就是说,如果重新编译log()方法的所有调用程序,可以直接替换全部的四个方法。然而,如果需要向后的二进制兼容性,那么就需要舍去前三个方法。只有最后那个带一个字符串数组参数的方法等效于可变参数版本,因此可以被可变参数版本替换。

类型强制转换
  如果希望调用程序了解应该使用哪种类型的参数,那么应该避免用可变参数进行类型强制转换。看下面这个例子,第一项希望是String,第二项希望是Exception:
    Log.log(Object...  objects) {
String message = (String)objects[0];
if (objects.length > 1) {
Exception e = (Exception)objects[1];
// Do something with the exception
}
}
  方法签名应该如下所示,相应的可变参数分别使用String和Exception声明:

Log.log(String message, Exception e, Object... objects) {...}

  不要使用可变参数破坏类型系统。需要强类型化时才可以使用它。对于这个规则,PrintStream.printf()是一个有趣的例外:它提供类型信息作为自己的第一个参数,以便稍后可以接受那些类型。

协变返回
  协变返回的基本用法是用于在已知一个实现的返回类型比API更具体的时候避免进行类型强制转换。在下面这个例子中,有一个返回Animal对象的Zoo接口。我们的实现返回一个AnimalImpl对象,但是在JDK 1.5之前,要返回一个Animal对象就必须声明。:
    public interface Zoo  {
public Animal getAnimal();
}
public class ZooImpl  implements Zoo {
public Animal getAnimal(){
return new AnimalImpl();
}
}
  协变返回的使用替换了三个反模式:
 
直接字段访问。为了规避API限制,一些实现把子类直接暴露为字段:
ZooImpl._animal
另一种形式是,在知道实现的实际上是特定的子类的情况下,在调用程序中执行向下转换:
((AnimalImpl)ZooImpl.getAnimal()).implMethod();
我看到的最后一种形式是一个具体的方法,该方法用来避免由一个完全不同的签名所引发的问题:
ZooImpl._getAnimal();

  这三种模式都有它们的问题和局限性。要么是不够整洁,要么就是暴露了不必要的实现细节。

协变
  协变返回模式就比较整洁、安全并且易于维护,它也不需要类型强制转换或特定的方法或字段:
public AnimalImpl getAnimal(){
return new AnimalImpl();
}
  使用结果:
ZooImpl.getAnimal().implMethod();

使用泛型
  我们将从两个角度来了解泛型:使用泛型和构造泛型。我们不讨论List、Set和Map的显而易见的用法。知道泛型集合是强大的并且应该经常使用就足够了。
  我们将讨论泛型方法的使用以及编译器推断类型的方法。通常这些都不会出问题,但是当出问题时,错误信息会非常令人费解,所以需要了解如何修复这些问题。
泛型方法
  除了泛型类型,Java 5还引入了泛型方法。在这个来自java.util.Collections的例子中,构造了一个单元素列表。新的List的元素类型是根据传入方法的对象的类型来推断的:
static <T> List<T> Collections.singletonList(T o)
示例用法:
public List<Integer> getListOfOne() {
return Collections.singletonList(1);
}
  在示例用法中,我们传入了一个int。所以方法的返回类型就是List<Integer>。编译器把T推断为Integer。这和泛型类型是不同的,因为您通常不需要显式地指定类型参数。
这也显示了自动装箱和泛型的相互作用。类型参数必须是引用类型:这就是为什么我们得到的是List<Integer>而不是List<int>。
不带参数的泛型方法
  emptyList()方法与泛型一起引入,作为java.util.Collections中EMPTY_LIST字段的类型安全置换:
static <T> List<T> Collections.emptyList()
示例用法:
public List<Integer> getNoIntegers() {
return Collections.emptyList();
}
  与先前的例子不同,这个方法没有参数,那么编译器如何推断T的类型呢?基本上,它将尝试使用一次参数。如果没有起作用,它再次尝试使用返回或赋值类型。在本例中,返回的是List<Integer>,所以T被推断为Integer。
  如果在返回语句或赋值语句之外的位置调用泛型方法会怎么样呢?那么编译器将无法执行类型推断的第二次传送。在下面这个例子中,emptyList()是从条件运算符内部调用的:
public List<Integer> getNoIntegers() {
return x ? Collections.emptyList() : null;
}
  因为编译器看不到返回上下文,也不能推断T,所以它放弃并采用Object。您将看到一个错误消息,比如:“无法将List<Object>转换为List<Integer>。”
为了修复这个错误,应显式地向方法调用传递类型参数。这样,编译器就不会试图推断类型参数,就可以获得正确的结果:
return x ? Collections.<Integer>emptyList() : null;
  这种情况经常发生的另一个地方是在方法调用中。如果一个方法带一个List<String>参数,并且需要为那个参数调用这个传递的emptyList(),那么也需要使用这个语法。
集合之外
  这里有三个泛型类型的例子,它们不是集合,而是以一种新颖的方式使用泛型。这三个例子都来自标准的Java库:
Class<T>
Class在类的类型上被参数化了。这就使无需类型强制转换而构造一个newInstance成为可能。
Comparable<T>
Comparable被实际的比较类型参数化。这就在compareTo()调用时提供了更强的类型化。例如,String实现Comparable<String>。对除String之外的任何东西调用compareTo(),都会在编译时失败。
Enum<E extends Enum<E>>
Enum被枚举类型参数化。一个名为Color的枚举类型将扩展Enum<Color>。getDeclaringClass()方法返回枚举类型的类对象,在这个例子中就是一个Color对象。它与getClass()不同,后者可能返回一个无名类。
通配符
  泛型最复杂的部分是对通配符的理解。我们将讨论三种类型的通配符以及它们的用途。
  首先让我们了解一下数组是如何工作的。可以从一个Integer[]为一个Number[]赋值。如果尝试把一个Float写到Number[]中,那么可以编译,但在运行时会失败,出现一个ArrayStoreException:
Integer[] ia = new Integer[5];
Number[] na = ia;
na[0] = 0.5; // compiles, but fails at runtime
如果试图把该例直接转换成泛型,那么会在编译时失败,因为赋值是不被允许的:
List<Integer> iList = new ArrayList<Integer>();
List<Number> nList = iList; // not allowed
nList.add(0.5);
  如果使用泛型,只要代码在编译时没有出现警告,就不会遇到运行时ClassCastException。
上限通配符
  我们想要的是一个确切元素类型未知的列表,这一点与数组是不同的。
List<Number>是一个列表,其元素类型是具体类型Number。
List<? extends Number>是一个确切元素类型未知的列表。它是Number或其子类型。
上限
  如果我们更新初始的例子,并赋值给List<? extends Number>,那么现在赋值就会成功了:
List<Integer> iList = new ArrayList<Integer>();
List<? extends Number> nList = iList;
Number n = nList.get(0);
nList.add(0.5); // Not allowed
  我们可以从列表中得到Number,因为无论列表的确切元素类型是什么(Float、Integer或Number),我们都可以把它赋值给Number。
  我们仍然不能把浮点类型插入列表中。这会在编译时失败,因为我们不能证明这是安全的。如果我们想要向列表中添加浮点类型,它将破坏iList的初始类型安全——它只存储Integer。
  通配符给了我们比数组更多的表达能力。
为什么使用通配符
  在下面这个例子中,通配符用于向API的用户隐藏类型信息。在内部,Set被存储为CustomerImpl。而API的用户只知道他们正在获取一个Set,从中可以读取Customer。
此处通配符是必需的,因为无法从Set<CustomerImpl>向Set<Customer>赋值:
public class CustomerFactory {
private Set<CustomerImpl> _customers;
public Set<? extends Customer> getCustomers() {
return _customers;
}
}
通配符和协变返回
  通配符的另一种常见用法是和协变返回一起使用。与赋值相同的规则可以应用到协变返回上。如果希望在重写的方法中返回一个更具体的泛型类型,声明的方法必须使用通配符:
public interface NumberGenerator {
public List<? extends Number> generate();
}
public class FibonacciGenerator extends NumberGenerator {
public List<Integer> generate() {
...
}
}
  如果要使用数组,接口可以返回Number[],而实现可以返回Integer[]。
下限
  我们所谈的主要是关于上限通配符的。还有一个下限通配符。List<? super Number>是一个确切“元素类型”未知的列表,但是可能是Mnumber,或者Number的超类型。所以它可能是一个List<Number>或一个List<Object>。
  下限通配符远没有上限通配符那样常见,但是当需要它们的时候,它们就是必需的。
下限与上限
List<? extends Number> readList = new ArrayList<Integer>();
Number n = readList.get(0);
List<? super Number> writeList = new ArrayList<Object>();
writeList.add(new Integer(5));
  第一个是可以从中读数的列表。
  第二个是可以向其写数的列表。
无界通配符
  最后,List<?>列表的内容可以是任何类型,而且它与List<? extends Object>几乎相同。可以随时读取Object,但是不能向列表中写入内容。
公共API中的通配符
  总之,正如前面所说,通配符在向调用程序隐藏实现细节方面是非常重要的,但即使下限通配符看起来是提供只读访问,由于remove(int position)之类的非泛型方法,它们也并非如此。如果您想要一个真正不变的集合,可以使用java.util.Collection上的方法,比如unmodifiableList()。
  编写API的时候要记得通配符。通常,在传递泛型类型时,应该尝试使用通配符。它使更多的调用程序可以访问API。
  通过接收List<? extends Number>而不是List<Number>,下面的方法可以由许多不同类型的列表调用:
void removeNegatives(List<? extends Number> list);
构造泛型类型
  现在我们将讨论构造自己的泛型类型。我们将展示一些例子,其中通过使用泛型可以提高类型安全性,我们还将讨论一些实现泛型类型时的常见问题。
集合风格(Collection-like)的函数
  第一个泛型类的例子是一个集合风格的例子。Pair有两个类型参数,而且字段是类型的实例:
public final class Pair<A,B> {
public final A first;
public final B second;
public Pair(A first, B second) {
this.first = first;
this.second = second;
}
}
  这使从方法返回两个项而无需为每个两种类型的组合编写专用的类成为可能。另一种方法是返回Object[],而这样是类型不安全或者不整洁的。
在下面的用法中,我们从方法返回一个File和一个Boolean。方法的客户端可以直接使用字段而无需类型强制转换:
public Pair<File,Boolean> getFileAndWriteStatus(String path){
// create file and status
return new Pair<File,Boolean>(file, status);
}
Pair<File,Boolean> result = getFileAndWriteStatus("...");
File f = result.first;
boolean writeable = result.second;
集合之外
  在下面这个例子中,泛型被用于附加的编译时安全性。通过把DBFactory类参数化为所创建的Peer类型,您实际上是在强制Factory子类返回一个Peer的特定子类型:
public abstract class DBFactory<T extends DBPeer> {
protected abstract T createEmptyPeer();
public List<T> get(String constraint) {
List<T> peers = new ArrayList<T>();
// database magic
return peers;
}
}
通过实现DBFactory<Customer>,CustomerFactory必须从createEmptyPeer()返回一个Customer:
public class CustomerFactory extends DBFactory<Customer>{
public Customer createEmptyPeer() {
return new Customer();
}
}
泛型方法
  不管想要对参数之间还是参数与返回类型之间的泛型类型施加约束,都可以使用泛型方法:
  例如,如果编写的反转函数是在位置上反转,那么可能不需要泛型方法。然而,如果希望反转返回一个新的List,那么可能会希望新List的元素类型与传入的List的类型相同。在这种情况下,就需要一个泛型方法:

<T> List<T> reverse(List<T> list)
具体化
  当实现一个泛型类时,您可能想要构造一个数组T[]。因为泛型是通过擦除(erasure)实现的,所以这是不允许的。
  您可以尝试把Object[]强制转换为T[]。但这是不安全的。
具体化解决方案
  按照泛型教程的惯例,解决方案使用的是“类型令牌”,通过向构造函数添加一个Class<T>参数,可以强制客户端为类的类型参数提供正确的类对象:
public class ArrayExample<T> {
private Class<T> clazz;
public ArrayExample(Class<T> clazz) {
this.clazz = clazz;
}
public T[] getArray(int size) {
return (T[])Array.newInstance(clazz, size);
}
}
  为了构造ArrayExample<String>,客户端必须把String.class传递给构造函数,因为String.class的类型是Class<String>。
拥有类对象使构造一个具有正确元素类型的数组成为可能。
结束语
  总而言之,新的语言特性有助于从根本上改变Java。通过了解在什么场景下使用以及如何使用这些新特性,您将会编写出更好的代码。




  RowSet 新特性简介
  Java 5在Java Database Connectivity (JDBC)方面加强了支持,其中加入了新的包javax.sql.rowset,javax.sql.rowset.serial,javax.sql.rowset.spi。从RowSet接口继承规定了五个新的接口:
  1. CachedRowSet: CachedRowset可以不用与数据源建立长期的连接,只有当从数据库读取数据或是往数据库写入数据的时候才会与数据库建立连接,它提供了一种轻量级的访问数据库的方式,其数据均存在内存中。
  2. JdbcRowSet:对ResultSet的对象进行包装,使得可以将ResultSet对象做为一个JavaBeans ™ 组件。
  3. FilteredRowSet:继承自CachedRowSet,可以根据设置条件得到数据的子集。
  4. JoinRowSet:继承自CachedRowSet,可以将多个RowSet对象进行SQL Join语句的合并。
  5. WebRowSet:继承自CachedRowSet,可以将WebRowSet对象输出成XML格式。
  下面分别演示如何使用这五个新接口。



  实验环境
  IBM DB2 Universal 8.1
  数据库名:DemoDB
  数据库用户名:db2admin
  数据库密码:password



  CachedRowSet
  CachedRowSet可以通过调用populate(ResuletSet rs)来生成数据,一旦获得数据,CachedRowSet就可以断开与数据库的连接,直到往数据库写入数据的时候才需建立连接。
  可以使用自己扩展的或是使用Reference Implement的实现类进行访问数据库。下面的代码演示了如何根据ResultSet建立一个CachedRowSet对象,在中断与数据库连接的情况下,读取数据,并做更新,最后再获取数据库连接,将更新落实到数据库中。


public static void testCachedRowSet(){
    Connection conn = null;
    try {
        // 获得数据库连接
        conn= DriverManager.getConnection(DB2URL, DB2USER, DB2PASSWORD);
        Statement stmt = conn.createStatement();
        // 查询数据库,获得表数据
        ResultSet rs = stmt.executeQuery("select * from student");//$NON-NLS-1$
        // 根据ResultSet对象生成CachedRowSet类型的对象
        CachedRowSetImpl crs = new CachedRowSetImpl();
        crs.populate(rs);
        // 关闭ResultSet
        rs.close();
        // 关闭数据库的连接
        conn.close();
        // 在中断与数据库连接的情况下,对CachedRowSet进行操作
        operateOnRowSet(crs);
        // 重新获取与数据库的连接
        conn= DriverManager.getConnection(DB2URL, DB2USER, DB2PASSWORD);
        // 将CachedRowSet的内容更新到数据库
        crs.acceptChanges(conn);
        // 关闭CachedRowSet
        crs.close();
        // 关闭数据库连接
        conn.close();
    } catch (InstantiationException e) {
        System.out.println("Andrew: InstantiationException!");//$NON-NLS-1$
    } catch (IllegalAccessException e) {
        System.out.println("Andrew: IllegalAccessException!");//$NON-NLS-1$
    } catch (ClassNotFoundException e) {
        System.out.println("Andrew: ClassNotFoundException!");//$NON-NLS-1$
    }catch (SQLException e) {
          System.out.println("Andrew: SQLException!");//$NON-NLS-1$
        e.printStackTrace();
    }    
}

  其中operateOnRowSet方法遍历读取RowSet中的元素,并将id值加1。RowSet允许注册监听器,可以在光标移动,RowSet发生改变时触发。其具体代码如下:


public static void operateOnRowSet(RowSet rs){
    // 为RowSet注册监听器
    MyRowsetListener myListener = new MyRowsetListener();
    rs.addRowSetListener(myListener);
    // 操作RowSet数据
    try{
        // 遍历读取数据
        while (rs.next()) {
            String id = rs.getString("ID");//$NON-NLS-1$
            String name = rs.getString("NAME");//$NON-NLS-1$
            System.out.println("ID="+id+",NAME="+name);//$NON-NLS-1$
            //在id最末位连接"1"
            rs.updateString(1, id+"1");
        }
    }catch (SQLException e) {
        System.out.println("Andrew: SQLException!");//$NON-NLS-1$
        e.printStackTrace();
    }
}
class MyRowsetListener implements RowSetListener{
    // 光标发生移动
    public void cursorMoved(RowSetEvent event) {
        System.out.println("cursor moved");
    }
    // row发生改变
    public void rowChanged(RowSetEvent event) {
        System.out.println("row changed");
    }
    // RowSet发生改变
    public void rowSetChanged(RowSetEvent event) {
        System.out.println("row set changed");
    }
}
public static void main(String[] args) {
    try {
        Class.forName(DB2DRIVER).newInstance();
    } catch (InstantiationException e) {
        System.out.println("Andrew: InstantiationException!");//$NON-NLS-1$
    } catch (IllegalAccessException e) {
        System.out.println("Andrew: IllegalAccessException!");//$NON-NLS-1$
    } catch (ClassNotFoundException e) {
        System.out.println("Andrew: ClassNotFoundException!");//$NON-NLS-1$
    }
    testCachedRowSet();
}

  上面的程序的运行结果如下:


cursor moved
ID=001,NAME=zhou
cursor moved
ID=002,NAME=zhang
cursor moved
cursor moved
cursor moved
cursor moved
cursor moved
cursor moved
row set changed

  并且数据库中的id更新为0011,0021。



·Java Reflection (JAVA反射)详解
·Java: JNI完全手册
·Java连接数据库谈
·Java虚拟机类装载:原理、实现与应用
·JAVA中配置环境变量设置方法大全
·125条常见的java面试笔试题大汇总
·深入Java字节码加密
·Java反编译的研究
·让界面更加绚丽 Java SE 6.0 GUI体验
·Java中数据库事务处理的实现

JdbcRowSet
  JdbcRowSet功能与ResultSet类似,与CachedRowSet不同,JdbcRowSet在操作时保持与数据库的连接。可以将与数据库连接的URL,用户名,密码以及执行的SQL语句通过setXXX形式绑定。另外,JdbcRowSet返回的结果默认是可以上下滚动和可更新的,当然这需要数据库厂商提供的JDBC Driver支持。下面的代码演示了如何通过set方法设定数据库连接参数,以及如何操作JdbcRowSet对象。


public static void testJdbcRowSet() {
    JdbcRowSetImpl jrs = new JdbcRowSetImpl();
    try {
        // 设置连接数据库的URL
        jrs.setUrl(DB2URL);
        // 设置连接数据库的用户名
        jrs.setUsername(DB2USER);
        // 设置连接数据库的密码
        jrs.setPassword(DB2PASSWORD);
        // 设置执行数据库的SQL语句
        jrs.setCommand("select * from student");
        // 执行操作
        jrs.execute();
        // 对获得的JdbcRowSet进行操作
        operateOnRowSet(jrs);
        // 关闭JdbcRowset
        jrs.close();
    } catch (SQLException e) {
        System.out.println("Andrew: SQLException!");//$NON-NLS-1$
        e.printStackTrace();
    }
}

public static void operateOnRowSet(RowSet rs) {
    // 为RowSet注册监听器
    MyRowsetListener myListener = new MyRowsetListener();
    rs.addRowSetListener(myListener);
    // 操作RowSet数据
    try {
        // 遍历读取数据
        while (rs.next()) {
            String id = rs.getString("ID");//$NON-NLS-1$
            String name = rs.getString("NAME");//$NON-NLS-1$
            System.out.println("ID=" + id + ",NAME=" + name);//$NON-NLS-1$
        }
    } catch (SQLException e) {
        System.out.println("Andrew: SQLException!");//$NON-NLS-1$
        e.printStackTrace();
    }
}
    

  其运行结果如下:


cursor moved
ID=0011,NAME=zhou
cursor moved
ID=0021,NAME=zhang
cursor moved



  FilteredRowSet
  FilteredRowSet接口中规定了可以设定过滤器,其过滤接口为Predicate接口,必须实现Predicate接口中的evaluate方法。具体的代码如下:


public static void testFilteredRowSet() {
    try {
        // 获得数据库连接
        Connection conn = DriverManager.getConnection(DB2URL, DB2USER,
            DB2PASSWORD);
        Statement stmt = conn.createStatement();
        // 查询数据库,获得表数据
        ResultSet rs = stmt.executeQuery("select * from student");//$NON-NLS-1$
        FilteredRowSet frs = new FilteredRowSetImpl();
        frs.populate(rs);
        // 设置过滤器
        MyDBFilter filter = new MyDBFilter(11, 100);
        frs.setFilter(filter);
        operateOnRowSet(frs);
        // 关闭FilteredRowSet
        frs.close();
        // 关闭与数据库的连接
        conn.close();
    } catch (SQLException e) {
        System.out.println("Andrew: SQLException!");//$NON-NLS-1$
        e.printStackTrace();
    }
}

public static void operateOnRowSet(RowSet rs) {
    // 为RowSet注册监听器
    System.out.println("operateOnRowSet!");//$NON-NLS-1$
    MyRowsetListener myListener = new MyRowsetListener();
    rs.addRowSetListener(myListener);
    // 操作RowSet数据
    try {
        // 遍历读取数据
        while (rs.next()) {
            String id = rs.getString("ID");//$NON-NLS-1$
            String name = rs.getString("NAME");//$NON-NLS-1$
            System.out.println("ID=" + id + ",NAME=" + name);//$NON-NLS-1$
        }
    } catch (SQLException e) {
        System.out.println("Andrew: SQLException!");//$NON-NLS-1$
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    try {
        Class.forName(DB2DRIVER).newInstance();
    } catch (InstantiationException e) {
        System.out.println("Andrew: InstantiationException!");//$NON-NLS-1$
    } catch (IllegalAccessException e) {
        System.out.println("Andrew: IllegalAccessException!");//$NON-NLS-1$
    } catch (ClassNotFoundException e) {
        System.out.println("Andrew: ClassNotFoundException!");//$NON-NLS-1$
    }
    testFilteredRowSet();
}

  其中MyDBFilter实现了Predicate接口,其实现代码如下:


class MyDBFilter implements Predicate {
    private int low;
    private int high;
    public MyDBFilter(int low, int high) {
        this.low = low;
        this.high = high;
    }
    public boolean evaluate(RowSet rs) {
        CachedRowSet crs=(CachedRowSet)rs;
        //如果id在low和high之间返回真
        try {
            String id = (String) crs.getString("id");
            int idValue = Integer.parseInt(id);
            if (low < idValue && idValue < high) {
                return true;
            }
        } catch (SQLException e) {
            
        }
        return false;
    }
    public boolean evaluate(Object arg0, int arg1) throws SQLException {
        return false;
    }

    public boolean evaluate(Object arg0, String arg1) throws SQLException {
        return false;
    }
}

  其运行结果如下:


cursor moved
ID=0021,NAME=zhang
cursor moved



  JoinRowSet
  JoinRowSet可以将多个RowSet对象进行join合并,Join的列可以通过每个RowSet通过调用setMatchColumn方法来设置。setMatchColumn方式是Joinable接口定义的方法,五种类型的RowSet规定都需要实现该接口。下面的代码演示将student表和intern表中id相同的数据进行join操作。JoinRowSet不需要保持与数据库的连接。




使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。和下面要提到的Set

不同,List允许有相同的元素。除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。LinkedList类LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remov,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:Listl ist=Collections.synchronizedList(new LinkedList(...));ArrayList类ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用
ensureCapacity方法来增加ArrayList的容量以提高插入效率。和LinkedList一样,ArrayList也是非同步的(unsynchronized)。
Vector类Vector非常类似,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。Stack类Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。    Set接口Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。请注意:必须小心操作可变对象(MutableObject)
。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题Map接口请注意,Map没有继承Collection

接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。Hashtable类Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。添加数据用put(key,value),取出数据使用get(key),这两个基本操作的时间开销为常数。Hashtable通过initialcapacity和loadfactor两个参数调整性能。通常缺省的loadfactor0.75较好地实现了时间和空间的均衡。增大loadfactor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。使用Hashtable的简单示例如下将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”:
Hashtable   numbers =new  Hashtable();

numbers.put(“one”,new Integer(1));

numbers.put(“two”,new Integer(2));

numbers.put(“three”,new Integer(3));
要取出一个数,比如2,用相应的key:I
nteger   n =(Integer)numbers.get(“two”);

System.out.println(“two=”+n);
由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。Hashtable是同步的。HashMap类HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即nullvalue和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者loadfacto过低。WeakHashMap类WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。总结如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。尽量返回接口而非实际的类型,如返回List而非ArrayList,如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

java的类反射机制

一.什么是反射:反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。其中LEAD/LEAD++  OpenC++、MetaXa和OpenJava等就是基于反射机制的语言。最近,反射机制也被应用到了视窗系统、操作系统和文件系统中。反射本身并不是一个新概念,尽管计算机科学赋予了反射概念新的含义。在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
二、什么是Java中的类反射:Reflection是Java程序开发语言的特征之一,它允许运行中的Java程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。Java的这一能力在实际应用中用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性。例如,Pascal、C或者C++中就没有办法在程序中获得函数定义相关的信息。Reflection是Java被视为动态(或准动态)语言的关键,允许程序于执行期ReflectionAPIs取得任何已知名称之class的內部信息,包括package、typeparameters、superclass、implementedinterfaces、innerclasses,outer class,  fields、constructors、methods、modifiers,並可于执行期生成instances、变更fields內容或唤起methods。

三、Java类反射中所必须的类:Java的类反射所需要的类并不多,它们分别是:Field、Constructor、Method、Class、Object,下面我将对这些类做一个简单的说明。Field类:提供有关类或接口的属性的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)属性或实例属性,简单的理解可以把它看成一个封装反射类的属性的类。


0 0
原创粉丝点击