2008年7月3日星期四

浅析Java内部类与嵌套类

浅析Java内部类与嵌套类

Abstraction:

本文描述了Java语言的嵌入类、内部类的具体分类和行为表现。

1 概述

我们知道,Java是一种完全的面向对象的语言,作为对象的灵魂,类的种类是多种多样的。类大致可以分外部类和内部类两种,外部类就是我们通常使用的类,而内部类的使用要比外部类少的多,最常见的是GUI事件侦听器。内部类的应用虽然不多,但是如果能够有效的使用内部类,能达到事半功倍的效果。废话不多说,下面开始:

2 内部类和嵌套类

要讨论内部类和嵌套类,首先要分清他们两者的区别于联系。

首先,内部类(Inner Classes)和嵌套类(Nested Classes)是指在一个类里面定义的另一个类。其次无论是内部类还是嵌套类,在编译时都被当作一个独立的整体。对于访问他们的其他对象来说(假如他们对这个类来说是可见的)他们的使用和我们通常用的类是一样的。

但是,内部类和嵌套类的区别在于:

1. 嵌套类是静态的,而内部类不是,也就是说嵌套类的实例化不需要外部类的实例。但是内部类是需要这个实例的。

2. 嵌套类可以任意声明静态成员,内部类不允许声明除了编译时常量以外的任何静态成员。这一限制也适用于静态初始化函数。

3. 嵌套类都是命名的,匿名的类声明不能声明运行时静态成员(不管声明是不是静态的)。

注意:类内部声明的接口都属于嵌入类。

下面我们来分别讨论一下他们的具体行为表现。

3 内部类

内部类是指在一个类里面以非静态形式声明的另一个类。内部类的实例化需要外部实例的存在。一个类可以拥有多个内部类,一个外部实例可以拥有多个内部类的实例。但是内部类有且只有一个外部实例,并且在实例化时就已经指定,无法更改。

3.1 内部类的分类

内部类根据访问权限不同可以分为以下一种类型:普通内部类局部内部类

普通内部类和局部内部类主要的区别在于作用域和访问权限的不同,普通内部类可以被所有人访问(只要访问控制符允许),而局部内部类的作用域更像一个变量,只能在定义它的函数内部被使用,其他人是无法使用这个类的。而且局部类可以访问定义它的函数中的final变量。

根据声明方式不同又可以分为:命名内部类匿名内部类。命名内部类和匿名内部类的区别在于:

首先,命名类可以抽象的。匿名类不能为抽象,事实上,匿名内部类在编译时被隐形的声明为最终的(final)

其次,命名类声明可以继承一个父类并实现多个接口,匿名类只能有一个父类(或者接口)。

再次,命名类可以被多次实例化,而匿名类只能在定义时被实例化一次。

最后,命名类可以声明多个构造函数并控制访问哪一个父类的构造函数,匿名类无法声明构造函数。

下面分别描述这些区别:

3.2 普通内部类

普通内部类是指在类成员定义中定义的类,这些类可以拥有访问控制符(public, private)。如果访问控释符允许,则这些类可以被外面直接应用。普通命名内部类可以声明为一个接口,或者是抽象的。嵌入类其实是特殊的普通内部类,但由于其特殊性,故在下面独立讨论。

3.2.1 声明

3.2.1.1 命名类

class OuterClass{

//Outer class deflation
class InnerNamedClass{
//Inner class definition

}
}

3.2.1.2 匿名类

声明匿名类时,可以使用一个类或者接口作为他的父类。

class OuterClass{
//Outer class deflation
Object unnamedObject = new Object(){
//Inner class definition
}
}

3.2.2 实例化:

3.2.2.1 命名类

外部或静态方法

OuterClass outerObject = new OuterClass();
OuterClass.InnerNamedClass innerObject = outerObject.new InnerNamedClass();

内部

InnerNamedClass innerObject = new InnerNamedClass();

3.2.2.2 匿名类

定义时即完成实例化

3.2.3 访问权限:

3.2.3.1 内部实例对外部实例的访问权限为:

外部类定义或继承的所有字段

3.2.3.2 外部实例对内部实例的访问权限为:

内部类定义或继承的所有字段

3.2.3.3 其他对象对内部实例的访问权限为:

若内部类不可见,则只能访问其超类定义的字段

若内部类可见,则可访问内部实例的非私有字段,具体情况与通常的类类似

3.2.4 备注:

内部类的外部实例是在构造时作为参数传给构造函数的,在SUN JDK中,保存外部实例的字段通常被声明为:

final OutterClass this$0;

3.3 局部内部类

局部内部类,是指在函数体内声明的类,这种类是局域性的,只在函数内声明后有效,他最大的特点是:可以访问定义它的函数中的final变量。

3.3.1 声明:

3.3.1.1 命名类:

class OuterClass{
//Outer class deflation
void aMethod(){
class InnerNamedClass{
//Inner class definition
}
}
}

3.3.1.2 匿名类:

class OuterClass{
//Outer class deflation
void aMethod(){
Object unnamedObject = new Object(){
//Inner class definition
}
}
}

3.3.2 实例化:

3.3.2.1 命名类:

局部类的作用域仅限定义该类的方法中,无法在外部实例化或访问,实例化方法与通常类相同

3.3.2.2 匿名类:

定义时即完成实例化

3.3.3 访问权限:

3.3.3.1 内部实例对外部实例的访问权限为:

外部类定义或继承的所有字段

定义该内部类的方法在定义类之前所定义的所有final变量

3.3.3.2 外部实例对内部实例的访问权限为:

在定义该内部类的方法中可以访问内部类定义或继承的所有字段

在外部实例的其他字段中只能访问其超类定义的字段

3.3.3.3 其他对象对内部实例的访问权限为:

只能访问其超类定义的字段

3.3.4 备注:

当试图访问一个final变量时,编译器实际上把该变量的值赋值给内部类的一个隐含字段中。编译器会自动的在构造函数中添加相应的参数。在SUN JDK中,这个隐含字段通常被命名为:val$varname

3.4 使用内部类的注意事项:

本节包括了一些使用内部类时需要注意的事项,这些事项对所有内部类都适用。

3.4.1 静态成员

首先需要注意的是,内部类不能非常量声明静态成员,任何静态成员的声明都会被当成编译错误,例如:

InnerClasses.java:12: 内部类不能有静态声明

static String a;

^

1 错误

先要了解更加详细的信息,请参考附录A

3.4.2 访问外部实例

要在一个内部实例中访问外部实例,请使用:

OuterClass.this

3.4.3 字段覆盖

在内部类中声明的与外部类同签名的字段将覆盖外部字段,要访问外部字段请使用外部实例对象前缀。

OuterClass.this.field

OuterClass.this.Method();

4 嵌套类

嵌套类(Nested Classes)其实是普通内部类的一种特殊形式,首先它的声明是静态的,这就表示了这个类不需要外部实例。也表示了他不能访问外部类的实例字段。但是相应的,嵌套类可以拥有非常量静态成员。事实上,JDK通常把嵌套类当成一个具有特殊名字的独立类。

另外,嵌套类还拥有一种特殊形式:匿名嵌套类。前面说过,匿名类都是内部类,但是匿名嵌套类是一个特例,从理论上讲,他既不属于嵌套类,也不属于内部类。匿名嵌套类 不允许拥有非常量静态成员,但是他也没有外部实例供访问。

4.1 声明:

4.1.1 命名类:

class OuterClass{

//Outer class deflation
static class StaticInnerNamedClass{
//Inner class definition

}
}

4.1.2 匿名类:

class OuterClass{
//Outer class deflation
static Object unnamedObject = new Object(){
//Inner class definition
}
}

4.2 实例化

4.2.1 内部

StaticInnerNamedClass staticInnerNamedObject = new StaticInnerNamedClass();

4.2.2 外部

OuterClass.StaticInnerNamedClass staticInnerNamedObject = new OuterClass.StaticInnerNamedClass();

4.3 访问权限:

4.3.1 内部实例对外部实例的访问权限为:

外部类定义或继承的所有静态字段

4.3.2 外部实例对内部实例的访问权限为:

与通常内部类相同

4.3.3 其他对象对内部实例的访问权限为:

与通常内部类相同

A. 附录(Appendix

A) 为什么内部类不能声明静态成员?

关于这个问题,MS JLS没有给出一个明确的答案(还是我没找到么?)。后来我在Usenet的新闻组上发贴子问了一下,结果大概是这样的:

1) 匿名类使用非常量静态成员是不必要的,因为匿名类只有一个实例

2) 命名内部类如果允许使用静态 的话会使内部类显得过分独立,应该尽量使用外部类的设施。

(原帖见https://groups.google.com/group/comp.lang.java.programmer/browse_thread/thread/0f109738f2f089d9?hl=en#d61250f96cf8e96e

Why inner classes can not have static members?

bearice@gmail.com

Feb 4, 10:15 pm

as title.

Lew

Feb 4, 11:07 pm

> bear...@gmail.com wrote:

> as title.

Cultural note: It's considered polite to repeat the question inside the body of the post to facilitate others' ability to read the conversation.

Inner classes can have static members, provided such members are compile-time constants.

Asking "why" the language specifies something is an exercise in telepathy, unless the language designers left some notes or blog entries or white papers behind explaining the rational, which they might have done. GIYF.

I know that static members of inner classes would confuse me. Would such a member only be static within the context of the immediately enclosing instance of some instances of the inner class, or would it apply to all instances of the inner class?

The problem is that inner classes are (generally) instantiated within an instance of their enclosing class. Inner classes are not "static" enough on their own for me to be comfortable with a "static" that operates across all enclosing instance contexts. I suppose I could get used to it if Java were defined that way, but it isn't. It's defined to make inner classes very dependent on their enclosing instances.

If you want static members, use non-inner nested classes, or avoid nested classes altogether.

Why is a static member of an inner class necessary for you? Why is an alternative idiom not acceptable?

Lew

Markus Tazl

Feb 4, 11:18 pm

wrote:

> As title.

Wow, a question in shorthand? :-)

Anyway, Sun's Docs are your friend. Explained here in detail:

http://java.sun.com/docs/books/tutorial/java/javaOO/nested.html, see

Topic: "Inner classes" at the end of the page

Best regards

Markus Tazl

bearice@gmail.com

Feb 5, 4:53 am

- Quoted text -

I see, sorry for my impolite way of asking. And thank all of you for answering me.

I'm not really need a static member in an inner class. just very curious about it when the complier tells me it is an error.

Thanks again.

Lew

Feb 5, 8:38 am

Well, the simple answer is because that's the way it's defined for the language. The rules explicitly allow only compile-time constants to be static members of inner classes. Non-inner nested classes do not have this restriction.

> Inner classes may not declare static initializers (§8.7) or member interfaces.

> Inner classes may not declare static members, unless they are compile-time constant fields (§15.28). ...

> Nested classes that are not inner classes may declare static members freely, in accordance with the usual rules of the Java programming language.

In practice the restriction causes no difficulties. If anything, it makes inner classes easier to deal with, as one can conceptualize the inner class as "belonging" entirely to the enclosing instances that instantiate it without wondering if static members should differ between different enclosing instances.

Language designers always face interesting decisions like this, such as what the value of the remainder operator '%' should be when the denominator is negative. Sometimes they get it wrong, as many feel Java did by not (yet) making generics reifiable. I have never encountered an authoritative explanation for why inner classes in Java cannot have non-compile-time-constant static members; OTOH I've never encountered a situation where the restriction caused trouble.

Inner classes are an odd duck anyway. They're useful for sure, because having a class than can access the members (even private ones) of its enclosing class instance sure helps out - it mitigates the lack of closures, for example.

However, one should always consider using a non-inner nested class or non-public top-level class before creating an inner class that does not need such access.

--

Lew

da...@dagon.net

Feb 5, 9:01 am

bear...@gmail.com wrote:

>Why inner classes can not have static members?

Lew wrote:

>Inner classes can have static members, provided such members are compile-time constants.

Static inner classes can have static members without such a restriction. This is legal:

class Test {

static class Foo {

static int a;

public static void setA(int newA) {

a = newA;

}

}

}

The difference between a static and non-static inner class is important. A static inner class is a fairly normal class, and behaves very similarly to other outer classes. A non-static inner class has an implicit pointer to an instance of it's enclosing class, so doesn't really have a static context to execute in.

Note: anonymous inner classes are always non-static.

--

Lew

Daniel Pitts

Feb 5, 9:57 am

Mark Rafn wrote:

The difference between a static and non-static inner class is important. A static inner class is a fairly normal class, and behaves very similarly to other outer classes. A non-static inner class has an implicit pointer to an instance of it's enclosing class, so doesn't really have a static context to execute in

Note: anonymous inner classes are always non-static.

I actually believe that's called a nested class and has different semantics.

Lew

Feb 5, 11:19 am

Daniel Pitts wrote:

> I actually believe that's called a nested class and has different semantics.

"Static inner class" is a contradiction. Inner classes are never static, in that they are *defined* as nested classes that do not use the "static" keyword.

Nested classes comprise both inner classes and static nested classes.

The relevant section of the JLS is linked up thread.

--

Lew

B) 内部类与嵌套类的命名规则

内部类和嵌套类在编译时被分开成独立的类,编译器对这些独立的类有相应的命名规则,下面就来描述一下这些规则:

B.1匿名类的命名规则

所有的匿名类统一按下列规则重命名:

$

其中,ID是这个匿名类在外部类中出现的位置,例如第一个匿名类就是1,第二个就是2

B.2 命名类的命名规则

命名类重命名规则为:

$

其中InnerClass为内部类的类名

B.3 局部命名类的命名规则

局部命名类的命名规则为:

$

其中,ID是同名的局部类在外部类中出现的位置,例如第一个就是1,第二个就是2InnerClass为内部类的类名

C) 一个完整的例子

interface PublicPrint{
void pubPrint();
}
class OuterClass{
public static final void print(String str){System.out.println(str);}
public String pubStr = "public string";
private String prvStr = "private string";
public static String staPubStr = "static public string";
private static String staPrvStr = "static private string";
//命名内部类 OuterClass$InnerNamedClass
public class InnerNamedClass implements PublicPrint{
public void pubPrint(){
print(getClass().getName());
print(pubStr);
print(prvStr);
print(staPubStr);
print(staPrvStr);
}
}
//匿名内部类 OuterClass$1
public PublicPrint innerUnnamedObject = new PublicPrint(){
public void pubPrint(){
print(getClass().getName());
print(pubStr);
print(prvStr);
print(staPubStr);
print(staPrvStr);
}
};
//命名嵌套 OuterClass$StaticInnerNamedClass
public static class StaticInnerNamedClass implements PublicPrint{
public void pubPrint(){
print(getClass().getName());
print(staPubStr);
print(staPrvStr);
}
}
//匿名嵌套 OuterClass$2
public static PublicPrint staticInnerUnnamedObject = new PublicPrint(){
public void pubPrint(){
print(getClass().getName());
print(staPubStr);
print(staPrvStr);
}
};
public PublicPrint defineNamedEmbedClass(final String arg){
//命名局部 OuterClass$1InnerEmbedNamedClass
class InnerEmbedNamedClass implements PublicPrint{
public void pubPrint(){
print(getClass().getName());
print(pubStr);
print(prvStr);
print(staPubStr);
print(staPrvStr);
print(arg);
}
}
PublicPrint innerEmbedNamedObject = new InnerEmbedNamedClass();
//innerEmbedNamedObject.pubPrint();
return innerEmbedNamedObject;
}
public PublicPrint defineUnnamedEmbedClass(final String arg){
//匿名局部 OuterClass$3
PublicPrint innerEmbedUnnamedObject = new PublicPrint(){
public void pubPrint(){
print(getClass().getName());
print(pubStr);
print(prvStr);
print(staPubStr);
print(staPrvStr);
print(arg);
}
};
//innerEmbedNamedObject.pubPrint();
return innerEmbedUnnamedObject;
}
}
class Runner{
public static void main(String[] args){
//外部实例
OuterClass outerObject = new OuterClass();
//命名内部类
OuterClass.InnerNamedClass innerObject = outerObject.new InnerNamedClass();
innerObject.pubPrint();
//匿名内部类
outerObject.innerUnnamedObject.pubPrint();
//命名嵌入
OuterClass.StaticInnerNamedClass staticInnerNamedObject = new OuterClass.StaticInnerNamedClass();
staticInnerNamedObject.pubPrint();
//匿名嵌入
OuterClass.staticInnerUnnamedObject.pubPrint();
//命名局部
PublicPrint innerEmbedNamedObject = outerObject.defineNamedEmbedClass("Hello");
innerEmbedNamedObject.pubPrint();
//匿名局部
PublicPrint innerEmbedUnnamedObject = outerObject.defineUnnamedEmbedClass("World");
innerEmbedUnnamedObject.pubPrint();
}
}

D) 参考文档

JLS: <>

HCJ: <>

E) 版权声明

Mails referenced in Appendix A belong to their authors.

Java TM, Sun, are trade mark of Sun Microsystems Corp.

This document is released under CDDL.

Copy rights © 2008 Bearice [MDSA Group]

1 条评论: