---
title: Java CH5
tags: Java, OCA, OCP, SE8, InnerClass
Date: 2019/11/18 2135
---
## 內部類別
- 在一般類別中在定義一個類別,依位置可以分為:
- 一般類部類別 (regular inner class)
- 方法類別 (method class)
- 匿名類別 (anonymous class)
- 靜態內部類別 (static inner class)
### 一般類部類別 (regular inner class)
- 和我們一般建立的類別一模一樣
- 一個外部類別可以有很多個內部類別,同樣的一個內部類別也可以有很多個次內部類別
- 語法:
- ```
class OuterClass {
class InnerClass {
class SubInnerClass {
...
}
}
}
```
- 編譯後的檔名
- 以上面為例,會產生三個檔案,以$區隔
- ```OuterClass.class``` (class OuterClass)
- ```OuterClass$InnerClass.class``` (class InnerClass)
- ```OuterClass$InnerClass$SubInnerClass.class``` (class SubInnerClass)
- 外部類別存取內部類別
- ```
class OuterClass {
class InnerClass {
void mA(){
System.out.println("OuterClass.InnerClass.mA()");
}
}
}
class test {
public static void main(String args[]) {
OuterClass oc = new OuterClass();
OuterClass.InnerClass ic = oc.new InnerClass();
ic.mA();
OuterClass.InnerClass ocic = new OutClass().new InnerClass();
ocic.mA();
}
}
```
- 對 OuterClass 而言,可以直接寫成 InnerClass ic = new InnerClass(); (因為他本來就位於 OuterClass 內。
- 對其他的外部類別來說,InnerClass 是 OuterClass的內部類別,所以需宣告成 OuterClass.InnerClass
#### 一般內部類別的成員存取關係
```
class Outer {
private static int sx=9;
private int x = 7;
class Inner {
private int x=77;
public void foo() {
int x=777;
System.out.println("local x = " + x); //x=777
System.out.println("Inner's x = " + this.x); //Inner's x = this.x = 77
System.out.println("Outer's x = " + Outer.this.x); //Outer's x = Outer.this.x = 7
System.out.println("Outer's sx = " + Outer.sx); //Outer's sx = 9
}
}
}
public class test {
public static void main(String[] args) {
new Outer().new Inner().foo();
}
}
```
Q: 為甚麼Inner可以參照到Outer.this.x??
A: 因為執行時期,Outer會提供自己的this參照給Inner所用。另外Outer.sx也可以是因為Outer.this.sx (nonstaic call static)
### 方法內部類別 (method class)
- DEF:在方法中宣告的類別
- EX:
```
public class test {
public static void main(String[] args) {
new test().see();
}
void see() {
class Inner {
void foo() {
System.out.println("Method Inner Class");
}
}
Inner i=new Inner();
i.foo();
}
}
```
- 建立的方法內部類別物件的程式碼**必須寫在方法類別定義之後**
- 編譯之後,方法內部類別的檔名是 **test$1Inner.class**,其中**1**是流水號
#### 方法區域變數
> 不能直接存取方法內部非 Final 的區域變數 [color=#FF0FFF]
>
```
public class test {
public static void main(String[] args) {
new test().see();
}
void see() {
final int fx=7;
int x = 77;
class Inner {
void foo() {
System.out.println("Local x = " + x); //error,要嘛加上final,不然刪除
System.out.println("Final fx = " + sx);
}
}
Inner i=new Inner();
i.foo();
}
}
```
### 匿名類別 (anonymous class)
> 就是沒有宣告名稱的類別,直接以{...}實作程式碼
> 適用於只會用到一次,之後便不需再使用的類別[color=#45ed40]
>
```
interface Pet {
String attr = "cute";
void skill();
void move();
}
class test {
public static void main(Stringp[] args) {
Pet p = new Pet() {
public void skill() {
System.out.println(attr);
}
public void move() {
System.out.println("run");
}
}; //重要,記得加上分號
p.skill();
p.move();
}
}
```
#### 匿名類別實體檔名
- 外部類別$流水號(1,2...n).class
- 匿名類別在實作interface時,也是用implements來實作的。
- 同樣的在實作abstract class時,也會用extends來實作的。(你在反組譯匿名類別的class時會發現)
- 這種寫法的出現,就是為了簡化成式的撰寫方式
- 多行的問題,在sdk1.6_10被修正了,會在編譯時期拋出例外
### 靜態內部類別 (static innner class)
> 如同加上static 的變數一樣,只是它屬於某個類別,並不是類別中的實體。[color=#45ed40]
> 本身並沒有this的參考
>
> 若外部類別有宣告static成員的話,因為也是被配置在Globe區,因此**外部的靜態成員與靜態內部類別**可以互相看見並存取。[color=#ff0000]
> 而他也是**唯一一個內部類別**可以同時宣告static和non-static的內部類別。
> **註:一般內部類別若宣告了static成員就等於是靜態內部類別**
```
class Outer {
static class Inner {
public void mA() {
out.println("static Inner class A.");
}
public static void mB() {
out.println("static Inner class B.");
}
}
}
public class Test {
public static void main(String[] args) {
Outer.Inner oi = new Outer.Inner();
oi.mA();
oi.mB();
Outer,Inner.mB();
}
}
```
#### 靜態內部類別與成員之間的關係
```
class Outer {
private int x=7;
static private int sx = 9;
static class Inner {
private int x=77;
static private int sx=99;
public void mA() {
out.println(sx); //99
out.println(Outer.sx); //9
out.println(x); //77
out.println(this.x); //77
out.println(Outer.this.x); //error, can't reference Outer nonstatic x
//if want access Outer x, should rewrite in "new Outer().x"
}
public void mB() {
out.println(sx); //99
out.println(Outer.sx); //9
}
}
}
public class Test {
public static void main(String[] args) {
Outer.Inner oi = new Outer().Inner;
oi.mA();
}
}
```
## Keyword: final
- 用final宣告的類別、方法、屬性,不能以任何方式修改(只能被初始化一次)
- 類別不可再被繼承
- 方法不能再被複寫
- 屬性給予定值後不可更動
- 若沒在宣告同時初始化的話,則稱為:[blank final variable]
- 只能在建構子裡使用此方法
## 物件型別轉換
- 種類
- 隱含式轉換 (Casts Up)
- 範圍:大(子類別) 轉 小(父類別)
- EX:多型
- 強制型轉換 (Downward Casts)
- 範圍:小(父類別) 轉 大(子類別)
- EX:FatherClass f = (FatherClass)new ChildrenClass();
- 在轉換時會有風險,應先用**instanceof**來判斷是否可以強制轉型
- **instanceof運算子**
- 使用時機:判斷是否可以強制轉型
- 回傳值:true or false
- 語法:```被轉物件變數 instanceof 欲轉類別名稱```
## Initializer實作區塊
- 實作區塊:指的是{...}
- 若單獨放在class類就是**Initializer實作區塊**
- 種類
- instance initializer
- static initializer
### initializer
- EX:用來初始化instance物件變數
- 編譯時,會將initializer中的程式碼**複製到所有建構子中**
- 假設建構子中原本就有實作的話,**則會將initializer的程式碼複製到原先程式碼的前面**
```
public class Test {
String id, name;
int birth;
Test() {}
Test(int i){}
{ //this is an initializer
id = "A123456789";
name = "Joe";
birth = 19530423;
}
void getInfo() {
System.out.printf("Name: %s, id: %s, birth: %d\n", name, id, birth);
}
public static void main(Stringp[] args) {
new Test().getInfo(); //Name: Joe, id: A123456789, birth: 19530423
new Test(3).getInfo(); //Name: Joe, id: A123456789, birth: 19530423
}
}
```
### static initializer
- **就是針對static成員的初始化**
- 宣告在類別中,而不是在方法裡
- 用來指派或初始static變數
- **於類別載入時執行,且只執行一次**
```
static {
......
}
```
> 注意:static initialzier中,不可以存取或初始化instance的物件變數
> 但在instance initializer中,卻可以存取或初始畫static成員變數 [color=#ff0fff]
>
## 覆寫 equals() 和 hashCode()
### equal()
- 目的
- 讓程式設計者可以很直覺地用來做兩物件之間的比較
- **Object類別中原本就有提供 equals()**
- 比的是兩個物件的**記憶體參照位置**
- 與實際物件本身的數型沒有任何關係 (所以不管怎麼比較都不會是相同的,因為參照值本來就不同,除非引用同個物件)
- 因此我們必須依照實際的情況,來自定什麼條件(屬性)相等,來實作(覆寫)原本的equals()
```
class Ball {
String color;
int num;
Ball(String c, int n) {
color = c;
num = n;
}
}
public class Test {
public static void main(String[] args) {
Ball b1 = new("red", 1);
Ball b2 = new("red", 1);
System.out.println(b1 == b2); //false
System.out.println(b1.equals(b2));
//false,因為Ball繼承Object,使用Object::equals()
}
}
```
#### 覆寫equals()的基本原則
- 使用 **==** 判斷,也許要比較兩者是否來自同個物件實體
- 比較兩者是否屬於同個類別,用 **getClass()**
- 用該類別的必要的成員變數來判斷,利用if-else
- 將Ball類別加上我們覆寫的equals()
```
public boolean equals(Object o) {
if(this == o) return true; //是否來自同個物件實體?
if(o != null && getClass() == o.getClass()) { //是否視同個類別?
if(o instanceof Ball) { //是否類別叫做Ball?
Ball ball = (Ball)o;
if(color.equals(ball.color) && num.equals(ball.num)) //屬性是否相等?
return true;
}
}
return false;
}
```
#### **在覆寫equals()時,一律覆寫hashcode()**
- hashcode (雜湊碼),可以想成是物件的編號
- 用來作物件定址
- 在同個應用程式執行期間,對同個物件呼叫hashcode(),其回傳值必須一樣
- 若兩物件被equals()定義為相等的話,其hashcode()也必須相等。
- 反之,兩物件不相等,**其hashcode()可以不必產生相同的解果(相同也可以)**
- 對不同的物件產生不同的hashcode,有可能提升hashtable的效率
- 詳細參考[Effective Java Programming Language Guide,條款8]