Java8-函数编程基础

简介: 《基础系列》


在Java世界里面,面向对象还是主流思想,对于习惯了面向对象编程的开发者来说,抽象的概念并不陌生。面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象。现实世界中,数据和行为并存,程序也是如此,因此这两种编程方式我们都得学。

这种新的抽象方式还有其他好处。很多人不总是在编写性能优先的代码,对于这些人来说,函数式编程带来的好处尤为明显。程序员能编写出更容易阅读的代码——这种代码更多地表达了业务逻辑,而不是从机制上如何实现。易读的代码也易于维护、更可靠、更不容易出错。

在写回调函数和事件处理器时,程序员不必再纠缠于匿名内部类的冗繁和可读性,函数式编程让事件处理系统变得更加简单。能将函数方便地传递也让编写惰性代码变得容易,只有在真正需要的时候,才初始化变量的值。

面向对象编程是对数据进行抽象;函数式编程是对行为进行抽象。

核心思想: 使用不可变值和函数,函数对一个值进行处理,映射成另一个值。

对核心类库的改进主要包括集合类的API和新引入的流Stream。流使程序员可以站在更高的抽象层次上对集合进行操作。

lambda表达式

  • lambda表达式仅能放入如下代码: 预定义使用了 @Functional 注释的函数式接口,自带一个抽象函数的方法,或者SAM(Single Abstract Method 单个抽象方法)类型。这些称为lambda表达式的目标类型,可以用作返回类型,或lambda目标代码的参数。例如,若一个方法接收Runnable、Comparable或者 Callable 接口,都有单个抽象方法,可以传入lambda表达式。类似的,如果一个方法接受声明于 java.util.function 包内的接口,例如 Predicate、Function、Consumer 或 Supplier,那么可以向其传lambda表达式。
  • lambda表达式内可以使用方法引用,仅当该方法不修改lambda表达式提供的参数。本例中的lambda表达式可以换为方法引用,因为这仅是一个参数相同的简单方法调用。
list.forEach(n -> System.out.println(n)); 
list.forEach(System.out::println);  // 使用方法引用
        @pdai: 代码已经复制到剪贴板


然而,若对参数有任何修改,则不能使用方法引用,而需键入完整地lambda表达式,如下所示:

list.forEach((String s) -> System.out.println("*" + s + "*"));
        @pdai: 代码已经复制到剪贴板


事实上,可以省略这里的lambda参数的类型声明,编译器可以从列表的类属性推测出来。

  • lambda内部可以使用静态、非静态和局部变量,这称为lambda内的变量捕获。
  • Lambda表达式在Java中又称为闭包或匿名函数,所以如果有同事把它叫闭包的时候,不用惊讶。
  • Lambda方法在编译器内部被翻译成私有方法,并派发 invokedynamic 字节码指令来进行调用。可以使用JDK中的 javap 工具来反编译class文件。使用 javap -p 或 javap -c -v 命令来看一看lambda表达式生成的字节码。大致应该长这样:
private static java.lang.Object lambda$0(java.lang.String);
        @pdai: 代码已经复制到剪贴板


  • lambda表达式有个限制,那就是只能引用 final 或 final 局部变量,这就是说不能在lambda内部修改定义在域外的变量。
List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7});
int factor = 2;
primes.forEach(element -> { factor++; });
        @pdai: 代码已经复制到剪贴板


Compile time error : "local variables referenced from a lambda expression must be final or effectively final" 另外,只是访问它而不作修改是可以的,如下所示:

List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7});
int factor = 2;
primes.forEach(element -> { System.out.println(factor*element); });
        @pdai: 代码已经复制到剪贴板

分类

惰性求值方法

lists.stream().filter(f -> f.getName().equals("p1"))
        @pdai: 代码已经复制到剪贴板


如上示例,这行代码并未做什么实际性的工作,filter只是描述了Stream,没有产生新的集合

如果是多个条件组合,可以通过代码块{}

及早求值方法

List<Person> list2 = lists.stream().filter(f -> f.getName().equals("p1")).collect(Collectors.toList());
        @pdai: 代码已经复制到剪贴板


如上示例,collect最终会从Stream产生新值,拥有终止操作。

理想方式是形成一个惰性求值的链,最后用一个及早求值的操作返回想要的结果。与建造者模式相似,建造者模式先是使用一系列操作设置属性和配置,最后调用build方法,创建对象。

stream & parallelStream

stream & parallelStream

每个Stream都有两种模式: 顺序执行和并行执行。

顺序流:

List <Person> people = list.getStream.collect(Collectors.toList());
        @pdai: 代码已经复制到剪贴板


并行流:

List <Person> people = list.getStream.parallel().collect(Collectors.toList());
        @pdai: 代码已经复制到剪贴板


顾名思义,当使用顺序方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时,数组会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。

parallelStream原理:

List originalList = someData;
split1 = originalList(0, mid);//将数据分小部分
split2 = originalList(mid,end);
new Runnable(split1.process());//小部分执行操作
new Runnable(split2.process());
List revisedList = split1 + split2;//将结果合并
        @pdai: 代码已经复制到剪贴板


大家对hadoop有稍微了解就知道,里面的 MapReduce 本身就是用于并行处理大数据集的软件框架,其 处理大数据的核心思想就是大而化小,分配到不同机器去运行map,最终通过reduce将所有机器的结果结合起来得到一个最终结果,与MapReduce不同,Stream则是利用多核技术可将大数据通过多核并行处理,而MapReduce则可以分布式的。

stream与parallelStream性能测试对比

如果是多核机器,理论上并行流则会比顺序流快上一倍,下面是测试代码

long t0 = System.nanoTime();
//初始化一个范围100万整数流,求能被2整除的数字,toArray()是终点方法
int a[]=IntStream.range(0, 1_000_000).filter(p -> p % 2==0).toArray();
long t1 = System.nanoTime();
//和上面功能一样,这里是用并行流来计算
int b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray();
long t2 = System.nanoTime();
//我本机的结果是serial: 0.06s, parallel 0.02s,证明并行流确实比顺序流快
System.out.printf("serial: %.2fs, parallel %.2fs%n", (t1 - t0) * 1e-9, (t2 - t1) * 1e-9);
        @pdai: 代码已经复制到剪贴板


Stream中常用方法如下:

  • stream(), parallelStream()
  • filter()
  • findAny()findFirst()
  • sort
  • forEach void
  • map(), reduce()
  • flatMap() - 将多个Stream连接成一个Stream
  • collect(Collectors.toList())
  • distinct, limit
  • count
  • min, max, summaryStatistics

看下所有API:

网络异常,图片无法展示
|

常用例子

匿名类简写

new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();
// 用法
(params) -> expression
(params) -> statement
(params) -> { statements }
        @pdai: 代码已经复制到剪贴板


forEach

// forEach
List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
features.forEach(n -> System.out.println(n));
// 使用Java 8的方法引用更方便,方法引用由::双冒号操作符标示,
features.forEach(System.out::println);
        @pdai: 代码已经复制到剪贴板


方法引用

构造引用

// Supplier<Student> s = () -> new Student();
Supplier<Student> s = Student::new;
        @pdai: 代码已经复制到剪贴板


对象::实例方法 Lambda表达式的(形参列表)与实例方法的(实参列表)类型,个数是对应

// set.forEach(t -> System.out.println(t));
set.forEach(System.out::println);
        @pdai: 代码已经复制到剪贴板


类名::静态方法

// Stream<Double> stream = Stream.generate(() -> Math.random());
Stream<Double> stream = Stream.generate(Math::random);
        @pdai: 代码已经复制到剪贴板


类名::实例方法

//  TreeSet<String> set = new TreeSet<>((s1,s2) -> s1.compareTo(s2));
/*  这里如果使用第一句话,编译器会有提示: Can be replaced with Comparator.naturalOrder,这句话告诉我们
  String已经重写了compareTo()方法,在这里写是多此一举,这里为什么这么写,是因为为了体现下面
  这句编译器的提示: Lambda can be replaced with method reference。好了,下面的这句就是改写成方法引用之后: 
*/
TreeSet<String> set = new TreeSet<>(String::compareTo);
相关文章
|
4天前
|
缓存 监控 Java
Java Socket编程最佳实践:优化客户端-服务器通信性能
【6月更文挑战第21天】Java Socket编程优化涉及识别性能瓶颈,如网络延迟和CPU计算。使用非阻塞I/O(NIO)和多路复用技术提升并发处理能力,减少线程上下文切换。缓存利用可减少I/O操作,异步I/O(AIO)进一步提高效率。持续监控系统性能是关键。通过实践这些策略,开发者能构建高效稳定的通信系统。
|
4天前
|
IDE Java 开发工具
从零开始学Java Socket编程:客户端与服务器通信实战
【6月更文挑战第21天】Java Socket编程教程带你从零开始构建简单的客户端-服务器通信。安装JDK后,在命令行分别运行`SimpleServer`和`SimpleClient`。服务器监听端口,接收并回显客户端消息;客户端连接服务器,发送“Hello, Server!”并显示服务器响应。这是网络通信基础,为更复杂的网络应用打下基础。开始你的Socket编程之旅吧!
|
4天前
|
Java
Java Socket编程与多线程:提升客户端-服务器通信的并发性能
【6月更文挑战第21天】Java网络编程中,Socket结合多线程提升并发性能,服务器对每个客户端连接启动新线程处理,如示例所示,实现每个客户端的独立操作。多线程利用多核处理器能力,避免串行等待,提升响应速度。防止死锁需减少共享资源,统一锁定顺序,使用超时和重试策略。使用synchronized、ReentrantLock等维持数据一致性。多线程带来性能提升的同时,也伴随复杂性和挑战。
|
4天前
|
安全 Java 网络安全
Java Socket编程教程:构建安全可靠的客户端-服务器通信
【6月更文挑战第21天】构建安全的Java Socket通信涉及SSL/TLS加密、异常处理和重连策略。示例中,`SecureServer`使用SSLServerSocketFactory创建加密连接,而`ReliableClient`展示异常捕获与自动重连。理解安全意识,如防数据截获和中间人攻击,是首要步骤。通过良好的编程实践,确保网络应用在复杂环境中稳定且安全。
|
4天前
|
Java 数据安全/隐私保护
深入剖析:Java Socket编程原理及客户端-服务器通信机制
【6月更文挑战第21天】Java Socket编程用于构建网络通信,如在线聊天室。服务器通过`ServerSocket`监听,接收客户端`Socket`连接请求。客户端使用`Socket`连接服务器,双方通过`PrintWriter`和`BufferedReader`交换数据。案例展示了服务器如何处理每个新连接并广播消息,以及客户端如何发送和接收消息。此基础为理解更复杂的网络应用奠定了基础。
|
2天前
|
Java
Java并发编程:深入理解synchronized与ReentrantLock
【6月更文挑战第22天】本文将深入探讨Java并发编程中两个重要的同步机制:synchronized关键字和ReentrantLock类。我们将通过实例分析它们之间的差异,以及在实际应用中如何根据场景选择恰当的同步工具。
|
1天前
|
Java
轻松入门Java中的Lambda函数
轻松入门Java中的Lambda函数
|
1天前
|
存储 安全 Java
java编程SimpleDateFormat详解
java编程SimpleDateFormat详解
|
2天前
|
Java
Java并发编程中锁的释放
Java并发编程中锁的释放
12 1
|
13小时前
|
Java 机器人 数据库
Java中的Servlet编程:从基础到高级应用
Java中的Servlet编程:从基础到高级应用