Java中的集合

1. 集合

1.1 为什么使用集合

开发中会使用大量相同数据类型的情况。如果使用数组来解决问题
1. 数组能够使用的方法非常少,功能方法需要程序员自己完成。
2. 数据类型单一化,不支持多种情况。
3. 数组容量不可以更改。

集合为解决问题而生:
1. 方法多种多样,基本功能完善
2. 数据类型支持多样化,但是又不失数据类型一致要求
3. 容量可以变,并且不用开发者操心

1.2 集合架构

Java中集合的【总接口】Collection。Java中所有和集合有关的内容,都是Collection接口的子接口或者实现类

interface Collection
–| interface List List接口,有序可重复
----| class ArrayList
【重点】可变长数组结构
原码实现,了解其中的特征,性能…
----| class LinkedList
【重点】双向链表结构
----| class Vector
【远古时代】JDK1.0 线程安全的ArrayList,如果不考虑线程安全问
题,建议使用ArrayList
–| interface Set Set接口,无序不可重复
----| HashSet 底层存储数据的方式是采用哈希表方式
----| TreeSet 底层存储数据的方式一个平衡二叉树方式

以上这些东西我们之后会一一讲解,现在我们先来了解一下collection接口的常用方法

1.3 Collection<E>接口下的常用方法

增:
boolean add(E e);
存入元素到当前集合对象中,这里要求的数据类型是E类型,也就是泛型对于
的具体数据类型
boolean addAll(Collection<? extends E> c);
class Dog extends Animal
class Cat extends Animal
class Tiger extends Animal
==> ? extends E 泛型的上限
要求存入的集合c中,存储的元素要么是E类型,要么是E类的子类
删:
void clear();
清空整个集合
boolean remove(Object obj);
删除集合中的指定元素
boolean removeAll(Collection<?> c);
删除两个集合的交集
boolean retainAll(Collection<?> c);
保留两个集合的交集
查:
int size();
返回集合中有效元素个数
boolean isEmpty();
判断当前集合是否为空
boolean contains(Object obj);
判断指定元素在当前集合中是否存在
boolean containsAll(Collection<?> c);
判断集合c是不是当前集合的子集合

以下是代码的实现:
注意导包,之前的部分代码也需要导包
就是这个:
import java.util.ArrayList;
import java.util.Collection;

public class Demo1 {
	public static void main(String[] args) {
		/*
		 * 因为Collection<E>是一个接口,接口没有自己的类对象
		 * 这里使用Collection接口的实现类来完成演示过程 ArrayList<E>
		 */
		Collection<String> c = new ArrayList<String>();
		
		c.add("82年的拉菲");
		c.add("82年的雪碧");
		c.add("82年的可乐");
		c.add("82年的老雪");
		
		System.out.println(c);
		
		Collection<String> c1 = new ArrayList<String>();
		
		c1.add("百威");
		c1.add("福佳白");
		c1.add("精酿啤酒");
		c1.add("修道院啤酒");
		
		c.addAll(c1);
		
		System.out.println(c);
		
		c.remove("82年的雪碧");
		System.out.println(c);
		
		//c.removeAll(c1);
		//System.out.println(c);
		
		//c.retainAll(c1);
		//System.out.println(c);
		
		System.out.println("size:" + c.size());
		System.out.println(c.isEmpty());
		
		System.out.println(c.contains("百威"));
		System.out.println(c.contains("哈尔滨"));
		
		System.out.println(c.containsAll(c1));
		
		c1.add("野格");
		System.out.println(c.containsAll(c1));
	}
}

1.4 迭代器

通过集合对象获取对应的Iterator迭代器
Iterator iterator();

常用方法:
boolean hasNext();
判断当前Iterator是否可以继续运行。
E next();
获取Iterator当前指向元素,并且指向下一个元素。
void remove();
删除
【注意】
1. remove方法有且只能删除通过next方法获取的元素
2. remove方法如果想要使用,必须紧挨着next方法

代码实现:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Demo1 {
	public static void main(String[] args) {
		Collection<String> c = new ArrayList<String>();
		
		c.add("星期一");
		c.add("星期二");
		c.add("星期三");
		c.add("星期四");
		c.add("星期五");
		c.add("星期六");
		c.add("星期日");
		
		System.out.println(c);
		
		/*
		 * 获取当前集合对应的Iterator迭代器对象
		 */
		Iterator<String> iterator = c.iterator();
		/*
		System.out.println("当前Iterator是否可以继续运行:" +iterator.hasNext());
		System.out.println("获取当Iterator指向元:" + iterator.next());
		System.out.println("获取当Iterator指向元:" + iterator.next());
		
		iterator.remove();
		System.out.println(c);
		iterator.remove();
		System.out.println(c);
		*/
		
		while (iterator.hasNext()) {
			iterator.next();
			iterator.remove();
		}
		
		System.out.println(c.isEmpty());
		
		
	}
}

1.5 Iterator使用注意问题

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Demo2 {
	public static void main(String[] args) {
		ArrayList<String> c = new ArrayList<String>();

		c.add("烤羊排");
		c.add("油焖大虾");
		c.add("土豆牛肉");
		c.add("黄焖鸡米饭");
		c.add("麻辣香锅");
		c.add("孜然肉片");
		c.add("酸汤肥牛");
		
		Iterator<String> iterator = c.iterator();
		
		/*
		 * ConcurrentModificationException
		 * Iterator在创建的过程中,会对整个集合所有元素打招呼,记录每一个元素位置。
		 * Iterator在执行next方法过程中,会按照初始条件一个一个遍历
		 * 当前集合通过remove方法,删除已经被Iterator记录的元素时,是有可能导致
		 * Iterator一脸懵逼!!!元素不见了!!!
		 * 
		 * 这里就会发生冲突!
		 * 
		 * 这里因为集合中元素,对于集合本身和当前Iterator而言是一个共享资源
		 * 不管是哪一方操作元素,都存在影响对方操作的情况。【共享资源冲突问题】
		 * 
		 * ArrayList存储元素不是连续的吗,土豆牛肉删除了,他的位置不是会被后面的元素顶上来吗

		 */
		while (iterator.hasNext()) {
			System.out.println(iterator.next());
			
			// 这里通过集合删除土豆牛肉元素
			// 后期代码中会出现很多相同名字方法,这里一定要注意!!!
			// 调用当前方法的是哪一个
			c.remove("酸汤肥牛");
		}
	}
}

Iterator出现并发错误原因:Iterator出现并发错误原因

1.6 List<E>

1.6.1 List集合接口特征和方法

特征:
有序,可重复

有序: 添加顺序和存储顺序一致
可重复:相同元素可以同时添加

List接口下的实现类,存在一定的下标操作机制
ArrayList 底层数组形式操作,可以通过下标直接访问
LinkedList 底层是一个双向链表结构,下标 ==> 计数器

特定的方法:
增:
add(E e);
List接口下,当前方法是添加元素到集合的末尾,尾插法

addAll(Collection<? extends E> c);
List接口下,当前方法是添加另一个集合到当前集合末尾,要求添加的
集合中保存的元素和当前集合保存元素一致,或者说是当前集合保存元
素的子类

add(int index, E e);
在指定的下标位置,添加指定元素

addAll(int index, Collection<? extends E> c);
在指定的下标位置,添加指定的集合,集合要求同上一个addAll方法

删:
void clear();
清空整个集合

remove(Object obj);
删除集合中的指定元素

removeAll(Colletion<?> c);
删除两个集合的交集

retainAll(Colletion<?> c);
保留两个集合的交集

E remove(int index);
删除集合中指定下标的元素。返回值是被删除的元素
改:
E set(int index, E e);
使用指定元素替换指定下标index的元素,返回值是被替换掉的元素。

查:
int size();
有效元素个数

boolean isEmpty();
判断当前集合是否为空

boolean contains(Object obj);

boolean containsAll(Collection<?> c);

int indexOf(Object obj);
找出指定元素在集合中的第一次出现位置

int lastIndexOf(Object obj);
找出指定元素在集合中最后一次出现位置

E get(int index);
获取指定下标的元素

List subList(int fromIndex, int endIndex);
获取当前集合的子集合
【特征】
获取数据的范围是 fromIndex <= n < endIndex
要头不要尾

1.7 ArrayList 可变长数组

特征:
数组形式的操作方式,查询效率高,但是删除,增加效率低。
数组:
Object类型数组

方法:
ArrayList使用的方法基本上都是从List接口中遵从实现的方法
特征:
ensureCapacity(int minCapacity);
判断当前容量是否足够
trimToSize();
截断整个数组容量 ==> size有效元素个数
时间换空间,空间换时间

自定义实现的ArrayList
Constructor构造方法

add(E e);
add(int index, E e);
addAll(自定义ArrayList e)
addAll(int index,自定义ArrayList e)
remove(Object obj);
remove(int index);
set(int index, E);
E get(int index);
int indexOf();
int lastIndexOf();
boolean contains(Object obj);
boolean containsAll(自定义ArrayList类型 list)
boolean isEmpty();
int size();
自定义ArrayList subList(int fromIndex, int endIndex);
Object[] toArray();

方法代码实现:

import java.util.Arrays;

/**
 * 自定义实现MyArraylist
 * @author Anonymous
 *
 * @param <E> 自定义泛型
 */
public class MyArrayList<E> {
	/**
	 * 准备一个底层数组,用于存储数据内容
	 */
	private Object[] elements;
	
	/**
	 * 初始化默认容量
	 */
	private static final int DEFAULT_CAPACITY = 10;
	
	/**
	 * 最大数组容量, -8是为了腾出一定的空间,保存数组的必要内容
	 */
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	
	/**
	 * 当前底层数组中保存的有效元素个数
	 */
	private int size = 0;
	
	/**
	 * 无参数构造方法,但是需要提供给用户一个初始化容量来保存必要的数据
	 */
	public MyArrayList() {
		elements = new Object[DEFAULT_CAPACITY];
	}
	
	/**
	 * 用户指定保存元素容量的初始化过程,要求用户指定的容量范围是有效的
	 * 
	 * @param initCapacity 用户指定的初始化容量,但是不能小于等于0 ,不能大于
	 * 						MAX_ARRAY_SIZE
	 */
	public MyArrayList(int initCapacity) {
		// 用户传入参数的合法性判断过程
		if (initCapacity < 0 || initCapacity > MAX_ARRAY_SIZE) {
			// 抛出异常
			// IllegalArgumentException 是一个RuntimeException运行时异常的子类
			// 不需要强制声明抛出异常
			throw new IllegalArgumentException("IllegalArgumentException : " + initCapacity);
		}
		
		elements = new Object[initCapacity];
	}
	
	/*
	 * 增加方法
	 */
	/**
	 * 添加元素到当前集合的末尾
	 * 
	 * @param e 要求是符合泛型约束的指定数据类型
	 * @return 添加成功返回true, 否则返回false
	 */
	public boolean add(E e) {
		// 直接调用在指定下标位置添加元素的方法,只不过这里指定下标位置就是
		// 尾插法下标位置
		return add(size, e);
	}
	
	/**
	 * 在底层数组的指定下标位置保存对应的元素
	 * 
	 * @param index 指定下标位置,不能超出有效范围,0<= index <= size
	 * @param e 符合泛型约束的数据类型
	 * @return 添加成功返回true, 否则返回false
	 */
	public boolean add(int index, E e) {
		if (index < 0 || index > size) {
			throw new ArrayIndexOutOfBoundsException(index);
		}
		
		ensureCapacity(size + 1);
		
		for (int i = size; i > index; i--) {
			elements[i] = elements[i - 1];
		}
		
		elements[index] = e;
		size += 1;
		
		return true;
	}
	
	/*
	 * addAll方法
	 * 		1. 需要得到添加集合中元素内容,有效元素个数
	 * 		2. 确认容量问题
	 * 		3. size = srcSize + newSize
	 */
	/**
	 * 添加另一个集合到当前集合的末尾
	 * 
	 * @param list MyArrayList类型,自定义ArrayList,要求存储元素和当前集合一致,或者
	 * 				是其子类
	 * @return 添加成功返回true,添加失败返回false
	 */
	public boolean addAll(MyArrayList<? extends E> list) {
		Object[] array = list.toArray();
		int newSize = array.length;
		
		ensureCapacity(size + newSize);
		
		for (int i = 0; i < newSize; i++) {
			elements[i + size] = array[i];
		}
		
		size += newSize;
		return true;
	}
	
	/**
	 * Do yourself 作业
	 * @param index
	 * @param list
	 * @return
	 */
	public boolean addAll(int index, MyArrayList<? extends E> list) {
		return true;
	}
	
	/**
	 * 删除指定元素 
	 * 
	 * @param obj 指定删除的元素
	 * @return 删除成功返回true
	 */
	public boolean remove(Object obj) {
		int index = indexOf(obj);
		
		return null != remove(index);
	}
	
	/**
	 * 删除下标元素
	 * 
	 * @param index 指定的下标范围
	 * @return 删除成功返回对应元素,失败返回null
	 */
	public E remove(int index) {
		if (-1 == index) {
			return null;
		}
		
		E e = get(index);
		
		for (int i = index; i < size - 1; i++) {
			elements[i] = elements[i + 1];
		}
		
		// 原本最后一个有效元素位置上的内容赋值为null
		elements[size - 1] = null;
		size -= 1;
		
		return e;
	}
	
	/**
	 * 获取集合中指定下标的元素
	 * 
	 * @param index 指定下标的范围,但是不能超出有效下标范围
	 * @return 返回对应的元素
	 */
	@SuppressWarnings("unchecked")
	public E get(int index) {
		if (index < 0 || index > size) {
			throw new ArrayIndexOutOfBoundsException(index);
		}
		
		return (E) elements[index];
	}
	
	/**
	 * 查询指定元素在集合中的第一次出现下标位置
	 * @param obj 指定的元素
	 * @return 返回值大于等于0表示找到元素,否则返回-1
	 */
	public int indexOf(Object obj) {
		int index = -1;

		for (int i = 0; i < size; i++) {
			// equals 判断对象是否一致地方的方法
			if (obj.equals(elements[i])) {
				index = i;
				break;
			}
		}
		
		return index;
	}
	
	/**
	 * 查询指定元素在集合中的最后一次出现下标位置
	 * 
	 * @param obj 指定的元素
	 * @return 返回值大于等于0表示找到元素,否则返回-1
	 */
	public int lastIndexOf(Object obj) {
		int index = -1;

		for (int i = size - 1; i >= 0; i--) {
			// equals 判断对象是否一致地方的方法
			if (obj.equals(elements[i])) {
				index = i;
				break;
			}
		}
		
		return index;
	}
	
	/**
	 * 返回MyArrayList集合中所有有效元素的Object数组
	 * 
	 * @return 包含所有集合元素的Object类型数组
	 */
	public Object[] toArray() {
		// size是有效元素个数,通过该方法可以获取到一个只有当前数组中有效元素的数组
		return Arrays.copyOf(elements, size);
	}
	
	
	/**
	 * 替换指定下标的元素
	 * 
	 * @param index 指定下标元素,但是必须在有效范围以内
	 * @param e 符合泛型约束的对应数据类型
	 * @return 被替换的元素
	 */
	public E set(int index ,E e) {
		if (index < 0 || index >= size) {
			throw new ArrayIndexOutOfBoundsException(index);
		}
		
		E temp = get(index);
		elements[index] = e;
		
		return temp;
	}
	
	/**
	 * 判断指定元素是否存在 
	 * 
	 * @param obj 指定元素
	 * @return 存在返回true,不存在返回false
	 */
	public boolean contains(Object obj) {
		return indexOf(obj) > -1; 
	}
	
	/**
	 * 
	 * @param list
	 * @return
	 */
	public boolean containsAll(MyArrayList<?> list) {
		return false;
	}
	
	/**
	 * 判断集合是否是空的
	 * 
	 * @return 如果为空,返回true, 否则返回false
 	 */
	public boolean isEmpty() {
		return size == 0;
	}
	
	/**
	 * 获取当前集合中有效元素个数
	 * 
	 * @return 有效元素个数
	 */
    public int size() {
    	return size;
    }
    
    /**
     * 获取当前集合的子集合,截取范围是fromIndex <= n < endIndex
     * 
     * @param fromIndex fromIndex <= endIndex 不得小于0
     * @param endIndex endIndex >= fromIndex 小于等于size
     * @return 截取得到的一个MyArrayList子集合对象
     */
    public MyArrayList<E> subList(int fromIndex, int endIndex) {
    	if (fromIndex > endIndex || fromIndex < 0 || endIndex > size) {
    		throw new ArrayIndexOutOfBoundsException();
    	}
    	
    	MyArrayList<E> listTemp = new MyArrayList<E>(endIndex - fromIndex);
    	
    	for (int i = fromIndex; i < endIndex; i++) {
			listTemp.add(this.get(i));
		}
    	
    	return listTemp;
    }

    @Override
    public String toString() {
    	String str = "[";
    	for (int i = 0; i < size - 1; i++) {
			str += elements[i] + ", ";
		}
    	
    	return str + elements[size - 1] + "]";
    	
    }
	/*
	 * 这里需要类内使用的可以用于判定当前容量是否满足添加要求的方法
	 * 如果满足直接进入添加模式,如果不满足,需要执行grow方法,完成
	 * 底层数组的扩容问题。
	 */
	/**
	 * 每一次添加元素,都需要进行容量判断,如果满足可以进行添加操作
	 * 不满足需要制定grow方法
	 * 
	 * @param minCapacity 要求的最小容量
	 */
	private void ensureCapacity(int minCapacity) {
		if (minCapacity > elements.length) {
			// 完成一个底层数组的扩容方法
			grow(minCapacity);
		}
	}
	
	
	/**
	 * 底层数组的扩容方法,原理是创建新数组,移植数据,保存新数组地址
	 * 
	 * @param minCapacity 要求的最小容量
	 */
	private void grow(int minCapacity) {
		// 1. 获取原数组容量
		int oldCapacity = elements.length;
		
		// 2. 计算得到新数组容量
		int newCapacity = oldCapacity + (oldCapacity >> 1);
		
		// 3. 判断新数组容量是否满足要求
		if (newCapacity < minCapacity) {
			newCapacity = minCapacity;
		}
		// 新数组容量是大于允许的最大数组容量
		if (newCapacity > MAX_ARRAY_SIZE) {
			// 二次判断minCapacity是否小于MAX_ARRAY_SIZE
			if (minCapacity < MAX_ARRAY_SIZE) {
				// 最小要求是不大于MAX_ARRAY_SIZE,代码可以运行
				newCapacity = minCapacity;
			} else {
				throw new OutOfMemoryError("Overflow MAX_ARRAY_SIZE");
			}
		}
		
		/*
		 * 4. 使用数组工具类方法完成操作
		 * Arrays.copyOf(源数据数组,可以是任意类型,采用泛型约束, 指定的新数组容量);
		 * 		a. 根据指定的新数组容量创建对应泛型数据类型的新数组
		 *      b. 从源数据数组中拷贝内容到新数组中
		 *      c. 返回新数组首地址
		 */
		elements = Arrays.copyOf(elements, newCapacity);
	}

}

1.8 ArrayList性能问题

增删慢

增加慢
1. 增加元素有可能出现调用grow方法,grow需要进行数组的扩容操作,操作过程中需要大
量的移动和拷贝过程,浪费时间
2. 在某一个指定位置添加元素,会导致从指定位置开始,之后的元素整体向后移动,涉及
移动复制操作,浪费时间。

删除慢:
1. 按照ArrayList可变长数组要求,删除元素之后,之后的内容都需要整体向前移动。

查询快

1.9 补充知识点【内存】

内存中的地址
1. 内存中的最小单元 字节
2. 计算机为了记录标记每一个字节的内存,给内存进行了编号
3. 航海中路60号,精确查询,唯一性
4. 计算机内存按照字节,一个字节对应一个地址,从0开始编号 到 34359738367 (32G
内存)
5. 计算机通过内存编号访问对应的内存,效率是非常高的!!!
6. 按照十进制表示地址编号,效果很差。 这里引入16进制 0x0 ~ 0x7 FFFF FFFF

null到底是谁?
null ==> 0x0 内存中编号为0的地址
该地址受到系统保护,任何程序读取,写入0x0地址,系统直接杀死程序
一般用于在开发中初始化引用数据类型的变量,利用null报错。NullPointerException
在这里插入图片描述

1.10 LinkedList

1.10.1 1 LinkedList特征

底层是一个双向链表
链子 自行车链子 船锚 手链

自行车链子
维修很方便,前断,后断,链接搞定!!! 找到损坏的位置,需要一个一个来

链表结构
1. 增删快
2. 查询很慢很慢很慢

  1. 存储数据,非连续空间。
  2. 数据之间通过引用连接,方便遍历和使用
  3. 遍历效率较低,数据非连续空间存储,需要通过引用跳转过程来完成
  4. 删除插入操作效率高,但是注意地址的转移和保存问题。
  5. LinkedList链表当中的操作其实大部分都是和C语言指针一个概念

双向链表结构图例

1.10.2 LinkedList需要了解的方法

LinkedList使用的方法,大部分都是从List接口中遵从实现的方法
但是有一些特征方法
addFirst(E e);
addLast(E e); ==> add(E e);

E getFirst();
E getLast();

removeFirst();
removeLast();

以上方法组合可以完堆栈队列操作

先进后出
弹夹
addLast(E e); E getLast(); removeLast();
队列
先进先出
addLast(E e); E getFrist(); removeFirst();

1.11 自定义完成MyLinkedList

1.11.1 节点

a. 向前的引用
b. 存储元素的引用
c. 向后的引用

class Node {
private Node prev;
private Node next;
private Object value;

// 构造方法存储元素,进入链接
}

1.11.2 管理节点的MyLinkedList

a. 链表头!!!
b. 当前链表中有多少个元素。

class MyLinkedList {
private Node first;
private int size;
}

要求实现的方法:
add(E e);
addFirst(E e);
addLast(E e);
E getFirst();
E getLast():
E get(int index);
removeFirst();
removeLast();
remove(int index);
remove(Object obj);
E set(int index, E);
size();

1.11.3 LinkedList实现

还需要补充的方法:
add(int index,E e);
addAll(MyLinkedList list);
addAll(int index, MyLinkedList list);
boolean isEmpty();
int indexOf(Object obj);
int lastIndexOf(Object obj);
boolean contains(Object obj);
boolean containsAll(MyLinkedList<?> list);
Object[] toArray();

1.12 Set集合

1.12.1 Set集合概述

特征:
无序,不可重复

无序:添加顺序和存储顺序不一致,【不代表有排序效果】
不可重复: 在一个Set集合中不能出现相同元素

interface Set
–| class HashSet 底层是哈希表存储数据
–| class TreeSet 底层存储数据是一个二叉树

1.12.2 HashSet

底层结构
在这里插入图片描述

import java.util.HashSet;

public class Demo2 {
	public static void main(String[] args) {
		HashSet<Person> hashSet = new HashSet<Person>();
		
		Person p1 = new Person(1, "寝室长", 10);
		Person p2 = new Person(2, "队长", 15);
		Person p3 = new Person(3, "宇哥", 2);
		Person p4 = new Person(4, "老康", -5);
		Person p5 = new Person(5, "港儿子", 11);
		
		/*
		 * 当前这里两个元素,ID一样 ==> hashCode值是一致,会通过底层哈希表运算
		 * 保存到同一个单元格位置。
		 * 这里会通过equals方法,比较两个对象是否一致,来决定是否能够保存。
		 * 如果两个对象一致,无法保存。
		 * 
		 * 期望每一个哈希表单元格内保存的数据是唯一
		 */
		Person p6 = new Person(6, "康爷", 8);
		Person p7 = new Person(6, "康牙", 10);
		
		
		hashSet.add(p4);
		hashSet.add(p6);
		hashSet.add(p3);
		hashSet.add(p5);
		hashSet.add(p2);
		hashSet.add(p1);
		hashSet.add(p7);
		
		System.out.println(hashSet);
	}
}
1.13.2 TreeSet
Tree树形结构

在这里插入图片描述

TreeSet存储方式

没有比较方式无法存储

Comparable接口使用

interface Comparable {
int compareTo(T t);
}

方法参数为T类型,由实现类遵从接口时约束,
compareTo方法,返回值类型int类型,0, 负数,正数
0 表示两个元素一致,如果在TreeSet中比较结果为0,表示同一个元素,无法存储第二个。

Comparable接口由存储元素对应的类遵从,完成该方法

Comparator接口使用

interface Comparator {
int compare(T o1, T o2);
}

需要完成一个自定义比较器类对象,
int 返回值 0,负数,正数
0 表示两个元素一致,如果在TreeSet中比较结果为0,表示同一个元素,无法存储第二个。
Comparator使用要高于Comparable使用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章