Java系列3:java8新特性

一、lambda函数遍历集合

1.1、java8中foreach + lambda函数式编程

(1)普通for循环

    @Test
    public void forTest() {
        List<Point> points = Arrays.asList(new Point(1, 2), new Point(2, 3));
        System.out.println("循环操作前:" + points);
        for (int i = 0; i < points.size(); i++) {
            points.get(i).translate(1, 1);
        } System.out.println("循环操作后:" + points);
    }

(2) foreach循环

    @Test
    public void forEachTest() {
        List<Point> points = Arrays.asList(new Point(1, 2), new Point(2, 3));
        System.out.println("循环操作前:" + points);
        for (Point p : points) {
            p.translate(1, 1);
        }System.out.println("循环操作后:" + points);
    }

(3) lambda函数式写法

    @Test
    public void lambdaForEachTest() {
        List<Point> points = Arrays.asList(new Point(1, 2), new Point(2, 3));
        System.out.println("循环操作前:" + points);
        /**仅此一行就够了
         * p -> p.translate(1, 1)
         * points集合的泛型为Point,里面存储的是Point对象
         * p即Point对象,箭头后面的即对此对象所做的操作,当然这个p可以随便命名
         * 比如Long类型的number: num -> sb.append(num + ",");
         * 场景是将一个id集合,使用StringBuilder拼接成逗号分隔的字符串   */
        points.forEach(p -> p.translate(1, 1));
        System.out.println("循环操作后:" + points);
    }

1.2、forEach 的lambda函数式写法之遍历Map集合

(1)普通遍历一个Map

Map<String, Integer> items = new HashMap<>();
items.put("A", 10);  items.put("B", 20);
items.put("C", 30);  items.put("D", 40);
items.put("E", 50);  items.put("F", 60);
for (Map.Entry<String, Integer> entry : items.entrySet()) {
    System.out.println("Item : " + entry.getKey() + " Count : " + entry.getValue());
}

(2)   foreach  + 拉姆达表达式遍历

Map<String, Integer> items = new HashMap<>();
items.put("A", 10);  items.put("B", 20);  items.put("C", 30);
items.put("D", 40);  items.put("E", 50);  items.put("F", 60);
items.forEach((k,v)->System.out.println("Item : " + k + " Count : " + v));
items.forEach((k,v)->{
    System.out.println("Item : " + k + " Count : " + v);
    if("E".equals(k)){
        System.out.println("Hello E");
    }
});

1.3、forEach 的lambda函数式写法之遍历List集合

(1)普通遍历一个List

List<String> items = new ArrayList<>();
items.add("A");   items.add("B");  items.add("C");
items.add("D");   items.add("E");
for(String item : items){
    System.out.println(item);
}

(2)java8中可以使用   foreach + 拉姆达表达式 或者 method reference(方法引用)

List<String> items = new ArrayList<>();
items.add("A"); items.add("B"); items.add("C"); items.add("D"); items.add("E");
//lambda    Output : A,B,C,D,E
items.forEach(item->System.out.println(item));
//Output : C
items.forEach(item->{
    if("C".equals(item)){
        System.out.println(item);
    }
});
 
//method reference     Output : A,B,C,D,E
items.forEach(System.out::println);

//Stream and filter    Output : B
items.stream()
    .filter(s->s.contains("B"))
    .forEach(System.out::println);

map和reduce:

    map用来归类,结果一般是一组数据,比如可以将list中的学生分数映射到一个新的stream中。
    reduce用来计算值,结果是一个值,比如计算最高分。


    public class StreamTest02 { 
        public static void main(String[] args) {
            //初始化List数据同上
            List<Student> list = InitData.getStudent();
            //使用map方法获取list数据中的name
            List<String> names = list.stream().map(Student::getName).collect(Collectors.toList());
            System.out.println(names);     
            //使用map方法获取list数据中的name的长度
            List<Integer> length = list.stream().map(Student::getName).map(String::length).collect(Collectors.toList());
            System.out.println(length);     
            //将每人的分数-10
            List<Integer> score = list.stream().map(Student::getScore).map(i -> i - 10).collect(Collectors.toList());
            System.out.println(score);    
            //计算学生总分
            Integer totalScore1 = list.stream().map(Student::getScore).reduce(0,(a,b) -> a + b);
            System.out.println(totalScore1);    
            //计算学生总分,返回Optional类型的数据,改类型是java8中新增的,主要用来避免空指针异常
            Optional<Integer> totalScore2 = list.stream().map(Student::getScore).reduce((a,b) -> a + b);
            System.out.println(totalScore2.get());     
            //计算最高分和最低分
            Optional<Integer> max = list.stream().map(Student::getScore).reduce(Integer::max);
            Optional<Integer> min = list.stream().map(Student::getScore).reduce(Integer::min);     
            System.out.println(max.get());
            System.out.println(min.get());
        }
    }

 

二.、Streams(流)

(并行流:将串行流的.stream改为.parallelStream()即可,在多个线程上同时执行

java.util.Stream 表示能应用在一组元素上一次执行的操作序列。

以下例子按照这个集合来分析:

List<String> stringList = new ArrayList<>();
stringList.add("ddd2");
stringList.add("aaa2");
stringList.add("bbb1");
stringList.add("aaa1");
stringList.add("bbb3");
stringList.add("ccc");
stringList.add("bbb2");
stringList.add("ddd1");

2.1、Stream操作的一些知识点

(1)Stream操作分为中间件操作和最终操作。最终操作返回 特定类型的计算结果;中间件操作返回Stream本身,可以将多个操作串起来。

(2)Stream的创建需要指定一个数据源,比如java.util.Collection的子类(List、Set),Map不支持

2.2、Filter(过滤)

过滤只保留符合条件的元素,该操作属于中间操作,所以还可以对过滤的结果进行其他Stream操作(比如forEach,forEach是一个最终操作不能在它之后执行其他Stream操作)

        // 测试 Filter(过滤)
        stringList
                .stream()
                .filter((s) -> s.startsWith("a"))
                .forEach(System.out::println);//aaa2 aaa1

2.3、Sorted(排序)

排序是一个中间操作。返回排序好的Stream,如果你不指定一个自定义的 Comparator 则会使用默认排序

        // 测试 Sort (排序)
        stringList
                .stream()
                .sorted()
                .filter((s) -> s.startsWith("a"))
                .forEach(System.out::println);// aaa1 aaa2

2.4、Map(映射)

映射是一个中间操作。会将元素根据指定的Function 接口来依次将元素转成另外的对象。

示例:将字符串转换为大写字符串

  // 测试 Map 操作
        stringList
                .stream()
                .map(String::toUpperCase)
                .sorted((a, b) -> b.compareTo(a))
                .forEach(System.out::println);// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

2.5、Map类型不支持streams(与Map映射区别开)

不过Map提供了一些新的有用的方法来处理一些日常任务,Map接口本身没有可用的 stream()方法,但是你可以在键,值上创建专门的流

或者通过

(1) map.keySet().stream()

(2)map.values().stream()

(3)map.entrySet().stream()

2.6、Match(匹配)

匹配是最终操作。返回一个boolean类型的值,允许监测指定的Predicate是否匹配整个Stream。

        // 测试 Match (匹配)操作
        boolean anyStartsWithA =stringList.stream()
                        .anyMatch((s) -> s.startsWith("a"));
        System.out.println(anyStartsWithA);      //是否有匹配 true

        boolean allStartsWithA =stringList.stream()
                        .allMatch((s) -> s.startsWith("a"));
        System.out.println(allStartsWithA);      //是否全部匹配 false

        boolean noneStartsWithZ =stringList.stream()
                        .noneMatch((s) -> s.startsWith("z"));
        System.out.println(noneStartsWithZ);      //是否没有匹配z  true

2.7、Count(计数)

计数是一个最终操作。返回Stream中元素个数,返回值类型是long

      //测试 Count (计数)操作
        long startsWithB =stringList.stream()
                        .filter((s) -> s.startsWith("b"))
                        .count();
        System.out.println(startsWithB);    // 3

2.8、Reduce(规约)——(字符串拼接,sum、min、max、average等math操作都是)

规约是一个最终操作。允许通过制定函数来将stream中的多个元素规约为一个元素,规约后的结果是通过Optional接口表示的

//测试 Reduce (规约)操作
        Optional<String> reduced =
                stringList
                        .stream()
                        .sorted()
                        .reduce((s1, s2) -> s1 + "#" + s2);

        reduced.ifPresent(System.out::println);//aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2

规约作用:主要作用是把Stream元素组合起来,它提供一个起始值(种子),然后依靠运算规则和前面Stream的第一个、第二个、第n个元素组合。

 

说明:这类有起始值(reduce第一个参数(空白字符)即为起始值)的reduce()返回具体对象

(1)字符串连接:concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); 
(2)求最小值:minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); 
(3)求和:sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);

说明:这类没有起始值的reduce(),没有足够的元素返回Optional

(4)求和:sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
(5)过滤,字符串连接:concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").filter(x -> x.compareTo("Z") > 0).reduce("", String::concat);

 

什么是Optional(这个很强大,一个进步)?

Optional类主要解决空指针异常,是一个包含有可选项的包装类(容器类),所以Optional既可以含有对象也可以为空

Optional使用示例:

Java8之前,任何访问对象方法或者属性的调用都可能导致空指针

例如:
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
需要修改为:
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String isocode = country.getIsocode();
            if (isocode != null) {
                isocode = isocode.toUpperCase();
            }
        }
    }
}

Optional 类比较常用的几个方法有:

  • isPresent() :值存在时返回 true,反之 flase
  • get() :返回当前值,若值不存在会抛出异常
  • orElse(T) :值存在时返回该值,否则返回 T 的值
  • of() :为非null值创建一个Optional——创建对象时传入参数不能为空
  • ofNullable():为指定的值创建一个Optional——参数可为空
  • orElse():如果有值则将其返回,没有则返回orElse方法传入的参数
  • orElseGet():与orElse方法类似,区别在于得到的默认值。orElse方法将传入的字符串作为默认值,orElseGet方法可以接受Supplier接口的实现用来生成默认值
  • map(): 如果有值,则对其执行调用mapping函数得到返回值。如果返回值不为null,则创建包含mapping返回值的Optional作为map方法返回值,否则返回空Optional
  • flatMap():flatMap与map方法类似,区别在于flatMap中的mapper返回值必须是Optional
  • filter():如果有值并且满足断言条件返回包含该值的Optional,否则返回空Optional

 

2.9、Date API(日期相关API)

Java 8在 java.time 包下包含一个全新的日期和时间API

(1)Clock

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

Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
System.out.println(millis);//1552379579043
Instant instant = clock.instant();
System.out.println(instant);
Date legacyDate = Date.from(instant); //2019-03-12T08:46:42.588Z
System.out.println(legacyDate);//Tue Mar 12 16:32:59 CST 2019

(2)Timezones(时区)

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

//输出所有区域标识符
System.out.println(ZoneId.getAvailableZoneIds());

ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());// ZoneRules[currentStandardOffset=+01:00]
System.out.println(zone2.getRules());// ZoneRules[currentStandardOffset=-03:00]

(3)LocalTime(本地时间)

LocalTime 定义了一个没有时区信息的时间,例如 晚上10点或者 17:30:15

LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2));  // false

long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

System.out.println(hoursBetween);       // -3
System.out.println(minutesBetween);     // -239

(4)LocalDate(本地日期)

LocalDate 表示了一个确切的日期,比如 2014-03-11

LocalDate today = LocalDate.now();//获取现在的日期
System.out.println("今天的日期: "+today);//2019-03-12
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
System.out.println("明天的日期: "+tomorrow);//2019-03-13
LocalDate yesterday = tomorrow.minusDays(2);
System.out.println("昨天的日期: "+yesterday);//2019-03-11
LocalDate independenceDay = LocalDate.of(2019, Month.MARCH, 12);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println("今天是周几:"+dayOfWeek);//TUESDAY

(5)LocalDateTime(本地日期时间)

LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime 和 LocalTime还有 LocalDate 一样,都是不可变的

 

 

三、collect 收集数据

coollect 方法作为终端操作,接受的是一个 Collector 接口参数,能对数据进行一些收集归总操作

3.1、收集

最常用的方法,把流中所有元素收集到一个 List, Set 或 Collection 中

  • toList
  • toSet
  • toCollection
  • toMap
List newlist = list.stream.collect(toList());
//如果 Map 的 Key 重复了,可是会报错的哦
Map<Integer, Person> map = list.stream().collect(toMap(Person::getAge, p -> p));

3.2、汇总

(1)counting

用于计算总和:

long l = list.stream().collect(counting());

下面这样也可以:

long l = list.stream().count();

推荐第二种

(2)summingInt ,summingLong ,summingDouble

summing,也是计算总和,不过这里需要一个函数参数

计算 Person 年龄总和:

int sum = list.stream().collect(summingInt(Person::getAge));

当然,这个可以也简化为:

int sum = list.stream().mapToInt(Person::getAge).sum();

除了上面两种,其实还可以:

int sum = list.stream().map(Person::getAge).reduce(Interger::sum).get();

推荐第二种

由此可见,函数式编程通常提供了多种方式来完成同一种操作

(3)averagingInt,averagingLong,averagingDouble

看名字就知道,求平均数

Double average = list.stream().collect(averagingInt(Person::getAge));

当然也可以这样写

OptionalDouble average = list.stream().mapToInt(Person::getAge).average();

不过要注意的是,这两种返回的值是不同类型的

(4)summarizingInt,summarizingLong,summarizingDouble

这三个方法比较特殊,比如 summarizingInt 会返回 IntSummaryStatistics 类型

IntSummaryStatistics l = list.stream().collect(summarizingInt(Person::getAge));

IntSummaryStatistics 包含了计算出来的平均值,总数,总和,最值,可以通过下面这些方法获得相应的数据

3.3、取最值

maxBy,minBy 两个方法,需要一个 Comparator 接口作为参数

Optional<Person> optional = list.stream().collect(maxBy(comparing(Person::getAge)));

我们也可以直接使用 max 方法获得同样的结果

Optional<Person> optional = list.stream().max(comparing(Person::getAge));

3.4、joining 连接字符串

也是一个比较常用的方法,对流里面的字符串元素进行连接,其底层实现用的是专门用于字符串连接的 StringBuilder

String s = list.stream().map(Person::getName).collect(joining());

结果:jackmiketom
String s = list.stream().map(Person::getName).collect(joining(","));

结果:jack,mike,tom

joining 还有一个比较特别的重载方法:

String s = list.stream().map(Person::getName).collect(joining(" and ", "Today ", " play games."));

结果:Today jack and mike and tom play games.

即 Today 放开头,play games. 放结尾,and 在中间连接各个字符串

3.5、groupingBy 分组

groupingBy 用于将数据分组,最终返回一个 Map 类型

Map<Integer, List<Person>> map = list.stream().collect(groupingBy(Person::getAge));

例子中我们按照年龄 age 分组,每一个 Person 对象中年龄相同的归为一组

另外可以看出,Person::getAge 决定 Map 的键(Integer 类型),list 类型决定 Map 的值(List<Person> 类型)

多级分组

groupingBy 可以接受一个第二参数实现多级分组:

Map<Integer, Map<T, List<Person>>> map = list.stream().collect(groupingBy(Person::getAge, groupingBy(...)));

其中返回的 Map 键为 Integer 类型,值为 Map<T, List<Person>> 类型,即参数中 groupBy(...) 返回的类型

按组收集数据

Map<Integer, Integer> map = list.stream().collect(groupingBy(Person::getAge, summingInt(Person::getAge)));

该例子中,我们通过年龄进行分组,然后 summingInt(Person::getAge)) 分别计算每一组的年龄总和(Integer),最终返回一个 Map<Integer, Integer>

根据这个方法,我们可以知道,前面我们写的:

groupingBy(Person::getAge)

其实等同于:

groupingBy(Person::getAge, toList())

3.6、 partitioningBy 分区

分区与分组的区别在于,分区是按照 true 和 false 来分的,因此partitioningBy 接受的参数的 lambda 也是 T -> boolean

根据年龄是否小于等于20来分区
Map<Boolean, List<Person>> map = list.stream()
                                     .collect(partitioningBy(p -> p.getAge() <= 20));

打印输出
{
    false=[Person{name='mike', age=25}, Person{name='tom', age=30}], 
    true=[Person{name='jack', age=20}]
}

同样地 partitioningBy 也可以添加一个收集器作为第二参数,进行类似 groupBy 的多重分区等等操作。

 

 

 

 

 

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值