第5關:抽象類
任務描述
本關任務:掌握抽象類的定義及用法。
相關知識
為了完成本關任務,你需要掌握:1.抽象類的概念;2.如何定義抽象類。
抽象類的概念
前面對類的繼承進行了初步的講解。通過繼承,可以從原有的類派生出新的類。原有的類稱為基類或父類,而新的類則稱為派生類或子類。通過這種機制,派生出的新的類不僅可以保留原有的類的功能,而且還可以擁有更多的功能。
除了上述的機制之外,Java也可以創建一種類專門用來當作父類,這種類稱為“抽象類”。抽象類的作用有點類似“模版”,其目的是要設計者依據它的格式來修改並創建新的類。但是並不能直接由抽象類創建對象,只能通過抽象類派生出新的類,再由它來創建對象。
如何定義抽象類
抽象類的定義規則:
- 抽象類和抽象方法都必須用abstract關鍵字來修飾;
- 抽象類不能被實例化,也就是不能用new關鍵字去產生對象;
- 抽象方法只需聲明,而不需實現;
- 含有抽象方法的類必須被聲明為抽象類,抽象類的子類必須復寫所有的抽象方法后才能被實例化,否則這個子類還是個抽象類。
抽象類的定義格式:
abstract class 類名稱 // 定義抽象類
{
聲明數據成員;
訪問權限 返回值的數據類型 方法名稱(參數...)//定義一般方法
{
...
}
abstract 返回值的數據類型 方法名稱(參數...);
//定義抽象方法,在抽象方法里,沒有定義方法體
}
注意:
- 在抽象類定義的語法中,方法的定義可分為兩種:一種是一般的方法,它和先前介紹過的方法沒有什么兩樣;另一種是“抽象方法”,它是以
abstract
關鍵字為開頭的方法,此方法只聲明了返回值的數據類型、方法名稱與所需的參數,但沒有定義方法體。 - 抽象類也可以像普通類一樣,有構造方法、一般方法、屬性,更重要的是還可以有一些抽象方法,留給子類去實現,而且在抽象類中聲明構造方法后,在子類中必須明確調用。
編程要求
根據提示,在右側編輯器補充代碼:
聲明一個名為Person
的抽象類,在Person類中聲明了三個屬性name
、age
和occupation
和一個抽象方法talk()
;
聲明一個Student
類和一個Worker
類,都繼承自Person
類,添加帶有三個參數的構造方法;
分別實例化Student
類與Worker
類的對象,分別調用各自類中被復寫的talk()
方法打印輸出信息;
具體輸出要求請看測試說明。
測試說明
測試輸入: 無
預期輸出:
學生——>姓名:張三,年齡:20,職業:學生!
工人——>姓名:李四,年齡:30,職業:工人!
實現代碼
package case5;
public class abstractTest {
public static void main(String[] args) {
/********* begin *********/
// 分別實例化Student類與Worker類的對象,並調用各自構造方法初始化類屬性。
Student student = new Student("張三",20,"學生");
Worker worker = new Worker("李四",30,"工人");
// 分別調用各自類中被復寫的talk()方法 打印信息。
System.out.println(student.talk());
System.out.println(worker.talk());
/********* end *********/
}
}
// 聲明一個名為Person的抽象類,在Person中聲明了三個屬性name age occupation和一個抽象方法——talk()。
abstract class Person {
/********* begin *********/
String name;
int age;
String occupation;
//abstract void talk(){ //此處錯誤,talk()方法返回值類型是String,同上一關一樣。
//} //並且定義抽象方法,沒有方法體,即不加這一對{}。
public abstract String talk();
/********* end *********/
}
// Student類繼承自Person類,添加帶三個參數的構造方法,復寫talk()方法 返回姓名、年齡和職業信息
class Student extends Person {
/********* begin *********/
public Student(String name,int age,String occupation){
this.name = name;
this.age = age;
this.occupation = occupation;
}
public String talk(){ //復寫父類g抽象類方法時,去掉abstract修飾,其余的修飾完全相同
// System.out.println("學生->姓名:"+this.name+",年齡:"+this.age+",職業:"+this.occupation+"!");//此處直接返回String類型,而不寫輸出語句。
return "學生——>姓名:"+this.name+",年齡:"+this.age+",職業:"+this.occupation+"!";
}
/********* end *********/
}
// Worker類繼承自Person類,添加帶三個參數的構造方法,復寫talk()方法 返回姓名、年齡和職業信息
class Worker extends Person {
/********* begin *********/
public Worker(String name,int age,String occupation){
this.name = name;
this.age = age;
this.occupation = occupation;
}
public String talk(){
return "工人——>姓名:"+this.name+",年齡:"+this.age+",職業:"+this.occupation+"!";
}
/********* end *********/
}
實驗分析
- talk()方法同第4關一樣,返回值類型是String類型,是一句話
- 構造方法沒有返回值
- 子類復寫父類的抽象方法時,去掉abstract修飾
- 抽象方法沒有方法體,不用寫{}。
第6關:final關鍵字的理解與使用
任務描述
本關任務:理解並能正確使用final
關鍵字。
相關知識
為了完成本關任務,你需要掌握:1.final關鍵字的使用; 2.final關鍵字修飾類、成員變量和成員方法。
final關鍵字的使用
在Java中聲明類、屬性和方法時,可使用關鍵字final來修飾。
- final標記的類不能被繼承;
- final標記的方法不能被子類復寫;
- final標記的變量(成員變量或局部變量)即為常量,只能賦值一次。
final關鍵字修飾類、成員變量和成員方法
1.final類
final用來修飾一個類,意味着該類成為不能被繼承的最終類。出於安全性的原因和效率上的考慮,有時候需要防止一個類被繼承。例如,Java類庫中的String類,它對編譯器和解釋器的正常運行有着很重要的作用,不能輕易改變它,因此把它修飾為final類,使它不能被繼承,這就保證了String類的惟一性。同時,如果你認為一個類的定義己經很完美,不需要再生成它的子類,這時也應把它修飾為final類。
定義一個final類的格式如下:
final class ClassName{
...
}
注意:聲明為final的類隱含地聲明了該類的所有方法為final方法。
下面的結論是成立的:聲明一個類既為abstract,又為final是非法的,因為抽象類必須被子類繼承來實現它的抽象方法。下面是一個final類的例子:
final class A{
...
}
class B extends A{//錯誤!不能繼承A
...
}
2.final修飾成員變量
變量被聲明為final
后,成為常值變量(即常量),一旦被通過某種方式初始化或賦值,即不能再被修改。通常static
與final
一起使用來指定一個類常量。例如:
static final int SUNDAY=0;
把final變量用大寫字母和下划線來表示,這是一種編碼規定。
3.final修飾成員方法
用final修飾的方法為最終方法,不能再被子類重寫,可以被重載。
盡管方法重寫是Java非常有力的特征,但有時卻需要避免這種情況的發生。為了不允許一個方法被重寫,在方法的聲明中指定final屬性即可。例如:
class A{
final void method(){}
}
class B extends A{//定義A類的一個子類B
void method(){}//錯誤,method()不能被重寫
}
該例中,因為method()方法在A中被聲明為final,所以不能在子類B中被重寫。如果這樣做,將導致編譯錯誤。
方法被聲明為final有時可以提高性能:編譯器可以自由地內聯調用它們,因為它“知道”它們不會被子類重寫。
編程要求
根據提示,在右側編輯器Begin-End
處補充代碼:
仔細閱讀代碼,在右側編輯器中調整代碼使程序能正確編譯運行;
具體輸出要求請看測試說明。
測試說明
測試輸入:無
預期輸出:
speedlimit=120
running safely with 100kmph
running safely with 100kmph
實現代碼
package case6;
public class finalTest {
public static void main(String args[]) {
Bike1 obj = new Bike1();
obj.run();
Honda honda = new Honda();
honda.run();
Yamaha yamaha = new Yamaha();
yamaha.run();
}
}
//不可以修改 final 變量的值
// final方法,不可以重寫
//// 不可以擴展 final 類
//請在此添加你的代碼
/********** Begin *********/
class Bike1 {
/*final */int speedlimit = 90; //去掉final就好了
void run() {
speedlimit = 120;
System.out.println("speedlimit=120");
}
}
class Bike2 {
/*final */void run() {
System.out.println("running");
}
}
class Honda extends Bike2 {
void run() {
System.out.println("running safely with 100kmph");
}
}
/*final */class Bike3 {
}
class Yamaha extends Bike3 {
void run() {
System.out.println("running safely with 100kmph");
}
}
/********** End **********/
第7關:接口
任務描述
本關任務:掌握接口相關的知識。
相關知識
為了完成本關任務,你需要掌握:
1.接口的定義;
2.接口的實現;
3.接口的擴展。
接口的定義
接口(interface
)是Java所提供的另一種重要技術,它的結構和抽象類非常相似,也具有數據成員與抽象方法,但它與抽象類又有以下兩點不同:
- 接口里的數據成員必須初始化,且數據成員均為常量;
- 接口里的方法必須全部聲明為
abstract
,也就是說,接口不能像抽象類一樣保有一般的方法,而必須全部是“抽象方法”。
接口定義的語法如下:
interface 接口名稱 // 定義抽象類
{
final 數據類型 成員名稱 = 常量; //數據成員必須賦初值
abstract 返回值的數據類型 方法名稱(參數...);
//抽象方法,注意在抽象方法里,沒有定義方法主體
}
接口與一般類一樣,本身也具有數據成員與方法,但數據成員一定要賦初值,且此值將不能再更改,方法也必須是“抽象方法”。也正因為方法必須是抽象方法,而沒有一般的方法,所以抽象方法聲明的關鍵字abstract是可以省略的。
相同的情況也發生在數據成員身上,因數據成員必須賦初值,且此值不能再被更改,所以聲明數據成員的關鍵字final也可省略。事實上只要記得:
接口里的“抽象方法”只要做聲明即可,而不用定義其處理的方式;
數據成員必須賦初值。
接口的實現
在Java中接口是用於實現多繼承的一種機制,也是Java
設計中最重要的一個環節,每一個由接口實現的類必須在類內部復寫接口中的抽象方法,且可自由地使用接口中的常量。
既然接口里只有抽象方法,它只要聲明而不用定義處理方式,於是自然可以聯想到接口也沒有辦法像一般類一樣,再用它來創建對象。利用接口打造新的類的過程,稱之為接口的實現(implementation
)。
接口實現的語法:
class 類名稱 implements 接口A,接口B //接口的實現
{
...
}
接口的擴展
接口是Java實現多繼承的一種機制,一個類只能繼承一個父類,但如果需要一個類繼承多個抽象方法的話,就明顯無法實現,所以就出現了接口的概念。一個類只可以繼承一個父類,但卻可以實現多個接口。
接口與一般類一樣,均可通過擴展的技術來派生出新的接口。原來的接口稱為基本接口或父接口,派生出的接口稱為派生接口或子接口。通過這種機制,派生接口不僅可以保留父接口的成員,同時也可加入新的成員以滿足實際的需要。
同樣的,接口的擴展(或繼承)也是通過關鍵字extends
來實現的。有趣的是,一個接口可以繼承多個接口,這點與類的繼承有所不同。
接口擴展的語法:
interface 子接口名稱 extends 父接口1,父接口2...
{
...
}
編程要求
根據提示,在右側編輯器補充代碼:
聲明一Person接口,並在里面聲明三個常量:name、age、occupation,並分別賦值;
聲明一Student類,此類實現Person接口,並復寫Person中的talk()方法;
實例化一Student的對象s,並調用talk()方法,打印信息;
具體輸出要求請看測試說明。
測試說明
測試輸入:無
預期輸出:
學生——>姓名:張三,年齡:18,職業:學生!
實現代碼
package case7;
public class interfaceTest {
public static void main(String[] args) {
// 實例化一Student的對象s,並調用talk()方法,打印信息
/********* begin *********/
Student s = new Student();
System.out.println(s.talk());
/********* end *********/
}
}
// 聲明一個Person接口,並在里面聲明三個常量:name、age和occupation,並分別賦值,聲明一抽象方法talk()
interface Person {
/********* begin *********/
final String name = "張三";
final int age = 18;
String occupation = "學生"; //final可以省略,因為接口中的屬性都是常量,不可以更改。
public abstract String talk(); //抽象方法沒有方法體。這條語句中abstract也可以省略
/********* end *********/
}
// Student類繼承自Person類 復寫talk()方法返回姓名、年齡和職業信息
class Student implements Person {
/********* begin *********/
public String talk(){
return "學生——>姓名:"+name+",年齡:"+age+",職業:"+occupation+"!";
}
/********* end *********/
}
第8關:什么是多態,怎么使用多態
任務描述
本關任務:掌握對象的多態性。
相關知識
為了完成本關任務,你需要掌握:1.什么是多態;2.多態的實現條件;3.多態的實現形式。
什么是多態
所謂多態:就是指一個類實例的相同方法在不同情形有不同表現形式。多態機制使具有不同內部結構的對象可以共享相同的外部接口。這意味着,雖然針對不同對象的具體操作不同,但通過一個公共的類,它們(那些操作)可以通過相同的方式予以調用。
多態就是同一個接口,使用不同的實例而執行不同操作,如圖所示:
多態性是對象多種表現形式的體現。
現實中,比如我們按下F1
鍵這個動作:
如果當前在Flash
界面下彈出的就是AS 3
的幫助文檔;
如果當前在Word
下彈出的就是Word
幫助;
在Windows
下彈出的就是Windows
幫助和支持。
同一個事件發生在不同的對象上會產生不同的結果。
多態的實現條件
多態的三個條件:
- 繼承的存在(繼承是多態的基礎,沒有繼承就沒有多態);
- 子類重寫父類的方法(多態下調用子類重寫的方法);
- 父類引用變量指向子類對象(子類到父類的類型轉換)。
子類轉換成父類時的規則: - 將一個父類的引用指向一個子類的對象,稱為向上轉型(
upcasting
),自動進行類型轉換。此時通過父類引用調用的方法是子類覆蓋或繼承父類的方法,不是父類的方法。 此時通過父類引用變量無法調用子類特有的方法; - 如果父類要調用子類的特有方法就得將一個指向子類對象的父類引用賦給一個子類的引用,稱為向下轉型,此時必須進行強制類型轉換。
以下是一個多態實例的演示,詳細說明請看注釋:
public class TestAnimalDemo {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 對象調用 show 方法
show(new Dog()); // 以 Dog 對象調用 show 方法
Animal a = new Cat(); // 向上轉型,創建一個Animal類的對象,但創建的方法是Animal類的子類(Cat類)的方法。
a.eat(); // 調用的是 Cat 的 eat
Cat c = (Cat) a; // 向下轉型
c.work(); // 調用的是 Cat 的 work
}
public static void show(Animal a) {
a.eat();
// 類型判斷
if (a instanceof Cat) { // 貓做的事情
Cat c = (Cat) a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog) a;
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃魚");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨頭");
}
public void work() {
System.out.println("看家");
}
}
輸出結果:
吃魚
抓老鼠
吃骨頭
看家
吃魚
抓老鼠
可以用 instanceof
判斷一個類是否實現了某個接口,也可以用它來判斷一個實例對象是否屬於一個類。instanceof 的語法格式為:
對象 instanceof 類(或接口)
它的返回值是布爾型的,或真(true
)、或假(false
)。
多態的實現形式
在Java中有兩種形式可以實現多態:繼承和接口。
- 基於繼承實現的多態
基於繼承的實現機制主要表現在父類和繼承該父類的一個或多個子類對某些方法的重寫,多個子類對同一方法的重寫可以表現出不同的行為。
基於繼承實現的多態可以總結如下:對於引用子類的父類類型,在處理該引用時,它適用於繼承該父類的所有子類,子類對象的不同,對方法的實現也就不同,執行相同動作產生的行為也就不同。
如果父類是抽象類,那么子類必須要實現父類中所有的抽象方法,這樣該父類所有的子類一定存在統一的對外接口,但其內部的具體實現可以各異。這樣我們就可以使用頂層類提供的統一接口來處理該層次的方法。 - 基於接口實現的多態
繼承是通過重寫父類的同一方法的幾個不同子類來體現的,那么就可能是通過實現接口並覆蓋接口中同一方法的幾不同的類體現的。
在接口的多態中,指向接口的引用必須是指定實現了該接口的一個類的實例程序,在運行時,根據對象引用的實際類型來執行對應的方法。
繼承都是單繼承,只能為一組相關的類提供一致的服務接口。但是接口可以是多繼承多實現,它能夠利用一組相關或者不相關的接口進行組合與擴充,能夠對外提供一致的服務接口。所以它相對於繼承來說有更好的靈活性。
編程要求
根據提示,在右側編輯器補充代碼:
聲明一個Animal類,此類中定義eat()方法;
聲明Dog類、Cat類、Lion類,均繼承自Animal類,並復寫了eat()方法;
運用多態方式實例化子類對象並調用eat()方法打印輸出信息;
具體輸出要求請看測試說明。
測試說明
測試輸入:無
預期輸出:
eating bread...
eating rat...
eating meat...
實現代碼
package case8;
public class TestPolymorphism {
public static void main(String[] args) {
// 以多態方式分別實例化子類對象並調用eat()方法
/********* begin *********/
Animal a = new Dog();
a.eat();
Animal b = new Cat();
b.eat();
Animal c = new Lion();
c.eat();
/********* end *********/
}
}
// Animal類中定義eat()方法
abstract class Animal { //定義為abstract類
/********* begin *********/
/*public void eat(){ //父類中方法不寫方法體,可定義為抽象類
}*/
abstract void eat(); //定義為abstract方法
/********* end *********/
}
// Dog類繼承Animal類 復寫eat()方法
class Dog extends Animal {
/********* begin *********/
public void eat(){
System.out.println("eating bread...");
}
/********* end *********/
}
// Cat類繼承Animal類 復寫eat()方法
class Cat extends Animal {
/********* begin *********/
public void eat(){
System.out.println("eating rat...");
}
/********* end *********/
}
// Lion類繼承Animal類 復寫eat()方法
class Lion extends Animal {
/********* begin *********/
public void eat(){
System.out.println("eating meat...");
}
/********* end *********/
}
實驗分析:
- 向上轉型:創建一個Animal類的對象,但創建的方法是Animal類的子類(Cat類)的方法。
- 含有抽象方法的類要定義為抽象類。