1,java.util.stream(函数式编程风格)
1)概念
Stream的操作可以串行执行或者并行执行。
2)流的特性
1>Intermediate
- 一个流可以后面跟随零个或多个 intermediate 操作。
- Intermediate操作是惰性化的(lazy),并没有真正开始流的遍历。
- 相关操作:
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
2>Terminal
- 一个流只能有一个 terminal 操作,作为流的结束操作。
- Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
- 相关操作:
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
3>Short-circuiting
- 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
- 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。
- 当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。
- 相关操作:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit.
3)流的使用
三个步骤:
聚合操作为最终操作。
1>获取一个数据源(source)
创建Stream:
功能 | 举例 | 备注 |
---|---|---|
常量转换为stream | Stream stream = Stream.of("a", "b", "c") | 通过Stream接口的静态工厂方法 |
数组转换为stream | Arrays.asList(strArray).stream() | 通过Collection接口的默认方法 |
并行Stream则是在多个线程上同时执行 | long count = values.parallelStream().sorted().count(); | |
生成一个无限长度的Stream | Stream.generate(() -> Math.random()); Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println); | 一般和limit一起使用 |
map转steam | aMap.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() 详细用法见下文 | 返回新的stream,不会影响原有数据源。 |
1>sort 排序
- map升序:
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
- map降序:
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
- list升序:
list.stream().sorted()...
- list降序:
list.stream().sorted(Comparator.reverseOrder())...
List<Object>
排序
.sorted(new Comparator<OrgInfo>() {
@Override
public int compare(OrgInfo o1, OrgInfo o2) {
//升序排序,降序反写
return o1.getId()-o2.getId();
}
})
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)->...) | 计算并更新值 | |
computeIfAbsent | key不存在时才计算 | |
computeIfPresent | key存在时才计算 |
统计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;
}
- 接口注解,必须有且仅有一个抽象方法;
每一个该类型的lambda表达式都会被匹配到这个抽象方法。
函数式接口可以被隐式转换为 lambda 表达式。 - 可以有很多非抽象方法;
默认方法 不算抽象方法;
接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么 也不算抽象方法。 @FunctionalInterface
注解不是必须的。
5,java.util.Optional<T>
- 场景:为了避免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
- 表示一个格式为yyyy-MM-dd的日期,如2021-06-13;不存储时间或时区。
- 不可变类型(线程安全)
java.util.Date不是线程安全的 - 两值比较: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() | |
转换为long | long 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编解码器:
- 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
- URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
- 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,提高内存利用率,且更利于垃圾回收