怒学Java8系列一:Lambda表达式

PDF文档已上传Github git

Github:https://github.com/zwjlpeng/Angrily_Learn_Java_8github

第一章 Lambda

1.1 引言

课本上说编程有两种模式,面向过程的编程以及面向对象的编程,其实在面向对象编程以前还出现了面向函数的编程(函数式编程,之前一直被忽略、不被重视,如今从学术界已经走向了商业界,对函数编程语言的支持目前有ScalaErlangF#PythonPhpJavaJavascript等,有人说他将会是编程语言中的下一个主流...express

1.2 Lambda表达式

为何须要Lambda表达式?编程

1.使用Lambda表达式可使代码变的更加紧凑,例如在Java中实现一个线程,只输出一个字符串Hello World!,咱们的代码以下所示:设计模式

public static void main(String[] args) throws Exception {
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello World!");
        }
    }).start();
    TimeUnit.SECONDS.sleep(1000);
}

使用Lambda表达式以后代码变成以下形式:数组

public static void main(String[] args) throws Exception {
    new Thread(() -> System.out.println("Hello World!")).start();
    TimeUnit.SECONDS.sleep(1000);
}

是否是代码变的更紧凑了~,其余的例如各类监听器,以及事件处理器等均可以用这种方式进行简化。多线程

2.修改方法的能力,其实说白了,就是函数中能够接受以函数为单元的参数,在C/C++中就是函数指针,在Java中就是Lambda表达式,例如在Java中使用集合类对一个字符串按字典序列进行排序,代码以下所示:编程语言

public static void main(String[] args) {
    String []datas = new String[] {"peng","zhao","li"};
    Arrays.sort(datas);
    Stream.of(datas).forEach(param ->     System.out.println(param));
}

在上面代码中用了Arrays里的sort方法,如今咱们不须要按字典排序,而是按字符串的长度进行排序,代码以下所示:ide

public static void main(String[] args) {
    String []datas = new String[] {"peng","zhao","li"};
    Arrays.sort(datas,(v1 , v2) -> Integer.compare(v1.length(), v2.length()));
    Stream.of(datas).forEach(param -> System.out.println(param));
}

是否是很方便,咱们不须要实现Comparable接口,使用一个Lambda表达式就能够改变一个函数的形为~函数式编程

1.3 Syntax

1.Lambda表达式的形式化表示以下所示

Parameters -> an expression 

2.若是Lambda表达式中要执行多个语句块,须要将多个语句块以{}进行包装,若是有返回值,须要显示指定return语句,以下所示:

Parameters -> {expressions;};

3.若是Lambda表达式不须要参数,可使用一个空括号表示,以下示例所示

() -> {for (int i = 0; i < 1000; i++) doSomething();};

4.Java是一个强类型的语言,所以参数必需要有类型,若是编译器可以推测出Lambda表达式的参数类型,则不须要咱们显示的进行指定,以下所示,在Java中推测Lambda表达式的参数类型与推测泛型类型的方法基本相似,至于Java是如何处理泛型的,此处略去

String []datas = new String[] {"peng","zhao","li"};
Arrays.sort(datas,(String v1, String v2) -> Integer.compare(v1.length(), v2.length()));

上述代码中 显示指定了参数类型Stirng,其实不指定,以下代码所示,也是能够的,由于编译器会根据Lambda表达式对应的函数式接口Comparator<String>进行自动推断

String []datas = new String[] {"peng","zhao","li"};;
Arrays.sort(datas,(v1, v2) -> Integer.compare(v1.length(), v2.length()));

5.若是Lambda表达式只有一个参数,而且参数的类型是能够由编译器推断出来的,则能够以下所示使用Lambda表达式,便可以省略参数的类型及括号

Stream.of(datas).forEach(param -> {System.out.println(param.length());});

6.Lambda表达式的返回类型,无需指定,编译器会自行推断,说是自行推断

7.Lambda表达式的参数可使用修饰符及注解,如final@NonNull等 

1.4函数式接口

函数式接口是Java 8为支持Lambda表达式新发明的,在上面讲述的Lambda Syntax时提到的sort排序方法就是一个样例,在这个排序方法中就使用了一个函数式接口,函数的原型声明以下所示

public static <T> void sort(T[] a, Comparator<? super T> c)

上面代码中Comparator<? Super T>就是一个函数式接口,? Super T or ? entends TJava 5支持泛型时开始引入,得理解清楚,在此忽略讲述

什么是函数式接口

1.函数式接口具备两个主要特征,是一个接口,这个接口具备惟一的一个抽像方法,咱们将知足这两个特性的接口称为函数式接口,说到这,就不得不说一下接口中是有具体实现这个问题啦~

2.Lambda表达式不能脱离目标类型存在,这个目录类型就是函数式接口,所下所示是一个样例

String []datas = new String[] {"peng","zhao","li"};
Comparator<String> comp = (v1,v2) -> Integer.compare(v1.length(), v2.length());
Arrays.sort(datas,comp);
Stream.of(datas).forEach(param -> {System.out.println(param);}); 

Lambda表达式被赋值给了comp函数接口变量

3.函数式接口可使用@FunctionalInterface进行标注,使用这个标注后,主要有两个优点,编译器知道这是一个函数式接口,符合函数式的要求,另外一个就是生成Java Doc时会进行显式标注

4.异常,若是Lambda表达式会抛出非运行时异常,则函数式接口也须要抛出异常,说白了,仍是一句话,函数式接口是Lambda表达式的目标类型

5.函数式接口中能够定义public static方法,想一想在Java中咱们提供了Collection接口,同时还提供了一个Collections工具类等等,在Java中将这种Collections的实现转移到了接口里面,可是为了保证向后兼容性,之前的这种Collection/Collections等逻辑均未改变

6.函数式接口能够提供多个抽像方法,纳尼!上面不是说只能有一个嘛?是的,在函数式接口中能够提供多个抽像方法,但这些抽像方法限制了范围,只能是Object类型里的已有方法,为何要这样作呢?此处忽略,你们能够自已研究

7.函数式接口里面能够定义方法的默认实现,以下所示是Predicate类的代码,不只能够提供一个default实现,并且能够提供多个default实现呢,Java 8之前能够嘛?我和个人小伙伴们都惊呆了,这也就致使出现了多继承下的问题,想知道Java 8是如何对其进行处理的嘛,其实很Easy,后面我会再讲~

8.为何要提供default接口的实现?以下就是一个默认实现

default Predicate<T> or(Predicate<? super T> other) {
     Objects.requireNonNull(other);
     return (t) -> test(t) || other.test(t);
}

Java 8中在接口中增长了默认实现这种函数,其实在很大程序上违背了接口具备抽象这种特征的,增长default实现主要缘由是由于考虑兼容及代码的更改为本,例如,在Java 8中向iterator这种接口增长一个方法,那么实现这个接口的全部类都要需实现一遍这个方法,那么Java 8须要更改的类就太多的,所以在Iterator接口里增长一个default实现,那么实现这个接口的全部类就都具备了这种实现,说白了,就是一个模板设计模式吧

1.5方法引用 

有时,咱们须要执行的代码在某些类中已经存在,这时咱们不必再去写Lambda表达式,能够直接使用该方法,这种状况咱们称之为方法引用,以下所示,未采用方法引用前的代码

以下所示

Stream.of(datas).forEach(param -> {System.out.println(param);});

使用方法引用后的代码以下所示

Stream.of(datas).forEach(System.out::println);

以上示例使用的是out对象,下面示例使用的是类的静态方法引用对字符串数组里的元素忽略大小写进行排序

String []datas = new String[] {"peng","Zhao","li"};
Arrays.sort(datas,String::compareToIgnoreCase);
Stream.of(datas).forEach(System.out::println);

上面就是方法引用的一些典型示例

方法引用的具体分类

Object:instanceMethod
Class:staticMethod
Class:instanceMethod

上面分类中前两种在Lambda表达式的意义上等同,都是将参数传递给方法,如上示例

System.out::println == x -> System.out.println(x)

最后一种分类,第一个参数是方法执行的目标,以下示例 

String::compareToIgnoreCase == (x,y) ->     x.compareToIgnoreCase(y)

还有相似于super::instanceMethod这种方法引用本质上与Object::instanceMethod相似

1.6构造方法引用

构造方法引用与方法引用相似,除了一点,就是构造方法引用的方法是new!如下是两个示例

示例一:

String str = "test";
Stream.of(str).map(String::new).peek(System.out::println).findFirst();

示例二:

String []copyDatas = Stream.of(datas).toArray(String[]::new);
Stream.of(copyDatas).forEach(x -> System.out.println(x));

总结一下,构造方法引用有两种形式

Class::new
Class[]::new

1.7 Lambda表达式做用域

整体来讲,Lambda表达式的变量做用域与内部类很是类似,只是条件相对来讲,放宽了些之前内部类要想引用外部类的变量,必须像下面这样

final String[] datas = new String[] { "peng", "Zhao", "li" };
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(datas);
    }
}).start();

将变量声明为final类型的,如今在Java 8中能够这样写代码

String []datas = new String[] {"peng","Zhao","li"};
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(datas);
    }
}).start();

也能够这样写

new Thread(() -> System.out.println(datas)).start();

总之你爱怎么写,就怎么写吧,I don’t Care it!

看了上面的两段代码,可以发现一个显著的不一样,就是Java 8中内部类或者Lambda表达式对外部类变量的引用条件放松了,不要求强制的加上final关键字了,可是Java 8中要求这个变量是effectively final

What is effectively final?

Effectively final就是有效只读变量,意思是这个变量能够不加final关键字,可是这个变量必须是只读变量,即一旦定义后,在后面就不能再随意修改,以下代码会编译出错

String []datas = new String[] {"peng","Zhao","li"};
datas = null;
new Thread(() -> System.out.println(datas)).start();

Java中内部类以及Lambda表达式中也不容许修改外部类中的变量,这是为了不多线程状况下的race condition

Lambda中变量以及this关键字

Lambda中定义的变量与外部类中的变量做用域相同,即外部类中定义了,Lambda就不能再重复定义了,同时在Lambda表达式使用的this关键字,指向的是外部类,你们能够自行实践下,此处略

未完待写....

相关文章
相关标签/搜索