初步了解函数编程的面貌,并接触基础的使用。如果可以的话,总结一篇自己对异步编程、同步编程的认识与理解
什么是函数式编程
函数式编程是一种范式,在函数式语言中函数作为第一对象,在语言中他可以作为变量、参数、返回值。函数式语言注重描述而非具体的执行步骤,sql语句就算是描述性语言。
函数式编程的几大特性:
- 确定性:变量中的值不会存在不确定的改变原,避免了并发过程中可能产生的问题
- 闭包
- 惰性求值
- 部分应用
- 结合率
- 柯里华
- 高阶函数:
Lamada表达式:
方法的要素:方法签名(参数、返回值)、方法体、名字
语法糖:类型可推导、只有一行可省略大括号、只有一个参数可省略小括号:
(int a,int b)_>{return a+b;}
(a,b)->a+b
a->a*a
()->1
函数式接口的基本用法:
@FunctionalInterface注解是否添加没有影响但是如果添加了的话在编译器会检查接口中是否只有一个没有被实现的方法:
@FunctionalInterface
public interface IPersion {
public void helloSelf(String name);
public default void helloInterface1(){
System.out.println("Hello,helloInterface1");
}
public default void helloInterface2(){
System.out.println("Hello,helloInterface2");
}
}
@Test
public void test01(){
//函数式接口测试
iPersion.helloSelf("Lisi");
iPersion.helloInterface1();
iPersion.helloInterface2();
}
Java内置的几种函数式接口:
@Test
public void test02(){
//自己写法
Function<Integer,Integer> functionSelf = (b)->b*b*b-3;
Consumer<String> consumerSelf = (String name)->{
System.out.println("Hello,"+name);
};
Supplier<Staff> supplierStaff = ()->{
Staff staff = new Staff("Test", (int) new Date().getTime());
return staff;
};
System.out.println(functionSelf.apply(3));
consumerSelf.accept("LiuMenglei");
supplierStaff.get().staffRec();
}
@Test
public void test03(){
//简化写法
Consumer run = System.out::println;//run.accept(xx);
Supplier<Integer> supplier = () -> 1;
Consumer<?> consumer = a -> {};
Predicate<?> predicate = a -> true;
Function<?, Boolean> fn = a ->true;
run.accept("LiuMenlgei");//LiuMenglei
System.out.println(supplier.get());//1
System.out.println(predicate.test(null));//true
System.out.println(fn.apply(null));//true
}
a->b->c->d代表什么?
方法引用
应有Java中已经有的方法,方法引用的要素需要能够确定使用那个方法,这其中应该包括类名、方法名
public class Staff {
private String name;
private int age;
public void hello(){
System.out.println("Hello,everyBody!!!");
}
public Staff(String name){
System.out.println("这里是有参构造函数,参数name为:"+name);
}
public void staffRec(){
System.out.println("name:"+name+",age:"+age+2);
}
。。。。。。。。。。。。。
}
@Test
public void test04(){
//方法引用测试
Supplier<Staff> supplier = Staff::new;
supplier.get().hello();//Hello,everyBody!!!
Function<String,Staff> function = Staff::new;
function.apply("LiuMenglei");//这里是有参构造函数,参数name为:LiuMenglei
Staff staff = new Staff("LiuShu",34);
//既没有输入也没有输出
Runnable runnable = staff::staffRec;
runnable.run();//name:LiuShu,age:342
}
方法引用的指定类型任意调用
函数接口装换
由于Java是强类型,在某些场合下我们并不要求函数签名完全一致,这时候就可以进行如下的类型装换:
- 忽略输入:Function<-Supplier
- 忽略返回:Consumer<-Function
- 忽略输入和返回:Runnable<-Supplier
public class Utils {
//将一个Supplier装换为Function,Function的输入与他的输出无关
//忽略输入:将Supplier装换为Funtion的时候,调用的传参以及返回值标准是按照Function来的,这时候有输入
//但是实际调用的Supplier是不需要输入的所以说这时候就是我们说的忽略输入
public static <T,R> Function<T,R> S2F(Supplier<R> supplierFunction){
//这种写法是不行的,显示为不能够成功转换
//ClassCastException: java.lang.Integer cannot be cast to java.util.function.Function
//return (Function<T, R>) supplierFunction.get();
return any->supplierFunction.get();
}
//将Consumer装换为Runnable
//忽略输入和返回:
public static <T> Runnable S2R(Supplier<T> supplierFunction){
return ()->supplierFunction.get();
}
//将Function转换为Consumer
//忽略返回:转换后的类型为Consumer但是实际的调用为Function,Function实际上是有返回的
//但是在Consumer的调用表现上并没返回
public static<T,R> Consumer<T> F2S(Function<T,R> function){
return any->function.apply(any);
}
public static void main(String[] args) {
Supplier<Integer> intSupplier = ()->{
System.out.println("运行了intSupplier函数");
Random random = new Random();
return random.nextInt(100)+1;
};
Function<Object,Integer> randomInterge = S2F(intSupplier);
//apply中输入任何参数均可,他的值与输出无关
System.out.println(randomInterge.apply("fjalksdfj"));
Runnable runnable = S2R(intSupplier);
//在这里的运行中intSupplier虽然得到了执行,但是并没有办法输出返回值
runnable.run();
Function<Integer,Integer> function = a->{
System.out.println("输入的值为:"+a);
return a*a;
};
Consumer<Integer> integerConsumer = F2S(function);
integerConsumer.accept(10);
}
}
特殊的void-compatibility 规则:
如果lambda是一个语句表达式,那么即使该lambda有返回值也可以赋值给返回值 签名为void的函数
//在lamada表达式中如果有return,但是却只有一条语句,这时候可以将此表达式赋给一个没有返回值的类型
//虽然知道可以这样写但是却不知道他的意义何在,能应用在何处
Consumer<Integer> consumer = a->a++;
Runnable runnable1 = ()->Integer.getInteger("11");
consumer.accept(1);
Stream的创建与接口分类
@Test
public void streamTest(){
//静态数据创建
Stream staticStream = Stream.of(1,2,3,4,5,6,7,8);
staticStream.forEach(System.out::println);
Stream.of(2,2,2,2,2,2,2,2,2,2).forEach(System.out::println);
//通过集合的形式创建
ArrayList<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(4);
integers.add(5);
integers.stream().map(a->a+3).filter(a->{
return a>3;
}).forEach(System.out::println);
//动态创建
Random random = new Random();
//public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
Stream.iterate(23 , a-> random.nextInt(a) ).limit(100).filter(a->{return a>50;})
.forEach(System.out::println);
//public static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random).limit(1000).map(a->new Double(a*100).intValue())
.filter(a->{return a>50;}).forEach(System.out::println);
}
Stream的状态可分为Intermediate、Terminal两个状态,不同的API分属与这两个中状态或是说,有些API会造成Intermediate往Terminal的转变,在下面分类中标红的是尚未实现的:
Intermediate:filter、distinct、skip、limit、map、flatMap、sorted
Terminal:count、sum、collect、reduce、forEach、anyMatch、allMatch、noneMath
Stream接口实践
@Test
public void test2(){
Random random = new Random();
//如果random.nextInt(a<1?23:a)中不做三元运算判断的话则会造成
// java.lang.IllegalArgumentException: bound must be positive
//这是因为如他的没入参都是来自于上一个产生的,而非是固定的23
Stream.iterate(23 , a-> random.nextInt(a>0?a:23) ).limit(100).map(a->a+1).distinct()
.forEach(System.out::println);
System.out.println(Stream.iterate(23 , a-> random.nextInt(a<1?23:a) ).limit(100).map(a->a+1).distinct()
.count());
//跳过前面的第n个元素,输出的是5——10
Stream.iterate(1 , a->a+1).limit(10).skip(4).forEach(System.out::println);
//flatmap方法的使用
}
入门三板斧
函数式编程推荐书籍:SICP
说函数是编程的三板斧之前应该要了解一下Stream的相关特性,我们通常建Stream理解成是一个集合的概念这是错误的,Stream相对于List有着如下的特性:
- 无限性:Stream是可以不断的添加元素进去,他的这一特性也体现在他的接口分为两个阶段:Intermediate、Terminal。简而言之就是,Intermediate阶段Stream还是不断的接受输入的,但是如果运行了Terminal阶段的接口的话Stream就不再接受新的元素进入
- 可能延迟处理
- 可并行处理:
在上面的代码中已经涉及到三板斧中的filter、map,还剩下一个reduce,这也是函数式编程中一个很重要的API,接下来着重介绍这最后的一板斧:
public class TransducerUtils {
//模拟实现reduce
public static <T,R> R reducer(R initData, Collection<T> datas, BiFunction<R,T,R> accumulator){
R ret = initData;
for (T data:datas){
ret = accumulator.apply(ret,data);
}
return ret;
}
//采用reduce模拟实现sum操作
public static int sum(Collection<Integer> datas){
return datas.stream().reduce(0,(acc,cur)->acc+cur);
}
//用reduce模拟实现max的操作_public static int max(int a, int b)
public static int max(Collection<Integer> datas){
return datas.stream().reduce(0,Math::max);
}
//用reduce模拟实现toList的操作
public static<T> List<T> toList(Stream<T> streamSource){
List<T> ret = new ArrayList<>();
return streamSource.reduce(ret,(acc,cur)->{acc.add(cur);return acc;}
,(list1,list2)->{list1.addAll(list2);return list1;});
}
//模拟map的实现
public static<T,R> List<R> map(List<T> list,Function<T,R> mapFun){
List<R> ret = new ArrayList<>();
return list.stream().reduce(ret,(acc,curr)->{
R newValue = mapFun.apply(curr);
acc.add(newValue);
//添加这步之后再运行的过程中会出现
// List-ret的大小为:1
//List-ret的大小为:2
//List-ret的大小为:3,这可以理解成他讲ret作为了一个中间变量
System.out.println("List-ret的大小为:"+ret.size());
return acc;
},(list1,list2)->{
list1.addAll(list2 );
return list1;
});
}
//模拟filter的实现
public static <T, R> List<T> filter(List<T> list, Predicate<T> p) {
List<T> ret = new ArrayList<>();
return list.stream().reduce(ret, (acc, curr)->{
if (p.test(curr)) {
acc.add(curr);
}
return acc;
},
(list1, list2) -> {list1.addAll(list2); return list1;} );
}
public static void main(String[] args) {
//测试自己写的map
ArrayList<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
map(integers,a->a+1).stream().forEach(System.out::println);
}
}