科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道体验JAVA 5的新增语言特性2

体验JAVA 5的新增语言特性2

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

 Java 5.0发布了,许多人都将开始使用这个JDK版本的一些新增特性。从增强的for循环到诸如泛型(generic)之类更复杂的特性,都将很快出现在您所编写的代码中。我们刚刚完成了一个基于Java 5.0的大型任务,而本文就是要介绍我们使用这些新特性的体验。

作者:Jess Garms, Tim Hanson 来源:IT专家网 2008年6月1日

关键字: 特性 语言 体验 java

  • 评论
  • 分享微博
  • 分享邮件
可变参数(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。编译器把T推断为Integer。这和泛型类型是不同的,因为您通常不需要显式地指定类型参数。

  这也显示了自动装箱和泛型的相互作用。类型参数必须是引用类型:这就是为什么我们得到的是List而不是List

  不带参数的泛型方法

  emptyList()方法与泛型一起引入,作为java.util.Collections中EMPTY_LIST字段的类型安全置换:

static <T> List<T> Collections.emptyList()
示例用法:
public List<Integer> getNoIntegers() {
    return Collections.emptyList();
}

  与先前的例子不同,这个方法没有参数,那么编译器如何推断T的类型呢?基本上,它将尝试使用一次参数。如果没有起作用,它再次尝试使用返回或赋值类型。在本例中,返回的是List,所以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。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章