Java类的分类

Java类的分类

1. 絮絮叨叨

在学习JDK 8的lambda表达式这一新特性时,发现很多文章都是以匿名类作为切入点,从而体现lambda表达式的简洁,或者帮助我们理解lambda表达式的写法在对lambda表达式语法不熟悉的情况下,自己总是尝试先用匿名类实现这个接口,然后再跟lambda表达式一一对应 😂通过学习JDK 8的官方文档,发现原来我所认识的内部类也有这么多讲究:嵌套类、静态嵌套类、非静态嵌套类等

2. 嵌套类

所谓嵌套类(Nested Class),就是在一个类中定义的类

之前自己一直把这种类直接叫做内部类,原来JDK中的专业术语是嵌套类

class OuterClass { // 外部类

static class StaticNestedClass { // 这是静态嵌套类

// 内容省略

}

class NonStaticNestedClass { // 这是非静态嵌套类,又称内部类

// 内容省略

}

}

根据是否被static关键字修饰,嵌套类分为:静态嵌套类(static nested class)、非静态嵌套类(non-static nested class)

非静态嵌套类,又称内部类(inner class)

2.1 为什么要使用嵌套类?

这是对仅在一个地方使用的类的逻辑分类方法:

类B只在类A中使用,则类B应该被嵌入到类A中。将类B这样的帮助类(helper class)进行嵌套定义,可以让包更加精简 嵌套类增强了封装性:

两个顶层类(top-level class)A和B,如果B需要访问A中的成员,则A中的成员不能使用private修饰将类B嵌套在类A中,类B可以访问到类A中的private成员

类B类本身可以看做类A的成员,因此可以访问类A的private成员

同时,类B本身可以对外界隐藏 增加了代码的易读性和可维护性:将小的类嵌套到顶层类中,一般会在靠近使用嵌套类的地方定义嵌套类

2.2 嵌套类的一些说明

2.2.1 关于访问权限

嵌套类是上层类(enclosing class)的成员变量,可以使用public、protected、private和包权限进行修饰外部类,只能使用public或包权限进行修饰

2.2.2 关于访问范围

内部类可以访问上层类的所有成员,静态成员或实例成员都可以直接访问,甚至private成员也可以访问注意: 内部类中,不能定义静态成员(static final的成员变量除外)静态嵌套类,只能直接访问上层类的静态成员;若想访问实例成员,需要通过上层类的实例对象进行访问

解读1:内部类可以访问上层类的所有成员

内部类访问上层类的静态成员,无需过多解释

内部类是上层类的实例成员,在使用时必须先实例化上层类,再实例化内部类

这时,内部类的实例对象当然可以访问上层类的实例成员

public class OuterClass {

private static String staticOuterField = "static field in outer class";

private String outerField = "instance field in outer class";

public static void staticOuterMethod() {}

public void outerMethod() {}

class InnerClass{

public void method() {

System.out.println(staticOuterField);

System.out.println(outerField);

staticOuterMethod();

outerMethod();

}

}

public static void main(String[] args) {

// 内部类的实例化

OuterClass outerClass = new OuterClass();

OuterClass.InnerClass innerClass = outerClass.new InnerClass();

innerClass.method();

}

}

静态嵌套类无法直接访问上层类的实例成员

静态嵌套类可以看做上层类的静态成员,因此可以直接访问上层类的静态成员

静态嵌套类在使用时无需初始化,除非主动实例化上层类,否则将不存在上层类的实例

因此,静态嵌套类无法直接访问上层类的实例成员

这时,静态嵌套类就像一个顶层类(所谓顶层类,自己的理解:跟上层类同一level的类)

public class OuterClass {

private static String staticOuterField = "static field in outer class";

private String outerField = "instance field in outer class";

public static void staticOuterMethod() {}

public void outerMethod() {}

static class StaticNestedClass {

public void method() {

System.out.println(staticOuterField);

staticOuterMethod();

OuterClass outerClass = new OuterClass();

System.out.println(outerClass.outerField);

outerClass.outerMethod();

}

}

}

// 其他类的main方法

public static void main(String[] args) {

// 实例化静态嵌套类,并访问静态嵌套类的实例方法

OuterClass.StaticNestedClass staticNestedClass = new OuterClass.StaticNestedClass();

staticNestedClass.method();

}

2.3 Shadowing(阴影)

在学习类的继承时,super和this两个关键字,想必大家都不陌生

尤其是存在同名字段或方法时,可以通过super关键字访问父类的实例成员(实例变量、构造函数、实例方法),可以通过this关键字(或者省略this关键字)访问当前类的实例成员

在内部类中,很容易出现类型声明相同的情况。

例如,下面代码中的x,在上层类ShadowTest中是一个成员变量,在内部类FirstLevel中是一个成员变量,在内部类的方法methodInFirstLevel()中是一个入参

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的作用域将会发生Shadowing(在我看来就是覆盖😂)

被shadowed的x,需要通过添加作用域前缀才能被识别

x:表示方法入参xthis.x:表示内部类的成员变量xShadowTest.this.x:表示上层类的成员变量x 最终的执行结果如下:

3. 特殊的内部类

到目前为止,我们看到的内部类都是定义在一个类中的非静态嵌套类内部类其实还有两种特殊的类型:

定义在方法体中的内部类,叫做局部类(local class)定义在方法体中的、没有名字的内部类,叫做匿名类(anonymous class) 具体来说,是定义在语句块(block)中的类,语句块可能是方法体、for循环、if语句等

3.1 局部类

下面是一个局部类示例,通过创建局部类PhoneNumberCheck判断一个电话号码是否有效(11位的手机号才有效)

public class LocalClassTest {

private static final String regx = "[0-9]+";

public static void checkPhoneNumber(String phoneNumber1, String phoneNumber2) {

// 定义电话号码的长度,jdk 8之前必须使用final修饰

int length = 11;

// 定义局部类,判断电话号码

class PhoneNumberCheck{

private String phoneNumber;

public PhoneNumberCheck(String phoneNumber) {

this.phoneNumber = phoneNumber;

}

public boolean checkPhoneNumber() {

// 电话号码要求为11位,只能包含数字;局部类可以直接访问上层类的成员

if (phoneNumber.replaceAll(regx, "").length() == 0 && phoneNumber.length() == length) {

return true;

}

return false;

}

// jdk 8开始,允许访问方法的入参

public void printParameter() {

System.out.println("phoneNumber1: " + phoneNumber1 + ", phoneNumber2: " + phoneNumber2);

}

}

// 定义好局部类后,通过局部类判断电话号码是否有效

PhoneNumberCheck check = new PhoneNumberCheck(phoneNumber1);

// 打印方法入参中的两个电话号码

check.printParameter();

// 校验电话号码是否有效

if (check.checkPhoneNumber()) {

System.out.println("phoneNumber1 is valid!");

} else {

System.out.println("phoneNumber1 is invalid! ");

}

check = new PhoneNumberCheck(phoneNumber2);

if (check.checkPhoneNumber()) {

System.out.println("phoneNumber2 is valid!");

} else {

System.out.println("phoneNumber2 is invalid! ");

}

}

public static void main(String[] args) {

checkPhoneNumber("18380122330", "7283456");

}

}

执行结果:

关于局部类的特殊说明

如果局部类定义在静态方法中,则局部类只能访问上层类的静态成员

例如,checkPhoneNumber()为静态方法,如果将regx定义为非静态变量,编译时会报错 局部类能访问上层语句块的实例成员,所以局部类必须是非静态的(不太理解😂)接口可以看做特殊的抽象类,但接口本质上是静态的,所以不能在语句块中定义一个接口。JDK 8以来的变化

final 或effectively final

JDK 8以前,局部类要想访问上层语句块中的局部变量或参数,要求局部变量或参数必须是final类型(这里的参数,不知道是否为方法的入参)JDK 8开始,要求变量或参数是final或effectively final即可所谓的effectively final是指,变量或参数的值一旦初始化,就从未改变过(隐式的final类型) JDK 8之后,如果局部类定义在方法体中,局部类可以访问方法体的入参

3.2 匿名类

局部类属于类的定义,匿名类则是一个表达式,可以在定义类的同时实例化一个类

匿名类的定义类似于通过构造函数new一个对象,但在构造函数之后有一个代码块,这个代码块是匿名类的具体定义

interface HelloWorld {

public void greet(String name);

}

// 在方法体中定义匿名类

HelloWorld frenchGreeting = new HelloWorld() {

public void greet(String name) {

System.out.println("Salut " + name);

}

};

对匿名类的特殊说明

匿名类不能访问上层作用域中的非final或非effectively final的变量匿名类中可以定义字段、额外的方法(即使没有实现或重写超类的任何方法)、实例初始化、局部类,但是不能定义构造函数匿名类,在Java的GUI编程中使用非常多