Java中的 内部类(吐血总结)
1. 内部类的作用
内部类是一个独立的实体,可以用来实现闭包;能与外部类通信;内部类与接口使得多继承更完整
2. 内部类的分类
1)普通内部类
类的实例相关,可以看成是一个实例变量。内部类的类名由 “外部类.内部类” 确定。
普通内部类不能声明 static相关的变量或方法。内部类可以直接访问外部类的所有成员(包括 private成员),隐式或显式(外部类.this)。而外部类可以 new 内部类,实例相关的可以直接 new,static 相关(类相关)需要使用实例的引用去 引用.new。内部类 class 前可以添加 private 与 protected 表示只对其外部类或其子类有访问权限。
final class Demo { private int m = 4; private Demo() { System.out.println("Demo initial"); } private class Inner { private int i = 23; Inner() { System.out.println("Inner initial"); } private void f() { System.out.println("f method" + " i:" + this.i + " m:" + Demo.this.m); } } public static void main(String[] args) { Demo demo = new Demo(); System.out.println("Dispatch Inner class"); Demo.Inner inn = demo.new Inner(); inn.f(); } } /* out: Demo initial Dispatch Inner class Inner initial f method i:23 m:4 *///~
* 关于闭包
Java 中通过 内部类指向顶级类的指针(Demo.this) 访问顶级类中的(又称内部类所在词法作用域)变量。其中 Demo.this ,表示的是顶级类的上下文;而内部类中的 this (可省略)表示的是内部类上下文。
Js 中只有一个 this,它既可以访问函数体外部又可以访问函数体内部。先在内部寻找,如果找不到则去外部寻找。而去外部找就是所谓的闭包引用。闭包又称为词法闭包。
var myObject = { foo: "bar", func: function() { var self = this; console.log("outer func:" + this); console.log("outer func: this.foo = " + this.foo); console.log("outer func: self.foo = " + self.foo); (function() { console.log("inner func:" + this) console.log("inner func: this.foo = " + this.foo); console.log("inner func: self.foo = " + self.foo); }()); } }; myObject.func(); //output: outer func:[object Object] outer func: this.foo = bar outer func: self.foo = bar inner func:[object Window] inner func: this.foo = undefined inner func: self.foo = bar
* 注意:
for (var i = 0; i < 5; i++) { var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); btn.addEventListener('click', function(){ console.log(i); }); document.body.appendChild(btn); } //提供两种基本解决思路: for (var i = 0; i < 5; i++) { var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); btn.a = i btn.addEventListener('click', function(){ console.log(this.a); }); document.body.appendChild(btn); } for (var i = 0; i < 5; i++) { var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); //使用 IIFE 的方式有个副作用,函数体内代码立即执行。而这里不应立即执行,故返回一个函数,延迟执行。 btn.addEventListener('click', (function(arg){ return function() {console.log(arg);} })(i)); document.body.appendChild(btn); }
2)局部内部类
方法和作用域中的内部类称为局部内部类,只在其词法作用域中可被访问。可以重载构造器。
3)匿名内部类
匿名内部类引用外部类方法中的局部变量必须是 final的原因:对于外部类方法中的局部变量,在该方法调用完成后从栈中清除( remove from stack) ,这时匿名内部类对象将变得无法访问它们。如果将这些变量声明为 final,他们实际上将不再是一个变量而变成了常量,编译器将在编译时把它们替换为具体的常量。(也有观点认为局部变量整体复制,final 保证一致性。)
匿名内部类没有具名构造器,new Wrapper(x) {} 将值传递给基类的构造器。可以被匿名内部类直接引用,不需要 final。
abstract class Base { public Base(int i) { System.out.println("Base constructor, i=" + i + "\n"); } public abstract void f(); } class AnonymousConstructor { private int m = 10000; public Base getBase(int i, final int outer) { return new Base(i) { //这里引用 局部变量 i,是被匿名内部类的基类使用,不需要声明 final。 { System.out.println("AnonymousInnerClass instance initializer\n"); } @Override public void f() { // 局部变量 outer 也是被匿名内部类直接使用,需要声明为 fianl System.out.println("This variable must declare final first: " + outer + "\n"); // 不同于前面对局部变量 i 的使用,这里是匿名直接使用,则需要声明 final。否则报错!(java 8 或以上除外) //System.out.println("This variable must declare final first: " + i + "\n"); // 对非局部变量 m 的使用,不需要声明 final。m 等同与 AnonymousConstructor.this.m System.out.println(m); } }; } public static void main(String[] args) { AnonymousConstructor ac = new AnonymousConstructor(); Base base = ac.getBase(3, 9); base.f(); } }
4)嵌套类
如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为 static,称之为静态内部类或嵌套类。特点是:创建嵌套类的实例不需要其外围类的实例;不能从嵌套类的实例中访问非静态的外围类的实例(没有 this 引用);嵌套类不会随外部类加载而初始化。接口内部的类自动是 public static 的,可以将嵌套类放入接口中,从而使得他们可以被该接口的不同实现所共用。
final class Demo { private int m = 4; private static int n = 6; private Demo() { System.out.println("Demo initial"); } static class Inner { private int i = 23; Inner() { System.out.println("Inner initial"); } private void f() { System.out.println("f method" + " i:" + this.i + " n:" + Demo.n); } } public static void main(String[] args) { // Demo.Inner inn = new Inner(); // Inner initial // inn.f(); // f method i:23 n:6 new Demo(); // Demo initial } } /* out: Demo initial *///~
* 关于 java 中的 interface:
正如 接口内部的类自动是 public static 的,类中 嵌套的接口 (nested interface)也是自动 "static" 的,并且能够被移除。同样的,interface 方法上的 "public" 和域名上的 "public final" 也可以被移除。嵌套 interface 示例:
public class Foo { public interface Bar { void callback(); } public static void registerCallback(Bar bar) {...} } // ...elsewhere... Foo.registerCallback(new Foo.Bar() { public void callback() {...} });