Java是一门面向对象编程语言。面向对象编程语言和函数式编程语言中的基本元素(Basic Values)都可以动态封装程序行为:面向对象编程语言使用带有方法的对象封装行为,函数式编程语言使用函数封装行为。但这个相同点并不明显,因为Java的对象往往比较“重量级”:实例化一个类型往往会涉及不同的类,并需要初始化类里的字段和方法。
在Java8中接口也很多变化:
1、接口变量的访问
1) 局部变量:
- 从Java8开始,如果在内部类里面访问局部变量,会自动给局部变量加上final修饰
- Java8之前,内部类只能访问final的局部变量
2) 类变量:
内部类可以直接访问外部类变量,相当于是当前类访问一样。
3)实例变量:
- 如果在静态的方法里面写上匿名内部类,访问实例变量,必须通过外部类的实例来进行访问
- 如果外部类的实例是一个局部变量,该实例对应的变量不能多次赋值。但实例变量本身不会有影响
2、函数式接口 – Lambda的使用
函数式接口:当接口只有一个抽象方法的时候(可以有其它默认方法),就是函数式接口,可以使用注解(@FunctionalInterface)强制限定接口只能有一个抽象方法。
lambda语法:
([形参列表,不带数据类型]) -> {
// 执行语句
[return ...;]
}
其中:
() : 表示参数列表,不需要指定参数类型,会自动推断
-> : 连接符
{} : 表示方法体
注意点:
- 1.如果形参列表是空的,只需要保留()即可。
- 2.如果没有返回值,只需要在{}写执行语句即可。
- 3.如果接口的抽象方法只有一个形参,()可以省略,只需要参数的名称即可
- 4.如果执行语句只有一行,可以省略{},但是如果有返回值的时候,不能省略
- 5.形参列表的数据类型自动推断,只要参数名称
- 6.如果函数式接口的方法有返回值,必须要给定返回值,如果执行语句只有一行代码,可以省略大特号,但必须同时省略return关键字
lambda表达式就是函数式接口,也可以认为是一种特殊的匿名内部类。下面就看看匿名内部类的lamdba表达式的写法:
1) 方法无参数,无返回值
public class LambdaDemo {
public static void main(String[] args) {
// 1. 匿名内部类的方式实现,在Java8之前,没有Lambda表达式
UserService userService = new UserService() {
@Override
public void test() {
System.out.println("不使用lambda表达式");
}
};
userService.test();
// lambda 右边的类型,会自动根据左边的变量的类型进行推断
UserService userService1 = () -> {
System.out.println("使用lambda表达式");
};
userService1.test();
// lambda 如果方法体只有一句话,可以省略大括号以及省略一个分号
// 如果有返回值,连return也可以省略
UserService userService2 = () -> System.out.println("使用最简lambda表达式");
userService2.test();
}
}
@FunctionalInterface
// 没有参数,没有返回值
interface UserService{
void test();
}
Result:
2) 有一个参数,无返回值
public class LambdaDemo {
public static void main(String[] args) {
// 2 方法有一个参数,园括号里面只需要知道参数的名称,不需要参数的类型。
// 数据类型自动根据函数式接口的定义自动推断
UserService1 test1 = (x) -> {
System.out.println("一个参数,一行代码输出参数的值 : " + x);
};
test1.test(100);
// 如果参数列表里面,只有一个参数,可以省略园括号
UserService1 test2 = x -> System.out.println("一个参数,一行代码输出参数的值 : " + x);
test2.test(100);
}
}
@FunctionalInterface
// 有一个参数,没有返回值
interface UserService1{
void test(int i);
}
3) 有二个参数,没有返回值
public class LambdaDemo {
public static void main(String[] args) {
UserService2 test3 = (x, y) -> {
System.out.println("两个参数 : " + x);
System.out.println("两个参数 : " + y);
};
test3.test(100, 200);
}
}
@FunctionalInterface
// 有二个参数,没有返回值
interface UserService2{
void test(int i, int j);
}
4) 有一个参数,有返回值
public class LambdaDemo {
public static void main(String[] args) {
// 4 有返回值
UserService3 test4 = b -> {
b = b + 10;
return b;
};
int o = test4.test(15);
System.out.println(o);
// 如果省略大括号,return一定要省略掉。代码里面的表达式返回值会自动作为方法的返回值
UserService3 test5 = b -> b + 10;
System.out.println(test5.test(15));
}
}
@FunctionalInterface
// 有一个参数,有返回值
interface UserService3{
int test(int i);
}
3、方法的引用 – Lambda的使用
1) 引用实例方法
public class TestMethodRef {
public static void main(String[] args) {
MethodRef r = s -> System.out.println(s);
r.test("字符串的");
// 使用方法的引用 : 实例方法的引用
// System.out是一个实例
MethodRef r1 = System.out :: println;
r1.test("方法引用");
}
}
@FunctionalInterface
interface MethodRef{
void test(String s);
}
2) 引用类方法
public class TestMethodRef {
public static void main(String[] args) {
// 能够根据函数式接口的方法参数,推断引用的方法的参数的数据类型
// 不引用方法进行排序
MethodRef1 r3 = (o) -> Arrays.sort(o);
// 引用类方法
MethodRef1 r2 = Arrays :: sort;
int[] a = new int[]{4, 12, 32, 44, 5, 9};
// 引用方法排序
r2.test(a);
// 引用方法输出
r1.test(Arrays.toString(a));
}
}
@FunctionalInterface
interface MethodRef1{
void test(int[] arr);
}
3) 引用类实例方法
public class TestMethodRef {
public static void main(String[] args) {
// *** 引用类的实例方法
MethodRef2 r4 = PrintStream :: println;
// 第二个之后的参数,作为引用方法的参数
r4.test(System.out, "第二个参数");
}
}
@FunctionalInterface
interface MethodRef2{
void test(PrintStream out, String str);
}
4) 引用构造器
public class TestMethodRef {
public static void main(String[] args) {
// 引用构造器,根据函数式接口的方法名来推断引用哪个构造器
MethodRef4 r5 = String :: new;
String ok = r5.test(new char[]{'阿' , '器'});
System.out.println(ok);
MethodRef4 r6 = (c) -> {return new String(c);};
String o1 = r6.test(new char[]{'阿' , '器'});
System.out.println(o1);
}
}
// 测试构造器引用
@FunctionalInterface
interface MethodRef4{
String test(char[] str);
}
4、接口中的静态方法
从java8开始接口里面可以有静态方法(之前接口中是不能定义静态方法的),和普通类里面的静态方法类似,使用static修饰,但是接口里面的只能是public的。格式为:
[public] static <返回值> <方法名> ([形参列表])
{
// 方法体
}
例子如下:
public interface TestStaticMethod {
// 这是一个函数式接口,因为这个接口里面只有一个抽象方法
public void test();
// 静态方法不是抽象方法
static void test1(){
System.out.println("这个是接口里面的静态方法,直接可以使用接口调用此方法");
}
public static void main(String[] args) {
System.out.println("自从接口可以有静态方法,从此接口可以写main方法,可以作为程序的入口");
TestStaticMethod.test1();
}
}
class TestStaticMethodClass{
public static void main(String[] args) {
// 调用接口的静态方法
TestStaticMethod.test1();
}
}
5、接口中的默认方法
在Java8中除了可以在接口里面写静态方法,还可以写非静态方法,但是必须用default进行修饰。
public interface TestDefaultMethod {
// 使用 default 修改的方法,表示实例方法, 必须通过实例来方法
public default void test(){
System.out.println("这个是接口里面的默认方法" + this);
}
public static void main(String[] args) {
// 使用匿名内部类初始化实例
TestDefaultMethod tdm = new TestDefaultMethod() {};
// 使用对象来访问默认方法
tdm.test();
}
}
Result:
接口中可以使用this关键字,但是我们可以看到结果中有$,这是不是和我们的匿名内部类有点像了?
注意:
- 默认方法可以被继承。如果继承多个父接口有重复的默认方法被继承到子接口,必须使用super引用明确指定调用哪个接口的默认方法在子接口必须重写重复方法,并使用下面的语法重写父接口方法重复的问题
<父接口类名>.super.<重复的方法名>([参数]);
- 同样,如果实现了多个接口,遇到有重复的默认方法,也需要使用重写重复的方法,使用super引用解决问题,和接口一样。
- 父接口的抽象方法,在子接口里面可以使用默认方法实现,这样是实现类里面就不需要再实现了。如果实现类再去实现默认方法,那么相当于是”方法覆盖”。
- 如果父接口有一个抽象方法,在子接口里面可以重写为抽象方法(去掉父接口的形为)
1) 默认方法多继承
interface A{
default void test(){
System.out.println("接口A里面的默认方法");
}
}
interface B{
default void test(){
System.out.println("接口B里面的默认方法");
}
}
public interface C extends A, B {
// 明确指定引用父接口,使用super引用调用父接口的默认方法
// <父接口类名>.super.<重复的方法名>([参数])
default void test(){
B.super.test();
System.out.println("接口C重写的默认test方法");
}
}
如果注释掉C接口中的test方法就会报以下的错误:
Test
class Test{
public static void main(String[] args) {
C c = new C(){};
c.test();
}
}
Result:
2) 默认方法的重写
public interface E {
default void test(){
System.out.println("默认方法");
}
}
interface F extends E{
// 在子接口重写父接口的默认方法,把默认方法改为抽象方法
void test();
}
class RemoveDefaultMethod {
public static void main(String[] args) {
E e = new E(){};
e.test();
F f = () -> System.out.println("匿名内部类重写父接口方法");
f.test();
}
}
Result:
推荐阅读: 深入理解Java 8 Lambda