主要介绍内部类,顺便提一下接口中的嵌套接口和嵌套类。
1.内部类
内部类是定义在其它类中的类。这个定义很宽泛,因为内部类也可以定义在类中的方法里,可以用匿名内部类,还可以声明为public、private、protected、static,不同的情形下,内部类表现也不一致,分别举例说明。
1.1 public 内部类
这是最普遍的情形,在一个类的内部定义另一个类,假设两个类分别叫A
和InnerA
,此时InnerA
可以访问A中的属性,其它类也可以通过A
的实例来实例化InnerA
,因为InnerA
是声明为public
。为什么要通过A
的实例呢?因为InnerA
在A
中跟其它属性、方法一样,属于“对象属性”,必须通过类的实例才能访问。
firstpackage
类A
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| package firstpackage;
public class A{
private String propA;
public A(){};
public A(String s){
this.propA=s;
}
public void say(){
InnerA innerA=new InnerA();
innerA.say();
}
public class InnerA{
public void say(){
System.out.println("这是InnerA的say()方法,但是可以读取到外部类A的propA属性=====>"+propA);
}
}
}
|
secondpackage
类TestB
1
2
3
4
5
6
7
8
9
10
11
| package secondpackage;
import firstpackage.A;
public class TestB {
public static void main(String[] args) {
A a=new A("一条来自secondpackage的信息");
A.InnerA innerA=a.new InnerA();
innerA.say();
}
}
|
输出:“这是InnerA的say()方法,但是可以读取到外部类A的propA属性=====>一条来自secondpackage的信息”
1.2 protected、private内部类
这两个修饰符只能用在内部类上,普通类只能public或者不加修饰符(表示只能在同一个package内访问)。
跟属性、方法的修饰符一样,protected表示包内可以访问(也可以被子类访问,内部类基本不会这么用),private表示只能在该外部类内部访问到这个内部类,在其它地方通过外部类也无法访问到这个内部类。
可以把上面的例子中的TestB
类放到跟类A
同一个package下来验证protected
的行为或者把上面的例子中的main
方法放到类A
中来验证private
的行为。
1.3 方法中的内部类(local/局部/本地内部类)
如果内部类只在方法中用到一次,那么直接把类声明在这个方法中也是可以的。这里并不能用修饰符,因为方法作用域中的内容不能被外部访问,这比private
的作用域还要严格。方法中的内部类还可以获取方法参数传递进来的变量,并保存为本地变量。
下面的例子中,通过say(String s)
方法传递进来的字符串s
,当方法结束后,变量s
就消失了,但是InnerA
在使用这个变量的时候保存在了本地,后续每一次事件触发时,都能正确的输出这个变量的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| package firstpackage;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class A{
public static void main(String[] args) throws Exception{
A a=new A("这是构造A的对象时传入的字符串");
a.say("这是通过方法传递的字符串");
//这里只是为了让程序保持运行
Thread.sleep(5000);
System.exit(0);
}
private String propA;
public A(){};
public A(String s){
this.propA=s;
}
public void say(String s){
class InnerA implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("这是内部类,但能获取外部类的属性========>"+propA+"\n还可以获取方法的参数并保存为本地变量========>"+s);
}
}
Timer timer=new Timer(1000,new InnerA());
timer.start();
}
}
|
输出:
这是内部类,但能获取外部类的属性========>这是构造A的对象时传入的字符串
还可以获取方法的参数并保存为本地变量========>这是通过方法传递的字符串
这是内部类,但能获取外部类的属性========>这是构造A的对象时传入的字符串
还可以获取方法的参数并保存为本地变量========>这是通过方法传递的字符串
这是内部类,但能获取外部类的属性========>这是构造A的对象时传入的字符串
还可以获取方法的参数并保存为本地变量========>这是通过方法传递的字符串
这是内部类,但能获取外部类的属性========>这是构造A的对象时传入的字符串
还可以获取方法的参数并保存为本地变量========>这是通过方法传递的字符串
这是内部类,但能获取外部类的属性========>这是构造A的对象时传入的字符串
还可以获取方法的参数并保存为本地变量========>这是通过方法传递的字符串
1.4 匿名内部类
上面的例子,InnerA
只是为了实现ActionListener
接口,这种情况下,可以进一步用匿名内部类简化,连类名都不需要,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| package firstpackage;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class A{
public static void main(String[] args) throws Exception{
A a=new A("这是构造A的对象时传入的字符串");
a.say("这是通过方法传递的字符串");
//这里只是为了让程序保持运行
Thread.sleep(5000);
System.exit(0);
}
private String propA;
public A(){};
public A(String s){
this.propA=s;
}
public void say(String s){
Timer timer=new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("这是内部类,但能获取外部类的属性========>"+propA+"\n还可以获取方法的参数并保存为本地变量========>"+s);
}
});
timer.start();
}
}
|
后来,java
引入lambda
表达式进一步简化了这种写法,上面的say()
方法可以改为:
1
2
3
4
5
6
| public void say(String s){
Timer timer=new Timer(1000, (ActionEvent e)->{
System.out.println("这是内部类,但能获取外部类的属性========>"+propA+"\n还可以获取方法的参数并保存为本地变量========>"+s);
});
timer.start();
}
|
PS:lambda
表达式只能用于代替实现FunctionalInterface
接口的内部类,就是内部只有一个抽象方法的接口(default方法数量不限)
1.5 static内部类
static
内部类和别的内部类有几点区别:
- 因为是static,所以使用该类时不需要外部类的实例
- 无法访问外部类的信息
- 可以声明
static
的变量和方法
static
内部类的作用类似package,只是让内部类更深了一层,可以让代码的结构更清晰,减少重名问题等,所以static
内部类跟普通的类很像,很多地方称之为"嵌套类"。
关于static
和其它内部类实例化的区别:跟类中的static
属性一样,在外部类加载完成后,static
内部类也加载完成了,所以如果要实例化static
内部类,只要用new Outer.Inner()
的表达方式就可以了。而普通的非static
内部类,因为是定义在类中,属于"对象属性"级别,在外部类加载完成时,这些"对象属性"还不存在,等到外部类实例化时,内部类才作为这个外部类实例对象的"对象属性"加载进来,但此时只是有内部类完成,还没有实例化,需要使用外部类的对象来实例化内部类。
2.接口中的嵌套接口和嵌套类
接口中的类型成员,包括类和接口,都默认是public staic
,也强制只能是public static
。
首先,接口不能被实例化,所以,任何非static
的内容都毫无意义。至于public
,JavaSE规范中规定,接口中的类型成员不是public
就会在编译时报错。
关于这两个嵌套类型,举个例子,spring boot
源码中有一个接口叫DeferredImportSelector
,代码如下:
显示代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
| package org.springframework.context.annotation;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.Nullable;
public interface DeferredImportSelector extends ImportSelector {
@Nullable
default Class<? extends Group> getImportGroup() {
return null;
}
interface Group {
void process(AnnotationMetadata metadata, DeferredImportSelector selector);
Iterable<Entry> selectImports();
class Entry {
private final AnnotationMetadata metadata;
private final String importClassName;
public Entry(AnnotationMetadata metadata, String importClassName) {
this.metadata = metadata;
this.importClassName = importClassName;
}
public AnnotationMetadata getMetadata() {
return this.metadata;
}
public String getImportClassName() {
return this.importClassName;
}
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
Entry entry = (Entry) other;
return (this.metadata.equals(entry.metadata) && this.importClassName.equals(entry.importClassName));
}
@Override
public int hashCode() {
return (this.metadata.hashCode() * 31 + this.importClassName.hashCode());
}
@Override
public String toString() {
return this.importClassName;
}
}
}
}
|
DeferredImportSelector
接口中有一个抽象方法getImportGroup
,返回的是实现Group
接口的对象的Class,Group
是一个嵌套接口。通过IDE查看实现或者使用Group
的地方,都跟DeferredImportSelector
有关,所以定义在其内部作为嵌套接口比较直观、合理。虽然两者经常搭配使用,但从语法上来说,实现外部接口和它的嵌套接口没有必然联系,可以只实现其中的一个。
Group
接口中还包含一个静态内部类Entry
。Group
接口中有个抽象方法selectImports
返回的是Iterable<Entry>
,Entry
这个名称很普遍,放到内部类里可以避免重名,并且可以通过外部类的名称直观的看出这个类的作用。
3 总结
spring
的源码中就经常使用内部类、内部接口等,可以让源码可读性更好,让外部类和内部类直接联系起来。总的来说,内部类和嵌套类、嵌套接口是为了提高程序代码的内聚性而存在的,也是封装思想的一种体现。
版权声明
本博客使用CC BY-NC-SA 4.0许可协议(创意共享4.0:保留署名-非商业性使用-相同方式共享)。