目录
一、jdk8简介
Oracle甲骨文公司于2015年1月15日发布了新一版JDK8,新版本加入了许多新的特性。这些特性带来了一些改变,可以很大方便Java程序的编写。新特性主要涉及:对于JDK7中Fork/Join并行处理的升级;支持Lambda表达式;添加了Stream API;对于注解的拓展,加入了类型注解、重复注解;在G1回收器中支持字符串去重;内存空间中删除了永久代,引入了元空间。
二、Lambda表达式
java8中引入了一个新的操作符“->”,该操作符称为箭头操作符或Lambda操作符,箭头操作符将Lambda表达式拆成两部分:
- 左侧:Lambda表达式的参数列表
- 右侧:Lambda表达式中所需执行的功能,即Lambda体 使用Lambda必须有接口,并且接口中有且仅有一个抽象方法。
注意:
- 只有当接口中的抽象方法存在且唯一时,才可以使用Lambda,但排除接口默认方法以及声明中覆盖Object的公开方法。
- 使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
语法格式:
语法格式1:无参数,无返回值 ()->System.out.println(‘hello Lambda!’)
语法格式2:有一个参数,无返回值 (x)->System.out.println(x);
语法格式3:若只有一个参数,小括号可以省略不写 x->System.out.println(x);
语法格式4:有两个以上的参数,有返回值,并且Lambda体中有多条语句
语法格式5:若Lambda体只有一条语句,return 和大括号都可以省略不写
语法格式6:Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出数据类型,即类型推断
三、jdk8 内置四大核心函数接口
消费型接口
Consumer:消费型接口
void accept(T t);
至于具体怎么消费(使用),需要自定义
andThen:
Consumer接口的默认方法andThen
作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,在对数据进行消费
//消费型接口Consumer
@Test
public void test3() {
happy(300, m -> System.out.println("每次消费:" + m + "元"));
}
private void happy(double money, Consumer<Double> consumer) {
consumer.accept(money);
}
供给型接口
Supplier:供给型接口
T get();
Supplier接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据
//供给型接口Supplier
@Test
public void test4() {
List<Integer> list = randomInts(10, () -> (int) (Math.random() * 100));
for (Integer i : list) {
System.out.println(i);
}
}
private List<Integer> randomInts(int n, Supplier<Integer> supplier) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < n; i++) {
list.add(supplier.get());
}
return list;
}
函数型接口
Function:函数型接口 接口用来根据一个类型的数据得到另一个类型的数据,
前者称为前置条件,后者称为后置条件
R apply(T t); Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
@Test
public void test5() {
String s = "hello world! ";
String s1 = strHandle(s, (str) -> str.trim());
System.out.println("去空格:" + s1);
String s2 = strHandle(s, str -> str.substring(1));
System.out.println(s2);
}
public String strHandle(String s, Function<String, String> function) {
return function.apply(s);
}
断言型接口
Predicate接口
作用:对某种数据类型的数据进行判断,结果返回一个boolean值
Predicate接口中包含一个抽象方法:
boolean test(T t):用来对指定数据类型数据进行判断的方法
结果:
符合条件,返回true
不符合条件,返回false
// 断言型接口Predicate
@Test
public void test6() {
List<String> list = Arrays.asList("hello", "hehehe", "heiheihei", "haha", "wuwu", "enen", "yiyiyi");
// 集合中小于6的字符串
List<String> len_lt6 = filterStrs(list, (s) -> s.length() < 6);
for (String s : len_lt6) {
System.out.println(s);
}
}
public List<String> filterStrs(List<String> strs, Predicate<String> predicate) {
List<String> list = new ArrayList<>();
for (String str : strs) {
if (predicate.test(str)) {
list.add(str);
}
}
return list;
}
四、对象引用
方法引用
若Lambda体中的内容有方法已经实现了,我们可以使用“方法引用”(可以理解为方法引用是Lambda表达式的另一种表现形式)
语法格式:
对象::实例方法名
类::静态方法名
类::实例方法名
@Test
public void test1(){
Consumer c1 = (x)->System.out.println(x);
Consumer c2 = System.out::println;
PrintStream printStream = System.out;
Consumer c3 = printStream::println;
}
注意:
1、Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
2、若Lambda参数列表中的第一参数是 实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method
@Test
public void test2(){
BiPredicate<String,String> biPredicate = (x,y)->x.equals(y);
BiPredicate<String,String> biPredicate2 = String::equals;
}
构造引用
格式: ClassName::new
注意: 需要调用的构造器的参数列表要与函数式接口中的抽象方法的参数列表保持一致!
// 无构造参数的情况
@Test
public void test3(){
Supplier<Employee> supplier = ()->new Employee();
Supplier<Employee> supplier2 = Employee::new;
System.out.println(supplier.get());
System.out.println(supplier2.get());
}
// 一个构造参数的情况,类型推断
@Test
public void test4(){
Function<String,Employee> function = (name)->new Employee(name);
Function<String,Employee> function2 = Employee::new;
System.out.println(function.apply("zhangsan"));
System.out.println(function2.apply("zhangsan"));
}
数组引用
格式: Type[]::new
@Test
public void test5(){
Function<Integer,String[]> function = (n)->new String[n];
Function<Integer,String[]> function2 =String[]::new;
System.out.println(function.apply(10).length);
System.out.println(function2.apply(20).length);
}
五、StreamAPI
特性:
- Stream流不是一种数据结构,不保存数据,它只是在原数据集上定义了一组操作。
- 这些操作是惰性的,即每当访问到流中的一个元素,才会在此元素上执行这一系列操作。
- Stream不保存数据,故每个Stream流只能使用一次。
生成方法
- Collection接口的stream()或parallelStream()方法
- 静态的Stream.of()、Stream.empty()方法
- Arrays.stream(array, from, to)
- 静态的Stream.generate()方法生成无限流,接受一个不包含引元的函数
- 静态的Stream.iterate()方法生成无限流,接受一个种子值以及一个迭代函数
- Pattern接口的splitAsStream(input)方法
- 静态的Files.lines(path)、Files.lines(path, charSet)方法
- 静态的Stream.concat()方法将两个流连接起来
流的Intermediate方法(中间操作)
- filter(Predicate)
将结果为false的元素过滤掉 - map(fun)
转换元素的值,可以用方法引元或者lambda表达式 - flatMap(fun)
若元素是流,将流摊平为正常元素,再进行元素转换 - limit(n)
保留前n个元素 - skip(n)
跳过前n个元素 - distinct()
剔除重复元素 - sorted()
将Comparable元素的流排序 - sorted(Comparator)
将流元素按Comparator排序 - peek(fun)
流不变,但会把每个元素传入fun执行,可以用作调试
流的Terminal方法(终结操作)
约简操作
- max(Comparator)
- min(Comparator)
- count()
- findFirst()
返回第一个元素 - findAny()
返回任意元素 - anyMatch(Predicate)
任意元素匹配时返回true - allMatch(Predicate)
所有元素匹配时返回true - noneMatch(Predicate)
没有元素匹配时返回true - reduce(fun)
从流中计算某个值,接受一个二元函数作为累积器,从前两个元素开始持续应用它,累积器的中间结果作为第一个参数,流元素作为第二个参数 - reduce(a, fun)
a为幺元值,作为累积器的起点 - reduce(a, fun1, fun2)
与二元变形类似,并发操作中,当累积器的第一个参数与第二个参数都为流元素类型时,可以对各个中间结果也应用累积器进行合并,但是当累积器的第一个参数不是流元素类型而是类型T的时候,各个中间结果也为类型T,需要fun2来将各个中间结果进行合并
收集操作
- iterator()
- forEach(fun)
- forEachOrdered(fun)
可以应用在并行流上以保持元素顺序 - toArray()
- toArray(T[] :: new)
返回正确的元素类型 - collect(Collector)
- collect(fun1, fun2, fun3)
fun1转换流元素;fun2为累积器,将fun1的转换结果累积起来;fun3为组合器,将并行处理过程中累积器的各个结果组合起来
六、Optional容器类
Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在,原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。
Optional.of:创建一个容器实例
Optional.ofNullable:创建一个容器实例(推荐)
orElse:获取容器内部元素,没有元素返回默认值
orElseGet:获取容器内部元素,没有元素返回默认值(推荐)
map:如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty();
flatMap:与map类似,返回的对象必须是Optional
相关实体类:
public class Girl {
private String name;
@Override
public String toString() {
return "Girl{" +
"name='" + name + '\'' +
'}';
}
public Girl() {
}
public Girl(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Boy {
private Girl girl;
public Girl getGirl() {
return girl;
}
public void setGirl(Girl girl) {
this.girl = girl;
}
public Boy() {
}
public Boy(Girl girl) {
this.girl = girl;
}
}
public class BoyNew {
private Optional<Girl> girl = Optional.empty();
public Optional<Girl> getGirl() {
return girl;
}
public void setGirl(Optional<Girl> girl) {
this.girl = girl;
}
public BoyNew() {
}
public BoyNew(Optional<Girl> girl) {
this.girl = girl;
}
}
测试论证:
@Test
public void test3() {
Boy boy = new Boy();
System.out.println("女朋友是:" + getGirlName(boy));
Boy boy2 = new Boy(new Girl("玛利亚"));
System.out.println("女朋友是:" + getGirlName(boy2));
BoyNew boyNew = new BoyNew();
System.out.println("女朋友是:" + getGirlName2(boyNew));
BoyNew boyNew2 = new BoyNew(Optional.ofNullable(new Girl("松岛")));
System.out.println("女朋友是:" + getGirlName2(boyNew2));
}
public String getGirlName2(BoyNew boy) {
return Optional.ofNullable(boy)
.orElse(new BoyNew())
.getGirl()
.orElse(new Girl("波多"))
.getName();
}
public String getGirlName(Boy boy) {
if (boy != null) {
Girl girl = boy.getGirl();
if (girl != null) {
return girl.getName();
}
}
return "波多";
}
七、日期时间LocalDateTime
具体可参考:JDK8时间日期API
@Test
public void test4(){
LocalDate localDate = LocalDate.now();
System.out.println(localDate);
LocalTime localTime = LocalTime.now();
System.out.println(localTime);
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
System.out.println(localDateTime.getYear());
System.out.println(localDateTime.getMonthValue());
System.out.println(localDateTime.getDayOfMonth());
System.out.println(localDateTime.getHour());
System.out.println(localDateTime.getMinute());
System.out.println(localDateTime.getSecond());
}
@Test
public void test5(){
LocalDateTime localDateTime = LocalDateTime.now();
DateTimeFormatter sdf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(localDateTime));
}