10. Java集合
10.1 Java集合框架概述
-
为了方便对多个对象的操作,就要对多个对象进行存储,而Java 集合就像一种容器,可以动态地把多个对象的引用放入容器中
-
集合、数组都是对多个数据进行存储操作的数据结构,简称Java容器。
-
但使用Array存储对象方面具有一些弊端:
- 数组长度一旦确定后,就不能修改了,不方便
- 如String[ ] arr;一但定义好后,其元素的类型也就确定了,也就只能操作指定类型了(优点)
- 数组提供的方法十分有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高
- 获取数组中实际元素的个数的需求,数组没有现成的属性和方法可以用
- 数组存储数据的特点:有序、可重复。对无序、不可重复的需求无法满足
-
集合框架:
-
Collection接口:单列接口,用来存储一个一个的对象
-
List接口:存储有序的、可重复的数据 → \to → 动态数组
- ArrayList、LinkedList、Vector
-
Set接口:存储无序的、不可重复的数据 → \to → 数学中的集合
- HashSet、LinkedHashSet、TreeSet
-
-
Map接口:双列集合,用来存储一对(key-value)数据
-
HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
-
-
10.2 Collection接口方法
-
add(Object obj)
Collection coll = new ArrayList(); coll.add("AA"); coll.add("BB"); coll.add(123);//自动装箱 coll.add(new Date());
-
size()
System.out.println(coll.size());//4
-
addAll(Collection coll)
Collection coll1 = new ArrayList(); coll1.add("CC"); coll1.add(456); coll.addAll(coll1); System.out.println(coll.size());//6 System.out.println(coll); //[AA, BB, 123, Sat Jun 05 10:02:11 CST 2021, CC, 456]
-
isEmpy()、clear()
System.out.println(coll.isEmpty());//false coll.clear(); System.out.println(coll.isEmpty());//true
-
contains(Object obj)
:判断当前集合中是否包含obj,判断时会调用obj对象所在类的equals方法,故向collection接口实现类的对象中添加数据obj时,要求obj所在类要重写equals()Collection col1 = new ArrayList(); col1.add("123"); col1.add(345); col1.add(new String("Tom")); col1.add(false); col1.add(new Person("Tom", 23)); boolean contains = col1.contains(345); System.out.println(contains);//true System.out.println(col1.contains(123));//false System.out.println(col1.contains(new String("123")));//true System.out.println(col1.contains(new Person("Tom", 23)));//false //底层使用equals()比较,String重写了equals()比较的时内容;Person没有重写,比较的是地址
若在Person类中重写equals():
@Override public boolean equals(Object o) { System.out.println("Person equals()..."); if (this == o) return true; if (!(o instanceof Person)) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); }
则:
System.out.println(col1.contains(new Person("Tom", 23))); //Tom在第五个 //故要执行5次重写的equals() //true
-
contains(Collection col)
:拿两个集合的元素挨个比较Collection col2 = Arrays.asList("123",345); System.out.println(col1.containsAll(col2));//true
-
remove(Object obj)
:判断是否是要删除的那个元素。 只会删除找到的第一个元素col1.remove(345); System.out.println(col1); //[123, Tom, false, Person{name='Tom', age=23}] //注意:也会执行重写的equals()
-
removeAll(Collection col)
:取当前集合的差集col1.removeAll(col2); System.out.println(col1); //[Tom, false, Person{name='Tom', age=23}]
-
retainAll(Collection col)
:把交集的结果存在当前集合中col1.retainAll(col2);System.out.println(col1); //[123, 345] 可见col1被修改了 //会将交集结果返回给调用方法的集合
-
equals(Object obj)
:集合是否相等,注意如果时List则要看元素顺序、如果是Set那就不看元素顺序 -
Object[] toArray()
:转成对象数组,集合 → \to →数组Object[] objects = col1.toArray(); for (int i = 0; i < col1.size(); i++) { System.out.println(objects[i]); } //123 //345 //Tom //false //Person{name='Tom', age=23
拓展:数组 → \to →集合,Arrays的静态方法:
asList()
Collection col2 = Arrays.asList("123",345);
-
hashCode()
:获取集合对象的哈希值System.out.println(col1.hashCode());//1510696
-
iterator()
:返回Iterator接口实例,用于遍历集合元素
10.3 Iterator迭代器接口
-
Iterator对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素
-
迭代器模式:
提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。 迭代器模式,就是为容器而生
-
集合对象每次调用
iterator()
方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前 -
Iterator接口的方法:
-
hasNext()
、next()
-
Iterator iterator = col1.iterator(); //hasNext():判断是否还有下一个元素 while (iterator.hasNext()){ //next(): //1.只要调用就会使指针下移 //2.将下移以后集合位置上的元素返回 System.out.println(iterator.next()); }
-
在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常
-
-
remove()
-
Iterator iterator = col1.iterator(); while (iterator.hasNext()){ Object next = iterator.next(); if (next.equals("Tom")){ iterator.remove(); } } Iterator iterator1 = col1.iterator(); while (iterator1.hasNext()){ System.out.println(iterator1.next()); }
-
注意:
- Iterator可以删除集合的元素, 但是是遍历过程中通过迭代器对象的remove方
法, 不是集合对象的remove方法 - 如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,
再调用remove都会报IllegalStateException
- Iterator可以删除集合的元素, 但是是遍历过程中通过迭代器对象的remove方
-
-
-
foreach 循环遍历集合或数组的元素
-
Java 5.0 提供了 foreach
for (:) { }
循环迭代访问 Collection和数组 -
遍历操作不需获取Collection或数组的长度,无需使用索引访问元素
-
遍历集合的底层调用Iterator完成操作
-
foreach还可以用来遍历数组
-
//for(集合元素的类型 局部变量 : 集合对象) for (Object obj:col1) { System.out.println(obj); }
-
foreach并不会修改原集合中的内容
for (Object obj : col1) { obj = new Person("Jack",20); //只是赋值给了对象obj System.out.println(obj); } for (Object obj:col1) { System.out.println(obj); //原来的集合并未发生改变 }
-
10.4 Collection子接口一:List
-
List接口:存储有序的、可重复的数据 → \to → 动态数组
三个实现类:
- ArrayList:List的主要实现类;线程不安全的,效率高(如果需要是线程安全的,则使collections工具类中的 synchronized(List<T> list) 来实现线程安全);底层使用Object[ ]数组存储(数组的动态分配)只要用到数组都可以使用ArrayList
- LinkedList:对于频繁的插入和删除操作,使用此类效率更高;底层使用双向链表存储
- Vector:List的古老实现类;线程安全的,效率低;底层使用Object[ ]数组存储(基本不用)
-
ArrayList源码分析:
-
ArrayList是对象引用的一个"变长"数组
-
ArrayList的JDK1.8之前与之后的实现区别?
- JDK1.7: ArrayList像饿汉式,直接创建一个初始容量为10的数组,如果此次添加导致底层elementData数组不够用,则扩容,默认情况下扩容为原来的1.5倍,同时将原数组的内容复制到新数组中
- JDK1.8: ArrayList像懒汉式,一开始创建一个长度为0的数组 { },当添加第一个元
素时再创建一个始容量为10的数组,扩容时,与1.7中相同
-
-
LinkedList源码分析:
-
对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高
-
内部声明了Node类型的first和last属性(头尾指针),默认值为null
-
底层使用双向链表存储,故Node的定义为:
private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
-
-
面试题:请问ArrayList/LinkedList/Vector的异同? 谈谈你的理解? ArrayList底层是什么?扩容机制? Vector和ArrayList的最大区别?
- ArrayList和LinkedList的异同
二者都线程不安全,相对线程安全的Vector,执行效率高。
此外, ArrayList是实现了基于动态数组的数据结构, LinkedList基于链表的数据结构。对于
随机访问get和set, ArrayList觉得优于LinkedList,因为LinkedList要移动指针。对于新增
和删除操作add(特指插入)和remove, LinkedList比较占优势,因为ArrayList要移动数据 - ArrayList和Vector的区别
Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于
强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用
ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。 Vector每次扩容请求其大
小的2倍空间,而ArrayList是1.5倍。 Vector还有一个子类Stack
- ArrayList和LinkedList的异同
-
List接口方法:
-
增:
add(Object obj)
-
长度:
size()
-
遍历:
-
Iterator迭代器
Iterator iterator = linkedList.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); }
-
foreach循环
for (Object obj:linkedList ) { System.out.println(obj); }
-
-
插:
void add(int index, Object ele)
:在index位置插入ele元素LinkedList linkedList = new LinkedList(); linkedList.add(0,21); ArrayList arrayList = new ArrayList(); arrayList.add(0,12); //arrayList.add(2,12);IndexOutOfBoundsException,与初始化有关
-
boolean addAll(int index, Collection eles)
:从index位置开始将eles中的所有元素添加进来 -
查:
Object get(int index)
:获取指定index位置的元素 -
改:
Object set(int index, Object ele)
:设置指定index位置的元素为ele -
int indexOf(Object obj)
:返回obj在集合中首次出现的位置 -
int lastIndexOf(Object obj)
:返回obj在当前集合中末次出现的位置 -
删:
Object remove(int index)
:移除指定index位置的元素,并返回此元素//ArrayList在删除后都会补齐 ArrayList arrayList = new ArrayList(); arrayList.add(0,12); arrayList.add(1,21); arrayList.remove(0); System.out.println(arrayList.get(0)); //21
注:区分子接口list中的
remove(int index)
与collection接口中的remove(Object obj)
LinkedList linkedList = new LinkedList();linkedList.add(0,1); linkedList.add(1,2); linkedList.remove(1); System.out.println(linkedList); //[1]按index删除 linkedList.remove(new Integer(1)); System.out.println(linkedList); //[]按value删除
-
List subList(int fromIndex, int toIndex)
:返回从fromIndex到toIndex位置的左闭右开子集合,原list不会改变,只会返回想要的子元素 -
boolean contains(Object obj)
:判断元素是否在动态数组中
-
10.5 Collection子接口二:Set
-
Set接口:存储无序的、不可重复的数据 → \to → 数学中的集合
- HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
- LinkedHashSet:作为HashSet的子类:使得遍历其内部数据时,可以按照添加顺序遍历
- TreeSet:可以按照添加对象的指定属性,进行排序
- HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
-
Set的无序性:(以HashSet为例说明)
- 不等于随机性
- 存储的数据在底层数组中,并非按照数组索引的顺序添加,而是根据数据的哈希值添加的
-
Set的不可重复性:
- 保证添加的元素按照equals( )判断时,不能返回true
- 相同的元素只能添加一次
-
Set中添加元素的过程:(以HashSet为例说明)
-
首先调用元素a所在类的hashCode( )方法,计算出元素a的哈希值
-
通过某种算法计算出在HashSet底层数组中存放的位置(索引位置)
-
判断数组此位置上
- 没有元素:则元素a添加成功
- 有其他元素b(或以链表形式存在的多个元素):比较元素a与b的哈希值
- 如果哈希值不相同:则元素a添加成功
- 如果哈希值相同:调用元素a所在类的equals( ),返回true,元素a添加失败;返回false,元素添加成功
-
说明:对于添加成功的情况2和情况3而言:元素a与已经存在指定位置上的数据以链表的形式存储
-
-
向Set中添加的数据,元素所在的类一定要重写equals( )和hashCode( ),且重写的方法必须具有一致性:相等的对象必须具有相等的散列码
@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Person)) return false; Person person = (Person) o; if (age != person.age) return false; return name != null ? name.equals(person.name) : person.name == null; } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + age; return result; }
以Eclipse/IDEA为例,在自定义类中可以调用工具自动重写equals和hashCode。
问题: 为什么用Eclipse/IDEA复写hashCode方法,有31这个数字?- 选择系数的时候要选择尽量大的系数。因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。(减少冲突)
- 并且31只占用5bits,相乘造成数据溢出的概率较小
- 31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化。 (提高算法效率)
- 31是一个素数,素数作用就是如果我用一个数字来乘以这个素数,那么最终出来的结果只能被素数本身和被乘数还有1来整除! (减少冲突)
-
LinkedHashSet:
-
LinkedHashSet 是 HashSet 的子类
-
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的
-
LinkedHashSet插入性能略低于 HashSet, 但在频繁遍历全部元素时有很好的性能
-
-
TreeSet:
-
向TreeSet 添加数据,要求数据是相同类的对象
-
TreeSet 是 SortedSet 接口的实现类, TreeSet 可以确保集合元素处于排序状态
-
两种排序方式:
-
自然排序:
-
元素类实现Comparable接口,并重写元素类中的compareTo(Object o)
//按name排序,name相同的按照age排 @Override public int compareTo(Object o) { if(o instanceof Person){ Person person = (Person) o; int compare = -this.name.compareTo(person.name); if(compare != 0){ return compare; }else { return Integer.compare(this.age,person.age); } }else { throw new RuntimeException("输入的数据类型不匹配"); } }
TreeSet set = new TreeSet(); set.add(new Person("Tom",22)); set.add(new Person("Jack",20)); set.add(new Person("Jack",18)); for (Object obj:set ) { System.out.println(obj); } //Person{name='Tom', age=22} //Person{name='Jack', age=18} //Person{name='Jack', age=20}
-
自然排序中,比较两个对象是否相同的标准为:compareTo( )返回0,不再是equals( )
-
-
定制排序:
-
new一个comparator排序器
Comparator comparator = new Comparator() { @Override public int compare(Object o1, Object o2) { if(o1 instanceof Person && o2 instanceof Person){ Person p1 = (Person) o1; Person p2 = (Person) o2; return Integer.compare(p1.getAge(),p2.getAge()); }else { throw new RuntimeException("输入的数据类型不匹配"); } } }; TreeSet set = new TreeSet(comparator); set.add(new Person("Tom",22)); set.add(new Person("Jack",20)); set.add(new Person("Jack",18)); for (Object obj:set ) { System.out.println(obj); }
-
定制排序中,比较两个对象是否相同的标准为:compare( )返回0,不再是equals( )
-
-
-
10.6 Map接口
-
Map:双列数据,存储key-value对的数据
- HashMap:作为Map的主要实现类;线程不安全的,效率高;键值都可以为null
- LinkHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历,原因时在原有的HashMap结构上,添加了一对指针指向前后的元素
- TreeMap:保证按照添加的key-value对进行排序,实现排序遍历,此时考虑key的自然排序,底层使用红黑树
- Hashtable:古老的实现类;线程安全的,效率不高,键值都不可以为null
- Properties:常用来处理配置文件,key和value都是String类型
- HashMap:作为Map的主要实现类;线程不安全的,效率高;键值都可以为null
-
Map结构的理解:
-
Map中的key:无序的、不可重复的,使用Set存储所有的key
- key所在的类要重写equals( )和hashCode( )
-
Map中的value:无序的、可重复的,使用Collection存储所有的value
- value所在的类要重写equals( )
-
一个键值对:key-value构成了一个Entry对象
-
Map中的Entry:无序的、不可重复的,也使用Set存储所有的entry
-
-
⭐HashMap的底层实现原理:(以jdk7为例)
-
创建HashMap对象,实例化以后,底层创建了长度是16的一维数组Entry [ ] table
HashMap hashMap = new HashMap();
-
首先调用key1所在类的hashCode( )计算key1的哈希值,此哈希值经过某种算法计算后,得到Entry数组中存放的位置
⭐属性 → h a s h C o d e ( ) \to^{hashCode()} →hashCode() → \to →哈希值 → 某 种 算 法 \to^{某种算法} →某种算法 → \to →哈希表的索引位置 → 若 位 置 相 同 , 比 较 哈 希 值 \to^{若位置相同,比较哈希值} →若位置相同,比较哈希值 → 若 哈 希 值 相 同 , 用 e q u a l s ( ) 比 较 k e y \to^{若哈希值相同,用equals()比较key} →若哈希值相同,用equals()比较key → 若 k e y 相 同 , 替 换 v a l u e \to^{若key相同,替换value} →若key相同,替换value
hashMap.put(key1,value1);
- 如果此位置上数据为空,此时key1-value1添加成功,放到数组中
- 如果此位置上存在一个或多个数据(以链表形式存在),比较key1和已经存在的数据的哈希值:
- 不同:key1-value1添加成功,链进去
- 和某一个数据(key2-value2)的哈希值相同:继续比较,调用key1所在类的equals( )方法,比较key是否相同:
- 返回false:此时key1-value1添加成功,链进去
- 返回true:使用value1替换相同key的value2值(保证key不能重复)
-
在不断添加的过程中,涉及到扩容问题,默认的扩容方式:扩容为原来的2倍,并将原有的数据复制过来
-
JDK 8相较于JDK 7的不同:
-
new HashMap( ):底层并没有创建一个长度为16的数组
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }
-
jdk 8 底层的数组是:Node[ ],而非Entry[ ]
static class Node<K,V> implements Map.Entry<K,V> {}
-
首次调用put( )时,底层才创建一个长度为16的数组
-
jdk 7底层结构:只有数组+链表;jdk 8底层结构:数组+链表+红黑树
-
当数组某一个索引位置上的元素以链表形式存在的数据个数>8,且当前数组长度> 64时,此时索引位置上所有数据改为使用红黑树存储
-
面试题:
-
谈谈你对HashMap中put/get方法的认识?如果了解再谈谈HashMap的扩容机制?默认大小是多少?什么是负载因子(或填充比)? 什么是吞吐临界值(或阈值、 threshold)?
-
负载因子值的大小,对HashMap有什么影响
答:
- 负载因子的大小决定了HashMap的数据密度
- 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降
- 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间
- 按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数
-
-
Map接口中定义的方法:
-
添加、 删除、修改操作:
Object put(Object key,Object value)
:将指定key-value添加到(或修改)当前map对象中void putAll(Map m)
:将m中的所有key-value对存放到当前map中Object remove(Object key)
:移除指定key的key-value对,并返回valuevoid clear()
:清空当前map中的所有数据
-
元素查询的操作:
Object get(Object key)
:获取指定key对应的valueboolean containsKey(Object key)
:是否包含指定的keyboolean containsValue(Object value)
:是否包含指定的valueint size()
:返回map中key-value对的个数boolean isEmpty()
:判断当前map是否为空boolean equals(Object obj)
:判断当前map和参数对象obj是否相等
-
元视图操作的方法(得到集合后,可以使用集合的遍历方式进行遍历):
-
Set keySet()
:返回所有key构成的Set集合HashMap hashMap = new HashMap(); hashMap.put(1,2); hashMap.put("Tom",22); hashMap.put("Tom","abc");//这里的put是修改 Set set = hashMap.keySet(); for (Object obj:set ) { System.out.println(obj); } //1 //Tom
-
Collection values()
:返回所有value构成的Collection集合Collection values = hashMap.values(); Iterator iterator = values.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } //2 //abc
-
Set entrySet()
:返回所有key-value对构成的Set集合Set set1 = hashMap.entrySet(); for (Object obj:set1 ) { Map.Entry entry = (Map.Entry) obj; System.out.println(entry.getKey()+":"+entry.getValue()); } //1:2 //Tom:abc
-
-
-
TreeMap:
-
TreeMap存储 Key-Value 对时, 需要根据 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态
-
TreeSet底层使用红黑树结构存储数据
-
TreeMap 的 Key 的排序:
- 自然排序: TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
- 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable 接口
-
TreeMap判断两个key相等的标准:
两个key通过compareTo()方法或者compare()方法返回0
-
-
Properties:
-
Properties 类是 Hashtable 的子类,该对象用于处理属性文件
-
由于属性文件里的 key、 value 都是字符串类型,所以 Properties 里的 key和 value 都是字符串类型
-
存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法
public static void main(String[] args) { FileInputStream fileInputStream = null; try { Properties properties = new Properties(); fileInputStream = new FileInputStream("jdbc.properties"); properties.load(fileInputStream); String name = properties.getProperty("name"); String password = properties.getProperty("password"); System.out.println(name+"-"+password); //TOM-123 } catch (IOException e) { e.printStackTrace(); }finally { if(fileInputStream != null){ try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
-
10.7 Collections工具类
-
Collections 是一个操作 Set、 List 和 Map 等集合的工具类
-
提供了一系列静态的方法对集合元素进行排序、查询和修改等操作:
-
排序操作:(均为static方法)
reverse(List)
: 反转 List 中元素的顺序shuffle(List)
: 对 List 集合元素进行随机排序sort(List)
: 根据元素的自然顺序对指定 List 集合元素按升序排序sort(List, Comparator)
: 根据指定的 Comparator 产生的顺序对 List 集合元素进行排序swap(List, int, int)
: 将指定 list 集合中的 i 处元素和 j 处元素进行交换
-
查找、替换
-
Object max(Collection)
: 根据元素的自然顺序,返回给定集合中的最大元素 -
Object max(Collection, Comparator)
: 根据 Comparator 指定的顺序,返回给定集合中的最大元素 -
Object min(Collection)
-
Object min(Collection, Comparator)
-
int frequency(Collection, Object)
: 返回指定集合中指定元素的出现次数 -
void copy(List dest,List src)
:将src中的内容复制到dest中//复制时要保证dest和src的size相同 List list = new ArrayList(); list.add(1); list.add(2); //错误的: //List list1 = new ArrayList(); //IndexOutOfBoundsException List list1 = Arrays.asList(new Object[list.size()]); Collections.copy(list1,list); System.out.println(list1);//[1, 2]
-
boolean replaceAll(List list, Object oldVal, Object newVal)
: 使用新值替换List 对象的所有旧值
-
-
同步控制:
-
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题
-
ArrayList和HashMap都不是线程安全的:
List list = new ArrayList(); list.add(1); list.add(2); //返回的list1即为线程安全的 List list1 = Collections.synchronizedList(list);
-
-