JavaImprove--Lesson08--集合框架二,Collection工具类,Map集合,Stream流

一.前置知识-Collections工具类

可变参数

可变参数是一种特殊的形参,定义在方法,构造器参数列表中,格式是:数据类型...参数名称;

可变参数的特点和好处:

  • 特点:可以不传递参数给它,可以传递一个或多个参数给他,也可以传递一个数组给他
  • 好处:常常用于灵活的接收数据

可变参数的注意事项:

  1. 可变参数在方法内部就是一个数组
  2. 一个形参列表中只能有一个可变参数
  3. 可变参数必须在参数列表的最后
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这类单列集合,工具类有一些综合优化的方法

1.public static <T> boolean addAll(collection<? super T> c,T...elements)

使用集合名.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流使用步骤

 总的来说就是三个过程:

  1. 获取Stream流
  2. 使用Stream流常见的中间方法
  3. 使用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}
 */

 

posted @ 2024-04-11 22:01  回忆也交给时间  阅读(10)  评论(0编辑  收藏  举报