JavaImprove--Lesson08--集合框架二,Collection工具类,Map集合,Stream流
一.前置知识-Collections工具类
可变参数
可变参数是一种特殊的形参,定义在方法,构造器参数列表中,格式是:数据类型...参数名称;
可变参数的特点和好处:
- 特点:可以不传递参数给它,可以传递一个或多个参数给他,也可以传递一个数组给他
- 好处:常常用于灵活的接收数据
可变参数的注意事项:
- 可变参数在方法内部就是一个数组
- 一个形参列表中只能有一个可变参数
- 可变参数必须在参数列表的最后
public static void main(String[] args) { t1(); //0[]-------------------- t1(10);//1[10]-------------------- t1(10,20,30);//3[10, 20, 30]-------------------- t1(new int[]{10,20,3,50});//4[10, 20, 3, 50]-------------------- } public static void t1(int...nums){ System.out.print(nums.length); System.out.print(Arrays.toString(nums)); System.out.println("--------------------"); } //可变参数应当在参数列表最后 public static void t2(int num,int...nums){ System.out.print(nums.length); System.out.print(Arrays.toString(nums)); System.out.println("--------------------"); }
Collections工具类的常用方法
针对collection这类单列集合,工具类有一些综合优化的方法
使用集合名.add()方法增加参数时只能一个一个的添加,使得代码看起来很冗余,所以工具类提供addAll方法用来一列完成多行添加
//public static <T> boolean addAll(collection<? super T> c,T...elements); //往集合中批量加入参数 ArrayList<String> list = new ArrayList<>(); Collections.addAll(list,"1","2","3","4"); System.out.println(list);//[1, 2, 3, 4]
2.public static void shuffle(List<?> list)
List集合类是基于数组实现的,所以是有序的,shuffle可以打乱原有的顺序(需要乱序时可用)
//public static void shuffle(List<?> list) //打乱集合中元素顺序,针对List类集合 System.out.println(list);//[1, 2, 3, 4] Collections.shuffle(list); System.out.println(list);//[4, 2, 1, 3]
3.public static <T> void short (List<T> list)
针对集合中存放的基本数据类型,可以按照升序进行排序
//public static <T> void short (List<T> list); //对List集合中的元素进行升序排序,针对非对象类型 System.out.println(list);//[1, 4, 3, 2] Collections.sort(list); System.out.println(list);//[1, 2, 3, 4]
4.public static <T> void short (List<T> list, Comparator<? super T> c)
针对对象类型的排序,需要制定排序规则,所以先要new Comparator接口
//public static <T> void short (List<T> list, Comparator<? super T> c); //对list中的对象类型进行排序,因为对象类型需要指定排序规则 ArrayList<Student> list1 = new ArrayList<>(); Collections.addAll(list1,new Student("m1","boy",12),new Student("m2","boy",18),new Student("m3","boy",16)); Collections.sort(list1, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getGrade()-o2.getGrade(); } }); //Collections.sort(list1,(o1,o2)->{return o1.getGrade()-o2.getGrade();}); System.out.println(list1);
卡牌游戏模拟
本案例主要讲述的是卡牌的洗牌,及发牌过程并不涉及打牌的规则比较
卡牌类:
public class Card { //卡牌的点数 private String number; //卡牌花符 private String color; //卡牌优先级 private int size; public Card() { } public Card(String number, String color, int size) { this.number = number; this.color = color; this.size = size; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } @Override public String toString() { return number+color; } }
房间类:洗牌,发牌
public class Room { private List<Card> allCards = new ArrayList<>(); public Room() { //初始化54张牌 String[] numbers = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"}; String[] colors ={"♠","❤","♣","♦"}; int size= 0; for (String number : numbers) { size++; for (String color : colors) { Card card = new Card(number,color,size); allCards.add(card); } } //录入大小王 Card card = new Card("🌝", "小", ++size); Card card1 = new Card("🤡", "大", ++size); Collections.addAll(allCards,card,card1); } //启动游戏的方法 public void start(){ //打乱排序,洗牌 Collections.shuffle(allCards); //构造三个用户接收这副牌 List<Card> user1=new ArrayList<>(); List<Card> user2=new ArrayList<>(); List<Card> user3=new ArrayList<>(); for (int i = 0; i < allCards.size()-3; i++) { if(i%3==0){ user1.add(allCards.get(i)); } else if (i % 3 == 1) { user2.add(allCards.get(i)); }else if (i % 3 == 2){ user3.add(allCards.get(i)); }else { System.out.println("cardError"); } } List<Card> lastThreeCard = allCards.subList(allCards.size() - 3, allCards.size()); user2.addAll(lastThreeCard); //对牌进行排序 Collections.sort(user1,((o1, o2) -> o1.getSize()-o2.getSize())); Collections.sort(user2,((o1, o2) -> o1.getSize()-o2.getSize())); Collections.sort(user3,((o1, o2) -> o1.getSize()-o2.getSize())); //输出牌 System.out.println("地主牌:"+lastThreeCard); System.out.println("用户1:"+user1); System.out.println("用户2:"+user2); System.out.println("用户3:"+user3); } }
启动函数Main:
public static void main(String[] args) { //创建54张牌 Room room = new Room(); //发牌 room.start(); }
二.Map集合
Map集合是是双列集合,即一列有两个元素组成,分别是键和值,也别称为键值对(key-value)
格式:{key1 = value1,key2=value2,key3=value3}
Map集合的每个元素”key=value“称为键值对/键值对象/一个Entry对象
Map集合系列下所有的健都是不可重复的,但是值是可以重复的,键值是一一对应的,每个键只能找到自己对应的值
注意点:在存储一一对应的数据时,可以使用Map集合
Map集合的实现类也有很多,但是我们常用的就哪几种
HashMap:无序,不重复,无索引(使用最多)
LinkedHashMap:有序,不重复,无索引
TreeMap:排序,不重复,无索引
Map集合的特点都是由键决定的,值是附属
Map集合的常用方法
Map是一个接口类,所以Map的常用方法,它的实现类都是会被继承的
1. public void put(K k); 添加Map集合中的元素
HashMap<String, Integer> map = new HashMap<>(); //添加元素 public void put(K k); map.put("java",1); map.put("mysql",2); map.put("java",2); map.put("spring", 5); System.out.println(map);
2.public int size() 获取集合的大小
//public int size() 获取集合的大小 System.out.println(map.size());//3
3.public void clear() 清空集合
//public void clear() 清空集合 map.clear();
4.public boolean isEmpty()判断集合是否为空
//public boolean isEmpty() 判断集合是否为空 System.out.println(map.isEmpty());//true
5.public V get(object key)根据键获取对应值
//public V get(object key)根据键获取对应值 Integer java = map.get("java"); System.out.println(java);//2
6.public V remove(object key) 根据键删除元素,并且返回删除的键的值
//public V remove(object key) 根据键删除元素,并且返回删除的键的值 Integer java1 = map.remove("java"); System.out.println(java1);//2
7.public boolean containsKey(object key) 判断是否包含某个键值
//public boolean containsKey(object key) 判断是否包含某个键值 boolean java2 = map.containsKey("java"); System.out.println(java2);//true
8.public boolean containsValue(object value) 判断是否包含某个键值
//public boolean containsValue(object value) 判断是否包含某个键值 boolean b = map.containsValue(2); System.out.println(b);//true
9.public Set<V> keySet() 获取集合的全部键,并放入一个set集合中(不重复)
//public Set<V> keySet() 获取集合的全部键,并放入一个set集合中(不重复) Set<String> s = map.keySet(); System.out.println(s);
10.public Collection<V> values(); 获取map集合的所有值
//public Collection<V> values(); 获取map集合的所有值 Collection<Integer> v = map.values(); System.out.println(v);//[5, 2]
11.把其它Map集合导入自己的Map集合中
//把其它集合导入自己的集合中 HashMap<String, Integer> map1 = new HashMap<>(); map1.put("springMvc",10); map1.put("mybatis",12); map.putAll(map1); //导入集合中 System.out.println(map); //{spring=5, mybatis=12, mysql=2, springMvc=10}
三.Map类集合的遍历方式
方式一:键找值
由于Map集合中的键都是不可重复的,所以键有很强的唯一性
通过先找键,然后再通过键拿取到值,这样重复的操作就可以完全遍历完一个Map集合
需要用到的两个方法:
public Set<K> keySet();拿取到Map集合所有的键,并封装到Set集合中
public V get(object key);根据键,拿取键对应的值
public static void main(String[] args) { HashMap<String, Integer> map = new HashMap<>(); map.put("java",1); map.put("mysql",2); map.put("spring",3); map.put("springMvc",4); map.put("mybatis",5); //获取map集合所有的键 Set<String> keys = map.keySet(); //通过遍历键,获取键值 for (String key : keys) { Integer value = map.get(key); System.out.println(key+":"+value); /* spring:3 java:1 mybatis:5 mysql:2 springMvc:4 */ } }
方法二:键值对
把键值对看成一个整体进行遍历,需要引入一个新的数据类型Map.entry,这是Map集合的独有类型,翻译过来就是Map集合的实体类型
这种类型是将双列集合的键值封装成一个键值对对象,然后进行遍历
因为这种遍历方式也是通过增强for遍历,增强for需要知道数据集合的类型,所以Map.entry类型
需要用到的方法:
Set<Map.Entry<K,V>> entrySet(); 将Map集合封装成键值对对象
public static void main(String[] args) { HashMap<String, Integer> map = new HashMap<>(); map.put("java",1); map.put("mysql",2); map.put("spring",3); map.put("springMvc",4); map.put("mybatis",5); //获取键值对对象 Set<Map.Entry<String, Integer>> es = map.entrySet(); for (Map.Entry<String, Integer> e : es) { System.out.println(e.getKey()+":"+e.getValue()); } /* spring:3 java:1 mybatis:5 mysql:2 springMvc:4 */ }
方法三:Lambda表达式
这是一项Jdk1.8后的技术,使得遍历map集合变得很简单
使用map集合的forEach方法,在方法中写上Lambda表达式,就可以完成遍历
public static void main(String[] args) { HashMap<String, Integer> map = new HashMap<>(); map.put("java",1); map.put("mysql",2); map.put("spring",3); map.put("springMvc",4); map.put("mybatis",5); //Lambda表达式 map.forEach((k,v)->{ System.out.println(k+":"+v); }); /* spring:3 java:1 mybatis:5 mysql:2 springMvc:4 */ }
在forEach方法中其实我们实现一个方法,Lambda表达式只是简写,此接口本来就是一个函数式接口,所以可以使用Lambda表达式简化
Lambda表达式可以转写为如下:
map.forEach(new BiConsumer<String, Integer>() { @Override public void accept(String k, Integer v) { System.out.println(k+":"+v); } });
如上,accept就是我们需要实现的方法,而BiConsumer接口是函数式接口,new 它实际上是实例化它的实现类
而ForEach方法也是使用增强for来实现的
通过增强for将每次遍历出来的key和value,交给我们实现的accept方法,这里有一个很有意思的点
在一般情况下,都是官方写好方法,我们传递参数,然后使用方法,而这里是,官方写好方法,然后遍历出参数,调用我们实现的方法,有一个逆反的过程
当然,这也是必须的,毕竟遍历出的结果是我们使用,怎么使用,就看方法实现
Map集合的实现原理
其实Map的实现底层就是Set 的实现底层,换句话说,当Map不存值,只存键的时候,就是Set集合
所以HashMap集合的底层就是HashSet,只是有没有存值的问题
故:HashMap的底层和HashSet的底层是对应的,LinkedHashMap--LinkedHashSet,TreeMap--TreeSet
可以在源码中看出来
HashMap--HashSet
LinkedHashMap--LinkedHashSet
TreeMap--TreeSet
所以要看Map的底层实现原理,就可以直接去看前面我写的Set集合底层实现:JavaImprove--Lesson07--异常处理的两种方式,collection集合
在HashMap在进行Hash计算是使用的就是entry,即Map的实体对象,将map的键值封装成entry实体,在通过取模“键”
综合示例
集合嵌套,使用一种集合可以嵌套另一种集合,如map嵌套List,List嵌套map,使用最多的就是Map嵌套List
public static void main(String[] args) { //创建一个map集合 HashMap<String, List<String>> map = new HashMap<>(); //创建List集合 List<String> list1 = new ArrayList<>(); Collections.addAll(list1,"温迪","尤娜","颗粒"); List<String> list2 = new ArrayList<>(); Collections.addAll(list2,"钟离","胡桃","夜阑"); List<String> list3 = new ArrayList<>(); Collections.addAll(list3,"雷电将军","八重神子","珊瑚宫心海"); map.put("蒙德",list1); map.put("璃月",list2); map.put("稻妻",list3); //遍历map集合 Set<String> keys = map.keySet(); for (String key : keys) { System.out.println(key+":"+map.get(key)); } /* 璃月:[钟离, 胡桃, 夜阑] 蒙德:[温迪, 尤娜, 颗粒] 稻妻:[雷电将军, 八重神子, 珊瑚宫心海] */ }
四.Stream流
Stream流也是Jdk8之后引入的新特性,其中还有一个特性就是Lambda表达式
Stream流是用于操作数组和集合的
它与数组和集合自带的API优势在于:Stream集合了大量的Lambda表达式的语言风格,提供了一种更加强大且简单的方式操作数组和集合,代码简洁
入门Stream流
在一个集合中筛选出包含“枫”和“须”字的地名:
public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); Collections.addAll(list,"蒙德","璃月","稻妻","枫丹","须弥","纳塔","至冬"); //普通的方式操作集合 ArrayList<String> list1 = new ArrayList<>(); for (String l : list) { if (l.startsWith("枫") || l.startsWith("须")){ //找出包含“枫或须的地名” list1.add(l); } } System.out.println(list1);//[枫丹, 须弥] //使用Stream流 List<String> list2 = list.stream().filter(s -> s.startsWith("枫") || s.startsWith("须")).collect(Collectors.toList()); //filter()过滤方法, System.out.println(list2);//[枫丹, 须弥] }
Stream流使用步骤
总的来说就是三个过程:
- 获取Stream流
- 使用Stream流常见的中间方法
- 使用Stream流常见的终结方法
获取Stream流
获取Stream流主要有两个方法:stream(),of()
1.获取集合的流方法:default Stream<E> stream()
Collection系列:
public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); Collections.addAll(list,"蒙德","璃月","稻妻","枫丹","须弥","纳塔","至冬"); //List集合获得Stream流 Stream<String> stream = list.stream(); List<String> li = stream.filter(s -> s.contains("月")).collect(Collectors.toList()); System.out.println(li);//[璃月] }
Map系列:
public static void main(String[] args) { HashMap<String, String> map = new HashMap<>(); map.put("温迪","蒙德"); map.put("钟离","璃月"); map.put("雷电将军","稻妻"); map.put("纳西妲","须弥"); //由于map集合是双列集合,并不能直接使用stream方法操作集合 //方案一:分别操作值和键,思路转换为Collection集合 //操作键 Set<String> keys = map.keySet(); keys.stream().filter(s -> s.contains("将军")).forEach(s -> System.out.println(s));//雷电将军 //操作值 Collection<String> values = map.values(); values.stream().filter(s -> s.contains("妻")).forEach(s -> System.out.println(s));//稻妻 //方案二:转换为map.entry类型,即map集合的实体,以组装键值的方式 Set<Map.Entry<String, String>> entries = map.entrySet(); entries.stream().filter(s -> s.getValue().contains("妻")&&s.getKey().contains("将军")).forEach(s -> System.out.println(s));//雷电将军=稻妻 }
2.获取数组的Stream流方法:public static<T> Stream<T> of(T...values) 或者使用 public static <T> Stream<T> stream(T[ ] array)
没错数组获得stream流有两个方法
public static void main(String[] args) { String[] str ={"蒙德","璃月","稻妻","枫丹","须弥","纳塔","至冬"}; //Arrays工具类自带的Stream()方法 Stream<String> st1 = Arrays.stream(str); st1.filter(s -> s.contains("月")).forEach(s -> System.out.println(s));//璃月 //Stream类自带的of()方法 Stream<String> st2 = Stream.of(str); st2.filter(s -> s.contains("月")).forEach(s -> System.out.println(s));//璃月 }
Stream流中间方法
中间方法指的是当Stream流调用完此类方法之后,依旧会返回一个新的Stream流,可以继续使用(支持链式编程)
1.Stream <T> filter(Predicate <? super T> predicated )过滤方法,过滤流后的条件
int[] arr={5,8,6,7,3}; //Stream <T> filter(Predicate <? super T> predicated ) //过滤方法 //筛选大于5的数 Arrays.stream(arr).filter(s -> s>5).forEach(System.out::println);//8,6,7
2.Stream<T> sorted(Comparator <? super T> comparator)排序方法,可以将基本类型进行升序排序,而对象则是配置比较器
//Stream<T> sorted(Comparator <? super T> comparator) //排序方法 //默认升序排序,或者根据比较器比较 ArrayList<Student> list = new ArrayList<>(); Collections.addAll(list,new Student("温迪","boy",501),new Student("钟离","boy",3000),new Student("雷电将军","girl",600),new Student("纳西妲","gril",500)); list.stream().filter(s->s.getGrade()>500).sorted((a,b)->a.getGrade()-b.getGrade()).forEach(System.out::print);
//Student{name='温迪', sec='boy', grade=501}Student{name='雷电将军', sec='girl', grade=600}Student{name='钟离', sec='boy', grade=3000}
3.Stream<T> limit(long maxSize)分页函数,显示参数列表限定的列
//Stream<T> limit(long maxSize) //分页方法 //将集合中岁数最大的两个人展示出来人物 list.stream().sorted((a,b)->-(a.getGrade()-b.getGrade())).limit(list.size()-2).forEach(System.out::print); //Student{name='钟离', sec='boy', grade=3000}Student{name='雷电将军', sec='girl', grade=600} System.out.println();
4.Stream<T> skip(long n)逆分页函数,显示最后的n列,和limit函数相反
//Stream<T> skip(long n) //逆分页函数 //将集合中最后的两个人物展示出来 list.stream().sorted((a,b)->a.getGrade()-b.getGrade()).skip(2).forEach(System.out::print); //Student{name='雷电将军', sec='girl', grade=600}Student{name='钟离', sec='boy', grade=3000}
5.Stream<T> distinct() 去重函数,可以将流中的相同元素去掉
//Stream<T> distinct() //去重函数 //新增一个一模一样的元素,使用此函数去掉它 list.add(new Student("温迪","boy",501)); list.stream().filter(s->s.getName().contains("温迪")).forEach(System.out::print); //Student{name='温迪', sec='boy', grade=501}Student{name='温迪', sec='boy', grade=501} //去重 list.stream().filter(s->s.getName().contains("温迪")).distinct().forEach(System.out::print); //Student{name='温迪', sec='boy', grade=501}
6.<R> Stream<R> map(Function <? super L,? extends T> mapper) 提取函数,加工函数,可以将流中的单个属性提取出来
//<R> Stream<R> map(Function <? super L,? extends T> mapper) //提取函数,加工函数 //将集合中的人物都只输出名字,其它信息不输出 list.stream().distinct().map(s->s.getName()).forEach(System.out::print); //温迪钟离雷电将军纳西妲
7.static <T> Stream<T> concat(Stream s1,Stream s2) 连接函数,连接两个流
//static <T> Stream<T> concat //连接函数 //将两个流连接成一个新流 Stream<Integer> s1 = Stream.of(1, 2); Stream<Integer> s2 = Stream.of(9, 5); Stream.concat(s1,s2).sorted().forEach(System.out::println); //1 2 5 9
Stream终结方法
总结方法指的是调用完成以后,不会返回新的Stream了没有办法使用流了
1.void ForEach(Consumer action)对最后的流进行遍历
//void ForEach(Consumer action) //对运算后的流进行遍历 list.stream().forEach(System.out::print); //Student{name='温迪', sec='boy', grade=501}Student{name='钟离', sec='boy', grade=3000}Student{name='雷电将军', sec='girl', grade=600}Student{name='纳西妲', sec='gril', grade=500} System.out.println();
2.long count()计算函数,计算当前流中的元素个数
//long count() //统计计算,计算流中的元素个数 long count = list.stream().count(); System.out.println(count);//4
3.Optional<T> max(Comparator <? super T > comparator)在流中找到最大元素并且返回
//Optional<T> max(Comparator <? super T > comparator) //返回最大的元素项,但是需要制定比较规则 Optional<Student> max = list.stream().max((a, b) -> a.getGrade() - b.getGrade()); System.out.println(max);//name='钟离', sec='boy', grade=3000}
4.Optional<T> min(Comparator <? super T > comparator) 返回最小的元素项,但是需要制定比较规则
//Optional<T> min(Comparator <? super T > comparator) //返回最小的元素项,但是需要制定比较规则 Optional<Student> min = list.stream().min((a, b) -> a.getGrade() - b.getGrade()); System.out.println(min); //Student{name='纳西妲', sec='gril', grade=500}
Java类型返回
在进行参数传递的时候,并不希望传递的是流,而是原类型(集合/数组)
所以还有两个终结方法特别重要,也就是流转集合,流转数组
1.R collect(Collector collector)流转集合
//流转集合 --注意点:流是一次性的,也就是说,一次终结方法使用后,就没有了,需要再次转为流 //转List集合 Stream<Student> s1 = list.stream();//集合构造的流 List<Student> l1 = s1.sorted((a, b) -> a.getGrade() - b.getGrade()).collect(Collectors.toList()); //转为Set集合 Stream<Student> s2 = list.stream(); Set<Student> l2 = s2.sorted((a, b) -> a.getGrade() - b.getGrade()).collect(Collectors.toSet()); //转为map集合 --注意点,map是双列集合,需要告诉map,那个属性是键,那个属性是值 Stream<Student> s3 = list.stream(); Map<Integer, String> map = s3.sorted((a, b) -> a.getGrade() - b.getGrade()).collect(Collectors.toMap(a -> a.getGrade(), b -> b.getName()));
2.Object[ ] toArray() 流转数组
//流转数组 Stream<Student> s4 = list.stream();//获得流 Student[] arr = s4.toArray(s -> new Student[s]);//s -> new Student[s]封装数组类型,可以不写,就是Object for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } /* Student{name='温迪', sec='boy', grade=501} Student{name='钟离', sec='boy', grade=3000} Student{name='雷电将军', sec='girl', grade=600} Student{name='纳西妲', sec='gril', grade=500} */