目录
一、四种内部类
在类中或者类方法中定义的类就是内部类,内部类分为成员内部类,局部内部类,匿名内部类,静态内部类(嵌套内部类)。
1.1成员内部类和静态内部类
静态内部类就是用static关键字修饰的成员内部类,成员内部类同样可以使用private、public、protected、final、abstract等关键字修饰,内部类同样可以继承自其他类或者实现某种接口,即内部类的声明方式与普通类完全一样。在外部类的方法中使用内部类时跟使用普通类完全一样,在外部类之外使用内部类时,如果是成员内部类则必须通过外部类的实例对象生成内部类实例对象,如果是静态内部类,可以通过静态内部类的类名直接访问静态内部类的静态方法和属性,非静态方法和属性只能通过静态内部类的实例对象方法,静态内部类的实例化不需要外部类的实例化对象。也因此静态内部类不能访问外部类中的非静态属性和方法。另外,内部类的实例化与外部类的实例化完全无关,内部类并不等同于外部类的一个“成员”,外部类在初始化的时候不会初始化内部类,除非显示调用内部类的初始化方法,从这一层面上讲外部类更像是内部类的“包”。最后在外部类的方法中实例化内部类对象,可通过该对象访问内部类的私有属性跟方法。
成员内部类不能声明静态方法和静态属性,可以访问外部类中的所有成员变量和方法,当内部类中没有同名变量时可以直接引用,有同名变量时可以通过外部类类名.this返回返回外部类实例对象的引用显示调用外部类中的同名变量,如果是静态变量,可以通过类名直接引用。在成员内部类中使用this时,this指代的是内部类对象本身。
静态内部类同静态方法一样,只能访问外部类中的静态变量和方法,可以声明静态或非静态的方法或者属性。当静态内部类中有同名变量时,通过外部类类名直接引用外部类中的静态成员和方法,否则直接引用。静态内部类无法通过外部类名.this的方式返回外部类的实例对象引用,但可以直接new一个外部类对象,通过该实例对象访问外部类中的非静态变量和非静态方法。最后,静态内部类与静态变量不同,可以有多个静态内部类实例化对象。
public class OuterA {
private String name="shl";
private static int age=24;
private String address="北京";
public void printName(){
System.out.println(name);
}
public static void printAge(){
System.out.println(age);
}
private void printAddress(){
System.out.println(address);
}
public OuterA() {
System.out.println("外部类初始化");
}
public class InnerB{
private String name="shl";
//非静态内部类不能声明静态变量
//private static int age=24;
public InnerB(){
//非静态内部类可以访问外部类中的静态或者非静态方法,public或者private方法
printAge();
OuterA.printAge();
printName();
OuterA.this.printName();
printAddress();
OuterA.this.printAddress();
}
//不允许定义静态块
// static{
//
// }
public void printName(){
//内部类中具有同名变量时,内部类的变量会覆盖外部类中的变量,除非显示指定使用外部类变量
System.out.println(name);
//内部类中this指代内部类本身
System.out.println(this.name);
//显示指定使用外部类变量,OuterA.this返回外部类的引用。
System.out.println(OuterA.this.name);
System.out.println(OuterA.age);
}
//非静态内部类不能声明静态方法
// public static void printAge(){
// System.out.println(age);
// }
//非静态内部类可以自由访问外部类中的静态或者非静态变量。
public void printAge(){
System.out.println(age);
}
public void printAddress(){
System.out.println(address);
}
}
private static class InnerC{
private String name="shl";
//允许声明静态变量
private static boolean sex=true;
static{
System.out.println("静态内部类的静态块");
}
public InnerC(){
//静态内部类只能访问外部类中的静态方法
printAge();
OuterA.printAge();
//printAddress();
System.out.println("静态内部类的初始化");
}
public void printName(){
System.out.println(name);
System.out.println(this.name);
System.out.println(OuterA.age);
}
//静态内部类声明静态方法
public static void printAge(){
System.out.println(age);
}
//静态内部类不能访问非静态变量
// public void printAddress(){
// System.out.println(address);
// }
public static void printAddress(){
OuterA a=new OuterA();
System.out.println(a.address);
}
}
public abstract class InnerD{
}
public void printInnerBname(){
//外部类访问成员内部类时必须通过成员内部类的实例对象
//外部类方法中初始化成员内部类时与初始化其他类一样
InnerB b=new InnerB();
System.out.println(b.name);
b.printAddress();
}
public void printInnerCname(){
//可以通过类名直接访问静态内部类的静态方法和属性,非静态成员必须通过实例化对象
System.out.println(InnerC.sex);
InnerC.printAge();
InnerC c=new InnerC();
System.out.println(c.name);
c.printName();
}
public static void main(String[] args) {
// InnerB b=new InnerB();
InnerB b=new OuterA().new InnerB();
b.printAge();
System.out.println("=====================");
OuterA a=new OuterA();
InnerC c=new InnerC();
InnerC c2=new OuterA.InnerC();
InnerC c3=new OuterA.InnerC();
c.printName();
c.printAddress();
}
}
1.2 局部内部类和匿名内部类
匿名内部类是局部内部类的一种简写形式,当局部内部类只需要初始化一次时就可以将其改写为匿名内部类,但是要求匿名内部类必须实现某个接口或者继承自父类,父类可以是实现类或者抽象类。通过实现接口创建的匿名内部类只有默认的无参构造函数,通过继承自父类的匿名内部类拥有跟父类相同的构造函数,即父类有带参的构造函数时,匿名内部类同样有带参的构造函数。无论是匿名内部类还是局部内部类,都不允许创建static变量或者方法,两者可以直接访问外部类的变量或者方法,若存在同名变量则需要通过外部类类名.this显示指定访问外部类的变量或者方法,或者通过外部类类名访问外部类的静态变量或者静态方法。当访问方法内的局部变量(包括方法参数)时,如果是用作构造方法的参数则不要求方法参数是final的,否则必须是final的,因为内部类编译后通过构造方法参数的形式将方法参数复制了一个副本,内部类修改该副本与该方法内部修改方法参数互不影响,为了避免参数不一致的情况,强制要求被内部类直接引用的方法参数(构造方法除外)必须是final的。最后两者只在声明的方法或者作用域内可见。局部内部类可以加abstract或者final关键字,而匿名内部类不允许加任何关键字。
public interface InterfaceA {
void printName();
}
public abstract class AbstractC {
private String firstName;
private String lastName;
public AbstractC(String firstName,String lastName) {
this.firstName=firstName;
this.lastName=lastName;
}
public abstract void printName();
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
public class OuterB {
private String name="shl";
private static int age=24;
private String address="北京";
public void printName(final boolean sex){
String firstName="sun";
//局部内部类的写法,可以将类的声明放在方法中或者if等语句的大括号中
//不能被public、private、protected、static等修饰,只能用final或者abstract修饰
class InnnerC implements InterfaceA{
private String name="sun";
private int age=18;
// 无法声明静态变量或者静态方法
// private static int age=18;
public InnnerC(boolean sex,String firstName) {
// TODO Auto-generated constructor stub
}
public void printName(){
//可以访问外部类中的变量或者方法或者方法参数
System.out.println(sex);
//存在同名变量时,需要显示指定调用外部类中的同名变量
System.out.println(name);
System.out.println(OuterB.this.name);
System.out.println(age);
System.out.println(OuterB.age);
//访问方法内的局部变量时要求该变量为final
// System.out.println(firstName);
printAge();
printAddress();
}
// public static void printName2(){
//
// }
}
//构造方法中使用方法参数时不要求方法参数是final
InnnerC c=new InnnerC(sex,firstName);
c.printName();
//匿名内部类写法,如果该局部内部类在方法或者作用域内只初始化一次,可以采用匿名内部类的写法
//此时要求匿名内部类必须实现某个接口或者采用抽象类,或者某一个具体的类然后覆写某个方法
InterfaceA a=new InterfaceA() {
@Override
public void printName() {
System.out.println(name);
}
};
a.printName();
InterfaceA b=new InterfaceA() {
@Override
public void printName() {
//放访问方法参数时,方法的参数必须加final修饰符,否则报错
//原因在于编译后内部类实际使用的方法参数是该参数的副本,即内部类修改参数和方法中修改该参数是互不影响的,为了
//避免参数不一致的情况强制要求局部
if (sex) {
System.out.println("男");
}else{
System.out.println("女");
}
}
};
b.printName();
}
//通过带有带参构造函数的抽象类实现带参构造函数的匿名内部类
public void printName(String firstName, String lastName){
//构造方法中使用方法参数,不要求参数为final
AbstractC c=new AbstractC(firstName,lastName) {
@Override
public void printName() {
//直接访问父类中的firstName和lastName不要求方法参数是final的
System.out.println(getFirstName()+getLastName());
}
// public void printName2(){
// //因为父类中的firstName和LastName是private所以这里访问的是方法参数firstName和lastName
// //就要求firstName和lastName必须是final
// System.out.println(firstName+lastName);
// }
};
}
public static void printAge(){
System.out.println(age);
}
private void printAddress(){
System.out.println(address);
}
public OuterB() {
System.out.println("外部类初始化");
}
public static void InnerCTest(){
//InnerC为OuterA中的内部类,OuterB中的InnerC只在InnerC声明的方法或者作用域范围内可见
InnerC c=new InnerC();
c.printName();
}
}
1.3、内部类的编译
内部类编译后会产生一个独立的class文件,如果是成员内部类和静态内部类就是文件名为外部类类名$内部类的类名,如果是局部内部类就是外部类类名$+数字编号+内部类的类名,如果是匿名内部类就是外部类类名$+数字编号,数字编号从1开始,依次加1。可以利用此特性将用于测试的main方法代码放在静态内部类中,这样实际的测试代码就生成的独立的内部类class文件,避免外部类class文件中带有无关的测试代码,打包时将该内部类class文件剔除即可。不过通常的做法是单独写一个测试类来测试功能类的方法,测试类与被测试类是放在两个不同的包中,打包时直接忽略测试文件所在的包。下图为生成的编译文件:
public class OuterF {
public void test(){
System.out.println("main方法入口测试");
}
public static class InnerA{
public static void main(String[] args) {
OuterF f=new OuterF();
f.test();
}
}
}
二、内部类进阶
2.1、内部类继承
内部类继承只针对静态内部类和成员内部类,局部内部类和匿名内部类在方法外不可见。因为静态内部类与外部类“无关”,与继承普通类一样。对于成员内部类,因为成员内部类的创建必须通过外部类的实例对象,因此必须将外部类的实例化对象作为参数传递进构造参数中
public class OuterC extends InnerB{
//将外部类的实例对象传递该构造方法
public OuterC(OuterA a) {
a.super();
}
}
public class OuterD extends InnerC {
public OuterD() {
// TODO Auto-generated constructor stub
}
}
2.2、接口内部的类
将内部类放在接口中,此时内部类自动是public和static的,相当于将内部类置于接口这个“命名空间内”,并没有违反接口的定义规则,当实现某接口的类有一些公共的方法时可以将这些公共方法提取到接口的内部类中。不过通常的实现方案是实现该接口创建一个抽象类,将公共方法作为抽象类的方法,实现类继承自该抽象类。与接口内部类相比灵活性降低了,但是代码更容易理解。接口内部类实例如下:
public interface InterfaceB {
void test();
class InnerA implements InterfaceB{
@Override
public void test() {
System.out.println("测试接口内部类");
}
}
}
public class OuterE implements InterfaceB{
@Override
public void test() {
InterfaceB.InnerA a=new InterfaceB.InnerA();
a.test();
}
public static void main(String[] args) {
OuterE e=new OuterE();
e.test();
}
}
2.3、通过内部类实现多重继承和同一接口的“多重实现”
内部类最重要的特性在于:每个内部类可以独立的继承自某一个实现类或者实现多个接口,而不受外部类是否继承了该实现类或者实现了相同的接口的影响。借此特性可以实现java的“多重继承”和同一接口的“多重实现”,极大的提高了代码的灵活性。
public class OuterG {
class InnerA extends OuterA{
}
class InnerB extends OuterB{
}
class InnerC implements InterfaceA{
@Override
public void printName() {
System.out.println("sun");
}
}
class InnnerD implements InterfaceA{
@Override
public void printName() {
System.out.println("shl");
}
}
}
2.4 内部类与闭包
1、闭包的定义:
- 是引用了自由变量的函数。这个函数通常被定义在另一个外部函数中,并且引用了外部函数中的变量。 -- <<wikipedia>>
- 是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。-- <<Java编程思想>>
- 是一个匿名的代码块,可以接受参数,并返回一个返回值,也可以引用和使用在它周围的,可见域中定义的变量。--Groovy ['ɡru:vi]
- 是一个表达式,它具有自由变量及邦定这些变量的上下文环境。
- 闭包允许你将一些行为封装,将它像一个对象一样传来递去,而且它依然能够访问到原来第一次声明时的上下文。
- 是指拥有多个变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
这几种不同的定义本质上都是相同的,外部函数可以理解为“创建它的作用域”、“在它周围的可见域”、“上下文环境”,在外部函数内创建了一个内部函数或者代码块,该内部函数引用了外部函数中的变量,并且可以作为一个对象返回被外部函数以外的对象所调用,当外部函数以外的对象调用了该内部函数时即形成了闭包,此时只能通过内部函数间接访问外部函数中的变量,可以通俗的理解为内部函数对于外部函数中的变量形成一种外部不能直接访问的“包裹”,简称“闭包”。在javascript中“闭包”可以很完整的体现,java可以通过内部类来模拟这种行为特性。
2、 javaScript中的闭包
function Outer(){
var i=0;
function Inner(){
alert(++i);
}
return Inner;
}
var inner = Outer();
inner();
inner();
inner();
在上述代码中Outer()函数内部定义的函数Inner()被返回,外界只能通过Inner()函数访问Ouer()函数内部的变量i,而inner()函数在多次调用的过程中能够”记忆“其被定义的环境中的变量i。
3、 通过内部类模拟闭包
interface Incrementable {
void increment();
}
class Callee1 implements Incrementable {
private int i = 0;
public void increment() {
i++;
System.out.println(i);
}
}
class MyIncrement {
protected void increment() {System.out.println("Other operation");}
static void f(MyIncrement mi) {mi.increment();}
}
class Callee2 extends MyIncrement {
private int i=0;
private class Closure implements Incrementable {
public void increment() {
Callee2.this.increment();
i++;
System.out.println(i);
}
}
Incrementable getCallbackReference() {
return new Closure();
}
}
class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbh) {callbackReference = cbh;}
void go() {callbackReference.increment();}
}
public class Callbacks {
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();
}
}
/*
MyIncrement.f(c2)执行结果:
Other operation
====================
call1的两次调用的执行结果
1
2
===================
call2的两次调用的执行结果
Other operation
1
Other operation
2
*/
上述例子参考自《java编程思想》第四版,个人觉得代码跟文本描述有点不一致。主要的改动是将MyIncrement中incrment()方法由public改为protected,Callee2中没有覆写原来的increment()方法,改为在内部类Closuer中直接调用该方法,从模拟闭包的行为来看也可以不调用该方法。对于改过后的代码,Callee2中只有一个public方法getCallbackReference返回内部类的实例引用,注意Closure是private的,在Callee2外部无法实例化,并且Callee2中并没有提供操作private变量i的get/set方法,如此只有通过Closure的实例调用其increment()方法才能访问到私有变量i,当连续调用Closure的increment()方法时,increment()方法能够“记忆”其外部定义的private变量i。再看另外一个例子:
interface Action
{
void Run();
}
public class ShareClosure {
private int j=10;
public List<Action> Input()
{
List<Action> list = new ArrayList<Action>();
for(int i=0;i<10;i++)
{
list.add(new Action() {
@Override
public void Run() {
j++;
System.out.println(j);
}
});
}
return list;
}
public static void main(String[] args) {
ShareClosure sc = new ShareClosure();
List<Action> actions=sc.Input();
for(Action action:actions){
action.Run();
}
}
/*
11
12
13
14
15
16
17
18
19
20
*/
}
上述代码ShareClosure中只有一个public方法返回匿名内部类实例的List列表,只有通过该内部类实例才能访问到ShareClosure中的private变量j。调用该匿名内部类实例的run方法可知每个内部类实例都“记忆”了定义该内部类的外部环境的变量i。
2.5、内部类的覆写
先看代码:
class A{
public A(){
new B();
}
public class B{
public B(){
System.out.println("内部类B");
}
}
}
class C extends A{
//显示调用被覆盖后的B
public C(){
new B();
}
public class B{
public B(){
System.out.println("覆盖内部类B2");
}
}
}
class D extends A{
//显示调用父类中的B
public D(){
new A().new B();
//编译出错
// super().new B();
// super.new B();
}
public class B{
public B(){
System.out.println("覆盖内部类B3");
}
}
}
public class InnerClassOverride extends A{
public class B{
public B(){
System.out.println("覆盖后的内部类B");
}
}
public static void main(String[] args) {
new A();
System.out.println("=============");
new InnerClassOverride();
System.out.println("=============");
new C();
System.out.println("=============");
new D();
}
}
内部类B
=============
内部类B
=============
内部类B
覆盖内部类B2
=============
内部类B
内部类B
内部类B
InnerClassOverride覆写了父类A中的内部类B,但是实际调用的还是父类中的B。由此看见子类并不能“覆写”父类中的内部类,这两个内部类虽然同名,但属于不同的“命名空间”(外部类)中,是互相独立的。类C显示的调用了自己的内部类B,执行时会先调用父类的构造方法。类D显示调用了父类中B,注意此实例化过程是先调用父类中的构造方法,再调用子类的构造方法,执行子类构造方法时,先实例化一个类A,在通过该实例实例化一个类B,比较有意思的是无法通过super关键字实例化类B。再看一段代码:
class A1{
protected void doSay(B b){
b.say();
}
public void doSayByB(){
doSay(new B());
}
public class B{
public void say(){
System.out.println("say hello");
}
}
}
public class InnerClassInherit extends A1{
public class B extends A1.B{
public void say(){
System.out.println("say hi");
}
}
public void doSayByB(){
doSay(new B());
}
public static void main(String[] args) {
new InnerClassInherit().doSayByB();
}
}
InnerClassInherit中的内部类B继承自父类中的内部类B,通过“覆写”doSayByB()方法可以显示调用InnerClassInherit中的类B,由此可见子类与父类中的同名内部类是属于不同命名空间里的独立的两个类,外部类对于内部类而言是一种“包裹”或者“命名空间”,跟类中的方法与类的关系不同。
2.6 总结
内部类最为常见的是匿名内部类,在spring JDBC中行处理器的定义,Java Swing的事件监听,观察者模式中的方法回调等场景,内部类的最大意义在于可以借助内部类非常方便且容易理解的实现“多重继承”,最容易混淆的是对外部类或者外部方法中变量的访问。最后对所有参考的博客的博主致谢,感谢你们把自己的理解跟经验分享出来!