final關(guān)鍵字
- 被其修飾的類,不能被繼承。
- 被其修飾的方法,不能被覆蓋。
- 被其修飾的變量,是一個常量,不能被修改,所以定義時必須初始化(和C++的const類似)。
一般有final,會搭配static使用。如
final static double PI = 3.14;
- 常量的命名規(guī)則 --> 所有字母大寫,多個單詞,中間用下劃線連接。
抽象類
貓和狗有共性,將共性抽取出來,放入Animal中,Animal是抽象的(想象不出實體是什么)。
public abstract class Animal {
// 抽象類也有構(gòu)造函數(shù),給子類初始化用
public Animal() {
System.out.println("我們都是動物");
}
public abstract void eat();
}
// 也可以存在非抽象函數(shù)
public void sleep() {
System.out.println("睡覺了zzz~");
}
}
class Cat extends Animal {
Cat() {
System.out.println("我是一只小貓咪");
}
@Override
public void eat() {
System.out.println("小貓吃魚");
}
}
class Dog extends Animal {
Dog() {
System.out.println("我是一條小狗狗");
}
@Override
public void eat() {
System.out.println("狗狗啃骨頭");
}
}
public class Demo {
public static void main(String args[]) {
public static void main(String[] args) {
Animal a = new Cat();
a.eat();
a.sleep();
// 打印如下內(nèi)容
// 我們都是動物
// 我是一只小貓咪
// 小貓吃魚
// 睡覺了zzz~
}
}
因為類Cat、類Dog或者其他類要繼承類Animal,且類Animal的show()是抽象的未被定義的,子類必須覆蓋父類的方法,定義自己特有的eat()
方法(貓和狗吃的東西不全一樣),故在抽象類中的方法只是聲明。
抽象類的特點
- 方法只有聲明沒有實現(xiàn),為抽象方法,修飾符abstract,抽象方法必須在抽象類中。
- 抽象類不可以實例化,即不能new。因為調(diào)用抽象方法沒有意義。
- 子類必須覆蓋抽象類的所有抽象方法后,該子類才能實例化,否則還是抽象類。
Q:抽象類有沒有構(gòu)造函數(shù)?
A:有,用來給子類初始化,如上例,最先打印我們都是動物。
Q:可以沒有抽象方法嗎,或者說,可以不全是抽象方法嗎?
A;可以。但是接口interface必須全是抽象方法。
abstract關(guān)鍵字不能和哪些關(guān)鍵字共存
- private:因為子類要覆蓋抽象類的抽象方法,私有后不可訪問,怎么覆蓋?
- static:抽象類中只是聲明了,子類需要重寫抽象類的方法。而靜態(tài)方法不能被重寫,所有矛盾了。
- final:final修飾的方法不能被覆蓋,子類要覆蓋啊,所以不能加。
接口-interface
當一個抽象類中全部是抽象方法時,可將其定義為interface。
- 變量規(guī)定為:public static final,即使寫成int a,也被認為是
public static final int a;
- 方法規(guī)定為:public abstract
- 接口中的成員,都是public的
接口和抽象類一樣,不可實例化,用來給其他類擴展功能,接口與接口之間為繼承關(guān)系,接口可以多繼承。Java不支持直接多繼承,改成了多實現(xiàn),即一個子類多個接口,解決了單繼承的局限性。
抽象類和接口
抽象類只能被單繼承,接口可以背多實現(xiàn)。
抽象類中也可以定義非抽象方法,子類可以直接用非抽象方法。接口中只能定義抽象方法。共同點是,其抽象方法都必須被重寫。
抽象類定義基本的共性內(nèi)容,接口是定義額外的功能。
多態(tài)
對于Animal a = new Cat()
,new出子類,卻讓父類指向子類,就叫多態(tài)。
public void method(Animal a) {
a.eat(); // 運行時根據(jù)具體傳入的實參,來調(diào)用對應的方法
// 若Dog和Cat都繼承了Animal,參數(shù)傳入Dog就執(zhí)行dog.eat(),傳入Cat就執(zhí)行cat.eat()
// 這有點像C++中的動態(tài)綁定
}
// 和以下函數(shù)重載比較起來,是不是方便了
public void method(Dog a) {
a.eat()
}
public void method(Cat a) {
a.eat();
}
多態(tài)好處
從上例可以看出,多態(tài)的好處:前期定義的代碼可以用于后期,比如再來一個Wolf類繼承Animal,就可以傳入wolf調(diào)用以上函數(shù),傳入的就是wolf了。
多態(tài)缺點
前期定義的內(nèi)容,不可調(diào)用后期子類的特有內(nèi)容。舉個例子
public void method(Animal a) {
a.eat(); // 父類定義了該方法
a.catchMouse(); // Animal沒有定義“抓老鼠”的方法,若是傳入的參數(shù)不是Cat,則報錯
}
// 怎么解決?強轉(zhuǎn)回來!如下
public void method(Animal a) {
a.eat();
// 先判斷傳入的實參,若是Cat,進行強轉(zhuǎn)回Cat后,再執(zhí)行“抓老鼠”
if (a instanceof Cat){
Cat c = (Cat)a;,
c.catchMouse();
}
// 若傳入的是狗,就執(zhí)行“叫”
if (a instanceof Dog) {
Dog d = (Dog)a;
d.bark();
}
}
Animal a = new Cat();
- 若子類沒有覆蓋父類的方法,就調(diào)用父類自己的方法。
- 若子類調(diào)用了自己特有的方法,父類并沒有定義該方法,則編譯不通過。Cat類型提升為Animal后,Cat特有的方法會被舍棄,與Animal同名的函數(shù)被覆蓋。
換個說法,a是Animal,它只能用Animal定義過的方法和變量,但執(zhí)行函數(shù)時實際執(zhí)行的是Cat的重寫方法。總的來說,如下
- 對于成員變量,和靜態(tài)函數(shù):編譯和運行都是看父類是否具有該變量和靜態(tài),若a調(diào)用了Cat獨有的變量,則報錯。
- 對于成員函數(shù),編譯時候檢查Animal是否定義該方法,運行時候執(zhí)行子類重寫的方法,若子類沒有重寫,自然執(zhí)行父類的。
內(nèi)部類-嵌套類
public class Out {
private String out;
private class In {
private String in;
}
}
- 內(nèi)部類可以直接訪問外部類的成員(private的也行)
- 但是外部類要訪問內(nèi)部類,必須建立內(nèi)部類的對象。
- 內(nèi)部類也可以是static的,這是,內(nèi)部類隨著外部類的加載而加載,相當于是外部類。
Out.In in = new Out().new In(); // 非static內(nèi)部類的實例化
Out.In in = new Out.In(); // static內(nèi)部類可以這樣實例化
In in = new In(); // static內(nèi)部類也可以這樣實例化,相當于外部類了,僅在本例有效
如果內(nèi)部類用到了靜態(tài)成員,則內(nèi)部類必須是static的。
同名變量的訪問。
public class Out {
private int num = 1;
private class In {
int num= 2;
public void show() {
int num = 3;
System.out.println(num); // 打印3
System.out.println(this.num); // 打印2
System.out.println(Out.this.num); // 打印1
}
}
}
方法內(nèi)部的內(nèi)部類
public class Out {
public void func() {
// num是func方法中的局部變量,func中又定義了內(nèi)部類,用到這個num,該num必須是final的
final int num = 1;
// java8中,會默認加上final,所以可以直接int num = 1;
class In {
public void show() {
System.out.println(num);
}
}
}
}
方法里的內(nèi)部類不能訪問外部類方法中的局部變量,除非變量被聲明為final類型,因為內(nèi)部類對象的生命周期超過局部變量的生命周期。有可能出現(xiàn)成員方法已調(diào)用結(jié)束,局部變量已死亡,但匿名內(nèi)部類的對象仍然活著。
Java 8中:如果局部變量被匿名內(nèi)部類訪問,那么該局部變量相當于自動使用了final修飾。
匿名內(nèi)部類
若一個程序的某個類只使用了一次,則可定義為匿名內(nèi)部類,用完就消亡。條件是必須繼承或者實現(xiàn)一個外部類或者接口。
abstract class Person {
public abstract void eat();
}
// 不用內(nèi)部類的情況,需要再寫一個類繼承Person覆蓋方法
class Child extends Person {
@Override
public abstract void eat() {
System.out.println("Child eat");
}
}
public class Out {
public static void main(String[] args) {
// 匿名內(nèi)部類,這里相當于繼承了抽象類Person,新寫了一個無名的子類,并覆蓋了eat方法
new Person() {
public void eat() {
System.out.println("eat something");
}
}.eat();
}
}
下面內(nèi)容轉(zhuǎn)自Nerxious-博客園,總得得不錯,特地搬過來。
// 不使用匿名內(nèi)部類
abstract class Person {
public abstract void eat();
}
class Child extends Person {
public void eat() {
System.out.println("eat something");
}
}
public class Demo {
public static void main(String[] args) {
Person p = new Child();
p.eat();
}
}
//運行結(jié)果:eat something
// 可以看到,我們用Child繼承了Person類,然后實現(xiàn)了Child的一個實例,將其向上轉(zhuǎn)型為Person類的引用但是,如果此處的Child類只使用一次,那么將其編寫為獨立的一個類豈不是很麻煩?這個時候就引入了匿名內(nèi)部類
// 匿名內(nèi)部類的基本實現(xiàn)
abstract class Person {
public abstract void eat();
}
public class Demo {
public static void main(String[] args) {
Person p = new Person() {
public void eat() {
System.out.println("eat something");
}
};
p.eat();
}
}
//運行結(jié)果:eat something
// 可以看到,我們直接將抽象類Person中的方法在大括號中實現(xiàn)了這樣便可以省略一個類的書寫并且,匿名內(nèi)部類還能用于接口上
// 在接口上使用匿名內(nèi)部類
interface Person {
public void eat();
}
public class Demo {
public static void main(String[] args) {
Person p = new Person() {
public void eat() {
System.out.println("eat something");
}
};
p.eat();
}
}
//運行結(jié)果:eat something
// 由上面的例子可以看出,只要一個類是抽象的或是一個接口,那么其子類中的方法都可以使用匿名內(nèi)部類來實現(xiàn)。最常用的情況就是在多線程的實現(xiàn)上,因為要實現(xiàn)多線程必須繼承Thread類或是繼承Runnable接口
// Thread類的匿名內(nèi)部類實現(xiàn)
public class Demo {
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
}
};
t.start();
}
}
// 運行結(jié)果:1 2 3 4 5
// Runnable接口的匿名內(nèi)部類實現(xiàn)
public class Demo {
public static void main(String[] args) {
Runnable r = new Runnable() {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
}
};
Thread t = new Thread(r);
t.start();
}
}
// 運行結(jié)果:1 2 3 4 5
by @sunhiayu
2016.12.11