一、内部类的定义
可以将一个类的定义放在另一个类的定义内部,这就是内部类
public class OutterClass{
private class InnerClass{
}
}
二、创建内部类
以下在Test类中定义了一个InnerClass内部类,然后在Test中的useInnerClass方法中使用
public class Test {
public void useInnerClass(String name){
// 使用内部类
InnerClass inner = new InnerClass(name);
System.out.println("Name:" + inner.getName());
}
public static void main(String[] args){
Test t = new Test();
t.useInnerClass("张三");
}
// 定义一个内部类
class InnerClass{
private String name;
InnerClass(String name){
this.name = name;
}
String getName(){
return this.name;
}
}
}
/*
输出结果:
Name:张三
但是更典型的情况是,外部类将有一个方法,该方法返回指向内部类的引用。
如下面Test类的getInnerClass方法
*/
public class Test {
public void useInnerClass(String name){
// 使用内部类
InnerClass inner = new InnerClass(name);
System.out.println("Name:" + inner.getName());
}
// 通过一个公共的方法返回内部类的实例
public InnerClass getInnerClass(String str){
return new InnerClass(str);
}
public static void main(String[] args){
Test t = new Test();
/*
如果想在外部类的非静态方法之外的任意位置创建某个内部类的对象,
那么,必须使用:"外部类.内部类"的方式指明这个对象的类型
如:Test.InnerClass
*/
Test.InnerClass innerC = t.getInnerClass("张三");
System.out.println("Name:" + innerC.getName());
}
// 定义一个内部类
class InnerClass{
private String name;
InnerClass(String name){
this.name = name;
}
String getName(){
return this.name;
}
}
}
/*
输出结果:
Name:张三
*/
三、连接到外部类
当生成一个内部类的对象时,此对象与制造它的外围对象之间就有一种联系,所所以它能
访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有外围类的所有元素
的访问权。
public class Test {
// Test的成员属性
private String name="张三";
public static void main(String[] args){
Test t = new Test();
Test.InnerClass inner = t.new InnerClass(); // 创建内部类
System.out.println("Name:" + inner.getName()); // Name:张三
}
// 定义一个内部类
class InnerClass{
String getName(){
return name; // 访问Test外围类的name属性
}
}
}
四、使用.this和.new
如果你需要生成对外部类对象的引用,可以使用"外部类.this"的形式获取。这样产生
的引用自动地具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行
时开销。
public class Test {
// 定义一个显示指定str的方法
private void print(String str){
System.out.println("我是外部类输出的:" + str);
}
public static void main(String[] args){
// 创建内部类的一个引用
Test.InnerClass innerClass = new Test().new InnerClass();
innerClass.getTestObject().print("我来自外部类");
}
// 定义一个内部类
class InnerClass{
public Test getTestObject(){
// Test.this
return Test.this; // 返回外部类的引用
}
}
}
有时你可能想要告知某些其他对象,去创建其某个内部类对象。要实现此目的,你
必须在new表达式中提供对其他外部类对象的引用,这是需要使用.new语法。如下:
方式一:
Test test = new Test();
Test.InnerClass innerClass = test.new InnerClass();
方式二:
Test.InnerClass innerClass = new Test().new InnerClass();
注意:在创建内部类对象时,你不能想你想象的那样使用外部类的类名而是使用外部类
对象的引用去创建。
在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗
地连接到创建它的外部类对象身上。但是,如果你创建的是嵌套类(静态内部类),
那么就不需要外部类对象的引用。
public class Test {
public static void main(String[] args){
// 创建内部类的一个引用
InnerClass innerClass = new InnerClass();
innerClass.print();
}
// 定义一个嵌套内部类(静态内部类)
static class InnerClass{
public void print(){
System.out.println("内部类的输出信息...");
}
}
}
五、内部类与向上转型
当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就非
常有用了。因为此类部类能够完全不可见
public class Test {
public static void main(String[] args){
// 创建OuterClass,并利用该对象的引用创建InnerClass对象
OuterClass outer = new OuterClass();
Inner inner = outer.getInnerClass(); // 内部类对象
// 无法访问OuterClass内部的private属性InnerClass
// OuterClass.InnerClass inner = outer.getInnerClass();
inner.print();
}
}
class OuterClass{
// 返回内部类InnerClass的对象
public Inner getInnerClass(){
return new InnerClass();
}
// 定义一个内部类,实现了Inner接口
// 该内部类隐藏了Inner接口实现的所有细节
private class InnerClass implements Inner{
@Override
public void print(){
System.out.println("内部类的输出信息...");
}
}
}
interface Inner{
void print();
}
六、在方法和作用域内的内部类
在方法内部定义一个完整的类,这被成为局部内部类。InnerClass标识
符可以在同一个目录下任意定义,而不会产生命名冲突
public class Test {
public Inner getInner(){
// 局部内部类
class InnerClass implements Inner{
@Override
public void print(){
System.out.println("我是内部类");
}
}
return new InnerClass(); // 返回局部内部类的引用
}
public static void main(String[] args){
Test test = new Test();
Inner inner = test.getInner();
inner.print();
}
}
interface Inner{
void print();
}
// 展示如何在作用域中定义内部类
public class Test {
public Inner getInner(boolean flag){
if(flag){
// 定义局部内部类
class InnerClass implements Inner{
@Override
public void print(){
System.out.println("我是内部类");
}
}
return new InnerClass();
}
// System.out.println(new InnerClass()); // 访问不到InnerClass局部内部类
System.out.println("内部类没有被创建");
return null;
}
public static void main(String[] args){
Test test = new Test();
Inner inner = test.getInner(true);
inner.print();
}
}
interface Inner{
void print();
}
注意:InnerClass类被嵌入在if语句的作用域内,并不是说该类(InnerClass)的创建是有
条件的,它其实与别的类一起编译过了。
七、匿名内部类
public class Test {
public Inner getInner(){
return new Inner(){ // 创建一个继承自Inner的匿名内部类
@Override
public void print(){
System.out.println("我是匿名内部类");
}
};
}
/*
// 等价与上面的getInner方法
class MyInner implements Inner{
@Override
public void print(){
System.out.println("我是匿名内部类");
}
}
public Inner getInner(){
return new MyInner();
}
*/
public static void main(String[] args){
Test test = new Test();
Inner inner = test.getInner();
inner.print();
}
}
interface Inner{
void print();
}
// 怎样向有参数的匿名类构造参数
public class Test {
public Wrapping getInner(int a){
// 尽管Wrapping只是一个普通类,但还是被它的导出类当作公共“接口”来使用
return new Wrapping(a){
public int value(){
return super.value() * 2;
}
};
}
public static void main(String[] args){
Test test = new Test();
Wrapping inner = test.getInner(12);
System.out.println("value: " + inner.value());
}
}
class Wrapping{
private int i;
public Wrapping(int x){
i = x;
}
public int value(){
return i;
}
}
// 在匿名内部类中定义字段时,还能够对其初始化操作:
public class Test {
public Inner getInner(String str){
return new Inner(){ // 匿名类实现接口;不能有参数
// 从内部类中访问局部变量 str;需要被声明为最终类型
private String label = str;
@Override
public void print(){
System.out.println(label);
}
};
}
public static void main(String[] args){
Test test = new Test();
Inner inner = test.getInner("有参数的匿名内部类");
inner.print();
}
}
interface Inner{
public void print();
}
注意:如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么
编译器会要求其参数引用是final的,就像在getInner方法中一样。如果只是简
单地给一个字段赋值,那么上面的例子是很好的。但是,要想做一些类似构造
器的行为,该怎么办?在匿名函数中不可能有命名构造器(因为他根本没有名字),
但是通过实例初始化,就你能够达到为匿名内部类创建一个构造器的效果,如下:
public class Test {
public static Base getBase(int i){
return new Base(i){
{
System.out.println("Inside instance initializer");
}
public void f(){
System.out.println("In anonymous f()");
}
};
}
public static void main(String[] args){
Base base = Test.getBase(12);
base.f();
// Base constructor, i=12
// Inside instance initializer
// In anonymous f()
}
}
abstract class Base{
public Base(int i){
System.out.println("Base constructor, i=" + i);
}
public abstract void f();
}
注意:在上面例子中,不要求变量i一定是final的。因为i被传递给匿名类的基类的
构造器,它并不会在匿名内部类内部直接使用。下面的例子是带实例初始化的形式。
public class Test {
public static Inner getInner(final String username, final int age){
return new Inner(){
private int ageTemp;
private String nameTemp = username;
// 为每个对象的实例初始化,
{
ageTemp = age;
if(ageTemp > 18){
System.out.println("成年人");
} else {
System.out.println("未成年人");
}
}
public void print(){
System.out.println("名称: " + nameTemp);
}
};
}
public static void main(String[] args){
Inner inner = Test.getInner("张三", 12);
inner.print();
}
}
interface Inner{
public void print();
}
// 注意:是不合法的
public class Test{
public Inner getInner(){
return new Inner() extends Other{}
// return new Inner() implements Other{}
}
}
八、嵌套类
如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static。
这通常成为“嵌套类”。要想了解static应用与内部类的含义,就必须记住,普通内部类对
象隐式的保存了一个指向外围类对象的引用。然而,使用static定义内部类时,情况就不
同了。
1、要创建嵌套内部类的对象,并不需要其外围类的对象。如下:
public class Test {
private static class MyInner implements Inner{
@Override
public void print(){
System.out.println("我来自内部类");
}
}
public static void main(String[] args){
MyInner inner = new MyInner();
inner.print();
}
}
interface Inner{
public void print();
}
2、不能从嵌套类的对象中访问非静态的外围类对象。如下:
public class Test {
private String name1 = "张三";
private static String name2 = "李四";
private static class MyInner implements Inner{
@Override
public void print(){
// 无法从静态上下文中引用非静态 变量 name1
// System.out.println("name1: " + name1);
System.out.println("name2: " + name2); // OK
}
}
public static void main(String[] args){
MyInner inner = new MyInner();
inner.print();
}
}
interface Inner{
public void print();
}
3、嵌套类与普通内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,
所以普通内部类不能拥有static数据和static字段,也不能包含嵌套类。但是嵌套类可以。
public class Test {
// 普通内部类
private class MyInner{
// private static String name = "zhangsan"; // 内部类不能有静态声明
private String name = "zhangsan"; // 输出: zhangsan
// private static class MyInnerInner{} // 此处不允许使用修饰符 static
public String getName(){
return name;
}
}
// 嵌套内部类
private static class MyStaticInner{
private static String name = "zhangsan";
public String getName(){
return name;
}
}
public static void main(String[] args){
// 普通内部类
Test.MyInner inner = new Test().new MyInner();
System.out.println("Name: " + inner.getName());
// 嵌套内部类
MyStaticInner staticInner = new MyStaticInner();
System.out.println("Name: " + staticInner.getName());
}
}
九、接口内部类
在正常情况下,不能在接口内部放置任何代码,但是嵌套类可以作为接口的一部分。
你放到接口中的任何类都自动地是public 和static的。因为类是static的,只是将嵌套
类置于接口的命名空间内,这并不违反接口的规则。甚至可以在内部类中实现其外围接
口。如下:
public interface Test {
void show();
class MyTest implements Test{ // 该类是public static的
public void show(){
System.out.println("hi! java");
}
public static void main(String[] args){
new MyTest().show();
}
}
}
运行方式:java Test$MyTest
如果你想要创建某些公共代码,提供给某个接口的所有实现类使用,那么在接口内部的嵌套类
会显得很方便。如下:
public class Test {
public static void main(String[] args){
MyInner1 inner1 = new MyInner1();
inner1.print();
MyInner2 inner2 = new MyInner2();
inner2.print();
}
}
// 定义一个接口
interface Inner{
void print();
// 定义一个嵌套类,作为Inner接口的所有实现类的公共类
class MyInterfaceInner{
public void show(String str){
System.out.println(str);
}
}
}
class MyInner1 implements Inner{
public void print(){
// 调用接口中的嵌套类
new MyInterfaceInner().show("Inner实现类一");
}
}
class MyInner2 implements Inner{
public void print(){
// 调用接口中的嵌套类
new MyInterfaceInner().show("Inner实现类二");
}
}
// 一个内部类被嵌套多少层并不重要,他能透明地访问所有外围类的所有成员。如下:
public class Test {
public static void main(String[] args){
Outter outer = new Outter();
Outter.Inner inner = outer.new Inner();
Outter.Inner.InnerIn in = inner.new InnerIn();
in.h();
}
}
class Outter{
private void f(){
System.out.println("Outter class");
}
// 内部类
class Inner{
private void g(){
System.out.println("Inner class");
}
// 内部类中内部类
public class InnerIn{
public void h(){
f();
g();
System.out.println("InnerIn");
}
}
}
}
十、为什么需要内部类
一般来说,内部类继承自某个类或实现某个接口,内部类的代码可以很方便的操作
外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口。使用内部类最吸
引人的地方:
每个内部类都能独立的继承自一个(接口)实现,所以无论外围类是否已经继承了
某个(接口的)实现,对于内部类都没有影响。如下:
public class Test implements Inner{
@Override
public void print(){
System.out.println("Test Class");
}
private class MyInner implements Inner{
@Override
public void print(){
System.out.println("MyInner Class");
}
}
public static void main(String[] args){
Test t = new Test();
t.print();
Test.MyInner inner = t.new MyInner();
inner.print();
}
}
interface Inner{
public void print();
}
如果没有内部类提供的可以继承多个具体或抽象类的能力,一些涉及与编程问题
就很难解决。从这个角度看,内部类使得多继承的解决方案变得完整,接口只解决了
一部分问题。一个类要实现两个接口,则有两种方式
方式一
public class Test implements A, B {
}
方式二
public class Test implements A{
private B getB(){
return new B(){
};
}
}
// 一个类继承两个具体或抽象的类,则只能使用内部类
public class Test extends A{
private B getB(){
return new B(){
};
}
}
使用内部类获得的特性:
1、内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围对象的信息向独立。
2、在单个外围类中,可以让多个内部类一不同的方式实现同一个接口或继承同一个类。
3、创建内部类对象的时刻并不依赖于外围对象的创建。
4、内部类并没有令人迷惑的“is-a”关系,它是一个独立的实体。
public class Test{
public static void main(String[] args){
Callee1 c1 = new Callee1();
Callee2 c2 = new Callee2();
MyIncrement.f(c2);
Caller caller1 = new Caller(c1);
Caller caller2 = new Caller(c2.getCallbackReference());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
}
}
interface Incrementable{
void increment();
}
class Callee1 implements Incrementable{
private int i = 0;
public void increment(){
i++;
System.out.println(i);
}
}
class MyIncrement{
public void increment(){
System.out.println("other operation");
}
static void f(MyIncrement mi){
mi.increment();
}
}
class Callee2 extends MyIncrement{
private int i = 0;
public void increment(){
super.increment();
i++;
System.out.println(i);
}
private class Closure implements Incrementable{
public void increment(){
Callee2.this.increment();
}
}
Incrementable getCallbackReference(){
return new Closure();
}
}
class Caller{
private Incrementable callbackReference;
Caller(Incrementable cbh){
callbackReference = cbh;
}
void go(){
callbackReference.increment();
}
}
注意:在创建一个内部类时,并没有在外围类的接口中添加东西,也没有修改外围类的接口。
十一、内部类的继承
因为内部类的构造器必须连接到指向外围类对象的引用,所有在继承内部类时很复杂。
问题在于,指向外围类对象的引用必须被初始化,而在导出类中不再存在可连接的默认对
象。要解决这个问题,必须使用特殊的语法来明确他们之间的关联。如下:
public class Test extends Outer.Inner{
// 使用默认构造器构造继承自内部类的类时会产生编译错误
// public Test(){} // 需要包含 Outter.Inner 的封闭实例
// 解决办法
public Test(Outer outer){
outer.super();
}
public static void main(String[] args){
// Test test = new Test();
Outer outer = new Outer();
Test test = new Test(outer);
}
}
class Outer{
class Inner{
public void show(){
System.out.println("内部类");
}
}
}
十二、内部类可以被覆盖吗?
不能被覆盖。如:
public class Test extends OutterClass{
public class InnerClass{
public InnerClass(){
System.out.println("Test().InnerClass()");
}
}
public static void main(String[] args){
new Test();
}
}
class OutterClass {
private InnerClass inner;
protected class InnerClass{
public InnerClass(){
System.out.println("InnerClass()");
}
}
public OutterClass(){
System.out.println("OutterClass()");
inner = new InnerClass();
}
}
输出结果:
OutterClass()
InnerClass()
也可以明确继承某个内部类,如下:
public class Test extends OutterClass{
// 试着覆盖内部类
public class InnerClass extends OutterClass.InnerClass{
public InnerClass(){
System.out.println("Test().InnerClass");
}
public void f(){
System.out.println("Test().InnerClass f()");
}
}
public Test(){
// 将新的InnerClass设置到Outter中
insertInnerClass(new InnerClass());
}
public static void main(String[] args){
OutterClass outter = new Test();
outter.g();
}
}
// 外部类
class OutterClass {
// 内部类
protected class InnerClass{
public InnerClass(){
System.out.println("InnerClass");
}
public void f(){
System.out.println("InnerClass f()");
}
}
private InnerClass inner = new InnerClass();
public void insertInnerClass(InnerClass inner){
this.inner = inner;
}
public void g(){
inner.f();
}
public OutterClass(){
System.out.println("OutterClass");
}
}
输出结果:
InnerClass
OutterClass
InnerClass
Test().InnerClass
Test().InnerClass f()
十三、局部内部类
前面介绍过局部内部类是定义在代码块中的内部类。局部内部类不能访问说明符,
因为它不是外围类的一部分;但是它可以访问当前代码块内的常量,以及此外围类的
所有成员。如下:
public class Test {
// 在外围类中定义一个成员变量
private String name = "hello world";
public void getInner(boolean flag){
if(flag){
// 该内部类只能在if代码块中访问
/* private */ class MyInner{
// 非法的表达式开始,此处不能使用修饰符(private)
public void print(){
System.out.println("Name: " + name); // 在局部内部类中访问外围类中的使用成员 OK
}
}
new MyInner().print();
}
// MyInner inner = new MyInner(); // 找不到符号
// inner.print();
}
public static void main(String[] args){
Test test = new Test();
test.getInner(true);
}
}
十四、内部类标识符
由于每个类都会产生一个.class文件,其中包含了创建该类型对象的全部信息。
而内部类的.class名称有严格的规则:
1、匿名内部类的名字:外围类名$数字.class 如:Outer$1.class
2、普通内部类:外围类名$内部类名.class 如:Outer$Inner.class