通俗易懂的Java8-Lambda与StreamAPI与新DateAPI

通俗易懂的Java8-Lambda与StreamAPI与新DateAPI

已更新至完整版

接口的默认方法(Default Methods for Interfaces)

Java 8使我们能够通过使用 default 关键字向接口添加非抽象方法实现。 此功能也称虚拟扩展方法

interface Formula {
	
    double calculate(int a);
    //接口的默认实现方法
    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

主函数:

public class GoJava8 {
    public static void main(String[] args) {
        // 通过匿名内部类方式访问接口
        Formula formula = new Formula() {
            //实现接口方法
            @Override
            public double calculate(int a) {
                return sqrt(a * 100);
            }
        };
        System.out.println(formula.calculate(100));     // 100.0
        //接口默认实现方法可以直接调用
        System.out.println(formula.sqrt(16));           // 4.0
    }
}

formula 是作为匿名对象实现的,我们可以这样理解:一个内部类实现了接口里的抽象方法并且返回一个内部类对象,之后我们让接口的引用来指向这个对象

关于匿名内部类,这不是Java8的新特性,如果你没有掌握,参考我之前写的

Java基础-内部类与匿名内部类总结笔记

Lambda表达式(Lambda expressions)

首先来看看,我们之前是如果对集合进行比较的

        //要排序的字符串
        List<String> names = Arrays.asList("bb","aa","cc")  ;

        //传统方式,匿名内部类
        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                //字符串的比较方法
                return o1.compareTo(o2);
            }
        });

:通过匿名内部类,实现比较器Comparator接口

如果使用Lambda表达式呢?

        Collections.sort(names, (a,b)->a.compareTo(b));

只需要一行代码即可实现

Lambda表达式的基本语法为

  • 操作符:->
  • 左侧:参数列表
  • 右侧:执行代码块 / Lambda 体

函数式接口(Functional Interfaces)

使用Lambda表达式需要函数式接口的支持

“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口

像这样的接口,可以被隐式转换为lambda表达式,java.lang.Runnablejava.util.concurrent.Callable 是函数式接口最典型的两个例子

Java 8增加了一种特殊的注解@FunctionalInterface,但是这个注解通常不是必须的(某些情况建议使用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口

一般建议在接口上使用@FunctionalInterface 注解进行声明,这样的话,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会进行报错

注意:

  • 函数式接口只有一个抽象方法
  • default方法某默认实现,不属于抽象方法
  • 接口重写了Object的公共方法也不算入内,如equal()

在上述代码中,Comparator就是一个函数式接口

Lambda表达式的几种常用情况

1.无参数,无返回值的Lambda

        Runnable runnable = ()-> System.out.println("it is run method");
        runnable.run();

@FunctionalInterface
public interface Runnable {  //jdk提供
    public abstract void run();
}

2.有一个参数,无返回值(小括号可省略)

Consumer consumer = (x)-> System.out.println(x) ;
consumer.accept("it accept method");
@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
    }

3.有两个及以上参数,有返回值

        Collections.sort(names, (a,b)->a.compareTo(b));

这个就是

可以看下比较器接口(sort的第二个参数)

@FunctionalInterface
public interface Comparator<T> {

    int compare(T o1, T o2);
    ...
    }

内置的四种基本接口

函数式接口我们一般无需自己定义,根据返回值和参数的不同,函数式接口的形式是有限的,每次要用lambda都去写个函数式接口那不是更麻烦了吗

1.消费型接口-Consumer

2.提供型接口

  • 无参有返回值

  • 模拟返回随机数

  •     Supplier<Integer> supplier1 = ()->(int)(Math.random()*10);
        System.out.println(supplier1.get());
    
  • @FunctionalInterface
    public interface Supplier<T> {
    
    
        T get();
    }
    
  • 注意,在有返回值的情况下,如有多句语句,必须写成:

  •     Supplier<Integer> supplier1 = ()->{
            System.out.println("hehe");
            return (int)(Math.random()*10);//一定要有return
        };
        System.out.println(supplier1.get());
    

3.函数型接口

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}
        String str = "abcdefg" ;
        Function<String,String> function = (st)->st.substring(1,3);
        System.out.println(function.apply(str));

典型的传入参数与返回参数型接口

其他函数式接口:

JAVA8 函数式接口- osc_umxygzar的个人空间- OSCHINA

4.断言型接口

        Predicate<Integer> predicate = (i)->i>=35 ;
        System.out.println(predicate.test(25)); ;
@FunctionalInterface
public interface Predicate<T> {

	//传入参数,返回布尔值
    boolean test(T t);
}

引用

若 Lambda 表达式体中的内容已有方法实现,则我们可以使用“方法引用”

分为:

  • 对象 :: 实例方法
  • 类 :: 静态方法
  • 类 :: 实例方法
        /*对象::实例方法
        * 要求:实现的方法返回值与参数与函数式接口中的方法保持一致
        * */

        PrintStream out = System.out;//PrintStream中实现了println方法
        Consumer<String> consumer = out::println; //实例::方法名
        consumer.accept("hello");
        /*类::静态方法
        * 要求:实现的方法返回值与参数与函数式接口中的方法保持一致 
        * */

        Comparator<Integer> comparator = (x,y)->Integer.compare(x,y);
        System.out.println(comparator.compare(2,1));

        Comparator<Integer> comparator1 = Integer::compare ;
        System.out.println(comparator1.compare(1,2));
        /*类::实例方法
        * Lambda 参数列表中的第一个参数是方法的调用者,第二个参数是方法的参数时,才能使用 ClassName :: Method
        * 如 "ab".equal("cd")
        * */

        BiPredicate<String,String > biPredicate = String::equals ;
        System.out.println(biPredicate.test("ab","cd"));
        /*构造器引用
        * className::new
        * */

        //传统
        Supplier<List> supplier = ()-> new ArrayList<>();
        //引用
        supplier = ArrayList::new ;

StreamAPI

流(Stream)是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列

集合讲数据,流讲计算

注意:

  • Stream不会自己存储数据
  • Stream不会改变源对象,相反,它们回返回一个持有结果的新Stream
  • Stream操作是延迟执行的,这意味着它们会等到需要结果的时候才执行(懒汉)

Stream操作的三个步骤

  • 创建Stream
  • 中间操作:一条操作链,对数据源的数据进行处理
  • 终止操作:执行中间操作,产生结果

创建流的几种方式

        /*创建流的几种方式*/

        //集合流
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream();

        //数组流
        String[] strings = {"aa","bb","cc"} ;
        Stream<String> stream1 = Arrays.stream(strings);

        //Stream类 -> of方法 返回的其实就是Arrays.stream(strings)
        Stream<String> stream2 = Stream.of("aa", "bb", "cc");

        /*
        * 无限流 seed:the initial element
        * */

        Stream<Integer> iterate = Stream.iterate(0, (i) -> ++i+i++);
        iterate.forEach(System.out::println);


        /*生成流*/

        Stream.generate(()->Math.random())
                .limit(3)
                .forEach(System.out::println);

中间操作_筛选/切片

  • filter:接收 Lambda ,从流中排除某些元素
  • limit:截断流,使其元素不超过给定数量
  • skip(n):跳过元素,返回一个舍弃了前n个元素的流;若流中元素不足n个,则返回一个空流;与 limit(n) 互补
  • distinct:筛选,通过流所生成的 hashCode() 与 equals() 取除重复元素

实例:

        //模拟数据库->表
        class  Employee{
            Integer id ;
            String name ;
            Integer age ;
            double sal ;

            @Override
            public String toString() {
                return "Employee{" +
                        "id=" + id +
                        ", name='" + name + '\'' +
                        ", age=" + age +
                        ", sal=" + sal +
                        '}';
            }

            public Employee(Integer id, String name, Integer age, double sal) {
                this.id = id;
                this.name = name;
                this.age = age;
                this.sal = sal;
            }

            public Integer getId() {
                return id;
            }

            public String getName() {
                return name;
            }

            public Integer getAge() {
                return age;
            }

            public double getSal() {
                return sal;
            }
        }


        List<Employee> emps = Arrays.asList(
                new Employee(101, "Z3", 19, 9999.99),
                new Employee(102, "L4", 20, 7777.77),
                new Employee(103, "W5", 35, 6666.66),
                new Employee(104, "Tom", 44, 1111.11),
                new Employee(105, "Jerry", 60, 4444.44)
        );


        emps.stream()
                .filter((x)->x.getAge()>20)
                .limit(3)
                .distinct()
                .skip(1)
                .forEach(System.out::println);

结果:

Employee{id=104, name=‘Tom’, age=44, sal=1111.11}
Employee{id=105, name=‘Jerry’, age=60, sal=4444.44}

映射

  • map:接收 Lambda ,将元素转换为其他形式或提取信息;接受一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
  • flatMap:接收一个函数作为参数,将流中每一个值都换成另一个流,然后把所有流重新连接成一个流
        List<String> list = Arrays.asList("bb","aa","cc") ;
        list.stream()
                .map((str)->str.toUpperCase())
                .forEach(System.out::println);

result:

BB
AA
CC

排序

sorted():自然排序
sorted(Comparator c):定制排序

        list.stream()
                .sorted()
                .forEach(System.out::println);

        emps.stream()
                .sorted((e1,e2)->{
                    return e1.getAge().compareTo(e2.getAge()) ;
                })
        .forEach(System.out::println);

查找、匹配

  • allMatch:检查是否匹配所有元素
  • anyMatch:检查是否至少匹配一个元素
  • noneMatch:检查是否没有匹配所有元素
  • findFirst:返回第一个元素
  • findAny:返回当前流中的任意元素
  • count:返回流中元素的总个数
  • max:返回流中最大值
  • min:返回流中最小值
        List<Status> statuses = Arrays.asList(Status.FREE, Status.BUSY, Status.VOCATION) ;
        boolean flag1 = statuses.stream()
                .allMatch((x)->x.equals(Status.BUSY));
        System.out.println(flag1);

        boolean flag2 = statuses.stream()
                .anyMatch((x)->x.equals(Status.BUSY));
        System.out.println(flag2);
        
                Optional<Status> first = statuses.stream()
                .findFirst();
        System.out.println(first);
    public enum Status{
        FREE,BUSY,VOCATION ;
    }

规约

归约:reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中的数据反复结合起来,得到一个值

        List<Integer> list1  = Arrays.asList(1,2,3,4) ;
        Integer reduce = list1.stream()
                .reduce(3, (x, y) -> x - y);//x代表3,y代表list中数字
        System.out.println(reduce); //-7

新的DateAPI

  • Clock 类提供了访问当前日期和时间的方法,Clock 是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用 Instant 类来表示,Instant 类也可以用来创建旧版本的java.util.Date 对象。
  • 在新API中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法of来获取到。 抽象类ZoneId(在java.time包中)表示一个区域标识符。 它有一个名为getAvailableZoneIds的静态方法,它返回所有区域标识符。
  • jdk1.8中新增了 LocalDate 与 LocalDateTime等类来解决日期处理方法,同时引入了一个新的类DateTimeFormatter 来解决日期格式化问题。可以使用Instant代替 Date,LocalDateTime代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。

Clock

Clock 类提供了访问当前日期和时间的方法,Clock 是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用 Instant 类来表示,Instant 类也可以用来创建旧版本的java.util.Date 对象

        Clock clock = Clock.systemDefaultZone() ;
        System.out.println(clock);

        System.out.println(System.currentTimeMillis());

        long millis = clock.millis();
        System.out.println(millis);

        Instant instant = clock.instant() ;
        System.out.println(instant);

        Date from = Date.from(instant);
        System.out.println(from);

Timezones(时区)

在新API中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法of来获取到。 抽象类ZoneId(在java.time包中)表示一个区域标识符。 它有一个名为getAvailableZoneIds的静态方法,它返回所有区域标识符。

        /*Timezones*/
        //所有区域标识符

        System.out.println(ZoneId.getAvailableZoneIds());
        ZoneId zoneId = ZoneId.of("Asia/Aden") ;
        System.out.println(zoneId.getRules()); //有偏移量

LocalDate(本地日期)

LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例,这保证了线程的安全性

        /*LocalData*/

        LocalDate now = LocalDate.now();
        System.out.println(now);

        LocalDate tomorrow = now.plus(1, ChronoUnit.DAYS);
        System.out.println(tomorrow);

        LocalDate yest = now.minusDays(1);
        System.out.println(yest);

        LocalDate date = LocalDate.of(2020, 6, 18);
        System.out.println(date);

DateTimeFormatter

       String str = "2020==06==18 03时06分09秒" ;

        // 根据需要解析的日期、时间字符串定义解析所用的格式器
        DateTimeFormatter fomatter = DateTimeFormatter
                .ofPattern("yyyy==MM==dd HH时mm分ss秒");

        LocalDateTime parse = LocalDateTime.parse(str,fomatter);
        System.out.println(parse);

        LocalDateTime now1 = LocalDateTime.now();
        String format = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(now1);
        System.out.println(format);

        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss");
        System.out.println(dateTimeFormatter.format(now1));

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章