【java学习】java8新特性:stream、Lambda函数、接口的默认方法和类方法、函数式接口、Optional、DateTime

1,java.util.stream(函数式编程风格)

1)概念

Stream的操作可以串行执行或者并行执行。

2)流的特性

1>Intermediate

  1. 一个流可以后面跟随零个或多个 intermediate 操作。
  2. Intermediate操作是惰性化的(lazy),并没有真正开始流的遍历。
  3. 相关操作:
    map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

2>Terminal

  1. 一个流只能有一个 terminal 操作,作为流的结束操作。
  2. Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
  3. 相关操作:
    forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

3>Short-circuiting

  1. 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
  2. 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。
  3. 当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。
  4. 相关操作:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit.

3)流的使用

三个步骤:
语句结构聚合操作为最终操作。

1>获取一个数据源(source)

创建Stream:

功能举例备注
常量转换为streamStream stream = Stream.of("a", "b", "c")通过Stream接口的静态工厂方法
数组转换为streamArrays.asList(strArray).stream()通过Collection接口的默认方法
并行Stream则是在多个线程上同时执行long count = values.parallelStream().sorted().count();
生成一个无限长度的StreamStream.generate(() -> Math.random());
Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
一般和limit一起使用
map转steamaMap.entrySet().stream()

2>中间操作(转换stream)

每次转换原有的Stream对象不改变,返回一个新的Stream对象。

功能举例备注
去重distinct()去重逻辑依赖元素的equals方法
过滤. filter(word -> word.length() > 0)filter里面是true,表示保留
一对一转换.map(String::toUpperCase)
.map(n -> n * n)
map转list:.map(Map.Entry::getValue)
新生成的Stream只包含转换生成的元素
一对多转换.flatMap((childList) -> childList.stream())把子Stream中的元素压缩到父集合中
转换为int.mapToInt(x -> x.length())
.mapToInt(Integer::parseInt)
查看中间执行结果peek主要用于调试目的,通常用于查看中间操作的结果,例如在对流进行过滤或映射之后。不会影响原来steam的内容
截断.limit(10)用法:
1. 与skip搭配进行分页计算;
2. topN
跳过元素skip(2)跳过前n个元素并丢弃,返回新的stream。如果元素数量小于n,返回空stream;
.stream().skip(2).limit(2).collect(Collectors.toList())) 输入[0, 1, 2, 3, 4],输出[2, 3] 分页
排序.sorted()
1. map升序:map.entrySet().stream().sorted(Map.Entry.comparingByValue())
2. map降序:map.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
可指定自定义的Comparator,或者使用默认排序。
返回新的stream,不会影响原有数据源。

3>Reduce Stream(聚合操作,最终操作)

将输入元素转换为一个最终形式返回。

功能方法备注
归约Optional<String> reduced = list.stream().reduce((s1, s2) -> s1 + "#" + s2)将传入的函数作用在序列的第一个元素得到结果后,把这个结果继续与下一个元素作用(累积计算);
规约后的结果是通过Optional接口表示
转换为list.collect(toList())
转换为set.collect(toSet())
转换为map.collect(Collectors.toMap(Task::getTitle, Function.identity(), (existing, replacement) -> existing))Function.identity()相当于task->task
(existing, replacement) -> existing) 表示key值冲突保留之前(replace表示新值替换旧值)
对值分组为map.collect(Collectors.groupingBy(task -> task.getType()))
分割流中的元素.collect(Collectors.partitioningBy(task -> task.getDueOn().isAfter(LocalDate.now())))比如将任务分割为要做和已经做完的任务
求和Integer count = list.stream().mapToInt(Task::getCount).sum()
求最值max和min
计数.count()
数据匹配allMatch:是不是Stream中的所有元素都满足给定的匹配条件;
anyMatch:Stream中是否存在任何一个元素满足匹配条件;
noneMatch:是不是Stream中的所有元素都不满足给定的匹配条件
返回boolean值
boolean anyStartsWithA = list.stream().anyMatch((s) -> s.startsWith("a"))
返回Stream中的第一个元素findFirst返回Stream中的第一个元素,如果Stream为空,返回空Optional
循环forEach和for性能无差异;
不能修改自己包含的本地变量值,也不能用 break/return 之类的关键字提前结束循环

4)其它

①流转换为其它数据结构

// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();

②map新特性

Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。

putIfAbsent

不需要我们做额外的存在性检查。

map.putIfAbsent(i, "val" + i);
foreach
map.forEach((id, val) -> System.out.println(val));
compute & computeIfAbsent & computeIfPresent

支持多线程并发操作。

方法区别举例
compute(key, (k,v)->...)计算并更新值
computeIfAbsentkey不存在时才计算
computeIfPresentkey存在时才计算

统计map的key并计算value:

//如果key存着,value+1(此处++v相当于v+1);不存在,计数为1
map.compute(key, (k, v) -> v == null ? 1 : ++v);
merge

对Map的元素做合并:如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。

map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9concat

2,Lambda函数

1)概念

java 8新特性,它支持Java也能进行简单的“函数式编程”。
lambda表达式是匿名方法,但是它不是内部类的语法糖(lambda表达式编译后只有一个class文件,内部类在编译后会有2个文件),而是依赖了几个JVM底层的lambda相关api。

2)实现

lambda表达式的语法由参数列表、箭头符号->和函数体组成。函数体既可以是一个表达式,也可以是一个语句块:

表达式:表达式会被执行然后返回执行结果。
语句块:语句块中的语句会被依次执行,就像方法中的语句一样:
return语句会把控制权交给匿名方法的调用者。
break和continue只能在循环中使用。
如果函数体有返回值,那么函数体内部的每一条路径都必须返回值。

(int x, int y) -> x + y  //接收x和y这两个整形参数并返回它们的和
() -> 42  //不接收参数,返回整数'42'
(String s) -> { System.out.println(s); }  //接收一个字符串并把它打印到控制台,不返回值

用在嵌套中,作为方法的参数。

FileFilter java = (File f) -> f.getName().endsWith("*.java");

String user = doPrivileged(() -> System.getProperty("user.name"));//作为参数

new Thread(() -> {
  connectToService();
  sendNotification();
}).start();

3)方法引用

方法引用的唯一用途是支持Lambda简写。方法引用有4类,前两类比较常用。

方法引用通过方法的名字来指向一个方法。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 ::。

①引用静态方法

ContainingClass::staticMethodName 

例子: String::valueOf,对应的Lambda:(s) -> String.valueOf(s)
比较容易理解,和静态方法调用相比,只是把.换为::

②引用特定对象的实例方法

containingObject::instanceMethodName 

例子: x::toString,对应的Lambda:() -> this.toString()
与引用静态方法相比,都换为实例的而已

③引用特定类型的任意对象的实例方法

ContainingType::methodName 

例子: String::toString,对应的Lambda:(s) -> s.toString()
难以理解、也难以维护。建议还是不要用该种方法引用。
实例方法要通过对象来调用,方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。

④引用构造函数

ClassName::new 

例子: String::new ,对应的Lambda: () -> new String()
构造函数本质上是静态方法,只是方法名字比较特殊。

使用:

class Person {
    String firstName;
    String lastName;
    Person() {}

    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

接下来我们指定一个用来创建Person对象的对象工厂接口:

interface PersonFactory<P extends Person> {
    P create(String firstName, String lastName);
}

这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:

PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。

3,接口的默认方法和类方法(静态方法)

以前,接口里的方法要求全部是抽象方法,java8以后允许在接口里定义默认方法和类方法:

不同的是:
默认方法可以通过实现接口的类实例化的对象来调用,而类方法只能在本接口中调用或在实现类中实现。

public interface MyInter {
    default void df(){    //声明一个接口的默认方法
        
        System.out.println("i'am default f");
        sf();        //调用本接口的类方法
    }
    static void sf(){    //声明一个接口的类方法(即static方法,属于类的方法;区别于实例方法)
        
        System.out.println("i'am static f");
    }
}

4,函数式接口(FunctionalInterface )

@FunctionalInterface
public interface ApplicationRunner {
    void run(ApplicationArguments args) throws Exception;
}
  1. 接口注解,必须有且仅有一个抽象方法;
    每一个该类型的lambda表达式都会被匹配到这个抽象方法。
    函数式接口可以被隐式转换为 lambda 表达式。
  2. 可以有很多非抽象方法;
    默认方法 不算抽象方法;
    接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么 也不算抽象方法。
  3. @FunctionalInterface注解不是必须的。

5,java.util.Optional<T>

  1. 场景:为了避免NullPointerException,显式的检查null。
//用ofNullable或者of(明确不会有null)创建值
//如果传入值未null,of会抛出NullPointerException
Optional<List<User>> optional = Optional.ofNullable(userService.getUsers());
//判空 
if(optional.ifPresent())  true/false
//空检查 如果不为null执行表达式
optional.ifPresent(users-> rich(users));
//获取值
optional.get()
//user不为null返回user,为空返回user2
Optional.ofNullable(user).orElse(createNewUser());
//同orElse,但是性能更高。
//高性能的点在于:即使user不为null,orElse依然会执行createNewUser,但是orElseGet不会执行
Optional.ofNullable(user).orElseGet( () -> createNewUser());
//orElseThrow:对象为null时抛出异常
Optional.ofNullable(user)
      .orElseThrow( () -> new IllegalArgumentException());
//值转换:map()、flatMap(), 有值就转换,无值就返回orElse内容
String email = Optional.ofNullable(user)
      .map(u -> u.getEmail()).orElse("default@gmail.com");
String position = Optional.ofNullable(user)
      .flatMap(u -> u.getPosition()).orElse("default");  
//过滤 filter返回测试结果为 true 的值,如果结果为false就返回空的Optional
Optional<User> result = Optional.ofNullable(user)
      .filter(u -> u.getEmail() != null && u.getEmail().contains("@"));         

java9新增了三个方法:

//对象为空时,执行or()
User result = Optional.ofNullable(user)
      .or( () -> Optional.of(new User("default","1234"))).get();
//有值执行第一个动作,无值执行第二个动作
Optional.ofNullable(user).ifPresentOrElse( u -> logger.info("User is:" + u.getEmail()),
  () -> logger.info("User not found"));  
//把实例转换为 Stream 对象,可以使用stream的所有api
List<String> emails = Optional.ofNullable(user)
      .stream()
      .collect(Collectors.toList());

6,Date Time API − 加强对日期与时间的处理

1)java.time.LocalDate

  1. 表示一个格式为yyyy-MM-dd的日期,如2021-06-13;不存储时间或时区。
  2. 不可变类型(线程安全)
    java.util.Date不是线程安全的
  3. 两值比较:equals
说明常用方法备注
初始化LocalDate localDate = LocalDate.now();
字符串转日期LocalDate localDate = LocalDate.parse("2018-10-01")
日期格式转换localDate.format(DateTimeFormatter.ofPattern("MMM dd, yyyy"))
获取该日期格式getChronology()例如:ISO
加法LocalDate localDate2 = localDate1.plus(15, ChronoUnit.DAYS)
localDate1.plus(Period.ofDays(15))
localDate1.plusDays(15)
同理有plusWeeks plusMonths plusYears
减法minus用法同上
调整日期当前星期的星期天:localDate1.with(DayOfWeek.SUNDAY)
当前日期,把年改成2017年 localDate1.with(ChronoField.YEAR, 2017)
当前日期把天改成当月10号 localDate1.withDayOfMonth(10)
当年的第110天 localDate1.withDayOfYear(110)
当前日期,月份改成6月 localDate1.withMonth(6);
获取日期某个int值获取当前日期的年 int val = localDate.get(ChronoField.YEAR)
比较当前对象和other对象在时间上的大小,返回值如果为正,则当前对象时间较晚int compareTo(ChronoLocalDate other)
比较当前对象日期是否在other对象日期之前isBefore(ChronoLocalDate other)同理有isAfter isEqual
检查年份是否为闰年isLeapYear()
给出月份的最大天数lengthOfMonth()同理有lengthOfYear()

2)java.time.LocalTime

3)java.time.LocalDateTime

说明常用方法备注
初始化LocalDateTime time = LocalDateTime.now()
转换为longlong time= localDateTime.toEpochSecond(ZoneOffset.UTC)

4)使用

本地化日期时间 API


import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Month;
 
public class Java8Tester {
   public static void main(String args[]){
      Java8Tester java8tester = new Java8Tester();
      java8tester.testLocalDateTime();
   }
    
   public void testLocalDateTime(){
    
      // 获取当前的日期时间
      LocalDateTime currentTime = LocalDateTime.now();
      System.out.println("当前时间: " + currentTime);
        
      LocalDate date1 = currentTime.toLocalDate();
      System.out.println("date1: " + date1);
        
      Month month = currentTime.getMonth();
      int day = currentTime.getDayOfMonth();
      int seconds = currentTime.getSecond();
        
      System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
        
      LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
      System.out.println("date2: " + date2);
        
      // 12 december 2014
      LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
      System.out.println("date3: " + date3);
        
      // 22 小时 15 分钟
      LocalTime date4 = LocalTime.of(22, 15);
      System.out.println("date4: " + date4);
        
      // 解析字符串
      LocalTime date5 = LocalTime.parse("20:15:30");
      System.out.println("date5: " + date5);
   }
}

输出:

$ javac Java8Tester.java 
$ java Java8Tester
当前时间: 2016-04-15T16:55:48.668
date1: 2016-04-15: APRIL,: 15,: 48
date2: 2012-04-10T16:55:48.668
date3: 2014-12-12
date4: 22:15
date5: 20:15:30

使用时区的日期时间API

如果我们需要考虑到时区,就可以使用时区的日期时间API:


import java.time.ZonedDateTime;
import java.time.ZoneId;
 
public class Java8Tester {
   public static void main(String args[]){
      Java8Tester java8tester = new Java8Tester();
      java8tester.testZonedDateTime();
   }
    
   public void testZonedDateTime(){
    
      // 获取当前时间日期
      ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
      System.out.println("date1: " + date1);
        
      ZoneId id = ZoneId.of("Europe/Paris");
      System.out.println("ZoneId: " + id);
        
      ZoneId currentZone = ZoneId.systemDefault();
      System.out.println("当期时区: " + currentZone);
   }
}

输出:

$ javac Java8Tester.java 
$ java Java8Tester
date1: 2015-12-03T10:15:30+08:00[Asia/Shanghai]
ZoneId: Europe/Paris
当期时区: Asia/Shanghai

7,新工具 − 新的编译工具

如:Nashorn引擎 jjs、 类依赖分析器jdeps。

Nashorn.JavaScript 引擎

Rhino的接替者,轻量级高性能的javascript运行环境。它允许我们在JVM上运行特定的javascript应用。

Nashorn取代Rhino(JDK 1.6, JDK1.7)成为Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1规范以及一些扩展。它使用基于JSR 292的新语言特性,其中包含在JDK 7中引入的 invokedynamic,将JavaScript编译成Java字节码。与先前的Rhino实现相比,这带来了2到10倍的性能提升。

主要是给Android的混合开发使用的。

jjs

jjs是个基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。

例如,我们创建一个具有如下内容的sample.js文件:print('Hello World!');
打开控制台,输入以下命令:$ jjs sample.js
以上程序输出结果为:Hello World!

8,新增base64加解密API

在Java 8中,Base64编码已经成为Java类库的标准。
Java 8 内置了 Base64 编码的编码器和解码器。
Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:

  1. 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
  2. URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
  3. MIME:输出隐射到MIME友好格式。输出每行不超过76字符,并且使用’\r’并跟随’\n’作为分割。编码输出最后没有行分割。

内嵌类:

内嵌类描述
static class Base64.Decoder该类实现一个解码器用于,使用 Base64 编码来解码字节数据。
static class Base64.Encoder该类实现一个编码器,使用 Base64 编码来编码字节数据。
方法:
方法名描述
static Base64.Decoder getDecoder()返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。
static Base64.Encoder getEncoder()返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。
static Base64.Decoder getMimeDecoder()返回一个 Base64.Decoder ,解码使用 MIME 型 base64 编码方案。
static Base64.Encoder getMimeEncoder()返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案。
static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator)返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案,可以通过参数指定每行的长度及行的分隔符。
static Base64.Decoder getUrlDecoder()返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。
static Base64.Encoder getUrlEncoder()返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。
Base64 类的很多方法从 java.lang.Object 类继承。

9,Annotation 注解(重复注解)

10,JVM的permGen空间移除,被Metaspace元空间取代

最直接的后果,就是以后再也不会有outofmemoryerror permgen space这个错误了

最根本的好处就是在效率提升的同时增加了安全性。相对于C++来说,java语言更加安全,jvm具有自动的垃圾回收机制,而C++的程序员需要手动去清除垃圾。JVM这种设计思路确实使java语言的安全性提高了,并且不需要程序员手动去清理垃圾,但是带来了时间上的开销,也就导致了java语言效率低,速度相对慢。大部分实时系统比如股票实时显示系统智能用C或者C++来做,根本不能用java。基于jvm最初的设计,为了能在保证安全性的同时还能提高效率,最好的方式就是减少垃圾的产生,关闭String池就是从JDK7到JDK8的一个过渡,还有一些方式,总知思路就是减少产生垃圾的空间。

permSize:原来的jar包及你自己项目的class存放的内存空间,这部分空间是固定的,启动参数里面-permSize确定,如果你的jar包很多,经常会遇到permSize溢出,且每个项目都会占用自己的permGen空间
改成metaSpaces,各个项目会共享同样的class内存空间,比如两个项目都用了fast-json开源包,在mentaSpaces里面只存一份class,提高内存利用率,且更利于垃圾回收

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值