Java8的新特性

一、Java8新特性
1、新特性
接口的变化
第三代日期时间API
HashMap底层变化
...
Lambda表达式
StreamAPI
Optional类

2、Lambda表达式是一种新的语法糖,它是让Java开始支持“函数式编程”的语法。
Java原来说自己是面向对象的编程语言,所有的操作几乎都是基于对象,“一切皆对象”,
如果某个变量或形参是引用数据类型,那么必须给他赋值一个对象。

在开发中,有时候我们并不关心这个对象,我们关心的是这个对象的某个方法的功能,方法体代码,其实就是函数的实现。
按照面向对象的编程思想,为了传递这段代码,还要new一个对象,就显得有点啰嗦。

函数式编程思想:把函数当成数据传递

public class TestJava8 {
    @Test
    public void test06(){
        //有一个字符串数组
        String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};
        //希望对这个数组进行排序,不区分大小写的排序
        //调用Arrays工具类有sort方法
        Arrays.sort(arr);//区分大小写的,因为这个方法是调用元素的类型String的compareTo方法排序
        System.out.println(Arrays.toString(arr));//[Chai, Hi, Jock, atguigu, hello, world]

        Arrays.sort(arr, String::compareToIgnoreCase);
        System.out.println(Arrays.toString(arr));//[atguigu, Chai, hello, Hi, Jock, world]
    }

    @Test
    public void test05(){
        //有一个字符串数组
        String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};
        //希望对这个数组进行排序,不区分大小写的排序
        //调用Arrays工具类有sort方法
        Arrays.sort(arr);//区分大小写的,因为这个方法是调用元素的类型String的compareTo方法排序
        System.out.println(Arrays.toString(arr));//[Chai, Hi, Jock, atguigu, hello, world]

        Arrays.sort(arr, (o1, o2) -> o1.compareToIgnoreCase(o2));
        System.out.println(Arrays.toString(arr));//[atguigu, Chai, hello, Hi, Jock, world]
    }

    @Test
    public void test04(){
        //有一个字符串数组
        String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};
        //希望对这个数组进行排序,不区分大小写的排序
        //调用Arrays工具类有sort方法
        Arrays.sort(arr);//区分大小写的,因为这个方法是调用元素的类型String的compareTo方法排序
        System.out.println(Arrays.toString(arr));//[Chai, Hi, Jock, atguigu, hello, world]

        Arrays.sort(arr, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareToIgnoreCase(o2);
            }
        });
        System.out.println(Arrays.toString(arr));//[atguigu, Chai, hello, Hi, Jock, world]
    }

    @Test
    public void test03(){
        //需求:用实现Runnable接口的方式启动一个线程,打印“hello"
        new Thread(() ->System.out.println("hello")).start();
    }

    @Test
    public void test02(){
        //需求:用实现Runnable接口的方式启动一个线程,打印“hello"
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        }).start();
    }

    @Test
    public void test01(){
        //需求:用实现Runnable接口的方式启动一个线程,打印“hello"
        MyRunnable my = new MyRunnable();
        Thread t = new Thread(my);
        t.start();
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("hello");
    }
}

二、函数式接口
Java中引入了函数式编程思想,但是只有部分情况才支持使用。
Lambda表达式只针对“函数式接口”使用。

1、函数式接口,是SAM接口(Single Abstract Method)有唯一的抽象方法的接口。

回忆之前学过的接口:

1)java.lang.Runnable:
        抽象方法:public abstract void run();
(2)java.util.Comparator<T>
        抽象方法:    int compare(T o1, T o2);

        从Comparator<T>接口中看有一个boolean equals(Object obj);,形式也像是抽象方法,
        但是不要求我们实现Comparator<T>接口的类强制重写,因为实现类默认从Object继承了一个equals方法的实现。
        但是它建议我们重写equals方法。希望equals方法的实现与compare方法的实现,逻辑一致。
        例如:Student类 有重写equals方法,按照id来比较的,认为两个id相同的学生对象就是同一个学生对象,
            Student类有一个Comparator的比较器StudentComparator,它实现Comparator<T>接口,重写compare方法,
            这个方法中说两个学生的成绩score相等,就返回0,就代表他们是“相等”的对象。

            如果说,我们把学生对象放到TreeSet中,有序的集合。
(3)java.lang.Comparable<T>
        抽象方法:int compareTo(T o);
 (4)java.lang.Iterable<T>  :实现它,支持foreach遍历
         抽象方法:Iterator<T> iterator();
 (5)java.util.Iterator<T> 不是函数式接口,因为有多个抽象方法
        抽象方法:
            boolean hasNext();
            E next();
 (6)java.io.Serializable:不是函数式接口,因为没有抽象方法
 (7)java.lang.Cloneable:不是函数式接口,因为没有抽象方法
 (8)Collection,List,Set,Map都不是,因为有多个抽象方法
 (9)java.io.FileFilter
        抽象方法:
            boolean accept(File pathname);

总结:之前学过的接口中,满足函数式接口的要求的
    java.lang.Runnable、java.util.Comparator<T>、java.lang.Comparable<T>、java.lang.Iterable<T>、java.io.FileFilter

2、建议对满足函数式接口的特点的接口,并且接口上面标记了@FunctionalInterface注解的接口,再使用Lambda表达式。
  上面满足函数式接口的特点的接口中,标记@FunctionalInterface注解的有:
  java.lang.Runnable
  java.util.Comparator<T>
  java.io.FileFilter

java.lang.Comparable<T>接口,没有标记@FunctionalInterface注解,因为它的实现类通常是要比较大小的元素的类型,
例如:Student类本身实现这个接口,这个类里面信息非常丰富,一般不会只关注 compareTo这一个方法。
而我们使用Lambda表达式的场景,通常都是只关注接口的抽象方法的实现的常量。

java.lang.Iterable<T>接口,也没有标记@FunctionalInterface注解,因为它的实现类通常都是容器,ArrayList等,
它里面也不可能值关注Iterator<T> iterator();这一个方法。需要关注更多的add等方法。

总结:学过的接口中只有三个接口,可以使用Lambda表达式。其他的暂时不建议使用。

public class TestFunctionalInterface {
    public static void main(String[] args) {
        //TreeSet的特点:有序(按大小顺序),不可重复
        TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
           @Override
            public int compare(Student o1, Student o2) {
                return o1.getScore() - o2.getScore();
            }//错误的


 /*           @Override
            public int compare(Student o1, Student o2) {
                int result = o1.getScore() - o2.getScore();
                return result == 0 ? o1.getId() - o2.getId(): result;
            }*/
        });

        set.add(new Student(1,"张三",86));
        set.add(new Student(1,"张三",86));
        set.add(new Student(2,"李四",86));

        System.out.println(set);//[Student{id=1, name='张三', score=86}]
    }
}
class Student{
    private int id;
    private String name;
    private int score;

    public Student(int id, String name, int score) {
        this.id = id;
        this.name = name;
        this.score = score;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", score=" + score +
                '}';
    }

    /*@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id; //为了突出问题,故意只选id
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }*/
}

三、Lambda表达式
1、语法结构
    (Lambda表达式的形参列表) -> {Lambda体}

说明:
(1)->:称为箭头操作符,或Lambda操作符
    由减号和大于号组成,中间不能有空格
(2)  (Lambda表达式的形参列表),它其实就是 你的lambda表达式要针对的函数式接口的抽象方法的形参列表
    例如:Lambda表达式是针对java.lang.Runnable接口使用,Lambda表达式的形参列表就是()
            因为java.lang.Runnable接口的抽象方法 void run()

         Lambda表达式是针对java.util.Comparator<T>接口使用,Lambda表达式的形参列表就是(T t1, T t2)
            因为java.util.Comparator<T>接口的抽象方法 int compare(T t1, T t2)


         Lambda表达式是针对java.util.function.Consumer<T>接口使用,Lambda表达式的形参列表就是(T t)
            因为   java.util.function.Consumer<T>接口的抽象方法void accept(T t)
(3) {Lambda体},它其实就是你实现这个抽象方法的方法体

2、Lambda表达式的作用是给函数式接口的变量/形参赋值的

3、Lambda表达式可以简化
(1)当{Lambda体}只有一个语句,可以省略{}和语句后面的;
(2)当{Lambda体}只有一个语句,可以省略{}和语句后面的;。如果此时是return语句,要把return也省略
(3)当(Lambda表达式的形参列表)的形参列表的形参类型是已知的,或者是可以根据泛型自动推断的,那么类型可以省略
(4)如果(Lambda表达式的形参列表)的形参列表的类型省略了,并且形参只有一个,那么()可以省略,
        如果形参列表是多个参数,()不能省略
        如果形参列表是空参(),那么()不能省略

public class TestLambda {
    @Test
    public void test04(){
        //需求:列出"D:\to_student\尚硅谷_210728Java_柴林燕_JavaSE\预装软件"目录下的.exe文件
        File dir = new File("D:\\to_student\\尚硅谷_210728Java_柴林燕_JavaSE\\预装软件");

        /*
        调用java.io.File类的public File[] listFiles(FileFilter filter)方法
        这个方法的形参是 (FileFilter filter),FileFilter是函数式接口,可以使用Lambda表达式赋值
            FileFilter接口的抽象方法 boolean accept(File pathname)

            Lambda表达式 :(File pathname) -> { 过滤文件或目录的条件}
            Lambda表达式 :(File pathname) -> { return pathname.getName().endsWith(".exe");}

         */
//        File[] files = dir.listFiles((File pathname) -> { return pathname.getName().endsWith(".exe");});

        //(2)当{Lambda体}只有一个语句,可以省略{}和语句后面的;。如果此时是return语句,要把return也省略
//        File[] files = dir.listFiles((File pathname) ->pathname.getName().endsWith(".exe"));

        //(3)当(Lambda表达式的形参列表)的形参列表的形参类型是已知的,或者是可以根据泛型自动推断的,那么类型可以省略
//        File[] files = dir.listFiles((pathname) ->pathname.getName().endsWith(".exe"));

        //(4)如果(Lambda表达式的形参列表)的形参列表的类型省略了,并且形参只有一个,那么()可以省略,
        File[] files = dir.listFiles(pathname ->pathname.getName().endsWith(".exe"));

        for (File file : files) {
            System.out.println(file);
        }
    }

    @Test
    public void test03(){
        //需求:列出"D:\to_student\尚硅谷_210728Java_柴林燕_JavaSE\预装软件"目录下的.exe文件
        File dir = new File("D:\\to_student\\尚硅谷_210728Java_柴林燕_JavaSE\\预装软件");

        File[] files = dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {//(File pathname)参数是dir这个目录的下一级文件或目录对象
                return pathname.getName().endsWith(".exe");
            }
        });
        for (File file : files) {
            System.out.println(file);
        }
    }

    @Test
    public void test02(){
        //有一个字符串数组
        String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};
        //希望对这个数组进行排序,不区分大小写的排序
        //调用Arrays工具类有public static <T> void sort(T[] a, Comparator<? super T> c)
        //T[] a:接收一个对象数组,给哪个数组排序,就传哪个数组
        //Comparator<? super T> c:需要一个Comparator接口的实现类对象
        /*
        这里不传Comparator接口的实现类对象,我们用Lambda表达式给他赋值。
            Comparator接口的抽象方法 int compare(T t1, T t2)
         Lambda表达式:
            (T t1, T t2) -> {写t1,t2的排序规则}

           T类型是什么? 因为Comparator接口的实现类对象,或者说,int compare(T t1, T t2)是给arr数组的元素排序,
                        这个数组是String[]类型,元素是String类型,这个T就是String类型

         Lambda表达式:
            (String t1, String t2) -> {写t1,t2的排序规则,区分大小写的排序}
            (String t1, String t2) -> {t1.compareToIgnoreCase(t2);}

         因为  int compare(T t1, T t2)有返回值类型,是int,所以在{}中需要return语句

         Lambda表达式:
            (String t1, String t2) -> {return t1.compareToIgnoreCase(t2);}
         */
//        Arrays.sort(arr, (String t1, String t2) -> {return t1.compareToIgnoreCase(t2);});

        //(2)当{Lambda体}只有一个语句,可以省略{}和语句后面的;。如果此时是return语句,要把return也省略
//        Arrays.sort(arr, (String t1, String t2) -> t1.compareToIgnoreCase(t2));

        //(3)当(Lambda表达式的形参列表)的形参列表的形参类型是已知的,或者是可以根据泛型自动推断的,那么类型可以省略
        Arrays.sort(arr, (t1, t2) -> t1.compareToIgnoreCase(t2));
    }

    @Test
    public void test01(){
        //需求:用实现Runnable接口的方式启动一个线程,打印“hello"
        //Thread(Runnable target),构造器需要一个Runnable接口的实现类对象,现在要传Lambda表达式
        /*
        使用Lambda表达式给(Runnable target)参数赋值。
        java.lang.Runnable接口的抽象方法 void run()

        Lambda表达式:
            () -> {System.out.println("hell");}
         */
//        new Thread(() -> {System.out.println("hello");}).start();

        //当{Lambda体}只有一个语句,可以省略{}和语句后面的;
        new Thread(() -> System.out.println("hello")).start();
    }
}

4、演示Java8新增的函数式接口的Lambda表达式的应用
(1)消费型接口
    Consumer<T>  void accept(T t)

 Java8不仅增加了函数式接口,还升级了集合框架的API。
 在Java8版的java.lang.Iterable<T>接口中新增了一个默认方法:
    default void forEach(Consumer<? super T> action):这个方法的作用是用来遍历/迭代 容器中的元素

 观察这个方法的形参列表是   (Consumer<? super T> action),形参的类型Consumer是函数式接口,
 所以调用这个方法,可以传入Lambda表达式。

 因为Collection系列的集合继承/实现了Iterable<T>接口,所以Collection系列的集合都有了forEach方法。

 (2)消费型接口
 BiConsumer<T,U> 抽象方法  void accept(T t, U u)

 Java8在java.util.Map<K,V>接口中也增加
    default void forEach(BiConsumer<? super K,? super V> action)

 forEach方法的形参:BiConsumer,它也是函数式接口,并且是Consumer的变形,也是消费性接口

 (3)判断型接口
 Predicate<T> 抽象方法  boolean test(T t)

 Java8版本,对Collection接口增加了方法 default boolean removeIf(Predicate<? super E> filter)
 这个方法的形参类型:Predicate,它是函数式接口,是判断型函数式接口

 (4)功能型接口
BiFunction<T,U,R>  抽象方法  R apply(T t, U u)


 在JDK1.8时Map接口增加方法:
    default void replaceAll(BiFunction<? super K,? super V,? extends V> function)

 replaceAll方法的形参是   BiFunction,它是功能型接口

public class TestLambda2 {
    @Test
    public void test05(){
        HashMap<Integer,String> map = new HashMap<>();
        map.put(1,"hello");
        map.put(2,"world");
        map.put(3,"java");

        //需求:把value的字符串中包含o字母的字符串,换成大写
        //调用default void replaceAll(BiFunction<? super K,? super V,? extends V> function)
        /*
        replaceAll方法的形参是   BiFunction,它是功能型接口,抽象方法  R apply(T t, U u)
        Lambda表达式:
            T:是map的key类型
            U:是map的value类型
            (Integer key, String value) -> {把value的字符串中包含o字母的字符串,换成大写}
            (Integer key, String value) -> {
                        if(value.contains("o")){
                            return value.toUpperCase();//返回转为大写的value
                        }
                        return value;//返回原来的value
            }
         */
        map.replaceAll((Integer key, String value) -> {
            if(value.contains("o")){
                return value.toUpperCase();//返回转为大写的value
            }
            return value;//返回原来的value
        });

        //简化:
        map.replaceAll((key, value) -> value.contains("o") ? value.toUpperCase() : value);

        System.out.println(map);
    }
    @Test
    public void test04(){
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "hello","java","world","mysql");

        //需求,删除字符串中包含o字母的字符串
       //调用default boolean removeIf(Predicate<? super E> filter)
        /*
        形参是Predicate,它是函数式接口,是判断型函数式接口,抽象方法boolean test(T t)
                调用这个方法,需要传递参数,这个参数传递进去之后,在test方法肯定是用于条件判断,满足xx条件返回true,否则返回false。
        Lambda表达式:(T t)->{判断t是否满足删除条件}
            T类型就是集合元素的类型,String类型
        Lambda表达式:(String t)->{ return t.contains("o");}
        */
//        list.removeIf((String t)->{ return t.contains("o");});

        //简化
//        list.removeIf(t->t.contains("o"));

        list.removeIf(element->element.contains("o")); //修改形参名,使得可读性更强

        System.out.println(list);
    }

    @Test
    public void test03(){
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "hello","java","world","mysql");

        //需求,删除字符串中包含o字母的字符串
        //原来Collection系列的集合,要根据条件删除,必须使用Iterator迭代器
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            String str = iterator.next();
            if(str.contains("o")){
                iterator.remove();
            }
        }
    }

    @Test
    public void test02(){
        HashMap<Integer,String> map = new HashMap<>();
        map.put(1,"hello");
        map.put(2,"world");
        map.put(3,"java");

        //调用map的default void forEach(BiConsumer<? super K,? super V> action)
        /*
        形参BiConsumer,它也是函数式接口,它的抽象方法void accept(T t, U u)
        Lambda表达式:  (T t, U u) 这个T和U其实就是Map的K,V
            (Integer key, String value) -> {遍历键值对}
         */
//        map.forEach((Integer key, String value) -> {System.out.println(key+":" + value);});

        //简化
        map.forEach((key, value) -> System.out.println(key+":" + value));
    }

    @Test
    public void test01(){
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "hello","java","world","mysql");

        //调用default void forEach(Consumer<? super T> action)方法
        /*
        形参是:Consumer类型,它是函数式接口,它的抽象方法 void accept(T t)
        lambda表达式:
            (T t) -> {遍历集合的元素}
         T的类型,由集合的元素类型决定,是String类型
          lambda表达式:
            (String t) -> {System.out.println(t);}
         */
//        list.forEach((String t) -> {System.out.println(t);} );

        //简化
        list.forEach(t -> System.out.println(t));
    }
}

四、方法引用和构造器引用
1、Lambda表达式是用于给“函数式接口(SAM接口,只有一个抽象方法的接口)”的变量/形参赋值的一种语法。
可以简化/替代原来的匿名内部类的方式实现函数式接口的代码。

方法引用是对Lambda表达式的再次简化。

2、方法引用和构造器引用的语法格式:
  方法引用:  类名/对象名 :: 方法名
  构造器引用: 类名 :: new

3、说明
(1)当{lambda体}中只有一个语句,并且这个语句是调用现有的对象/类的方法来完成。
(2)Lambda表达式的(形参列表)中的形参,要么作为调用方法的对象,要么作为调用方法的实参,所有形参都用上了
(3)在整个{lambda体}中,没有使用到额外的数据/对象等

例如:Arrays.sort(arr, (t1, t2) -> t1.compareToIgnoreCase(t2));
(1){lambda体}中只有一个语句    return t1.compareToIgnoreCase(t2);
(2)Lambda表达式的(形参列表)是(t1, t2),t1是作为调用compareToIgnoreCase方法的对象,
                                      t2是作为调用compareToIgnoreCase方法的实参。

例如:list.forEach(t -> System.out.println(t));
(1){lambda体}中只有一个语句    System.out.println(t)
(2)Lambda表达式的(形参列表)是(String t) , t作为println方法调用的实参。

例如:new Thread(()->System.out.println("hello")).start();
(1){lambda体}中只有一个语句   System.out.println("hello")
(2)Lambda表达式的(形参列表)是(),没有参数可以使用
(3)在整个{lambda体}中,没有使用到额外的数据/对象等   不满足,因为出现了"hello"

public class TestFunction {

    @Test
    public void test04(){
        //供给型接口Supplier<T>,抽象方法T get()
        //Lambda表达式可以直接给函数式接口的变量赋值
//        Supplier<String> s = () -> new String();

        //使用构造器引用进行简化
        Supplier<String> s = String :: new;
    }

    @Test
    public void test03(){
        //需求:用实现Runnable接口的方式启动一个线程,打印“hello"
        new Thread(()->System.out.println("hello")).start();//多线程打印了一个"hello"

        //是否可以使用方法引用进行简化呢? 不能简化
        new Thread(System.out::println).start(); //多线程只是打印了一个空行
    }
    @Test
    public void test02(){
        //集合遍历
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list,"hello","java","world");

        //调用forEach方法进行遍历
        //forEach的形参类型是Consumer<T>,抽象方法 void accept(T t)
//        list.forEach(t -> System.out.println(t));

        //使用方法引用进行简化
        list.forEach(System.out::println);
    }

    @Test
    public void test01(){
        //有一个字符串数组
        String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};

        //实现排序,不区分大小写的排序
//        Arrays.sort(arr, (t1, t2) -> t1.compareToIgnoreCase(t2));

        //使用方法引用来进行简化
        Arrays.sort(arr, String::compareToIgnoreCase);

        System.out.println(Arrays.toString(arr));
    }
}

4、JDK1.8中新增加了很多函数式接口给我们使用,它们在java.util.function包。
四大代表:
(1)Consumer<T>,消费型接口       抽象方法:void accept(T t)              

特点:有参无返回值       调用这个方法,需要给他传递实参,但是得不到返回值,“有去无回”
(2)Supplier<T>,供给型接口     抽象方法:T get()                    

   特点:无参有返回值       调用者方法,不需要传递参数,但是可以得到一个返回值,“空手套白狼”       这个接口的抽象方法,相当于“奉献型”
 (3)Predicate<T>,判断型接口,断定型接口     抽象方法:boolean test(T t)      

       特点:有参有返回值,但是返回值类型是boolean
                 调用这个方法,需要传递参数,这个参数传递进去之后,在test方法肯定是用于条件判断,满足xx条件返回true,否则返回false。
(4)Function<T,R>:功能型接口      抽象方法:R apply(T t)          

    特点:有参有返回值,参数的类型和返回值的类型不确定,并且可能不一样
            调用这个方法,需要传递参数,也可以得到一个返回值,但是参数的类型和返回值的类型,需要在使用时才能确定,什么都有可能。所以这个方法相当于只是完成一个功能。

其他接口,都是上面这些接口的变形:
(1)Consumer<T>,消费型接口

bi:binary二元的,两个的

(2)Supplier<T>,供给型接口

 

(3)(3)Predicate<T>,判断型接口,断定型接口 

(4)Function<T,R>:功能型接口

 

 UnaryOperator:一元的,一个参数一个返回值,参数类型和返回值类型一样
 BinaryOperator = Bi + UnaryOperator,有两个参数,一个返回值,参数和返回值的类型都是一样的

 5、自定义函数式接口
(1)这个接口只能定义一个抽象方法
(2)这个接口加@FunctionalInterface注解

public class TestFunctionalInterface3 {
}

@FunctionalInterface
interface MyInterface{
    void method();
}

//下面这个接口就没必要定义,因为java8的 java.util.function包下已经有相似的类型
//| IntConsumer          | void accept(int value)         | 接收一个int值
@FunctionalInterface
interface OtherInterface{
    void method(int a);
}

五、Java8新增了一个工具类/容器:java.util.Optional<T>
1、Optional类的对象是一个容器,容器就是用来装对象的
可能里面是空的,可能里面有一个对象,
这个类通常用来当做一个方法的返回值来使用,避免返回null值。

到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。
以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,
Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。
受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。

例如:Stream的API中的一些方法返回值就是Optional类。
| **Optional<T>** **findFirst()**            | 返回第一个元素                                               |
| **Optional<T>** **findAny()**              | 返回当前流中的任意元素                                       |                                       |
| **Optional<T>** **max(Comparator c)**      | 返回流中最大值                                               |
| **Optional<T>** **min(Comparator c)**     |返回流中的最小值
因为流中可能没有元素,那么这些方法就没有真正的元素返回,如果没有Optional,就需要返回null,
调用这些方法的地方,就可能发生空指针异常。

2、如何创建一个Optional类的对象
(1)创建一个空的容器对象       Optional.empty();
(2)创建一个容器对象,里面可能包含空值,可能包含具体值  Optional.ofNullable(x);  x可能为空
(3)创建一个非空的容器对象,  Optional.of(x);  x一定是非空

3、如花使用Optional
(1)判断是否为空
boolean isPresent()

(2)获取里面的对象
T get()  :取出Optional容器中的对象,要求里面必须有对象,否则抛出 NoSuchElementException
T orElse(T other)  :如果Optional容器中有对象,那么直接返回里面 的对象,否则返回备胎
T orElseGet(Supplier<? extends T> other)  :如果Optional容器中有对象,那么直接返回里面 的对象,否则返回Supplier供给型接口提供的备胎

(3)void ifPresent(Consumer<? super T> consumer)

public class TestOptional {
    @Test
    public void test01(){
        Optional<Object> empty = Optional.empty();
        System.out.println(empty);
    }

    @Test
    public void test03(){
        Optional<String> opt = findGirlFriend2("汪飞");
        System.out.println(opt);
        System.out.println("汪飞的女朋有是否存在: " + opt.isPresent());
        System.out.println("女朋友:" + opt.get());
        opt.ifPresent(System.out::println);//如果女朋友存在,就打印,否则就不打印

        Optional<String> opt2 = findGirlFriend2("李天棋");
        System.out.println("opt2 = " + opt2);// Optional.empty
        System.out.println("李天棋的女朋有是否存在: " + opt2.isPresent());
//        System.out.println("女朋友:" + opt2.get());
        System.out.println("女朋友:" + opt2.orElse("石榴姐"));
        System.out.println("女朋友:" + opt2.orElseGet(String::new));//空字符串
        opt2.ifPresent(System.out::println);//如果女朋友存在,就打印,否则就不打印

    }

    public Optional<String> findGirlFriend2(String boyName){
        HashMap<String,String > map = new HashMap<>();//这个集合后面是数据库中的数据
        map.put("汪飞", "如花");
        map.put("二虎", "翠花");
        map.put("邱世玉", "似玉");
        map.put("李天棋", null);

        return Optional.ofNullable(map.get(boyName));
    }

    @Test
    public void test02(){
        String girl1 = findGirlFriend("汪飞");
        System.out.println("girl1 = " + girl1);
        System.out.println("姓:" + girl1.charAt(0));

        String girl2 = findGirlFriend("李天棋");
        System.out.println("girl2 = " + girl2);
        System.out.println("姓:" + girl2.charAt(0));
    }
    public String findGirlFriend(String boyName){
        HashMap<String,String > map = new HashMap<>();//这个集合后面是数据库中的数据
        map.put("汪飞", "如花");
        map.put("二虎", "翠花");
        map.put("邱世玉", "似玉");
        map.put("李天棋", null);

        return map.get(boyName);
    }
}

六、StreamAPI
1、Stream:流
我们之前在IO章节学习的  InputStream,OutputStream 和我们今天要学习的Stream不是一回事,没关系。

2、Java8新增的包,java.util.stream
作用:针对集合等容器中的数据进行操作的。
使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。

数据库:简单理解数据仓库,用来存储数据的。
      数据库是一个管理系统,可以用来存储数据,并且管理数据(增删改查,备份...)
数据库分为:
    文件系统的数据库(数据存储在硬盘上)
    内存数据库(数据存储在内存中)

    存在硬盘上的好处:永久性保存,就算系统断电了,数据是不会丢失的
    内存数据库的好处:速度快

3、Stream的API的使用分为三步:
第一步:创建Stream
告诉Stream的数据来源是什么

第二步:中间操作,对数据进行处理

第三步:终结操作,看最后结果


4、Stream的API的特点
(1)Stream的对象是不可变对象,凡是修改会产生新Stream对象,必须重新接收
(2)Stream流的操作不会改变原数据
(3)中间操作可以是0~n步
(4)中间操作是一个延迟操作,在执行终结操作之前,中间不会执行,直到我们要执行终结操作,一口气执行
(5)一旦终结就结束了,除非创建新的Stream
(6)Stream不负责存储数据,只负责处理数据

public class TestStream {
    @Test
    public void test2() {
        ArrayList<Student> list = new ArrayList<>();
        list.add(new Student("张三",89));
        list.add(new Student("李四",45));
        list.add(new Student("王五",75));

        //第一步:创建Stream
        Stream<Student> stream = list.stream();

        //第二步:中间操作,对数据进行处理
        stream = stream.filter(s -> s.getScore()<60);//找出成绩不及格

        //        第三步:终结操作,看最后结果
        stream.forEach(System.out::println);

    }
    @Test
    public void test(){
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list,"hello","world","java","hao");

        //第一步:创建Stream
        Stream<String> stream = list.stream();

        //第二步:中间操作,对数据进行处理
        stream = stream.filter(t -> t.contains("o"));//只留下包含“o”字母的单词
        stream = stream.sorted();

//        第三步:终结操作,看最后结果
        stream.forEach(System.out::println);

        System.out.println(list);
    }
}

class Student{
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        System.out.println("getScore方法被调用了");
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

5、创建Stream的方式
(1)集合.stream()
Collection系列的集合,在Java8之后增加了一个方法:default Stream<E> stream()

Map系列的集合,要先转为Collection系列的集合再获取Stream。

(2)Arrays.stream(数组)
在数组工具类Arrays中增加了一批stream,可以根据数组来创建Stream

(3)Stream接口的静态方法,of方法,创建一个有限的Stream
Stream.of(T... args)
Stream.of(T t)

(4)Stream接口的静态方法,generate和iterate,创建无限的Stream
static <T> Stream<T> generate(Supplier<T> s)
static <T> Stream<T> iterate(T seed,  UnaryOperator<T> f)

public class TestCreateStream {
    @Test
    public void test11(){
        /*
        static <T> Stream<T> iterate(T seed,  UnaryOperator<T> f)
        iterate方法的形参有两个:
        第一个是T seed,种子
        第二个是UnaryOperator<T>,它是今天学习的函数式接口的一种,
                UnaryOperator<T>抽象方法T apply(T t) ,有参有返回值,参数和返回值的类型一样

          需求:从种子(数字1)开始,不断的累加2
         */
        //第一步创建Stream
        Stream<Integer> stream = Stream.iterate(1, t -> t + 2);

        //第三步:终结操作
        stream.forEach(System.out::println);
    }
    @Test
    public void test10(){
        /*
        static <T> Stream<T> generate(Supplier<T> s)
        generate方法的形参类型是Supplier<T>,它是供给型函数式接口,抽象方法:T get()
        Lambda表达式可以给  (Supplier<T> s)形参赋值
          Lambda表达式:
                () -> {有一个语句,可以产生数据}


         */
        //第一步创建Stream
//        Stream<Double> stream = Stream.generate(() -> {return Math.random();});

//        Stream<Double> stream = Stream.generate(() -> Math.random());

        //使用方法引用简化
        Stream<Double> stream = Stream.generate(Math::random);

        //第三步:终结操作
        stream.forEach(System.out::println);
    }

    @Test
    public void test9(){
        Stream<String> stream = Stream.of("hello", "java", "world");
    }
    @Test
    public void test8(){
        Integer[] arr = {1,2,3,4};
        Stream<Integer> stream = Arrays.stream(arr);

    }

    @Test
    public void test4(){
        int[] arr = {1,2,3,4};
        IntStream stream = Arrays.stream(arr);

    }

    @Test
    public void test3(){
        String[] arr = {"hello","java","world"};

        Stream<String> stream = Arrays.stream(arr);
    }
    @Test
    public void test2(){
        HashMap<Integer,String> map = new HashMap<>();
        map.put(1,"hello");
        map.put(2,"world");
        map.put(3,"java");

        Set<Integer> keys = map.keySet();
        Stream<Integer> keyStream = keys.stream();

        System.out.println("-------------------------");
        Collection<String> values = map.values();
        Stream<String> valueStream = values.stream();

        System.out.println("-------------------------");
        Set<Map.Entry<Integer, String>> entries = map.entrySet();
        Stream<Map.Entry<Integer, String>> entryStream = entries.stream();
    }

    @Test
    public void test(){
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list,"hello","world","java","hao");

        Stream<String> stream = list.stream();
    }
}

6、Stream的中间操作:这些方法的返回值类型都是Stream

(1)filter过滤
(2)distinct() 去重
(3)limit(long maxSize) 限制个数
(4)skip(long n)跳过前面的几个
(5)sorted():使用元素的自然排序,要求元素实现Comparable接口
   sorted(Comparator com),使用定制排序,指定Comparator比较规则

(6)peek(Consumer action):对流中的元素做action指定的操作,但是不修改元素
  (7)map(Function f):对流中的每一个元素映射xxx操作
(8)flatMap(Function f):把f函数式接口中的方法体得到的小的流的结果再合成大的Stream

public class TestMiddleStream {

    @Test
    public void test14(){
        String[] arr = {"hello","world","java"};
        //第一步:创建Stream
        Stream<String> stream = Arrays.stream(arr);

        //第二步:中间处理,把上面流中的每一个单词,拆开一个一个的字母
        //t.split("|"):拆分字符串,得到一个一个的String[]数组,元素是单个的字母
        //Stream.of(t.split("|"):把数组又构建成了一个小的Stream
        //map:把Stream.of(t.split("|")得到Stream作为新的流的元素
        Stream<Stream<String>> streamStream = stream.map(t -> Stream.of(t.split("|")));
        //第三步:终结操作
        streamStream.forEach(System.out::println);
    }

    @Test
    public void test13(){
        String str = "hello";
        String[] strings = str.split("|");
        System.out.println(Arrays.toString(strings));//[h, e, l, l, o]
    }
    @Test
    public void test12(){
        String[] arr = {"hello","world","java"};
        //第一步:创建Stream
        Stream<String> stream = Arrays.stream(arr);
        
        //第二步:中间处理,把上面流中的每一个单词,拆开一个一个的字母
        //t.split("|"):拆分字符串,得到一个一个的String[]数组,元素是单个的字母
        //Stream.of(t.split("|"):把数组又构建成了一个小的Stream
        //flatMap:把Stream.of(t.split("|")得到的一个一个的Stream,又合并成一个大的Stream
        Stream<String> stringStream = stream.flatMap(t -> Stream.of(t.split("|")));
        //第三步:终结操作
        stringStream.forEach(System.out::println);
    }

    @Test
    public void test10(){
        //有一个字符串数组
        String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};

        //取出每一个字符串的首字母
        /*
        map(Function f)方法的形参是Function<T,R>功能型接口,抽象方法:R apply(T t)
         */
        Arrays.stream(arr).map(t-> t.charAt(0)).forEach(System.out::println);

  /*      //第一步:创建Stream
        Stream<String> stream = Arrays.stream(arr);

        //第二步:中间处理
        Stream<Character> characterStream = stream.map(t -> t.charAt(0));

        //第三步:终结
        characterStream.forEach(System.out::println);*/
    }

    @Test
    public void test09(){
        //随机产生10个[0,100)的整数,然后取出前三名最大的数字
        List<Integer> collect = Stream.generate(() -> (int) (Math.random() * 100))
                .limit(10)
                .peek(System.out::println)
                .sorted((t1,t2)-> t2-t1)
                .limit(3)
                .collect(Collectors.toList());
        System.out.println(collect);
    }

    @Test
    public void test08(){
        //随机产生10个[0,100)的整数,然后取出前三名最大的数字
        List<Integer> collect = Stream.generate(() -> (int) (Math.random() * 100))
                .limit(10)
                .peek(System.out::println)
                .sorted()
                .skip(7)
                .collect(Collectors.toList());
        System.out.println(collect);
    }

    @Test
    public void test07(){
        //随机产生10个[0,100)的整数,然后取出前三名最大的数字
        Stream.generate(() -> (int)(Math.random()*100)).limit(10).sorted().skip(7).forEach(System.out::println);
    }
    @Test
    public void test06(){
        //有一个字符串数组
        String[] arr = {"hello","Hi","Chai","Jock","world","atguigu"};

        //按照字符串大小排序,不区分大小写
        //方案一:Arrays.sort
        //方案二:使用Stream
        Arrays.stream(arr).sorted(String::compareToIgnoreCase).forEach(System.out::println);
    }

    @Test
    public void test05() {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8);

        //跳过前面3个,再取3个
        list.stream().skip(3).limit(3).forEach(System.out::println);
    }

        @Test
    public void test04(){
        //使用Math.random()方法随机产生一些数据,取前10个
        Stream.generate(Math::random).limit(10).forEach(System.out::println);
    }


    @Test
    public void test03(){
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8);

        //获取集合中不重复的元素,即去掉重复元素
        //方案一:可以放到set中
        //方案二:可以使用Stream
        //三步连起来
        list.stream().distinct().forEach(System.out::println);
    }


    @Test
    public void test02(){
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,1,2,3,4,5,6,7,8);

        //获取list集合中的所有偶数
        //三步连起来
        list.stream().filter(t -> t % 2 == 0).forEach(System.out::println);
    }

    @Test
    public void test01(){
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,1,2,3,4,5,6,7,8);

        //获取list集合中的所有偶数
        //第一步:创建Stream
        Stream<Integer> stream = list.stream();

        //第二步:过滤,把偶数留下
        /*
        filter(Predicate p)方法的形参是判断型接口Predicate<T>,抽象方法 boolean test(T t)
         */
        stream = stream.filter(t -> t % 2 == 0);

        //第三步:终结操作,打印结果
        stream.forEach(System.out::println);
    }
}

7、Stream终结操作:当我们调用了Stream的某个方法,这个方法的返回值类型不是Stream类型,那么就说明这个操作是终结操作

 

(1)allMatch:判断流中的元素是否某匹配xx条件
(2)anyMatch:任意一个匹配
(3)noneMatch:没有一个匹配,都不匹配xx条件
(4)findFirst:返回第一个
     findAny:返回任意一个     如果这个流的数据是固定,那么findAny和findFirst
                            如果流是一个无限流,或者流中元素不固定,findAny的结果就和findFirst不同

 (5)long  count()
 (6)Optional<T> max(Comparator c)
    Optional<T> min(Comparator c)
 (7)void forEach(Consumer c)
 (8)T reduce(T iden, BinaryOperator b)
          U reduce(BinaryOperator b)
(9)R  collect(Collector c):把流中剩下的元素收集到集合或其他的容器中
        这个方法需要配合Collectors工具类

public class TestEndStream {
    @Test
    public void test11() {
        //随机产生10个[0,10)之间的整数,打印10个整数,收集其中的偶数,不能重复
        List<Integer> list = Stream.generate(() -> (int) (Math.random() * 10))
                .limit(10)
                .peek(System.out::println)
                .filter(t->t%2==0)
                .distinct()
                .collect(Collectors.toList());
        System.out.println("list = " + list);
    }
    @Test
    public void test10() {
        //随机产生10个[0,10)之间的整数,打印10个整数,收集其中的偶数,不能重复
        Set<Integer> set = Stream.generate(() -> (int) (Math.random() * 10))
                .limit(10)
                .peek(System.out::println)
                .filter(t -> t % 2 == 0)
                .collect(Collectors.toSet());
        System.out.println("set = " + set);
    }

    @Test
    public void test09() {
        //随机产生10个[0,10)之间的整数,打印10个整数,收集其中的偶数
        List<Integer> list = Stream.generate(() -> (int) (Math.random() * 10))
                .limit(10)
                .peek(System.out::println)
                .filter(t->t%2==0)
                .collect(Collectors.toList());
        System.out.println("list = " + list);
    }
    @Test
    public void test08() {
        //随机产生10个[0,10)之间的整数,打印10个整数,累加它们的和
        Optional<Integer> result = Stream.generate(() -> (int) (Math.random() * 10))
                .limit(10)
                .peek(System.out::println)

                .reduce((t1, t2) -> t1 + t2);
        /*
        reduce(BinaryOperator b)形参BinaryOperator<T>是函数式接口,抽象方法T apply(T t,  T u)
         */
        System.out.println("result = " + result);
    }
    @Test
    public void test07() {
        //随机产生10个[0,100)之间的整数,打印10个整数,并且返回最大值
        Optional<Integer> max = Stream.generate(() -> (int) (Math.random() * 100))
                .limit(10)
                .peek(System.out::println)
                .max((t1, t2) -> t1 - t2);
        System.out.println("max = " + max);
    }
    @Test
    public void test06() {
        //随机产生10个[0,100)之间的整数,打印10个整数,并统计里面的偶数个数
        ArrayList<Integer> list = new ArrayList<>();
        int count = 0;
        for(int i=0; i<10; i++){
            int num = (int) (Math.random() * 100);
            list.add(num);
            if(num%2==0){
                count++;
            }
        }
        System.out.println(list);
        System.out.println("偶数的个数count = " + count);
    }
    @Test
    public void test05() {
        //随机产生10个[0,100)之间的整数,打印10个整数,并统计里面的偶数个数
        long count = Stream.generate(() -> (int) (Math.random() * 100))
                .limit(10)
                .peek(System.out::println)
                .filter(t -> t % 2 == 0)
                .count();
        System.out.println("偶数的个数count = " + count);
    }


    @Test
    public void test04() {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        //第一步:创建Stream
        Stream<Integer> stream = list.stream();

        //第二步:留下比5大的整数
        stream = stream.filter(t -> t>5);

        //第三步:findFirst
        Optional<Integer> first = stream.findFirst(); //Optional是一个容器,它用来包装返回值结果
        System.out.println("first = " + first);//Optional.empty
    }

    @Test
    public void test03() {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        //第一步:创建Stream
        Stream<Integer> stream = list.stream();
        
        //第三步:findFirst
        Optional<Integer> first = stream.findFirst(); //Optional是一个容器,它用来包装返回值结果
        System.out.println("first = " + first);
    }
    @Test
    public void test02(){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        //第一步:创建Stream
        Stream<Integer> stream = list.stream();

        //第二步:过滤
        stream = stream.filter(t -> t % 2 == 0);//只留下偶数

        //第三步:终结操作
        boolean result = stream.allMatch(t -> t % 2 == 0);
        System.out.println("result = " + result);
    }
    @Test
    public void test01(){
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        //第一步:创建Stream
        Stream<Integer> stream = list.stream();

        //第三步:终结操作
        boolean result = stream.allMatch(t -> t % 2 == 0);
        System.out.println("result = " + result);
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值