Java8新特性

一、概述

于2014年发布,支持

  • lambda表达式
  • 函数式接口
  • 方法引用
  • Stream API
  • 新时间API

二、lambda表达式

18_Java8新特性 - 图1

是特殊的匿名内部类,只是语法更简洁。只能简化接口中只有一个抽象方法的匿名内部类

定义一个接口:

  1. @FunctionLInterface //可以不加,但是如果加上此注解必须是函数式接口,里面
  2. public interface MyInterface {
  3. void say(String name);
  4. }

通过匿名内部类的方式,创建对象,并调用该方法:

  1. public class TestMain1 {
  2. public static void main(String[] args) {
  3. MyInterface m = new MyInterface() {
  4. @Override
  5. public void say(String name) {
  6. System.out.println(name);
  7. }
  8. };
  9. m.say("zhangsan");
  10. }
  11. }
  12. //如果是无返回值的话,最终形式
  13. //如果只有一个参数,参数外的小括号可以不加,无参数不可省略小括号
  14. //如果方法体只有一行,大括号可以不加
  15. public class TestMain1 {
  16. public static void main(String[] args) {
  17. MyInterface m = name -> System.out.println(name);
  18. m.say("zhangsan");
  19. }
  20. }
  21. //有返回值最终形式
  22. //如果方法有返回值,且代码只有一行,可以简化去掉大括号,但是需要去掉return关键字。
  23. public class TestMain2 {
  24. public static void main(String[] args) {
  25. MyInterface2 m = () -> "Hello";
  26. System.out.println(m.m1());
  27. }
  28. }
  1. //示例,对一个数组进行升序排列,用lambda表达式简化
  2. public static void main(String[] args) {
  3. Integer[] age = {23,53,24,66,22,78,56,87,43};
  4. /*
  5. Arrays.sort(age);
  6. for (Integer integer : age) {
  7. System.out.print(integer+" ");
  8. }
  9. sort默认的是升序的
  10. */
  11. /*
  12. Arrays.sort(age, new Comparator<Integer>() {
  13. @Override
  14. public int compare(Integer o1, Integer o2) {
  15. return o1-o2 ;
  16. }
  17. }).;
  18. 指定排序按照降序,简化之后如下
  19. */
  20. Arrays.sort(age, ( o1, o2) -> o2-o1 );
  21. System.out.println(Arrays.toString(age));
  22. // [87, 78, 66, 56, 53, 43, 24, 23, 22]
  23. }

将上面的匿名内部类简化为:

  1. public class TestMain1 {
  2. public static void main(String[] args) {
  3. MyInterface m = (String name) -> {
  4. System.out.println(name);
  5. };
  6. m.say("zhangsan");
  7. }
  8. }

注意:

  • 参数列表的类型会自动推断。

上面的代码可以简化为:

  1. public class TestMain1 {
  2. public static void main(String[] args) {
  3. MyInterface m = (name) -> {
  4. System.out.println(name);
  5. };
  6. m.say("zhangsan");
  7. }
  8. }

注意:

  • 如果参数列表为空,需要保留小括号。
  • 如果参数列表有参,且只有一个参数,那么小括号可以去掉。
  1. public class TestMain1 {
  2. public static void main(String[] args) {
  3. MyInterface m = name -> {
  4. System.out.println(name);
  5. };
  6. m.say("zhangsan");
  7. }
  8. }

注意:

  • 如果方法没有返回值,且代码只有一行,大括号可以省略。
  1. public class TestMain1 {
  2. public static void main(String[] args) {
  3. MyInterface m = name -> System.out.println(name);
  4. m.say("zhangsan");
  5. }
  6. }

如果有返回值, 代码变成lambda表达式简化过程,如下:

  1. public interface MyInterface2 {
  2. String m1();
  3. }
  1. public class TestMain2 {
  2. public static void main(String[] args) {
  3. MyInterface2 m = new MyInterface2() {
  4. @Override
  5. public String m1() {
  6. return "Hello";
  7. }
  8. };
  9. System.out.println(m.m1());
  10. }
  11. }

简化后:

public class TestMain2 {
    public static void main(String[] args) {
        MyInterface2 m = () -> {return "Hello";};
        System.out.println(m.m1());
    }
}

简化成一行:

注意:

  • 如果方法有返回值,且代码只有一行,可以简化去掉大括号,但是需要去掉return关键字。
public class TestMain2 {
    public static void main(String[] args) {
        MyInterface2 m = () -> "Hello";
        System.out.println(m.m1());
    }
}

注意:

  • lambda表达式不会单独生成一个内部类的文件,

三、函数式接口

3.1 函数式接口的定义

上面的lambda表达式的使用需要接口中只有一个方法,如果接口中有多个方法,则无法使用,如果在定义接口时,只定义了一个方法,并且使用了lambda表达式,但是如果后面扩展时添加了其他抽象方法,会导致之前使用的lambda表达式报错,为了解决此问题,使用了函数式接口的定义。

语法:

@FunctionalInterface
public interface MyInterface2 {
    String m1();
}

此时,如果在该接口中定义多个抽象方法,会报错。

3.2 常用的函数式接口
  • 消费型接口Cosumer,方法的特点是有参T,无返回值,主要作用是对该参数进行处理。
  • 供给型接口Supplier,方法的特点是无参,返回一个T对象,主要作用是用来创建对象。
  • 函数型接口Fuction,方法的特点是有参T,返回一个R对象。
  • 断言型接口Predicate,方法的特点是有参T,返回一个判断结果boolean值。

四、方法引用

4.1 概念和分类

是lambda表达式的简写,如果lambda表达式中只是调用一个特定的方法,则可以使用方法引用。

方法引用有4种形式:

对象::实例方法

类::静态方法

类::实例方法

类::new

注意:方法引用实际上类似于c语言中的方法指针。

4.2 对象::实例方法

当函数式接口中的方法定义与类中的实例方法定义基本一致(返回值和参数列表),此时,可以直接作为方法的引用,表示使用该类中的实例方法来作为接口方法的实现。

public interface MyInterface {
    void say(String name);
}
public class MyClass1 {
    public void a(String b) {
        String str = "hello, " + b;
        System.out.println(str);
    }
}

上面类中定义的a方法,与接口中定义的say方法,返回值类型与参数列表几乎一致,可以作为方法引用。

public class TestMain3 {
    public static void main(String[] args) {
        MyClass1 c = new MyClass1();
        // 接口中的方法引用对象中的方法
        MyInterface m = c::a;
        // 调用接口中的方法
        m.say("张三");

        // 接口中的方法与系统提供的println方法也一致,也可以引用
        MyInterface m1 = System.out::println;
        m1.say("hello, world");
    }
}

注意:返回值类型是否可以不一致?接口中返回值类型必须大于引用的方法类型。

public interface MyInterface {
    double say(String name);
}
public class MyClass1 {
    public int a(String b) {
        String str = "hello, " + b;
        System.out.println(str);
        return 1;
    }
}

注意:返回值类型是否可以不一致?接口中返回值类型为void时,引用的方法类型可以有返回值。

public interface MyInterface {
    void say(String name);
}
public class MyClass1 {
    public int a(String b) {
        String str = "hello, " + b;
        System.out.println(str);
        return 1;
    }
}

4.3 类::静态方法

当函数式接口中的方法定义与类中的静态方法定义基本一致(返回值和参数列表),此时,可以直接作为方法的引用,表示使用该类中的静态方法来作为接口方法的实现。

public interface MyInterface {
    void say(String name);
}
public class MyClass1 {
    public static void m1(String b) {
        System.out.println(b);
    }
}
public class TestMain3 {
    public static void main(String[] args) {
        MyInterface m2 = MyClass1::m1;
        m2.say("aaa");
    }
}

4.4 类::new

用来创建对象。可以使用系统定义供给型接口。

@FunctionalInterface
public interface MyInterface3<T> {
    T get();
}
public class TestMain4 {
    public static void main(String[] args) {
        MyInterface3<MyClass1> m = MyClass1::new;
        MyClass1 c = m.get();
        System.out.println(c);

        // 使用系统提供的供给型接口
        Supplier<MyClass1> s = MyClass1::new;
        MyClass1 c1 = s.get();
        System.out.println(c1);

        // 消费型接口的使用
        Consumer<String> con = System.out::println;
        con.accept("hello, world");

        // 断言型接口的使用,返回boolean值
        String str = "hello";
        Predicate<String> p = str::equals;
        System.out.println(p.test("Hello"));
    }
}

4.5 类::实例方法

类::实例方法,是表示可以使用方法的引用去引用类中的实例方法,需要注意虽然是用类来::实例方法,但是还是需要使用对象才能调用该方法,所以在使用接口去引用时,接口中的方法必须要传入类的对象。意味着接口会比引用的方法要多一个参数(该对象类型的参数)。

public interface MyInterface {
    void say(String name);
}
public class TestMain5 {
    public static void main(String[] args) {
        MyInterface m = String::length;
        m.say("hello");
    }
}

自定义的案例:

public interface MyInter<T> {
    void m1(T t);
}
public class MyClass {
    public void test() {
        System.out.println("hello, world");
    }
}
public class TestMain {
    public static void main(String[] args) {
        MyInter<MyClass> m1 = MyClass::test;
        m1.m1(new MyClass());
    }
}

自定义的案例2:

public interface MyInter1<T> {
    void m1(T t, String str);
}
public class MyClass {
    public void test1(String str) {
        System.out.println(str);
    }
}
public class TestMain {
    public static void main(String[] args) {
        MyInter1<MyClass> m2 = MyClass::test1;
        m2.m1(new MyClass(), "aaaaa");
    }
}

比较字符串是否相等:

public class TestMain1 {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "world";
        s1.equals(s2);

        // 在比较字符串前,需要先创建一个字符串,意味着只能比较hello与另一个字符串
        Predicate<String> p1 = s1::equals;
        p1.test(s2);

        // 在比较字符串前,不需要先创建任何字符串,直到调用时才传入两个字符串,灵活性更强
        BiPredicate<String, String> p2 = String::equals;
        p2.test(s1, s2);
    }
}

五、StreamAPI

5.1 作用和目的

用于简化集合和数组操作的API;

18_Java8新特性 - 图2

使用步骤:

1、创建流,分为数组和集合

2、中间操作,可以使用链式操作

3、终止操作,一旦终止操作结束,流就关闭了

创建流.中间操作.中间操作....终止操作

5.2 创建流
//  [集合创建流]    先创建一个集合,然后调用Steam()方法,返回就是一个流对象  *

/**Map集合获取流*/
        HashMap<String, Integer> maps = new HashMap<>();

        Stream<String> keyStream = maps.keySet().stream();
        Stream<Integer> stream = maps.values().stream();
        //键值对流
        Stream<Map.Entry<String, Integer>> entryStream = maps.entrySet().stream();

//   [数组创建流]  两种方法
    String[] names={"张三","李四","王五","赵六","田七"};
        Stream<String> stream1 = Arrays.stream(names);

        Stream<String> stream2 = Stream.of(names);
public class TestMain1 {
    public static void main(String[] args) {
        Stream<Integer> s1 = Stream.iterate(1, x -> x + 1); // 后面的数字比前面的数字的变化
        Stream.iterate(1, x -> {
            // 可以加一些判断条件
            return x + 1;
        }); 

        Stream<Integer> s2 = Stream.of(12,35,78,43,22,15); // 列举,得到一组数据的流

        Stream<Integer> s3 = Stream.generate(new Random()::nextInt); // 得到一组随机数操作

        String [] arr = {"hello", "aaa", "bbb", "ccc"};
        Stream<String> s4 = Arrays.stream(arr); // 将数组变成流操作

        ArrayList<String> list = new ArrayList<String>();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        Stream<String> s5 = list.stream(); // 将集合变成流,单线程操作
        Stream<String> s6 = list.parallelStream();// 将集合变成流,多线程操作

        LongStream s7 = LongStream.range(1, 100); // 得到1到100(不包含100)的流
        LongStream s8 = LongStream.rangeClosed(1, 100); // 得到1到100(包含100)的流

        Stream.iterate(1, x ->x + 1) // 创建一个流操作,每个数字比前面大1,第一个数字是1
    .limit(100) // 得到100个数字,中间操作
    .forEach(System.out::println); // 循环打印,终止操作


    }
}

5.3 中间操作

常用的API

limit:表示长度限制

filter:过滤、筛选

skip:跳过多少个元素

distinct:去掉重复的元素

sorted:排序

map:映射,迭代对每一个元素进行操作

parallel:使用多线程操作

public class TestMain2 {
    public static void main(String[] args) {
        // 得到10个随机数
        Stream.generate(Math::random)
        .limit(10)
        .forEach(System.out::println);

        Stream.iterate(1, x -> x + 1)
        .limit(100)
        .skip(20) // 跳过20个数字
        .filter(t -> t % 2 == 0) // 筛选所有的偶数
        .forEach(System.out::println);

        Stream.of(12,34,34,23,15,88,12,15)
        .distinct() // 去掉重复的元素
        .map(x -> x * 2) // 映射,循环每一个元素进行处理
//        .parallel() // 使用多线程操作,对排序结果有影响
        .sorted() // 排序
        .forEach(System.out::println);
    }
}

5.4 终止操作

forEach:循环

min:最小值

max:最大值

count:计数

reduce:归约,可以用来求和等操作

collect:收集

public class TestMain3 {
    public static void main(String[] args) {
        System.out.println(
                Stream.of(12,34,34,23,15,88,12,15)
                .min((o1, o2) -> o1 - o2) // 求最小值
                .get() // 得到值
                );

        long count = Stream.iterate(1, x -> x + 1)
        .limit(100)
        .filter(x -> x % 2 == 0)
        .count();
        System.out.println(count);

        ArrayList<Student> list = new ArrayList<Student>();
        list.add(new Student("zhangsan1", 20, "男"));
        list.add(new Student("zhangsan2", 21, "女"));
        list.add(new Student("zhangsan3", 22, "男"));
        list.add(new Student("zhangsan4", 23, "女"));
        list.add(new Student("zhangsan5", 24, "男"));
        list.add(new Student("zhangsan6", 25, "女"));
        list.add(new Student("zhangsan7", 26, "男"));

        long count2 = list.stream()
        .filter(x->x.getAge() > 22).count();
        System.out.println(count2);

        Integer integer = Stream.iterate(1, x -> x + 1)
        .limit(100)
        .filter(x -> x % 2 == 0)
        .reduce(0, (a, b) -> a + b); // 归约,此处用来求和
        System.out.println(integer);

        Integer ages = list.stream()
        .filter(x->x.getAge() > 22 && x.getSex().equals("男")) // 筛选出年龄大于22并且男性
        .map(Student::getAge) // 只要年龄属性构成一个新的流
        .reduce(0, Integer::sum); // 求和
        System.out.println(ages);

        List<Integer> list2 = Stream.of(12,34,34,23,15,88,12,15)
        .distinct() // 去掉重复的元素
        .sorted()
        .collect(Collectors.toList()); // 将结果收集为一个集合
        System.out.println(list2);

        List<Student> list3 = list.stream()
        .filter(x->x.getAge()>22 && x.getSex().equals("男"))
        .collect(Collectors.toList());
        System.out.println(list3);

        // 分组统计数量
        System.out.println(list.stream()
                .filter(x->x.getAge()>22)
                // 将男性和女性分别计数
                .collect(Collectors.groupingBy(Student::getSex, Collectors.counting())));
    }
}

六、新时间API

public class TestMain4 {
    public static void main(String[] args) {
        LocalDate now = LocalDate.now(); // 得到当前时间
        LocalDate date = LocalDate.of(2014, 3, 20); // 根据年月日创建当前时间
        System.out.println(date);
        int year = date.getYear();// 得到年
        // LocalDate得到日期
        // LocalTime得到时间
        // LocalDateTime得到日期时间
        // 时间戳
        Instant now2 = Instant.now();
        // 打印的格式是:2021-07-29T09:12:07.666Z
        System.out.println(now2);
        // 得到当前默认时区
        ZoneId zoneId = ZoneId.systemDefault();
        System.out.println(zoneId);
        // 格式化工具
        DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        System.out.println(pattern);
        // 格式化字符串
        LocalDate date2 = LocalDate.parse("2020-02-12", pattern);
        System.out.println(date2.getYear());

    }
}