lambda函数式接口

阅读: 评论:0

lambda函数式接口

lambda函数式接口

(注:本文章参看自《Java 8 实战》厄马(Raoul-Gabriel Urma) / 弗斯科(Mario Fusco) / 米克罗夫特(Alan Mycroft))

 

目录

1.1 新的函数式接口

1.1.1 Predicate

1.1.2 Consumer

1.1.3 Function

2.1 Lambda及函数式接口的例子

3.1 Lambda的方法引用

4.1 构造函数引用

5.1 复合 Lambda 表达式

5.1.1 比较器复合

5.1.2 谓词复合

5.1.3 函数复合


java8中,设计师在java.util.function包中引入了几个新的函数式接口:Predicate 、 Consumer 和 Function。

1.1 新的函数式接口

1.1.1 Predicate

java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。这恰恰和你先前创建的一样,现在就可以直接使用了。在你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。比如,你可以定义一个接受String对象的Lambda表达式,如下所示。
@FunctionalInterface 
public interface Predicate<T>{ boolean test(T t); 
}

示例: 

public class Demo {public static void main(String[] args) {List<String> listOfStrings = new ArrayList<>();listOfStrings.add("hello");listOfStrings.add("world.");listOfStrings.add("");Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);System.out.println(nonEmpty);//        List<String> nonEmpty = listOfStrings.stream()//                .filter(item -> !item.isEmpty())//                .List());}public static <T> List<T> filter(List<T> list, Predicate<T> p) {List<T> results = new ArrayList<>();for (T s : list) {if (p.test(s)) {results.add(s);}}return results;}
}

1.1.2 Consumer

java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)。你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。比如,你可以用它来创建一个forEach方法,接受一个Integers的列表,并对其中每个元素执行操作。在下面的代码中,你就可以使用这个forEach方法,并配合Lambda来打印列表中的所有元素。

@FunctionalInterface
public interface Consumer<T>{void accept(T t);
}

示例:

public class Demo {public static void main(String[] args) {List<String> listOfStrings = new ArrayList<>();listOfStrings.add("hello");listOfStrings.add("world.");listOfStrings.add("");forEach(listOfStrings, System.out::println);}public static <T> void forEach(List<T> list, Consumer<T> c) {for (T i : list) {c.accept(i);}}}

其中,Lambda是Consumer中accept方法的实现。 

1.1.3 Function

java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个 泛型T的对象,并返回一个泛型R的对象。如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)。在下面的代码中,我们向你展示如何利用它来创建一个map方法,以将一个String列表映射到包含每个 String长度的Integer列表。

@FunctionalInterface
public interface Function<T, R>{R apply(T t);
}

示例:

public class Demo {public static void main(String[] args) {List<String> listOfStrings = new ArrayList<>();listOfStrings.add("hello");listOfStrings.add("world");listOfStrings.add("!!");System.out.println(map(listOfStrings, String::length));}public static <T, R> List<R> map(List<T> list,Function<T, R> f) {List<R> result = new ArrayList<>();for (T s : list) {result.add(f.apply(s));}return result;}
}

 其中,Lambda是Function接口的apply方法的实现。

2.1 Lambda及函数式接口的例子

Lambda及函数式接口的例子
使用案例Lambda的例子对应的函数式接口
布尔表达式(List<String> list) -> list.isEmpty()Predicate<List<String>>
创建对象()->new Apple(10)Supplier<Apple>
消费一个对象
 
(Apple a) -> 
System.out.Weight()) 
Consumer<Apple>
从一个对象中选择/提取
 
(String s) -> s.length()

Function<String, Integer>


ToIntFunction<String>

合并两个值(int a, int b) -> a * bIntBinaryOperator
比较两个对象(Apple a1, Apple a2) -> 
a1.getWeight()Weight())

Comparator<Apple>


BiFunction<Apple, Apple, Integer>

ToIntBiFunction<Apple, Apple>

3.1 Lambda的方法引用

      Lambda的方法引用主要有三类。

  1. 指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)。
  2. 指向任意类型实例方法的方法引用(例如 String 的 length 方法,写作String::length)。
  3. 指向现有对象的实例方法的方法引用(假设你有一个局部变量expensiveTransaction
    用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)。

     第二种和第三种方法引用可能乍看起来有点儿晕。类似于String::length的第二种方法引用的思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。例如,Lambda表达式(String s) -> s.toUppeCase()可以写作String::toUpperCase。但第三种方法引用指的是,你在Lambda中调用一个已经存在的外部对象中的方法。例如,Lambda表达式
()-&Value()可以写作expensiveTransaction::getValue。

Lambda及其等效方法引用的例子
Lambda 等效的方法引用
(Apple a) -> a.getWeight() Apple::getWeight
() -> Thread.currentThread().dumpStack()  Thread.currentThread()::dumpStack
(str, i) -> str.substring(i)String::substring
(String s) -> System.out.println(s)System.out::println

4.1 构造函数引用

对于一个现有构造函数,我们可以使用ClassName::new这样的格式来创建对象的引用。它的功能与指向静态方法的引用类似。例如:

Supplier<Apple> c1 = Apple::new; 
Apple a1 = c1.get();

其中,构造函数引用指向默认的Apple()构造函数,然后调用Supplier的get方法将产生一个新的Apple。这就等价于:

Supplier<Apple> c1 = () -> new Apple(); 
Apple a1 = c1.get();

如果构造函数有一个参数,像Apple(Integer weight),那么它就适合Function接口的签名:

Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c2.apply(110);

以上代码等价于:

Function<Integer, Apple> c2 = (weight) -> new Apple(weight);
Apple a2 = c2.apply(110);

下面简单应用一下上面的例子,假如我们要得到了一个具有不同重量的苹果的List,可以这样做:

    // 苹果重量List<Integer> weights = Arrays.asList(7, 3, 4, 10);List<Apple> apples = map(weights, Apple::new);public static List<Apple> map(List<Integer> list,Function<Integer, Apple> f) {List<Apple> result = new ArrayList<>();for (Integer e : list) {result.add(f.apply(e));}return result;}

如果构造函数有两个参数,像Apple(String color, Integer weight),那么它就适合BiFunction接口的签名:

    BiFunction<String, Integer, Apple> c3 = Apple::new;Apple c3 = c3.apply("green", 110);

以上代码等价于:

BiFunction<String, Integer, Apple> c3 = (color, weight) -> new Apple(color, weight);
Apple c3 = c3.apply("green", 110);

 不将构造函数实例化却能够引用它,这个功能有一些有趣的应用。例如,你可以使用Map来将构造函数映射到字符串值。

public class Demo {static Map<String, Function<String, Animal>> map = new HashMap<>();static {map.put("animal", Animal::new);// }public static void main(String[] args) {Animal animal = giveMyAnimal();System.out.println(animal);}public static Animal giveMyAnimal() {("animal").apply("大黄狗");}
}

5.1 复合 Lambda 表达式

下面做个小需求,我们根据实体对象的年龄进行排序(升序):

listOfAnimals.sort(Comparatorparing(Animal::getAge));

5.1.1 比较器复合

1.逆序

如果按照年龄逆序排序,我们可以这样:

listOfAnimals.sort(Comparatorparing(Animal::getAge).reversed());

 2.比较器链

如果年龄一样的话,我们可以按照其他规则来排序,比如按照创建时间先后来排序:

listOfAnimals.sort(Comparatorparing(Animal::getAge).reversed().thenComparing(Animal::getCreateTime));

5.1.2 谓词复合

谓词接口包括三个方法:negate、and和or,让你可以重用已有的Predicate来创建更复杂的谓词。

比如,我们现在要筛选出要么是重(150克以上)的红苹果,要么是绿苹果的苹果集合:

Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150) .or(a -> "green".Color()));

5.1.3 函数复合

我们还可以把Function接口所代表的Lambda表达式复合起来。Function接口为此配了andThen和compose两个默认方法,它们都会返回Function的一个实例。

1.andThen方法

andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数。
比如,假设有一个函数f给数字加1 (x -> x + 1),另一个函数g给数字乘2,你可以将它们组
合成一个函数h,先给数字加1,再给结果乘2:

        // g(f(x))// f(x) = x +1 ;  g(x) = x * 2;Function<Integer, Integer> f = x -> x + 1;Function<Integer, Integer> g = x -> x * 2;Function<Integer, Integer> h = f.andThen(g);int result = h.apply(1);System.out.println(result); // 输出4

2. compose方法

compose方法先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果。比如在上一个例子里用compose的话,它将意味着f(g(x)), 而andThen则意味着g(f(x)) 。

        // f(g(x))// f(x) = x +1 ;  g(x) = x * 2;Function<Integer, Integer> f = x -> x + 1;Function<Integer, Integer> g = x -> x * 2;Function<Integer, Integer> h = fpose(g);int result = h.apply(1);System.out.println(result); // 输出3

实际应用当中,比方说我们有一系列工具方法,在Letter类中用String表示的一封信做文本转换:

public class Letter{ public static String addHeader(String text){ return "From Raoul, Mario and Alan: " + text; } public static String addFooter(String text){ return text + " Kind regards"; } public static String checkSpelling(String text){ placeAll("labda", "lambda"); } 
}

现在我们可以通过复合这些工具方法来创建各种转型流水线了,比如创建一个流水线:先加上
抬头,然后进行拼写检查,最后加上一个落款。

Function<String, String> addHeader = Letter::addHeader; 
Function<String, String> transformationPipeline = addHeader.andThen(Letter::checkSpelling) .andThen(Letter::addFooter);

                                                                                  

 第二个流水线可能只加抬头、落款,而不做拼写检查:

        Function<String, String> addHeader = Letter::addHeader;Function<String, String> transformationPipeline= addHeader.andThen(Letter::addFooter);

 (未完待续...)

本文发布于:2024-01-29 14:57:52,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170651147616089.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:函数   接口   lambda
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23