龙岗商城网站建设哪家便宜品牌网站建设 app建设
news/
2025/9/29 13:54:28/
文章来源:
龙岗商城网站建设哪家便宜,品牌网站建设 app建设,手表之家官网,付费可见wordpress问候#xff01; :) 离开几个月后#xff0c;我决定恢复风格:)。 我注意到我以前有关新的Date / Time API的一篇文章非常受欢迎#xff0c;因此这次我将把本篇文章专门介绍Java 8的另一个新功能#xff1a; Lambda Expressions 。 功能编程 Lambda表达式是Java编程语言最… 问候 :) 离开几个月后我决定恢复风格:)。 我注意到我以前有关新的Date / Time API的一篇文章非常受欢迎因此这次我将把本篇文章专门介绍Java 8的另一个新功能 Lambda Expressions 。 功能编程 Lambda表达式是Java编程语言最终实现函数式编程细微差别的方式。 函数式编程的定义充满争议。 以下是维基百科告诉我们的内容 “在计算机科学中函数式编程是一种编程范式一种构建计算机程序的结构和元素的方式将计算视为对数学函数的评估并避免了状态和可变数据” 总而言之 lambda表达式将允许将行为函数作为方法调用中的参数进行传递。 这是一个与Java程序员习惯不同的范例因为一直以来我们只编写了将对象作为参数的方法而没有其他方法 Java平台在这次聚会上实际上有点晚了。 诸如ScalaCPython甚至Javascript之类的其他语言已经这样做了相当长的时间。 有人认为即使lambda使“事半功倍”成为可能但它会损害代码的可读性。 那些反对在Java编程语言中添加lambda的人经常使用此指控。 马丁·福勒本人曾说过 任何傻瓜都可以编写计算机可以理解的代码。 好的程序员编写人类可以理解的代码。” 除了争议之外至少有一个很好的理由支持lambda表达式 并行性。 随着多核CPU的激增必须编写易于使用并行处理的代码。 在Java 8之前还没有一种简单的方法可以轻松并行地迭代大量对象。 正如我们将进一步看到的那样使用Streams将使我们能够做到这一点。 Lambdas与匿名内部类 对于那些无法抑制您的兴奋的人这里是第一个口味。 所谓的“经典”使用lambda会发生在通常选择匿名类的地方。 如果您想到它那就是我们想要传递“行为”而不是状态对象的确切位置。 例如我将使用大多数人可能已经知道的Swing API。 实际上在任何需要处理用户事件的GUI API中此类情况几乎都是相同的JavaFXApache WicketGWT等。 使用Swing 如果希望在用户单击按钮时执行某些操作则可以执行以下操作 上图显示的是我们在Java中处理事件的最常用方式之一。 但是请注意我们的真正意图只是将行为传递给addActionListener方法按钮动作。 我们最终要做的是传递一个对象状态作为参数即匿名ActionListener 。 以及如何使用lambda来完成完全相同的事情 像这样 如我之前所说我们可以“事半功倍”。 我们仅将我们真正想完成的动作仅行为作为参数传递给addActionListener方法。 创建匿名类所需的所有麻烦事就消失了。 语法细节将在以后进行探讨但是上面代码中的lambda表达式可以归结为 (event) - System.out.println(Button 2 clicked!) 我知道我知道。 你们中有些人可能在想 “等一下 自从《 地牢与龙》第一集问世以来我一直是一名摇摆式程序员但我从未见过仅用一行代码就能处理事件 冷静年轻的绝地武士。 也可以用“ n”行代码编写lambda 。 但是再说一遍代码越大我们获得的可读性就越少 就个人而言我仍然是那些认为即使使用多个语句使用lambda的代码也比使用匿名类的代码更干净的人的一部分。 如果我们忽略缩进那么所有语法要求就是将大括号加起来作为块定界符并且每个语句都有自己的“;” (event) - {System.out.println(First); System.out.println(Second);} 但是不要失去所有希望。 当您有多个语句时仍然存在使用lambda处理事件的更简洁的方法。 只需看下面的代码摘录 public class MyFrame extends Frame {public MyFrame() {//create the buttonJButton button5 new JButton(Button 5);//buttonClick() is a private method of this very classbutton5.addActionListener(e - buttonClick(e));//etc etc etc}private void buttonClick(ActionEvent event) {//multiple statements here}
} 看到 就那么简单。 FunctionalInterface 要编写lambda表达式您首先需要一个所谓的“功能接口” 。 “功能接口”是具有完全一种抽象方法的Java接口。 不要忘记这一部分“一种抽象方法”。 这是因为现在在Java 8中可以在接口内部具有具体的方法实现 默认方法和静态方法 。 就规范而言您在接口中可能拥有的所有默认方法和静态方法均不计入功能接口配额。 如果您有9个默认或静态方法并且只有一个抽象方法那么从概念上讲它仍然是一个函数接口 。 为了使情况更清楚一点有一个功能丰富的注释 FunctionalInterface其唯一作用是将接口标记为“功能性”。 请注意与Override一起发生时它的用途仅是在编译时演示意图。 尽管它是可选的但我强烈建议您使用它。 ps以前使用的ActionListener接口只有一个抽象方法这使其成为完整的功能接口。 让我们创建一个简单的示例以增强lambda表达式的语法。 想象一下我们想创建一个API一个类用作两个Double类型操作数的计算器。 也就是说一个java类具有加减除等方法两个类型为Double的对象 public class Calculator {public static Double sum(Double a, Double b) {return a b;}public static Double subtract(Double a, Double b) {return a - b;}public static Double multiply(Double a, Double b) {return a * b;}//etc etc etc...
} 为了“直接脱离NASA”使用此计算器API的客户端只需调用任何静态方法即可 Double result Calculator.sum(200, 100); //300 但是这种方法存在一些问题。 实际上不可能对Double类型的两个对象之间的所有可能操作进行编程。 很快我们的客户将需要较少的通用操作例如平方根或其他。 而您此API的所有者将永远被奴役。 如果我们的计算器足够灵活以使客户自己知道他们想使用哪种数学运算那不是很好吗 为了实现这个目标我们首先创建一个称为DoubleOperator的功能接口 FunctionalInterface
public interface DoubleOperator {public Double apply(Double a, Double b);} 我们的接口定义了一个合约通过该合约对两个Double类型的对象进行操作该对象还返回一个Double。 确切的操作将留给客户决定。 现在 Calculator类仅需要一个方法将两个Double操作数作为参数和一个lambda表达式 这些表达式将使我们的客户可以知道他们想要的操作 public class Calculator {public static Double calculate(Double op1, Double op2, DoubleOperator operator) {return operator.apply(op1, op2); //delegate to the operator}} 最后这是我们的客户如何在新API上调用方法 //sum
Double result1 Calculator.calculate(30d, 70d, (a, b) - a b);
System.out.println(result1); //100.0//subtract
Double result2 Calculator.calculate(200d, 50d, (a, b) - a - b);
System.out.println(result2); // 150.0//multiply
Double result3 Calculator.calculate(5d, 5d, (a, b) - a * b);
System.out.println(result3); // 25.0//find the smallest operand using a ternary operator
Double result4 Calculator.calculate(666d, 777d, (a, b) - a b ? b : a);
System.out.println(result4); //666.0 现在的天空是极限。 客户可以使用任何想到的方法调用calculate方法。 他们需要做的就是提供一个有效的lambda表达式 。 Lambda必须以字符“-”分隔。 左侧部分仅用于参数声明。 右侧部分代表方法实现本身 请注意左侧部分仅具有参数声明该参数声明对应于DoubleOperator.applyDouble aDouble b签名。 该参数的类型可以由编译器推断并且在大多数情况下无需通知。 同样参数变量的名称可以是我们想要的任何名称而不必像我们的功能接口的签名一样是“ a”和“ b” //sum with explicit types
Double result1 Calculator.calculate(30d, 70d, (Double x, Double y) - x y); //another way
OperadorDouble operator (Double op1, Double op2) - op1 op2;
Double result2 Calculator.calculate(30d, 70d, operador); 当功能接口的方法签名没有任何参数时您要做的就是放置一个空的“” 。 这可以在Runnable界面的帮助下看到 /* The r variable can be passed to any method that takes a Runnable */
Runnable r () - System.out.println(Lambda without parameter); 出于好奇我将展示一种替代语法该语法也可用于声明lambda 即方法参考 。 我不会深入探讨细节否则我将需要一整本书来撰写这篇文章。 当您的所有表达式想要进行方法调用时它提供了一种更简洁的方法 JButton button4 new JButton(Button 4);//this
button4.addActionListener(ActionEvent::getSource); //is equivalent to this
button4.addActionListener((event) - event.getSource());不要重新发明轮子 在继续之前让我们快速停下来以记住我们都知道的这个旧术语。 这意味着在Java的8 API中我们日常工作中可能已经需要大量的功能接口 。 其中包括一个可以完全消除我们的DoubleOperator接口的接口。 所有这些接口都位于java.util.function包内主要的接口是 名称 参量 返回 例 BinaryOperator T TT Ť 在相同类型的两个对象之间进行任何类型的操作。 消费者T Ť 虚空 打印一个值。 函数TR Ť [R 以Double类型的对象并将其作为String返回。 谓词T Ť 布尔值 对作为参数传递的对象进行任何类型的测试oneString.endsWith“ suffix” 供应商T – Ť 进行不带任何参数但具有返回值的操作。 不是吗 所有其他内容只是上述内容的变体。 足够快地当我们看到Streams的使用时我们将有机会看到大多数Streams的实际应用并且更容易适应整个画面。 不过我们可以重构我们的计算器类并通过在JDK中已经提供了一个代替古老DoubleOperator接口BinaryOperator public class Calculator {public static T T calculate(T op1, T op2, BinaryOperatorT operator) {return operator.apply(op1, op2);}} 对于我们的客户除了BinaryOperator接口具有参数化类型 泛型之外几乎没有什么改变现在我们的计算器更加灵活因为我们可以在任何类型的两个对象之间进行数学运算而不仅仅是Doubles //sum integers
Integer result1 Calculator.calculate(5, 5, (x, y) - x y);集合和流 作为开发人员我们可能会浪费大部分时间使用第三方API而不是自己开发。 这就是到目前为止我们已经完成了这些工作了解了如何在自己的API中使用lambda 。 现在是时候分析对核心Java API所做的一些更改了这些更改使我们可以在处理集合时使用lambda 。 为了说明我们的示例我们将使用一个简单的类Person 它具有名称 年龄和性别 “ M”代表男性“ F”代表女性 public class Person {private String name;private Integer age;private String sex; //M or F//gets and sets
} 前面的所有示例都需要对象集合因此假设我们有一个Person类型的对象集合 ListPerson persons thisMethodReturnsPersons(); 我们从添加到Collection接口的新方法stream开始。 由于所有集合都“扩展” Collection 因此所有Java集合都继承了此方法 ListPerson persons thisMethodReturnsPersons();
StreamPerson stream persons.stream(); //a stream of person objects 尽管它看来 流接口不只是一个更经常类型的集合。 Stream更多地是一种“数据流”抽象使我们能够转换或操纵其数据。 与我们已经知道的集合不同 Stream不允许直接访问其元素我们需要将Stream转换回Collection 。 为了进行比较让我们看看如果我们必须计算人员集合中有多少个女性对象我们的代码会是什么样子。 首先没有流 long count 0;
ListPerson persons thisMethodReturnsPersons();
for (Person p : persons) {if (p.getSex().equals(F)) {count; }
} 使用for循环我们创建一个计数器该计数器在每次遇到女性时都会增加。 这样的代码我们已经完成了数百次。 现在使用流同样的事情 ListPerson persons thisMethodReturnsPersons();
long count persons.stream().filter(person - person.getSex().equals(F)).count(); 清洁得多不是吗 这一切都始于调用stream方法所有其他调用都链接在一起因为Stream接口中的大多数方法都是在考虑到Builder模式的情况下设计的。 对于那些不习惯使用这种方法进行链接的用户可能更容易这样可视化 ListPerson persons thisMethodReturnsPersons();
StreamPerson stream persons.stream();
stream stream.filter(person - person.getSex().equals(F));
long count stream.count(); 让我们将注意力集中在我们使用的Stream的两种方法中 filter和count 。 filter采用条件来过滤集合。 这个条件由一个lambda表达式表示该表达式带有一个参数并返回一个布尔值 person - person.getSex().equals(F) 并非偶然用于表示该表达式的功能接口 filter方法的参数是谓词接口。 她只有一种抽象方法boolean testT t FunctionalInterface
public interface PredicateT {boolean test(T t);//non abstract methods here
} 参数化类型T表示流元素的类型即Person对象。 这样就好像我们的lambda表达式实现了test方法一样 boolean test(Person person) {if (person.getSex().equals(F)) {return true;} else {return false;}
} 过滤之后剩下的就是调用count方法。 没什么大不了的它只是计算过滤发生后我们流中还剩下多少个对象除了过滤之外我们还可以有更多的东西。 count方法被视为“终端操作”在调用该方法后该流被称为“已消耗”且无法再使用。 让我们看一下Stream接口的其他一些方法。 收集 通常使用collect方法对流执行可变还原 有关详细信息请参见链接。 这通常意味着将流转换回普通集合。 注意与count方法一样 collect方法也是“终端操作” 假设最后一个示例有一个小的变体我们只想从人的集合中过滤掉女性对象。 但是这一次我们不只是过滤女性 filter 然后对它们进行计数 count 。 我们将以物理方式将所有女性对象分离到一个完全不同的集合中该集合仅包含女性 ListPerson persons thisMethodReturnsPersons();//creating a List with females only
ListPerson listFemales persons.stream().filter(p - p.getSex().equals(F)).collect(Collectors.toList());//creating a Set with females only
SetPerson setFemales persons.stream().filter(p - p.getSex().equals(F)).collect(Collectors.toSet()); 过滤部分保持不变唯一的不同是最后对collect的调用。 如我们所见此调用接受一个参数和Collector类型的对象。 要构建类型为Collector的对象需要花费一些时间因此幸运的是有一个类允许我们以更方便的方式构建它们并与Collectors plural类会面。 如Collectors.toList和Collectors.toSet所示 。 一些有趣的例子 //We can choose the specific type of collection we want
//by using Collectors.toCollection().//another way for building a Stream
StreamString myStream Stream.of(a, b, c, d); //transforming into a LinkedList (using method reference)
LinkedListString linkedList myStream.collect(Collectors.toCollection(LinkedList::new));//transforming into a TreeSet
StreamString s1 Stream.of(a, b, c, d);
TreeSetString t1 s1.collect(Collectors.toCollection( () - new TreeSetString() ));//using method reference, the same would be accomplished like this
StreamString s2 Stream.of(a, b, c, d);
TreeSetString t2 s2.collect(Collectors.toCollection( TreeSet::new )); 请注意Collectors.toCollection方法如何采用类型Supplier的lambda表达式 。 功能接口 Supplier提供了一个抽象方法T get 该方法不接受任何参数并返回一个对象。 这就是为什么我们的表达式只是对我们要使用的集合构造函数的调用 () - new TreeSetString()地图 map方法非常简单。 当您要在其他某种类型的对象中转换一个集合的每个元素时可以使用它这意味着将一个集合的每个元素映射到另一种类型的元素。 让我们的示例更进一步让我们尝试以下情形给定Person对象的集合让我们获得一个完全不同的集合该集合仅包含女性对象名称如Strings全部使用大写字母。 概括起来除了使用filter和collect来将我们所有的女性对象分离在自己的集合中之外我们还将使用map方法将每个女性Person对象转换为其String表示形式名称用大写字母表示 这是代码 ListPerson persons thisMethodReturnsPersons();ListString names persons.stream().filter(p - p.getSex().equals(F)).map(p - p.getName().toUpperCase()).collect(Collectors.toList()); 用作map方法的参数的功能接口是Function 其唯一的抽象方法R applyT t将一个对象作为参数并返回不同类型的对象。 这就是map的确切含义拿一个Person并变成String 。 forEach和forEachOrdered 也许最简单的方法是forEach和forEachOrdered提供访问流中每个元素的方法例如在遇到控制台时打印每个元素。 两者之间的主要区别是第一个不保证“相遇顺序”第二个不保证。 流是否具有“相遇顺序”取决于它的始发集合以及在其中执行的中介操作。 源自列表的 流具有预期的定义顺序。 这次功能接口是Consumer 其抽象方法void acceptT t接受单个参数并且不返回任何内容 ListPerson persons thisMethodReturnsPersons();//print without any encounter order guarantee
persons.stream().forEach(p - System.out.println(p.getName()));//print in the correct order if possible
persons.stream().forEachOrdered(p - System.out.println(p.getName())); 请记住 forEach和forEachOrdered 也是终端操作 您不需要内心地知道这一点只需在需要时在javadocs中进行查找即可 min和max 使用lambda表达式查找集合的最小和最大元素也变得容易得多 。 使用常规算法这是一种既简单又令人讨厌的例程。 让我们获取Person对象的集合并在其中找到最年轻和最老的人 ListPerson persons thisMethodReturnsPersons();//youngest using min()
OptionalPerson youngest persons.stream().min((p1, p2) - p1.getAge().compareTo(p2.getAge()));//oldest using max()
OptionalPerson oldest persons.stream().max((p1, p2) - p1.getAge().compareTo(p2.getAge()));//printing their ages in the console
System.out.println(youngest.get().getAge());
System.out.println(oldest.get().getAge()); min和max方法也采用一个功能接口作为参数只有这一点并不新鲜 Comparator 。 ps 如果您正在阅读本文但不知道“比较器”是什么我建议您退后一步尝试使用Java基础知识然后再使用lambdas。 上面的代码还有一些我们之前从未见过的东西即Optional类。 这也是Java 8中的新功能我不会详细介绍它。 如果您感到好奇请点击此链接。 使用新的静态方法Comparator.comparing可以达到相同的结果该方法采用一个Function并充当创建比较器的实用程序 //min()
OptionalPerson youngest persons.stream().min(Comparator.comparing(p - p.getAge()));//max()
OptionalPerson oldest persons.stream().max(Comparator.comparing(p - p.getAge()));关于collect和Collector的更多信息 使用方法collect使我们能够进行一些非常有趣的操作以及一些内置的Collector的帮助 。 例如可以计算所有Person对象的平均年龄 ListPerson persons thisMethodReturnsPersons();Double average persons.stream().collect(Collectors.averagingDouble(p - p.getAge()));System.out.println(A average is: average); Collector类中有3种方法可以朝这个方向提供帮助每种方法特定于一种数据类型 Collectors.averagingInt 整数 Collectors.averagingLong longs Collectors.averagingDouble 双精度 所有这些方法都返回一个有效的收集器 该收集器可以作为参数传递给collect 。 另一个有趣的可能性是能够将一个集合stream划分为两个值集合。 当我们专门为女性“ Person”对象创建新集合时我们已经做过类似的事情但是我们的原始集合仍然将男性和女性对象混合在一起。 如果我们想将原始收藏划分为两个新收藏一个仅包含男性另一个则包含女性该怎么办 为了实现这一点我们将使用Collectors.partitioningBy ListPerson persons thisMethodReturnsPersons();//a Map Boolean - ListPerson
MapBoolean, ListPerson result persons.stream().collect(Collectors.partitioningBy(p - p.getSex().equals(M)));//males stored with the true key
ListPerson males result.get(Boolean.TRUE);//females stored with the false key
ListPerson females result.get(Boolean.FALSE); 上面显示的Collectors.partitioningBy方法通过创建一个包含两个元素的Map来工作一个元素存储为键“ true” 另一个存储为“ false”键。 由于它采用Predicate类型的功能接口 其返回值是一个boolean值 因此其表达式的值为“ true”的元素将进入“ true”集合而那些值为“ false”的元素将进入“ false”集合。 为了解决这个问题让我们假设另一个场景其中我们可能希望按年龄对所有Person对象进行分组。 看起来就像我们对Collectors.partitioningBy所做的一样只是这次不是一个简单的true / false条件而是我们由年龄决定的条件。 小菜一碟我们只使用Collectors.groupingBy //Map Age - ListPerson
MapInteger, ListPerson result persons.stream().collect(Collectors.groupingBy(p - p.getAge())); 如果没有lambda您将如何做 考虑一下让我头疼。 性能与并行性 在本文的开头我提到使用lambda表达式的优点之一是能够并行处理集合这就是我接下来要说明的内容。 令人惊讶的是没有什么可显示的。 为了使所有先前的代码成为“并行处理”我们需要做的就是更改一个方法调用 ListPerson persons thisMethodReturnsPersons();//sequential
StreamPerson s1 persons.stream();//parallel
StreamPerson s2 persons.parallelStream(); 而已。 只需将对stream的调用更改为parallelStream即可进行并行处理。 所有其他链接的方法调用均保持不变。 为了演示使用并行处理的区别我使用最后一个代码示例进行了测试该示例按年龄将所有Person对象分组。 考虑到2000万个对象的测试数据这是我们得到的 如果将不带lambda的“老派”方法与顺序lambda处理stream进行比较 可以说是平局。 另一方面 parallelStream的速度似乎快三倍。 只有4秒。 那是300的差异。 注意这绝不意味着您应该并行进行所有处理 除了显而易见的事实即我的测试过于简单以至于不能盲目地考虑之外在选择并行处理之前还必须考虑到并行性存在固有的开销将集合分解为多个集合然后再次合并以形成最终结果这一点很重要。 。 话虽这么说如果没有相对大量的元素那么并行处理的成本可能不会得到回报。 在不加选择地使用parallelStream之前请仔细分析。 好吧我想这就是全部。 当然涵盖所有内容是不可能的需要整本书但是我认为这里显示了很多相关方面。 如果您有什么话要发表评论。 编码愉快 翻译自: https://www.javacodegeeks.com/2015/03/java-8-lambda-expressions-tutorial.html
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/921867.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!