Collection集合
Interface Collection<E> E-此集合中的元素类型
在Java中所有的容器都属于Collection接口的子类,就像异常都属于Throwable的子类一样。
Java标准库自带的java.util
包提供了集合接口:Collection
,它是除Map
外所有其他集合类的根接口。Java的java.util
包主要提供了以下三种类型的集合:
List
:一种有序列表的集合,例如,按索引排列的Student
的List
;Set
:一种保证没有重复元素的集合,例如,所有无重复名称的Student
的Set
;Map
:一种通过键值(key-value)查找的映射表集合,例如,根据Student
的name
查找对应Student
的Map
。
Java集合的设计有几个特点:一是实现了接口和实现类相分离,例如,有序表的接口是List
,具体的实现类有ArrayList
,LinkedList
等。二是支持泛型,我们可以限制在一个集合中只能放入同一种数据类型的元素,例如:
List<String> list = new ArrayList<>(); // 只能放入String类型
最后,Java遍历集合是通过统一的方式——迭代器(Iterator)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的。
由于Java的集合设计非常久远,中间经历过大规模改进;
Collection集合常用方法
Collection不能直接实例化,要通过其子类实例,
案例:创建学生类并添加到Collection集合中
set 集合
Interface Set<E>
,Set是一个接口不能直接使用,我们一般使用HashSet
或TreeSet
Set集合特点:
- 不包含重复元素;
- 没有索引,不能通过for循环遍历,可以用foreach或Iterator遍历;
哈希值:**
是JDK根据对象的地址或字符串或数字根据哈希算法算出来的int类型数值
哈希表
https://www.bilibili.com/video/BV1si4y1b7Rs?from=search&seid=2727536263524650394
HashSet集合
特点:
- 底层数据结构是哈希表;
- 不保证集合的迭代顺序;
- 此类实现
Set
接口,不包含重复元素; - 没有索引,不能通过for循环遍历;
瞎摆->存储的速度快;
继承关系:
java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractSet<E>
java.util.HashSet<E>
LinkedHashSet
特点:
- Hash表和
Set
接口的链表实现,具有可预测的迭代顺序; - 此实现与
HashSet
不同之处在于它维护了一个贯穿其所有条目的双向链表, 此链接列表定义迭代排序,即元素插入集合( 插入顺序 ) 的顺序,保证元素有序的;
继承关系:
Class LinkedHashSet<E>
java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractSet<E>
java.util.HashSet<E>
java.util.LinkedHashSet<E>
TreeSet
按顺序摆->存储的时候帮你排序, 存储的速度就慢;
继承关系:
java.lang.Object
java.util.AbstractCollection<E>
java.util.AbstractSet<E>
java.util.TreeSet<E>
public class TreeSet<E>
extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, Serializable
//一个NavigableSet实现基于一个TreeMap
//元件使用其有序natural ordering ,或由Comparator集合创建时提供,这取决于所使用的构造方法。
案例:对存储在TreeSet集合中的学生对象按照年龄排序,若年龄相同,则按名字字母排序
- Student类要实现 Comparable接口,重写compareTo方法
此接口对实现它的每个类的对象强加一个总排序。 这种排序被称为类的自然顺序 ,类的
compareTo
方法被称为其自然比较方法 。
SetDemo.java
TreeSet<Student> ts = new TreeSet<>();
Student s1 = new Student("diaocan", 26);
Student s2 = new Student("xisi", 33);
Student s3 = new Student("wangzhaojun", 28);
Student s4 = new Student("yangguifei", 35);
Student s5 = new Student("zhangmanyu", 35);
Student s6 = new Student("lingqqingxia", 35);
Student s7 = new Student("lingqqingxia", 35);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
ts.add(s7);
for (Student s:ts
) {
System.out.println("name="+s.getName()+",age="+s.getAge());
}
Student.java
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student() { }
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Student s) {
// return 0; //相同,不添加
// return 1; //后一个比前一个大,正序
// return -1; //后一个比前一个小,倒序
int num = this.age - s.age; //this 后一项,s 当前项
//如果age相同则比较name 否则直接返回num
return num==0?this.name.compareTo(s.name):num; //String重写了compareTo方法
}
}
打印:
name=diaocan,age=26 name=wangzhaojun,age=28 name=xisi,age=33 name=lingqqingxia,age=35 name=yangguifei,age=35 name=zhangmanyu,age=35
总结:
- 自然排序:要在要比较的对象类中实现
_Comparable_
并重写compareTo
方法; - 比较器排序:在
TreeSet
构造方法中定义Comparator
实例对象;
案例:
TreeSet<StudentScore> ts = new TreeSet<>(new Comparator<StudentScore>() {
@Override
public int compare(StudentScore o1, StudentScore o2) {
Double ret = o1.scoreSum() - o2.scoreSum();
return ret>0?-1:1;
}
});
StudentScore s1 = new StudentScore("zhangsan", 76.5,92.6);
StudentScore s2 = new StudentScore("lisi", 79.5,88.9);
StudentScore s3 = new StudentScore("wangwu", 91.6,78.6);
ts.add(s1);
ts.add(s2);
ts.add(s3);
for (StudentScore ss:ts
) {
System.out.println(ss.getName()+":"+ss.scoreSum());
}
//打印结果
/*
wangwu:170.2
zhangsan:169.1
lisi:168.4
*/
List 列表
两种数据结构:
数组有索引,查询非常快,但是增加删除节点 该位置后面的节点都要移动,效率非常低;
链表是一种增删快的模型(对比数组)
链表是一种查询慢的模型(对比数组),查询每一个元素都要从头开始查
Interface List<E> java.util包
public interface ``List<E> ``extends
Collection
<E>
List接口特点:
- 有序集合,该接口的用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素;
-
List实例方法
add(obj) 添加元素
- get(i) 查看第i个元素
- size() 查看列表中的数据个数
- isEmpty() 判断是否是空列表
- indexOf(xxx) 查看xxx元素在列表中的位置
- lastIndexOf(xxx) 查看xxx元素在列表中最后一次出现的位置
- contains(xxx) 判断列表中是否包含了xxx
- subList(start. end) 从列表中start开始截取, 截取到end 但不包括end
- toArray() 转化成数组
- remove(obj) 删除某个元素
- remove(i) 删除某个位置的元素
- clear() 清空列表
List lst = new ArrayList();
lst.add("美团外卖");
lst.add("饿了么");
lst.add("百度外卖");
lst.add("我不吃");
lst.add("外卖");
System.out.println(lst); // [美团外卖, 饿了么, 百度外卖, 我不吃, 外卖]
System.out.println(lst.get(0)); // 查询第0个元素
System.out.println(lst.get(2)); // 查询第2个元素
System.out.println(lst.size()); // 列表中元素的个数
// 遍历出列表中所有的数据
for(int i = 0 ; i < lst.size(); i++){
System.out.println(lst.get(i));
}
// 判断列表是否是空列表
System.out.println(lst.isEmpty()); // false
//查看"我不吃"元素在列表中的位置
System.out.println(lst.indexOf("我不吃")); // 3
//查看最后一个"百度外卖"在列表中的位置
System.out.println(lst.lastIndexOf("百度外卖")); // 2
//判断是否包含饿了么
System.out.println(lst.contains("饿了么")); // true
//列表的截取, 从1截取到3 [1,3)区间
System.out.println(lst.subList(1, 3)); // [饿了么, 百度外卖]
// 转化成数组
System.out.println(lst.toArray());
lst.remove("外卖"); // 删除"外卖"
lst.remove(2); // 删除索引为2的元素
ArrayList
List
接口的可调整大小的阵列实现,底层数据结构是数组,查询快,增删慢根据异常信息分析源码
描述:一个线程正在迭代List 而另一个线程去修改List(Collection 多态),会抛出ConcurrentModificationException异常
//异常
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
at com.xjt.myList.ListDemo.main(ListDemo.java:18)
分析源码:
异常信息要从下往上读
1、at com.xjt.myList.ListDemo.main(ListDemo.java:18),在myList.ListDemo.main
函数第18行
2、at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967),ArrayList.java
中第967行
执行
itr.next()
方法时,会先去执行checkForComodification``()
方法
3、at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013) 在ArrayList.java
文件的1013行
判断 modCount != expectedModCount
,如果不相等 抛出 ConcurrentModificationException
异常
当执行arrayList.add("python")
modCount+1,下次循环进来的时候expectedModCount 和modCount就不相等了。
那么 modCount 和 expectedModCount
又是什么东东呢?
我们可以发现在 类Itr 里面定义了: ArrayList.java
中 Ctrl+F搜索发现modCount没有定义,是直接调用的,所有我们去它父类中找
问题找到了,那么我们怎么在遍历List时改变它的内容呢?
//for循环遍历
for (int i = 0; i < arrayList.size(); i++) {
if(arrayList.get(i).equals("java")){
arrayList.add("python");
}
}
注意:增强for(foreach)内部其实就是Iterator
通过ArrayList实例的ListIterator
实现遍历
ListIterator:列表的迭代器,允许程序员在任一方向上遍历列表,在迭代期间修改列表,并获取迭代器在列表中的当前位置。
ListIterator<String> lit = arrayList.listIterator();
while (lit.hasNext()) {
String s = lit.next();
if (s.equals("java")) {
arrayList.add("python");
}
}
这么多种遍历List方式我们怎么选择呢?
- 只是取List每一项值,不需要修改时,使用foreach最方便
- 需要获取索引值或需要修改List项的值时,选择for循环
ListIterator
可以往前遍历往后遍历或从某一项索引开始遍历,且能修改List,使用最灵活Iterator
遍历List可以删除/获取List每一项LinkedList
双链表实现了List
和Deque
接口,底层数据结构是链表,查询慢,增删快public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable
ArrayList和 LinkedList的区别对比:
功能 | ArrayList | LinkedList |
---|---|---|
获取指定元素 | 速度很快 | 需要从头开始查找元素,慢 |
添加元素到末尾 | 速度很快 | 速度很快 |
在指定位置添加/删除 | 该位置后面的所有元素都要移动 | 不需要移动元素 |
内存占用 | 少 | 较大 |
ArrayList的优势是查询;
LinkedList的优势是删除和添加;
List.of()创建列表
除了使用ArrayList
和LinkedList
,我们还可以通过List
接口提供的of()
方法,根据给定元素快速创建List
:
List<Integer> list = List.of(1, 2, 5);
但是List.of()
方法不接受null
值,如果传入null
,会抛出NullPointerException
异常。
Map 集合
Map实例的基本方法
- put(key,value) 向映射中添加一个键值对,相同的key会把前面保存的数据顶掉;
- get(key) 使用key查询value;
- isEmpty() 判断Map是否是空的;
- size() 返回key, value键值对的个数;
- containsKey(key) 判断是否存在key 返回布尔值;
- containsValue(Object value) 判断映射中是否包含值==value 返回布尔值
- remove(key) 根据key删除信息;
- keySet() 获取到map中key的集合set;
- map.values() 获取到map中所有value值;
- map1.equals(map2) 判断两个Set集合的元素是否相同;
用法示例:
//map实例化
Map<String,String> map = new HashMap<>();
////基本方法
//添加元素
map.put("name","xiong");
map.put("gender","男");
Map<String,String> map2 = new HashMap<>();
map2.put("2", "B");
map2.put("3", "C");
map.putAll(map2); //把一个map集合合并到另一个map集合里
System.out.println(map);
//修改元素
map.remove("2","2B");
//删除元素
map.remove("3");
//查询元素
System.out.println("name="+map.get("name"));
//获取集合大小
System.out.println(map.size());
//获取集合所有值/所有键
System.out.println(map.keySet()); //所有键
System.out.println(map.values()); //所有值
//清空集合
//map.clear();
//获得map中一组组键值对的集合
System.out.println(map.entrySet()); //[2=B, gender=男, name=xiong]
//原始方式遍历
for (Map.Entry<String,String> entry:map.entrySet()){
System.out.println("key:"+entry.getKey()+";value:"+entry.getValue());
}
//map的forEach方式(java8新特性)
map.forEach((k,v) ->
System.out.println("key:"+k+";value:"+v)
);
//迭代器方式遍历
System.out.println(map); //{2=B, gender=男, name=xiong}
System.out.println(map.entrySet()); //[2=B, gender=男, name=xiong]
Iterator it = map.entrySet().iterator(); // 拿到map内部的entry,逗号分隔的每一项都是一个entry
while(it.hasNext()){
Map.Entry entry = (Map.Entry) it.next();
System.out.println(entry.getKey()+"=>"+entry.getValue());
}
示例:
示例2:
示例3:
示例4:
ArrayList<HashMap<String, String>> arrayList = new ArrayList<>();
HashMap<String, String> hm1 = new HashMap<>();
hm1.put("令狐冲","任盈盈");
hm1.put("岳不群","宁中则");
hm1.put("林平之","岳灵珊");
arrayList.add(hm1);
HashMap<String, String> hm2 = new HashMap<>();
hm2.put("杨过","小龙女");
hm2.put("郭靖","黄蓉");
arrayList.add(hm2);
HashMap<String, String> hm3 = new HashMap<>();
hm3.put("张无忌","赵敏");
hm3.put("宋青书","周芷若");
hm3.put("张无忌","小昭");
arrayList.add(hm3);
for (HashMap<String, String> hm:arrayList) {
//Set<String> keys = hm.keySet();
//for (String key:keys) {
//System.out.println(key+"="+hm.get(key));
//}
//HashSet集合的第二种遍历方法
Set<Map.Entry<String, String>> entries = hm.entrySet();
for (Map.Entry<String, String> entry:entries
) {
String key = entry.getKey();
String val = entry.getValue();
System.out.println(key+"="+val);
}
}
示例5:
HashMap<String, ArrayList<String>> stringArrayListHashMap = new HashMap<>();
ArrayList<String> arr1 = new ArrayList<>();
arr1.add("张无忌");
arr1.add("赵敏");
arr1.add("小昭");
arr1.add("周芷若");
stringArrayListHashMap.put("倚天屠龙记",arr1);
ArrayList<String> arr2 = new ArrayList<>();
arr2.add("杨过");
arr2.add("小龙女");
arr2.add("李莫愁");
arr2.add("金轮法王");
stringArrayListHashMap.put("神雕侠侣",arr2);
ArrayList<String> arr3 = new ArrayList<>();
arr3.add("郭靖");
arr3.add("黄蓉");
arr3.add("杨康");
arr3.add("穆念慈");
arr3.add("欧阳克");
stringArrayListHashMap.put("射雕英雄传",arr3);
Set<Map.Entry<String, ArrayList<String>>> entries = stringArrayListHashMap.entrySet();
for (Map.Entry<String, ArrayList<String>> entry:entries) {
String key = entry.getKey();
ArrayList<String> values = entry.getValue();
System.out.println(key+":");
for (String value:values
) {
System.out.println("\t"+value);
}
}
示例6:
HashMap<Character, Integer> hm = new HashMap<>();
System.out.println("请输入一串字符:");
Scanner sc = new Scanner(System.in);
String line = sc.nextLine();
for (int i = 0; i < line.length(); i++) {
char key = line.charAt(i);
Integer value = hm.get(key);
if(value == null){
hm.put(key,1);
}else{
value++;
hm.put(key,value);
}
}
StringBuilder sb = new StringBuilder();
Set<Map.Entry<Character, Integer>> entries = hm.entrySet();
for (Map.Entry<Character, Integer> entry:entries) {
Character key = entry.getKey();
Integer value = entry.getValue();
sb.append(key).append("(").append(value).append(")");
}
System.out.println(sb.toString());
Collections工具类
Collections.sort()
Collections.reverse()
Collections.shuffle()
这些都是静态方法,通过类Collections直接调用
示例:
示例:用Collection.sort()方法对学生年龄排序,若年龄相同按学生姓名排序
案例:模拟斗地主
public static void main(String[] args) {
//1.创建一副牌
//♦ 3 4 5 6 7 8 9 10 J Q K A 2
//♣ 3 4 5 6 7 8 9 10 J Q K A 2
//♥ 3 4 5 6 7 8 9 10 J Q K A 2
//♠ 3 4 5 6 7 8 9 10 J Q K A 2
List<String> pokerList = new ArrayList<>();
//1.1创建花色和数字列表
String[] colors = {"♦","♣","♥","♠"};
String[] nums = {"3","4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
for (String color:colors
) {
for (String num:nums
) {
pokerList.add(color+num);
}
}
pokerList.add("大王");
pokerList.add("小王");
//1.2洗牌
Collections.shuffle(pokerList);
System.out.println(pokerList);
//2.创建3个玩家列表和一个底牌列表
ArrayList<String> playerAList = new ArrayList<>();
ArrayList<String> playerBList = new ArrayList<>();
ArrayList<String> playerCList = new ArrayList<>();
ArrayList<String> dipaiList = new ArrayList<>();
//3.发牌
for (int i = 0; i < pokerList.size(); i++) {
if(i>=pokerList.size()-3){
dipaiList.add(pokerList.get(i));
}else if(i%3 == 0){
playerAList.add(pokerList.get(i));
}else if(i%3 == 1){
playerBList.add(pokerList.get(i));
}else if(i%3 == 2){
playerCList.add(pokerList.get(i));
}
}
//4.三位玩家看牌,并亮出底牌
showPocker("张三丰",playerAList);
showPocker("张无忌",playerBList);
showPocker("张翠山",playerCList);
showPocker("底牌",dipaiList);
}
public static void showPocker(String name, ArrayList<String> array){
System.out.println("玩家-"+name+"手里的牌是:");
for (String pocker:array
) {
System.out.print(pocker+" "); //System.out.println()这个是在输出内容之后换行,而 System.out.print()输出内容之后不换行
}
System.out.println(); //空行
}
打印:
public static void main(String[] args) {
//1.创建一副牌
//♦ 3 4 5 6 7 8 9 10 J Q K A 2
//♣ 3 4 5 6 7 8 9 10 J Q K A 2
//♥ 3 4 5 6 7 8 9 10 J Q K A 2
//♠ 3 4 5 6 7 8 9 10 J Q K A 2
//1.1HashMap键值0 1 2 3 ...52 值对应的牌♦3 ♦4...
HashMap<Integer, String> pockerHM = new HashMap<>();
ArrayList<Integer> array = new ArrayList<>(); //存储牌的键值
Integer index = 0;
//1.1创建花色和数字列表
String[] colors = {"♦","♣","♥","♠"};
String[] nums = {"3","4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
for (String color:colors
) {
for (String num:nums
) {
array.add(index);
pockerHM.put(index,color+num);
index++;
}
}
array.add(index);
pockerHM.put(index,"大王");
index++;
array.add(index);
pockerHM.put(index,"小王");
//1.2洗牌
Collections.shuffle(array);
// System.out.println(array);
// System.out.println(pockerHM);
//2.创建3个玩家列表和一个底牌列表(存储的HashMap键值)
TreeSet<Integer> playerATreeSet = new TreeSet<>();
TreeSet<Integer> playerBTreeSet = new TreeSet<>();
TreeSet<Integer> playerCTreeSet = new TreeSet<>();
TreeSet<Integer> dipaiTreeSet = new TreeSet<>();
//3.发牌
for (int i = 0; i < pockerHM.size(); i++) {
Integer idx = array.get(i);
if(i>=pockerHM.size()-3){
dipaiTreeSet.add(idx);
}else if(i%3 == 0){
playerATreeSet.add(idx);
}else if(i%3 == 1){
playerBTreeSet.add(idx);
}else if(i%3 == 2){
playerCTreeSet.add(idx);
}
}
//4.三位玩家看牌,并亮出底牌
showPocker("张三丰",playerATreeSet,pockerHM);
showPocker("张无忌",playerBTreeSet,pockerHM);
showPocker("张翠山",playerCTreeSet,pockerHM);
showPocker("底牌",dipaiTreeSet,pockerHM);
}
public static void showPocker(String name, TreeSet<Integer> array,HashMap<Integer,String> playerHashMap){
System.out.println(name+"手里的牌是:");
for (Integer i:array
) {
System.out.print(playerHashMap.get(i)+" "); //System.out.println()这个是在输出内容之后换行,而 System.out.print()输出内容之后不换行
}
System.out.println(); //空行
}
打印:
作业:
有字符串”k:1,k1:2,k2:3,k3:4” 处理成Map: {‘k’=1,’k1’=2….}
package com.xjt.homework;
import java.util.HashMap;
import java.util.Map;
public class Work1 {
public static void main(String[] args) {
/*1. 有字符串”k:1,k1:2,k2:3,k3:4” 处理成Map: {‘k’=1,’k1’=2….}
*/
String str = "k:1,k1:2,k2:3,k3:4";
Map<String,Integer> mymap = new HashMap<String,Integer>();
String[] lst = str.split(",");
for (String item:lst
) {
String StrKey = item.split(":")[0];
String StrValue = item.split(":")[1];
mymap.put(StrKey,Integer.parseInt(StrValue));
}
System.out.println(mymap);
}
}
元素分类, 有如下值 int[] li= {11,22,33,44,55,66,77,88,99,90},将所有大于 等于66 的值保存至Map的第一个key中,将小于 66 的值保存至第二个key的值中。
即: {‘k1’: 大于66的所有值列表, ‘k2’: 小于66的所有值列表}package com.xjt.homework;
import java.util.*;
public class Work2 {
public static void main(String[] args) {
int[] li= {11,22,33,44,55,66,77,88,99,90};
//定义一个有泛型映射
Map<String, List<Integer>> map = new HashMap<String,List<Integer>>();
//定义两个有泛型的列表
List<Integer> lst1 = new ArrayList<>();
List<Integer> lst2 = new ArrayList<>();
for (Integer item:li
) {
if(item>=66){
lst1.add(item);
}else{
lst2.add(item);
}
}
map.put("k1",lst1);
map.put("k2",lst2);
System.out.println(map); //{k1=[66, 77, 88, 99, 90], k2=[11, 22, 33, 44, 55]}
}
}
作业参考答案:
给定一组连续的整数,例如:10,11,12,……,20,但其中缺失一个数字,试找出缺失的数字:
package com.xjt.homework;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test1 {
static int findMissingNumber(int start, int end, List<Integer> list) {
int ret=0;
for (int i = start; i <= end; i++) {
if (!list.contains(i)) {
ret = i;
}
}
return ret;
}
public static void main(String[] args) {
// 构造从start到end的序列:
final int start = 10;
final int end = 20;
List<Integer> list = new ArrayList<>();
for (int i = start; i <= end; i++) {
list.add(i);
}
// 洗牌算法shuffle可以随机交换List中的元素位置:
Collections.shuffle(list);
// 随机删除List中的一个元素:
int removed = list.remove((int) (Math.random() * list.size()));
int found = findMissingNumber(start, end, list);
System.out.println(list.toString());
System.out.println("missing number: " + found);
System.out.println(removed == found ? "测试成功" : "测试失败");
}
}
Iterator 迭代器
我们可以使用get() size() 搭配for循环来完成遍历,但是想一个问题. set好像没有get()方法. 那set集合怎么遍历啊….这就要用到咱们本节课讲的这个叫Iterator的东西. 这个东西叫迭代器。
啥是迭代器. 想一个事儿. 我们村以前有那种乡村诊所. 病人来了之后呢就在门口一站. 然后大夫在诊治的时候会喊一嗓子~ 下一个. 下一个人就进来了. 大夫不关心外面多少人, 也不关心外面的病人是谁. 他只关心”下一个”. OK. 咱们今天要说的这个叫迭代器的东西, 和乡村诊所里的大夫是一样一样的~~.
先看用法:
Set set = new HashSet();
set.add("王大锤");
set.add("二麻子");
set.add("三愣子");
set.add("哈利路呀大大大锤");
// 创建迭代器对象, 相当于创建一个医生
Iterator it = set.iterator();
// 吼一嗓子, 进来一个人
String s1 = (String) it.next();
System.out.println(s1);
// 吼一嗓子, 进来一个人
String s2 = (String) it.next();
System.out.println(s2);
// 吼一嗓子, 进来一个人
String s3 = (String) it.next();
System.out.println(s3);
// 吼一嗓子, 进来一个人
String s4 = (String) it.next();
System.out.println(s4);
// 没人了. 还吼, 吼破了喉咙也不会有人理你的. 报错
String s5 = (String) it.next(); //Exception in thread "main" java.util.NoSuchElementException
System.out.println(s5);
next() 会自动帮我们获取到元素,并且指针指向下一个元素。
但是呢,这时候我们发现迭代器, 太容易出错了,我也不知道啥时候还有没有元素了。怎么解决呢? 在next()之前, 我们可以做一个判断, 判断是否还有下一个元素。
set集合使用迭代器遍历
Set set = new HashSet();
set.add("王大锤");
set.add("王小锤");
set.add("王不大不小锤");
set.add("哈利路呀大大大锤");
// 拿到迭代器, 相当于创建一个医生
Iterator it = set.iterator(); //集合迭代器
while(it.hasNext()){ // 判断是否还有下一个元素
String s = (String) it.next();
System.out.println(s);
}
List使用迭代器遍历
package com.xyq.collection;
import java.util.*;
public class TestIter {
public static void main(String[] args) {
List list = new ArrayList();
list.add("武大郎");
list.add("武二郎");
list.add("武三郎");
Iterator it = list.iterator(); //列表迭代器
while(it.hasNext()){
System.out.println(it.next());
}
}
}
Map使用迭代器进行遍历
Map map = new HashMap();
map.put("浪里白条", "张顺");
map.put("及时雨", "松江");
map.put("行者", "悟空");
Iterator it = map.keySet().iterator(); //map映射迭代器,要对map映射的所有键进行迭代
while(it.hasNext()){
Object key = it.next();
Object value = map.get(key); // 通过key获取到value
System.out.println(key+"=>"+value);
}
Map集合还可以这样遍历.
Map map = new HashMap();
map.put("浪里白条", "张顺");
map.put("及时雨", "松江");
map.put("行者", "悟空");
Iterator it = map.entrySet().iterator(); // 拿到map内部的entry,逗号分隔的每一项都是一个entry
while(it.hasNext()){
Map.Entry entry = (Map.Entry) it.next();
System.out.println(entry.getKey()+"=>"+entry.getValue());
}
迭代器存在的意义:
一是可以让不同的数据类型拥有相同的遍历方式,List, Set, Map,完全不同的三种数据类型,但是都可以使用Iterator进行遍历;
二是:使用迭代器遍历比for循环遍历效率要高;
1.for each == iterator
有童鞋可能觉得使用Iterator
访问List
的代码比使用索引更复杂。但是,要记住,通过Iterator
遍历List
永远是最高效的方式。并且,由于Iterator
遍历是如此常用,所以,Java的for each
循环本身就可以帮我们使用Iterator
遍历。把上面的代码再改写如下:
List<String> list = List.of("apple", "pear", "banana");
for (String s : list) {
System.out.println(s);
}
上述代码就是我们编写遍历List
的常见代码。
实际上,只要实现了Iterable
接口的集合类都可以直接用for each
循环来遍历,Java编译器本身并不知道如何遍历集合对象,但它会自动把for each
循环变成Iterator
的调用,原因就在于Iterable
接口定义了一个Iterator iterator()
方法,强迫集合类必须返回一个Iterator
实例。
2.List和Array转换
把List
变为Array
有三种方法,第一种是调用toArray()
方法直接返回一个Object[]
数组:
List<String> list = List.of("apple", "pear", "banana");
Object[] array = list.toArray();
for (Object s : array) {
System.out.println(s);
}
这种方法会丢失类型信息,所以实际应用很少。
第二种方式是给toArray(T[])
传入一个类型相同的Array
,List
内部自动把元素复制到传入的Array
中:
List<Integer> list = List.of(12, 34, 56);
Integer[] array = list.toArray(new Integer[3]);
for (Integer n : array) {
System.out.println(n);
}
注意到这个toArray(T[])
方法的泛型参数并不是List
接口定义的泛型参数,所以,我们实际上可以传入其他类型的数组,例如我们传入Number
类型的数组,返回的仍然是Number
类型:
List<Integer> list = List.of(12, 34, 56);
Number[] array = list.toArray(new Number[3]);
for (Number n : array) {
System.out.println(n);
}
但是,如果我们传入类型不匹配的数组,例如,String[]
类型的数组,由于List
的元素是Integer
,所以无法放入String
数组,这个方法会抛出ArrayStoreException
。
如果我们传入的数组大小和List
实际的元素个数不一致怎么办?根据List接口)的文档,我们可以知道:
如果传入的数组不够大,那么List
内部会创建一个新的刚好够大的数组,填充后返回;如果传入的数组比List
元素还要多,那么填充完元素后,剩下的数组元素一律填充null
。
实际上,最常用的是传入一个“恰好”大小的数组:
Integer[] array = list.toArray(new Integer[list.size()]);
最后一种更简洁的写法是通过List
接口定义的T[] toArray(IntFunction generator)
方法:
Integer[] array = list.toArray(Integer[]::new);
这种函数式写法我们会在后续讲到。
反过来,把Array
变为List
就简单多了,通过List.of(T...)
方法最简单:
Integer[] array = { 1, 2, 3 };
List<Integer> list = List.of(array);
对于JDK 11之前的版本,可以使用Arrays.asList(T...)
方法把数组转换成List
。
要注意的是,返回的List
不一定就是ArrayList
或者LinkedList
,因为List
只是一个接口,如果我们调用List.of()
,它返回的是一个只读List
:
List<Integer> list = List.of(12, 34, 56); //返回一个只读的List
list.add(999); // UnsupportedOperationException
对只读List
调用add()
、remove()
方法会抛出UnsupportedOperationException
。
泛型
泛型概述
前面我们说了三种容器. 这三种容器有一个共同的特点, 就是所有存放在容器中的内容最终都被当成了Object来保存。当前, 我们能理解为了通用性把所有的元素向上转型成Object。
但是这样存储是有问题的。比如,酱油瓶里就应该装酱油,醋瓶就应该装醋。混着装不是不能装,是不能用 对吧。所以呢,我们要对容器进行规范,不能乱七八糟的乱装,这就需要用到泛型。通过泛型可以强制规定容器能装什么东西。就好比,给你家里的瓶瓶罐罐贴上标签来说明这个瓶子是用来装什么的。
规范集合存储数据类型,取数据时不需要强转了,在以后的代码中, 如果没有特殊要求,推荐使用泛型
List<String> list = new ArrayList<String>(); // 强制规定这个列表只能装字符串,第二个尖括号里面的String可以省略
// list.add(1);// 报错. 只能装字符串
list.add("NB"); // OK
// 好处是, 从list中获取到的数据不需要强转了.
String s = list.get(0) ;// 这个列表只能装字符串, 拿出来也一定是字符串
System.out.println(s);
创建一个只能装歌手的列表(自定义泛型类)
package com.xjt.fanxing;
import java.util.ArrayList;
import java.util.List;
public class Test1 {
public static void main(String[] args) {
List<Singer> singerList = new ArrayList<Singer>(); // 只能装歌手类型
Singer singer1 = new Singer("周杰伦",45);
singerList.add(singer1); // 添加歌手
Singer singer2 = new Singer("王菲",50);
singerList.add(singer2); // 添加歌手
Singer ss = singerList.get(0); // 查询歌手
System.out.println(ss);
}
}
class Singer{
String name;
int age;
@Override
public String toString() {
return "Singer{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Singer(String name, Integer age) {
this.name = name;
this.age = age;
}
}
Set集合和List的泛型是一样的,强制规定集合中元素的类型
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("pear");
set.add("orange");
for (String s : set) {
System.out.println(s);
}
List<String> list = new ArrayList<>();
list.add("apple"); // size=1
list.add("pear"); // size=2
list.add("apple"); // 允许重复添加元素,size=3
System.out.println(list.size());
Map集合的泛型比较特殊. 因为这个鬼东西里面有key和value.两项内容.
Map<String, String> map = new HashMap<String, String>();
map.put("萝莉", "小蔡");
map.put("御姐", "苏打强");
// map.put(1, 2) ; // 报错, 只能是字符串
String v = map.get("萝莉");
System.out.println(v);
定义泛型类
泛型方法
- 在调用该方法时决定传入什么类型参数
public class GenericDemo1 {
public static void main(String[] args) {
//调用普通方法
Generic g1 = new Generic();
g1.show("hello");
g1.show(111);
g1.show(true);
//泛型类的方法调用
Generic2<String> g21 = new Generic2<String>();
g21.show("hello");
Generic2<Integer> g22 = new Generic2<Integer>();
g22.show(123);
Generic2<Boolean> g23 = new Generic2<Boolean>();
g23.show(true);
//泛型方法调用(终极版)
Generic3 g3 = new Generic3();
g3.show("hello");
g3.show(111);
g3.show(true);
g3.show(12.34);
}
}
class Generic{
//普通方法
public void show(String s){
System.out.println(s);
}
public void show(Integer s){
System.out.println(s);
}
public void show(Boolean s){
System.out.println(s);
}
}
//泛型类改进
class Generic2<T>{
public void show(T t){
System.out.println(t);
}
}
//泛型方法改进
class Generic3{
public <T> void show(T t){
System.out.println(t);
}
}
泛型接口
具体可参考Collection接口及其实现类 ArrayList
Interface Collection<E> //接口定义的是泛型
public interface List<E> //List接口继承的仍然是泛型
extends Collection<E>
public class ArrayList<E> //最终实现类
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
//方法仍然是泛型
void add(int index, E element) //将指定元素插入此列表中的指定位置。
boolean add(E e) //将指定的元素追加到此列表的末尾。
类型通配符
可变参数
第一个确定的参数必须放置前面,后面不确定个数的参数封装到a集合里了