65.9K
CodeProject 正在变化。 阅读更多。
Home

Java 5 新特性

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.64/5 (7投票s)

2008年7月13日

CPOL

6分钟阅读

viewsIcon

42805

本文讨论了 Java 5 中的新特性,并探讨了如何从中受益。在第一部分中,我们将介绍自动装箱和 foreach。

摘要

Java 5 有一些不错的新特性。本文将讨论这些特性,并探讨如何从中受益。在第一部分中,我们将介绍自动装箱和 foreach

Java 5 新特性

Java 语言在 1.5 版本(或称 Java 5)中添加了许多有趣的特性。语言级别的特性包括自动装箱、foreach、枚举、可变参数、注解、静态导入和泛型。

这些特性中的大多数都可以被认为是进步。它们通过减少冗长的语法和使代码更直观来提高开发人员的生产力。

下图显示了我对这些特性实用性的看法

左侧的特性(foreach、可变参数、自动装箱、枚举)非常好。注解非常有用,但我们还需要弄清楚何时何地正确使用它。静态导入只提供了微不足道的价值收益(也可能导致糟糕的代码),而另一个特性则相当糟糕。

自动装箱

Java 有两类“公民”:对象和原始类型。你无法在代码中很好地混合使用它们。如果你想编写一个可以接受任何参数类型的通用 API 怎么办?这在 Reflection API 中实现。让我们在这里编写一个对 Reflection 的 invoke() 方法的拙劣模仿

package com.agiledeveloper;

class A {}
class B {}

public class Test
{
    public static void foo1(A obj)
    {
        System.out.println("foo called with " + obj);
    }

    public static void foo2(A obj1, B obj2)
    {
        System.out.println("foo2 called with " + obj1 + " and " + obj2);
    }

    // Poor imitation of refelction API to illustrate the point
    public static void invoke(String method, Object[] args)
    {
        if (method.equals("foo1"))
        {
            foo1((A) args[0]);
        }
        if (method.equals("foo2"))
        {
            foo2((A) args[0], (B) args[1]);
        }
    }

    public static void main(String[] args)
    {
        invoke("foo1", new Object[]{new A()});
        invoke("foo2", new Object[]{new A(), new B()});
    }
}

main() 中,我先用 foo1 调用 invoke(),然后用 foo2 调用。为了发送任意数量的参数,invoke() 接受一个对象数组。将 A 的对象或 B 的对象发送到此方法没有问题。但是,让我们添加另一个方法

    public static void foo3(int value)
    {
        System.out.println("foo3 called witih " + value);
    }

我们如何使用 invoke() 方法调用此方法?invoke() 期望一个对象数组,而 foo3() 期望一个 int。在 Java 的早期版本中,你必须将 int 包装成一个对象并将其发送到 invoke() 方法。包装 int 的标准对象是 Integer。同样,我们有用于 doubleDouble、用于 charCharacter 等等。虽然 Integer.class 代表 Integer 类,但 Integer.TYPE 代表此类的包装器所服务的 int 原始类型。

这种手动装箱和手动拆箱的方法有相当多的缺点

  • 这导致代码混乱
  • 需要开发人员做更多的工作
  • 看起来不自然
  • 这让新手程序员感到困惑

让我们修改 invoke() 方法,看看我们将如何调用它

    public static void invoke(String method, Object[] args)
    {
        if (method.equals("foo1"))
        {
            foo1((A) args[0]);
        }
        if (method.equals("foo2"))
        {
            foo2((A) args[0], (B) args[1]);
        }
        if (method.equals("foo3"))
        {
            foo3(((Integer) (args[0])).intValue());
        }
    }

并且,对它的调用将如下所示

        invoke("foo3", new Object[]{new Integer(3)}); // Until Java 1.4

请注意,您如何将值 3 放入(或装箱)到 Integer 对象中。同样,在 invoke() 中调用 foo3() 时,您必须解箱该值。

在 Java 5 中,自动装箱和自动拆箱功能旨在减轻这种混乱。虽然这仍然在底层发生,但在源代码级别,我们不必这样做。这是修改后的代码

invoke("foo3", new Object[] {3});

上面的代码在编译时会被转换,以便值 3 会自动装箱成一个 Integer 对象。

让我们考虑另一个例子——我们将添加一个方法 foo4(),如下所示

    public static Integer foo4(Integer v)
    {
        System.out.println("foo4 called with " + v.intValue());
        return new Integer(5);
    }

现在,这是使用 Java 5 自动装箱/拆箱功能调用它的方法

        int someValue = foo4(4);
        System.out.println("Result of call to foo4 " + someValue);

如您所见,这段代码看起来更自然,更简洁。

优点和缺点

  • 请注意,装箱和拆箱仍然在底层发生。看看生成的字节码
  •    92:  iconst_4
       93:  invokestatic    #31; 
    //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       96:  invokestatic    #33; 
    //Method foo4:(Ljava/lang/Integer;)Ljava/lang/Integer;
       99:  invokevirtual   #23; 
    //Method java/lang/Integer.intValue:()I
       102: istore_1

    这段源代码呈现出一种具有欺骗性的外观。它会产生性能影响,特别是当你调用计算密集型方法时。

  • 如果 Integer 对象为 null,将其赋值给 int 将导致抛出运行时异常 – NullPointerException
  • 了解 == 的含义。对于对象,您比较的是身份;对于原始类型,您比较的是值。在自动拆箱的情况下,会发生基于值的比较。
  • 仅在性能不关键的情况下使用。

foreach

循环是一种控制结构,自我们开始编程以来就一直存在。你最常使用的旧式循环语法是

for(int i = 0; i < arr.length; i++)
{
    ... = arr[i] ...
}

虽然这种结构很简单,但即使我们不需要索引 i,我们也被迫使用它。

如果您想遍历一个集合,例如 ArrayList 怎么办?我们会做类似的事情

for(Iterator iter = lst.iterator(); iter.hasNext(); )
{
    System.out.println(iter.next());
}

不是很优雅,不是吗?在 for 语句中,最后一个 ; 之后什么都没有。你必须在循环体内调用 next() 方法才能前进到下一个元素,同时获取要处理的元素。

Java 5 中引入的 foreach 进一步简化了循环。让我们看看如何使用旧方法和新方法进行循环的示例

package com.agiledeveloper;

import java.util.ArrayList;
import java.util.Iterator;

public class Test
{
    public static void main(String[] args)
    {
        String[] messages = {"Hello", "Greetings", "Thanks"};

        for (int i = 0; i < messages.length; i++)
        {
            System.out.println(messages[i]);
        }

        for (String msg : messages)
        {
            System.out.println(msg);
        }

        ArrayList lst = new ArrayList();
        lst.add(1);
        lst.add(4.1);
        lst.add("test");

        for (Iterator iter = lst.iterator(); iter.hasNext();)
        {
            System.out.println(iter.next());
        }

        for (Object o : lst)
        {
            System.out.println(o);
        }

        ArrayList<Integer> values = new ArrayList<Integer>();
        values.add(1);
        values.add(2);

        int total = 0;

        for (int val : values)
        {
            total += val;
        }

        System.out.println("Total : " + total);
    }
}

foreach 被巧妙地引入,以避免引入任何新关键字。你会这样读

        for (String msg : messages)

意为“对于 消息中的每个字符串 msg”。设计者没有发明另一个关键字“foreach”,而是决定使用旧的“for”。此外,“in”可能在现有代码中用于字段、变量或方法。为了避免踩到你的痛处,他们决定改用 :。在 foreach 的循环中,msg 代表数组 String[] 中的一个 String 元素。

如你所见,循环要简单得多,更优雅(暂时忽略丑陋的 :),也更容易编写。但是,这里到底发生了什么?我们可以再次求助于字节码来理解。

代码

        for (String msg : messages)
        {
            System.out.println(msg);
        }

翻译为

   51:  aload_1
   52:  astore_2
   53:  aload_2
   54:  arraylength
   55:  istore_3
   56:  iconst_0
   57:  istore  4
   59:  iload   4
   61:  iload_3
   62:  if_icmpge       85
   65:  aload_2
   66:  iload   4
   68:  aaload
   69:  astore  5
   71:  getstatic       #6; 
//Field java/lang/System.out:Ljava/io/PrintStream;
   74:  aload   5
   76:  invokevirtual   #7; 
//Method java/io/PrintStream.println:(Ljava/lang/String;)V
   79:  iinc    4, 1
   82:  goto    59

对于数组,foreach 只是简单地转换为我们熟悉的 for 循环。让我们看看 foreach 会发生什么

        for (Object o : lst)
        {
            System.out.println(o);
        }

其中 lst 是一个 ArrayList。 इसका字节码是

   157: aload_2
   158: invokevirtual   #22; 
//Method java/util/ArrayList.iterator:()Ljava/util/Iterator;
   161: astore_3
   162: aload_3
   163: invokeinterface #18,  1; 
//InterfaceMethod java/util/Iterator.hasNext:()Z
   168: ifeq    190
   171: aload_3
   172: invokeinterface #19,  1; 
//InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
   177: astore  4
   179: getstatic       #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   182: aload   4
   184: invokevirtual   #20; 
//Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   187: goto    162

如您所见,在这种情况下,foreach 转换为遍历 Iteratorfor 循环。

所以,foreach 仅仅是一个语法糖,在编译时会转换为我们传统的循环。

在上面的例子中,我将 foreach 用于 String[]ArrayList。如果你想将 foreach 用于你自己的类怎么办?你可以这样做,前提是你的类实现了 Iterable 接口。这非常简单。为了让 foreach 工作,你必须返回一个 Iterator。当然,你知道 Collection 提供了迭代器。但是,如果你想在自己的类上使用 foreach,期望你实现更庞大的 Collection 接口是不公平的。Iterable 接口只有一个方法

/** Implementing this interface allows an object to be the target of
 *  the "foreach" statement.
 */
public interface Iterable<T> {

    /**
     * Returns an iterator over a set of elements of type T.
     * 
     * @return an Iterator.
     */
    Iterator<T> iterator();
}

我们来试一试

//Wheel.java
package com.agiledeveloper;

public class Wheel
{
}

//Car.java
package com.agiledeveloper;

import java.util.Iterator;
import java.util.ArrayList;

public class Car implements Iterable<Wheel>
{
    ArrayList<Wheel> wheels = new ArrayList<Wheel>();

    public Car()
    {
        for(int i = 0; i < 4; i++)
        {
            wheels.add(new Wheel());
        }
    }

    public Iterator<Wheel> iterator()
    {
        return wheels.iterator();
    }
}

现在,我可以在 Car 对象上使用 foreach,如下所示

        Car aCar = new Car();
        for(Wheel aWheel : aCar)
        {
            // aWheel ...
        }

优点和缺点

  • foreach 看起来更简单、更优雅,也更容易使用。
  • 但是,您不能一直使用它。如果您需要访问集合中的索引(用于设置或更改值)或者您想要访问迭代器(例如,用于删除元素),那么您就不能使用 foreach
  • 如果您的类没有实现 Iterable 接口,您的类的用户将无法在您的对象上使用 foreach

结论

Java 5 具有一些不错的新特性。在第一部分中,我们讨论了其中两个:自动装箱和 foreach。这些特性消除了代码中的混乱,使代码更容易理解。然而,它们仅仅是语法糖,并不能提高代码的性能。我们将在本文的后续部分讨论其余的特性。

参考文献

  1. Java 开发者网络
  2. 请查看“演示文稿”部分,获取“Java 5 特性”示例和幻灯片.
© . All rights reserved.