JDK8新特性
1、Lambda表达式
1.1、基础语法:
- Java8中引入了一个新的操作符 "->" 该操作符称为箭头操作符或 Lambda 操作符箭头操作符将 Lambda 表达式拆分成两部分。
- 左侧:Lambda 表达式的参数列表
右侧:Lambda 表达式中所需执行的功能, 即 Lambda 体。
1.2、语法格式
1.2.1、语法格式1:
- 无参数无返回值:() -> System.out.println("Hello Lambda!");
@Test
public void nothing(){
Runnable r = ()-> System.out.println("hello,lambda!");
r.run();
}
1.2.2、语法格式2:
- 有一个参数,并且无返回值:(x) -> System.out.println(x)
@Test
public void oneParam(){
Consumer consumer = (x)-> System.out.println(x);
consumer.accept("周末愉快");
}
1.2.3、语法格式3:
- 若只有一个参数,小括号可以省略不写:x -> System.out.println(x)
@Test
public void oneParam(){
Consumer consumer = x -> System.out.println(x);
consumer.accept("周末愉快");
}
1.2.4、语法格式4:
- 有两个以上的参数,lambda体中有多条语句,lambda体必须使用{}
@Test
public void twoParam(){
Comparatorcomparable = (x,y)-> {
System.out.println("开始比较");
return Integer.compare(x,y);
};
System.out.println(comparable.compare(3,5));
}
1.2.5、语法格式5:
- 若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
@Test
public void twoParam(){
Comparatorcomparable = (x, y)-> Integer.compare(x,y);
System.out.println(comparable.compare(3,5));
}
1.2.6、语法格式6:
- Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
@Test
public void twoParam(){
Comparatorcomparable = (x, y)-> Integer.compare(x,y);
System.out.println(comparable.compare(3,5));
}
1.3、函数式接口:
- 接口中只有一个抽象方法的接口,称为函数式接口。可以使用注解@FunctionalInterface修饰,可以检查是否是函数式接口
- eg、
@FunctionalInterface
public interface Comparator{
int compare(T o1, T o2);
}
2、Java内置的四大核心函数式接口
2.1、消费型接口:
- Consumer
: void accept(T t);
2.2、供给型接口:
- Supplier
: T get();
2.3、函数型接口
- Function
: R apply(T t);
2.4、断言型接口
- Predicate
: boolean test(T t);
3、方法引用
- 若 Lambda 体中的功能,已经有方法提供了实现,可以使用方法引用(可以将方法引用理解为 Lambda 表达式的另外一种表现形式)
- 该方法的参数列表和返回值必须与Lambda保持一致。
3.1、语法格式一:对象::实例方法名
@Test
public void quote(){
// Consumer consumer = x-> System.out.println(x) ;
Consumer consumer = System.out::println;
consumer.accept("方法引用实例");
}
3.2、语法格式二:类::静态方法
public void quote(){
// Comparator com = (x,y) -> Integer.compare(x,y);
Comparator com = Integer::compare;
System.out.println(com.compare(5,2));
}
3.3、语法格式三:类::实例方法
// 规则:第一个参数是方法的调用者,第二个参数是方法的参数时,才可以这样使用
public void quote(){
// BiPredicate biPredicate = (x,y)-> x.equals(y);
BiPredicate bi2 = String::equals;
System.out.println(bi2.test("a","b"));
}
4、构造器引用
- 构造器的参数列表和接口中抽象方法的构造器一致
@Test public void quote(){ // Supplier supplier = ()->new Employee(); Supplier supplier = Employee::new; System.out.println(supplier.get()); }
5、Stream API
5.1、基础概念
- 对数据源(集合、数组等)进行一系列的流水线式的中间操作,产生一个新流。原来的数据源不会发生改变,会产生一个新流。
5.2、Stream的操作三步骤
5.2.1、创建Stream:
-
一个数据源(如集合、数组),获取一个流
//1、可以通过Collection系列集合提供的stream()或parallelStream()
Listlist = new ArrayList<>();
Streamstream01 = list.stream(); //2、通过Arrays中的静态方法stream()获取数组流
Employee[] emps = new Employee[10];
Streamstream02 = Arrays.stream(emps); //3、通过Stream类中的静态方法of()
Streamstream03 = Stream.of("aa","bb","cc"); //4、创建无限流:迭代
Stream.iterate(0,(x)->x+2).forEach(System.out::println); ;//5、创建无限流:生成
Stream.generate(()->Math.random()).limit(5).forEach(System.out::println);
5.2.2、中间操作:
- 一个中间操作链,对数据源的数据进行处理——中间操作不会有任何结果,只有当执行终止操作后所有的中间操作才会一次性执行全部,即为惰性求值、延迟加载
- filter: 接受Lambda,从流中排出某些元素
public void stream(){
emps.stream()
.filter(o->o.getAge()>35)
.forEach(System.out::println);
} - limit:截断流,使其元素不超过给定的数量,会短路:迭代找到数据后,不再继续执行。
@Test
public void stream(){
emps.stream()
.filter(o->o.getAge()>35)
.limit(1)
.forEach(System.out::println);
} - skip:跳过元素:返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
@Test
public void stream(){
emps.stream()
.filter(o->o.getAge()>35)
.skip(1)
.forEach(System.out::println);
} - distinct:筛选:通过流所生成元素的hashcode()和equals()去除重复元素
@Test
public void stream(){
emps.stream()
.distinct()
.forEach(System.out::println);
} - map:映射 接收lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并且将其映射成一个新的元素。
@Test
public void stream(){
emps.stream()
.map(
o->o.getName().toUpperCase()
)
.forEach(System.out::println);
} - sort:排序
- 自然排序(Comparable) compareTo方法
@Test
public void sortByComparable(){
ListstrList = Arrays.asList("gas","asd","fsfd");
strList.stream().sorted().forEach(System.out::println);
} - 定制排序(Comparator)
@Test public void sortByComparator(){ emps.stream() .sorted( (e1,e2)->{ if(e1.getAge()!= e2.getAge()){ return Integer.compare(e1.getAge(),e2.getAge()); }else{ return e1.getName().compareTo(e2.getName()) ; } } ) .forEach(System.out::println); }
- 自然排序(Comparable) compareTo方法
- filter: 接受Lambda,从流中排出某些元素
5.2.3、终止操作:
- 一个终止操作,执行中间操作链,并产生结果。
5.2.3.1、查询与匹配
- allMatch:检查是否匹配所有元素
@Test
public void stream(){
boolean b = emps.stream()
.allMatch(e->e.getStatus().equals(Employee.Status.BUZY));
System.out.println(b);
} - anyMatch:检查是否至少匹配一个元素
//anyMatch:检查是否匹配至少一个元素
boolean b2 = emps.stream().anyMatch(e->e.getStatus().equals(Employee.Status.BUZY));
System.out.println(b2); - noneMatch:检查是否没有匹配一个元素
boolean b3 = emps.stream().noneMatch(
e->e.getStatus().equals(Employee.Status.BUZY));
System.out.println(b3); - findFirst:返回第一个元素
Optionalop = emps.stream().sorted((e1,e2)->
Double.compare(e1.getSalary(),e2.getSalary())).findFirst();
System.out.println(op.get()); - findAny:返回任意一个
Optionalop2 = emps.stream()
.filter( e->e.getStatus().equals(Employee.Status.FREE) )
.findAny();
System.out.println(op2.get()); - count:返回流中元素的总个数
Long count = emps.stream()
.count();
System.out.println(count); - max:返回流中元素最大值
Optionalmax = emps.stream()
.max((e1,e2)->
Integer.compare(e1.getAge(),e2.getAge())
);
System.out.println(max.get()); - min:返回流中元素最大值
Optionalmin = emps.stream()
.min((e1,e2)->
Integer.compare(e1.getAge(),e2.getAge())
);
System.out.println(min.get()); - min:查询工资最小值
OptionalminValue = emps.stream()
.map(Employee::getSalary)
.min(Double::compare);
System.out.println(minValue.get());
5.2.3.2、规约与收集
- 规约:reduce(T identity, BinaryOperator) / reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。
- 两个参数规约
Listlist = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(sum); - 一个参数规约
Optionalop = emps.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(op.get());
注意点: 当返回值确定是不为null时,返回原类型,若返回值有可能为null,则返回Optional
- 两个参数规约
- 收集:
转化成集合、数组等
//List
Listlist = emps.stream()
.map(Employee::getName)
.collect(Collectors.toList());
//Set
Setset = emps.stream()
.map(Employee::getName)
.collect(Collectors.toSet());
//HashSet
HashSeths = emps.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(HashSet::new));-
数学运算
//最大值
Optionalmax = emps.stream()
.map(Employee::getSalary)
.collect(Collectors.maxBy(Double::compare));//最小值 Optional
op = emps.stream() .collect(Collectors.minBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))); //总数 Optional sum = emps.stream() .map(Employee::getSalary) .collect(Collectors.reducing(Double::sum)); //总数 Double sum = emps.stream() .collect(Collectors.summingDouble(Employee::getSalary)); //平均值 Double avg = emps.stream() .collect(Collectors.averagingDouble(Employee::getSalary)); //数量 Long count = emps.stream() .collect(Collectors.counting()); //另一种方式求最大值、最小值等 DoubleSummaryStatistics dss = emps.stream() .collect(Collectors.summarizingDouble(Employee::getSalary)); //获取最大值 System.out.println(dss.getMax()); //平均值 System.out.println(dss.getAverage()); //数量 System.out.println(dss.getCount()); //最小值 System.out.println(dss.getMin()); //总数 System.out.println(dss.getSum()); -
分组:
- 单级分组
Map> map = emps.stream()
.collect(Collectors.groupingBy(Employee::getStatus)); - 多级分组
Map>> map = emps.stream()
.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((e) -> {
if(e.getAge() >= 60)
{ return "老年";}
else if(e.getAge() >= 35)
{ return "中年";}
else
{ return "成年";}
})));
- 单级分组
分区:
Map> map = emps.stream()
.collect(Collectors.partitioningBy((e) -> e.getSalary() >= 5000));-
带分隔符
String str = emps.stream()
.map(Employee::getName)
.collect(Collectors.joining("," , "----", "----"));输出结果: ----李四,张三,王五,赵六,赵六,赵六,田七----
5.3、流式操作练习
现有以下数据
public class Exer04 {
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");
List transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
}
-
找出2011年发生的所有交易, 并按交易额排序(从低到高)
@Test public void exer01(){ transactions.stream() .filter(t->t.getYear()==2011) .sorted(Comparator.comparingInt(Transaction::getValue)) .forEach(System.out::println);
}
-
交易员都在哪些不同的城市工作过?
@Test
public void exer02(){
transactions.stream()
.map(t->t.getTrader().getCity())
.distinct()
.forEach(System.out::println);
} -
查找所有来自Cambridge的交易员,并按姓名排序
@Test
public void exer03(){
transactions.stream()
.filter(t-> "Cambridge".equals(t.getTrader().getCity()))
.sorted((t1,t2)->t1.getTrader().getName().compareTo(t2.getTrader().getName()))
.forEach(System.out::println);
} -
返回所有交易员的姓名字符串,按字母顺序排序
@Test
public void exer04(){
transactions.stream()
.map(t->t.getTrader().getName())
.sorted(String::compareTo)
.forEach(System.out::println);
} -
有没有交易员是在米兰工作的?
@Test
public void exer05(){
boolean bl = transactions.stream()
.anyMatch((t) -> t.getTrader().getCity().equals("Milan"));
System.out.println(bl);
} -
打印生活在Cambridge的交易员的所有交易额
@Test
public void exer06(){
Optionalsum = transactions.stream()
.filter((t) -> t.getTrader().getCity().equals("Cambridge"))
.map(Transaction::getValue)
.reduce(Integer::sum);
System.out.println(sum.get());
} -
所有交易中,最高的交易额是多少
@Test
public void exer07() {
Optional o = transactions.stream()
.map(Transaction::getValue)
.max(Integer::compare);
System.out.println(o.get());
} -
找到交易额最小的交易
@Test
public void exer08() {
Optionalt = transactions.stream()
.min(Comparator.comparingInt(Transaction::getValue));
System.out.println(t.get());
}
6、并行流
6.1、Fork/Join 框架:
6.1.1、基础概念:
- 就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
6.1.2、工作窃取模式:
- 当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
6.1.3、优势
- 相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态.而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能。
6.1.4、自己实现Fork/Join 框架
public class ForkJoinCalculate extends RecursiveTask{
private static final long serialVersionUID = 13475679780L;
private long start;
private long end;
private static final long THRESHOLD = 10000L; //临界值
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{
long middle = (start + end) / 2;
ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
left.fork(); //拆分,并将该子任务压入线程队列
ForkJoinCalculate right = new ForkJoinCalculate(middle+1, end);
right.fork();
return left.join() + right.join();
}
}
}
7、Optional容器类
- Optional
类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
7.1、Optional.of(T t) :
- 创建一个 Optional 实例
//当传入null时,会报空指针异常,这样就可以快速锁定空指针异常的位置
Optionalop = Optional.of(null);
System.out.println(op.get());
7.2、Optional.empty():
- 创建一个空的 Optional 实例
Optionalop = Optional.empty();
//该对象调用get方法时会报空指针异常
System.out.println(op.get());
7.3、Optional.ofNullable(T t):
- 若 t 不为 null,创建 Optional 实例,否则创建空实例
//此处不报错
Optionalop = Optional.ofNullable(null);
//该对象调用get方法时会报空指针异常
System.out.println(op.get());
7.4、isPresent():
- 判断是否包含值
Optionalop = Optional.ofNullable(new Employee());
//有值才会打印
op.ifPresent(System.out::println);
7.5、orElse(T t):
- 如果调用对象包含值,返回该值,否则返回t
Optionalop = Optional.ofNullable(null);
//有值才会打印
System.out.println(op.orElse(Employee.builder().name("默认").build()));
7.6、orElseGet(Supplier s):
- 如果调用对象包含值,返回该值,否则返回 s 获取的值
Optionalop = Optional.ofNullable(null);
System.out.println(op.orElseGet(()->Employee.builder().name("默认").build()));
7.7、map(Function f):
- 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
Optionalop = Optional.ofNullable(Employee.builder().name("默认").build());
Optionalstr = op.map(Employee::getName);
str.ifPresent(System.out::println);
7.8、flatMap(Function mapper):
- 与 map 类似,要求返回值必须是Optional
Optionalop = Optional.ofNullable(Employee.builder().name("默认").build());
Optionalstr = op.flatMap(e->Optional.of(e.getName()));
str.ifPresent(System.out::println);
8、接口中的默认方法与静态方法
-
接口中允许存在默认方法
public interface MyInterface {default String getName(){ return "呵呵呵"; } }
-
接口中允许存在静态方法
public interface MyInterface {public static void show(){ System.out.println("接口中的静态方法"); } }
类优先原则
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。接口冲突
如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法
是否是默认方法),那么必须覆盖该方法来解决冲突。
9、全新的时间、日期API
- LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。线程安全!!!
9.1、 LocalDate、LocalTime、LocalDateTime
now()
LocalDateTime now = LocalDateTime.now();of()
LocalDateTime of = LocalDateTime.of(2021,7,6,0,16,04);plusYears():加减操作会生成新对象,原对象不会变化
LocalDateTime plusyears = localDateTime.plusYears(2);
9.2、时间戳:Instant
- (以Unix元年:1970年1月1日00:00:00到某个时间之间的毫秒值)默认获取UTC时间(格林威治时间)
Instant ins1 = Instant.now();
OffsetDateTime offsetDateTime = ins1.atOffset(ZoneOffset.ofHours(8));
//带偏移量输出
System.out.println(offsetDateTime);
//毫秒输出
System.out.println(ins1.toEpochMilli());
9.3、间隔:Duration、Period
Instant ins1 = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant ins2 = Instant.now();
Duration duration = Duration.between(ins1, ins2);
System.out.println(duration.getSeconds());
System.out.println(duration.toMillis());
LocalTime localTime1 = LocalTime.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LocalTime localTime2 = LocalTime.now();
System.out.println(Duration.between(localTime1,localTime2).toMillis());
LocalDate localDate1 = LocalDate.of(2010,02,02);
LocalDate localDate2 = LocalDate.of(2020,02,02);
System.out.println(Period.between(localDate1,localDate2).getDays());
9.4、时间校正器
TemporalAdjuster:时间校正器。
TemporalAdjusters : 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现。
使用静态方法校正时间
LocalDateTime ldt = LocalDateTime.now();
//利用现成的静态方法:下一个周五
System.out.println(ldt.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)));自定义校正器
//自定义:下一个工作日
ldt.with(
l->{
LocalDateTime ldt2 = (LocalDateTime) l;
DayOfWeek dow = ldt2.getDayOfWeek();
if(dow.equals(DayOfWeek.FRIDAY)){
return ldt2.plusDays(3);
}else if(dow.equals(DayOfWeek.SATURDAY)){
return ldt2.plusDays(2);
}else{
return ldt2.plusDays(1);
}
}
);
9.5、时间格式化
日期转字符串
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String s = dtf.format(LocalDateTime.now()) ;字符串转日期
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
LocalDateTime newDate = LocalDateTime.parse("2020年1月23日 02:02:09",dtf);
9.6、时区
获取全部时区
Setset = ZoneId.getAvailableZoneIds();
set.forEach(System.out::println);指定时区生成时间
LocalDateTime dt = LocalDateTime.now(ZoneId.of("Europe/Tallinn"));
System.out.println(dt);带时区格式的时间
ZonedDateTime zdt = dt.atZone(ZoneId.of("Europe/Tallinn"));
System.out.println(zdt);