400-616-5551

您所在位置: 首页> 学习课程> java培训学校 | java编程思想--内部类

java培训学校 | java编程思想--内部类

发布百知教育 来源:学习课程 2019-10-15

可以将一个类的定义放在另一个类的定义内部,这就是内部类。

内部类是一种非常有用的特性,因为它允许你把逻辑相关的类组织在一起,并控制位于内部的类的可视性。但是必须了解,内部类与组合是完全不同的概念。


在最初,内部类看起来就像是一种代码隐藏机制:将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,他能了解外围类,并能与之通信。


一、创建内部类


public

 

class

 

Parcel1

 

{

  

class

 

Contents

 

{

    

private

 

int

 i 

=

 

11

;

    

public

 

int

 value

()

 

{

      

return

 i

;

    

}

  

}

  

class

 

Destination

 

{

    

private

 

String

 label

;

    

Destination

(

String

 whereTo

)

 

{

      label 

=

 whereTo

;

    

}

    

String

 readLabel

()

 

{

      

return

 label

;

    

}

  

}

  

public

 

void

 ship

(

String

 dest

)

 

{

    

Contents

 c 

=

 

new

 

Contents

();

    

Destination

 d 

=

 

new

 

Destination

(

dest

);

    

System

.

out

.

println

(

d

.

readLabel

());

  

}

  

public

 

static

 

void

 main

(

String

[]

 args

)

 

{

    

Parcel1

 p 

=

 

new

 

Parcel1

();

    p

.

ship

(

"Tasmania"

);

  

}

}

当我们在ship方法里面使用内部类时,与使用普通的类没有什么不同。在这里,实际的区别只是内部类的名字是嵌套在Parcel1里面的。


更典型的是,外部类将有一个方法,该方法返回一个指向内部类的引用,就像在to()和contents()方法中看到的那样:


public

 

class

 

Parcel2

 

{

  

class

 

Contents

 

{

    

private

 

int

 i 

=

 

11

;

    

public

 

int

 value

()

 

{

      

return

 i

;

    

}

  

}

  

class

 

Destination

 

{

    

private

 

String

 label

;

    

Destination

(

String

 whereTo

)

 

{

      label 

=

 whereTo

;

    

}

    

String

 readLabel

()

 

{

      

return

 label

;

    

}

  

}

  

public

 

Destination

 to

(

String

 s

)

 

{

    

return

 

new

 

Destination

(

s

);

  

}

  

public

 

Contents

 contents

()

 

{

    

return

 

new

 

Contents

();

  

}

  

public

 

void

 ship

(

String

 dest

)

 

{

    

Contents

 c 

=

 

new

 

Contents

();

    

Destination

 d 

=

 to

(

dest

);

    

System

.

out

.

println

(

d

.

readLabel

());

  

}

  

public

 

static

 

void

 main

(

String

[]

 args

)

 

{

    

Parcel2

 parcel2 

=

 

new

 

Parcel2

();

    parcel2

.

ship

(

"Tasmania"

);

    

Parcel2

 q 

=

 

new

 

Parcel2

();

    

Parcel2

.

Contents

 c 

=

 q

.

contents

();

    

Parcel2

.

Destination

 d 

=

 q

.

to

(

""

);

  

}

}

二、链接到外部类


到目前为止,内部类似乎还只是一种名字隐藏和组织代码的模式。他还有其他的用途。当生成一个内部类的对象时,此对象与制造他的外围对象之间就有了一种联系,所以他能访问其外围对象的所有成员,二不需要任何条件。此外,内部类还拥有其外围类的所有元素的访问权。下面的例子说明了这点:


public

 

class

 

Sequence

 

{

  

private

 

Object

[]

 items

;

  

private

 

int

 next 

=

 

0

;

  

public

 

Sequence

(

int

 size

)

 

{

    items 

=

 

new

 

Object

[

size

];

  

}

  

public

 

void

 add

(

Object

 x

)

 

{

    

if

 

(

next 

<

 items

.

length

)

 

{

      items

[

next

++]

 

=

 x

;

    

}

  

}

  

public

 

Selector

 selector

()

 

{

    

return

 

new

 

SequenceSelector

();

  

}

  

private

 

class

 

SequenceSelector

 

implements

 

Selector

 

{

    

private

 

int

 i 

=

 

0

;

    

@Override

    

public

 

boolean

 end

()

 

{

      

return

 i 

==

 items

.

length

;

    

}

    

@Override

    

public

 

Object

 current

()

 

{

      

return

 items

[

i

];

    

}

    

@Override

    

public

 

void

 next

()

 

{

      

if

 

(

<

 items

.

length

)

 i

++;

    

}

  

}

  

public

 

static

 

void

 main

(

String

[]

 args

)

 

{

    

Sequence

 sequence 

=

 

new

 

Sequence

(

10

);

    

for

 

(

int

 i 

=

 

0

;

 i 

<

 

10

;

 i

++)

 

{

      sequence

.

add

(

Integer

.

toString

(

i

));

      

Selector

 selector 

=

 sequence

.

selector

();

    

}

  

}

}

interface

 

Selector

 

{

  

boolean

 end

();

  

Object

 current

();

  

void

 next

();

}

最初看到SequenceSelector,可能会觉得他只不过是另一个内部类罢了。但是end()、current()和next()都用到了objects,这是一个引用,他并不是SequenceSelector的一部分,而是外围类的一个private字段。然而内部内可以访问其外围类的方法和字段,就像自己拥有他们似的,这带来的很大的方便。


所以内部类自动拥有对外围类所有成员的访问权是如何做到的呢?当某个外围类的对象创建了一个内部类对象时,此内部类必定会秘密的捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。编译器会帮你处理所有的细节。但你现在可以看到:内部类的对象只能在与其外围类的对象相关联的情况下才能被创建(内部类不是static)时。构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。


三、使用this与new


如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和this。这样产生的引用自动具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。


public

 

class

 

DotThis

 

{

  

void

 f

()

 

{

    

System

.

out

.

println

(

"DotThis.f()"

);

  

}

  

public

 

class

 

Inner

 

{

    

public

 

DotThis

 outer

()

 

{

      

return

 

DotThis

.

this

;

    

}

  

}

  

public

 

Inner

 inner

()

 

{

    

return

 

new

 

Inner

();

  

}

  

public

 

static

 

void

 main

(

String

[]

 args

)

 

{

    

DotThis

 dotThis 

=

 

new

 

DotThis

();

    

DotThis

.

Inner

 dti 

=

 dotThis

.

inner

();

    dti

.

outer

();

  

}

}



有时你可能想要告知其他对象,去创建其某个内部类的对象。要实现此目的,你必须在new表达式中提供对其他外部类对象的引用,这是需要使用.new语法:


class

 

DotNew

 

{

  

public

 

class

 

Inner

 

{

  

}

  

public

 

static

 

void

 main

(

String

[]

 args

)

 

{

    

DotNew

 dotNew 

=

 

new

 

DotNew

();

    

DotNew

.

Inner

 dni 

=

 dotNew

.

new

 

Inner

();

  

}

}

在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗的连接到创建他的外部类对象上。但是,你如果创建的是静态内部类,那么他就不需要对外部类的引用。


四、内部类和向上转型


当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。这是因为此内部类-某个接口的实现-能够完全不可见,并且不可用。所得到的只是指向基类或者接口的引用,所以能够很方便的隐藏实现细节。


public

 

interface

 

Destination

 

{

  

String

 readLabel

();

}

public

 

interface

 

Contents

 

{

  

int

 value

();

}

现在Contents和Destination表示客户端程序员可用的接口。


当取得了一个指向基类或接口的引用时,甚至可能无法找出他确切的类型,例如:


public

 

class

 

Parcel4

 

{

  

private

 

class

 

PContents

 

implements

 

Contents

 

{

    

private

 

int

 i 

=

 

11

;

    

@Override

    

public

 

int

 value

()

 

{

      

return

 i

;

    

}

  

}

  

protected

 

class

 

PDestination

 

implements

 

Destination

 

{

    

private

 

String

 label

;

    

public

 

PDestination

(

String

 label

)

 

{

      

this

.

label 

=

 label

;

    

}

    

@Override

    

public

 

String

 readLabel

()

 

{

      

return

 label

;

    

}

  

}

  

public

 

Destination

 destination

(

String

 s

)

 

{

    

return

 

new

 

PDestination

(

s

);

  

}

  

public

 

Contents

 contents

()

 

{

    

return

 

new

 

PContents

();

  

}

}

class

 

TestParcel

 

{

  

public

 

static

 

void

 main

(

String

[]

 args

)

 

{

    

Parcel4

 parcel4 

=

 

new

 

Parcel4

();

    

Contents

 c 

=

 parcel4

.

contents

();

    

Destination

 d 

=

 parcel4

.

destination

(

"Tasmania"

);

  

}

}

Parcel4增加了一些新东西:内部类PContents是private,所以除了Parcel4,没有人能访问他。PDestination是protected,所以只有Parcel4及其子类、还有与Parcel4同一个包中的类能访问PDestination,其他类都不能访问PDestination。这意味着客户端程序员想了解或者访问这些成员,是要受到限制的。实际上,甚至不能向下转型为private内部类或protected类,因此不能访问其名字,就像TestParcel类中看到的那样。


于是private内部类给类设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。


此外,从客户端程序员的角度来看,由于不能访问任何新增加的、原本不属于公共接口的方法,所以扩展接口是没有价值的。这也给java编译器提供了生成更高效代码的机会。


五、在方法和作用域内的内部类


到目前为止,读者所看到的都是内部类的典型用途。通常,如果所读、写的代码包含了内部类,那么他们都是平凡的内部类,简单并且容易理解。然而,内部类的语法覆盖了大量其他的更加难以理解的技术。例如,可以在一个方法里面或者任意的作用域定义内部类。这么做有两个理由:


如前所示,你实现了某类型的接口,于是可以创建并返回对其的引用

你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。

在后面的例子中,先前的代码将被修改,以用来实现:


一个定义在方法中的类

一个定义在作用域内的类,此作用域在方法的内部

一个实现了接口的匿名类

一个匿名类,他扩展了非默认构造器的类

一个匿名类,他执行字段初始化

一个匿名类,它通过实例初始化实现构造(匿名类不可能有构造器)

第一个例子展示了在方法作用域内(而不是在其他类的作用域内)创建一个完整的类,这叫做局部内部类:


public

 

class

 

Parcel5

 

{

  

public

 

Destination

 destination

(

String

 s

)

 

{

    

class

 

PDestination

 

implements

 

Destination

 

{

      

private

 

String

 label

;

      

@Override

      

public

 

String

 readLabel

()

 

{

        

return

 

null

;

      

}

    

}

    

return

 

new

 

PDestination

();

  

}

  

public

 

static

 

void

 main

(

String

[]

 args

)

 

{

    

Parcel5

 parcel5 

=

 

new

 

Parcel5

();

    

Destination

 d 

=

 parcel5

.

destination

(

"test"

);

  

}

}

PDestination类是destination()方法的一部分,而不是Parcel5的一部分。所以,在destination()之外不能访问PDestination。注意出现在return语句中的向上转型--返回的类型是Destination的引用,他是PDestination的基类。当然,在destination()中定义了内部类PDestination,并不意味着dest()方法执行完毕,PDestination就不可用了。


你剋有在同一个子目录下的任意类中对某个内部类使用类标识符PDestination,这并不会引起命名冲突。


下面的例子展示了如何在任意的作用域内嵌入一个内部类:


public

 

class

 

Parcel6

 

{

  

private

 

void

 internalTracking

(

boolean

 b

)

 

{

    

if

 

(

b

)

 

{

      

class

 

TrackingSlip

 

{

      

}

    

}

 

  

}

}

TrackingSlip类被嵌入在if语句的作用域内,这并不是说该类的创建是有条件的,他其实与其他类一起编译过了。然而,在定义TrackingSlip的作用域外,他是不可用的,除此之外与普通的类一样。


六、匿名内部类


下面的例子看起来奇怪:


public

 

class

 

Parcel7

 

{

  

public

 

Contents

 contents

()

 

{

    

return

 

new

 

Contents

()

 

{

      

@Override

      

public

 

int

 value

()

 

{

        

return

 

0

;

      

}

    

};

  

}

  

public

 

static

 

void

 main

(

String

[]

 args

)

 

{

    

Parcel7

 parcel7 

=

 

new

 

Parcel7

();

    

Contents

 c 

=

 parcel7

.

contents

();

  

}

}

这种奇怪的语法是指:创建一个继承自Contents的匿名类的对象。通过new 表达式返回的引用被自动向上转型为对Contents的引用。


在这个匿名内部类中,使用了默认的构造器来生成Contents。下面的代码展示的是,如果你的基类需要一个有参数的构造器,应该怎么办。


public

 

class

 

Parcel8

 

{

  

public

 

Wrapping

 wrapping

(

int

 x

)

 

{

    

return

 

new

 

Wrapping

(

x

)

 

{

      

@Override

      

public

 

int

 value

()

 

{

        

return

 

super

.

value

();

      

}

    

};

  

}

}

class

 

Wrapping

 

{

  

private

 

int

 i

;

  

public

 

Wrapping

(

int

 i

)

 

{

    

this

.

=

 i

;

  

}

  

public

 

int

 value

()

 

{

    

return

 i

;

  

}

}

如果定义一个匿名内部类,并且希望他使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的。


如果想要做一些类似构造器的行为,应该怎么办呢?在匿名内部类中不可能有命名构造器,但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果。如下所示:


public

 

class

 

AnonymousConstructor

 

{

  

public

 

static

 

Base

 getBase

(

int

 i

)

 

{

    

return

 

new

 

Base

(

i

)

 

{

      

{

        

System

.

out

.

println

(

"Inside instance"

);

      

}

      

@Override

      

public

 

void

 f

()

 

{

      

}

    

};

  

}

  

public

 

static

 

void

 main

(

String

[]

 args

)

 

{

    

Base

 base 

=

 getBase

(

1

);

    base

.

f

();

  

}

}

abstract

 

class

 

Base

 

{

  

public

 

Base

(

int

 i

)

 

{

    

System

.

out

.

println

(

"base"

);

  

}

  

public

 

abstract

 

void

 f

();

}

在这个例子中,不要求变量i一定是final的。因为i被传递给匿名类的基类的构造器,他并不会在匿名类内部被直接使用。


七、嵌套类


如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static。这通常称为嵌套类。想要理解static应用于内部类时的含义,就必须记住,普通的内部类对象隐式的保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样的。嵌套类意味着:


要创建嵌套类的对象,不需要其外围类的对象

不能从嵌套类的对象中访问非静态的外围类对象

嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含这些东西:


public

 

class

 

Parcel11

 

{

  

private

 

static

 

class

 

ParcelContents

 

implements

 

Contents

 

{

    

private

 

int

 i 

=

 

11

;

    

@Override

    

public

 

int

 value

()

 

{

      

return

 

0

;

    

}

  

}

  

protected

 

static

 

class

 

ParcelDestination

 

implements

 

Destination

 

{

    

private

 

String

 label

;

    

public

 

static

 

void

 f

()

 

{

    

}

    

static

 

int

 x 

=

 

10

;

    

static

 

class

 

AnotherLevel

 

{

      

public

 

static

 

void

 f

()

 

{

      

}

      

static

 

int

 x 

=

 

10

;

    

}

    

private

 

ParcelDestination

(

String

 whereTo

)

 

{

      label 

=

 whereTo

;

    

}

    

@Override

    

public

 

String

 readLabel

()

 

{

      

return

 

null

;

    

}

  

}

  

public

 

static

 

Destination

 destination

(

String

 s

)

 

{

    

return

 

new

 

ParcelDestination

(

s

);

  

}

  

public

 

static

 

Contents

 contents

()

 

{

    

return

 

new

 

ParcelContents

();

  

}

  

public

 

static

 

void

 main

(

String

[]

 args

)

 

{

    

Contents

 c 

=

 contents

();

    

Destination

 destination 

=

 destination

(

"test"

);

  

}

}

在main()中,没有任何Parcel11的对象是必须的。而是使用选取static成员的普通语法来调用方法--这些方法返回对Contents和Destination的引用。


就像前面所说的,在一个普通(非static)内部类中,通过一个特殊的this引用可以链接到外围类对象。嵌套类就没有这个特殊的this引用。


1、接口内部的类


正常情况下,不能在接口内部防止任何代码,但是嵌套类可以作为接口的一部分。放到接口中的任何类都自动是public和static的。因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外围接口。


public

 

interface

 

ClassInInterface

 

{

    

void

 howdy

();

    

class

 

Test

 

implements

 

ClassInInterface

 

{

    

}

}

如果你想要创建某些公共代码,使得他们可以被某个接口的所有不同实现所公用,那么使用接口内部的嵌套类会显得很方便。


2、从多层嵌套类中访问外部类的成员


一个内部类被嵌套多少层并不重要,他能透明的访问所有它所嵌入的外围类的所有成员。


class

 MNA 

{

    

private

 

void

 f

()

 

{}

    

class

 A 

{

        

private

 

void

 B

()

 

{

            

private

 

void

 g

()

 

{}

        

}

        

class

 B 

{

            

void

 h

()

 

{

                g

();

                f

();

            

}

        

}

    

}

}

八、为什么需要内部类


一般来说,内部类继承自某个类或者某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。


内部类必须要回答的一个问题是:如果只是需要一个接口的引用,为什么不通过外围类来实现这个接口呢?答案是:如果这能满足需求,那就应该这样做。那么内部类实现一个接口与外围类实现这个接口有什么区别呢?答案是:后者不是总能享受到接口带来的方便,有时候需要用到接口的实现。所以,使用内部类最吸引人的原因是:每个内部类都能独立的继承一个接口的实现,所以无论外围类是否已经继承了某个接口的实现,对于内部类都没有影响。


如果没有内部类提供的‘可以继承多个具体或抽象的类的能力,一些设计和编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效的实现了多重继承。也就是说,内部类允许继承多个非接口类型(类或者抽象类)。


为了看到更多的细节,让我们考虑这样一种情形:即必须在一个类中以某种形式实现两个接口。由于接口的灵活性,你有两种选择,使用单一类,或者使用内部类。


public

 

class

 

MultiInterfaces

 

{

  

interface

 A 

{

  

}

  

interface

 B 

{

  

}

  

;

  

class

 X 

implements

 A

,

 B 

{

  

}

  

class

 Y 

implements

 A 

{

    B makeB

()

 

{

      

return

 

new

 B

()

 

{

      

};

    

}

  

}

}

当然,这里假设在两种方式下的代码结构都确实有逻辑意义。然而遇到问题的时候,通常问题本身就能给出某些指引,告诉你是应该使用单一类,还是使用内部类。但如果没有任何其他限制,从实现的观点来看,前面的例子并没有什么区别,他们都能正常运作。


但是如果拥有的是抽象的类或者具体的类,而不是接口,那就只能使用内部类才能实现多重继承。


public

 

class

 

MultiImplementation

 

{

  

class

 D 

{

  

}

  

abstract

 

class

 E 

{

  

}

  

class

 Z 

extends

 D 

{

    E makeE

()

 

{

      

return

 

new

 E

()

 

{

      

};

    

}

  

}

}

但如果不需要解决多重继承的问题,那么自然可以用别的方式进行编码,而不需要使用内部类。但如果使用内部类,还可以获得一些其他的特性:


内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围内对象的信息相互独立

在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类

创建内部类对象的时刻并不依赖于外围类对象的创建

内部类没有令人迷惑的is-a关系,他就是一个独立的实体

九、内部类的继承


因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情会变得有些复杂。问题在于,那个指向外围类对象的秘密的引用必须被初始化,而在子类不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清他们之间的关联:


class

 

WithInner

 

{

  

class

 

Inner

 

{

  

}

}

public

 

class

 

InheritInner

 

extends

 

Inner

 

{

  

public

 

InheritInner

(

WithInner

 withInner

)

 

{

    withInner

.

super

();

  

}

  

public

 

static

 

void

 main

(

String

[]

 args

)

 

{

    

WithInner

 withInner 

=

 

new

 

WithInner

();

    

InheritInner

 inheritInner 

=

 

new

 

InheritInner

(

withInner

);

  

}

}

可以看到,InheritInner只继承内部类,而不是外围类。但是要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用。此外,必须在构造器内部使用如下语法:


enclosingClassReference

.

super

();

十、局部内部类


既然局部内部类的名字在方法外是不可见的,那为什么我们仍然使用局部内部类而不是匿名内部类呢?唯一的理由是我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类是能用于实例化。


所以使用局部内部类而不是用匿名内部类的另一个理由就是,需要不止一个该内部类的对象。


十一、内部类标识符


由于每个类都会产生一个.class,其中包含了如何创建该类型的对象的全部信息,你可能猜到了,内部类也必须生成一个.class文件以包含他们的class对象信息。这些类文件的命名有严格的规则:外围类的名字,加上$,再加上内部类的名字。


如果内部类是匿名的,编译器会简单的产生一个数字作为其标识符。如果内部类是嵌套在别的内部类中,只需直接将他们的名字加载其外围类标识符与$的后面



java培训学校:http://www.baizhiedu.com/java2019





上一篇:java培训学校 | Java 编程技巧之数据结构

下一篇:应届生去公司找个Java程序员的职位需要什么技能?

相关推荐

www.baizhiedu.com

有位老师想和您聊一聊

关闭

立即申请