集合
集合类是 Java 数据结构的实现,提供一种存储空间可变的数据模型,容量可以随时发生改变。集合类是 java.util 包中的重要内容,它允许以各种方式将元素分组,并定义了各种使这些元素更容易操作的方法。集合类是可以往里面保存多个对象的类,存放的是对象,不同的集合类有不同的功能和特点,适合不同的场合,用以解决一些实际问题。
Java 中有许多种集合类,比如单列的 Collection 接口和双列的 Map 接口。其中 Collection 接口中又包含元素可重复的 List 接口和不可重复的 Set 接口。之前学习的 ArrayList 就是 List 的一个实现类。
Collection 接口与迭代器
想要实例化 Collection,必须借助多态的方式:
Collection<Class> c = new ArrayList<Class>();接下来给出一些常用的方法:
| 方法名 | 说明 |
|---|---|
| boolean add(Element e) | 添加元素 |
| boolean remove(Object o) | 移除指定元素 |
| void clear() | 清空元素 |
| boolean contains(Object o) | 判断集合中是否存在指定元素 |
| boolean isEmpty() | 判断集合是否为空 |
| int size() | 返回集合的长度 |
演示:
import java.util.ArrayList;
import java.util.Collection;
public class Demo {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("Fxxk U");
c.add("Fxxk Me");
c.add("Fxxk Him");
System.out.println("列举元素:" + c);
// 判断是否真的去除成功了(输出 boolean 值)
System.out.println("是否去除成功:" + c.remove("Fxxk Her"));
c.remove("Fxxk Him");
// 判断集合中是否包含指定元素
// public boolean contain(Object o)
System.out.println("是否包含:" + c.contains("Fxxk U"));
// 输出现在集合的长度和它的元素
System.out.println("集合长度:" + c.size() + ",再列举元素:" + c);
// 清空集合并验证
c.clear();
System.out.println("是否为空:" + c.isEmpty());
}
}输出:
列举元素:[Fxxk U, Fxxk Me, Fxxk Him]
是否去除成功:false
是否包含:true
集合长度:2,列举元素:[Fxxk U, Fxxk Me]
是否为空:true当我们想要对集合中的元素进行遍历时,需要引入迭代器(iterator, /ɪtə'reɪtə/),一种用于访问集合的方法。别忘记导包。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Demo {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("Fxxk U");
c.add("Fxxk Me");
c.add("Fxxk Him");
// 依据集合 c 的 iterator() 方法建立 String 型迭代器
Iterator<String> istr = c.iterator();
// 是否存在下一个元素
while (istr.hasNext()) {
// 输出“下一个”元素
System.out.println(istr.next());
}
}
}输出:
Fxxk U
Fxxk Me
Fxxk Him在使用迭代器遍历集合时,不可进行导致集合尺寸变化的行为,否则会触发并发修改异常。
案例:人物信息遍历
定义 Character 类,包含有人物的基本信息。利用迭代器,将 Character 的集合遍历输出。
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Demo {
public static void main(String[] args) {
Collection<Character> c = new ArrayList<Character>();
c.add(new Character("胡桃", "璃月", 16));
c.add(new Character("可莉", "蒙德", 8));
c.add(new Character("绫华", "稻妻", 16));
Iterator<Character> istr = c.iterator();
while (istr.hasNext()) {
System.out.println(istr.next());
}
// 重置迭代器,再单独输出一遍 name
istr = c.iterator();
while (istr.hasNext()) {
System.out.println(istr.next().name);
}
}
}
class Character {
String name, birthPlace;
int age;
public Character() {
}
public Character(String name, String birthPlace, int age) {
this.name = name;
this.birthPlace = birthPlace;
this.age = age;
}
public String toString() {
return name + ":" + birthPlace + "人," + age + "岁。";
}
}输出:
胡桃:璃月人,16岁。
可莉:蒙德人,8岁。
绫华:稻妻人,16岁。
胡桃
可莉
绫华List 接口与列表迭代器
List 类为有序集合,亦称列表,相比于 Collection 类多了索引的概念。 List 类允许重复的元素。
接下来介绍一下 List 类特有的一些方法:
| 方法名 | 说明 |
|---|---|
| void add(int index, Element e) | 在集合的指定位置插入指定元素 |
| Element remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
| Element set(int index, Element e) | 修改指定索引处的元素,返回被删除的元素 |
| Element get(int index) | 返回指定索引处的元素 |
import java.util.ArrayList;
import java.util.List;
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
// 两个不一样的 add()
list.add("雷泽");
list.add(0, "香菱");
System.out.println(list);
// 删改
list.remove(1);
list.set(0, "重云");
System.out.println(list);
// 调取指定元素
System.out.println(list.get(0));
}
}输出:
[香菱, 雷泽]
[重云]
重云列表有自己独有的迭代器,叫做 ListIterator 接口,继承自 Iterator 接口。它允许向任意方向遍历列表,并在遍历期间修改列表。
它包含下列常见方法:
| 方法名 | 说明 |
|---|---|
| Element next() | 返回迭代中的下一元素 |
| boolean hasNext() | 返回迭代具有更多元素的布尔值 |
| Element previous() | 返回迭代中的上一元素 |
| boolean hasPrevious() | 返回反向迭代具有更多元素的布尔值 |
| void add(Element e) | 插入指定元素到列表的当前迭代到的位置 |
演示:
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("雷泽");
list.add("香菱");
list.add("重云");
// 正向遍历
ListIterator<String> li = list.listIterator();
while (li.hasNext()) {
String thisOne = li.next();
if (thisOne.equals("雷泽"))
li.add("可莉");
if (thisOne.equals("重云"))
li.set("行秋");
System.out.println(thisOne);
}
System.out.println("--------");
// 反向遍历
while (li.hasPrevious()) {
System.out.println(li.previous());
}
}
}输出:
雷泽
香菱
重云
--------
行秋
香菱
可莉
雷泽由上可见,在迭代时修改的元素,在本次迭代中是不会呈现的。
下面演示一段错误的代码:
// 前文省略
ListIterator<String> li = list.listIterator();
while (li.hasNext()) {
li.add("可莉");
System.out.println(li.next());
}输出:
Exception in thread "main" java.lang.IllegalStateException
at java.base/java.util.ArrayList$ListItr.set(ArrayList.java:1053)
at Demo.main(Demo.java:15)这里产生了 IllegalStateException 异常。通过追踪,发现该异常在 java.util.ArrayList$ListItr.set() 中被抛出。
if (lastRet < 0)
throw new IllegalStateException();抛出条件是 lastRet < 0,追踪发现该变量在 ArrayList$Itr 开头定义:
int lastRet = -1;也就是说,只要在迭代中,lastRet 就是 -1,一用 set() 就一定会抛出异常。但为什么在调用过 next() 方法后再用 set() 就没问题呢?下面是 next() 在 ArrayList$Itr 中的实现:
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor; // 对象初始化令 cursor=0,故 i=0
if (i >= size) // 这接下来一大段都不用看
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i]; // lastRet被赋予 i 的值,1
}所以,我们发现,想要修改列表元素,需要先调取(next())才能修改(set())。
增强 for 语句
增强 for 语句,用于简化数组和集合的遍历,其实质就是迭代器。下面是它的模板:
for (ElementOfArrayOrCollection e : arraylike) {
// e means the ?th element of array or collection
}演示:
public class Demo {
public static void main(String[] args) {
String[] arr = { "1不是1", "2不是2", "3不是3" };
for (String whoCaresTheName : arr) {
// whoCaresThename会不断迭代
System.out.println(whoCaresTheName);
}
}
}输出:
1不是1
2不是2
3不是3数据结构初步
栈与队列:
栈(Stack)是一种数据先进后出的模型,本质是一种运算受限的线性表(只能在表尾(栈顶)进行操作)。数据进入栈模型的过程称为压栈/进栈(Push),离开的过程称为弹栈/出栈(Pop)。第一个进入的数据称为栈底元素,最后进入的数据称为栈顶元素。若有 ABCD 四个数据进栈,则只能以 DCBA 的方式出栈。
队列(Queue)是一种数据先进先出的模型,本质也是一种运算受限的线性表,但与栈有所不同(只允许在表的前端(Front)进行删除操作,而在表的后端(Rear)进行插入操作。数据从后端进入队列模型的过程称为入队列,从前端离开的过程称为出队列。若有 ABCD 四个数据入队列,则以 ABCD 的方式出队列。
数组与链表:
数组(Array)在内存中具有连续的一片空间,可使用索引(Index)查询数据。查询任意数据耗时都相同,查询效率高。增删数据时会影响之后的所有元素,增删效率低。
链表(Linked List)是一种物理存储单元上非连续、非顺序的存储结构,可以充分利用计算机内存空间,但失去了数组随机读取的优点。查询数据时,无论是依据“索引”还是数据内容,都需要从头(Head)遍历,查询效率低。增删数据只需要改动结点中存储的地址值,增删效率高。
LinkedList 类
在前面,我们学习了 ArrayList ,它是 List 的实现类,这里的 LinkedList 也是 List 的实现类。不同之处在于,它的底层数据结构是链表,而 ArrayList 底层数据结构是数组。除此之外,两者没有太大的区别。我们在需要使用列表的时候,可以按照需要(查询优先或增删优先)选用两者其一。
接下来了解一下 LinkedList 的一些特有方法:
| 方法名 | 说明 |
|---|---|
| void addFirst(Element e) | 在列表开头插入指定元素 |
| void addLast(Element e) | 将指定元素追加到列表末尾 |
| Element getFirst() | 返回列表中的第一个元素 |
| Element getLast() | 返回列表中的最后一个元素 |
| Element removeFirst() | 从此列表中删除并返回第一个元素 |
| Element removeLast() | 从此列表中删除并返回最后一个元素 |
演示:
import java.util.LinkedList;
public class Demo {
public static void main(String[] args) {
LinkedList<String> ll = new LinkedList<String>();
// 花式添加
ll.add("巴巴托斯");
ll.addFirst("摩拉克斯");
ll.addLast("巴尔泽布");
System.out.println(ll);
// 获取和删除
System.out.println(ll.getFirst());
ll.removeFirst();
System.out.println(ll.getFirst());
}
}输出:
[摩拉克斯, 巴巴托斯, 巴尔泽布]
摩拉克斯
巴巴托斯Set 接口
Set 接口位于 java.util.Set 。相较于 List ,它是一个不包含重复元素的集合,且没有带索引的方法。想要获取 Set 的对象,需要用子类实现,这里介绍 HashSet 。
HashSet 由 HashMap (哈希表)支持,对集合的迭代顺序不做保证,且不保证列表在一段时间内保持不变。下面演示 Set 的应用:
import java.util.HashSet;
import java.util.Set;
public class Demo {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
// 依次添加三个不同元素
set.add("114514");
set.add("1919810");
set.add("Yarimasunei!");
// 再次添加已经存在过的元素
set.add("114514");
// 检测
System.out.println(set);
}
}输出:
[1919810, Yarimasunei!, 114514]那为什么 HashSet 会有这样的特性呢?先讲元素唯一性:需要先了解哈希值的概念:JDK 根据对象的地址或者字符串或者数字算出来的 int 类型的数值。在 Object 类中,有方法 hashCode() 可以返回对象的哈希值。
一个对象的哈希值在内容未被更改的情况下是恒定不变的。在不特意重写 hashCode() 的情况下,两个内容完全相同的对象不会拥有相同的哈希值,除非发生碰撞。字符串由于特有常量池,所以两个相同内容的 String 类对象应该有着同样的哈希值。当然,两个不相同的对象也有可能有着同样的哈希值,这就好比 3 * 2 = 1 * 6。
如果向一个 HashSet 中加入两个哈希值相同但内容不同的对象该怎么办呢?系统会再用 equals() 方法进一步比较。因而这点不用担心。
然后是无序性:与哈希表有关。JDK 8 之前,底层采用数组 + 链表的方式实现哈希表,即以链表为元素的数组。 HashSet 中的元素依照各自的哈希值按照一定规律排序,所以会扰乱用户指定的顺序。
如果想要保留特定的顺序,应当使用 LinkedHashMap ,它额外使用了一层链表来确保顺序(形同队列):
import java.util.LinkedHashSet;
public class Demo {
public static void main(String[] args) {
LinkedHashSet<String> lhs = new LinkedHashSet<>();
lhs.add("114514");
lhs.add("1919810");
lhs.add("Yarimasunei!");
lhs.add("114514");
System.out.println(lhs);
}
}输出:
[114514, 1919810, Yarimasunei!]此外,你还可以使用 TreeSet ,它能实时的自动排序集合内的元素。
import java.util.TreeSet;
public class Demo {
public static void main(String[] args) {
// 无参构造,默认自然排序
TreeSet<Integer> lhs = new TreeSet<>();
lhs.add(114514);
lhs.add(10086);
lhs.add(1919);
for (Integer i : lhs)
System.out.println(i);
}
}输出:
1919
10086
114514案例:悲情回忆录
咱的罗曼史非常的不幸......但好在有时可以回想起她们的一些话语碎片,作为精神慰籍,尽管也不一定是什么好话(抖 M 是吧)......用 TreeSet 集合存储这种信息,并单一按照姓名升序排序信息,最后合理的输出。
import java.util.TreeSet;
public class Demo {
public static void main(String[] args) {
TreeSet<BigMiss> lhs = new TreeSet<>();
/*
* 也可以使用带参构造,并重写 Comparator 接口中的 compare() 方法
* TreeSet<BigMiss> lhs = new TreeSet<>(new Comparator<BigMiss>() {
* public int compare(BigMiss miss1, BigMiss miss2) {
* return miss1.name.compareTo(miss2.name);
* }
* });
*/
lhs.add(new BigMiss("Duan Mingxuan", 19) {
@Override
public void tsundere() {
System.out.println("我,我没有委屈,就是想哭放松一下......");
}
});
lhs.add(new BigMiss("Liu Xingrui", 19) {
@Override
public void tsundere() {
System.out.println("这道题我不会呢?你会的吧?");
}
});
lhs.add(new BigMiss("Dai Qingtong", 19) {
@Override
public void tsundere() {
System.out.println("你别再找我了,咱俩不合适。");
}
});
for (BigMiss thisMiss : lhs) {
System.out.print(thisMiss.name + "(" + thisMiss.age + "岁):");
thisMiss.tsundere();
}
}
}
// 由于目标类包含多个属性,需要重写 compareTo() 定义新的规则,所以必须实现 Comparable 接口
abstract class BigMiss implements Comparable<BigMiss> {
String name;
int age;
public BigMiss(String name, int age) {
this.name = name;
this.age = age;
}
public abstract void tsundere();
@Override
public int compareTo(BigMiss o) {
// 完全依照姓名来升序排序
return this.name.compareTo(o.name);
}
}输出:
Dai Qingtong(19岁):你别再找我了,咱俩不合适。
Duan Mingxuan(19岁):我,我没有委屈,就是想哭放松一下......
Liu Xingrui(19岁):这道题我不会呢?你会的吧?男同学们,一定要好好学技术,不要沦落为女人的玩物!投资自己,做 sigma 男人!
泛型
泛型的本质上将类型参数化,在使用时再传入具体的类型。这种参数类型可被用于类、接口和方法。
// 指定一种类型的格式
<Type1>
// 指定多种类型的格式
<Type1, Type2>比如 ArrayList ,如果未给定泛型,则:
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
/*
* ArrayList lhs = new ArrayList();
* 相当于:
* ArrayList<Object> lhs = new ArrayList<>();
* 或
* ArrayList<?> lhs = new ArrayList<>();
* 但后者不能添加元素。
*/
ArrayList lhs = new ArrayList();
lhs.add(114514);
lhs.add(1919.810);
lhs.add("Yarimasunei!");
lhs.add(true);
for (Object i : lhs)
System.out.println(i);
}
}输出:
114514
1919.81
Yarimasunei!
true为了接受特定类型的参数,且不在编写程序时指定,我们引入泛型的概念,让用户来指定。举例一个常见的使用场景:你编写了一个冒泡排序方法,但是由于参数(数组的类型)可能会五花八门,你不得不重写很多遍,非常的麻烦。而此时如果使用泛型......
import java.util.Arrays;
public class Demo {
public static void main(String[] args) {
Integer[] a = { 1, 1, 4, 5, 1, 4 };
Double[] b = { 114.514, 1919.810, -3.14 };
String[] c = { "充Q币!", "为什么不充!", "你奶奶滴!" };
Boolean[] d = { false, true, false, true };
bubbleSort(a);
bubbleSort(b);
bubbleSort(c);
bubbleSort(d);
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(b));
System.out.println(Arrays.toString(c));
System.out.println(Arrays.toString(d));
}
/*
* 未被“定义”的 Type 现在被视为接口。
* 为了实现 compareTo() 方法,需要继承 Comparable 接口。
* 但这种继承不是普通的继承,此时认为 Comparable 不仅是 Type 的终极父类,
* 还能够享受 Comparable 的所有实现类(子类)提供的成员。
* 相反的,如果要使 Comparable 作为下限,即 Type 能继承其与其所有父类,
* 则可写 <Type super Comparable>。这也与我们熟知的继承有着相同的含义。
*/
public static <Type extends Comparable> void bubbleSort(Type[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j + 1].compareTo(arr[j]) < 0) {
Type temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}输出:
[1, 1, 1, 4, 4, 5]
[-3.14, 114.514, 1919.81]
[为什么不充!, 你奶奶滴!, 充Q币!]
[false, false, true, true]可以看到,本来要重写很多次的方法,用一个泛型介入,就省去了很多工作量。上面是泛型方法的应用。如果我们要对多个方法批量使用泛型介入,则应当使用泛型类/泛型接口。
public class RevolutionaryList<AnyType> {
public void show(AnyType info) {
System.out.println(info);
}
}public interface PathbreakingList<AnyAnyType> {
void show(AnyAnyType info);
}可变参数
在一些时刻,我们想要根据需求向方法传入不定数量的参数,由此引入可变参数的概念。
下面是基本格式:
public void function(int... a) {
// a 变成了含传入的若干个 int 变量的数组
// 可用 forEach 语句进行操作
}比如说要编写方法,将传入的若干个数值相加,并返回和。
public class Demo {
public static void main(String[] args) {
System.out.println(sumUp((Integer) 1, (Integer) 2, (Integer) 3));
System.out.println(sumUp((Double) 1.1, (Double) 1.2, (Double) 1.3));
}
public static <Type extends Number> String sumUp(Type... a) {
double sum = 0;
for (Type i : a)
sum += i.doubleValue();
return String.format("%.1f", sum);
}
}输出:
6.0
3.6Map 接口
Map 接口的形式:
interface Map<Key, Value>Map 将 Key 映射到 Value 的对象; Map 不包含重复的 Key;每个 Key 可以映射最多一个 Value。
Map 有实现类 HashMap ,也就是之前提到的哈希表。我们来看看它的使用特点:
import java.util.HashMap;
public class Demo {
public static void main(String[] args) {
// 以长整型类为 Key,对应它们各自的字符串为 Value
HashMap<Long, String> hm = new HashMap<>();
// 对 Map 添加元素,需要使用 put(Key k, Value v)
hm.put(114514L, "田所浩二");
hm.put(100000L, "田所浩二");
System.out.println(hm);
hm.put(114514L, "田所浩八");
hm.put(100001L, "田所浩八");
System.out.println(hm);
}
}输出:
{100000=田所浩二, 114514=田所浩二}
{100001=田所浩八, 100000=田所浩二, 114514=田所浩八}可见 Map 的元素也对集合的迭代顺序不做保证,并且元素唯一性只取决于 Key。当有同 Key 不同 Value 的多个元素被放进集合里时,新 Value 总会取代旧 Value。
接下来我们来了解一下 Map 的常用方法:
| 方法名 | 说明 |
|---|---|
| Value put(Key k, Value v) | 添加元素,返回被替换的 value |
| Value remove(Object key) | 删除 key 对于的 value,返回被删除的 value |
| void clear() | 清空集合 |
| boolean containsKey(Object key) | 返回集合包含 key 的布尔值 |
| boolean containsValue(Object value) | 返回集合包含 value 的布尔值 |
| boolean isEmpty() | 返回集合为空的布尔值 |
| int size() | 返回集合的长度 |
| Value get(Object key) | 返回 key 对应的 value |
| Set<Key> keySet() | 获取所有 key 的集合 |
| Collection<Value> values() | 获取所有 value 的集合 |
| Set<Map.Entry<Key, Value>> entrySet() | 获取所有 key-value 对的集合 |
重点应用最后四个方法:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Demo {
public static void main(String[] args) {
HashMap<String, String> couple = new HashMap<>();
// 三对前受后攻(意味深
couple.put("远野", "田所");
couple.put("德川", "我修院");
couple.put("铃木悠太", "高槻律");
// 一种遍历 HashMap 的方案
Set<String> zero = couple.keySet();
for (String name : zero)
System.out.println(couple.get(name) + " x " + name);
// 另一种遍历 HashMap 的方案
Set<Map.Entry<String, String>> cps = couple.entrySet();
// 在 forEach 语句中,泛型可不加
for (Map.Entry cp : cps)
// Map.Entry 提供了两个方法 getKey() 和 getValue()
System.out.println(cp.getValue() + " x " + cp.getKey());
}
}输出:
高槻律 x 铃木悠太
田所 x 远野
我修院 x 德川
高槻律 x 铃木悠太
田所 x 远野
我修院 x 德川案例:后宫拓扑
用 ArrayList 包裹 HashMap ,存储几部热门后宫动漫的女主(Key)和男主(Value)的名字,并合理地输出。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
public class Demo {
public static void main(String[] args) {
ArrayList<HashMap<String, String>> animes = new ArrayList<>();
HashMap<String, String> dateALive = new HashMap<>(),
swordArtOnline = new HashMap<>(),
monsterMusumenoIruNichijou = new HashMap<>();
// 约会大作战
dateALive.put("夜刀神十香", "五河士道");
dateALive.put("鸢一折纸", "五河士道");
dateALive.put("五河琴里", "五河士道");
animes.add(dateALive);
// 刀剑神域
swordArtOnline.put("结城明日奈", "桐谷和人");
swordArtOnline.put("绫野珪子", "桐谷和人");
swordArtOnline.put("筱崎里香", "桐谷和人");
animes.add(swordArtOnline);
// 魔物娘的同居日常
monsterMusumenoIruNichijou.put("米娅", "来留主公人");
monsterMusumenoIruNichijou.put("帕比", "来留主公人");
monsterMusumenoIruNichijou.put("赛尔特蕾娅", "来留主公人");
animes.add(monsterMusumenoIruNichijou);
// 遍历输出
for (HashMap anime : animes) {
Set<String> girls = anime.keySet();
for (String girl : girls)
System.out.println(girl + " -> " + anime.get(girl));
System.out.println("--------------------");
}
}
}输出:
五河琴里 -> 五河士道
夜刀神十香 -> 五河士道
鸢一折纸 -> 五河士道
--------------------
结城明日奈 -> 桐谷和人
筱崎里香 -> 桐谷和人
绫野珪子 -> 桐谷和人
--------------------
帕比 -> 来留主公人
赛尔特蕾娅 -> 来留主公人
米娅 -> 来留主公人
--------------------案例:统计字符串内各字符出现的次数
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Scanner;
public class Demo {
public static void main(String[] args) {
// 获取字符串
Scanner sc = new Scanner(System.in);
String str = sc.nextLine();
sc.close();
// Character 表示每个字符,Integer 表示出现的次数
HashMap<Character, Integer> countTable = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
// 如果没有特定 key,就新建“次数”为 1 的元素
if (countTable.get(str.charAt(i)) == null)
countTable.put(str.charAt(i), 1);
else {
countTable.put(
str.charAt(i), countTable.get(str.charAt(i)) + 1);
}
}
// 将 key 的集合转入有序集合中,便于排序
ArrayList<Character> chars = new ArrayList<>(countTable.keySet());
Collections.sort(chars);
// 美观地输出
for (Character thisChar : chars)
System.out.println("[" + thisChar + "]出现了" + countTable.get(thisChar) + "次");
}
}输入:
nnnd, wsmbc?输出:
[ ]出现了1次
[,]出现了1次
[?]出现了1次
[b]出现了1次
[c]出现了1次
[d]出现了1次
[m]出现了1次
[n]出现了3次
[s]出现了1次
[w]出现了1次Collections 类
Collections 类中包含许多对集合操作的静态方法。
我们给出几个常用方法:
| 方法名 | 说明 |
|---|---|
| static <T extends Comparable<? super T>> void sort(List<T> l) | 将指定的列表升序排序 |
| static void reverse(List<?> l) | 反转指定列表中元素的顺序 |
| static void shuffle(List<?> l) | 使用默认的随机源随机排列指定的列表 |
精选案例:斗地主发牌器
package com.penyo.chinesepoker;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.TreeSet;
public class Poker {
/*
* 这里用三种集合存储扑克信息。
* LinkedHashMap 保存“新牌”,ArrayList 保存“拆封牌”,TreeSet 保存“到手牌”。
* String 表示牌名,Integer 表示伪权重(真权重需要除以4)。
* TreeSet 的比较器被重写,单一依照伪权重降序排序。
*/
private LinkedHashMap<String, Integer> poker = new LinkedHashMap<>();
private ArrayList<String> pokerName;
private TreeSet<String> yourPoker = new TreeSet<>(new Comparator<String>() {
public int compare(String card1, String card2) {
return poker.get(card2) - poker.get(card1);
}
});
// 与洗牌相关的一些变量
private boolean isShufflePattern = true, isShuffled = false;
// 无参构造器,只进行初始化
public Poker() {
initialize();
}
// 带参构造器控制是否要在一开始就洗牌
public Poker(boolean needShuffle) {
initialize();
if (needShuffle)
shuffle();
}
// 产生一副“新牌”并“拆开”
public void initialize() {
String[] values = { "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2" },
suits = { "黑桃", "红桃", "方块", "梅花" };
int weight = 0;
for (String value : values)
for (String suit : suits)
poker.put(suit + value, weight++);
// B-Joker 即“小鬼”,R-Joker 即“大鬼”
poker.put("B-Joker", weight);
weight += 4;
poker.put("R-Joker", weight);
pokerName = new ArrayList<>(poker.keySet());
}
// 洗洗更健康
public void shuffle() {
if (isShufflePattern)
Collections.shuffle(pokerName);
isShuffled = true;
}
// 设置洗牌/不洗牌玩法
public void setShufflePattern(boolean isShufflePattern) {
this.isShufflePattern = isShufflePattern;
}
// 发牌,seatCode = 0 或 1 或 2 表示地主或两个农民
public TreeSet getPoker(boolean needShuffle, int seatCode) {
if (needShuffle)
yourPoker.clear();
if (isShuffled == false)
shuffle();
for (int i = seatCode, piece = 0; piece < 17; i += 3, piece++)
yourPoker.add(pokerName.get(i));
if (seatCode == 0) {
yourPoker.add(pokerName.get(51));
yourPoker.add(pokerName.get(52));
yourPoker.add(pokerName.get(53));
}
return yourPoker;
}
// 检视未打乱的原始的牌
@Override
public String toString() {
StringBuilder originalPokerInfo = new StringBuilder("[");
ArrayList<String> originalPoker = new ArrayList<>(poker.keySet());
for (String thisPoker : originalPoker)
originalPokerInfo.append(thisPoker + ", ");
originalPokerInfo.deleteCharAt(originalPokerInfo.length() - 1).deleteCharAt(originalPokerInfo.length() - 1)
.append("]");
return originalPokerInfo.toString();
}
}package com.penyo.chinesepoker;
import java.util.Scanner;
public class Demo {
public static void main(String[] args) {
System.out.println("您想当地主吗?\nA. 是\tB. 否");
Scanner sc = new Scanner(System.in);
String choice = sc.nextLine().toUpperCase();
Poker play = new Poker(true);
if (choice.equals("A"))
System.out.println(play.getPoker(false, 0));
else
System.out.println(play.getPoker(false, 1));
}
}输入:
B输出:
[B-Joker, 方块2, 红桃A, 黑桃A, 方块K, 红桃K, 黑桃K, 方块J, 红桃10, 黑桃10, 梅花8, 红桃8, 红桃7, 黑桃7, 方块5, 梅花3, 红桃3]