java8-新特性
Java8 全新Stream 机制讲解Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。Java 8新特性优点:速度更快代码更少(增加了新的语法 Lambda 表达式)强大的 Stream API便于并行最大化减少空指针异常 Optional其中最为核心的为 Lambda 表达式与Stream API从匿名类到 Lambda 的转
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。
Java 8新特性优点:
- 速度更快
- 代码更少(增加了新的语法 Lambda 表达式)
- 强大的 Stream API
- 便于并行
- 最大化减少空指针异常 Optional
其中最为核心的为 Lambda 表达式与Stream API
- 从匿名类到 Lambda 的转换
Lambada表达式基础语法:
Lambda 表达式在Java 语言中引入了一个新的操作符为 “->” ,它将 Lambda 分为两个部分:
左侧:Lambda 表达式的参数列表
右侧:Lambda 表达式所需执行的功能,即Lambada体
口令:左右遇一括号省
左侧推断类型省
函数式接口
什么是函数式接口?
- 只包含一个抽象方法的接口,称为函数式接口。
- 你可以通过 Lambda 表达式来创建该接口的对象。
- 我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。
自定义函数式接口:
@FunctionalInterface
public interface MyFun<T> {
T getValue(T T);
}
作为参数传递 Lambda 表达式
//需求:用于处理字符串
public String strHandler(String str,MyFun mf){
return mf.getValue(str);
}
@Test
public void test(){
System.out.println(strHandler("\t\t\t 哈哈哈哈 ", str -> str.trim()));//去除首位空格
System.out.println(strHandler("asdsad", str -> str.toUpperCase()));//小写转换为大写
System.out.println(strHandler("sfsdf", str -> str.substring(2, 4))); //截取字符串
}
作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。
Java 内置四大核心函数式接口
其他接口
方法引用与构造器引用
1.方法引用
若Lambada体中的内容有方法已经实现了(javaApI已经实现的方法),可以使用方法引用!
==注意:==Lambada体中调用方法的参数列表和返回值列表,要与函数式接口中的抽象方法的函数列表和返回值类型一致!
方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。
java中的实例方法是什么意思?
实例方法相对于静态方法(或者叫类方法)而言,它就是没有 static 前缀的一类一般方法,被对象拥有(这也是称之为“实例”方法的原因)。
如下三种主要使用情况:
- 对象::实例方法名
@Test
public void test(){
Consumer<String> con =x-> System.out.println(x);
//等同于
Consumer<String> con1 =System.out::println;
con1.accept("waaf");
}
@Test
public void test2(){
Employee emp =new Employee();
Supplier<String> sup = ()->emp.getName();
System.out.println(sup.get());
//等同于
Supplier<Integer> sup2 = emp::getAge;
System.out.println(sup2.get());
}
- 类::静态方法名
@Test
public void test3(){
Comparator<Integer> com = (x,y)-> Integer.compare(x,y);
//等同于
Comparator<Integer> com1 = Integer::compare;
}
- 类::实例方法名
- 构造器引用
格式: ClassName::new
需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致!
@Test
public void test5(){
Supplier<Employee> sup =()->new Employee();
//构造器引用方式
Supplier<Employee> sup2 =Employee::new;
}
- 数组引用
格式: type[] :: new
// 数组引用
@Test
public void test6(){
Function<Integer,String[]> fun =x->new String[x];
System.out.println(fun.apply(10).length);
//等同于
Function<Integer,String[]> fun2 =String[]::new;
System.out.println(fun.apply(20).length);
}
强大的 Stream API
了解 Stream
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。
什么是 Stream
流(Stream) 到底是什么呢?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,流讲的是计算!”
注意:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream 的操作三个步骤
- 创建 Stream
一个数据源(如:集合、数组),获取一个流 - 中间操作
一个中间操作链,对数据源的数据进行处理 - 终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果
创建 Stream
- 可以通过Java8 中的 Collection 系列集合提供的stream() 或parallelStream()
- default Stream stream() : 返回一个串行流
- default Stream parallelStream() : 返回一 个并行流
- 由数组创建流,Arrays 的静态方法 stream() 可以获取数组流
- 由值创建流,可以使用静态方法 Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数。
- 由函数创建流:创建无限流,可以使用静态方法 Stream.iterate() 和Stream.generate(), 创建无限流。
@Test
public void test(){
//1.通过Java8 中的 Collection 系列集合提供的stream() 或parallelStream()
List<String> list =new ArrayList<>();
Stream<String> stream1 = list.stream();
//2.通过Arrays中的静态方法stream()获取数组流
Employee[] emps =new Employee[10];
Stream<Employee> stream = Arrays.stream(emps);
//3.通过Stream类中的静态方法of()
Stream<String> stream3 = Stream.of("asd", "sa", "as");
// 4.创建无限流
//迭代
Stream<Integer> stream4 = Stream.iterate(0, x -> x + 2);//两个参数,一个起始值,一个一元运算操作
stream4.limit(10).forEach(System.out::println);
//生成
Stream.generate(()->Math.random())
.limit(5)
.forEach(System.out::println);
}
Stream 的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
筛选与切片
//内部迭代:迭代操作有stream API完成
@Test
public void test(){
employees.stream()
//中间操作
.filter(e->e.getAge()>18)
.limit(2) //取出符合条件的两条
.distinct()
//终止操作
.forEach(System.out::println);
}
@Test
public void test2(){
employees.stream()
//中间操作
.filter(e->e.getAge()>18)
.skip(2) //跳过前n条
//终止操作
.forEach(System.out::println);
}
映射
/**
* map(Function f) 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
* flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
*/
@Test
public void test3(){
List<String> list =Arrays.asList("asss","ada","sas","sad");
list.stream()
.map(str->str.toUpperCase())
.forEach(System.out::println);
//------------------------------------------------------
employees.stream()
.map(Employee::getAge)
.forEach(System.out::println);
//----------------------------------------------------------
list.stream()
.map(StreamMiddle::filterCharacter) //{{a,s,s},{a.d.a}}
.forEach(sm->{sm.forEach(System.out::println);});
//-------------------------------------------------------
list.stream()
.flatMap(StreamMiddle::filterCharacter) //{a,s,s,a.d.a}
.forEach(System.out::println);
//map,flatMap类似于add(Object obj),addAll(Collection,coll)
}
public static Stream<Character> filterCharacter(String str){
List<Character> list =new ArrayList<>();
for (Character ch : str.toCharArray()) {
list.add(ch);
}
return list.stream();
}
排序
/**
* sorted() 产生一个新流,其中按自然顺序排序(Comparable)
* sorted(Comparator comp) 产生一个新流,定制排序
*/
@Test
public void test4(){
List<String> list =Arrays.asList("ccc","aaa","ddd","bbb");
list.stream()
.sorted()
.forEach(System.out::println);
//----------------------------------------------
//先按年龄排,年龄一样按姓名排
employees.stream()
.sorted((e1,e2)->{
if(e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
}else {
return -e1.getAge().compareTo(e2.getAge());
}
}).forEach(System.out::println);
}
Stream 的终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
查找与匹配
/**
* allMatch(Predicate p) 检查是否匹配所有元素
* anyMatch(Predicate p) 检查是否至少匹配一个元素
* noneMatch(Predicate p) 检查是否没有匹配所有元素
* findFirst() 返回第一个元素
* findAny() 返回当前流中的任意元素
* count() 返回流中元素总数
* max(Comparator c) 返回流中最大值
* min(Comparator c) 返回流中最小值
*/
@Test
public void test(){
System.out.println(employees.stream()
.allMatch(e -> e.getStatus().equals(Employee.Status.BUSY)));
System.out.println(employees.stream()
.anyMatch(e -> e.getStatus().equals(Employee.Status.BUSY)));
System.out.println(employees.stream()
.noneMatch(e -> e.getStatus().equals(Employee.Status.BUSY)));
Optional<Employee> op = employees.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findFirst(); //Optional是容器类
System.out.println(op);
System.out.println(employees.stream()
.filter(e -> e.getStatus().equals(Employee.Status.VOCATION))
.findAny());
System.out.println(employees.stream()
.count());
Optional<Employee> op1 = employees.stream()
.max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(op1);
Optional<Double> op2 = employees.stream()
.map(Employee::getSalary)
.min(Double::compare);
System.out.println(op2);
}
归约
备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。
/**
*归约 reduce(T iden, BinaryOperator b)/reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。
*/
@Test
public void test2(){
List<Integer> list=Arrays.asList(1,2,3,4,5);
System.out.println(list.stream()
.reduce(0, (x, y) -> x + y));
Optional<Double> op = employees.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(op);
}
收集
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态
方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
/**
*收集 collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法
*/
@Test
public void test3(){
employees.stream()
.map(Employee::getName)
.collect(Collectors.toList()).forEach(System.out::println);
employees.stream()
.map(Employee::getName)
.collect(Collectors.toSet()).forEach(System.out::println);
employees.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(HashSet::new)).forEach(System.out::println);
}
//一些主函数 max(),min(),count(),sum()...
@Test
public void test4(){
//总数
System.out.println(employees.stream()
.collect(Collectors.counting()));
//平均值
System.out.println(employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary)));
//总和
System.out.println(employees.stream()
.collect(Collectors.summingDouble(Employee::getSalary)));
//最大值
Optional<Employee> max = employees.stream()
.collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(max.get());
//最小值
System.out.println(employees.stream()
.map(Employee::getSalary)
.collect(Collectors.minBy(Double::compare)).get());
}
//分组
@Test
public void test5(){
Map<Employee.Status, List<Employee>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(map);
}
//多级分组
public void test6(){
Map<Employee.Status, Map<String, List<Employee>>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(e -> {
if (((Employee) e).getAge() <= 35) {
return "青年";
} else if (((Employee) e).getAge() <= 50) {
return "中年";
} else {
return "老年";
}
})));
System.out.println(map);
}
//分区
public void test7(){
System.out.println(employees.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 500)));
}
//流中属性的统计值。
public void test8(){
DoubleSummaryStatistics dss = employees.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(dss.getMax());
}
//连接字符串
public void test9(){
System.out.println(employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(",")));
}
并行流与串行流
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与sequential() 在并行流与顺序流之间进行切换。
/**
* java8
*/
@Test
public void test3(){
Instant start = Instant.now();
LongStream.rangeClosed(0L,1000000000L)
.parallel()
.reduce(0,Long::sum);
Instant end = Instant.now();
System.out.println("耗费时间为:"+ Duration.between(start,end).toMillis());//511
}
了解 Fork/Join 框架
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总.
Fork/Join 框架与传统线程池的区别
采用 “工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态.而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行.那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程
的等待时间,提高了性能。
其他新特性
Optional 类
Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
常用方法:
Optional.of(T t) : 创建一个 Optional 实例
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
isPresent() : 判断是否包含值
orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
flatMap(Function mapper):与 map 类似,要求返回值必须是Optional
@Test
public void test(){
Optional<Employee> op = Optional.of(new Employee());
System.out.println(op.get());
Optional<Object> op1 = Optional.empty();
System.out.println(op1.get()); //可以精确定位,java.util.NoSuchElementException: No value present
Optional<Employee> op2 = Optional.ofNullable(new Employee());
// if(op2.isPresent()){
// System.out.println(op2.get());
// }
System.out.println(op2.orElse(new Employee(16,"sfsf",456, Employee.Status.FREE)));
op2.orElseGet(()->new Employee());
Optional<Employee> op3 = Optional.ofNullable(new Employee(16,"sfsf",456, Employee.Status.FREE));
Optional<String> str = op3.map(e -> e.getName());
Optional<String> s = op3.flatMap(e -> Optional.of(e.getName()));
}
接口中的默认方法与静态方法
接口中的默认方法
Java 8中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用 default 关键字修饰。
接口默认方法的”类优先”原则
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时
- 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
- 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法
是否是默认方法),那么必须覆盖该方法来解决冲突
接口中的静态方法
Java8 中,接口中允许添加静态方法。
新时间日期 API
相对以前的时间日期线程安全。
//jdk1.8之前解决多线程下对时间日期格式的转换问题
public class TestSimpleDateFormat {
public static void main(String[] args) throws Exception{
Callable<Date> task=new Callable<Date>() {
@Override
public Date call() throws Exception {
return DateFormatThreadLocal.convert("20211213");
}
};
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> results=new ArrayList<>();
for (int i = 0; i <10 ; i++) {
results.add(pool.submit(task));
}
for (Future<Date> future : results) {
System.out.println(future.get());
}
pool.shutdown();
}
public class DateFormatThreadLocal {
private static final ThreadLocal<DateFormat> df=new ThreadLocal<DateFormat>(){
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd");
}
};
public static Date convert(String source) throws ParseException {
return df.get().parse(source);
}
}
//jdk8解决了线程安全问题
public class TestSimpleDateFormat {
public static void main(String[] args) throws Exception{
DateTimeFormatter dtf=DateTimeFormatter.ofPattern("yyyyMMdd");
Callable<LocalDate> task=new Callable<LocalDate>() {
@Override
public LocalDate call() throws Exception {
return LocalDate.parse("20161216",dtf);
}
};
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<LocalDate>> results=new ArrayList<>();
for (int i = 0; i <10 ; i++) {
results.add(pool.submit(task));
}
for (Future<LocalDate> future : results) {
System.out.println(future.get());
}
pool.shutdown();
}
}
使用 LocalDate、LocalTime、LocalDateTime
- LocalDate、LocalTime、LocalDateTime 类的实例是==不可变的对象,==分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。
注: 日历系统是国际标准化组织制定的现代公民的日期和时间的表示法
@Test
public void test(){
//LocalDate LocalTime LocalDateTime
System.out.println(LocalDateTime.now());//获取当前系统时间 2021-04-05T20:16:47.810,ISO-8601格式输出标椎
System.out.println(LocalDateTime.of(2021, 2, 18, 14, 28, 30));
//向当前localDate对象加2年
System.out.println(LocalDateTime.now().plusYears(2));
//向当前localDate对象减2月
System.out.println(LocalDateTime.now().minusMonths(2));
//获得年份
System.out.println(LocalDateTime.now().getYear());
}
Instant 时间戳
- 用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算
//时间戳:计算机所读的时间日期格式
//Instant:时间戳(是以Unix元年:1970年1月1日00:00:00到某个时间扥毫秒值)
@Test
public void test2(){
System.out.println(Instant.now());//默认获取UTC时区 2021-04-05T12:29:58.142Z
System.out.println(Instant.now().atOffset(ZoneOffset.ofHours(8)));//带偏移量的时间
System.out.println(Instant.now().toEpochMilli());//转成毫秒时间
System.out.println(Instant.ofEpochSecond(1));//1970-01-01T00:00:00Z+1s
}
Duration 和 Period
- Duration:用于计算两个“时间”间隔
- Period:用于计算两个“日期”间隔
//Duration:用于计算两个“时间”间隔 Period:用于计算两个“日期”间隔
@Test
public void test3(){
//时间间隔
Instant ins1 = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant ins2 = Instant.now();
System.out.println(Duration.between(ins1, ins2));//PT1S
System.out.println(Duration.between(ins1, ins2).toMillis());//转换成毫秒
LocalTime loc1 = LocalTime.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
LocalTime loc2 = LocalTime.now();
System.out.println(Duration.between(loc1, loc2).toMillis());
//日期间隔
LocalDate id1 = LocalDate.of(2015, 1, 1);
LocalDate id2 = LocalDate.now();
System.out.println(Period.between(id1, id2).getDays());
}
日期的操纵
- TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。
- TemporalAdjusters : 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现。
//TemporalAdjuster : 时间校正器。
@Test
public void test4(){
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
LocalDateTime ldt2 = ldt.withDayOfMonth(10);//指定日
System.out.println(ldt2);
System.out.println(ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)));//获取下个周日
//自定义:下个工作日
LocalDateTime ldt4 = ldt.with(x -> {
LocalDateTime ldt3 = (LocalDateTime) x;
if (ldt3.getDayOfWeek().equals(DayOfWeek.FRIDAY)) {
return ldt3.plusDays(3);
} else if (ldt3.getDayOfWeek().equals(DayOfWeek.SATURDAY)) {
return ldt3.plusDays(2);
} else {
return ldt3.plusDays(1);
}
});
System.out.println(ldt4);
}
解析与格式化
java.time.format.DateTimeFormatter 类:该类提供了三种
格式化方法:
- 预定义的标准格式
- 语言环境相关的格式
- 自定义的格式
//DateTimeFormatter 格式化时间/日期
@Test
public void test5(){
DateTimeFormatter dtf=DateTimeFormatter.ISO_DATE;//默认指定格式
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt.format(dtf));
//自定义
DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String strData = dtf2.format(ldt);
System.out.println(strData);
System.out.println(ldt.parse(strData,dtf2)); //将字符串转化会时间日期格式
}
时区的处理
Java8 中加入了对时区的支持,带时区的时间为分别为:
ZonedDate、ZonedTime、ZonedDateTime
其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式
例如 :Asia/Shanghai 等
ZoneId:该类中包含了所有的时区信息
getAvailableZoneIds() : 可以获取所有时区时区信息
of(id) : 用指定的时区信息获取 ZoneId 对象
与传统日期处理的转换
//ZonedDate、ZonedTime、ZonedDateTime
@Test
public void test6(){
//输出所有的时区
ZoneId.getAvailableZoneIds().forEach(System.out::println);
LocalDateTime idt2 = LocalDateTime.now(ZoneId.of("Asia/Chita"));//指定一个时区
ZonedDateTime zdt = idt2.atZone(ZoneId.of("Asia/Chita"));
System.out.println(zdt);
}
重复注解与类型注解
Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。
更多推荐
所有评论(0)