Java Lambda函数式编程

## 简介

Lambda表达式(Lambda expressions)是Java 8中的一个新特性,用于函数式编程。为了快速创建一个接口的实现,一般我们会使用到匿名类(anonymous classes),比如:

 public class Anonymous {
    public interface TaskInterface {
        void run();
        void preRun();
    }
    
    public void createAnonymous(){
        TaskInterface task = new TaskInterface() {
            
            @Override
            public void run() {
                System.out.println("Task is running...");
            }
            
            @Override
            public void preRun() {
                System.out.println("Before the task is running...");
            }
        };
        task.preRun();
        task.run();
    }
    
    public static void main(String[] args) {
        new Anonymous().createAnonymous();
    }
}

对于仅有一个方法的接口,此时使用匿名类不是那么明智了,我们可以使用Lambda更简洁地实现,将函数作为参数传递给方法,比如:
首先定义一个函数式接口(functional interface),接口:

@FunctionalInterface
public interface TaskInterface {
    public void run(String message);
}

创建一个类,调用该接口方法:

public class TaskExecuter {
    public void execut(TaskInterface task) {
        task.run("Task is running...");
    }
}

将函数作为参数传递给方法执行:

public class Lambda {
    public static void main(String[] args) {
        new TaskExecuter().execut((String message) -> System.out.println(message));
    }
}

运行Lambda类,输出:

Task is running...

函数式接口

函数式接口(functional interface)是仅有一个抽象方法的接口,但是可以有一个或多个默认方法或者静态方法。因为函数式接口仅有一个抽象方法,所以可以使用能够Lambda表达更加简洁的实现。
函数式接口很大程度区别于参数和返回值,你可以根据需求声明自己的接口,但Java 8也提供了现成,从参数和返回值可以判断这些是否合适自己:

接口方法适用条件
Function <T,R>R apply(T t);有参数和返回值
BiFunction<T,U,R>R apply(T t, U u);有两个参数和返回值
BinaryOperator<T>T apply(T t1, T t2);有两个参数和返回值,且他们的类型相同,继承自 BiFunction<T,U,R>
Consumer<T>void accept(T t);有参数无返回值
BiConsumer<T,U>void accept(T t, U u);有两个参数无返回值
Predicate<T>boolean test(T t);有参数,且返回值是boolean类型
BiPredicate<T,U>boolean test(T t, U u);有两个参数,且返回值是boolean类型
Supplier<R>R get();有返回值但无参数
java.util.function包下还有很多其他函数式接口,都是各种返回值或者参数为基本类型的情景。

Lambda表达式语法(Syntax of Lambda Expressions)

  • 参数
    用括号将参数包裹起来,多个参数用“,”分割,与普通方法声明参数相同,参数类型和数量应当与函数式接口抽象方法保持一致:
(String message) -> System.out.println(message)
(String m1,String m2) -> System.out.println(m1+m2)

如果参数只有一个,括号和参数类型可以省略;如果参数类型相同,可省略参数类型:

message-> System.out.println(message)

BiConsumer<String, String> function = (y1,y2)->System.out.println(y1+y2);
  • 使用箭头->
  • 实现部分,可以是简单的表达式或声明块。

变量

在Lambda表达式中,可以访问作用域中的变量(方法中的参数,成员变量),但是Lambda表达式变量没有屏蔽(Shadowing)作用, 简单的说,Lambda表达式不能对作用域的变量进行重新赋值,以下代码会出现编译错误提示,因为x = 0;使得x不再是final的。

Local variable x defined in an enclosing scope must be final or effectively final
public class Lambda {
	public void run(int x) {
		Consumer<String> function = (String y) -> {
			x = 0;
			System.out.println("x=" + x);
			System.out.println("y=" + y);
		};
	}
}

屏蔽(Shadowing)

public class ShadowTest {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {
            System.out.println("x = " + x);
            System.out.println("this.x = " + this.x);
            System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
        }
    }

    public static void main(String... args) {
        ShadowTest st = new ShadowTest();
        ShadowTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

运行输出:

x = 23
this.x = 1
ShadowTest.this.x = 0

例子中定义了3个x变量:1)ShadowTest中的成员变量 2)内部类FirstLevel成员变量 3)methodInFirstLevel方法参数。

methodInFirstLevel方法参数中的变量会遮蔽掉内部类FirstLevel成员变量,因此挡在methodInFirstLevel方法中使用x时,实在使用方法参数。
为了使用内部类FirstLevel成员变量,应当:

System.out.println("this.x = " + this.x);

应当通过类名来使用作用域更大的成员变量,以此分辨他们的归属,比如在methodInFirstLevel方法中使用ShadowTest中的成员变量:

System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);

方法引用(Method References)

在使用Lambda表达式中,很多时候我们仅仅是为了调用一个方法,比如前面使用的:

Consumer<String> consumer = msg -> System.out.println(msg);

方法引用可以更加简洁的书写:

Consumer<String> consumer = System.out::println;

方法引用(Method References)顾名思义就是调用一个方法,以此作为Lambda的实现体,至于能调用什么方法和怎么调用,一方面要遵循调用该方法的参数和返回参数应当与函数式接口的抽象方法保持一致,另一方面遵循语法规则:

类型模型例子
引用一个静态方法ContainingClass::staticMethodNameSystem.out::println
引用构造方法ClassName::newArrayList::new
引用实例方法containingObject::instanceMethodNameString str = new String(); Function<String, Integer> function = str::compareTo;
引用特定类型中的实例方法ContainingType::methodName

引用特定类型中的实例方法比较绕,是引用函数式接口的抽象函数的第一个参数类型的实例方法,引用过程类似于引用一个静态方法,但是该方法实际上是实例方法。假设现在有接口

public interface Finder {
    String find(String str, int start, int end);
}

此接口为了截取字符,可以使用Lambda这样实现:

Finder finder = String::substring;

等价于:

Finder finder = (String str, int start, int end) -> str.substring(start, end);

从上面例子可以看到,其实

Finder finder = String::substring;

的作用相当于调用了

(String str, int start, int end)

第一个参数的类型中的一个方法substring,并将其余的参数作为substring的参数。

另一个典型的例子是排序
假设有一个类IdName,对多个该对象进行排序:

class IdName{
	private String id;
	private String name;
	public IdName(String id,String name) {
		this.id = id;
		this.name = name;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

Arrays.asList(new IdName("He", "asd"), new IdName("QW", "Kl"), new IdName("OP", "GL")).stream()
				.sorted(Comparator.comparing(idName -> idName.getId())).forEach(idName -> {
					System.out.println(idName.getId());
				});

例子中根据IdName的id进行排序Comparator.comparing(idName -> idName.getId()),因为返回的结果为IdName.getId(),即调用参数的实例方法而且是直接返回的,因此可以使用方法引用调用该类的方法:

		Arrays.asList(new IdName("He", "asd"), new IdName("QW", "Kl"), new IdName("OP", "GL")).stream()
				.sorted(Comparator.comparing(IdName::getId)).forEach(idName -> {
					System.out.println(idName.getId());
				});

另外一个排序例子:

Arrays.asList("Hl", "WU", "Ao", "Uo", "Nu", "Ni")
     .stream()
     .sorted((s1, s2) -> s1.compareTo(s2))
	 .forEach(System.out::println);
Arrays.asList("Hl", "WU", "Ao", "Uo", "Nu", "Ni")
     .stream()
     .sorted(String::compareTo)
	 .forEach(System.out::println);

交流+V B423651

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值