Java创建List、Map等集合对象的同时进行赋值操作
问题简介
在Java当中,若希望在创建数组的同时给数组赋值很简单,可以想下面这样:
int[] num = {1,2,3};
String strs = {"a", "b", "c"}
但是,如果我们创建List集合,或者Map集合时,也想快速地为它赋初始值,应当如何做呢?
解决方式
方式1:调用函数
请看如下代码:
ArrayList<String> list = new ArrayList<>(Arrays.asList("aa", "bb", "cc"));
Arrays.asList(T... a) 方法的参数是一个可变长参数,也就是说他能够传入一个数组,也能够传入多个参数,而它的作用就是将传入的数组或多个数据封装成List集合返回,而上面的代码就是接收返回的List集合,并将其作为参数传入ArrayList的构造方法,创建一个新的ArrayList对象。
说到这里有人可能要问了,为什么不能直接将asList方法的返回值赋给list参数,而要将它传入构造器创建新的对象呢?这不是脱裤子放屁——多此一举吗。当然不是,请看下面的代码:
// 代码1
List<String> list1 = Arrays.asList("aa", "bb", "cc");
list1.add("dd"); // UnsupportedOperationException
// 代码2
String[] str = {"a","b","c"};
List<String> list = Arrays.asList(str);
str[0] = "e"; // list中的0号位置也一同改变
上面有两段代码,看似没有问题,但是运行结果却和大家想象的有些不同。首先代码1,使用asList方法返回的创建的List对象,不允许进行修改操作,否则将会抛出一个UnsupportedOperationException;再来看代码2,我们将一个数组作为asList的参数,得到一个List对象,但是此时我们改变这个数组中元素的值,list对象的值也会发生改变,因为这个List对象底层引用的就是这个数组,并且和代码1一样,这个list也不能修改。
但是,若我们将返回的List对象作为参数传入ArrayList的构造器中,这个问题就不会发生,因为ArrayList的构造器将会把传入的list中所有的元素复制一份,因此不会影响到原数组,且可以随意改变。
方式2:匿名内部类
这是一个非常机智的方式,就是看到了下面这行代码,我才忍不住写了这篇博客:
List<String> list = new ArrayList<String>(){ {add("a"); add("b"); add("c");} };
乍一看是不是有点懵逼,我们将这段代码展开来看,就会清晰很多:
List<String> list = new ArrayList<String>() {
{
add("a");
add("b");
add("c");
}
};
这下应该比之前容易理解了。这段代码就是创建了一个匿名内部类对象,且这个类继承自ArrayList,在这个匿名内部类中添加了一个非静态代码块,并在代码块中调用了三次add方法,为这个List对象赋值。
我们知道,若我们想创建一个对象,可以直接new 构造方法,但是我们若想写一个匿名内部类,这个匿名内部类继承自某个类,只需在构造方法后面加上一对大括号。同时,非静态代码块会在构造方法执行前被执行,所以我们将赋值语句放在了代码块中,于是就有了上面这段代码。若还是看不明白,没关系,看下面这段代码十有八九就明白了,我们将上面的代码换另一种方式写出来:
public class Test {
public static void main(String[] args) {
List<String> list = new MyList();
}
}
// 创建一个类继承自ArrayList
class MyList extends ArrayList{
// 在类的非静态代码块中编写赋值语句
{
add("a");
add("b");
add("c");
}
}
以上代码就是最开始那句代码的完整版,创建一个MyList类(名字随意),继承自ArrayList,并编写一个非静态代码块调用三次add方法,这个代码块将会在构造方法执行前被执行,因此创建一个MyList对象后,它肯定已经有三条数据了。若到此时还没有听懂,可能就需要去了解一下匿名内部类,以及代码块的执行机制了。
这种为集合赋值的好处就是,它可以用在任意一种集合类型上(Map,Set......),如下代码:
// 使用此方法为map赋值
HashMap<String, Integer> map = new HashMap<String, Integer>() {
{
put("a", 1);
put("b", 2);
put("c", 3);
}
};
当然,这种方法也有一些弊端,就拿ArrayList来说,那就是这种方法得到的对象,它的类型并不是ArrayList,我们调用对象.getClass().getName()方法获取对象的类名,得到的是代码所在类的类名+$1(这里和匿名内部类的机制有关,就不详细叙述了)。所以在代码中,如果对对象的类型有着严格的要求,就需要谨慎考虑是否应该使用这种方式。
博客总结
在平常编写代码时,还是第一种方式使用的比较多,因为简单而且不容易产生问题;而第二种方,我个人建议还是少用(虽然我就是为第二种方式写的博客.....),因为在类型要求严格的程序中,可能会产生问题。当然,第二种方式真的非常机智(感叹),而且可以用在各种类型的集合上,学习一下还是很有帮助的。
参考文献
《Java核心技术 卷Ⅰ》