Java 允许你在一个类的内部定义一个新的类,这个新的类就被称为嵌套类。下面给出一个例子
class EnclosingClass{ //被嵌套类
class NestedClass{ //嵌套类
}
}
注明:嵌套类有两种,静态类型和非静态类型。被声明为静态类型的嵌套类称之为静态嵌套类,另一种则称之为内部类。下面给出一个例子
class EnclosingClass{
class StaticNestedClass{ //静态嵌套类
}
class InnerClass{ //内部类
}
}
嵌套类是包围类的成员(就像包围类中方法和变量一样)。内部类可以访问包围类中的其他成员,即使那些成员被声明为private.非静态嵌套类不能访问包围类的成员。作为包围类的成员,内部类可以被声明为private,public, protected或package private(package private就是不做任何声明的情况)。
注明:除了nested class外,其他任何类都不可以被声明为private和protected.
为什么要使用嵌套类(nested class)?
- 逻辑分组只会在一个场合使用的那些类如果一个类(class NestedClass)仅仅只对一个类(class EnclosingClass)有用,那么NestedClass就可以被嵌套在EnclosingClass里面,让它们成为一个整体。嵌入这样的一个类可以使EnclosingClass所在的包更加的合理(因为NestedClass不会被其他的类使用,没有必要单独把NestedClass放在这个包里面)。
- 增加了封装特性。假设我们有两个类A和B,B需要访问在A中被声明为private的成员。那么就可以把B隐藏在A里面,A的成员就可以被声明为private并且B也能访问它们。此外,B自身也可以不被外界可见。
- 增加的代码的可读性和维护性。把类A嵌入在另一个类B的内部使A在位置上靠近它被使用的地方。
静态嵌套类(static nested class)
就像类中的方法和变量一样,静态嵌套类也与它的包围类有关。就像静态类(与静态嵌套类不同)的静态方法一样,静态嵌套类是不能直接使用包围类中定义的实例变量和实例方法的,只能通过对象的引用去操作它们。
注明:事实上,静态内部类操作包围类的实例成员的时候和其他普通类是一样的,之所以嵌套在包围类的内部,仅仅是为了打包的方便性。
访问静态内部类的时候,需要使用包围类的名字。
EnclosingClass.StaticNestedClass snc = new EnclosingClass.StaticNestedClass();
内部类(inner class)
就像实例变量和方法一样,内部类是直接与包围类的实例相关联的。 它可以直接访问包围类的变量和方法。同时,因为内部类与实例相关联,所以在内部类的内部是不能定义任何的静态成员的(因为实例是可以不断变化的,静态成员是不能变化的,外部实例的变化必然会导致与之相关联的静态类的变化,所以在内部类的内部是不能定义任何静态成员的)。
内部类的对象在没有包围类的实例的时候是不能单独存在的,看下面这个例子
class EnclosingClass{
class InnerClass{
}
}
内部类的实例只能存在与包围内的实例中,所以声明一个
InnerClass ic = new InnerClass()
是不行的。但可以如下声明
EnclosingClass.InnerClass ic = enclosingObject.new InnerClass(); // enclosingObject 是EnclosingClass
的一个实例。
局部类(local classes)
局部类定义在一个由大括号括起来的代码块中,通常是定义在方法体中
声明局部类
You can define a local class inside any block (see Expressions, Statements, and Blocks for more information). For example, you can define a local class in a method body, a for loop, or an if clause.
可以再任何代码块中定义局部内部类。下面给出一个例子
public class LocalClassExample {
static String regularExpression = "[^0-9]";
public static void validatePhoneNumber(
String phoneNumber1, String phoneNumber2) {
final int numberLength = 11;
// Valid in JDK 8 and later:
// int numberLength = 11;
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");
}
public static void main(String... args) {
validatePhoneNumber("123-456-78901", "456-7890");
}
}
访问包围类的成员
成员类可以访问包围类的成员。在上面这个例子中,PhoneNumber的构造函数访问了EnclosingClass的regularExpression。此外,成员类也可以访问被声明为final的成员变量。当成员类访问起所在代码块的成员变量或参数时,它会捕获该成员变量或参数。比如说,PhoneNumber的构造函数可以访问成员变量numberLength因为它被声明为final。numberLength 是被捕获的变量。从Java SE8 开始,成员类可以访问其所在代码块中的final 或者 有效final(初始化之后,值就不再改变的变量或参数)变量或者参数。同样,从Java SE8 开始,如果在方法体中定义了成员类,成员类可以访问方法的参数。
成员类与内部类相似
成员类与内部类相似,因为它们都不能定义或者声明任何的静态成员。在静态方法中的成员类,只能使用包围类中的静态成员,比如上面例子中的regularExpression.
成员类是非静态的,因为它们可以访问包围块的实例实例成员
在代码块中不能声明接口。因为接口本身是静态的。
匿名类(anonymous class)
匿名类让你的代码更加简洁。它们能让你同时声明和实例化类。除了匿名之外,其与成员类想象。 如果你只需要使用一次成员类,那么久可以使用匿名类
匿名类声明
成员类和一般的类声明是一样的,匿名类则是一个表达式。这意味着你将会在另一个表达式中定义匿名类。下面这个匿名类,HelloWorldAnonymousClass, 在初始化成员变量frenchGreering和spanishGreeting的时候用到了匿名类,同时用了一个成员类去初始化englishGreeting
public class HelloWorldAnonymousClass{
interface HelloWorld{
public void greet();
public void greetSomeone(String someone);
}
public void sayHello(){
class EnglishGreeting implements HelloWorld{
String name = new String("world");
public void greet(){
greetSomeone(name);
}
public void greetSomeone(String someone){
name = someone;
System.out.println("Hello, " + name);
}
}
HelloWorld englishGreeting = new EnglishGreeting();
HelloWorld frenchGreeting = new FrenchGreering(){
String name = new String("tout le mond");
public void greet(){
greetSomeone(name);
}
public void greetSomeone(String someone){
name = someone;
System.out.println("Salut " + name);
}
};
HelloWorld spanishGreeting = new SpanishGreering(){
String name = new String("mundo");
public void greer(){
greetSomeone(name);
}
public void greetSome(String someone){
name = someone;
System.out.println("Halo " + name);
}
};
englishGreeting.greet();
frenchGreeting.greetSomeone("Fred");
spanishGreeting.greet();
}
public static void main(String... args) {
HelloWorldAnonymousClasses myApp =
new HelloWorldAnonymousClasses();
myApp.sayHello();
}
}
匿名类语法
按照上面说的,匿名类是表达式。匿名类的表达式语法和构造函数很像。看下面这段代码
//frenchGreeting Object的初始化
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);
}
};
声明隐蔽
若在某个范围内(比如在内部类或者方法中)的某个声明(比如一个成员变量或者参数名字)与在整个包围类中的另外一个声明使用了相同的名字,那么这个声明就会隐藏在包围类中的声明。隐藏的声明不能仅仅通过它的名字起作用。下面给出一个例子
public class ShadowedClass{
int x = 2;;
class InnerClass{
public int x = 1;
void innerMethod(int x){
System.out.println("x = " + x);
System.out.println("this.x = " + this.x);
System.out.println("ShadowedClass.this.x = " + ShadowedClass.this.x);
}
}
public static void main(String[] args){
ShadowedClass sc = new ShadowedClass();
ShadowedClass.InnerClass sic = sc.new InnerClass();
sic.innerMethod(5);
}
}
the output is: