目录
一、介绍
嵌套类分为两种:静态嵌套类和内部类。内部类中又有两种特殊的内部类:局部类和匿名类。如果局部类只使用一次,则使用匿名类更简洁。如果实现的接口只有一个方法,则lambda表达式更简洁。如果已存在可用方法,则方法引用更简洁。。。
二、嵌套类
这里不谈及特殊的内部类。嵌套类作为类的成员,可以被所有访问修饰符修饰(private、public、protected、包私有)。被static修饰的为静态嵌套类,无static修饰的为内部类。
2.1、静态内部类
静态类行为上和顶层的类相似,不能访问外部类的成员,只能访问静态成员。
如果要在外部类之外访问静态内部类,需要写出外部类名:
OuterClass.StaticNestedClass nestedObject =new OuterClass.StaticNestedClass();
注意:接口天生就是静态的
2.2、内部类
内部类可以访问外部类所有成员,但是内部类不能定义静态成员。
如果要在外部类之外访问内部类,则:
OuterClass.InnerClass innerObject = outerObject.new InnerClass();
注意:有两种特殊的内部类:局部类和匿名类。
2.3、Shadowing(隐藏)
一个语句块就是一个作用域,作用域内的名字隐藏了作用域外的名字。
public class ShadowTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
System.out.println("x = " + x);
System.out.println("this.x = " + this.x);
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
}
}
public static void main(String... args) {
ShadowTest st = new ShadowTest();
ShadowTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
输出:
x = 23
this.x = 1
ShadowTest.this.x = 0
2.4、序列化
内部类(包括局部类和匿名类)最好不要被序列化,因为不同jvm对内部类的实现不同,因此不利于跨平台。
三、局部类
局部类被定义在语句块中(大括号中),比如方法体中。内部类可以访问外部类的成员,还可以访问局部变量和参数(必须是final修饰或者effectively final)。effectively final指的是没有被final修饰,但是也没有被修改过。这个过程被称为捕获。
局部类属于内部类,也不能被申明为static成员和不能有静态初始器,但不能被访问修饰符修饰。
局部类可以存在运行时常量。
如果局部类在静态块中声明,比如静态方法,那么局部类只能访问外部类的静态成员。
四、匿名类
匿名类能够然代码更简洁,在声明类的时候同时实例化。匿名类没有名字,因此不能声明构造函数,但是可以传递参数给被继承(实现)的类(接口)的构造函数,如:
public class App {
public static void main(String[] arg) {
App app=new App();
app.new Human("Hello") {
void say() {
System.out.println("World");
}
}.say();
}
class Human{
public Human(String s) {
// TODO Auto-generated constructor stub
System.out.print(s);
}
}
}
如上,由于声明匿名类是一个表达式(非语句),因此声明后需要加上分号。
匿名类表达式由new操作符、要继承(实现)的类名(接口名)、传给构造函数的参数(如果实现接口,则没有参数)和类体(body)构成。
和局部类一样,匿名类能够捕获变量。比如匿名类可以访问外部类的成员;能够访问fianl或effectively final的局部变量或者参数;能够隐藏外部作用域的同名变量。匿名类也不能声明静态初始器和静态成员,但是能够声明运行时常量。
注意,没有类名则不能声明构造函数。
五、Lambda表达式
如果要实现的接口(不能为类)只有一个方法,那么匿名类仍然不够简洁,因此有了Lambda表达式,如:
public class App {
public static void main(String[] arg) {
method(()->System.out.println("hello human"));
}
static void method(Human human) {
human.say();
}
interface Human{
void say() ;
}
}
Lambda表达式由参数、->、body(主体)组成。
参数由逗号分隔,忽略参数的类型。如果只有一个参数,则可以忽略括号,如:
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
body(主体)为单个的表达式或者一个语句块。如果是一个表达式,jvm会计算表达式然后返回他的值,如上面所示。也可以使用return语句,由于return语句不是表达式,因此要声明语句块:
p -> {
return p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25;
}
如果仅仅调用一个无参方法,可以省略括号和分号:
email -> System.out.println(email)
Lambda表达式不会引入新的作用域,因此Lambda的参数和body中的变量处于外部作用域中,如下面的例子:
import java.util.function.Consumer;
public class LambdaScopeTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
Consumer<Integer> myConsumer = (y) ->
{
System.out.println("x = " + x); // Statement A
System.out.println("y = " + y);
System.out.println("this.x = " + this.x);
System.out.println("LambdaScopeTest.this.x = " +
LambdaScopeTest.this.x);
};
myConsumer.accept(x);
}
}
public static void main(String... args) {
LambdaScopeTest st = new LambdaScopeTest();
LambdaScopeTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
输出:
x = 23
y = 23
this.x = 1
LambdaScopeTest.this.x = 0
其他的类似,比如只能可以访问外部类成员、捕获final和effectively fianl局部变量(参数)、隐藏其他变量等等。
Lambda表达式是什么类型(具体实现的接口)可以由编译器推断出,因为同一个Lambda表达式可以表示很多接口的实现。
六、方法引用
如果创建lambda表达式只是为了调用已存在的方法,那么lambda表达式也不够简洁。。。。因此出现了方法引用。
比如排序数组,用lambda表达式调用已存在的方法:
Arrays.sort(rosterAsArray,
(a, b) -> Person.compareByAge(a, b)
);
现在使用方法引用:
Arrays.sort(rosterAsArray, Person::compareByAge);
两者其实是一样的,只是方法引用更简洁。
下面列出了四种方法引用。
Kind | Example |
---|---|
Reference to a static method | ContainingClass::staticMethodName |
Reference to an instance method of a particular object | containingObject::instanceMethodName |
Reference to an instance method of an arbitrary object of a particular type | ContainingType::methodName |
Reference to a constructor | ClassName::new |
参考
https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html