zoukankan      html  css  js  c++  java
  • 20180629 Java Lambda

    • lambda从Java8开始

    基础语法

    expression = (variable) -> action
    
    • variable: 这是一个变量,一个占位符。像x,y,z,可以是多个变量;
    • action: 这里我称它为action, 这是我们实现的代码逻辑部分,它可以是一行代码也可以是一个代码片段。

    函数式接口

    • 我们把这些只拥有一个方法的接口称为函数式接口。(之前它们被称为SAM类型,即 单抽象方法类型(Single Abstract Method))
    • 注解@FunctionalInterface标识一个接口为函数式接口,被标识的接口只能有一个抽象方法(除了已经在Object中定义的方法),否则会报错
    • 不标识@FunctionalInterface的接口,但事实上也是函数式接口的,依然可以使用lambda表达式
    • JDK中的函数式接口位置:java.util.function,包含了常用的函数式接口:

    Predicate——接收T并返回boolean
    Consumer——接收T,不返回值
    Function<T, R>——接收T,返回R
    Supplier——提供T对象(例如工厂),不接收值
    UnaryOperator——接收T对象,返回T
    BinaryOperator——接收两个T,返回T

    • Java SE 7 中已经存在的函数式接口
    java.lang.Runnable
    java.util.concurrent.Callable
    java.util.Comparator
    java.io.FileFilter
    
    
    • 除了上面的这些基本的函数式接口,还提供了一些针对原始类型(Primitive type)的特化(Specialization)函数式接口,例如IntSupplierLongBinaryOperator,还有很多在java.util.function包中,应该每一个都有所了解

    四大核心函数式接口:

    image

    消费型接口示例

    public static void donation(Integer money, Consumer<Integer> consumer){
        consumer.accept(money);  
    }
    public static void main(String[] args) {
        donation(1000, money -> System.out.println("好心的麦乐迪为Blade捐赠了"+money+"元")) ;
    }
    

    供给型接口示例

    public static List<Integer> supply(Integer num, Supplier<Integer> supplier){
           List<Integer> resultList = new ArrayList<Integer>()   ;
           for(int x=0;x<num;x++)  
               resultList.add(supplier.get());
           return resultList ;
    }
    public static void main(String[] args) {
        List<Integer> list = supply(10,() -> (int)(Math.random()*100));
        list.forEach(System.out::println);
    }
    

    函数型接口示例

    public static Integer convert(String str, Function<String, Integer> function) {
        return function.apply(str);
    }
    public static void main(String[] args) {
        Integer value = convert("28", x -> Integer.parseInt(x));
    }
    

    断言型接口示例

    public static List<String> filter(List<String> fruit, Predicate<String> predicate){
        List<String> f = new ArrayList<>();
        for (String s : fruit) {
            if(predicate.test(s)){
                f.add(s);
            }
        }
        return f;
    }
    public static void main(String[] args) {
        List<String> fruit = Arrays.asList("香蕉", "哈密瓜", "榴莲", "火龙果", "水蜜桃");
        List<String> newFruit = filter(fruit, (f) -> f.length() == 2);
        System.out.println(newFruit);
    }
    

    自定义函数式接口

    • 实现简单加法
    public class Test2 {
    	/*
    	一个接口,如果只有一个显式声明的抽象方法,
    	那么它就是一个函数接口。
    	一般用@FunctionalInterface标注出来(也可以不标)
    	*/
    	interface Inteface1 {
    		// 可以不用abstract修饰
    		public abstract void test(int x, int y);
    
    		// public void test1();//会报错,不能有两个方法,尽管没有使用abstract修饰
    		public boolean equals(Object o);// equals属于Object的方法,所以不会报错
    	}
    
    	public static void main(String args[]) {
    		Inteface1 f1 = (x, y) -> {
    			System.out.println(x + y);
    		};
    		f1.test(3, 4);
    		Inteface1 f2 = (int x, int y) -> {
    			System.out.println("Hello Lambda!	 the result is " + (x + y));
    		};
    		f2.test(3, 4);
    	}
    }
    

    lambda表达式

    • lambda 表达式的语法由参数列表、箭头符号->和函数体组成。函数体既可以是一个表达式,也可以是一个语句块
    • 出现在语句中的lambda表达式
    FileFilter java = (File f) -> f.getName().endsWith("*.java");
    String user = doPrivileged(() -> System.getProperty("user.name"));
    new Thread(() -> {
      connectToService();
      sendNotification();
    }).start();
    

    目标类型(Target typing)

    • 同样的 lambda 表达式在不同上下文里可以拥有不同的类型
    Callable<String> c = () -> "done";
    PrivilegedAction<String> a = () -> "done";
    

    第一个 lambda 表达式 () -> "done" 是Callable的实例,而第二个 lambda 表达式则是PrivilegedAction的实例。
    编译器负责推导 lambda 表达式类型。
    lambda 表达式的参数类型可以从目标类型中得出。
    当 lambda 的参数只有一个而且它的类型可以被推导得知时,该参数列表外面的括号可以被省略:

    FileFilter java = f -> f.getName().endsWith(".java");
    button.addActionListener(e -> ui.dazzle(e.getModifiers()));
    

    目标类型的上下文(Contexts for target typing)

    • lambda 表达式本身也可以为它自己的函数体提供目标类型,也就是说 lambda 表达式可以通过外部目标类型推导出其内部的返回类型,这意味着我们可以方便的编写一个返回函数的函数:
    Supplier<Runnable> c = () -> () -> { System.out.println("hi"); };
    
    • 条件表达式可以把目标类型“分发”给其子表达式:
    Callable<Integer> c = flag ? (() -> 23) : (() -> 42);
    
    • 转型表达式(Cast expression)可以显式提供 lambda 表达式的类型,这个特性在无法确认目标类型时非常有用:
    // Object o = () -> { System.out.println("hi"); }; 这段代码是非法的
    Object o = (Runnable) () -> { System.out.println("hi"); };
    

    词法作用域(Lexical scoping)

    • lambda 表达式函数体里面的变量和它外部环境的变量具有相同的语义,不同于内部类
    public class Hello {
    	Runnable r1 = () -> {
    		System.out.println(this);
    	};
    
    	Runnable r2 = new Runnable() {
    		public void run() {
    			System.out.println(this);
    		}
    	};
    
    	public String toString() {
    		return "Hello, world";
    	}
    
    	public static void main(String... args) {
    		new Hello().r1.run();	// Hello, world
    		new Hello().r2.run();	// lambda.Hello$1@1fb3ebeb
    	}
    }
    

    变量捕获(Variable capture)

    • 类似于在匿名类内部引用函数局部变量,必须将其声明为final
    public static Supplier<Integer> testClosure() {
    	int i = 1;
    	// i++; // 会报错
    	return () -> {
    		return i;
    	};
    }
    

    方法引用(Method references)

    • 不需要为方法引用提供方法体,可以直接通过方法名称引用已有方法
    Comparator<Person> byName = Comparator.comparing(p -> p.getName());
    Comparator<Person> byName = Comparator.comparing(Person::getName);  // 缩写形式
    
    • 因为函数式接口的方法参数对应于隐式方法调用时的参数,所以被引用方法签名可以通过放宽类型,装箱以及组织到参数数组中的方式对其参数进行操作,就像在调用实际方法一样:
    Consumer<Integer> b1 = System::exit; // void exit(int status)
    Consumer<String[]> b2 = Arrays::sort; // void sort(Object[] a)
    Consumer<String> b3 = MyProgram::main; // void main(String... args)
    Runnable r = Myprogram::mapToInt // void mapToInt()
    

    方法引用的种类(Kinds of method references)

    • 静态方法引用:ClassName::methodName
    • 实例上的实例方法引用:instanceReference::methodName
    • 超类上的实例方法引用:super::methodName
    • 类型上的实例方法引用:ClassName::methodName
    • 构造方法引用:Class::new
    • 数组构造方法引用:TypeName[]::new

    默认方法和静态接口方法(Default and static interface methods)

    • 默认方法 利用面向对象的方式向接口增加新的行为。它是一种新的方法:接口方法可以是 抽象的 或是 默认的。默认方法拥有其默认实现,实现接口的类型通过继承得到该默认实现(如果类型没有覆盖该默认实现)。此外,默认方法不是抽象方法,所以我们可以放心的向函数式接口里增加默认方法,而不用担心函数式接口的单抽象方法限制。
    • 除了默认方法,Java SE 8 还在允许在接口中定义 静态 方法。这使得我们可以从接口直接调用和它相关的辅助方法(Helper method),而不是从其它的类中调用(之前这样的类往往以对应接口的复数命名,例如 Collections)
    • 理解这两种方法的一个实例:java.util.Comparator<T>

    使用实例

    Collections.sort(list, new Comparator<Person>() {
    	public int compare(Person x, Person y) {
    		return x.getLastName().compareTo(y.getLastName());
    	}
    });
    
    Collections.sort(list, (Person x, Person y) -> x.getLastName().compareTo(y.getLastName()));
    
    Collections.sort(list, Comparator.comparing((Person p) -> p.getLastName()));
    
    Collections.sort(list, Comparator.comparing(p -> p.getLastName()));
    
    Collections.sort(list, Comparator.comparing(Person::getLastName));
    
    list.sort(Comparator.comparing(Person::getLastName));
    
    

    参考资料

  • 相关阅读:
    jquery扩展鼠标mousewheel事件
    addEventListener和attachEvent介绍, 原生js和jquery的兼容性写法
    HTML,CSS,font-family:中文字体的英文名称
    最好的Java和Android开发IDE---IntelliJ IDEA使用技巧
    《Thinking in Java》习题——吸血鬼数字
    Java学习之——JavaBeans
    Android 开源库——侧滑菜单栏(SlidingMenu)的导入和使用
    Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)
    Android 布局学习之——Layout(布局)详解一
    Android 布局学习之——LinearLayout的layout_weight属性
  • 原文地址:https://www.cnblogs.com/huangwenjie/p/9252116.html
Copyright © 2011-2022 走看看