[隨筆][Java][讀書筆記][thinking in java][第十章 內部類]
- 可以將一個類定義在另一個類的內部,這就是內部類。
10.1 創建內部類
public class Parcell { class Contents { private int i = 11; public int value() { return i; } } class Destination { private String label; Destination(String whereTo) { label = whereTo; } String readLabel() { return label; } } public void ship(String dest) { Contents c = new Contents(); Destination d = new Destination(dest); System.out.println(d.readLabel()); } public static void main(String[] args) { Parcell p = new Parcell(); p.ship("Hello"); } }
- 更典型的用法,外部類有一個方法, 方法返回一個指向內部類的引用。
public class Parcell2 { class Contents { private int i = 11; public int value() { return i; } } class Destination { private String label; Destination(String whereTo) { label = whereTo; } String readLabel() { return label; } } public Destination to(String s) { //外部類的方法,返回一個指向內部類的引用 return new Destination(s); } public Contents contents() { return new Contents(); } public void ship(String dest) { Contents c = contents(); Destination d = to(dest); System.out.println(d.readLabel()); } public static void main(String[] args) { Parcell2 p = new Parcell2(); p.ship("Hello"); Parcell2 q = new Parcell2(); Parcell2.Contents c = q.contents(); //內部類的名字嵌套在外部類的名字中 Parcell2.Destination d = q.to("world"); } }
- 如果想從外部類的非靜態方法之外的任意位置創建某個內部類的對象,必須指明這個對象的類型:OuterClassName.InnerClassName。
//錯誤實例 public class Demo { class A { private int i = 11; public int value() { return i; } } public A getA() { return new A(); } public static void main(String[] args) { Demo.A obj = new Demo.A(); System.out.println(obj.value()); } } ![](https://img2018.cnblogs.com/blog/1034390/201809/1034390-20180905151651930-991083308.png) ---------------- //正確實例 public class Demo { class A { private int i = 11; public int value() { return i; } } public A getA() { return new A(); } public static void main(String[] args) { Demo tmp = new Demo(); Demo.A obj = tmp.getA(); System.out.println(obj.value()); } } //內部類相對於外部類來說是非靜態的,只有外部類的對象創建了之後,才可以使用內部類。需要在外部類創建一個方法,返回一個內部類的對象。如果直接在外部類使用限定的名字創建內部類的對象,則會引起“從靜態上下文引用非靜態變量”的編譯錯誤。
10.2 鏈接到外部類
- 創造一個內部對象需要首先創建一個外部對象,當生成一個內部類的對象的時候,此對象與制造它的外圍對象之間就產生了一種聯系,所以它能訪問其外圍對象的所有成員。內部類還擁有其外圍類的所有元素的訪問權。
interface Selector {
boolean end();
Object current();
void next();
}
public class Sequence {
private Object[] items;
private int next = 0;
public Sequence(int size) { items = new Object[size]; }
public void add(Object x) {
if(next < items.length)
items[next++] = x;
}
private class SequenceSelector implements Selector { //內部類定義成為private的
private int i = 0;
public boolean end() { return i == items.length; } //內部類的對象可以訪問外部類關聯對象的成員變量
public Object current() { return items[i]; }
public void next() { if(i < items.length) i++; } //同時內部類的對象可以修改關聯對象的成員變量,即使是私有的
}
public Selector selector() {
return new SequenceSelector();
}
public static void main(String[] args) {
Sequence sequence = new Sequence(10);
for(int i = 0; i < 10; i++) {
sequence.add(Integer.toString(i));
}
Selector selector = sequence.selector();
while(!selector.end()) {
System.out.println(selector.current() + " ");
selector.next();
}
}
}
- 內部類自動擁有對其外部類所有成員的訪問權。當某個外圍類的對象創建了一個內部類的對象時,內部類對象會秘密捕獲一個指向外圍對象的引用。在構建一個內部類對象的時候,需要一個指向其關聯的外部對象的引用,如果編譯器看不到這個引用,就會報錯。
- 定義的內部類可以有一些成員變量和外部類的一致。
10.3 使用.this和.new
- 如果需要使用外部類對象的引用,可以使用.this,產生的引用自動具有正確的類型。
public class DotThis {
void f() { System.out.println("DotThis.f()"); }
public class Inner {
public DotThis outer() {
return DotThis.this; //使用.this引用關聯的外部對象
}
}
public Inner inner() { return new Inner(); }
public static void main(String[] args) {
DotThis dt = new DotThis();
DotThis.Inner dti = dt.inner();
dti.outer().f();
}
}
- 使用.new創建某個內部類的對象。
public class DotNew {
public class Inner {}
public static void main(String[] args) {
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
}
- 可以在外部類中定義一個方法,在這個方法創建一個內部類的對象,然後返回該對象的引用---依賴這種方法創建一個內部類的對象。或者創建一個外部類的對象之後,在使用.new創建一個內部類的對象。共兩種方式。
如果想創建內部類的對象,必須使用外部類的對象來創建內部類的對象。在擁有外部類對象之前,是不可以擁有內部類對象的,因為內部類會暗地裏鏈接到創建它的外部對象上面,如果創建的是嵌套類(靜態的內部類),則不需要對外部類對象的引用。
- 將.new應用到Parcell例子上
public class Parcell3 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) { label = whereTo; }
String readLabel() { return label; }
}
public static void main(String[] args) {
Parcell3 p = new Parcell3();
Parcell3.Contents c = p.new Contents();
Parcell3.Destination d = p.new Destination("hello");
}
}
14.4 內部類與向上轉型
- 將內部類向上轉型,轉型為一個接口。
- 接口的所有成員都默認設置成為public。protected同時擁有包訪問權限。
class Parcel4 {
private class PContents implements Contents {
private int i = 11;
public int value() { return i; }
}
protected class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
}
public Destination destination(String s) {
return new PDestination();
}
public Contents contents() {
return new PContents();
}
}
public class TestParcel {
public static void main(String[] args) {
Parcel4 p = new Parcel4();
Contents c = p.contents();
Destination d = p.destination();
Parcel4.PContents pc = p.new PContents(); //編譯錯誤,無法訪問私有類
}
}
- 內部類被聲明為private的,所有除了使用外部類的成員方法之外無法訪問。
10.5 在方法和作用域內的內部類
- 可以在方法或者其他任意的作用域內定義內部類。---創建一個輔助類,但是不希望這個類是公共的。
- 局部內部類(在方法的作用域內的類)
10.6 匿名內部類
public class Parcel7 {
public Contents contents() {
return new Contents() { //Contents是一個接口
private int i = 11;
public int value() { return i; }
}
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
}
//等價於
class MyContents implements Contents {
private int i = 11;
public int value() { return i; }
}
public Contents contents() { return new MyContents(); }
//contents方法將返回值的生成與表示這個返回值的類的定義結合到了一起。同時這個類是匿名的。
//創建一個繼承自Contents的匿名類的對象
//使用的是默認構造方法進行構造
- 使用有參數的構造方法
public class Parcel8 {
public Wrapping warpping(int x) {
return new Warpping(x) { //這個x是傳遞給基類的構造函數的參數,直接傳遞過去了,內部匿名類可以自定義構造函數嗎???
public int value() { return super.value(); }
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Warpping w = p.warpping(10);
}
}
- 在匿名類中定義字段時,對其進行初始化
public class Parcel9 {
public Destination destination(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.destination("hello");
}
}
interface Destination {
String readLabel();
}
//或者
public class Parcel9 {
public Destination destination(String dest) {
final String tmp = dest;
return new Destination() {
private String label = tmp;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.destination("hello");
System.out.println(d.readLabel());
}
}
interface Destination {
String readLabel();
}
- 如果一個匿名內部類希望使用一個在其外部定義的對象,編譯器要求其參數引用是final的。既可以在參數聲明的型參數聲明為final的,也可以在方法內部聲明一個final的類型應用,然後在匿名內部類中使用這個final類型的變量。
- 在匿名類中不可能有明明構造方法(匿名類沒有名字)。通過實力初始化,可以達到為匿名內部類創建一個構造器的效果。
abstract class Base {
public Base(int i) {
System.out.println("base constructor " + i);
}
public abstract void f();
}
public class Demo {
public static Base getBase(int i) {
return new Base(i) {
{ System.out.println("inside instance initialize"); }
public void f() {
System.out.println("in anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
}
//結果
![](https://images2018.cnblogs.com/blog/1034390/201809/1034390-20180906102113192-530630349.png)
如果一個參數是被內部匿名類直接使用,該變量需要是final的,如果該參數是傳遞給匿名內部類的基類的,則不需要是final的。實例初始化就是在類定義的內部被大括號包住的一個或幾個語句。
- 對於這樣的一個類,實例初始化先進行,然後是構造函數
public class Demo {
Demo() { System.out.println("a"); }
{ System.out.println("b"); }
public static void main(String[] args) {
Demo tmp = new Demo();
}
}
//結果
![](https://images2018.cnblogs.com/blog/1034390/201809/1034390-20180906102705260-803198269.png)
- 匿名內部類或者繼承某個類或者實現某個接口,兩者不可兼得。當實現某個接口的時候,只能實現一個接口。
10.7 嵌套類
- 如果不需要內部類對象與其外部類對象之間存在聯系,可以將內部類聲明為static的,稱為嵌套類。
- 普通的內部類對象隱式的保存了一個引用,指向創建它的外圍類對象。
對於嵌套類
要常見嵌套類對象,並不需要其外圍類的對象
不能從嵌套類的對象中訪問非靜態的外圍類對象- 普通內部類中不能存在static的字段和數據,也不能包含嵌套類。內部類的字段方法,只能放在類的外部層次上。
public class Parcel11 {
private static class ParcelContents implements Contents {
private int i = 11;
public int value() { return i; }
}
protected static class ParcelDestination implements Destination {
private String label;
private ParcelDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
public static void f() {}
static int x = 10;
static class AnotherLevel {
public static void f() {}
static int x = 10;
}
}
public static Destination destination(String s) {
return new ParcelDestination(s);
}
public static Contents contents() {
return new ParcelContents();
}
public static void main(String[] args) {
Contents c = contents();
Destination d = destination("hello");
}
}
interface Destination {
String readLabel();
}
interface Contents {
int value();
}
//在main方法中,不需要首先創建外圍類的對象,
//Contents c = new ParcelContents();的形式也可以。
//普通的內部類有一個.this引用可以鏈接到外圍類的對象,嵌套類就沒有這個特殊的this引用。
- 嵌套類可以作為接口的一部分。放置到接口內部的任何類都是自動稱為public和static的。可以在內部類中實現外圍接口。
public interface ClassInInterface {
void howdy();
class Test implements ClassInInterface {
public void howdy() {
System.out.println("howdy");
}
public static void main(String[] args) {
new Test().howdy();
}
}
}
//結果
![](https://images2018.cnblogs.com/blog/1034390/201809/1034390-20180906111923422-2129704316.png)
用途:創建某些公共的代碼,是的他們可以被某個接口的所有不同實現所共用,可以使用接口內的嵌套類。去看一下接口中定義的變量!!!!!!
- 使用嵌套類放置測試代碼
public class TestBed {
public void f() { System.out.println("f()"); }
public static class Tester {
public static void main(String[] args) {
TestBed t = new TestBed();
t.f();
}
}
}
//編譯之後生成的字節碼文件
![](https://images2018.cnblogs.com/blog/1034390/201809/1034390-20180906113021181-393775854.png)
這樣,這些已經編譯的代碼並不包含那些用於測試的main方法了。
- 無論內部類嵌套了多少層,它都能透明的訪問所有它所嵌套的外圍類的所有成員
class MNA {
private void f() {}
class A {
private void g() {}
public class B {
void h() {
g();
f();
}
}
}
}
public class Demo {
public static void main(String[] args) {
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.h();
}
}
//生成的字節碼文件
![](https://images2018.cnblogs.com/blog/1034390/201809/1034390-20180906141317011-472838825.png)
10.8 為什麽需要內部類
- 一般來說,內部類繼承自某個類或者實現某個接口,內部類的代碼操作創建它的外圍類的對象。所以可以認為內部類提供了某種進入其他外圍類的窗口。
- 每個內部類都能獨立的繼承自一個接口,所以無論外圍類是否已經繼承了某個接口,對於內部類沒有影響。
- 兩種方式,實現多個接口。
interface A {}
interface B {}
class X implements A, B {}
class Y implements A {
B makeB() {
return new B() {};
}
}
public class MultiInterfaces {
static void takesA(A a) {}
static void takesB(B b) {}
public static void main(String[] args) {
X x = new X();
Y y = new Y();
takesA(x);
takesA(y);
takesB(x);
takesB(y.makeB());
}
}
- 如果擁有的是抽象的類或者具體的類,而不是接口,那麽只能使用內部類才能實現多重繼承。
class D {}
abstract class E {}
class Z extends D {
E makeE() { return new E() {}; }
}
public class Demo {
static void takesD(D d) {}
static void takesE(E e) {}
public static void main(String[] args) {
Z z = new Z();
takesD(z);
takesE(z.makeE());
}
}
閉包:一個可調用的對象,它記錄了一些信息,這些信息源自於創建它的作用域。內部類是面向對象的閉包,不僅包含外圍對象的信息,還自動擁有一個指向此外圍類對象的引用。在此作用域內,內部類有權操作所有的成員,包括private成員,包括成員變量和成員函數,都可以使用,因為包含一個引用,所以可以使用成員函數。
- 一個通過內部類提供閉包的功能的例子。
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 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();
}
}
//結果
![](https://images2018.cnblogs.com/blog/1034390/201809/1034390-20180906154337921-1816553863.png)
- 一個控制框架的例子
- 事件的描述,接口描述了要控制的事件,默認基於之間執行
public abstract class Event {
private long eventTime;
protected final long delayTime;
public Event(long delayTime) {
this.delayTime = delayTime;
start();
}
public void start() {
eventTime = System.nanoTime() + delayTime;
}
public boolean ready() {
return System.nanoTime() >= eventTime;
}
public abstract void action();
}
在構造函數中調用了非靜態的成員方法。一般認為構造函數是靜態的?????start方法沒有放在構造方法中,是因為在時間運行之後還可以重新啟動計時器。如果想要重復進行一件事,那麽就可以在action中調用start方法。
- 一個用來管理並觸發事件的實際控制框架。Event對此昂被保存在List
import java.util.*;
public class Controller {
private List<Event> eventList = new ArrayList<Event>();
public void addEvent(Event c) { eventList.add(c); }
public void run() {
while(eventList.size() > 0) {
for(Event e : new ArrayList<Event>(eventList)) {
if(e.ready()) {
System.out.println(e);
e.action();
eventList.remove(e);
}
}
}
}
}
run()方法循環遍歷eventList,尋找就緒的事件,調用其action方法,然後從隊列中移除。
- 框架的完整實現由單個的類創建,從而使得不同的實現細節被封裝起來。內部類用來表示解決問題所需要的不同action()。
- 利用內部類方便的訪問外部類的任意成員。
public class GreenhouseControls extends Controller {
private boolean light = false;
public class LightOn extends Event {
public LightOn(long delayTime) { super(delayTime); }
public void action() {
light = true;
}
public String toString() { return "Light is on"; }
}
public class LightOff extends Event {
public LightOff(long delayTime) { super(delayTime); }
public void action() {
light = false;
}
public String toString() { return "Light is off"; }
}
private boolean water = false;
public class WaterOn extends Event {
public WaterOn(long delayTime) { super(delayTime); }
public void action() {
light = true;
}
public String toString() { return "Water is on"; }
}
public class WaterOff extends Event {
public WaterOff(long delayTime) { super(delayTime); }
public void action() {
light = true;
}
public String toString() { return "Water is off"; }
}
private String thermostat = "Day";
public class ThermostatNight extends Event {
public ThermostatNight(long delayTime) {
super(delayTime);
}
public void action() {
thermostat = "Night";
}
public String toString() {
return "Thermostat on night setting";
}
}
public class ThermostatDay extends Event {
public ThermostatDay(long delayTime) {
super(delayTime);
}
public void action() {
thermostat = "Day";
}
public String toString() {
return "Thermostat on Day setting";
}
}
public class Bell extends Event {
public Bell(long delayTime) { super(delayTime); }
public void action() {
addEvent(new Bell(delayTime));
}
public String toString() { return "Bing!"; }
}
public class Restart extends Event {
private Event[] eventList;
public Restart(long delayTime, Event[] eventList) {
super(delayTime);
this.eventList = eventList;
for(Event e : eventList) {
addEvent(e);
}
}
public void action() {
for(Event e : eventList) {
e.start();
addEvent(e);
}
start();
addEvent(this);
}
public String toString() {
return "Restart system";
}
}
public static class Terminate extends Event {
public Terminate(long delayTime) { super(delayTime); }
public void action() { System.exit(0); }
public String toString() { return "terniating"; }
}
}
一個類的內部,可以存在多個public的內部類。
事件驅動,基於時間進行控制。每個時間對象都包含一個觸發時間,所有的對象入列。然後循環該隊列,找到一個就緒的對象,然後執行該對象的對應方法,然後把該對象從隊列中刪除。繼續循環該隊列,這就是事件驅動,基於時間進行控制。設計的終止時間其實也是一個時間對象,只不過它對應的動作是退出程序。
- 使用框架
public class GreenhouseController {
public static void main(String[] args) {
GreenhouseControls gc = new GreenhouseControls();
gc.addEvent(gc.new Bell(900));
Event[] eventList = {
gc.new ThermostatNight(0),
gc.new LightOn(200),
gc.new LightOff(400),
gc.new WaterOn(600),
gc.new WaterOff(800),
gc.new ThermostatDay(1400)
};
gc.addEvent(gc.new Restart(2000, eventList));
if(args.length == 1) {
gc.addEvent(new GreenhouseControls.Terminate(new Integer(args[0])));
}
gc.run();
}
}
10.9 內部類的繼承
- 內部類的構造器必須連接到指向其外圍類對象的引用,所以在繼承內部類的時候,事情會變得復雜。指向外圍類對象的引用必須初始化,而子類中不存在可連接的默認對象,必須使用特殊的語法說明他們之間的聯系。
class WithInner {
class Inner {}
}
public class InheritInner extends WithInner.Inner {
InheritInner(WithInner wi) {
wi.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInnerii = new InheritInnerii();
}
}
// 在子類的構造方法中,使用enclosingClassReference.super(),提供必要的引用,才能通過編譯。
10.10 內部類可以被覆蓋嗎
10.11 局部內部類
- 可以在一個代碼塊中創建內部類。局部內部類不能有訪問說明符,因為它不是外圍類的一部分;可以訪問當前代碼塊的常量,以及此外圍類的所有成員。
interface Counter {
int next();
}
public class LocalInnerClass {
private int count = 0;
Counter getCounter(final String name) {
class LocalCounter implements Counter {
public LocalCounter() {
System.out.println("LocalCounter()");
}
public int next() {
System.out.println(name);
return count++;
}
}
return new LocalCounter();
}
Counter getCounter2(final String name) {
return new Counter() {
{
System.out.println("Counter()");
}
public int next() {
System.out.println(name);
return count++;
}
};
}
public static void main(String[] args) {
LocalInnerClass lic = new LocalInnerClass();
Counter c1 = lic.getCounter("local inner");
}
}
//輸出結果
![](https://images2018.cnblogs.com/blog/1034390/201809/1034390-20180907114443970-2006876034.png)
10.12 內部類的標識
- 每個類都會產生一個.class文件,其中包含了如何創建該類型的對象的全部信息,此信息產生一個meta-class,叫Class對象。內部類對象也必須產生一個.class文件。外圍類的名字$內部類的名字。
- 什麽時候使用接口,什麽時候使用內部類,或者同時使用。
[隨筆][Java][讀書筆記][thinking in java][第十章 內部類]