为什么需要集合字面量?

28

我通过查阅多种Java 7在线文章了解到,Java 7将会拥有以下集合字面量1

List<String> fruits = [ "Apple", "Mango", "Guava" ];
Set<String> flowers = { "Rose", "Daisy", "Chrysanthemum" };
Map<Integer, String> hindiNums = { 1 : "Ek", 2 : "Do", 3 : "Teen" }; 

我的问题是:

  1. 在所有集合类中提供一个静态方法of,可以按以下方式使用,这样不是也可以吗:

List<String> fruits = ArrayList.of("Apple", "Mango", "Guava");

我认为这看起来和字面量版本一样好,而且也相当简洁。那么他们为什么要发明一种新的语法(编辑:我指的是“对于Java而言是新的”)?

  1. 当我说List<String> fruits = [ "Apple", "Mango", "Guava" ];时,我会得到哪个List?是ArrayList还是LinkedList或其他什么东西?

1 正如评论中所提到的,Java 7和Java 8都没有包含集合字面量。 (这里有一封来自Oracle开发人员Brian Goetz的email,总结了不包括此功能的原因; 这里是proposal本身。)


2
关于您的第一个问题:在保持类型安全的同时,您将如何以类似的方式创建地图? - Michael Williamson
3
@Paul:不需要指定类型,你可以调用泛型方法。Java会从参数的类型推断出类型。 - sepp2k
4
如果 Java 中加入了元组(在我看来这是一个更明智的添加),我们就可以使用这种语法来初始化 MapMap<String, String> capitals = HashMap.of(["India", "Delhi"], ["USA", "Washington DC"]); - Green Hyena
5
对于这种不寻常的情况,您可以使用类型注释,例如ArrayList.of<Object>("Apple", "Mango", "Guava");。否则,类型注释就不是必要的。 - Green Hyena
3
实际上,我认为这个特性没有被加入Java 7中。 “之前选择的集合支持和无符号字面量特性已经被推迟,并将在JDK 8中重新考虑。” http://openjdk.java.net/projects/coin/ - wolfcastle
显示剩余5条评论
11个回答

11

问题2的答案:

final List<Integer> piDigits = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 9];

提供给你一个不可变的列表。

这个提案的整个想法是子类型不能被指定。编译器根据右侧的集合选择合适的实现方式。

在这里阅读详细信息:提案:集合字面量

回答问题1:是的,它会有。这只是一种风格问题。


Q3:它总是必须是“final”吗? - Green Hyena
关于“编译器根据右侧集合选择适当的实现。”---编译器何时变得如此智能?(提供一些引用资料会更好。) - Green Hyena
@Green Hyena:这就是为什么我包含了链接,但我看到我链接到了错误的 URL。现在已经更正。感谢提示。 - Eric Eijkelenboom
@绿色的鬣狗:不,它并不一定是final。Immutable与final没有任何关系,因此可以省略该部分。再次,请参阅提案。 - Eric Eijkelenboom

7

需要注意的是,对于列表而言,使用变量参数(var args)非常容易,但是针对映射(map),需要再考虑一下该如何实现。除非引入额外的Map.Entry对象或类似方法,否则没有办法向变量参数方法提供一对参数。

因此,使用现有语法,您最终会得到:

Map<String,String> map = HashMap.of( new Entry( "key1", "value1" ), 
                                     new Entry( "key2", "value2" ) );

这将会非常快地让人感到疲劳。

即使您选择使用建造者模式(正如我们所做的那样),它看起来也相当丑陋。

Map<String,String> map = CollectionUtil.<String,String>map()
                                        .put( "key1", "value1" )
                                        .put( "key2", "value2" )
                                        .map();

1
如果他们在Java中添加了元组(在我看来这会是一个更明智的决定),我们就可以使用这种语法来初始化Map:Map<String, String> capitals = HashMap.of(["India", "Delhi"], ["USA", "Washington DC"]); - Green Hyena

4

以上未提及的是集合嵌套的简单性。比如说,您想要一个常量来存储不同国家的货币名称和价值。

// Currency devisions by country, and their various names
Map<String,Map<Float,List<String>>> CURRENCIES =
    { "US" : { 1 : ["dollar","buck"],
               0.25 : ["quarter"],
               0.10 : ["dime"],
               0.05 : ["nickel"],
               0.01 : ["penny"] },
      "UK" : { 1 : ["pound"],
               1.05 ["guinea"],
               0.125 : ["half crown"],
               0.05 : ["shilling","bob"],
               0.025 : ["sixpence"] } 
    };

我已经在十二个极易阅读的行中打包了大量数据。如果我使用 ArrayList.of(十次)和一些类似的地图构造函数(四次,每次有十个Map.EntryPair!),函数调用将使代码膨胀并且更难阅读。

虽然这绝对是语法糖的领域,但这是我期待Java 7中的#1功能(如果它真正被发布)。


3

A1 是的,这是可能的,但需要您不断地输入更多内容。

虽然这并不是什么本质上错误的事情,但它是开发人员抱怨最多的事情之一。他们(有时候包括我自己)感觉 Java 太啰嗦了

随着硬件的加速,新的编程语言开始变得可行,尽管最初执行计算的成本较高,但现在它们正在被越来越广泛地使用并变得非常流行。当开发人员再次开始输入 Java 代码时,他们会感到有点像“哦,不!”,“public static void main”又要输入了!

Java(以及静态类型语言)为提高执行速度所做的一项工作是在运行之前(即在编译阶段)尽可能多地进行验证。

这些新的编程语言(特别是 Python 和 Ruby)使编程成为一种乐趣,因为您只需输入较少的内容即可实现相同的功能。

Java 正在采取的反应是让语言变得更大,并将其中一些“语法糖”纳入到语言中。

即使像C#这样的编程语言也有这些扩展功能(例如属性),以使开发人员编写的代码更少。
最后,我认为这些添加对语言有益,但必须非常小心地添加,以避免破坏兼容性和/或创建一个太大的语言,没有人可以使用它。
我认为另一种选择是允许方法在名称上更具表现力,但对于Java来说这很复杂。也许在一种新语言中 :).
Map put方法签名可以是这样的:
 /**
  * Analog to put( Object k, Object v );
  */
 public [( Object = k )]=( Object  v ) {
 } 

这允许方法名称为:[(key)]=(value),最终可以省略括号(如Ruby或原始的Smalltalk),因此该方法将是:

 Map map = ....
 map.["name"]="Oscar";

由于[] =不是有效的方法标识符,所以这是不可能的。修改为:

 map.put("name","Oscar");

他们认为...太啰嗦了,因此决定添加这个解决方案。

另一个改进是数字字面量,因此您将能够输入:

 int million = 1_000_000;

A2:你将得到其他东西。

A3(扩展我对kloffy答案的评论)。

今天你可以用Java编写相同的代码。

假设Stage,Scente,Group,Text,ImageView,Image是现有的Java类,您可以键入:

 new Stage() {{
     title  =  "Group-Nodes-Transformation";
     scene  =  new Scene() {{
         width =  600;
         height = 600;
         content = new Group() {{
             content = new Object[] =  {
                 new Circle() {{
                     centerX = 300;
                     centerY = 300;
                     radius = 250;
                     fill = Color.WHITE;
                     stroke = Color.BLACK;
                 }},
                 new Text() {{
                     x = 300;
                     y = 300;
                     content = "Mr. Duke";
                 }},
                 new ImageView() {{
                     image = new  Image() {{
                         url = "D:\\TEST\\Documents\\duke.png"
                         width = 50;
                         height = 50;
                     }};
                 }};
             };
         }};
     }};
 }};

这个(map.["name"]="Oscar";)让我想起了C++的运算符重载。 - David

2

他们可能受到了JavaFX的声明式编程风格的启发。如果是这样的话,我很失望他们没有完全支持对象字面量。以下是JavaFX中使用对象字面量的示例:

Stage {
    title: "Group-Nodes-Transformation"
    scene: Scene {
        width: 600
        height: 600
        content: Group {
            content: [
                Circle {
                    centerX: 300
                    centerY: 300
                    radius: 250
                    fill: Color.WHITE
                    stroke: Color.BLACK
                },
                Text {
                    x: 300
                    y: 300
                    content: "Mr. Duke"
                },
                ImageView {
                    image: Image {
                        url: "D:\\TEST\\Documents\\duke.png"
                        width:50
                        height:50
                    }
                }
            ]
        }
    }
}

我认为这种编程风格适用于特定的应用领域。最终,这总是一个品味的问题...


1
我喜欢这个例子。比Java中的Swing代码更干净、更易读。 - Green Hyena
2
这在Java中也是可能的。我不太了解JavaFX,但如果每个元素都是类,你可以像这样输入:http://pastebin.com/jAqwXt7X - OscarRyz
@Oscar:带有实例初始化块的匿名类。好技巧!+1 :) - Green Hyena

1

在我看来,这只是一种简化代码的语法糖。为什么你不对同类语法糖感到惊讶呢?

int []arr1 = new int[] {1,2,3};
int []arr2 = {1,2,3};

这只是一种方便的表达简单想法的方式,而不是写类似于

int []arr = new int[3];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;

这个东西真的给我们的生活带来了新意吗?嗯,不是的。但它确实让我们的生活变得更轻松了一些,我认为是的。

关于第二个问题的类型,我不知道实际的类型会是什么。但坦率地说,你有多在意集合的实现方式呢?特别是考虑到这些集合通常是相对较小的(所有值都由开发人员输入)。无论如何,在你真正需要关心的罕见情况下,只需不使用这种简化语法并自己完成工作即可。


嗯...我的问题是,为什么不使用“of”而要使用这些有趣(在我看来)的语法呢? - Green Hyena
当你需要为测试创建固定装置时,这种语法非常有帮助。 - OscarRyz
为什么不使用“of”?同样的原因:节省一些打字。有一个主要的想法需要表达(创建集合),这似乎是最短的方式。集合的实际实现在这里根本不重要。集合及其数据是主要的事情。如果您想将“内联”集合作为参数传递给某个方法,则节省几个字符尤为重要。 - SergGr

1

语言设计是一种品味。话虽如此,这种列表/对象初始化已经变得非常流行。例如,C#几乎有同样的东西。语法相当灵活,正如你的例子所示,它可以让你在一行内初始化像字典这样的东西。我相当喜欢设计师在这里选择的语法。


2
你觉得这种语法有什么“灵活”的地方? - Green Hyena

0

只是为了澄清一点 - 他们并没有“发明新的语法”。

我不完全确定最初的想法来自哪里,但在Perl中使用“[]”表示列表,使用“{}”表示映射是存在的(在构建数组引用和哈希引用时使用这些符号)。

严格来说,Perl中没有“set”,但最典型的实现方式是使用映射(将成员映射到1),所以“{}”也适用。

因此,Perl会像你列出的那样说:

$fruits = [ "Apple", "Mango", "Guava" ]; # Creates array reference
$flowers = { "Rose" => 1, "Daisy" => 1, "Chrysanthemum" => 1 };
     # or { map {$_=>1} ("Rose", "Daisy", "Chrysanthemum"))
$hindiNums = { 1 => "Ek", 2 => "Do", 3 => "Teen" }; 

我并不是说上面的代码来自Perl,但它与至少另一种语言一致,因此可能与更广泛的使用相关。


0
我认为其中一个原因是,如果你试图用泛型对象的实例来填充of( ... ),它会生成一个未经检查的警告。
原因是...扩展为Java数组,而Java数组不喜欢泛型实例。
这里有一个处理这个特定问题的规范示例:
List<List<Integer>> pascalsTriangle =
    [[1],
     [1, 1],
     [1, 2, 1],
     [1, 3, 3, 1],
     [1, 4, 6, 4, 1]]

目前还没有一种简洁的方式来初始化这个变量,而且不会出现unchecked警告。


@Green Hyena:不要将其初始化为ArrayList< String >,尝试将其初始化为ArrayList<ArrayList< String >>,然后观察警告。如果这听起来太牵强,请尝试初始化List< Class<?> >而没有警告。 - Alexander Pogrebnyak
@Green Hyena:这是为了证明我的观点:http://ideone.com/WmgwT。顺便说一句,+1 表示感谢你展示这个好工具。 - Alexander Pogrebnyak

0

这并不是全新的语法,因为类似的字面量在Python、Javascript(json)和其他编程语言中都存在。

说实话,我还没有看到关于Java 7的任何信息(最近我们主要关注C++和Javascript),但是看到这样的语言增加确实是好事。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接