Java语言本身也在不断变化,不过大部分变化并不十分明显,都是一些bug修复、安全性提升、性能提升或平台优化方面的。当然也有一些变化会影响到我们的日常使用,让我们开发更加方便和规范。
近几年来,最显著的变化应该是Lambda 表达式和模块化了。Lambda 表达式在Java 8中引入,模块化从Java 9开始引入,Java 11中成为正式标准。
Lambda 表达式可以简化那些参数是(或包含)只有一个方法的接口对象的方法的写法。
Lambda 表达式的写法是:
(参数列表) -> 表达式
或
(参数列表) ->{ 多条语句 }
有点拗口,具体用法我们看例子:
//只包含一个有参方法的接口
@FunctionalInterface
interface Computer{
int calc(int a,int b);
}
//只包含一个无参方法的接口
@FunctionalInterface
interface Person{
void intro();
}
public class lambdaDemo {
//加法计算
public static int add(int x,int y,Computer com){
return com.calc(x,y);
}
//减法计算
public static int sub(int x,int y,Computer com){
return com.calc(x,y);
}
//个人信息介绍
public static void introduce(Person p){
p.intro();
}
public static void main(String[] args) {
//使用匿名内部类的形式实现加法
System.out.println("10+20="+(add(10, 20, new Computer() {
@Override
public int calc(int a, int b) {
return a+b;
}
})));
//使用Lambda表达式实现加法
System.out.println("10+20="+add(10,20,(x,y)->x+y));
//使用Lambda表达式实现减法
System.out.println("50-30="+sub(50,30,(x,y)->x-y));
//使用Lambda表达式实现个人信息介绍
introduce(()-> System.out.println("大家好,我是张三"));
}
}
从程序中可以看出,使用Lambda表达式可以大大简化程序的写法。
但需要注意的是,Lambda并没有从根本上改变Java的语法和底层,主要作用就是简化代码,我们可以把它理解为一种“语法糖”——确实挺甜。
在后面的课程中,我们还会遇到可以使用Lambda表达式的地方。
Lambda表达式是为了简化代码,如果Lambda表达式的主体只有一条语句,而且这条语句调用了另一个方法,还可以使用::
来实现方法引用。
换个角度来说,Lambda表达式的本质是实现了接口中的方法,而方法引用的方式是通过一个已有的方法来实现这个接口中的方法。
一共有四种引用形式:
引用形式 | Lambda表达式 | 方法引用 |
---|---|---|
通过类名引用静态方法 | (x,y)->类名.静态方法名(x,y) | 类名::静态方法名 |
通过对象名引用方法 | (x,y)->对象名.方法名(x,y) | 对象名::方法名 |
通过类名引用普通方法 | (x,y)->对象名.普通方法名(x,y) | 类名::普通方法名 |
引用构造方法 | (x,y)->new 构造方法名(x,y) | 类名::new |
下面我们来看几个例子。
package tp01;
@FunctionalInterface
interface Calculator{
int calc(int x,int y);
}
class Arithmetic{
public static int add(int x,int y) {
return x+y;
}
}
public class LambdaDemo001 {
//通过Calculator对象对参数进行计算
public static void printResult(int x,int y, Calculator c) {
System.out.println(c.calc(x,y));
}
public static void main(String[] args) {
//使用匿名内部类实现
printResult(10, 20, new Calculator() {
@Override
public int calc(int x, int y) {
return Arithmetic.add(x, y);
}
});
//使用Lambda表达式,使用Arithmetic类中的add方法实现Calculator接口中的calc方法
//下面有两个写方法,上面为标准写法,下面为简化写法,两者作用完全相同
//printResult(10, 20, (x,y)->{return Arithmetic.add(x, y);});
printResult(10, 20, (x,y)->Arithmetic.add(x, y));
//使用方法引用
printResult(10, 20, Arithmetic::add);
}
}
Calculator
接口中定义了一个calc
方法,我们在使用它时,使用Arithmetic
中的add
方法来实现calc
方法。
如果类中的方法不是静态的呢?我们可以通过类的对象来引用方法,来看这个例子:
@FunctionalInterface
interface Calculator{
int calc(int x,int y);
}
class Arithmetic{
public int add(int x,int y) {
return x+y;
}
}
public class LambdaDemo002 {
//通过Calculator对象对参数进行计算
public static void printResult(int x,int y, Calculator c) {
System.out.println(c.calc(x,y));
}
public static void main(String[] args) {
Arithmetic a=new Arithmetic();
//使用Lambda表达式
printResult(10, 20, (x,y)->a.add(x, y));
//使用方法引用
printResult(10, 20, a::add);
}
}
如果想通过类名直接引用普通方法,则需要在接口的方法和主方法调用的静态方法中添加对实现了接口方法的方法所在的类的引用。比较拗口,我们看看例子:
@FunctionalInterface
interface Calculator{
int calc(Arithmetic a,int x,int y);
}
class Arithmetic{
public int add(int x,int y) {
return x+y;
}
}
public class LambdaDemo003 {
//通过Calculator对象对参数进行计算
public static void printResult(Arithmetic a,int x,int y, Calculator c) {
System.out.println(c.calc(a,x,y));
}
public static void main(String[] args) {
Arithmetic a=new Arithmetic();
//使用Lambda表达式
printResult(a,10, 20, (obj,x,y)->obj.add(x, y));
//使用方法引用
printResult(a,10, 20, Arithmetic::add);
}
}
如果接口中的方法的返回值是一个对象,那么我们可以这样做:
@FunctionalInterface
interface CreatePet {
Pet create(String name);
}
class Pet {
String name;
public Pet(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class LambdaDemo004 {
public static void getPetName(String name, CreatePet cp) {
System.out.println(cp.create(name).getName() + "好可爱啊");
}
public static void main(String[] args) {
// 使用Lambda表达式
getPetName("小白", (name)->new Pet(name));
// 使用方法引用
getPetName("小灰", Pet::new);
}
}
我们可以直接使用类名::new
来调用构造方法,即使类中有多个构造方法,JVM也会自动识别。
Lambda表达式是为了简化代码,也引入了很多很特性新用法,例如函数式编程。但也存在一些问题,例如对应的接口一旦改变,Lambda表达式就需要修改甚至需要推翻;当需要异常处理时,也比较麻烦。所以需要我们认真学习Lambda表达式的使用条件和方法,正确使用。
04
模块化是更加重大的一项改进。传统的Java程序依靠包来管理资源,但这种方式容易导致两个问题:一是将程序打包时,可能会遗漏一部分依赖的包,即对依赖关系的处理方面可能会出问题;二是JRE程序目前已经非常臃肿了,客户安装Java程序时不得不安装一个很大的JRE程序,而且其中很多内容是根本用不到的。
为了解决这些问题,从Java 9开始,JDK引入了模块化。在Java 8及以前的版本中,标准库保存在rt.jar文件中,加上其他的配套文件,一个运行环境要上百兆,而且没法去掉那些用不到的部分。
使用模块化之后,标准库拆分成了几十个模块,保存在JDK安装目录的jmods
目录中,我这里截取了一部分文件的名称列表:
java.base.jmod
java.compiler.jmod
java.datatransfer.jmod
java.desktop.jmod
java.instrument.jmod
java.logging.jmod
java.management.jmod
java.management.rmi.jmod
java.naming.jmod
java.net.http.jmod
java.prefs.jmod
java.rmi.jmod
java.scripting.jmod
java.se.jmod
java.security.jgss.jmod
java.security.sasl.jmod
将来等我们开发好了程序,看看用户需要用到那些模块,有选择地生成一个JRE就行了,无需包含那些用不到的部分。
对于开发人员来说,模块化可以使模块内部的联系更加紧密,模块之间的联系弱化,更加有利于项目管理。
下面我们来看看如何在Eclipse中实现模块化开发。
首先在Eclipse中创建一个Java项目,叫做HelloFrame
吧,然后单击“Next”按钮,确保“Create module-info.java file”这一项被选中。(以前的例子中我都没选,只要不选,就是不采用模块化)然后单击“Finish”,会提示你给模块起个名字。模块名和给包起名字差不多,不再多说,我这里起名cn.edu.vk.hello.helloFrame
,然后单击“Create”完成创建,这是会打开一个名为module-info.java
的程序。
创建一个包,名为cn.edu.vk.hello.helloFrame
(注意,包的名称并不需要和模块名称一样,好吧,我懒得想名字了),然后在这个包中创建一个类,名为HelloFrame
(名字其实没什么限制,我还是懒……),内容如下:
package cn.edu.vk.hello.helloFrame;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class HelloFrame extends JFrame {
public void init(String title) {
JFrame helloFrame=new JFrame();
helloFrame.setTitle(title);
helloFrame.setSize(400,300);
helloFrame.setLocationRelativeTo(null);
helloFrame.setLayout(new BorderLayout());
helloFrame.add(new JLabel("Hello World!"));
helloFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
helloFrame.setVisible(true);
}
public HelloFrame(String title) {
init(title);
}
}
我知道,你极有可能看不明白这个程序,先不用着急,在后面我们会讲到的。这里我们只要明白,这段程序的作用就是产生一个带有“Hello World”字样的窗口。
这时,我们会发现,Eclipse会提示错误,这是由于它找不到某些必须的类,比如JFrame,因为这些类位于java.desktop
模块中。我们为了让这段程序能正常使用,需要回到module-info.java
的编辑窗口,在其中添加几行说明,最终应该是这个样子的:
module cn.edu.vk.hello.helloFrame {
exports cn.edu.vk.hello.helloFrame;
requires java.desktop;
}
module
指定了包的名称,花括号中的requires
指定需要使用的模块,exports
指定可以被其他模块访问的包。
然后我们再创建一个项目,名为ShowFrame
,模块名为cn.edu.vk.hello.helloFrame
。编辑module-info.java
为:
module cn.edu.vk.hello.showFrame {
requires cn.edu.vk.hello.helloFrame;
}
然后我们会发现:报错了。
因为第二个项目还不知道模块cn.edu.vk.hello.helloFrame的存在,我们需要告诉它。我们在项目上单击鼠标右键,在弹出菜单中选择Build Path --> Configure Build Path,选中Project选项卡,选中Modulepath一项,再点击右侧的Add按钮,在弹出对话框中选择项目HelloFrame,单击OK按钮,并单击Apply and Close按钮退出配置对话框,这时Eclipse就不会报错了。
下面就可以正常开发了。在项目中创建一个名为vk.show
的包,并在其中创建一个名为Show.java
的程序,代码为:
package vk.show;
import cn.edu.vk.hello.helloFrame.HelloFrame;
public class Show {
public static void main(String[] args) {
new HelloFrame("欢迎欢迎,热烈欢迎");
}
}
运行一下看看吧。
全新的感受哦。
能看到图形界面,可能有些贪心的朋友会想:我怎么把这个程序变成一个可执行文件呢?总不能给别人用的时候还要给他装个Eclipse吧?
别慌,办法也很简单。
在项目上单击鼠标右键,在弹出菜单中选择Export,在弹出的对话框中选择Java–>Runable JAR file(不要选择JAR file,那样只会打包本项目的内容,不会打包引用的模块包含的内容,还是不能运行),单击Next按钮,在Launch configuration中选择主类——也就是包含了主方法的那个类,如果有多个类中包含主方法,就选择包含需要运行的那个主方法所在的类,这里选择的是“Show - ShowFrame”,在Export Destination中选择保存的路径,单击Finish按钮,就开始导出。待导出完毕后,保存路径中会有一个后缀名为“jar”的文件,我们可以在命令行中运行这个jar包,命令格式为:java -jar jar包名称
。
如果希望做成双击就能运行的那种形式,则需要借助其他工具,有兴趣的朋友可以自行查找和尝试,这里不再赘述。
关于定制JRE,我们在第一部分中就介绍了,大家可以翻看前面的内容。
说明:
module-info.java
就说明不是用模块化技术,此时采用传统的方法;exports
表示其他模块可以访问的部分,和原来的访问权限控制机制并不冲突;04
至此,关于Java最基本的内容就介绍完了。在后面的文章中,我会介绍Java中常用的一些类和技巧,以及相关的一些语法知识。