Java8新特性之Lambda表达式初探

目录

 

●为什么要用Lambda表达式?

●Lambda表达式拆解

●Lambda表达式中引用的变量

●Java8内置的函数式接口

●小结


●为什么要用Lambda表达式?

在本文写作时,Java8其实已经推出4年有余,Lambda表达式已经不算是一个“新”东西了,很多语言,诸如C#、Python都支持了这个特性。笔者在工作中发现很多非互联网公司的传统项目,还是在用Java7进行开发,究其根本是对新技术学习成本和使用成本的控制,这个暂且按下不表。未来,工作中新的一个项目会基于Java8进行开发,笔者通过网络比较一了下两个JDK版本的差异,最大的不同,也是大家津津乐道的Java8对Lambda表达式的支持。

 

第一次接触这个特性的时候,走了一些弯路,直接看的《JAVA8函数式编程》这本书,感觉概念上有点抽象,后来在CSDN、知乎等平台看了一些碎片化的文章,对Lambda有了具象的了解。本文将从笔者,也是大部分初学Lambda表达式的同学的角度出发,提出问题“是什么”。

 

其实,在目前阶段,不深究其原理的话,要解答“是什么”,不如来看看“为什么要用Lambda表达式”:

 

public class App
{
    public static void main( String[] args )
    {
        //java7 传统写法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(String.format("This is a thread(%s) via java7...",Thread.currentThread().getId()));
            }
        }).start();

        //java8 Lambda写法
        new Thread(() -> System.out.println(String.format("This is a thread(%s) via java8...",Thread.currentThread().getId()))).start();
    }
}

代码很简单,两种写法作用都是主线程中开启新的一个子线程,打印一句话。前者用了经典教科书上创建线程的写法之一;后者则用了Lambda表达式

() -> System.out.println(String.format("This is a thread(%s) via java8...",Thread.currentThread().getId()))

实现同样功能。

 

发现了吗?利用Java8中Lambda表达式将原本匿名内部类的一段代码简化为了1行。这也是笔者认为的Lambda最重要也是能最先具象展现给各位程序员的用途。现在还看不懂它是怎么做到的,没关系,请记住这句话:将原本匿名内部类的一段代码简化。接下来第二部分我们来重点看看Lambda表达式的构成及各部分的含义。

 

●Lambda表达式拆解

以第一部分为例:

new Thread(() -> System.out.println(String.format("This is a thread(%s) via java8...",Thread.currentThread().getId()))).start();

我们知道,Thread类构造方法的参数应该是一个接口Runnable的实现类,实现其run()方法。因此,我们可以认为Lambda表达式整体其实就是一个接口的实现类。表达式“->”的右边即实现该接口方法的具体代码。为了防止引起歧义,要求该接口必须有且仅有一个方法待实现,这样的接口有一个名字,叫做“函数式接口(Functional Interface)”

 

这样的接口多吗?事实上,还真不少。大家开发中见过的Runnable接口(仅一个run()方法)、Comparable接口(仅一个compareTo(仅一个Object o)方法)、Comparator接口(仅一个compare(T o1, To2)方法)、Swing中ActionListener接口(仅一个actionPerformed(ActionEvent e)方法)……数不胜数,这还不包括Java8中所提供的4种内置函数式接口。

 

说完了Lambda表达式“->”右边的含义,我们再来看看“->”左边代表什么。本例中待实现的run()方法是没有参数的,但实际中,很多方法都是带参的。没错!在Lambda表达式中,方法的参数正是写在了“->”左边,如果没有歧义,你甚至可以不写明该参数的类型,交由编译器结合上下文进行推断。

 

至此,Lambda表达式最基础的知识点你已经了解了,总结一下:

  1. Lambda表达式本质上是函数式接口的实现类,可以简化代码的书写;
  2. Lambda表达式“->”右端是实现方法的具体代码;
  3. Lambda表达式“->”左端是实现方法的参数,大时候可以不显示声明参数类型。

 

●Lambda表达式中引用的变量

值得注意的是,在Lambda表达式中,不能引用所在方法中的变量,必须是定义的final类型或无歧义可等效的final类型(不对变量重复赋值),可以看代码:

for(int i=0 ; i < 3 ;i++){
    new Thread(() -> System.out.println(String.format("开启第 %s 个线程"),i)).start();
}

这段代码是无法编译通过的,可以看到Lambda表达式中,变量i处有错误,提示:Variable used in lambda expression should be final or effectively final。这其实并不是Lambda所引起的,即使在传统的内部类写法中,用到这个变量i也会报错:variable "i" is accessed within inner class. Needs to be declared final or effectively final。

 

究其根本,这是因为JAVA的内部类所引用的变量其实是引用该变量的值,而不是变量本身。换句话说,可以理解为内部类引用外部的变量A时,其实是将这个变量A“复制”了一份,用“复制”出来的克隆体B。内部类中如果对外部变量进行赋值修改,你看上去修改的是A,其实是在修改B,对A并不会有任何影响。从代码表面去看,这显然引入了歧义。因此,JAVA设计的时候,就强制了内部类只能引用final类型的外部变量,即无法二次赋值。直接限制了不让你去修改,一(fei)了(chang)百(tou)了(lan)。

 

●Java8内置的函数式接口

刚才已经说了,Lambda表达式就是函数式接口的实现类,Java8也很贴心的为各位程序员提供了许多常用的函数式接口,位于java.util.function包下面,参数和返回值已经定义好,大家直接写实现就OK了。接口很多,记忆的话可以结合前缀,例如,Consumer接口的函数需提供一个参数,返回值为空,那BiConsumer接口的函数则需要提供两个参数,返回值为空。我们来看几个常用到的:

函数式接口 函数名 参数 返回值
Predicate test (T) boolean
Consumer accept (T) void
Function apply (T) R
Supplier get none T
UnaryOperator identity (T) T

BinaryOperator

apply (T,T) T
BiConsumer accept (T,U) void
BiFunction apply (T,U) R

 

以BiFunction接口为例,我们结合代码来看看具体应该怎么使用:

定义实体类UserInfo.java

@Entity
@Table(name = "user_info")
public class userInfo {
	private Integer userId;//主键id

	private String account;//用户账号
	private String password;//登录密码

	private String name;
	private String tel;
	private String email;

	@Id
	@SequenceGenerator(name = "generator_user_info", sequenceName = "S_user_info", allocationSize = 5)
	@GeneratedValue(strategy = GenerationType.AUTO, generator = "generator_user_info")
	@Column(name = "user_id", unique = true, nullable = false)
	public Integer getUserId() {
		return this.userId;
	}
	
	public void setUserId(Integer userId) {
		this.userId = userId;
	}
	
	@Column(name = "account", nullable = false, length = 64)
	public String getAccount() {
		return this.account;
	}
	
	public void setAccount(String account) {
		this.account = account;
	}
	
	@Column(name = "password", nullable = false, length = 64)
	public String getPassword() {
		return this.password;
	}
	
	public void setPassword(String password) {
		this.password = password;
	}
	
	@Column(name = "name", nullable = true, length = 256)
	public String getName() {
		return this.name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	@Column(name = "tel", nullable = true, length = 64)
	public String geTtel() {
		return this.tel;
	}
	
	public void setTel(String tel) {
		this.tel = tel;
	}
	
	@Column(name = "email", nullable = true, length = 64)
	public String getEmail() {
		return this.email;
	}
	
	public void setEmail(String email) {
		this.email = email;
	}
	
}

实现对登录用户的账号密码匹配:

//对登录用户进行校验
BiFunction biFunction = (account, password) -> {
     if(null == account || "".equals(account)){
          return "Please input account!";
     }
     if(null == password || "".equals(password)){
          return "Please input password!";
     }
     if(null == getUserInfoByAccount(account)){
          return "User is not exist!";
     }
     userInfo userinfo = getUserInfoByAccount(account);
     if(password != userinfo.getPassword()){
          return "Password is wrong!";
     }
     String result = String.format("User %s login. Tel is %s",userinfo.getName(),userinfo.geTtel());
          return result;
};

System.out.println(biFunction.apply("admin","123456"));

这里,我们就通过Lambda表达式实现了BiFunction函数式接口中的方法。

 

●小结

至此,相信你对Java8中的Lambda表达式已经有了一定程度的了解,后续,笔者将会介绍一下Lambda表达式学习中的会遇到的流和方法引用的相关知识。今天,你学会了吗?

你可能感兴趣的:(JAVA)