Java - nested classes 嵌套类

本想学习一下Java中比较重要的几个关键字,然后就看到了static这个关键字,在查找资料的过程中看到了static nested class,然后就又查找了嵌套类的资料,然后源源不断、入坑不出一百年,索性就学习总结一下吧。

在这还是说个小插曲,遇到问题往往都比较喜欢去百度。但是百度的一般都是很多人的博客,如果能找到大牛的博客看还比较好,很多的博客只是图有其表,没有实质内容,很浪费时间。建议可以查找官方技术文档,还有一个推荐的网站:stackoverflow.com,是一个很好的论坛网站,技术知识介绍的比较简单易懂。当然无论是官方文档还是网站都是英文的,需要耐心和基础,学计算机,这应该是必经之路吧,坚持。

可以参考官方教程 : Java Tutorial - Nested Classes
下面是翻译(英文水平有限,大家就嗑着瓜子喝着茶,凑合看吧!):

Java语言允许在一个类里定义另一个类,这样的一个类就叫做嵌套类,可以定义成:
          class OuterClass{
               ...
               class NestedClass{
                    ...
               } 
          }


术语:嵌套类可以分为两类:静态和非静态。声明成静态的嵌套类叫做静态嵌套类(static nested classes)。非静态嵌套类叫做内部类(inner classes)。


为什么要用嵌套类?

使用嵌套类的几个原因如下:
  • 这是一种只用在一个位置的类的逻辑分组方式:如果一个类只对另一个类有用,那么将这个类嵌套在另一个类里可以使他们保持在一起。嵌套这样的“帮助类”可以使他们所在的包更有效率。
  • 增加封装性:考虑两个顶层类,A和B,B需要获取到A中声明为private的成员。通过将类B藏在A里面的方式,类A的成员可以声明为private,并且类B也可以获取到这些成员。除此之外,类B也和外界隔绝。
  • 可以产生易读性更高、可维护性更强的代码:嵌套小规模的类可以使这些代码离被用到的地方更近。

静态嵌套类(Static Nested Classes)

对于类方法和类变量,静态嵌套类可以从外部类中获取。(也就是说,静态嵌套类可以获取到外部类outer class中的类方法和类变量。)跟静态类方法一样,静态嵌套类不能直接获取定义在附属类(enclosing class)实例方法和变量:必须通过对象引用获取。

注意:一个静态嵌套类与外部类(和其他类)的实例成员的交互就和其他顶层类一样。事实上,一个静态嵌套类在行为上就是一个顶层类,只不过这个顶层类为了打包的方便而嵌套在其他顶层类中。

静态嵌套类可以通过附属类(enclosing class)名得到:
          OuterClass.StaticNestedClass
例如,创建静态嵌套类的对象,可以使用语法:
          OuterClass.StaticNestedClass nestedObject = 
                    new OuterClass.StaticNestedClass();

内部类(Inner Classes)

对于实例方法和实例变量,内部类可以从附属类(enclosing class)的实例中获取,并且可以直接获取到对象的方法和字段。因为内部类是和实例进行联系,所以 在内部类中不能自定义任何静态成员

一个内部类的实例对象存在于外部类的实例中。考虑下面类:
          class OuterClass {
               ...
               class InnerClass {
                    ...
               }
          }

InnerClass的实例只存在于OuterClass类的实例中,并且可以直接获取到附属类(enclosing class)的方法和字段。在实例化一个内部类之前,必须先实例化外部类。然后,在外部类对象中创建内部类对象:
          OuterClass.InnerClass innerObject = outerObject.new InnerClass();

有两种特殊内部类:局部内部类(local classes)和匿名内部类(anonymous classes)。

覆盖(Shadowing)

假如在一个特定的范围(scope)(比如内部类和方法定义)内,一个类型(变量或者参数名)的声明与附属范围(enclosing scope)内的其他声明名字相同,那么这个声明将覆盖附属范围的声明。你不用单独使用名字来引用被覆盖声明。下面的例子证明这一点:

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 = 23
this.x = 1
ShadowTest.this.x = 0

局部内部类(Local Classes)

局部类是定义在块中的类。典型的用处是定义在方法体中的局部类。

声明局部类

你可以在任何块中定义局部类。例如,你可以在方法体、for循环、或者if中定义局部类。
下面的例子,LocalClassExample,验证两个电话号码,在 validatePhoneNumber方法中定义了局部类PhoneNumber:
 
public class LocalClassExample {
 
    static String regularExpression = "[^0-9]";
 
    public static void validatePhoneNumber(
        String phoneNumber1, String phoneNumber2) {
     
        final int numberLength = 10;
       
        // Valid in JDK 8 and later:
       
        // int numberLength = 10;
       
        class PhoneNumber {
           
            String formattedPhoneNumber = null;

            PhoneNumber(String phoneNumber){
                // numberLength = 7;
                String currentNumber = phoneNumber.replaceAll(
                  regularExpression, "");
                if (currentNumber.length() == numberLength)
                    formattedPhoneNumber = currentNumber;
                else
                    formattedPhoneNumber = null;
            }

            public String getNumber() {
                return formattedPhoneNumber;
            }
           
            // Valid in JDK 8 and later:

//            public void printOriginalNumbers() {
//                System.out.println("Original numbers are " + phoneNumber1 +
//                    " and " + phoneNumber2);
//            }
        }

        PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
        PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);
       
        // Valid in JDK 8 and later:

//        myNumber1.printOriginalNumbers();

        if (myNumber1.getNumber() == null) 
            System.out.println("First number is invalid");
        else
            System.out.println("First number is " + myNumber1.getNumber());
        if (myNumber2.getNumber() == null)
            System.out.println("Second number is invalid");
        else
            System.out.println("Second number is " + myNumber2.getNumber());

    }

    public static void main(String... args) {
        validatePhoneNumber("123-456-7890", "456-7890");
    }
}

这个例子中验证手机号码的第一步是删掉0-9除外的其他字符,然后检查手机号码是否包括10个数字。这个例子的打印结果:

First number is 1234567890
Second number is invalid

获取附属类(enclosing class)成员

一个局部类可以获取它的附属类的成员。在上一个例子中,PhoneNumber构造器获取到LocalClassExample.regularExpression。

除此之外,一个局部类可以获取局部变量。但是局部类只能获取到声明为final类型的局部变量。当一个局部类可以获取局部变量或者参数时,它可以捕获这个变量或者参数。比如,PhoneNumber构造器可以获取局部变量numberLength,因为它被声明为final,numberLength是一个可捕获的变量。

但是,从Java SE 8开始,局部类可以获取附属块中的final以及effectively final的局部变量和参数。effectively final是指一个变量或参数的值在初始化后从未发生改变。例如,假定numberLength没有被声明为final,在构造器中加上这样一句:

PhoneNumber(String phoneNumber) {
    numberLength = 7;
    String currentNumber = phoneNumber.replaceAll(
        regularExpression, "");
    if (currentNumber.length() == numberLength)
        formattedPhoneNumber = currentNumber;
    else
        formattedPhoneNumber = null;
}
     
由于这样一句,numberLength就不再是effectively final。结果,Java编译器就会在内部类PhoneNumber试图获取numberLength变量的地方产生这样一条错误信息,“ local variables referenced from an inner class must be final or effectively final”:

     if (currentNumber.length() == numberLength)

从Java SE 8开始,声明在方法中的局部类可以获取该方法的参数。例如,可以在局部类PhoneNumber中定义这样一个方法:
  
public void printOriginalNumbers() {
    System.out.println("Original numbers are " + phoneNumber1 +
        " and " + phoneNumber2);
}

这个方法可以获取到方法validatePhoneNumber的参数phoneNumber1和phoneNumber2。

局部类和内部类相似

局部类和内部类一样都不能定义任何静态成员。定义在静态方法中的局部类只能引用附属类(enclosing class)的静态成员。例如,如果不把regularExPression定义成静态成员变量,Java编译器将产生这样一个错误“ non-static variable  regularExpression  cannot be referenced from a static context”。

局部类是非静态的,因为他们可以获取到附属块的实例成员。因此,他们不能包含大多数静态声明。

不能在一个块中声明一个接口,因为接口就是静态的。例如下面代码段不能通过编译,因为接口HelloThere定义在了greetInEnglish方法体中。

public void greetInEnglish() {
        interface HelloThere {
           public void greet();
        }
        class EnglishHelloThere implements HelloThere {
            public void greet() {
                System.out.println("Hello " + name);
            }
        }
        HelloThere myGreeting = new EnglishHelloThere();
        myGreeting.greet();
    }

不能在局部类中声明一个静态 initializers(不知道怎么翻译)和成员接口。下面代码将不会编译通过,因为   EnglishGoodbye.sayGoodbye被声明为静态的。Java编译器将产生这样一个错误信息,“ modifier 'static' is only allowed in constant variable declaration”。

           public void sayGoodbyeInEnglish() {
        class EnglishGoodbye {
            public static void sayGoodbye() {
                System.out.println("Bye bye");
            }
        }
        EnglishGoodbye.sayGoodbye();
    }

局部类中可以有常量形式的静态成员。(一个常量是原始类型或者String类型的变量被声明成final,在编译过程中初始化成常量表达式。编译时期常量表达式是一个在编译时生成的字符或者算术表达式。)下面代码段编译通过是因为 EnglishGoodbye.farewell是一个常量。
   
public void sayGoodbyeInEnglish() {
        class EnglishGoodbye {
            public static final String farewell = "Bye bye";
            public void sayGoodbye() {
                System.out.println(farewell);
            }
        }
        EnglishGoodbye myEnglishGoodbye = new EnglishGoodbye();
        myEnglishGoodbye.sayGoodbye();
 }


匿名内部类(Anonymous Classes)

匿名内部类可以使你的代码更简洁。他们可以使声明和实例化类同时发生。他们和局部类一样,只不要没有名字。如果你需要只使用局部类一次,那么用匿名内部类。

声明匿名内部类

局部类是类声明,匿名类是表达式,也就是说,匿名类定义在另一表达式中。下面的例子,HelloWorldAnonymousClasses,使用匿名类对变量 frenchGreeting 和 spanishGreeting 进行初始化声明,使用局部类对变量 englishGreeting 进行初始化声明:

           public class HelloWorldAnonymousClasses {
 
    interface HelloWorld {
        public void greet();
        public void greetSomeone(String someone);
    }
 
    public void sayHello() {
       
        class EnglishGreeting implements HelloWorld {
            String name = "world";
            public void greet() {
                greetSomeone("world");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hello " + name);
            }
        }
     
        HelloWorld englishGreeting = new EnglishGreeting();
       
        HelloWorld frenchGreeting = new HelloWorld() {
            String name = "tout le monde";
            public void greet() {
                greetSomeone("tout le monde");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Salut " + name);
            }
        };
       
        HelloWorld spanishGreeting = new HelloWorld() {
            String name = "mundo";
            public void greet() {
                greetSomeone("mundo");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hola, " + name);
            }
        };
        englishGreeting.greet();
        frenchGreeting.greetSomeone("Fred");
        spanishGreeting.greet();
    }

    public static void main(String... args) {
        HelloWorldAnonymousClasses myApp =
            new HelloWorldAnonymousClasses();
        myApp.sayHello();
    }           
}

匿名类语法

就像前面提到的一样,匿名类是表达式。匿名类的语法就像构造器的调用,除了有一个类定义在代码块中。

考虑 frenchGreeting 对象的实例化:

     HelloWorld frenchGreeting = new HelloWorld() {
                 String name = "tout le monde";
                 public void greet() {
                     greetSomeone("tout le monde");
                 }
                 public void greetSomeone(String someone) {
                     name = someone;
                     System.out.println("Salut " + name);
                 }
        };

匿名类表达式包含一下几个部分:
  • new 操作符
  • 用于实现的接口或者用于继承的类的名字。在这个例子中,匿名类实现接口HelloWorld。
  • 包含构造器参数的圆括号,就像普通类创建实例那样。注意:就像本例中,如果是实现接口,那么是不存在构造器的,所以用一对空的圆括号。
  • 类声明体。更具体的说,在声明体中,允许方法声明,不允许语句。

因为匿名类定义是一个表达式,所以它必须是语句的一部分。在这个例子中,匿名类表达式是 frenchGreeting 对象实例化语句的一部分。

获取附属范围中的局部变量以及声明和获取匿名类成员

和内部类相似,匿名类可以捕获变量。他们对附属范围内的局部变量有相同的权限:
  • 匿名类可以获取附属类的成员
  • 匿名类可以获取附属范围中声明为final或者effectively final的局部变量
  • 和嵌套类一样,匿名类中类型声明会覆盖附属范围内有相同名字的声明

匿名类和内部类也有相同的限制:
  • 不能声明静态initializers 和成员接口
  • 可以存在static final声明的常量

匿名类中可以声明:
  • 字段(Fields)
  • 其他方法(即使没有从supertype中实现任何方法)
  • instance initializers
  • 局部类

但是, 不能再匿名类中声明构造器


其实在后面还有最新Java se 8新加的lambda表达式,在这就不写了,有兴趣可以学习一下


更多记录:
  • 非静态方法和类可以使用静态成员,静态方法和类不能使用非静态成员。个人猜想:
    • 产生这个的原因是静态成员在类加载时只会产生一个,而非静态成员是实例化类时产生的,每个实例对象都会有各自独立的非静态成员。静态成员在类加载的时候就已经产生,而非静态成员必须使用new动态生成。静态成员可以单独使用,如果调用非静态成员,有可能这个非静态成员还没创建,这样就会造成错误。所以静态成员不能调用非静态成员。
    • 而反过来就不会有这个问题,因为静态成员一定是在非静态成员创建之间就已经存在。

  • 内部类不能定义静态成员,而静态嵌套类既可以定义静态成员,也可以定义非静态成员。
    • 内部类是依赖于外部类的,从创建方式上就可以看出,内部类创建实例对象时必须先创建外部类的实例对象,也就是说内部类是外部类实例的一部分,必须依赖外部类实例。因此内部类中必然是不能定义静态成员的,因为静态成员不依赖实例,而依赖类。
    • 上面已经介绍了静态嵌套类在行为上就和普通的类没有什么区别,之所以定义为嵌套类是因为突出嵌套类和外部类逻辑上的关系。所以,静态成员和非静态成员都可以在静态嵌套类中定义。至于为什么静态嵌套类中只能使用外部类中静态成员?因为静态嵌套类就与其他外部类一样,只能直接获取到静态成员,而其他成员必须通过实例获取。总的来说,不要以为静态嵌套类在某个类中就一定有什么联系,静态嵌套类完全可以当成在其他地方定义的类来使用。

  • 在静态方法中定义的局部类只能调用外部类的静态成员和final局部变量或参数。道理和静态方法相同。
  • 局部类中可以有static final声明的变量。因为在编译时期,static final声明的变量会被转化为常量,放在常量池中,这时的变量就不能当静态变量来看待了。


ps:文中的enclosing这个词不清楚怎么翻译,字典翻译是围绕,大概的意思就是包含的意思,比如外部类围绕在内部类外,也就是说外部类包含内部类。文中用 附属 可能不太准确,理解意思即可。

pps:本人也是新手上路,欢迎各位大神批评指正。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值