Java SE 8 for the Really Impatient读书笔记——Java 8 Lambda表达式

1. lambda表达式的语法

语法十分简单:参数->主体

1.1 参数

可以是零个参数,一个参数,也可以是多个参数,参数可以指定类型,在编译器可以推导出参数类型的情况下,也可以省略参数类型。

两个参数的例子:

(String first, String second)-> Integer.compare(first.length(), second.length())

0个参数的例子:

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

从jdk7开始,泛型可以简化写成如下形式:

Map<String, String> myMap = new HashMap<>(); 

编译器会根据变量声明时的泛型类型自动推断出实例化HashMap时的泛型类型。

同样的,如果编译器可以推导出Lambda表达式中参数的类型,则也可以省略,例如:

Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());

编译器可以推断出first和second的类型为String,此时,参数类型可省略。

在只有一个参数,且可推断出其类型的情况下,可以再将括号省略:

EventHandler<ActionEvent> listener = event ->System.out.println("Thanks for clicking!");

同方法参数一样,表达式参数也可以添加annotations或者final修饰:

(final String name) -> ...
(@NonNull String name) -> 

1.2 主体

主体一定要有返回值。

如果主体只有一句,则可以省略大括号:

Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());

多于一句的情况,需要用{}括上:

(String first, String second) -> {
   if (first.length() < second.length()) return -1;
   else if (first.length() > second.length()) return 1;
   else return 0;
}

主体必须有返回值,只在某些分支上有返回值也是不合法的,例如:

(int x) -> { if (x >= 0) return 1; }

这个例子是不合法的。

2. 函数式接口

只包含一个抽象方法的接口叫做函数式接口。

函数式接口可使用注解@FunctionalInterface标注(不强制,但是如果标注了,编译器就会检查它是否只包含一个抽象方法)

可以通过lambda表达式创建函数式接口的对象,这是lambda表达式在java中做的最重要的事情

在jdk8以前,其实已经存在着一些接口,符合上述函数式接口的定义。

2.1 JDK 8之前已有的函数式接口

java.lang.Runnable java.util.concurrent.Callable java.security.PrivilegedAction java.util.Comparator java.io.FileFilter java.nio.file.PathMatcher java.lang.reflect.InvocationHandler java.beans.PropertyChangeListener java.awt.event.ActionListener javax.swing.event.ChangeListener

在jdk8以前,这些接口的使用方式与其他接口并无不同。

通过两个例子来说明lambda表达式如何创建函数式接口实例

1.创建Runnable函数式接口实例,以启动线程——jdk8以前:

import java.util.*;

public class OldStyle {
    public static void main(String[] args) {
        // 启动一个线程
        Worker w = new Worker();
        new Thread(w).start();
        // 启动一个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();

    }
}

class Worker implements Runnable {
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

运行结果:

Thread-0
Thread-1

从代码角度来看,不管是通过内部类还是通过匿名内部类,启动线程需要编写的代码都较为繁琐,其中,由程序员自定义的仅仅是run方法中的这一句话:

System.out.println(Thread.currentThread().getName());

lambda表达式风格的启动线程:

// 启动一个线程
Runnable runner = () -> System.out.println(Thread.currentThread().getName());runner.run(); 
第一行实际上创建了一个函数式接口Runnable的实例runner,可以看出,lambda表达式的实体,恰好是run方法的方法体部分。

2.创建Comparator函数式接口实例,实现根据String的长度来排序一个String数组——jdk8以前:
import java.util.*;

public class OldStyle {
    public static void main(String[] args) {
        // 排序一个数组
        class LengthComparator implements Comparator<String> {
            public int compare(String first, String second) {
                return Integer.compare(first.length(), second.length());
            }
        }
        String[] strings = "Mary had a little lamb".split(" ");
        Arrays.sort(strings, new LengthComparator());
        System.out.println(Arrays.toString(strings));
    }
}

lambda表达式:

import java.util.*;

public class LambdaStyle {
    public static void main(String[] args) {
        // 排序一个数组
        String[] strings = "Mary had a little lamb".split(" ");
        Arrays.sort(strings, (first, second) -> Integer.compare(first.length(), second.length()));
        System.out.println(Arrays.toString(strings));
    }
}

可以看出,函数式接口通过lambda表达式创建实例,是如此的精简

java.util.function中定义了几组类型的函数式接口以及针对基本数据类型的子接口。

Predicate -- 传入一个参数,返回一个bool结果, 方法为boolean test(T t)
Consumer -- 传入一个参数,无返回值,纯消费。 方法为void accept(T t)
Function<t,r> -- 传入一个参数,返回一个结果,方法为R apply(T t)
Supplier -- 无参数传入,返回一个结果,方法为T get()
UnaryOperator -- 一元操作符, 继承Function<t,t>,传入参数的类型和返回类型相同。
BinaryOperator -- 二元操作符, 传入的两个参数的类型和返回类型相同, 继承BiFunction

3. 方法引用

方法引用增强了lambda表达式的可读性

方法引用例1:同样是排序一个数组的例子,这次是不区分大小写的排序一个数组:

import java.util.*;

public class LambdaStyle {
    public static void main(String[] args) {
        // 排序一个数组
        String[] strings = "Mary had a little lamb".split(" ");
        Arrays.sort(strings, (s1, s2) -> {
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1);
                    c2 = Character.toUpperCase(c2);
                    if (c1 != c2) {
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        });

        System.out.println(Arrays.toString(strings));
    }
}

上述例子,由于lambda表达式的主体代码较长,导致代码可读性下降,通过方法引用可以解决这个问题

方法引用例2:

import java.util.*;

public class LambdaStyle {
    public static void main(String[] args) {
        // 排序一个数组
        String[] strings = "Mary had a little lamb".split(" ");
        Arrays.sort(strings, LambdaStyle::myCompareToIgnoreCase);
        System.out.println(Arrays.toString(strings));
    }

    public static int myCompareToIgnoreCase(String s1, String s2){
        int n1 = s1.length();
        int n2 = s2.length();
        int min = Math.min(n1, n2);
        for (int i = 0; i < min; i++) {
            char c1 = s1.charAt(i);
            char c2 = s2.charAt(i);
            if (c1 != c2) {
                c1 = Character.toUpperCase(c1);
                c2 = Character.toUpperCase(c2);
                if (c1 != c2) {
                    c1 = Character.toLowerCase(c1);
                    c2 = Character.toLowerCase(c2);
                    if (c1 != c2) {
                        // No overflow because of numeric promotion
                        return c1 - c2;
                    }
                }
            }
        }
        return n1 - n2;
    }
}

将主体代码抽出来写到一个方法中,然后引用这个方法。

方法表达式的三种主要使用情况:

  1. 类::静态方法
  2. 对象::实例方法
  3. 类::实例方法

前两种很好理解,例2是第一种情况,第二种情况如下例所示

方法引用例3:

import java.util.*;

public class LambdaStyle {
    public static void main(String[] args) {
        // 排序一个数组
        String[] strings = "Mary had a little lamb".split(" ");
        LambdaStyle lambdaStyle = new LambdaStyle();
        Arrays.sort(strings, lambdaStyle::myCompareToIgnoreCase);
        System.out.println(Arrays.toString(strings));
    }

    public int myCompareToIgnoreCase(String s1, String s2){
        int n1 = s1.length();
        int n2 = s2.length();
        int min = Math.min(n1, n2);
        for (int i = 0; i < min; i++) {
            char c1 = s1.charAt(i);
            char c2 = s2.charAt(i);
            if (c1 != c2) {
                c1 = Character.toUpperCase(c1);
                c2 = Character.toUpperCase(c2);
                if (c1 != c2) {
                    c1 = Character.toLowerCase(c1);
                    c2 = Character.toLowerCase(c2);
                    if (c1 != c2) {
                        // No overflow because of numeric promotion
                        return c1 - c2;
                    }
                }
            }
        }
        return n1 - n2;
    }
}

在第三种情况中,第一个参数会成为执行方法的对象。

String类中实际上已经提供了不区分大小写比较字符串的方法:

public int compareToIgnoreCase(String str)

用法:

String s = "jdfjsjfjskd";
String ss = "dskfksdkf";
int i = s.compareToIgnoreCase(ss);System.out.println(i);

通过这个方法来实现不区分大小写的排序一个数组就是第三种情况

方法引用例4:

import java.util.*;

public class LambdaStyle {
    public static void main(String[] args) {
        // 排序一个数组
        String[] strings = "Mary had a little lamb".split(" ");
        Arrays.sort(strings, String::compareToIgnoreCase);
        System.out.println(Arrays.toString(strings));
    }
}

对于函数式接口Comparator来说,它的抽象方法为:

int compare(T o1, T o2);

这个方法有两个参数,前面出现在lambda表达式参数中的s1,s2,实际上就是这两个参数。

而本文所举的关于方法引用的前两个例子,所引用的方法都有两个参数。

而对于第三个关于方法引用的例子,String的compareToIgnoreCase方法只有一个参数。这时,第一个参数会成为执行方法的对象,(s1.compareToIgnoreCase(s2))

另外,也可以通过如下形式方法引用:

this::实例方法

super::实例方法

方法引用例5:

public class SuperTest {
    public static void main(String[] args) {
        class Greeter {
            public void greet() {
                System.out.println("Hello, world!");
            }
        }

        class ConcurrentGreeter extends Greeter {
            public void greet() {
                Thread t = new Thread(super::greet);
                t.start();
            }
        }

        new ConcurrentGreeter().greet();
    }
}

4. 构造器引用

和方法引用相似,只不过通过如下方式引用:

类::new

例1

Stream<Button> stream = labels.stream().map(Button::new);
Button[] buttons4 = stream.toArray(Button[]::new);

5.变量作用域

lambda表达式引用值,而不是变量。

lambda 表达式中引用的局部变量必须是:显示声明为final的,或者虽然没有被声明为final,但实际上也算是有效的final的。

在Java中与其相似的是匿名内部类关于局部变量的引用。

例1:匿名内部类引用局部变量——jdk8以前

public class Outter {

    public static void main(String[] args) {
        final String  s1 = "Hello ";
        new Inner() {
            @Override
            public void printName(String name) {
                System.out.println(s1 + name);
            }
        }.printName("Lucy");

    }
}

interface Inner{
    public void printName(String name);

};

如例1所示,在jdk8以前,匿名内部类引用外部类定义的局部变量,则该变量必须是final的。

jdk8将这个条件放宽,匿名内部类也可以访问外部类有效的final局部变量——即这个变量虽然没有显示声明为final,但定义后也没有再发生变化。

例2:匿名内部类引用局部变量——jdk8

public class Outter {

    public static void main(String[] args) {
        String  s1 = "Hello ";
        new Inner() {
            @Override
            public void printName(String name) {
                System.out.println(s1 + name);
            }
        }.printName("Lucy");

    }
}

interface Inner{
    public void printName(String name);

};

匿名内部类引用的外部类变量s1可以不显示定义为final。但是s1必须在初始化后不再改变。

lambda表达式对于引用局部变量的规则同jdk8中的匿名内部类一样:显示声明为final的,或者虽然没有被声明为final,但实际上也算是有效的final的

import java.io.*;
import java.nio.charset.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;

public class VariableScope {
    public static void main(String[] args) {
        repeatMessage("Hello", 100);
    }

    public static void repeatMessage(String text, int count) {
        Runnable r = () -> {
            for (int i = 0; i < count; i++) {
                System.out.println(text);
                Thread.yield();
            }
        };
        new Thread(r).start();
    }

    public static void repeatMessage2(String text, int count) {
        Runnable r = () -> {
            while (count > 0) {
                // count--; // Error: Can‘t mutate captured variable
                System.out.println(text);
                Thread.yield();
            }
        };
        new Thread(r).start();
    }

    public static void countMatches(Path dir, String word) throws IOException {
        Path[] files = getDescendants(dir);
        int matches = 0;
        for (Path p : files)
            new Thread(() -> {
                if (contains(p, word)) {
                    // matches++;
                    // ERROR: Illegal to mutate matches
                }
            }).start();
    }

    private static int matches;

    public static void countMatches2(Path dir, String word) {
        Path[] files = getDescendants(dir);
        for (Path p : files)
            new Thread(() -> {
                if (contains(p, word)) {
                    matches++;
                    // CAUTION: Legal to mutate matches, but not threadsafe
                }
            }).start();
    }

    // Warning: Bad code ahead
    public static List<Path> collectMatches(Path dir, String word) {
        Path[] files = getDescendants(dir);
        List<Path> matches = new ArrayList<>();
        for (Path p : files)
            new Thread(() -> {
                if (contains(p, word)) {
                    matches.add(p);
                    // CAUTION: Legal to mutate matches, but not threadsafe
                }
            }).start();
        return matches;
    }

    public static Path[] getDescendants(Path dir) {
        try {
            try (Stream<Path> entries = Files.walk(dir)) {
                return entries.toArray(Path[]::new);
            }
        } catch (IOException ex) {
            return new Path[0];
        }
    }

    public static boolean contains(Path p, String word) {
        try {
            return new String(Files.readAllBytes(p),
                    StandardCharsets.UTF_8).contains(word);
        } catch (IOException ex) {
            return false;
        }
    }
}
时间: 2024-08-03 09:03:21

Java SE 8 for the Really Impatient读书笔记——Java 8 Lambda表达式的相关文章

读书笔记-----Java并发编程实战(一)线程安全性

线程安全类:在线程安全类中封装了必要的同步机制,客户端无须进一步采取同步措施 示例:一个无状态的Servlet 1 @ThreadSafe 2 public class StatelessFactorizer implements Servlet{ 3 public void service(ServletRequest req,ServletResponse resp){ 4 BigInteger i = extractFromRequest(req); 5 BigInteger[] fact

《C++ Primer 4th》读书笔记 第5章-表达式

原创文章,转载请注明出处: http://www.cnblogs.com/DayByDay/p/3912114.html <C++ Primer 4th>读书笔记 第5章-表达式

[读书笔记]java核心技术

ps:有时间好好整理下格式.从别的编辑器拷贝过来啥都没了. ~~~~~~~~~~~~~~· 2.java程序设计环境 JDK 开发java使用的软件: JRE 运行java使用的软件: SE 用于桌面或简单服务器应用的java平台--废弃 EE 用于复杂服务器应用的java平台--通用. ME 手机或其他小型设备的java平台--废弃 库源文件和文档: src.zip---包含了所有公共类库的源代码. JDK目录结构: bin 编译器和工具, demo演示, docs 类库文档,include

读书笔记--Java核心技术--高级特征

第一章--流与文件---------------------------------------------- 流 读写字节 java.io.InputStream 1.0 abstract int read() //从数据中读入一个字节,并返回该字节,在碰到流的结尾时返回-1 int read(byte[] b) //读入一个字节数组,并返回实际读入的字节数,或者在碰到流的结尾时返回-1 int read(byte[] b, int off, int len) //读入一个字节数组.这个rea

读书笔记--Java核心技术--基础篇

第三章   Java基本程序设计----------------------------------------------------------------- 在JAVA中,/* */注释不能嵌套 Java有8种基本类型 4种整型:int, long, short, byte:长整型后缀加L,0x前缀表示十六进制,0前缀表示八进制,0b前缀表示二进制 2种浮点类型:float, double:后缀F表示float,后缀D表示double,默认使用double 常量Double.POSITIV

[读书笔记]java中的类加载器

以下内容大多来自周志明的<深入理解Java虚拟机>. 类加载器是java的一项创新,也是java流行的重要原因之一,它最初是为了满足java applet的需求而开发出来. 什么是applet? 作为新手,都不知道applet是什么鬼,看看百度百科的解释,应该就明白了: JavaApplet就是用Java语言编写的小应用程序,可以直接嵌入到网页中,并能够产生特殊的效果. 所以Applet就目前来看 我们用不到了,但是类加载器却在类层次划分.OSGI.热部署.代码加密等领域大放异彩,成为了JAV

高效 告别996,开启java高效编程之门 2-8实战:判断逻辑参数化-Lambda表达式

0 有用部分 1    函数式编程简介 2    函数式编程和lambda的关系 3    lambda表达式应用范围 4    lambda应用的五个案例 5    函数式编程应用条件 6 提出问题 0 有用部分 4 lambda应用的五个案例 5 函数式编程应用条件 6 提出问题 本节主要介绍了lambda的使用方式,具体demo参照2-10 和2-15 1 函数式编程简介 函数式编程是一种不同的编程思想,定义函数作为公民,可以赋值给变量,作为参数或者返回值来传递 2 函数式编程和lambd

《分布式Java应用之基础与实践》读书笔记三

对于大型分布式Java应用与SOA,我们可以从以下几个方面来分析: 为什么需要SOA SOA是什么 eBay的SOA平台 可实现SOA的方法 为什么需要SOA ??第一个现象是系统多元化带来的问题,可采用对共用逻辑的部分进行抽象的方法,形成多个按领域划分的公用业务逻辑系统:第二个现象是系统访问量.数据量上涨后带来的典型问题,可采用拆分系统的方式来解决.在构建了共用业务逻辑系统和拆分系统后,最明显的问题就是系统之间如何交互.为了整个系统的性能.可用性等考虑,统一的交互方式就成为明显的解决方案了,S

[读书笔记] java类初始化

以下内容来自周志明的<深入理解java虚拟机>: 类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制. 到了初始化阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码). 在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器<clinit>()方法的过程.