Java 8 引入的一个核心概念是函数式接口(Functional Interfaces)。经过在接口里面添加一个抽象方法,这些方法能够直接从接口中运行。若是一个接口定义个惟一一个抽象方法,那么这个接口就成为函数式接口。同时,引入了一个新的注解:@FunctionalInterface。能够把他它放在一个接口前,表示这个接口是一个函数式接口。这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。在接口中添加了 @FunctionalInterface 的接口,只容许有一个抽象方法,不然编译器也会报错,能够拥有若干个默认方法。html
java.lang.Runnable 就是一个函数式接口。 @FunctionalInterface public interface Runnable { public abstract void run(); }
Java中的lambda没法单独出现,它须要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现。java
lambda语法包含三个部分:编程
1)一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数api
2)一个箭头符号: ->数组
3)方法体,能够是表达式和代码块,方法体函数式接口里面的方法实现,若是是代码块要用{ }包起来,而且须要 return 返回值,若是方法返回值是 void 则不须要 { }安全
/** * @description: lambda表达式 * @author: liuxin * @create: 2019-01-26 18:39 **/ public class Test { private static void runNew(){ /* * Runnable是一个函数接口,只包含一个无参数返回void的run方法 * 因此lambda表达式没有参数也没有return * */ new Thread(()-> System.out.println("lambda方法")).start(); } private static void runOld(){ new Thread(new Runnable() { @Override public void run() { System.out.println("内部类实现方法"); } }).start(); } public static void main(String[] args) { Test.runNew(); Test.runOld(); } }
使用lambda表达式可使代码更加简洁的同时保持可读性。数据结构
方法引用是lambda表达式的一个简化写法,引用的方法实际上是表达式的方法体实现,语法很是简单,左边是容器(类名,实例名)中间是“” :: “”,右边则是相应的方法名。多线程
ObjectReference::methodName
引用格式:
1)静态方法:ClassName::methodName。例如 Object::equals
2)实例方法:Instance::menthodName。例如 Object obj =new Object();obj::equals
3)构造函数:ClassName::new
/** * @description: 方法引用 * @author: liuxin * @create: 2019-01-26 18:58 **/ public class TestMethod { public static void main(String[] args) { ArrayList<Integer> a =new ArrayList<>(); a.add(1); a.add(2); //这里addActionListener方法的参数是ActionListener,是一个函数式接口 //使用lambda表达式方式 a.forEach(e -> { System.out.println("Lambda实现方式"); }); //使用方法引用方式 a.forEach(TestMethod::sys); } public static void sys(Integer e) { System.out.println("方法引用实现方式"); } }
这里的sys()方法就是lambda表达式的实现,这样的好处是,当方法体过长影响代码可读性时,可使用方法引用。框架
默认方法:ide
Java 8 还容许咱们给接口添加一个非抽象的方法实现,就是接口能够有实现方法,并且不须要实现类去实现其方法,只须要使用 default 关键字便可,这个特征又叫作扩展方法。在实现该接口时,该默认扩展方法在子类上能够直接使用,它的使用方式相似于抽象类中非抽象成员方法。为何要有这个特性?首先,以前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当须要修改接口时候,须要修改所有实现该接口的类,目前的java 8以前的集合框架没有foreach方法,一般能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是无法在给接口添加新方法的同时不影响已有的实现。因此引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。但扩展方法不可以重载 Object 中的方法。例如:toString、equals、 hashCode 不能在接口中被重载。
public interface TestA { default void testA(){ System.out.println("这是默认方法!!!"); } } /** * @description: 默认方法 * @author: liuxin * @create: 2019-01-27 10:30 **/ public class TestDefult implements TestA{ public static void main(String[] args) { TestDefult testDefult =new TestDefult(); //调用testA()方法 testDefult.testA(); } }
静态方法:在接口中,还容许定义静态的方法。接口中的静态方法能够直接用接口来调用。
public interface TestA { static void TestB(){ System.out.println("这是静态方法!!!"); } } public class TestDefult { public static void main(String[] args) { TestA.TestB(); } }
这一个功能特性出来后,不少同窗都反应了,java 8的接口都有实现方法了,跟抽象类还有什么区别?其实仍是有的,请看下表对比。。
public interface TestA { default void testA(){ System.out.println("这是默认方法!!!"); } } public interface TestB extends TestA{ default void testA(){ System.out.println("这是默认方法B!!!"); } } public class TestDefult implements TestB,TestA{ public static void main(String[] args) { TestDefult testDefult =new TestDefult(); testDefult.testA(); } } 输出结果为:这是默认方法B!!!
若是想调用testA的默认函数,则要用X.super.m(。。。。。)
public class TestDefult implements TestA{ @Override public void testA(){ TestA.super.testA(); } public static void main(String[] args) { TestDefult testDefult =new TestDefult(); testDefult.testA(); } }
默认方法给予咱们修改接口而不破坏原来的实现类的结构提供了便利,目前java 8的集合框架已经大量使用了默认方法来改进了,当咱们最终开始使用Java 8的lambdas表达式时,提供给咱们一个平滑的过渡体验。
JSR是Java Specification Requests的缩写,意思是Java 规范请求,Java 8 版本的主要改进是 Lambda 项目(JSR 335),其目的是使 Java 更易于为多核处理器编写代码。JSR 335=lambda表达式+接口改进(默认方法)+批量数据操做。前面咱们已经是完整的学习了JSR335的相关内容了。
外部迭代就是咱们经常使用的for循环和while循环 public static void main(String[] args) { ArrayList<Integer> a =new ArrayList<>(); a.add(1); a.add(2); for (Integer i :a){ System.out.println(i); } }
在如今多核的时代,若是咱们想并行循环,不得不修改以上代码。效率能有多大提高还说定,且会带来必定的风险(线程安全问题等等)。
要描述内部迭代,咱们须要用到Lambda这样的类库,下面利用lambda和Collection.forEach重写上面的循环
public static void main(String[] args) { ArrayList<Integer> a =new ArrayList<>(); a.add(1); a.add(2);
a.forEach(i-> System.out.println(i)); }
如今是由jdk 库来控制循环了,库能够根据运行环境来决定怎么作,并行,乱序或者懒加载方式。这就是内部迭代
流(Stream)仅仅表明着数据流,并无数据结构,因此他遍历完一次以后便再也没法遍历(这点在编程时候须要注意,不像Collection,遍历多少次里面都还有数据),它的来源能够是Collection、array、io等等。
流做用是提供了一种操做大数据接口,让数据操做更容易和更快。它具备过滤、映射以及减小遍历数等方法,这些方法分两种:中间方法和终点方法,中间方法返回的是Stream,容许更多的链式操做,若是咱们要获取最终结果的话,必须使用终点操做才能收集流产生的最终结果。区分这两个方法是看他的返回值,若是是Stream则是中间方法,不然是终点方法。具体请参照Stream的api。
中间方法:
filter():对元素进行过滤;
sorted():对元素排序;
map():元素的映射;
distinct():去除重复元素;
subStream():获取子 Stream 等。
//过滤大于1的结果 public static void main(String[] args) { ArrayList<Integer> a =new ArrayList<>(); a.add(1); a.add(2); a.add(2); a.add(2); a.stream().filter(i->i>1).forEach(System.out::println); } 输出结果为2 2 2
//去重 public static void main(String[] args) { ArrayList<Integer> a =new ArrayList<>(); a.add(1); a.add(2); a.add(2); a.add(2); a.stream().distinct().forEach(System.out::println); } 输出结果为 1 2
终点方法:
forEach():对每一个元素作处理;
toArray():把元素导出到数组;
findFirst():返回第一个匹配的元素;
anyMatch():是否有匹配的元素等。
流有串行和并行两种,串行流上的操做是在一个线程中依次完成,而并行流则是在多个线程上同时执行。并行与串行的流能够相互切换:经过 stream.sequential() ( .sequential() 能够省略)返回串行的流,经过 stream.parallel() 返回并行的流。相比较串行的流,并行的流能够很大程度上提升程序的执行效率。
public static void main(String[] args) { //串行流计算一个范围100万整数流,求能被2整除的数字 int a[]= IntStream.range(0, 1_000_000).sequential() .filter(p -> p % 2==0).toArray(); long time1 = System.nanoTime(); //并行流来计算 int b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray(); long time2 = System.nanoTime(); System.out.printf("serial: %.2fs, parallel %.2fs%n", (time1 - time0) * 1e-9, (time2 - time1) * 1e-9); } 结果为 0.07s 和 0.02s,可见,并行排序的时间相比较串行排序时间要少不少。
若是没有lambda,Stream用起来至关别扭,他会产生大量的匿名内部类,若是没有default method,集合框架更改势必会引发大量的改动,因此lambda+default method使得jdk库更增强大,以及灵活,Stream以及集合框架的改进即是最好的证实。