- Java8新特性
- 1. lambda表达式
- 1.1 Lambda表达式内置四大核心函数
- 1.2 Lambda方法引用
- 2. StreamAPI
- 2.1 StreamAPI操作一:创建Stream
- 2.3 StreamAPI操作三:终止操作
- 2.3.2 规约
- 2.3.3 收集
- 2.3.4 例题
- 2.4 并行流和串行流
- 2.4.1 Fork/Join框架
- 2.4.2 Fork/Join栗子
- 2.5 Optional类
- 2.5.1 Optional类方法解析
- 2.5.2 Optional类应用栗子
- 3. 全新的时间日期API
- 3.1 Java8时间API常用方法
- 3.2 日期的操纵
- 4. Java8其他特性
- 4.1 接口中的默认方法和静态方法
- 4.1.1 冲突一
Lambda表达式的作用:一句话概括就是由垃圾代码变成高级代码的过程
- 演变过程: 垃圾代码 -> 策略模式 -> 匿名内部类 -> Lambda表达式
Consumer:
:消费型接口; 只有输入,没有输出,需要实现的抽象函数为:void accept(T t);
@Test
public void test01() {
happy(1000, (x) -> System.out.println("消费了" + x + "元"));
}
public void happy(double money, Consumer con) {
con.accept(money);
}
Supplier:
供给型接口; 没有输入,只有输出,用来产生一个对象,需要实现的抽象函数为:T get();
@Test
public void test02() {
List list = getNumList(10, () -> (int) (Math.random() * 100));
System.out.println(list);
}
//需求,产生指定个数的数字(类型未知)并放入集合中
private List getNumList(int num, Supplier sup) {
List list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}
Function:
函数型接口; 输入一个对象,返回一个对象,需要实现的抽象函数为:R apply(T t);
@Test
public void test03() {
System.out.println(strHandler("tt 我乃梁奉先是也 ", String::trim));
System.out.println(strHandler("我乃梁奉先是也", (str) -> str.substring(2, 5)));
}
//需求:用于处理字符串
public String strHandler(String str, Function function) {
return function.apply(str);
}
Predicate:
断言型接口; 输入一个对象,返回一个Boolean值,需要实现的抽象函数为boolean test(T t)
@Test
public void test04(){
List list= Arrays.asList("Hello","我乃梁奉先是也","Lambda","www","ok");
//过滤长度大于三的字符串
System.out.println(filterStr(list,(str)->str.length()>3));
}
//需求,将满足条件的字符串添加到集合中
private List filterStr(List list, Predicate predicate) {
List strList = new ArrayList<>();
for (String str : list) {
if (predicate.test(str)) {
strList.add(str);
}
}
return strList;
}
1.2 Lambda方法引用
方法引用主要有三种语法格式:方法引用:若Lambda体中的内容有方法已经实现了,我们可以使用"方法引用"(可以理解为方法引用是Lambda表达式的另一种表现形式)
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
- Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的参数列表和返回值类型保持一致
- 若Lambda参数列表中的第一参数是是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method
对象::实例方法名
@Test
public void test01() {
//Consumer consumer=(x)->System.out.println(x);
//用lambda表达式的方法引用形式实现抽象方法
Consumer consumer = System.out::println;
//往抽象方法传参
consumer.accept("我乃梁奉先是也");
}
```java @Test public void test03() { List list = Arrays.asList("a","aaa","ddd"); list.forEach(System.out::println); } ```类::静态方法名
类::实例方法名
@Test
public void test04(){
//BiPredicate是Predicate的子类,它可以传入两个参数,并返回一个Boolean值
BiPredicate bp= String::equals;
System.out.println(bp.test("1","2"));
}
2. StreamAPI
Stream 的三个操作步骤
- 创建Stream
- 中间操作
- 终止操作(终端操作)
终止操作常用方法:
一、查找和匹配
- allMatch:检查是否匹配所有元素
- anyMatch:检查是否至少匹配一个元素
- noneMatch:检查是否没有匹配所有元素
- findFirst:返回第一个元素
- findAny:返回当前流中的任意元素
- count:返回流中元素的总个数
- max:返回流中的最大值
- min:返回流中的最小值
二、规约
- reduce(T identity,BinaryOperator)/ reduce(BinaryOperator):可以将流中的元素反复结合起来,得到一个值
三、收集:
- collect:将流转换为其他形式。接受一个Collector接口的实现,用于给Stream中元素做汇总的方法
####2.3.1 查找和匹配
下面栗子中用到的employees集合:
Listemployees = Arrays.asList( new Employee("张三", 18, 9999.99, Employee.Status.FREE), new Employee("李四", 58, 5555.55, Employee.Status.BUSY), new Employee("王五", 26, 3333.33, Employee.Status.VOCATION), new Employee("赵六", 36, 6666.66, Employee.Status.FREE), new Employee("田七", 19, 8888.88, Employee.Status.BUSY), new Employee("田七", 19, 8888.88, Employee.Status.BUSY), new Employee("田七", 19, 8888.88, Employee.Status.BUSY) );
allMatch anyMatch noneMatch findFirst
@Test
public void test01() {
//allMatch:检查是否匹配所有元素
Boolean b = employees.stream()
.allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b);
System.out.println("---------------------------");
//anyMatch:检查是否至少匹配一个元素
Boolean b2 = employees.stream()
.anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b2);
System.out.println("---------------------------");
//noneMatch:检查是否没有匹配所有元素
Boolean b3 = employees.stream()
.noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b3);
System.out.println("---------------------------");
//findFirst:返回第一个元素
Optional op = employees.stream()
//先根据Salary升序
.sorted(Comparator.comparingDouble(Employee::getSalary))
//取出第一个元素
.findFirst();
System.out.println(op.get());
}
count max min
@Test
public void test02() {
//count:返回流中元素的总个数
Long count = employees.stream()
.count();
System.out.println(count);
System.out.println("-----------");
//max:返回流中的最大值
Optional op1 = employees.stream()
.max(Comparator.comparingDouble(Employee::getSalary));
System.out.println(op1.get());
System.out.println("------------");
Optional op2 = employees.stream()
.map(Employee::getSalary)
.min(Double::compare);
System.out.println(op2.get());
}
2.3.2 规约
reduce(T identity,BinaryOperator) reduce(BinaryOperator)
@Test
public void test03() {
//reduce(T identity,BinaryOperator)
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = list.stream()
//identity是一个种子,也就是规约的起始值;后面写对元素的操作
.reduce(0, Integer::sum);
System.out.println(sum);
//reduce(BinaryOperator),由于没有起始值所以可能为空,故返回Optional类型
Optional op = employees.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(op.get());
}
2.3.3 收集
collect
//对字符串收集,修改
@Test
public void test10(){
String str = employees.stream()
.map(Employee::getName)
//.collect(Collectors.joining());//返回字符串:张三李四王五赵六田七田七田七
//.collect(Collectors.joining(","));//返回字符串:张三,李四,王五,赵六,田七,田七,田七
//返回字符串:开始拼接:张三,李四,王五,赵六,田七,田七,田七;结束拼接
.collect(Collectors.joining(",","开始拼接:",";结束拼接"));
System.out.println(str);
}
//求算数运算的另一种方法
@Test
public void test09(){
DoubleSummaryStatistics dss = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println("求和为:"+dss.getSum());
System.out.println("求平均值为:"+dss.getAverage());
System.out.println("求最大值为:"+dss.getMax());
}
//分区(分片),可以分为true和false区,满足条件的一个区,不满足条件的一个区
@Test
public void test08(){
Map> map = employees.stream()
.collect(Collectors.partitioningBy((e) -> e.getSalary() > 8000));
System.out.println(map);
}
//多级分组进行操作
@Test
public void test07(){
Map>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
if (e.getAge() <= 35) {
return "青年";
} else if (e.getAge() <= 50) {
return "中年";
} else {
return "老年";
}
})));
System.out.println(map);
}
//分组进行操作
@Test
public void test06(){
Map> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(map);
}
@Test
public void test05() {
//总数
Long count = employees.stream()
.collect(Collectors.counting());
System.out.println("员工的总数是:" + count);
//平均值
Double avg = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println("工资的平均值是:" + avg);
//总和
Double sum = employees.stream()
.collect(Collectors.summingDouble(Employee::getSalary));
System.out.println("工资的总和是:" + sum);
//最大值
Optional max = employees.stream()
.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println("工资的最大值是:" + max.get());
//最小值
Optional min = employees.stream()
.map(Employee::getSalary)
.collect(Collectors.minBy(Double::compare));
System.out.println(min.get());
}
@Test
public void test04() {
//将处理后的流返回成一个List集合
List list = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
list.forEach(System.out::println);
System.out.println("--------------------");
//将处理后的流返回成一个Set集合
Set set = employees.stream()
.map(Employee::getName)
.collect(Collectors.toSet());
set.forEach(System.out::println);
System.out.println("---------------------");
//需要将搜集的结果存放在特定的数据结构中
HashSet hashSet = employees.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(HashSet::new));//产生一个集合
hashSet.forEach(System.out::println);
}
2.3.4 例题
@Test
public void test01(){
List list= Arrays.asList(1,2,3,4,5);
list.stream()
.map((x)->x*x)
.forEach(System.out::println);
}
@Test
public void test02(){
List employees = Arrays.asList(
new Employee("张三", 18, 9999.99, Employee.Status.FREE),
new Employee("李四", 58, 5555.55, Employee.Status.BUSY),
new Employee("王五", 26, 3333.33, Employee.Status.VOCATION),
new Employee("赵六", 36, 6666.66, Employee.Status.FREE),
new Employee("田七", 19, 8888.88, Employee.Status.BUSY),
new Employee("田七", 19, 8888.88, Employee.Status.BUSY),
new Employee("田七", 19, 8888.88, Employee.Status.BUSY)
);
Optional sum = employees.stream()
//这里表示每一个对象进去不进行操作,返回1
.map((e) -> 1)
//对每一个对象返回的1进行累加,即计数
.reduce(Integer::sum);
System.out.println(sum.get());
}
2.4 并行流和串行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流
Java 8中对并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明性地通过parallel()和sequential()在并行流与顺序流之间进行切换
2.4.1 Fork/Join框架Fork/Join框架就是把大任务fork成小任务,交给不同线程处理后将结果join的过程
2.4.2 Fork/Join栗子Fork/Join框架与传统线程池的区别
框架类
package com.fx.StreamAPI; import java.util.concurrent.RecursiveTask; public class ForkJoinCalculate extends RecursiveTask{ //提供一个序列号 private static final long serialVersionUID = 1234567890L; //操作的开始位置 private long start; //操作的结束位置 private long end; //拆分停止的临界值 private static final long THRESHOLD = 10000; //提供一个构造器 public ForkJoinCalculate(long start, long end) { this.start = start; this.end = end; } @Override protected Long compute() { long length = end - start; //如果拆分的长度小于临界值,执行操作,在这个案例里操作为累加求和 if (length <= THRESHOLD) { long sum = 0; for (long i = start; i < end; i++) { sum += i; } return sum; } else { //length长度大于临界值,进行拆分 //获得拆分长度的中间值 long middle = (start + end) / 2; ForkJoinCalculate left = new ForkJoinCalculate(start, middle); //拆分子任务,同时压入线程队列 left.fork(); ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end); right.fork(); //完成fork交给不同线程处理完后要Join结果 return left.join() + right.join(); } } }
测试类
package com.fx.StreamAPI;
import org.junit.Test;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
public class TestForkJoin {
@Test
public void test01(){
//记录一下时间戳
Instant startTime = Instant.now();
//执行ForkJoin需要ForkJoin线程池的支持
ForkJoinPool pool=new ForkJoinPool();
//从zero累加到billion
ForkJoinTask task=new ForkJoinCalculate(0,1000_000_000L);
Long sum=pool.invoke(task);
//记录一下时间戳
Instant endTime = Instant.now();
System.out.println(sum);
//打印时间戳
System.out.println("耗费时间:"+Duration.between(startTime,endTime).toMillis());//耗时325ms
}
@Test
public void test02(){
long sum=0L;
for (int i = 0; i <= 1000_000_000L; i++) {
sum+=i;
}
System.out.println(sum);//耗时2s645ms
}
@Test
public void test03(){
//记录一下时间戳
Instant startTime = Instant.now();
long Time = LongStream.rangeClosed(0, 1000_000_000L)
//启用并行流
.parallel()
.reduce(0, Long::sum);
System.out.println(Time);
Instant endTime = Instant.now();
System.out.println("耗费时间:"+Duration.between(startTime,endTime).toMillis());//耗费时间:242ms
}
}
2.5 Optional类
Optional
类(java.util.Optional)是一个容器类,代表一个值存在或不存在,原来用null表示一个值不存在,现在Optional可以更好的表达这个概念,并且可以有效的避免NullPointException
常用方法:
- Optional.of(T t):创建一个Optional实例
- Optional.empty():创建一个空的Optional实例
- Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
- get():取出容器类里封装的对象
- isPresent():判断是否包含值
- orElse(T t):如果调用对象包含值,返回该值,否则返回t
- orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回s获取的值
- map(Function f):如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
- flatMap(Function mapper):与map类似,要求返回值必须是Opeional
@Test
public void test06(){
Optional op = Optional.ofNullable(new Employee("钱一", 18, 8888.88,
Employee.Status.FREE));
//与Stream的map作用类似
Optional op2 = op.map(Employee::getName);
System.out.println(op2.get());
//flatmap要求返回对象也必须用Optional进行封装
Optional op3 = op.flatMap((e) -> Optional.of(e.getName()));
System.out.println(op3.get());
}
@Test
public void test05(){
Optional op = Optional.ofNullable(null);
//和orElse的区别在于,orElseGet实现的是一个供给性接口,Lambda里可以写逻辑代码,更加灵活
Employee emp = op.orElseGet(Employee::new);
System.out.println(emp);
}
@Test
public void test04(){
Optional op = Optional.ofNullable(null);
//防止为空,为空会替换成默认值
Employee emp = op.orElse(new Employee("钱一", 18, 8888.88, Employee.Status.FREE));
System.out.println(emp);
}
@Test
public void test03() {
//ofNullable方法是of()和empty方法的折中,底层代码为return value == null ? empty() : of(value);
Optional op = Optional.ofNullable(null);
//isPresent():判断是否包含某值,Present有现存的意思
if (op.isPresent()) {
System.out.println(op.get());
} else {
System.out.println("Optional封装的对象为空");
}
}
@Test
public void test02() {
Optional op = Optional.empty();
//会报java.util.NoSuchElementException: No value present,也是为了快速定位异常代码
System.out.println(op.get());
}
@Test
public void test01() {
//调用of方法封装的是null时会报NullPointException,封装的意义是快速定位异常发生的地方
Optional op = Optional.of(null);
Employee employee = op.get();
System.out.println(employee);
}
2.5.2 Optional类应用栗子
栗子里用到的JavaBean
public class NewMan {
//给一个empty值,防止报空指针异常
private Optional goddess=Optional.empty();
···提供对应的构造器、setter/getter方法、重写toString()方法
}
public class Man {
private Goddess goddess;
···提供对应的构造器、setter/getter方法、重写toString()方法
}
public class Goddess {
private String name;
···提供对应的构造器、setter/getter方法、重写toString()方法
}
栗子
//传统方式
@Test
public void test1001(){
Man man=new Man();
String goddessName=getGoddessName(man);
//会输出女神的默认值,因为Man类女神为空
System.out.println(goddessName);
}
//Java8优化后
@Test
public void test1002(){
Optional op = Optional.ofNullable(null);
String str = getGoddessName2(op);
System.out.println(str);
}
//改进后防止空指针的方法
public String getGoddessName2(Optional man){
return man.orElse(new NewMan())
.getGoddess()
.orElse(new Goddess("女神的默认值"))
.getName();
}
//传统的防止空指针异常的栗子
public String getGoddessName(Man man){
if(null!=man){
Goddess gn=man.getGoddess();
if(null!=gn){
return gn.getName();
}
}
return "一个女神的默认值";
}
3. 全新的时间日期API
由于之前的时间API中都不是线程安全的,所以在Java8中更新了一套全新的时间API
举个栗子:
@Test
public void test01() throws ExecutionException, InterruptedException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
//每次都解析这个时间戳
Callable task= () -> sdf.parse("20210928");
//创建一个拥有十个线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
List> results=new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add(pool.submit(task));
}
for (Future future:results) {
System.out.println(future.get());
}
}
使用Java8时间API:
@Test
public void test02() throws ExecutionException, InterruptedException {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
//每次都解析这个时间戳
Callable task= () -> LocalDate.parse("20210929",dtf);
//创建一个拥有十个线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
List> results=new ArrayList<>();
for (int i = 0; i < 10; i++) {
results.add(pool.submit(task));
}
for (Future future:results) {
System.out.println(future.get());
}
}
3.1 Java8时间API常用方法
Java8为我们提供了很多安全的时间API
- LocalDate:当地日期
- LocalTime:当地时间
- LocalDateTime:当地日期和时间
- Instant:时间戳(以Unix为例,元年指1970年1月1日零点零时零分到某个时间的毫秒值),这里获取的是UTC时间
- Period:计算两个“日期”之间的间隔
- Duration:计算两个“时间”之间的间隔
举个栗子:
@Test
public void test02() {
LocalDateTime ldf = LocalDateTime.now();
//输出ISO规定的标准时间
System.out.println(ldf);//输出2021-09-29T18:44:58.328
//输出年
System.out.println(ldf.getYear());
//输出月 getMonth()会输出英文的月份
System.out.println(ldf.getMonthValue());
//输出日
System.out.println(ldf.getDayOfMonth());
//输出时
System.out.println(ldf.getHour());
//输出分
System.out.println(ldf.getMinute());
//输出秒
System.out.println(ldf.getSecond());
}
@Test
public void test03(){
//UTC是时间统一时间,在数值上等同于格林威治(GMT)时间
Instant ins1 = Instant.now();//默认获取UTC时区
System.out.println(ins1);
//修正时间
OffsetDateTime odt = ins1.atOffset(ZoneOffset.ofHours(8));
System.out.println(odt);//输出2021-09-30T15:46:46.254+08:00
}
@Test
public void test05(){
LocalDate ld1 = LocalDate.of(2015, 1, 1);
LocalDate ld2 = LocalDate.now();
Period period = Period.between(ld1, ld2);
//默认输出ISO规定格式的时间差
System.out.println(period);//输出P6Y8M29D,表示差了6年8个月29天
//输出年
System.out.println(period.getYears());
//输出月
System.out.println(period.getMonths());
//输出日
System.out.println(period.getDays());
}
@Test
public void test04(){
Instant ins1=Instant.now();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
Instant ins2 = Instant.now();
Duration duration = Duration.between(ins1, ins2);
//获取两个时间之间的毫秒值
System.out.println(duration.toMillis());
//获取两个时间之间的秒值
System.out.println(duration.getSeconds());
}
3.2 日期的操纵
Java8为我们提供了许多关于时间的操纵
常用的方法:
- TemporalAdjuster:时间校正器
- TemporalAdjusters:通过静态方法提供了大量的常用的TemporalAdjuster的实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GQWhVUmS-1634730668996)(https://i.loli.net/2021/09/30/8lTbMqoQLFz5PEh.png)]
传统的输出时间的方式:
Date now=new Date(); //创建一个Date对象,获取当前时间
//指定格式化格式
SimpleDateFormat f=new SimpleDateFormat("现在时间是 "+"yyyy 年 MM 月 dd 日 E HH 点 mm 分 ss 秒");
System.out.pnntln(f.format(now)); //将当前时间袼式化为指定的格式
4. Java8其他特性
4.1 接口中的默认方法和静态方法
- 允许声明在接口中实现了的方法,称为默认方法
- 允许在接口中定义静态方法
当继承类和实现接口冲突时,遵守类优先的规则
举个栗子:
//用Java8新特性在接口中定义一个默认方法
public interface MyFunction {
default String getName(){
return "返回MyFunction里的值";
}
}
//定义一个普通的类
public class MyClass {
public String getName(){
return "返回MyClass里的值";
}
}
//定义一个类对上述类继承
public class SubClass extends MyClass implements MyFunction {}
//测试,输出‘返回MyClass里的值’,表示当继承类和实现接口冲突时,遵守类优先的规则
public class TestDefaultInterface {
public static void main(String[] args) {
SubClass subClass = new SubClass();
System.out.println(subClass.getName());
}
}
详解:
当前时间
//指定格式化格式
SimpleDateFormat f=new SimpleDateFormat("现在时间是 "+“yyyy 年 MM 月 dd 日 E HH 点 mm 分 ss 秒”);
System.out.pnntln(f.format(now)); //将当前时间袼式化为指定的格式
[外链图片转存中...(img-xDRqyNnw-1634730669000)] ## 4. Java8其他特性 ### 4.1 接口中的默认方法和静态方法
- 允许声明在接口中实现了的方法,称为默认方法
- 允许在接口中定义静态方法
举个栗子: ```java //用Java8新特性在接口中定义一个默认方法 public interface MyFunction { default String getName(){ return "返回MyFunction里的值"; } } //定义一个普通的类 public class MyClass { public String getName(){ return "返回MyClass里的值"; } } //定义一个类对上述类继承 public class SubClass extends MyClass implements MyFunction {} //测试,输出‘返回MyClass里的值’,表示当继承类和实现接口冲突时,遵守类优先的规则 public class TestDefaultInterface { public static void main(String[] args) { SubClass subClass = new SubClass(); System.out.println(subClass.getName()); } }当继承类和实现接口冲突时,遵守类优先的规则
详解:
[外链图片转存中…(img-yYbj4BNy-1634730669001)]



