嵌套类
嵌套类有两种类别:static and non-static,分别对应为静态嵌套类和内部类.
其中静态嵌套类只能访问外部类的静态成员,内部类可以访问外部类的任意成员;它们可以被声明为 private, public, protected, 或 package private.
class OuterClass {
...
static class StaticNestedClass {
...
}
class InnerClass {
...
}
}
静态嵌套类实例化方式为: OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
内部类实例化方式:OuterClass.InnerClass innerObject = outerObject.new InnerClass(); 即通过外部类实例才能访问内部类.
有两个比较特殊的内部类,分别为局部内部类和匿名类.
局部内部类
局部内部类(Local CLasses)可声明在类中任意块(block)中,如方法,for 或 if 块中
局部内部类可以访问外部类的成员,若局部内部类声明在静态块中,则可访问外部类的静态成员;若声明在非静态块中,则可访问外部类所有成员;
局部内部类可以访问所在块的局部变量,但该局部变量必须声明为 final;在 JDK8 中进行了改进,局部变量可以声明为 final 或 effectively final;
其他特性类似于普通内部类
其中 effectively final 与 final 局部变量的区别在于,前者可以不显式声明变量为 final,只要在整个过程中,该变量不会被修改(编译器默认该情况为 final).具体为什么局部内部类为什么必须引用 final 变量,可参考
java 为什么匿名内部类的参数引用时 final? .大致意思是局部内部类引用局部变量,其实是进行的值引用(或者说是值拷贝).可以认为避免外部代码块在内部类运行结束前结束,导致局部变量回收而出错.
匿名类
匿名类与局部内部类相似,只是没有命名,并且同时进行声明和实例化.如下:
匿名内部类适用于只用一次的情况.其他的特性与局部内部类相同.
HelloWorld frenchGreeting = new HelloWorld() {
String name = "tout le monde";
public void greet() {
greetSomeone("tout le monde");
}
public void greetSomeone(String someone) {
name = someone;
System.out.println("Salut " + name);
}
};
Lambda 表达式
在使用匿名内部类的时候,无需提供类名.对于只有一个方法的接口,使用 Lambda 显然比匿名类的实现简单明了.如下所示,定义一个 LambdaTest 接口,该接口只包含一个 opt 方法:
LambdaTest sumTest = (a, b) - >a + b;
interface LambdaTest {
int opt(int a, int b);
}
第 5 行即为 Lambda 表达式声明,其中(a,b)为方法的参数,a+b 为方法体,-> 表示将参数传递给方法体.
Lambda 表达式的方法体中,可以是一个表达式,也可以是代码块.若为表达式,Java 运行期会计算表达式,并返回结果;若为代码块,可以添加 return 语句,将结果返回.
Lambda 表达式其实是一个方法的声明,可以认为 Lambda 表达式是匿名方法.
Lambda 表达式与局部内部类和匿名类相似,可以访问外部类和外部代码块的变量;但与后两者不同,其不存在变量覆盖的问题,可以认为没有引入新的代码块,其与外部代码块中的局部变量同级.
由于第三条,所以在表达式的参数中,不能声明与同级作用域相同的变量名,否则会出现重复定义的异常.
Lambda 表达式是匿名内部类实现形式的一种,其访问的外部变量必须是 final 或 effectively final.
举例如下:
运行结果:
public class Lambda {
private int
var = 100;
private String x = "hello";
interface Cal {
int op(int a, int b);
}
interface Print {
void print(String msg);
}
public int operator(int a, int b, Cal cal) {
return cal.op(a, b);
}
public void operator1(String msg, Print print) {
print.print(msg);
}
public void operator2(String x) {
// x = "";
Print print = (msg) - >{
System.out.println("Lambda访问外部变量:");
System.out.println(x);
System.out.println(msg);
System.out.println(Lambda.this.x);
};
print.print(x);
}
public static void main(String[] args) {
Cal add = (a, b) - >{
return a + b;
};
Cal mul = (a, b) - >a * b;
Lambda lambda = new Lambda();
System.out.println("2+3=" + lambda.operator(2, 3, add));
System.out.println("2*3=" + lambda.operator(2, 3, mul));
lambda.
var = 200;
Print print = (msg) - >{
System.out.println(msg);
System.out.println(lambda.
var);
};
lambda.operator1("Hello World", print);
lambda.operator2("Hello Lambda");
}
}
Lambda访问外部变量:
2+3=5
2*3=6
Hello World
200
其中 operator2 方法可以验证后三条,如果将 24 行的注释取消,28 行就会报 "local variables referenced from a lambda expression must be final or effectively final" 的异常.
Hello Lambda
Hello Lambda
hello
目标类型(Target Type)
目标类型为外部类方法期望调用的类型,如上例中 operator 期望调用的目标方法为 Cal.Java 会根据 Lambda 表达式所处的语境和上下文信息判断目标类型,并实现调用.
举例如下:
声明三个接口(Cal Cal1 Cal2),具有相同名称的方法,但他们的返回值不同.另声明了 3 个 invoke 方法,分别接收 3 个类,即期望的目标类型不同.然后进行测试:
public class TargetType {
interface Cal {
String op();
}
interface Cal1 {
int op1();
}
interface Cal2 {
void op1();
}
public static String invoke(Cal cal) {
return cal.op();
}
public static void invoke(Cal1 cal1) {
cal1.op1();
}
public static void invoke(Cal2 cal2) {
cal2.op1();
}
public static void main(String[] args) {
invoke(() - >"done");
invoke(() - >100);
invoke(() - >{
return;
});
}
}
main 方法中的三个语句都通过编译,并且 eclipse 提示 28 行调用目标类型为 Cal 的 invoke,29 行调用目标类型为 Cal1 的 invoke,30 行调用目标类型为 Cal2 的 invoke,目标类型如下图所示:
(1)如果再添加一句如:invoke(() -> 100.0); 则编译器会报错,Type mismatch: cannot convert from double to String;
(2)如果将 Cal 接口方法的返回值改为 int,则除了 28 行报错,29 行也报错:The method invoke(TargetType.Cal) is ambiguous for the type TargetType,即编译器无法确定调用哪个目标类型.
官网文档中举的例子为 Runnable 和 Callable,原理一样,如下:
方法声明:
public interface Runnable {
void run();
}
public interface Callable < V > {
V call();
}
根据上下文确定目标类型,由于有返回值,所以会调用参数为 Callable 的 invoke 方法:
void invoke(Runnable r) {
r.run();
}
< T > T invoke(Callable < T > c) {
return c.call();
}
1 String s = invoke(() - >"done");
总结:
静态嵌套类与内部类区别
两类特殊的内部类,局部内部类和匿名内部类;
匿名内部类的特殊实现:Lambda 表达式,可认为匿名方法的实现;
Lambda 表达式会根据上下文环境确定目标类型
参考:
Nested Classes
Lambda Expressions
来源: https://www.cnblogs.com/shuimuzhushui/p/8207041.html