Java8主要的改变是为集合框架增长了流的概念,提升了集合的抽象层次。相比于旧有框架直接操做数据的内部处理方式,流+高阶函数的外部处理方式对数据封装更好。同时流的概念使得对并发编程支持更强。java
在语法上Java8提供了Lambda表达式来传递方法体,简化了以前方法必须藏身在没必要要的类中的繁琐。Lambda表达式体现了函数式编程的思想,即一个函数亦能够做为另外一个函数参数和返回值,使用了函数做参数/返回值的函数被称为高阶函数。python
Java 被诟病为繁琐的地方就在于不支持传递方法,Java中的方法必须依赖类存在,也不能将方法做为参数或返回值,这是与python等语言相比的弱势。Java 8中使用新特性Lambda表达式来改善这一点。编程
以Runnable接口为例,若是须要执行一个线程,实际只须要run()方法中的代码块,但形式上必需要先制造一个Runnable接口实现类(一般是匿名内部类)。数组
使用Lambda表达式仅仅须要一行代码,达到传递run方法的效果,而没必要定义匿名内部类。数据结构
new Thread(()->System.out.println("Lambda")).start();
Lambda表达式之因此可以作如此简化得益于Java的类型参数推断机制。全部省略的内容均可以由编译器经过上下文推断出来。并发
类型推断机制在Java中的应用普遍,例如数组类型肯定,Java7引入的菱形操做符等等。app
类型参数推断机制要推断的是Lambda表达式的目标类型,每每须要与Java的重载解析机制配合。其解析规则是框架
只有一个可能目标类型时,由响应函数接口里的参数类型推导得出ide
有多个可能目标类型,选择最具体的类型函数式编程
有多个可能目标类型但没法明确最具体类型,则编译报错
一个方法能够抽象成函数接口。函数接口相似于一个黑箱,只须要关注其参数和返回值类型,函数接口中只有单方法。Runnable的函数接口以下:
能够看到这是一个空接口。能够用它表明全部参数和返回值都为空的方法。
Java8中定义若干函数接口(位于包java.util.function)。
接口 | 参数 | 返回类型 | 示例 |
---|---|---|---|
Pridicate<T> |
T | boolean | 条件判断 |
Consumer<T> |
T | void | 消费者 |
Function<T, R> |
T | R | 再加工 |
Supplier<T> |
void | T | 供应者 |
BinaryOperator<T> |
<T,T> |
T | 求和 |
UnaryOperator<T> |
T | T | 逻辑非 |
以Pridicate函数接口为例,这是一个泛型接口,参数能够是任意类型,返回值是boolean类型,表明根据数值做判断的一类方法。
从类型推断的角度看很容易以为Lambda表达式是和泛型,装箱等机制同样的语法糖,编译器在背后补全了省略信息,但实际上并不是如此。
class Apple{ public String toString() {return "apple";}; Runnable r1 = ()->{System.out.println(this);}; Runnable r2 = new Runnable() { public void run() { System.out.println(this); } }; } -------------------- //执行两个线程获得的结果是 apple Day0917.Apple$1@22e90474
正常的匿名内部类中 this关键字 指向内部类对象自身,同时将生成Apple$1.class文件。
Lambda表达式中this所指向的则是外部类对象,并不会生成内部类class文件,这说明Lambda表达式并非语法糖,它没有产生一个内部类,也没有引入一个新的做用域。
Lambda与内部类相同之处在于其内部所定义的变量均为final或既成事实上的final.
Java8最重要的改变就是对类库的改造,使得接口中方法能够拥有代码体。这种定义在接口中的包含方法体的方法,须要用default修饰,称之为默认方法。
interface Apple{ default void show(){ System.out.println("interface"); } } class MyApple implements Apple{ @Override public void show() { Apple.super.show(); } }
若是实现类中重写了默认方法,则接口中默认方法就被覆盖了。
若是两个接口定义了相同的默认方法,则实现类中能够经过指定全称来肯定使用哪一个父类的方法。
若是将匿名内部类改造为Lambda表达式是偷懒的话,那方法引用则是懒到连Lambda表达式都不想写了。
在以前,咱们知道Lambda表达式能够做为函数参数和返回值,表示传递一个方法。方法引用就是使用 ClassName::MethodName 的形式来指定方法。故而方法引用与Lambda表达式彻底同源同种,能够相互替代。
//1,创建一个字符串 String::new //2.创建一个字符串数组 String[]::new
注意 lambda表达式与方法引用表示的是方法自己,将要被用太高阶函数的参数/返回值,并不能单独使用。
任务:建立一个姓名集合,要求出全部初始字母为a
的人的总数目。使用流处理的代码以下:
ArrayList<String> person = new ArrayList<>(); ----init---- //1.由集合得到流对象 Stream<String> steam = person.stream(); //2.对流对象进行过滤和统计 steam.filter((s)->s.startsWith("a")) //1.流过滤 .count(); //2.计算流对象中元素数目
使用函数接口(形式上表现为Lambda表达式)做为参数和返回值的函数就是所谓的高阶函数,如此处的filter,其参数为函数接口Predicate,亦能够理解为一个接口为 T--->boolean 的方法。
上述示例中为流对象的高阶函数传入一个函数接口Predicate,避免了直接处理集合中的数据对象。示例展现了流使用的通用格式:
得到流对象Stream
对流对象Stream进行惰性求值,返回值仍然是一个Stream对象。
对流对象Stream进行及早求值,返回值不在是一个Stream对象。
collect方法属于一个及早求值方法,负责将流对象转换成其余数据结构,如列表,集合,值等。这项工做由收集器Collector完成。java8为此提供了Collectors工具类。
List<Person> list = stream.collect(Collectors.toList()); List<Person> arraylist = stream.collect(Collectors.toCollection(ArrayList::new)); Set<Person> set = stream.collect(Collectors.toSet()); Set<Person> treeSet = stream.collect(Collectors.toCollection(TreeSet::new));
使用Collectors.toList()将流对象转换成集合时并不须要指定具体类型,Java默认选择了实现类型,若是要本身指定,可使用Collectors.toCollection(ArrayList::new),其参数ArrayList::new
就是上文中的方法引用,表示一个创建ArrayList对象的方法,ArrayList就是想要转换成的数据类型;
//1.得到最大最小值 Function<Person, Integer> getLevel = p->p.age; Comparator<Person> comparator = Comparator.comparing(getLevel); stream.collect(Collectors.maxBy(comparator)); stream.collect(Collectors.minBy(comparator)); //2.得到平均值 ToIntFunction<Person> getAverage = p->p.age; stream.collect(Collectors.averagingInt(getAverage));
将流对象按某种条件分红两部分
Predicate<Person> isTang = p->p.country.equals(Country.Tang); stream.collect(Collectors.partitioningBy(isTang));
Function<Person, Integer> country= p -> p.country.ordinal(); stream.collect(Collectors.groupingBy(country));
分块和分组看似相同,但意义不一样,分块使用判断做为方法,只能将流分红两块;分组则灵活的多。
stream.map(Person::getName).collect(Collectors.joining("/", "[", "]"));
stream.collect(Collectors.groupingBy(country,Collectors.counting()));
map是一个惰性求值方法。函数接口为Function<T, R>
函数接口,负责将数据从一个类型转换为另外一个类型;高阶函数map的做用就是将数据从一个流转换为另外一个流。
filter 是一个惰性求值方法。函数接口为Pridicate<T>
,此方法负责对数据进行判断,filter高阶函数负责根据判断结果对流进行过滤。
flatMap 是一个惰性求值方法。其参数亦为Function<T, R>
,将多个流组合为一个流。
//1.a1,a2是两个列表,map处理后还是两个列表 Stream.of(a1,a2).map(s->s) ------------- [1, 2, 3, 4] [] //2.flatMap将两者合并为一个流 Stream.of(a1,a2).map(s->s) .flatMap(s->s.stream()) ------------- 1234
看源码可知,flatMap中函数接口Function的输出类型为Stream<R>
。
属于一个及早求值方法。须要传入一个Comparator函数接口,Java8提供了Comparator.comparing方法得到该函数接口的实现,该静态方法是接口的静态方法,得到一个函数返回一个Comparator对象。
min(Comparator.comparing(s->s.toString()));
max/min的返回值是 Optional,表明一个或有或无的值,主要是用来取代万恶的null值;使用get方法能够获取其值。
属于一个及早求值方法。意为流数据的累加,有两个版本。
//1.无初始值累加 T t = person.stream().reduce((a,b)->a+b); //2.带初始值累加 Optional<T> t = person.stream().reduce("1",(a,b)->a+b);
属于一个及早求值方法,用来遍历流对象。
总而言之,Java8中流对象的引入使得能够在更高的层次上对集合进行处理,使得抽象的方法和具体的行为逻辑分离开来,也增强了数据的封装性,另外一个好处是对并发的支持更强,之后再补充。