0%

Java——内部类

将一个类的定义放在另一个类的定义内部,这就是内部类,外部类没有访问内部类成员的权限,它只能通过创建内部类的对象来调用内部类的成员。

创建内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Essay {
class Content{
private String content = "ABC";
public String getContent(){
return content;
}
}
public Content createContent(){
return new Content();
}
public static void main(String[] args) {
Essay essay = new Essay();
Content content = essay.createContent();
System.out.println(content.getContent());
}
}

链接到外部类

当创建一个非静态内部类的对象时,它不能像正常创建对象那样去创建,它必须由一个外部类的对象去创建。所以,一个内部类会自动地保存创建它的外部类的引用,即它会自动拥有外部类的所有访问权(包括private成员)。如下,这是一个迭代器设计模式的例子,SequenceSelector可以访问Sequence中的items成员,通过items来实现对序列的end(判断是否结束)、current(取出当前元素)、next(下移)操作,这里的SequenceSelector对于Sequence来说是一个辅助类,它为Sequence提供了一个迭代的功能。

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
interface Selector{
void next();
Object current();
boolean end();
public Sequence getSequence();
}
public class Sequence {
private Object[] items;
private int count = 0;

public Sequence(int size) {
items = new Object[size];
}
public void add(Object x){
if (count<items.length)
items[count++] = x;
}
//若想为类创建一个辅助类,但不想向外部暴露这个辅助类的话,可以考虑使用内部类
protected class SequenceSelector implements Selector{
private int next = 0;
@Override
public void next() {
if (next<items.length)
next++;
}
public Sequence getSequence(){
return Sequence.this;
}

@Override
public Object current() {
return items[next];
}

@Override
public boolean end() {
return next == items.length;
}
}

public Selector getSelector(){
//其实这里相当于this.new
return new SequenceSelector();

}

public static void main(String[] args) {
Sequence s = new Sequence(10);
for (int i = 0; i < s.items.length; i++) {
s.add("ABC");
}
Selector selector = s.getSelector();
while (!selector.end()){
System.out.print(selector.current()+" ");
selector.next();
}
}
}

内部类的访问修饰符

跟普通类一样,内部类有本类、子类、包的这几个范围,根据修饰符的不同而有所变化。若把内部类声明为private,该内部类就只能在外部类的范围内进行访问。

访问修饰符 本类 同包(无论是否继承) 不同包的子类 不同包的非子类
public true true true true
protected true true true
默认 true true
private true

使用.this与.new

在内部类中可以使用外部类的类名.this来表示外部类的引用,在外部类中,如果想要在创建某一个外部类对象关联的内部类,则可以使用外部类的对象名.new

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DotThis {
void f(){
System.out.println("DotThis.f()");
}
public class Inner{
public DotThis outer(){
return DotThis.this;
}
}
public void a(){
//相当于Inner inner = this.new Inner()
Inner inner = new Inner();
}
public static void main(String[] args) {
DotThis dotThis = new DotThis();
//由于在静态方法下没有this关键字,所以在静态方法下创建内部类对象时,必须指定其外部类的对象,
Inner inner = dotThis.new Inner();

DotThis outer = inner.outer();
System.out.println(dotThis==outer);
}
}

在不同的位置嵌入内部类

  1. 定义在方法中的内部类
  2. 定义在作用域内的类,比如if作用域
  3. 实现了接口的匿名类
  4. 扩展了非默认构造器的匿名类
  5. 执行字段初始化的匿名类
  6. 通过实例初始化实现构造的匿名类

匿名内部类

没有类名的类,即匿名类,一般这种类只能定义在别的类内部,所以,也叫匿名内部类,它是用来创建一个继承自某个类的匿名类的对象,通过new表达式会自动向上转型为对父类的引用。如下content方法中创建了一个继承自Contents的匿名类对象,该对象在返回时,自动向上转型为Contents类型。即匿名内部类是继承机制的一个简写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface Contents{
int value();
}
public class Parcel {
public Contents content(){
//匿名内部类,即创建一个继承自Contents的匿名类对象
return new Contents() {
int value = 10;
@Override
public int value() {
return value;
}
};
}
public static void main(String[] args) {
Parcel parcel = new Parcel();
Contents content = parcel.content();
System.out.println(content.value());
}
}

通过有参构造来创建匿名内部类

由于Wrapping类中只有一个带参的构造器,在创建继承自Wrapping的匿名内部类对象时,如果不传参数,程序会因找不到构造器而报错。

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
class Wrappinng{
private int i;
public Wrappinng(int x){
i = x;
}
public int value(){
return i;
};
}
public class Parcel2 {
//当基类没有默认构造器时,就需要传递相应的参数到基类的有参构造器
public Wrappinng wrappinng(int x){
return new Wrappinng(x){
@Override
public int value() {
return super.value() * 10;
}
};
}

public static void main(String[] args) {
Parcel2 parcel2 = new Parcel2();
Wrappinng wrappinng = parcel2.wrappinng(10);
System.out.println(wrappinng.value());
}
}

通过实例初始化来实现匿名内部类的构造效果

由于匿名内部类没有类名,所以不能为该类去定义一个构造器,但可以使用初始化实例来实现构造器的效果,如下{…}就是一个初始化代码,可以起到构造的效果。

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
class Destination{
public String readLabel(){return "";}
}
public class Parcel3 {
//当传入的参数是在匿名内部类中使用的就必须声明是final
public Destination destination(final String dest, final float price){
return new Destination(){
private int cost;
//通过对实例的初始化来达到在匿名内部类创建一个构造器的效果
{
cost = Math.round(price);
if (cost>100)
System.out.println("Over budget");
}
private String label = dest;
@Override
public String readLabel(){return label;}
};
}
public static void main(String[] args) {
Parcel3 parcel3 = new Parcel3();
Destination destination = parcel3.destination("ABC", 110F);
System.out.println(destination.readLabel());
}
}

使用匿名内部类来简化工厂方法

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
interface Game{
boolean move();
}
interface GameFactory{
Game getGame();
}
class Checkers implements Game{
private int move = 0;
private static final int MOVE = 3;
public static GameFactory gameFactory = new GameFactory(){
@Override
public Game getGame() {
return new Checkers();
}
};
@Override
public boolean move() {
System.out.println("Checkers Move "+move);
return ++move != MOVE;
}
}
class Chess implements Game{
private int move = 0;
private static final int MOVE = 4;
//静态的匿名内部类对象,可以来简化工厂模式的设计
public static GameFactory gameFactory = new GameFactory() {
@Override
public Game getGame() {
return new Chess();
}
};
@Override
public boolean move() {
System.out.println("Chess Move "+move);
return ++move != MOVE;
}
}
public class Games {
public static void playGame(GameFactory gameFactory){
Game game = gameFactory.getGame();
while (game.move());
}
public static void main(String[] args) {
playGame(Checkers.gameFactory);
playGame(Chess.gameFactory);
}
}

嵌套类

如果不需要内部类对象与其外部类的对象之间有联系,那可以将内部类声明为static,这通常称为嵌套类。嵌套类一般表示着三种含义:1、不需要外部类的对象;2、不能从嵌套类的对象中访问非静态的外部类成员;3、普通内部类不能有static数据和static方法,但嵌套类可以包含这些东西。

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
public class Parcel4 {
private static int i;
private static class ParcelContent implements Contents{
private int i = 10;
@Override
public int value() {
return i;
}
}
private static class ParcelDestination extends Destination {
private String label;

private ParcelDestination(String label) {
this.label = label;
i = 20;
}

@Override
public String readLabel() {
return label+i;
}
static class AnotherLevel{
static int x = 10;
public static void f(){}
}
}
public static Contents contents(){
return new ParcelContent();
}
public static Destination destination(){
return new ParcelDestination("ParcelDestination1");
}
public static void main(String[] args) {
//有static修饰的内部类称为嵌套类,它不会被外部类所限制
//在创建该内部类时,不用通过外部对象来进行创建,因为嵌套类没有this这个关键字
//它就相当于外部类的一个静态代码块,包含数据和方法
Contents c = contents();
Destination d = destination();
System.out.println(c.value());
System.out.println(d.readLabel());
//所以在这里可以直接创建嵌套类的对象
ParcelDestination parcelDestination = new ParcelDestination("ParcelDestination2");
System.out.println(parcelDestination.label);
System.out.println(ParcelDestination.AnotherLevel.x);
}
}

闭包与回调

关于闭包,有下面几种解释:

  1. 闭包是一个可调用的对象,它包含了一些信息,这些信息来自于创建它的作用域。
  2. 闭包是指可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。
  3. 是引用了自由变量的函数。这个函数通常被定义在另一个外部函数中,并且引用了外部函数中的变量。

在Java中,闭包是通过“接口和内部类来实现的”,可以看出,内部类是面对对象的闭包,它不仅包含了外部类对象的信息,还自动拥有了一个指向此外部类对象的引用,即内部类可以操作所有成员。因此可以把非静态内部类当成面向对象领域的闭包。那么,通过这种仿闭包的非静态内部类可以很方便地实现回调,这是一种非常灵活的功能。

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
interface Incrementable{
void increment();
}

class Callee1 implements Incrementable{
private int i = 0;
@Override
public void increment() {
i++;
System.out.println("Callee1:"+i);
}
}

class MyIncrement{
private int i = 0;
public void increment() {
i++;
System.out.println("MyIncrement:"+i);
}
}
class Callee2 extends MyIncrement{
//Closure是在Callee2作用域范围内的闭包,在闭包内可以操作Callee2范围的所有成员
private class Closure implements Incrementable{
private int i = 0;
@Override
public void increment() {
Callee2.this.increment();
i++;
System.out.println("Closure:"+i);
}
}
public Incrementable getIncrementable(){
return new Closure();
}
}
class Caller{
private Incrementable incrementable;

public Caller(Incrementable incrementable) {
//当一个内部类对象传入时,Caller对象就可以使用此引用来回调Callee类,使其能够轻松地调用闭包中的成员,来间接操作Callee2作用域的成员
this.incrementable = incrementable;
}
public void go(){
incrementable.increment();
}
}
public class Callbacks {
public static void main(String[] args) {
Caller caller1 = new Caller(new Callee1());
//在这里caller2表示回调,而new Callee2().getIncrementable()则表示一个闭包
Caller caller2 = new Caller(new Callee2().getIncrementable());
caller1.go();
caller2.go();
}
}

内部类的继承

当一个类是继承自内部类时,它的一部分成员是依赖于其继承的内部类的外部类的对象引用,所以,该类在构造时,必须传入一个指向外部类对象的引用,指定由此外部类去创建内部类,具体做法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class WithInner{
class Inner{
public void call(){
WithInner.this.print();
}
}
public void print(){
System.out.println("Print in withinner.");
}
}
public class InheritInner extends WithInner.Inner {
InheritInner(WithInner withInner) {
//想要构造一个继承自内部类的对象,就必须传入一个指向外部类对象的引用
withInner.super();
}
public void call(){
super.call();
}
public static void main(String[] args) {
WithInner withInner = new WithInner();
InheritInner inheritInner = new InheritInner(withInner);
inheritInner.call();
}
}

内部类标识符

由于在编译后,每个类都会产生一个.class文件,为了区别内部类和外部类,一般都在编译的时候在外部类的后面加一个”$”,比如,上面的例子生成的class文件是:WithInner$Inner.class,若是匿名内部类,系统会生成一个数字作为匿名类的名字。

-------------本文结束感谢您的阅读-------------