java 集合的总结
集合
1.集合Collection
1.1collection接口继承树
1.2Map接口继承树
2.Collectio接口方法
- Collection 接口是List、Set 和 Queue 接口的父接口,该接口中定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合
- 在 java5 之前, java 集合会丢失容器中所有对象的数据类型, 把所有对象都当成Object 类型处理;从 jdk5.0增加 泛型 以后,java 集合可以记住容器中对象的类型
- 添加
- add(Object obj)
- addAll(Collection coll):添加整个集合(不判重)
- 获取元素有效个数
- int size()
- 清空集合
- void clear()
- 判断空集合
- boolean isEmpty()
- 是否包含某个元素
- boolean contains(Object obj):通过元素的 equals 方法判断是否包含
- boolean containsAll(Collection c):也是调用元素的equals() 方法,挨个比较
- 删除
- boolean remove(Object obj):只会删除找到的第一个元素,返回操作是否发生
- boolean removeAll(Collection coll):取当前集合的差集
- 取两个集合的交集
- boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c,找到当前集合中与c中元素相同的元素(可以重复)
- 集合是否相等
- boolean equals(Object obj)
- 转成对象数组
- Object[] toArray()
- 获取集合对象的哈希值
- hashCode()
- 遍历
- iterator():返回迭代器对象,用于集合遍历
Collection coon = new ArrayList();
Iterator iter = coon.iterator();
while(iter.hasNext()) {
Object obj = iter.next();
System.out.println(obj);
}
3. Iterator迭代器接口
-
Iterator 对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素
-
GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公交车上的售票员”、“火车上的乘务员”、“空姐”。
-
Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
-
Iterator 仅用于遍历集合,Iterator本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。
-
集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
4. list 接口框架
Collection接口:单列集合,用来存储一个一个的对象
List接口:存储有序的、可重复的数据,动态数组
? ArrayList:作为List接口的主要实现类;线程不安全,效率高;底层使用Object[] elementsData存储
? LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层双向链表存储
? Vector:作为List接口的古老实现类,线程安全,效率低底层使用Object[] elementsData存储
ArrayList源码分析
jdk 7 情况下
ArrayList list = new ArrayList();// 底层创建了长度是10的Object[]数组elementData
list.add(123);// elementData = new Integer(123)
list.add(112); // 如果此次添加导致底层elementData数组容量不够,则扩容,默认情况下,扩容为原来的1.5倍,同时需要将原有数组中的数据复制到新的数组中
建议使用带参的构造器: ArrayList list = new ArrayList(int Capacity)
jdk 8 的变化
ArrayList list = new ArrayList(); // 底层Object[] elementData初始化为{},并没有创建长度为10的数组
list.add(123); // 第一次调用add()时,底层才创建l长度为10的数组,并添加数据到elemnetData中
// 后续的添加和扩容操作与jdk 7 一样
小结:jdk7中的 ArrayList的对象的创建类似于单例的饿汉式,而jdk8 中的类似于懒汉式。
LinkedList 源码分析
LinkedList list = new LinkedList(); // 创建LinkedList
/*
LinkedList 是使用java中链表结构来存储数据的,一个结点Node包含prev、next分别指向前驱和后驱
*/
// Node 结点
private static class Node {
E item;
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
// 链表结构
class LinkedList {
Node first;
Node last; // 记录首个和尾结点,用于遍历查找
public MyLinkList() {
}
public int size() {
return size;
}
// 让e最后一个结点
void linkLast(E e) {
final Node l = last; // 记录尾结点
final Node newNode = new Node<>(l, e, null);// 创造新节点
last = newNode;// 最后的尾结点是newNode
if (l == null) // 开始链表为空
first = newNode;// 首结点和尾结点都是newNode
else
l.next = newNode;// 否则最后一个结点向后指向新的尾结点
size++;
modCount++;
}
// 添加元素 add(E e)
public boolean add(E e) {
linkLast(e); // 添加元素即向链表尾部 加元素
return true;
}
// 同理,向首部添加元素
private void linkFirst(E e) {
final Node f = first;
final Node newNode = new Node<>(null, e, f);
first = newNode;
if (f == null) // 初始链表为空
last = newNode;// 首部和尾部都指向newNode
else
f.prev = newNode;// f(不为空)指向第一个结点,则前驱结点指向newNode
size++;
modCount++;
}
// 向链表中间添加元素, 在第index元素后面添加element
public void add(int index, E element) {
checkPositionIndex(index); // 检查index是否越界
if (index == size) // 在最后一个元素(尾部)添加
linkLast(element);
else
linkBefore(element, node(index));
// node返回的第index个元素的后一个元素,所以要在这之前插入给定元素
}
// 在结点之前插入元素
void linkBefore(E e, Node succ) {
// assert succ != null;
final Node pred = succ.prev;
final Node newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
}
vector源码分析
jdk 7 和 jdk 8 中通过Vector( ) 构造器创建对象时,底层创建了长度为10的数组,在扩容方面,默认扩容为原来的数组长度的2倍。
下面分析 jdk14的 Vector 源码
/*
vector 也采用Object[] 数组存储数据和扩容机制
*/
public class Vector extends AbstractList{
protected Object[] elementData; // Object数组
protected int elementCoune; // 有效元素的个数
protected int capacityIncrement;// 每次扩容的容量大小z
//构造函数
// - 有参构造(初始容量,自定义每次扩容大小)
public Vector(int initialCapacity,int Increment) {
super(); // 父类的构造哈函数
if(容量 < 0 ) 抛出异常 throw new Exception;
this.elementData = new Object[initialCapacity];
this.capacityIncrement = Increment
}
public Vector(int initialCapacity) {
this(initialCapacity,0); // 默认扩容为0
}
public Vector() {
this(10); // 默认容量为10
}
// 返回vector的容量
public synchronized int capacity() {
return elementData.length;
}
// 返回vector中元素的数量
public synchronized int size() {
return elementCount;
}
扩容机制(扩容之后返回的复制后的新数组)
- elementCount 数组元素的个数
- elementData.length 数组的长度(如长10,下标从 0 ~ 9)
// 1、grow() 无参数
// 添加元素
public synchronized boolean add(E e) {
modCount++;
add(e, elementData, elementCount); // 往数组最后面加元素
// 数组的个数比下标大1
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) // 越界了
elementData = grow(); // 开始扩容
elementData[s] = e;
elementCount = s + 1; // 元素的个数加1
}
// 2、扩容,调用有参的grow()
private Object[] grow() {
return grow(elementCount + 1); // 要多加1个元素,所以函数参数表示新数组的长度
}
// 3、扩容核心代码
// - minCapacity 最少需要的数组长度
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
capacityIncrement > 0 ? capacityIncrement : oldCapacity
/* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
}
/*
oldLength 现在数组的长度length
minGrowth 数组的最小增量
prefGrowth 数组首选zeng'zahng
*/
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
// assert oldLength >= 0
// assert minGrowth > 0
int newLength = Math.max(minGrowth, prefGrowth) + oldLength;
// 如果增长因子 > 0,vector增加max(minGrowth,capacityIncrement)
// 增长因子 < 0,vector容量加倍或者max(minGrowth,oldCapacity)+oldLength
if (newLength - MAX_ARRAY_LENGTH <= 0) {
return newLength;
}
return hugeLength(oldLength, minGrowth);
// 上面都不行则返回整数的最大值作为新数组的长度
}
如果增长因子 > 0,vector增加
max(minGrowth,capacityIncrement)
增长因子 < 0,vector容量加倍
或者max(minGrowth,oldCapacity)+oldLength
List接口中的常用方法(添加的)
List除继承集合Collection中的方法外,还添加了一些根据索引操作元素的方法
index从0开始
void add(int index,Object ele)
在index位置插入元素boolean addAll(int index,Collection eles)
从index位置插入结合的索引元素Object get(int index)
int indexOf(Object obj)
返回obj在集合中首次出现的位置int lastIndexOf(Object obj)
返回obj在集合中最后出现的位置Object remove(int index)
移除指定index位置的元素,返回该元素Object set(int index,Object ele)
设定指定index位置的元素为eleList subList(int fromIndex, int toIndex)
返回范围的子集合(左闭右开)
遍历List
- 迭代器Iterator方式
- 增强for循环
- 普通的循环
/*
List遍历的三种方式
*/
@Test
public void test3() {
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("Tom");
// 方式一
Iterator iter = list.iterator();
while(iter.hasNext()) {
Object obj = iter.next();
System.out.println(obj);
}
// 方式二
for(Object obj : list) {
System.out.println(obj);
}
// 方式三
for(int i = 0;i < list.size();i++) {
System.out.println(list.get(i));
}
}
5.Set接口框架
- Set接口是Collection的子接口,set接口没有提供额外的方法
- Set集合不允许包含相同的元素
- Set判断两个对象是否相同用
equals()
方法
Set接口: 存储无序的、不可重复的数据
HashSet:Set接口的主要实现类;线程不安全;可以存储null值;
LinkedHashSet:HashSet的子类,遍历内部数据时,可以按照添加的顺序的遍历,遍历的效率要高于HashSet
TreeSet:可以按照添加对象的属性,进行排序
无序性和不可重复性?
无序性不等于随机性,存储的数据在底层数组不是按照数组索引的顺序添加。而是根据数据的hash值决定
不可重复性:Set中不能有重复的数据元素(使用equals() 方法判断),如果是重复的类则这个类需要重写 equals() 和 hashCode( )方法
5.1 HashSet中元素的添加过程
-
HashSet 是 Set 接口的典型实现,大多数时候使用Set集合时,都使用这个实现类
-
HashSet 按照 Hash 算法存储集合中的元素,存取、查找、删除性能
-
HashSet :不能 保证元素的排列顺序
- 不是线程安全的
- 集合元素可以为null
-
底层是数组,初始容量16,当如果使用率超过0.75,就会扩容为原来的2倍。
-
HashSet 集合判断两个元素相等:
- 两个对象通过
hashCode()
比较相等 - 并且
equals()
方法返回值相等
- 两个对象通过
-
对于放在Set容器中的对象,对应的类一定要重写
equals()
和hashCode(Object obj)
方法,以实现对象相等规则。“相等的对象必须具有相等的散列码”
添加元素的过程:以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()
方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置
(即为:索引位置),判断
数组此位置上是否已经有元素:
? 如果此位置上没有其他元素
,则元素a添加成功。 --->情况1
? 如果此位置上有其他元素
b(或以链表形式存在的多个元素),则比较元素a与元素b 的hash值:
? 如果hash值不相同
,则元素a添加成功。--->情况2
? 如果hash值相同
,进而需要调用元素a所在类的equals()
方法:
? equals()返回true
,元素a添加失败
? equals()返回false
,则元素a添加成功
。--->情况3
- 对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数组+链表的结构
5.2 LinkedHashSet的使用
LinkedHashSet
是HashSet
的子类LinkedHashSet
根据元素的hasCode
值决定元素的存储位置,同时用双向链表维护元素的次序,使得元素看起来是以插入顺序保存,的。LinkedHashSet
插入性能低于HashSet
,但在迭代(频繁遍历)访问Set 里的全部元素时有很好的性能
5.3 TreeSet使用
TreeSet不能添加不同类的对象
TreeSet
是SortedSet
接口的实现类,TreeSet
可以确保集合元素处于排序状态TreeSet
底层使用红黑树结构存储数据(TreeMap也是)- 特点:有序,查询速度比
List
快
两种排序方法: 自然排序 定制排序,默认采用自然排序
5.4 自然排序
- 自然排序:
TreeSet
调用集合元素的compareTo(Object obj)
方法,两个对象通过compareTo()方法的返回值比较大小 Comparable
的典型实现BigDecimal
、BigInteger
以及所有的数值型对应的包装类:按数值大小比较Character
:按字符的unicode值来进行比较Boolean
:true对应的包装类实例大于false对应的包装类实例String
:按字符串中字符的unicode值进行比较Data、Time
:时间和日期从小到大
向TreeSet
中添加元素时,只有第一个元素无须比较compareTo()
方法,后面添加的所有元素都会调用compareTo()
方法进行比较。
5.5 定制排序
- 如果元素所属的类没有实现
Comparable
接口,通过Comparator
接口来实现,重写compare(T o1,T o2)
方法 - 需要将实现
Comparator
接口的实例作为形参传递给TreeSet
的构造器。 - 使用定制排序判断两个元素相等的标准是:通过Comparator比较两个元素返回了0。
@Test
public void test3() {
Comparator com = new Comparator() {
// 采用实现类实例化
// 匿名内部类一定要实现内部所有的抽象方法
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User) {
User u1 = (User) o1;
User u2 = (User) o2;
return Integer.compare(u1.getAge(),u2.getAge());
}else {
throw new RuntimeException("类型不一致");
}
}
};
TreeSet set2 = new TreeSet(com);
set2.add(new User("Tom",12));
set2.add(new User("Jerry",32));
set2.add(new User("Jim",2));
set2.add(new User("Mike",65));
set2.add(new User("Jack",33));
set2.add(new User("Jack",56));
Iterator iterator = set2.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
6.Map接口
6.1 Map的实现类的结构
Map :双列数据,存储 key-value 对的数据
HashMap : 作为Map 的主要实现类;线程不安全,效率高,存储null的key和value
- LinkedHashMap :保证在遍历 map 元素时,可以按照添加的顺序进行遍历 (原因:在原有的HashMap底层结构上,添加了一对指针,指向前一个和后一个元素,对于频繁的遍历操作,效率较高)
TreeMap :保证按照添加的 key-value对 进行排序,实现排序遍历,此时考虑key的自然排序或定制排序(底层使用红黑树)
Hashtable : 作为古老实现类;线程安全,效率低,不能存储null 的 key 和 value(过时了)
- Properties :常用来存储配置文件。key 和 value 都是 String类型
HashMap的底层:数组+链表 (jdk7及之前) * 数组+链表+红黑树 (jdk 8)
6.2 Map中存储key-value的特点
- Map 中的 key 和vallue 都可以是任何引用类型的数据
- Map 中的
key
用set
存放,不允许重复,即同一个Map
对象所对应的类,须重写hashCode()
和equals()
方法 HashMap
是Map
接口使用频率最高的实现类
Map结构的理解
Map中的key: 无序的、不可重复的,使用Set存储所有的key ---> key所在的类要重写equals( )和 hashCode() (以HashMap为例)
- Map 中的 value : 无序的、可重复的,使用Collection存储所有的value --->value所在的类要重写 equals() [以HashMap为例]
- 一个键值对 :key-value 构成了一个 Entry 对象。
- Map 中的 entry :无序的、不可重复的,使用 Set 存储所有的 entry
6.3 HashMap的底层原理?
HashMap的底层原理?
在实例化以后,底层创建了长度是16的一维数组Entry[] table
以jdk7为例:
首先,调用key1所在类的 hashCode()计算key1 哈希值,此哈希值经过某种算法计算后,得到在Entry数组中的存放位置 i
如果此位置上的数据为空,此时的key1-value1 添加成功。 ----情况1
如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据,链表形式),比较key1和已经存在的一个或多个数据的哈希值:
- 比较哈希值(存储位置相同不代表哈希值相同)
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1添加成功。 -----情况2
如果key1的哈希值和已经存在的某一个数据(key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)
- 比较equals() 判断key1的对象内容是否相等
如果equals()返回false:此时key1-value1添加成功。 -----情况3
如果equals()返回true:使用value1替换value2。
补充--关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储
扩容:在不断添加的过程中,会涉及到扩容问题,默认的扩容方式:扩容到原来的2倍,并将原有的数据复制过来
---
jdk8 相较于jdk7在底层实现方面的不同:
1. new HashMap():底层没有创建一个长度16的数组
2. jdk 8 底层的数组是:Node[],而非Entry[]
3. 首次调用put()方法时,底层才创建长度为16的数组
4. jdk7 只有数组+链表。 jdk8 底层结构: 数组+链表+红黑树
当数组的某一个索引位置上的元素以链表形式存在的数据个数 >= 8 且当前数组的长度 > 64 时,此时此索引位置上的所有数据改为使用红黑树存储
HashMap 源码中的重要常量
DEFAULT_INITIAL_CAPACITY
: HashMap的默认容量,16
-
`DEFAULT_LOAD_FACTOR`:HashMap的默认加载因子:0.75
-
`threshold`:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
-
`TREEIFY_THRESHOLD`:Bucket中链表长度大于该默认值,转化为红黑树:8
-
`MIN_TREEIFY_CAPACITY`:桶中的Node被树化时最小的hash表容量:64
6.4 Map中常用的方法
添加、删除、修改
Object put(Object key,Object value)
:添加key-value,返回valuevoid putAll(Map m)
:将m中所有key-value对存到当前map中Object remove(Object key)
:移除指定的key-value,返回valuevoid clear()
:清空当前map中的所有数据
查询
Object get(Object key)
:获取指定key对应的valueboolean containsKey(Object key)
boolean containsValue(Object value)
int size()
返回map中key-vaue对的个数boolean isEmpty()
: 判断是否为空booean equals(Object obj)
: 判断当前map 和 参数对象 obj是否相等
元视图操作
Set keySet()
:返回所有key构成的set集合Collection values()
:返回所有value构成的Collection集合Set entrySet()
:返回所有key-value 对的集合
遍历和方法测试
public void test2() {
/**
* 元视图操作的方法:
* Set keySet():返回所有key构成的Set集合
* Collection values():返回所有value构成的Collection集合
* Set entrySet():返回所有key-value对构成的Set集合
*/
Map map = new HashMap();
map.put("AA",123);
map.put(45,1234);
map.put("BB",56);
// 遍历所有的key集:keySet()
Set set = map.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("-----------");
// 遍历所有的values集:values()
Collection values = map.values();
for(Object obj : values) {
System.out.println(obj);
}
System.out.println("********");
// 遍历所有的key-values
// 方式一 直接遍历entrySet(存储键值对)
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while (iterator1.hasNext()) {
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey()+"--->"+entry.getValue());
}
System.out.println("/");
// 方式二
Set keySet = map.keySet(); // 获得所有的key
Iterator iterator2 = keySet.iterator();
while (iterator2.hasNext()) {
Object key = iterator2.next();
Object value = map.get(key);
System.out.println(key + "====" +value);
}
}
6.3 LinkedHashMap
LinkedHashMap
是HashMap
的子类- 在
HashMap
存储结构的基础下,使用一对双向链表来记录添加元素的顺序 HashMap
中的内部类:Node
static class Node implements Map.Entry {
final int hash;
final K key;
V value;
Node next;
}
LinkedHashMap
中的内部类:Entry
static class Entry extends HashMap.Node {
Entry before,after;
Entry(int hash,K key,V value,Node next) {
super(hash,key,value,next);
}
}
6.4 TreeMap两种添加方式
TreeMap
存储Key-Value
对时,需要根据key-value
对进行排序。TreeMap
可以保证所有的Key-Value
对处于有序状态。
key
的排序
- 自然排序:
TreeMap
的所有key
必须实现Comparable
接口,而且所有的key
要是同一个类的对象 - 定制排序:创建
TreeMap
时,传入一个Comparator
对象,此时不需要Map
的key
实现Comparable
接口 TreeMap
判断两个key
相等的标准:通过compareTo()
和compare
方法返回0
6.5 Properties
Properties
类是Hashtable
的子类,用于处理属性文件key和value
都是字符串类型- 存取数据时,
setProperty(String key,String value)
和getProperty(String key)
方法
7.Collections工具类
-
Collections
是一个操作Set
、List
和Map
等集合的工具类 -
Collections
中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法 -
排序操作:
static
方法 以下操作会直接影响到list的元素内容reverse(List)
:颠倒顺序shuffle(List)
:随机排序,任意顺序sort(List)
:根据元素的自然顺序对指定List 集合元素按升序排序sort(List,Comparator)
:定制排序 Comparatorswap(List,int,int)
:list集合中交换元素(下标)
-
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中 -
boolean replaceAll(List list,Object oldVal,Object newVal)
:使用新值替换 List 对象的所有旧值
public class CollectionsTest {
@Test
public void tets() {
Collection list = new ArrayList();
list.add(123);
list.add(140);
list.add(150);
Collections.reverse((List<?>) list);
System.out.println("原始数据"+list);
System.out.println("颠倒后"+list);
Collections.shuffle((List<?>) list);
System.out.println("随机排序"+list);
Collections.swap((List<?>) list,1,2);
System.out.println("交换元素后"+list);
// 自然排序:排序的类要实现Comparable接口,重写compareTo方法
LinkedList linkedList = new LinkedList();
linkedList.add(new User("Tom",12));
linkedList.add(new User("Jerry",14));
linkedList.add(new User("Kack",8));
linkedList.add(new User("Asia",15));
linkedList.add(new User("Bobo",9));
Collections.sort(linkedList);
System.out.println(linkedList);
}
@Test
public void test3() {
Comparator com = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User) {
User u1 = (User) o1;
User u2 = (User) o2;
return u1.getName().compareTo(u2.getName()); // name从小到大
}else
throw new RuntimeException("传入类型不一致");
}
};
// 定制排序
LinkedList linkedList = new LinkedList();
linkedList.add(new User("Tom",12));
linkedList.add(new User("Jerry",14));
linkedList.add(new User("Kack",8));
linkedList.add(new User("Asia",15));
linkedList.add(new User("Bobo",9));
Collections.sort(linkedList,com);
System.out.println(linkedList);
}
}
原始数据[150, 140, 123]
颠倒后[150, 140, 123]
随机排序[150, 140, 123]
交换元素后[150, 123, 140]
[User{name='Tom', age=12},
User{name='Kack', age=8},
User{name='Jerry', age=14},
User{name='Bobo', age=9},
User{name='Asia', age=15}]
// 定制排序
[User{name='Asia', age=15},
User{name='Bobo', age=9},
User{name='Jerry', age=14},
User{name='Kack', age=8},
User{name='Tom', age=12}]