七. 集合
-
集合是一个容器,可以容纳其他类型的数据(数组就是一个集合)
-
集合不能存储基本数据类型,也不能直接存储java对象(只能存储内存地址/引用)
-
java中每一个不同的集合,底层对应不同的数据结构。往不同的集合中存储元素相当于把数据放到了不同的数据结构中
-
所有的集合类和集合接口都在java.util包下
-
java中集合分为两大类:
- 单个方式存储元素,这一类集合中超级父接口:java.util.Collection
- 以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map
-
Collection集合继承关系
-
Iterable接口:表示可迭代、可遍历的,有iterator()方法
-
Collection接口继承/泛化(is a)Iterable接口,表示所有集合都是可迭代、可遍历的
-
Collection接口关联(has a)Iterator接口(集合的迭代器对象),Iterator接口有hasNext()、next()、remove()方法
-
List接口泛化Collection接口。List集合存储元素特点:有序可重复,存储的元素有下标(有序指存入和取出时顺序相同,List集合下标从0开始,以1递增;可重复指存储元素可以相同)
ArrayList
集合实现List接口,底层采用数组数据结构,非线程安全LinkedList
集合实现List接口,底层采用双向链表数据结构Vector
集合底层采用数组数据结构,是线程安全的(所有方法都有synchronized关键字修饰,但效率较低,保证线程安全有其他方案,Vector使用较少) -
Set接口泛化Collection接口。Set集合存储元素特点:无序不可重复(存入和取出时顺序不一定相同,Set集合中元素没有下标)
HashSet
集合实现Set接口。new HashSet集合时在底层实际上new了一个HashMap集合,向HashSet集合中存储元素实际上是存储到HashMap集合中。HashMap集合是一个哈希表数据结构SortedSet接口泛化Set接口。SortedSet集合存储元素特点:继承Set集合无序不可重复,但SortedSet集合中的元素可以自动排序,称为可排序集合
TreeSet
方法实现SortSet接口。TreeSet集合底层实际是TreeMap集合,TreeMap底层采用二叉树数据结构
-
-
Map集合继承关系
-
Map集合和Collection集合没有关系
Map集合以key和value键值对方式存储元素,key和value存储的是对象内存地址
所有Map集合的key都是无序不可重复的(Map集合的key和Set集合存储元素特点相同)
-
HashMap
类实现Map接口,HashMap集合底层是哈希表数据结构,非线程安全 -
Hashtable
类实现Map接口,底层是哈希表数据结构,是线程安全的(效率低,现在使用较少)Properties
类继承Hashtable类,也是线程安全的。Properties的key和value只支持String类型,Properties被称为属性类 -
SortedMap接口泛化Map接口。SortedMap集合存储key特点:无序不可重复,但会按照大小自动排序,称为可排序集合
TreeMap
类实现SortedMap接口,底层是二叉树数据结构,key可自动排序
-
1. Collection接口
-
没有使用“泛型”前,Collection可以存储Object的所有子类型;使用“泛型”后,Collection只能存储某个具体类型
-
Collection中的常用方法:
boolean add(Object e) 向集合中添加元素
int size() 获取集合中元素个数
void clear() 清空集合
Collection c = new ArrayList(); //多态
c.add(100); //自动装箱Integer
c.add(true);
c.add(new Object());
System.out.println("集合中元素个数:" + c.size()); //元素个数为3
c.clear(); //清空集合c
System.out.println("集合中元素个数:" + c.size()); //元素个数为0
boolean contains(Object o) 判断集合中是否包含某元素
底层调用equals()方法,集合中的元素要重写equals()方法
c.add("real");
c.add("heisenberg");
System.out.println(c.contains("heisenberg")); //true
System.out.println(c.contains("burger")); //false
//contains(Object o)方法底层调用equals()方法
Collection c = new ArrayList();
String s1 = new String("abc");
c.add(s1);
String s2 = new String("def");
c.add(s2);
String s3 = new String("abc");
System.out.println(c.contains(s3)); //s3.equals("abc")输出true
c.add(new Integer(20));
Integer i = new Integer(20);
System.out.println(c.contains(i)); //true
User u1 = new User("heisenberg");
c.add(u1);
User u2 = new User("heisenberg");
System.out.println(c.contains(u2)); //重写equals()方法前输出false,重写后为true
class User{
private String name;
public User(){}
public User(String s){
name = s;
}
}
boolean remove(Object o) 从集合中删除某元素
底层调用equals()方法
c.remove("real");
System.out.println(c.size()); //元素个数为1
Collection c1 = new ArrayList();
String s1 = new String("hello");
c1.add(s1);
String s2 = new String("hello");
c1.remove(s2);
System.out.println(c1.size()); //输出0
boolean isEmpty() 判断集合是否为空(集合中是否存在元素)
System.out.println(c.isEmpty()); //false
c.clear();
System.out.println(c.isEmpty()); //true
Object[] toArray() 把集合转换为数组(了解,使用较少)
c.add("100");
c.add("hello");
c.add(20);
Object[] obj = c.toArray();
for (int i = 0; i < obj.length; i++) {
System.out.println(obj[i]);
}
-
关于Collection集合遍历/迭代(Map集合不适用)
迭代器对象负责遍历/迭代集合中元素,初始时不指向第一个元素
迭代器Iterator中的boolean hasNext()方法:判断是否有更多元素,返回true表示还有元素可以迭代,返回false表示没有元素可以迭代
E next()方法:让迭代器前进一位,并且将指向的元素返回
Collection c = new ArrayList(); //有序
c.add("real");
c.add("heisenberg");
c.add(20);
c.add(new Object());
Iterator it = c.iterator(); //第一步:获取对象的迭代器对象Iterator
while (it.hasNext()){ //第二步:用迭代器对象迭代/遍历集合
System.out.println(it.next()); //依次调用集合元素的toString()方法并输出
}
Collection c = new ArrayList();
c.add(1);
c.add(2);
c.add(3);
c.add(1); //可重复
Iterator it = c.iterator();
while (it.hasNext()){
Object obj = it.next();
if (obj instanceof Integer){
System.out.println(obj); //输出1 2 3 1,说明存进去的是Integer类型数据
}
}
Collection c1 = new HashSet(); //无序不可重复
c1.add(10);
c1.add("heisenberg");
c1.add(20);
c1.add(10); //输入两个10,最后只输出一个10(只存进去一个)
Iterator it1 = c1.iterator();
while (it1.hasNext()){
System.out.println(it1.next()); //输出顺序与输入顺序不一定相同
}
集合结构改变必须重新获取迭代器(迭代器和集合状态必须一致)
迭代器的remove()方法可以同时删除迭代器和集合中元素
//以下代码报错:java.util.ConcurrentModificationException
Collection c = new ArrayList();
Iterator it = c.iterator();
c.add(1); //创建迭代器后改变了集合结构
while (it.hasNext()){
System.out.println(it.next());
}
//创建迭代器后使用remove()方法改变集合元素,同样报错(迭代器快照和原集合状态不同)
//调用Iterator的remove()方法则没问题(同时删除迭代器快照和原集合中的元素)
Collection c = new ArrayList();
c.add(1);
c.add(2);
c.add(3);
Iterator it = c.iterator();
while (it.hasNext()){
Object o = it.next();
//c.remove(o); 只在集合中删除,只输出1,后面报错
it.remove(); //正常输出1 2 3
System.out.println(o);
}
System.out.println(c.size()); //集合c已空
List mylist = new LinkedList();
mylist.add(1);
mylist.add(2);
mylist.add(3);
mylist.add(1, 20); //将20插入下标为1的位置
Iterator it = mylist.iterator();
while (it.hasNext()){
System.out.println(it.next()); //输出1 20 2 3
}
Object get(int index)方法:根据下标获取元素
Object firstObj = mylist.get(0);
System.out.println(firstObj); //输出1
for (int i = 0; i < mylist.size(); i++) { //List集合特有遍历方式
Object o = mylist.get(i);
System.out.println(o);
}
int indexOf(Object o)方法:获取指定对象第一次出现处的索引
System.out.println(mylist.indexOf(20)); //输出1
int lastIndexOf(Object o)方法:获取指定对象最后一次出现处的索引
mylist.add(3);
System.out.println(mylist.lastIndexOf(3)); //输出4
Object remove(int index)方法:删除指定下标位置的元素
mylist.remove(0);
System.out.println(mylist.size());
Object set(int index, Object element)方法:修改指定位置元素
mylist.set(0, "heisenberg"); //把下标为0的元素改为"heisenberg"
-
ArrayList集合
-
ArrayList集合初始化容量是10(底层创建长度为0的数组,添加第一个元素时初始化为10)
-
ArrayList集合底层是Object[]数组
-
ArrayList集合的扩容:
扩容后的容量是原容量的1.5倍(位运算右移一位)
优化:ArrayList底层是数组,尽可能少的扩容,事先预估元素个数
-
ArrayList集合使用最频繁(因为数组优点:查找、末尾添加元素效率高)
-
List list1 = new ArrayList(); //默认初始化容量10
//集合的size()方法获取的是当前集合中元素个数,不是获取集合容量
System.out.println(list1.size()); //输出0
List list2 = new ArrayList(20); //指定初始化容量20
list2.add(20);
System.out.println(list2.size()); //输出1
-
位运算符(>>、<<)
二进制右移一位:>> 1 二进制右移两位:>> 2
二进制左移一位:<< 1 二进制左移两位:<< 2
//左移一位相当于乘2,左移两位相当于乘2²……
System.out.println(10 << 1); //输出20
//右移一位相当于除以2,右移两位相当于除以2²……
System.out.println(10 >> 1); //输出5
System.out.println(11 >> 1); //还是5(最后一位扔掉了)
System.out.println(15 >> 2); //输出3
- ArrayList集合构造方法:ArrayList(Collection c)
Collection c = new HashSet();
c.add(10);
c.add(20);
c.add(30);
c.add(40);
List list3 = new ArrayList(c); //将HashSet集合转换为List集合
for (int i = 0; i < list3.size(); i++) {
System.out.println(list3.get(i));
}
-
链表数据结构
- 链表数据结构基本单元是节点Node
- 对于单向链表,任何一个节点Node都有两个属性:存储的数据和下一节点的内存地址
- 双向链表每个节点存储数据和上、下节点内存地址
- 链表优点:随机增删元素效率较高(不涉及大量元素位移)
- 链表缺点:查询效率低,每次查找都要从头节点开始遍历
- LinkedList集合没有初始化容量
-
Vector集合
- 底层也是数组,初始化容量为10
- Vector集合扩容后为原容量的两倍:10–>20–>40(ArrayList集合扩容后为原容量的1.5倍:10–>15,位运算)
-
集合工具类java.util.Collections可以将非线程安全的ArrayList转换为线程安全的
List mylist = new ArrayList();
Collections.synchronizedList(mylist);
public static void main(String[] args) {
Cat c = new Cat();
Bird b = new Bird();
List list01 = new ArrayList(); //不使用泛型
list01.add(c);
list01.add(b);
Iterator it = list01.iterator();
while (it.hasNext()){
//Animal a = it.next(); 编译报错,返回值类型为Object
Object obj = it.next();
if (obj instanceof Animal){ //需要强制类型转换
Animal a = (Animal)obj;
a.move();
}
}
//使用泛型List<Animal>表示List集合只允许存储Animal类型数据
List<Animal> mylist = new ArrayList<Animal>();
/*
Object o = new Object();
mylist.add(o); 编译报错
*/
mylist.add(c);
mylist.add(b);
//表示迭代器迭代的是Animal类型
Iterator<Animal> it0 = mylist.iterator();
while (it0.hasNext()){
Animal a = it0.next(); //直接返回Animal类型数据,不需要类型转换
a.move();
}
}
class Animal{
public void move(){
System.out.println("动物移动");
}
}
class Cat extends Animal{}
class Bird extends Animal{}
- JDK8新特性:自动类型推断机制(又称为钻石表达式)
List<Animal> list = new ArrayList<>(); //省略后面<>中的内容
- 自定义泛型
public class GenericTest03<E> { //<>中的内容是标识符,随便写。一般用E或T
//E代表Element;T代表Type
public void doSome(E e){
System.out.println(e);
}
public static void main(String[] args) {
GenericTest03<String> gt = new GenericTest03<>(); //指定String类型
gt.doSome("heisenberg");
GenericTest03<Integer> gt0 = new GenericTest03<>(); //指定Integer类型
gt0.doSome(256);
//不用泛型就是Object类型
}
}
- 增强for循环/foreach(JDK5新特性)
/*foreach语法格式:
for(元素类型 变量名 : 数组或集合){
System.out.println(变量名);
}*/
int[] a = {1, 2, 3, 4, 5};
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
for (int data : a){ //数组使用foreach
System.out.println(data);
}
List<String> list = new ArrayList<>();
list.add("real");
list.add("heisenberg");
//ArrayList三种遍历方式:
Iterator<String> it = list.iterator(); //1. 迭代器
while (it.hasNext()){
System.out.println(it.next());
}
for (int i = 0; i < list.size(); i++) { //2. get()方法+下标
System.out.println(list.get(i));
}
for (String s : list){ //3. foreach遍历
System.out.println(s);
}
Set<Integer> set = new HashSet<>();
set.add(10);
set.add(20);
set.add(30);
set.add(20);
set.add(20);
for (Integer it : set){
System.out.println(it); //输出20 10 30,无序不可重复
}
2. Map接口
- Map和Collection没有直接继承关系
- Map集合以键值对方式存储数据,key和value都是引用数据类型
- key起主导作用,value是key的附属品
-
java.util.Map接口常用方法:
V put(K key, V value) 向Map集合中添加键值对
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "heisenberg");
V get(Object key) 通过key获取value
String value = map.get(3);
System.out.println(value); //输出heisenberg
Collection< V > values() 获取Map集合中所有value,返回一个Collection集合
Collection<String> c = map.values();
for (String s : c){
System.out.println(s);
}
int size() 获取Map集合中键值对个数
System.out.println(map.size()); //输出3
V remove(Object key) 通过key删除键值对
map.remove(2);
System.out.println(map.size()); //输出2
boolean containsKey(Object key) 判断Map中是否包含某个key
//Contains方法底层调用equals方法,所以自定义类型必须重写equals方法
System.out.println(map.containsKey(3)); //true
System.out.println(map.containsKey(new Integer(3))); //true
boolean containsValue(Object vaule) 判断Map中是否包含某个value
System.out.println(map.containsValue("lisi")); //false
System.out.println(map.containsValue(new String("lisi"))); //false
void clear() 清空Map集合
map.clear();
System.out.println(map.size()); //0
boolean isEmpty() 判断Map集合中元素个数是否为零
System.out.println(map.isEmpty()); //true
Set< K > keySet() 获取Map集合所有key,返回一个Set集合(详见遍历)
Set<Map.Entry<K, V>> entrySet() 将Map集合转换为Set集合(详见遍历)
元素类型为Map.Entry<K, V>,Map.Entry是Map中的静态内部类
public class MyClass {
private static class InnerClass{
public static void m1(){
System.out.println("静态内部类m1方法执行");
}
public void m2(){
System.out.println("静态内部类实例方法执行");
}
}
class Test<E, T>{}
public static void main(String[] args) {
MyClass.InnerClass.m1(); //类名叫做MyClass.InnerClass
MyClass.InnerClass mi = new MyClass.InnerClass();
mi.m2();
Set<MyClass.Test<Integer, String>> set = new HashSet<>();
}
}
- Map集合的遍历
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "heisenberg");
//第一种方式:获取所有key,通过遍历key来遍历value
Set<Integer> keys = map.keySet();
Iterator<Integer> it = keys.iterator();
while (it.hasNext()){
Integer key = it.next();
String value = map.get(key);
System.out.println(key + "=" + value);
}
//foreach
for (Integer i : map.keySet()){
System.out.println(i + "=" + map.get(i));
}
//第二种方式:Set<Map.Entry<K, V>> entrySet()
//把Map集合转换为Set集合,Set集合元素类型为Map.Entry
Set<Map.Entry<Integer, String>> set = map.entrySet();
Iterator<Map.Entry<Integer, String>> it0 = set.iterator();
while (it0.hasNext()){
//遍历Set集合,每次取出一个node
Map.Entry<Integer, String> node = it0.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key + "=" + value);
}
//foreach,这种方式效率高,适合大数据量
for (Map.Entry<Integer, String> node : set){
System.out.println(node.getKey() + "=" + node.getValue());
}
public class HashMap {
Node<K, V>[] table; //一维数组
static class Node<K, V> { //静态内部类
final int hash; //哈希值
/*
哈希值是key的hashCode()方法的执行结果,哈希值通过哈希函数/算法可以转换为数组下标
hash值相同一定在同一个单向链表上,hash值不同也可能经过哈希算法后转换的数组下标相同(发生“哈希碰撞”)
*/
final K key; //存储到Map集合的key
V value; //存储到Map集合的value
Node<K, V> next; //下个节点的内存地址
}
}
-
map.put(key, value)实现原理:
- 先将key,value封装到Node对象中
- 底层调用k的hashCode()方法得到哈希值,通过哈希函数/算法转换为数组下标。如果下标位置上没有任何元素,则把Node添加到此位置;如果下标对应位置上有链表,则用k和链表上每一个key进行equals方法比较。如果所有equals方法返回都是false,那么新节点会被添加到链表末尾;如果有一个equals方法返回true,该节点的value会被覆盖。
-
value = map.get(key)实现原理:
先调用key的hashCode()方法得到哈希值,通过哈希算法转换为数组下标,如果该下标位置为空,返回null。如果该位置上有单向链表,则拿着key和单向链表上每个节点的key进行equals,如果所有结果为false,那么get方法返回null;只要有其中一个节点的key和参数key比较时返回true,get方法返回该节点的value值。
-
存取都是先调用hashCode()方法再调用equals()方法(当数组下标位置是null时不会调用equals)
-
哈希表的随机增删在链表上进行;查询只需要部分扫描,效率都很高
-
放在HashMap集合key部分的元素和HashSet集合的元素需要重写hashCode()和equals()方法
-
HashMap集合key部分元素无序不可重复:
无序:添加的元素不一定存放在哪个单向链表上
不可重复:equals方法保证,如果key已经存在,原value会被覆盖
-
如果将所有的hashCode()方法返回固定值,会导致哈希表变成单向链表,散列分布不均匀
如果所有hashCode()方法返回值都不同,哈希表变成一维数组,散列分布不均匀
散列分布均匀:假设有100个元素,10个单向链表,每个单向链表上有10个节点。重写hashCode()方法时要有技巧
-
HashMap集合的默认初始化容量是16,默认加载因子0.75(HashMap集合底层数组容量达到75%时开始数组扩容),HashMap集合初始化容量必须是2的倍数(为了达到散列均匀提高效率)
public class Student {
private String name;
//无参、有参构造,setter and getter
//重写hashCode()和equals()方法
}
public static void main(String[] args) {
Student s1 = new Student("zhangsan");
Student s2 = new Student("zhangsan");
//重写hashCode()方法前输出460141958,重写后输出-1432604525
System.out.println(s1.hashCode());
//重写hashCode()方法前输出1163157884,重写后输出-1432604525
System.out.println(s2.hashCode());
Set<Student> students = new HashSet<>();
students.add(s1);
students.add(s2);
//只重写equals()方法时输出2,都重写后输出1
System.out.println(students.size()); //hashCode()和equals()方法必须同时重写
}
-
JDK8之后,如果哈希表单向链表元素超过8个,单向链表变为红黑树。当红黑树的节点数量小于6时,会重新把红黑树变为单向链表
-
HashMap扩容:二进制左移一位(扩容后容量是原容量2倍)
-
HashMap集合key和value都可以是null,且只能有一个key为null
Hashtable的key和value都不能是null
Map map = new HashMap();
map.put(null, null);
System.out.println(map.size()); //输出1
map.put(null, 20);
System.out.println(map.size()); //输出1,
System.out.println(map.get(null)); //输出20
Map map0 = new Hashtable();
map0.put(null, 20); //NullPointerException
map0.put(20, null); //NullPointerException
-
Hashtable集合
- Hashtable和HashMap底层都是哈希表数据结构
- Hashtable初始化容量11,默认加载因子0.75f
- Hashtable集合扩容:原容量 * 2 + 1
-
Properties类
Properties继承Hashtable,key和value都是String类型,称为属性类对象
Properties pro = new Properties();
pro.setProperty("username", "root");
pro.setProperty("password", "123");
String username = pro.getProperty("username");
String password = pro.getProperty("password");
System.out.println(username + password);
TreeSet<String> ts = new TreeSet<>();
ts.add("d");
ts.add("a");
ts.add("c");
ts.add("b");
for (String s : ts){
System.out.println(s); //输出a b c d,升序排序
}
TreeSet<Integer> ts0 = new TreeSet<>();
ts0.add(10);
ts0.add(8);
ts0.add(3);
for (Integer i : ts0){
System.out.println(i); //输出3 8 10,升序
}
- 自定义类型排序方法一:实现Comparable类,重写compareTo方法
TreeSet<Person> ts1 = new TreeSet<>();
Person p1 = new Person("heisenberg");
ts1.add(p1); //编译报错:ClassCastException 原因:Person类没有实现Comparable接口
class Person{
private String name;
//无参、有参构造
}
public class TreeSetTest02 {
public static void main(String[] args) {
TreeSet<People> ts = new TreeSet<>();
People p1 = new People(20);
People p2 = new People(30);
ts.add(p1);
ts.add(p2);
for (People p : ts){
System.out.println(p);
}
}
}
class People implements Comparable<People>{
private Integer no;
//无参、有参构造
public int compareTo(People o) { //重写compareTo方法,按数字排序
return o.no - this.no; //降序
//return this.no - o.no; 升序
}
public String toString() {
return "no." + no;
}
}
- String已经重写了compareTo方法
public class TreeSetTest03 {
public static void main(String[] args) {
TreeSet<Vip> vips = new TreeSet<>();
vips.add(new Vip("abc", 20));
vips.add(new Vip("abd", 20));
vips.add(new Vip("xyz", 18));
for (Vip vip : vips){
System.out.println(vip); //输出xyz abc abd
}
}
}
class Vip implements Comparable<Vip>{
String name;
int age;
//有参、无参构造,toString方法
public int compareTo(Vip o) { //先按年龄升序,年龄相同再按姓名
if (this.age == o.age){
return this.name.compareTo(o.name); //调用String类compareTo方法
} else{
return this.age - o.age;
}
}
}
-
自定义类型排序方法二:使用比较器Comparator
Comparator接口的设计符合OCP原则(开闭原则)
public class TreeSetTest04 {
public static void main(String[] args) {
TreeSet<Bat> bats = new TreeSet<>(new BatComparator()); //传比较器进去
/*或者用匿名内部类
TreeSet<Bat> bats = new TreeSet<>(new Comparator<Bat>() {
public int compare(Bat o1, Bat o2) {
return o1.age - o2.age;
}
});
*/
bats.add(new Bat(50));
bats.add(new Bat(20));
bats.add(new Bat(25));
for (Bat bat : bats){
System.out.println(bat); //输出20 25 50
}
}
}
class Bat{
int age;
//toString方法、有参构造
}
class BatComparator implements Comparator<Bat>{ //比较器实现Comparator接口
public int compare(Bat o1, Bat o2) {
return o1.age - o2.age;
}
}
-
当比较规则不会改变或只有一个时,建议实现Comparable接口;比较规则有多个且切换频繁时,建议实现Comparator接口
-
自平衡二叉树
-
TreeSet/TreeMap是自平衡二叉树,遵循左小右大原则存放
-
二叉树前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
(前中后是指根的位置,左永远在右的左边)
-
TreeSet/TreeMap采用中序遍历方式(Iterator迭代器、左根右)
-
-
java.util.Collections:集合工具类
public class CollectionsTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.synchronizedList(list); //变成线程安全的
list.add("abc");
list.add("abb");
list.add("aba");
Collections.sort(list);
for (String s : list){
System.out.println(s);
}
List<Batman> bm = new ArrayList<>();
bm.add(new Batman(666));
bm.add(new Batman(20));
bm.add(new Batman(25));
Collections.sort(bm); //对list集合元素排序,元素必须实现Comparable接口
for (Batman b : bm){
System.out.println(b);
}
Set<String> set = new HashSet<>();
set.add("yxz");
set.add("xzy");
set.add("xyz");
List<String> l = new ArrayList<>(set); //Set集合排序:转换为List集合
Collections.sort(l);
for (String s : l){
System.out.println(s);
}
}
}
class Batman implements Comparable<Batman>{
int no;
//toString方法、有参构造
public int compareTo(Batman o) {
return this.no - o.no;
}
}