java函数式编程
前言
2014年,Oracle发布了Java8新版本。这对java来说是一个里程碑式的版本。他最主要的改进就是增加了函数式编程的功能(为了解决java程序总是冗长的问题),或许会感到奇怪,函数式编程和并发似乎没什么关系,但是java中与并发相关的API的实现,却是以函数式编程的范式来实现的。所以为了更好的理解这些功能,需要先学习下函数式编程。
java8的函数式编程一些特点
函数作为一等公民
我理解的,一句话来总结就是函数可以作为另一个函数的参数或者返回值吧!。如下面一段Javascript代码:
function f1(){ var n=1; function f2(){ alert(n); } return f2; } var result= f1(); result();//1
上述代码f1返回了f2并赋值给了result,此时result就是一个函数,指向f2,调用result就会打印n的值
无副作用
函数的副作用指的是函数除了返回值外,还修改了函数外部的状态,不如修改了一个全局变量。可以想象,这样当系统出现故障时,我们很难判断问题是由哪一个函数引起的,对调试和追踪是没有好处的。如果函数都是显式函数,那么函数的执行显然不会收到外部或者全局信息的干扰,有利于调试和追踪。所谓显式函数是指函数与外界交换数据的唯一渠道就是参数和返回值,显式函数不会读取或者修改函数的外部状态。而与之对应的隐式函数还会读取外部信息或者修改外部信息。
然而 实际上完全的无副作用是不可能是实现的,系统总是需要获取或者修改外部信息的,同时,模块之间的交互也极有可能是通过共享变量进行的。因此大多数的函数式编程都允许副作用的存在,如Clojure等,但是与面向对象相比,这种函数调用的副作用,在函数式编程里,需要进行一些有效的限制。
申明式的(Declarative)
函数式编程是申明式的的编程方式。相对与命令式而言,命令式的·程序者总是喜欢使用大量的 可变对象和指令。我们总是喜欢创建对象和变量,并且修改他们的状态或值,或者喜欢提供一系列指令要求程序执行。而在函数式编程中,那些细节的指令将会更好的被函数库所封装,我们只需要提出我们的要求,申明我们的用意即可。
下面,我们来看下两种方式的打印数组的代码:
//命令式 public static void imperative(){ int [] array={1,2,3,4}; for(int i=0;i<array.length;i++){ System.out.println("array[i]"); } }
申明式
public static void declarative(){ int [] array={1,2,3,4}; Arrays.stream(array).foreach(System.out::println); }
不变的对象
在函数式编程中几乎所有的对象都不会被轻易的i修改。下面我么看个例子来裂解:
static int [] ar={1,2,3}; Arrays.stream(ar).map((x)->x=x+1).foreach(System.out::println); System.out.println(); Arrays.stream(ar).foreach(System.out::println);
代码第二行看起来对数组每个元素进行了加一操作,但通过最后一行打印元素时会发现,数组成员并没有发生变化。
在使用函数式编程的时候,这种状态几乎是一种常态,几乎所有的对象都拒绝被修改。这非常类似与不变模式。
易于并行
由于对象都处于不变状态,因此函数式编程更加易于并行。不需要同步,也没有锁机制,其性能也会比较好。
更少的代码
不难理解,函数式编程的范式更加紧凑而且简洁。
函数式编程的基础
FunctionalInterface注解
java8提出了函数式接口的定义。简单来说,函数式接口,就是之定义了单一抽象方法的接口。如下面的定义:
@FunctionalInterface public static interface IntHandler{ void handle(int i); }
注释@FunctionalInterface表示这是一个函数式接口,该注释与@Override注释类似,不管该方法是否标注了该注释,只要满足重载或者函数式接口的定义,编译器就会把它看做重载方法或者函数式接口。
需要注意的是,并不是函数式接口只能有一个方法,首先接口运行存在实例方法,其次,任何被java.lang.Object实现的方法,都不能视为抽象方法。
比如下面 也是一个标准的函数式接口:
@FunctionalInterface public static interface IntHandler{ void handle(int i); boolean equals(Object obj); }
接口默认方法
在java8之前,接口只能包含抽象方法。java8之后,接口还可以包含若干个实例方法,是的java8有了类似多继承的能力。一个对象实例,将拥有来自不同接口的实例方法。
lambda表达式
lambda表达式可以说是函数式编程的核心。lambda表达式就是匿名函数,他是一段没有函数名的函数体,可以直接作为参数传递给调用者。
下面看一段lambda表达式的使用:
List<Integer> numbers=Arrays.aslist (1,2,3,4); numbers.forEach((Integer value) -> System.out.println(value));
上述代码遍历的输出列表的元素。
和匿名对象一样,lambda表达式也可以访问外部的局部变量,如下:
final int num=2; Function<Integer,Integer> stringConverter = (from) -> from*num; //num++; System.out.println(stringConverter.apply(3));
上述代码可以编译通过,输出结果6,与匿名内部对象一样,在这种情况外部的num变量必须申明为final,才能保证lambda表达式合法的访问它。
但是对lambda表达式而言,即使去掉final,程序依然会编译通过。但是我们就不能修改num的值了,java8会自动将lambda表达式中用到的变量视为final。
方法引用
方法引用是java8中提出的用来简化lambda表达式的一种手段。它通过类名和方法名来定位一个静态方法和实例方法。
方法引用在java8中使用的非常灵活。总的来说,分为以下几种:
- 静态方法引用:ClassName::methodName
- 实例上的实例方法引用:instanceReference::methodName
- 超类上的实例方法引用: super::methodName
- 类型上的实例方法引用: ClassName::methodName
- 构造方法引用: Class::new
- 数组构造方法引用: TypeName[]::new
总结来说 一般::前面表示类名或者实例名,后半部分表示方法名,构造函数用new表示。
应该容易理解,此处就不在用例子说明了。
java函数式编程的简单实践
下面我们以一个java8流的例子。来简单显示java函数式编程:
import java.lang.reflect.Array; import java.util.Arrays; import java.util.function.IntConsumer; public class Funtionpr { static int [] arr= {1,2,3,4}; /** * @param args */ /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub // for (int i:arr) { // System.out.println(i); // } // Arrays.stream(arr).forEach(new IntConsumer() { // @Override // public void accept(int value) { // System.out.println(value); // } // }); Arrays.stream(arr).forEach(( x)->System.out.println(x) ); } }
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java 中static与final使用
在java开发中,我们经常会用到static与final关键字,看过很多遍,总是会忘记,下面来总结一下 static关键字的使用 1.修饰成员变量 用static关键字修饰的变量称之为静态变量。而静态变量与非静态变量的区别主要在于: 静态变量:被所有的对象所共享,在内存中只存在一个副本,只在类初次加载时被初始化。 非静态变量:有对象所拥有,在对象创建时初始化,存在多个副本,各个对象拥有的副本互不影响。 ### 2.修饰方法 static方法称为静态方法。静态方法是不依赖于任何对象就可以进行访问的,由于都不依赖任何对象,故不存在this一说。需要注意的是静态方法只能访问或调用静态变量或者静态方法,不能访问类的任何非静态成员变量和方法。但是,在非静态方法中可以访问静态成员变量与方法。 3.修饰代码块 static还有个特殊用处就是可以用来修饰代码块,static代码块可以放在类中的任何地方。并且只会在类初次被加载时,按static代码块的顺序执行一次。static代码块可以用来优化程序性能,正式因为他只会在类初次加载时,执行一次。 4.static需要注意的一些点 静态成员变量虽然独立于对...
- 下一篇
js数组去重五种方法
js数组去重五种方法 今天来聊一聊JS数组去重的一些方法,包括一些网上看到的和自己总结的,总共5种方法(ES5)。 第一种:遍历数组法 这种方法最简单最直观,也最容易理解,代码如下: var arr = [2, 8, 5, 0, 5, 2, 6, 7, 2] var newArr = [] for (var i = 0; i < arr.length; i++) { if (newArr.indexOf(arr[i]) === -1) { newArr.push(arr[i]) } } console.log(newArr) // 结果:[2, 8, 5, 0, 6, 7] 这种方法很好理解,利用了indexOf()方法(indexOf()方法如果查询到则返回查询到的第一个结果在数组中的索引,如果查询不到则返回-1)。先创建一个新的空数组用来存储新的去重的数组,然后遍历arr数组,在遍历过程中,分别判断newArr数组里面是不是有遍历到的arr中的元素,如果没有,直接添加进newArr中,如果已经有了(重复),那么不操作,那么从头到尾遍历一遍,正好达到了去重的目的。 第二种:数组...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- 设置Eclipse缩进为4个空格,增强代码规范
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题