扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
那么,在下面,是我喜欢的Java5.0的五个新API特性。那些看完本文后的细心的读者会发现额外的奖励—第六个特性:很少有人知道的Java5.0支持的新语言语法,当然使用者就更少了。我非常喜欢它,因为它很新异。
Callable 和 Future 接口
我喜欢的第一个特性发掘自新的java.util.concurrent包。如它的名字暗示的,这是个并行编程工具包。在此有很多要探索的,而我要提的第一喜欢的特性是TimeUnit枚举类型。TimeUnit让我感兴趣的是它包含有用的时间相关工具--你通过一个枚举常量来调用它们,该常量代表度量时间的单位。例如:
TimeUnit.MILLISECONDS.sleep(200);
然而,TimeUnit并不是最值得夸奖的。java.util.concurrent最强大的特性之一是它的任务-执行/线程-池结构。ExecutorService接口提供了执行任务的能力。Executors类定义了工厂方法用于获取使用线程池的ExecutorService的实现。这是强大的要素。
我所喜欢的任务-执行框架的部分是它如何表现任务以及执行它的结果:Callable和Future接口。我们都熟悉用于定义线程的Runnable接口和它的run()方法。Callable像Runnable,但它的方法叫做call(),并且这个方法可以返回一个结果或者抛出一个异常,而这两点是Runnable.run()做不到的。
Callable是一个泛型,并且它的结果已经参数化。例如,一个计算BigInteger的任务,是Callable<BigInteger>
public interface Callable<V> {
V call() throws Exception;
}
当我想要异步执行一个Callable任务,我将它传递给ExecutorService的submit()方法。submit()的返回值—这也是我喜欢的部分—是一个Future对象:本质上是一个对将来某时刻的结果的“借条”。如果我准备使用我的任务的结果,我简单的调用Future对象的get()方法即可。如果任务的执行已完成,那么get()立刻返回结果。否则,它将阻塞直到结果可用。如果Callable抛出异常,那么get()方法将该异常包装为ExecutionException并且抛出它。Future还有方法用来对任务的执行进行取消和查询状态,但是你必须自己查找它们(这些方法)。Future也用了泛型,并且结果的类型也参数化了。因此如果我submit()一个Callable<BigInteger>来执行,我将获得一个Future< BigInteger >。
下面是一个简短的例子:
/**
* 这是一个用来计算大素数的Callable。
*/
public class PrimeSearch implements Callable<BigInteger>
{
static Random prng = new SecureRandom();
int n;
public PrimeSearch(int bitsize) { n = bitsize; }
public BigInteger call() {
return BigInteger.probablePrime(n, prng);
}
}
// 尝试同时计算两个素数
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<BigInteger> p = pool.submit(new PrimeSearch(512));
Future<BigInteger> q = pool.submit(new PrimeSearch(512));
// 将两个素数相乘来得到一个合数
BigInteger product = p.get().multiply(q.get());
可变参数和自动装箱
我说过我不想谈论Java5.0的新语言特性,我不会,但是我确实关注由于可变参数和自动装箱才变为可能的(或者被增强的旧API)新的API。
首先,当然,是Java5.0的printf风格的文本格式化能力,通过java.util.Formatter类和类似String.format()的工具方法。这类文本格式化是最常被引用来支持语言的增加的可变参数和自动装箱的那种用例。考虑这个:
String s = String.format("%s:%d: %s%n", filename,
lineNumber,
exception.getMessage());
关于这段代码没有什么特别值得注意的东西。我将它列在这是为了说明因为可变参数和自动装箱所以比下面的例子显得简单:
String s = String.format("%s:%d: %s%n", new Object[] {
filename,
new Integer(lineNumber),
exception.getMessage()});
可变参数和自动装箱还对java.lang.reflect API有一个实质性的影响。那就是当查找和调用方法时不再需要类和对象数组:
Method m = c.getMethod("put", Object.class,Object.class);
m.invoke(map, "key", "value");
如果我必须选择一个最喜欢的可变参数方法,那么,将是java.util.Arrays.asList()。这个方法真是个用于创建不变的对象列表的方便的工厂方法。它接受任何数量的类型T的参数并且将它们作为List
List<Integer> smallPrimes =
Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19);
能力
我们在上面谈论了Runnable和Callable,并且你毫无疑问已经听说过重要的Comparable, Serializable,和Cloneable接口。Java5.0新增加了五个重要的能力接口。第一个,当然,是java.lang.Iterable。你或许知道Java5.0新的for/in循环可以迭代数组和集合。你可能不知道它能够对任何实现了可迭代(Iterable)接口的对象工作。因此,如果你想让一个不是集合的数据结构可以简单地迭代,只需实现Iterable接口。你要做的就是增加一个返回java.util.Iterator 的iterator()方法。当然,写这个迭代器(Iterator)可能不是那么简单的。
下面的代码是一个实现了Iterable<String>(是的,Iterable是泛型的)的文本文件类,因而允许文本文件可以用for/in循环逐行的迭代。你可以用类似下面的代码使用它:
TextFile textfile = new TextFile(new File(f), "UTF-8");
int lineNumber = 0;
for(String line : textfile)
System.out.printf("%6d: %s%n", ++lineNumber, line);
下面是TextFile的代码。注意,迭代器不尝试检测对底层文件的并发的修改。如果你想自己做,看一看
java.nio.channels.FileLock。
import java.io.*;
import java.util.Iterator;
public class TextFile implements Iterable<String> {
File f;
String charsetName;
public TextFile(File f, String charsetName)
throws IOException
{
this.f = f;
this.charsetName = charsetName;
if (!f.exists())
throw new FileNotFoundException(f.getPath());
if (!f.canRead())
throw new IOException("Can't read: " +
f.getPath());
}
public Iterator<String> iterator() {
try {
return new TextFileIterator(f, charsetName);
}
catch(IOException e) {
throw new IllegalArgumentException(e);
}
}
static class TextFileIterator
implements Iterator<String>
{
BufferedReader in;
String nextline;
boolean closed = false;
public TextFileIterator(File f, String charsetName)
throws IOException
{
InputStream fis = new FileInputStream(f);
Reader isr =
new InputStreamReader(fis, charsetName);
in = new BufferedReader(isr);
getNextLine();
}
public boolean hasNext() {
return nextline != null;
}
public String next() {
String returnValue = nextline;
getNextLine();
return returnValue;
}
public void remove() {
throw new UnsupportedOperationException();
}
void getNextLine() {
if (!closed) {
try { nextline = in.readLine(); }
catch(IOException e) {
throw new IllegalArgumentException(e);
}
if (nextline == null) {
try { in.close(); }
catch(IOException ignored) {}
closed = true;
}
}
}
}
}
Iterable是到目前为止最重要的新能力接口,但是其它的也是非常的漂亮。接下来,我们碰到java.lang.Appendable。一个Appendable对象可以追加字符或字符序列(或者一个字符序列的子序列)。实现者包括StringBuffer和StringBuilder(如果你还没有听说过它,一定要看一看),Writer(及其子类),PrintStream,还有java.nio.CharBuffer。将可追加性从这些类中分离出来成为Appendable接口,使得新的java.util.Formatter类更强大:它能将文本格式化为任何可追加的对象,包括你自己的实现。(练习留给读者:你能否将上面的TextFile类变得既可迭代又可追加么?)。
java.lang.Readable接口和Appendable相反:一个可读对象可以将字符传输给给定的CharBuffer。java.io.Reader和它的全部子类都是可读的(当然了),CharBuffer本身也一样。就像Appendable是为了java.util.Formatter的利益而创造,Readable是为了java.util.Scanner的利益而创造。(Java5.0增加了Scanner,连同Formatter。这是Java对C的scanf()函数的适应,但是它(Scanner)不像Formatter之对应于printf()的关系那样密切。)
我想讨论的最后两个能力接口是java.io.Closeable和java.io.Flushable。如它们的名字暗示的,它们趋向于被任何类实现,通过一个close()或者flush()方法。Closeable被所有的输入和输出流类,RandomAccessFile和Formatter实现。Flushable被输出流类和Formatter实现。这些接口也是为了Formatter类的利益而定义。注意,Appendable对象(像StringBuilder)不总是可关闭或者可冲刷(flushable)。通过将可关闭性和可冲刷性分解出来成为这些接口,Formatter的close()和flush()方法能够决定它们操作的Appendable对象是否需要被关闭或被冲刷。(Java5.0还增加了第六个能力接口,并且它也是有关Formatter类的。那些想要控制它们的实例怎样被格式化的类可以实现java.util.Formattable接口。然而这个接口的API是难用的,我不想谈论它。)
@Override
毫无疑问,你已经听说过能用元数据标注Java5.0的类型和方法。但是你可能不熟悉增加到java.lang的标准标注类型。我喜欢的第四个特性就是java.lang.Override标注。当你写一个方法准备覆盖另一个的方法时,用@Override来标注它,这样编译器会进行检查来确保你确实,实际上,覆盖了你想覆盖的方法。
如果你拼写错了方法名字或者弄错了方法参数,那么你实际上并没有覆盖那个你认为你覆盖了的方法。这样就造成了一个如果不用@Override很难捕捉的臭虫。我所以知道是因为我的关于Java1.4的新API特性的文章就讲到了这个臭虫,并且这个错误至少有一年一直没被检测到(至少没有被报告)。在那篇文章中,你可以在第一页结尾看到我犯的错误。那篇文章现在包含一个链接到我的博客入口,在那里我改正了这个臭虫并且在代码中增加了@Override声明。
MatchResult
我喜欢的Java5.0的最后一个特性是java.util.regex.MatchResult。对于用于正则表达式的模式/匹配API我从来没有真正非常满意。Java5.0增加的MatchResult在让我大大地更加满意。当使用一个不太平凡的模式(Pattern),每次调用匹配者(Matcher)的find()方法会生成许多状态:开始位置,结束位置,匹配的文本,同时还有模式的开始,结束,每个子表达式的文本。在Java5.0以前,你只能从Matcher获取它们,通过在调用find()后再调用start(),end(),还有group(),如果需要的话。
然而,到了Java5.0,你可以只调用toMatchResult()来获取MatchResult对象再获取全部的状态,MatchResult对象可以保存并且可以以后再检查。MatchResult像Matcher一样有start(),end(),以及group()方法,并且,实际上,Matcher现在实现了MatchResult。
这里是一个有用的返回MatchResult的方法:
public static List<MatchResult> findAll(Pattern pattern,
CharSequence text) {
List<MatchResult> results =
new ArrayList<MatchResult>();
Matcher m = pattern.matcher(text);
while(m.find()) results.add(m.toMatchResult());
return results;
}
还有使用这个方法的代码:
List<MatchResult> results = findAll(pattern, text);
for(MatchResult r : results) {
System.out.printf("Found '%s' at (%d,%d)%n",
r.group(), r.start(), r.end());
}
十六进制浮点数字面值
我承诺谈论Java5.0的最晦涩的新语言特性。这就是:十六进制格式的浮点常量!这里是奇异的详情:一个十六进制符号的浮点常量以0X或者0x开头。随后的十六进制数字形成了数的基数。关键是这些数字可以包含一个小数点(一个十六进制小数点?)。在基数后面是指数,是必需的。十六进制浮点常量使用p或者P而不是e或者E来引入指数。(想一下“幂”来帮助记忆)。P或者P后面是指数,必须是一个十进制数,而不是十六进制数。而且这是个以二为根的指数,而不是以十为根。那就是,表示基数要乘以的2的幂。最后,整个常量可以跟随一个f或者F来表示一个浮点常量,或者一个d或者D表示一个双精度常量,就像一个十进制浮点数一样。
下面是一些例子:
double x = 0XaP0; // 10 * 2^0 = 10.0
double y = 0XfP2D; // 15 * 2^2 = 60.0
float z = 0Xf.aP1F; // (15 + 10/16ths) * 2^1 = 31.25f
// 用十进制来打印
System.out.printf("%f %f %f%n", x, y, z);
// 用十六进制来打印
System.out.printf("%a %a %a%n", x, y, z);
为什么Sun要对语言做这些?5.0的发行说明说:
为了允许特定浮点值实现精确及可预见的规范,十六进制符号可用于Float和Double的浮点字面值和字符串到浮点数的转换方法中。
这点是合理的。十进制小数像0.1是不能精确地用浮点格式表示的,并且如果你真的需要确切知道在一个浮点或者双精度值中比特位是怎么设的,那么你真的想要一个十六进制字面值。例如,Float.MAX_VALUE的Javadoc指出最大的浮点值是0x1.fffffeP+127f。
如果你知道并且喜欢IEEE-754浮点标准,那么十六进制浮点字段值或许是你喜欢的一个特性。我只是认为他们有趣。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者