JDK8新特性
一,接口的默认方法和静态方法
在jdk8之前,在接口中定义的普通方法都是抽象方法,方法前面默认都会添加
public abstract
,不能有方法实现,必须在接口的实现类中对方法进行具体实现。
下列代码展示非jdk8新特性写法
public interface MyJdk {
//jdk8之前写法
String jdk8 ();
}
public class MyjdkImpl implements MyJdk {
@Override
public String jdk8() {
return "jdk8之前写法!";
}
}
public class HelloWord {
public static void main(String[] args) {
// 向日葵
MyjdkImpl myjdk = new MyjdkImpl();
System.out.println(myjdk.jdk8());
}
}
但是JAVA8开始允许在接口中定义默认方法和静态方法,对于这两个方法,可以直接在接口对其进行实现,无需再在接口实现类中进行实现。
默认方法:扩展方法,在方法前面需通过default
修饰,不能直接通过接口调用,必须通过接口实现类的实例对象进行方法调用。
接口实现类对象.默认方法()
静态方法:类方法,在方法前面需通过static
修饰,可以直接通过接口调用。
接口.静态方法()
代码实现:
1,抽象接口
public interface MyJdk {
//jdk8之前写法
String jdk8 ();
//jdk8 新特性 默认方法
default String defaultMethod (){
return "jdk8新特性之默认方法!";
}
//jdk8 新特性 静态方法
static String staticMethod(){
return "jdk8新特性 静态方法!";
}
}
-- 接口实现类
public class MyjdkImpl implements MyJdk {
@Override
public String jdk8() {
return "jdk8之前写法!";
}
}
方法调用
public class HelloWord {
public static void main(String[] args) {
// 向日葵
MyjdkImpl myjdk = new MyjdkImpl();
System.out.println(myjdk.jdk8());
System.out.println(myjdk.defaultMethod());
System.out.println(MyJdk.staticMethod());
MyJdk myJdk1 = new MyJdk(){
@Override
public String jdk8() {
return null;
}
@Override
public String defaultMethod() {
return null;
}
};
}
}
普通方法必须实现,默认方法可以选择性重写,静态方法无法重写。
面试题:在Java 接口中,接口中的方法是否支持方法体?jdk8 后支持方法体
二,Lambda 表达式
Lambda表达式是Java8中非常重要的一个新特性,其基于函数式编程的思想,支持将代码作为方法参数进行使 用。可以把Lambda表达式理解为通过一种更加简洁的方式表示可传递的匿名函数。
它本身没有名称,而且不像方法那样属于某一个类,但是可以有参数列表、代码体、返回值。使用了Lambda表达 式之后就不需要再去编写匿名类了。
1,Lambda基础格式
(参数列表)->{
方法体
}
参数列表:即匿名方法的形参
2,Lambda运算符
方法体:用于执行业务逻辑。可以是单一语句,也可以是语句块。如果是单一语句,可以省略花括号。当需要返回 值,如果方法体中只有一条语句,可以省略return,会自动根据结果进行返回。
1)没有参数的Lambda表达式
()‐>new Student();
2)只有一个参数的Lambda表达式
x ‐> {
System.out.println(x);
return x;
}
3)有多个参数的Lambda表达式
(int x,int y) ‐>{
System.out.println(x);
System.out.println(x);
return x+y;
}
上述可以进行简写,因为在Lambda中,参数列表中参数的数据类型可以交给JVM根据上下文进行推断。所以可以 不用定义类型。
(x,y) ‐>{
System.out.println(x);
System.out.println(y);
return x+y;
}
4)一个参数和仅一条语句的Lambda表达式
x ‐> 3+x;
5)多个参数和仅一条语句的Lambda表达式
(x,y) ‐> x+y;
举例:
1,可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
2,可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
3,可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
4,可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
public class Java8Tester {
public static void main(String args[]){
Java8Tester tester = new Java8Tester();
// 类型声明
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
// 不用括号
GreetingService greetService1 = message ->
System.out.println("Hello " + message);
// 用括号
GreetingService greetService2 = (message) ->
System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");
greetService2.sayMessage("Google");
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
我们也可以直接在 lambda 表达式中访问外层的局部变量:
public class Java8Tester {
public static void main(String args[]) {
final int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2); // 输出结果为 3
}
public interface Converter<T1, T2> {
void convert(int i);
}
}
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
num = 5;
//报错信息:Local variable num defined in an enclosing scope must be final or effectively
final
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String first = "";
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); //编译会出错
2,Lambda 使用对比
遍历集合
public static void main(String[] args) {
String[] language = {"c", "c++",
"c#",
"java","python",
"go","hive",
"php"};
List<String> languageList = Arrays.asList(language);
//旧的循环方式
for (String s : languageList) {
System.out.println(s+",");
}
//lambda循环
languageList.forEach(s‐> System.out.println(s+","));
}
使用Lambda 替换匿名内部类的使用
//匿名内部类
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello world !");
}
};
//使用Lambda
Runnable runnable1 = ()‐> System.out.println("hello itcast");
实现Lambda实现集合排序
public static void main(String[] args) {
String[] language = {"c", "c++",
"c#",
"java","python",
"go","hive",
"php"};
//旧的循环比较方式
Arrays.sort(language,new Comparator<String>(){
@Override
public int compare(String o1, String o2) {
return (o1.compareTo(o2));
}
});
//lambda循环比较
Arrays.sort(language,(o1,o2)‐>(o1.compareTo(o2)));
3, Lambda 表达式底层原理解析
写一个使用Lambda表达式的方法
public class SourceDemo {
public void demo(){
String[] language = {"c", "c++",
"c#",
"java","python",
"go","hive",
"php"};
List<String> list = Arrays.asList(language);
list.forEach(s‐> System.out.println(s));
}
}
将当前.Java文件使用dos命令编译生成.class 文件,执行命令后,会在当前文件夹生成对应的.class文件
javac SourceDemo.java
将.class文件执行DOS命令进行反编译,查看文件内容
javap -p SourceDemo.java
生成内容如下:
Compiled from "SourceDemo.java"
public class com.itheima.lambda.source.SourceDemo {
public com.itheima.lambda.source.SourceDemo();
public void demo();
private static void lambda$demo$0(java.lang.String);
}
此时可以发现,代码中执行Lambda表达式的部分生成了一个静态私有函数。这个静态私有函数的函数干就是 Lambda表达式里面的内容。 那么对于这个静态私有函数,在JDK8内部是如何实现调用的呢?可以查看 LambdaMetafactory 类,该类下有一个 metafactory方法,lambda表达式每一次在执行的时候都会进入到这个方法中,并且为lambda表达式创建一个内 部类。
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();
}
如果想查看自己写的类里面的Lambda内容,可以在Lambda 表达式执行之前,添加
System.setProperty("jdk.internal.lambda.dumpProxyClasses", "D://");
这个方法会将运行时生成的内部类class文件进行输出。
这个方法是配置jdk 系统环境变量,
Lambda 在运行时会生成一个内部类,为了验证是否生成内部类,可以在运行时加上上面的代码,加上后,将会生成的内部类.class 输出到一个文件中.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-96JC4F25-1612259767249)(E:\JAVA学习笔记\JAVA 架构师课程\2,互联网架构及性能优化篇\第05章 JDK8以上提高开发效率有哪些3.0\JDK8以上提高开发效率有哪些\imgs\ss.png)]
当该文件生成后,可以通过 javap -c -p class文件名 查看文件中的内容
此时可以发现编译后的Lambda表达式已经被执行。
综上所述,Lambda表达式在执行的时候,会调用LambdaMetafactory.metafactory动态的生成内部类,在方法 内调用 SourceDemo$&Lambda$1 ,内部类里的调用方法块并不是动态生成的,只是在原class里已经编译生成了一个 静态的方法,内部类只需要调用该静态方法。
扩展:javac 命令是Java编译,javap是对javac编译的文件进行反编译
C:\>javap -help
用法: javap <options> <classes>
其中, 可能的选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示静态最终常量
-classpath <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示静态最终常量
-classpath
-bootclasspath
java -p 和Java -private 两个命令是一个效果.