java 集合视图_Java 基础-集合的视图和包装器

  • Post author:
  • Post category:java


在学习Java的过程中,我们知道,Java中的集合框架是非常重要,在实际的开发过程(笔者是做Android的)中也是非常的实用。在这里,将简单的介绍一下,Java中的视图和包装器。本文不会介绍Java集合的基本使用,只会介绍一下自己觉得比较重要,但是又很少见的东西。

1.视图是什么

搞过移动开发的朋友,看到视图肯定会想到View。但是我们这里说的不是Java中的awt和swing的View,而是集合中的View。但是这个View在集合中是什么意思呢?

在Java的集合中,我们可以通过视图(View)获得其他的实现了Collection接口和Map接口的对象。例如,我们在使用Map类的keySet方法可以获得一个Set集合对象。初看起来,这个方法创建一个新的set集合,并且将Map中所有的键都填进去,然后返回这个集合。但是,情况并且如此,keySet方法返回一个实现Set接口的类对象,这个类的方法对原来的Map对象进行操作。这个集合就称为视图(View)

2.轻量级集合包装器

前面简单的介绍一下是什么是视图,这里将再次的介绍一些视图的具体例子,并且会介绍普通的集合与视图集合有什么区别。

(1). Arrays.asList方法

在Arrays类中有一个静态方法–asList方法,这个方法作用是:将普通的Java数组包装成一个List集合。例如:

String []strings = new String[10];

strings[0] = “pby”;

strings[1] = “pby1”;

strings[2] = “pby2”;

List stringList = Arrays.asList(strings);

返回的对象不是一个ArrayList对象。它就是一个视图对象,这个对象带有底层数组的get和set方法。

那这个视图对象与普通的List或者ArrayList对象有什么区别吗?

在这里,视图对象不能操作所有改变数组大小的方法(比如说,add方法和remove方法),在调用这些方法的时候,程序会抛出一个UnsupportedOperationException异常;但是普通的List对象能够正常的调用改变数组大小的方法。

(2). Collections.nCopies方法

与Arrays.asList方法类似的另一个方法那就是在Collection中的nCopies方法。例如:

List stringList = Collections.nCopies(100, “pby”);

上面的代码将创建一个包含100个”pby”字符串的List集合对象。这样的操作优势在于存储代价很小,因为这个对象不能修改大小。这就是视图技术的一种巧妙应用。

3. 子范围

在Java中,我们还可以给 很多的集合建立子范围视图。例如,假设有一个集合对象list,我们想要从中取出第10个~第19个元素。可以使用subList方法来获得一个List集合对象的子范围视图。例如:

List list = new ArrayList<>();

for (int i = 0; i < 20; i++) {

list.add(“” + i);

}

System.out.println(list);

//获取第10个~第19个

List list2 = list.subList(9, 20);

System.out.println(list2);

System.out.println(list);

//清空自子范围视图之后,原来的List集合对象相应位置的数据也会被自动清空的

list2.clear();

System.out.println(list2);

System.out.println(list);

4. 不可修改的视图

Collections还有几个方法,用于产生集合的不可修改视图。这些视图对现有的集合增加了一个运行时的检查。如果发现对集合进行修改的话(这里不仅仅是改变数组的大小,并且包括set之类的方法),就会抛出一个异常,同时这个集合将保持未修改的状态。

可以使用如下8种方法来获得不可修改的视图:

1. Collections.unmodifiableCollection

2. Collections.unmodifiableList

3. Collections.unmodifiableSet

3. Collections.unmodifiableSortedSet

5. Collections.unmodifiableNavigableSet

6. Collections.unmodifiableMap

7. Collections.unmodifiableSortedMap

8. Collections.unmodifiableNavigableMap

每个方法都定义于一个接口。例如,Collections.unmodifiableList方法定义于List接口,与ArrayList、LinkedList或者任何实现了List接口的其他类一起协同工作。

例如,假设想要查看某个集合的内容,但是又能避免这个集合会被修改的情况,就可以进行下列的操作:

LinkedList list = new LinkedList<>();

list.add(“pby”);

list.add(“pby2”);

List list2 = Collections.unmodifiableList(list);

//是不能被修改的

//list2.set(0, “dasdas”);

Collections.unmodifiableList方法将返回一个List集合的对象(视图对象),我们可以从这个视图对象中取得内容,但是不能修改,因为在个视图对象中,所有修改类型的方法已经被重新定义为一个抛出UnsupportOperationException的异常,而不是将方法的调用传递给底层集合对象(这里底层集合对象指的就是当前List集合对象的实际类型对象,这种调用方式是由于Java的多态性导致的)。

但是我们这里需要注意的是,不可更改的视图对象并不是指集合本身不可修改,我们仍然可以通过集合原来的引用来(犹如上面例子中的list)对集合进行修改。同时,如果原来的引用修改了集合,那么视图对象的内容也是跟着变化的。

问题:这个有一个疑惑,笔者也是没有搞清楚。unmodifiableList方法将返回一个List集合的对象,它的equals方法不会调用底层集合的equals方法,相反的是,List继承于Object类,因此调用的是Object类中的equals方法,来检测这两个对象是否是同一个对象。

说了这么多,其实想表达的意思就是:通过unmodifiableList方法获得视图对象,在调用equals方法来比较两个对象,是比较两个对象是同一个对象,相当于是==的作用。

意思是非常的简单,但是如下代码:

LinkedList list = new LinkedList<>();

list.add(“pby”);

list.add(“pby2”);

List list2 = Collections.unmodifiableList(list);

System.out.println(list.equals(list2));

System.out.println(list == list2);

第一个输出的是true,第二个输出的是false。按道理来说,这里的equals方法是判断两个引用是否是指向同一个,返回的是true;而==的作用也是如此–判断两个引用是否是指向同一个对象。但是这里的结果却是不一样,这里就不是很好的理解了。如果有朋友懂的话,麻烦请解释一下,谢谢!

5. 同步视图

如果多个线程访问集合,就确保集合不会被意外的破坏。例如,如果一个线程视图将元素添加到Hash表中,同一个另一个线程正在对Hash表进行再散列,这种操作的结果是灾难性的。

但是我们使用视图机制来确保常规集合的线程安全,而不是实现线程安全的集合类。例如,Collections类的静态方法synchronizedMap方法可以将任何一个映射表转换成为具有同步访问方法的Map:

Map map = Collections.synchrizedMap(new HashMap());

现在,就可以自由的使用多线程来访问map对象了。像get和put这类方法都是同步操作的,即在另一个线程中调用另一个方法之前,刚才的方法调用必须彻底执行完毕。

6. 受查视图

受查视图是用来对泛型类型发生问题时,提供调试的支持。

List list = new ArrayList<>();

List list2 = list;

//程序运行到这里是不会报错的,但是如果后面访问这里元素,

//并且试图强制转换为String类型的变量才会抛出一个ClassCastException的异常

list2.add(10);

//这里会抛出ClassCastException异常

//String string = (String) list2.get(0);

例如,上面的例子,先创建了一个List类型的对象,再将它的泛型类型擦除,变成了List类型,由于泛型类型被擦除,原来的泛型类型就会被Object代替,所以我们在list2中添加一个整数(Integer)类型是不会有问题的,同时程序运行到add方法那里也不会报错的。也就是说,程序的编译时和运行时的错误,我们都成功的越过去了。但是在将添加进去的那个元素强制转成为String类型,就会抛出ClassCastException的异常。

,如果我们使用受查视图的话,例如:

List list3 = Collections.checkedList(list, String.class);

List list4 = list3;

//程序运行到这里就会抛出一个ClassCastException的异常

list4.add(10);

虽然在编译时,程序是没有报错的,但是程序一旦运行到add方法那里直接会抛出一个ClassCastException的异常。也就是说,通过受查视图,可以逃避编译时的检查,但是躲不过运行时的检查!



版权声明:本文为weixin_35671843原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。