課堂作業06
1. 繼承條件下的構造方法調用
(1)源代碼
package demo1;
//一級級的調用,構造時先調用基類的構造函數;
//super
//構造函數調用必須是構造函數中的第一個語句
class Gradeparent
{
public Gradeparent()//默認構造函數
{
System.out.println("Gradeparent Created");
}
public Gradeparent(String string)//重載
{
System.out.println("Gradeparent Created.String"+string);
}
}
class Parent extends
{
public Parent()
{
//super(" hello ");
System.out.println("Parent Created");
}
}
class Child extends Parent
{
public Child()
{
System.out.println("Child Created");
}
}
public class TestInherits {
public static void main(String args[])
{
Child c=new Child();
}
}
(2)設計思想
先定義一個Gradeparent類,再定義一個Parent,
(3)程序結果截圖
第一個是沒有用super,第二個用到了super,super()括號中有參數,所以就調用了有參數的基類構造方法。
(4)結論
父類與子類之間構造方法的調用關系修改Parent構造方法的代碼,顯式調用GrandParent的另一個構造函數,註意這句調用代碼是否是第一句,影響重大!
通過 super 調用基類構造方法,必須是子類構造方法中的第一個語句。繼承基類的方法是加extends關鍵詞。
2. 為什麽子類的構造方法在運行之前,必須調用父類的構造方法?能不能反過來?為什麽不能反過來
構造函數的作用就是初始化,就像親屬關系中,只有有了父親,才會有孩子,不能反過來。
3. 何為“不可變的類”?
創建“不可變的類”的對象後,此對象的屬性不可改,而且也無法從此類派生出新子類。String就是一個典型的例子。不可變的“類”可以方便和安全地用於多線程環境中,訪問它們可以不用加鎖,因而能提供較高的性能。
(1)源程序
public final class Address
{
private final String detail;
private final String postCode;
//在構造方法裏初始化兩個實例屬性
public Address()
{
this.detail = "";
this.postCode = "";
}
public Address(String detail , String postCode)
{
this.detail = detail;
this.postCode = postCode;
}
//僅為兩個實例屬性提供getter方法
public String getDetail()
{
return this.detail;
}
public String getPostCode()
{
return this.postCode;
}
//重寫equals方法,判斷兩個對象是否相等。
public boolean equals(Object obj)
{
if (obj instanceof Address)
{
Address ad = (Address)obj;
if (this.getDetail().equals(ad.getDetail()) && this.getPostCode().equals(ad.getPostCode()))
{
return true;
}
}
return false;
}
public int hashCode()
{
return detail.hashCode() + postCode.hashCode();
}
}
(2)設計思想
定義不允許繼承的類,類中有兩個參數,一個無參數一個有參數,進行方法的重載,類中有兩個實例屬性,還有get函數,得到實例屬性,equals方法判斷對象是否是相等
4.為什麽會輸出這樣的結果
(1)源程序
package demo1;
public class ExplorationJDKSource {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(new A());
}
}
class A{}
(2)截圖
(3)結果分析
前面示例中,main方法實際上調用的是:
public void println(Object x),這一方法內部調用了String類的valueOf方法。
valueOf方法內部又調用Object.toString方法:
public String toString() {
return getClass().getName() +"@" +
Integer.toHexString(hashCode());
}
hashCode方法是本地方法,由JVM設計者實現:
public native int hashCode();
5. 方法覆蓋
(1)源代碼
public class Shape {
public static void main(String args[])
{
Rectangle a=new Rectangle(3,4);
System.out.print("面積"+a.getArea());
}
/*void showArea()//抽象方法,求面積並且顯示
{
}*/
}
interface DiagArea//定義接口類
{
double getDiagonal();//求對角線長度方法
double getArea();//求面積
}
class Rectangle implements DiagArea//矩形類,實現接口
{
int a,b;//表示長和寬
Rectangle()
{
a=0;b=0;
}
Rectangle(int a1,int b1)
{
a=a1;b=b1;
}
public double getDiagonal()//覆蓋,求對角線長
{
return Math.sqrt(a*a+b*b);
}
public double getArea()//覆蓋,求面積
{
return a*b;
}
}
class Square extends Rectangle//正方形繼承矩形類
{
int x;//正方形的邊
//構造函數,進行初始化
Square()
{
x=0;
}
Square(int aa)
{
a=aa;
}
public double getDiagonal()//求對角線
{
return Math.sqrt(a*a*2);
}
public double getArea()//求面積
{
return a*a;
}
void display()//顯示邊長、面積、對角線長
{
System.out.println("正方形的邊長、面積、對角線分別是:");
System.out.println(x+" "+getArea()+" "+getDiagonal());
}
}
//class Circle
(2)設計思想
定義一個接口,裏面有一些函數,實現接口,在裏面實現覆蓋,接著在主方法中定義變量,調用覆蓋後的結果。
(3)截圖
(4)編譯錯誤,註意事項
覆蓋方法的允許訪問範圍不能小於原方法。覆蓋方法所拋出的異常不能比原方法更多。聲明為final方法不允許覆蓋。例如,Object的getClass()方法不能覆蓋。不能覆蓋靜態方法,覆蓋時要用public。
構造函數(constructor)是一種特殊的方法 。主要用來在創建對象時初始化對象, 即為對象成員變量賦初始值,總與new運算符一起使用在創建對象的語句中 。特別的一個類可以有多個構造函數 ,可根據其參數個數的不同或參數類型的不同來區分它們 即構造函數的重載。構造函數的功能主要用於在類的對象創建時定義初始化的狀態。
構造一個對象,先調用其構造方法,來初始化其成員函數和成員變量。
子類擁有父的成員變量和成員方法,如果不調用,則從父類繼承而來的成員變量和成員方法得不到正確的初始化。
不能反過來調用也是這個原因,因為父類根本不知道子類有神魔變量而且這樣一來子類也得不到初始化的父類變量,導致程序運行出錯!
1.源代碼:
public class ExplorationJDKSource {
public static void main(String[] args) {
System.out.println(new A());
}
}
class A{}
2.結果截圖:
3.結果分析:
前面示例中,main方法實際上調用的是: public void println(Object x),這一方法內部調用了String類的valueOf方法。 valueOf方法內部又調用Object.toString方法: public String toString()
{ return getClass().getName() +"@" + Integer.toHexString(hashCode()); }
hashCode方法是本地方法,由JVM設計者實現: public native int hashCode();
3.下列語句哪一個將引起編譯錯誤?為什麽?哪一個會引起運行時錯誤?為什麽?
m=d;
d=m;
d=(Dog)m;
d=c;
c=(Cat)m;
先進行自我判斷,得出結論後,運行TestCast.java實例代碼,看看你的判斷是否正確
編譯錯誤
d=m;d=c;
不正確 子類對象可以直接賦給基類變量。
基類對象要賦給子類對象變量,必須執行類型轉換,
其語法是:子類對象變量=(子類名)基類對象名;
運行錯誤c=(Cat)m
不正確 轉換混亂。如果類型轉換失敗Java會拋出以下這種異常:ClassCastException
4.下邊的程序運行結果是什麽? 2. 你如何解釋會得到這樣的輸出? 3. 計算機是不會出錯的,之所以得 到這樣的運行結果也是有原因的, 那麽從這些運行結果中,你能總 結出Java的哪些語法特性?
public class ParentChildTest {
public static void main(String[] args) {
Parent parent=new Parent();
parent.printValue();
Child child=new Child();
child.printValue();
parent=child;
parent.printValue();
parent.myValue++;
parent.printValue();
((Child)parent).myValue++;
parent.printValue();
}
}
class Parent{
public int myValue=100;
public void printValue() {
System.out.println("Parent.printValue(),myValue="+myValue);
}
}
class Child extends Parent{
public int myValue=200;
public void printValue() {
System.out.println("Child.printValue(),myValue="+myValue);
}
}
1)
Parent.printValue(),myValue=100
Child.printValue(),myValue=200
Child.printValue(),myValue=200
Child.printValue(),myValue=200
3)
當子類與父類擁有一樣的方法,並且讓一個父類變量引用一個子類對象時,到底調用哪個方法,由對象自己的“真實”類型所決定,這就是說:對象是子類型的,它就調用子類型的方法,是父類型的,它就調用父類型的方法。如果子類與父類有相同的字段,則子類中的字段會代替或隱藏父類的字段,子類方法中訪問的是子類中的字段(而不是父類中的字段)。如果子類方法確實想訪問父類中被隱藏的同名字段,可以用super關鍵字來訪問它。
如果子類被當作父類使用,則通過子類訪問的字段是父類的!
5.為什麽子類的構造方法在運行之前,必須調用父類的構造方法?能不能反過來?為什麽不能反過來?
原因:構造函數用來在創建對象時初始化對象,與new運算符一起使用在創建對象的語句時。子類擁有父類的成員變量和成員方法,如果不調用,則從父類繼承而來的成員變量和成員方法得不到正確的初始化。不可以反過來調用,父類不知道子類有什麽變量,導致子類得不到正確的初始化,程序出錯。
6. 多態含義和用途
讓我們看一個開發場景:
某動物園有一飼養員小李,
每天需要給他所負責飼養的獅子、猴子和鴿子餵食。
請用一個程序來模擬他餵食的過程。
①三種動物對應三個類,每個類定義一個eat()方法,表示吃飼養員給它們的食物。
再設計一個Feeder類代表飼養員,其name字段保存飼養員名字,三個方法分別代表餵養三種不同的動物,其參數分別引用三種動物對象。
public class Zoo
{
public static void main(String args[])
{
Feeder f = new Feeder("小李");
// 飼養員小李餵養一只獅子
f.feedLion(new Lion());
// 飼養員小李餵養十只猴子
for (int i = 0; i < 10; i++)
{
f.feedMonkey(new Monkey());
}
// 飼養員小李餵養5只鴿子
for (int i = 0; i < 5; i++)
{
f.feedPigeon(new Pigeon());
}
}
}
class Feeder
{
public String name;
public Feeder(String name)
{
this.name = name;
}
public void feedLion(Lion l)
{
l.eat();
}
public void feedPigeon(Pigeon p)
{
p.eat();
}
public void feedMonkey(Monkey m)
{
m.eat();
}
}
class Lion
{
public void eat()
{
System.out.println("我不吃肉誰敢吃肉!");
}
}
class Monkey
{
public void eat()
{
System.out.println("我什麽都吃,尤其喜歡香蕉。");
}
}
class Pigeon
{
public void eat()
{
System.out.println("我要減肥,所以每天只吃一點大米。");
}
}
這種編程方式有什麽不合理的地方?
每次餵食都要創建一次類。重復步驟多。
①引入繼承
定義一個抽象基類Animal,其中定義一個抽象方法eat(),三個子類實現這個抽象方法。
Feeder類的三個餵養方法現在可以合並為一個feedAnimal()方法,註意它接收一個類型為Animal參數,而不是三個具體的動物類型。
依據多態特性,此方法將可以接收任何一個派生自Animal類的子類對象
56
public class Zoo
{
public static void main(String args[])
{
Feeder f = new Feeder("小李");
//飼養員小李餵養一只獅子
f.feedAnimal(new Lion());
//飼養員小李餵養十只猴子
for (int i = 0; i < 10; i++)
{
f.feedAnimal(new Monkey());
}
//飼養員小李餵養5只鴿子
for (int i = 0; i < 5; i++)
{
f.feedAnimal(new Pigeon());
}
}
}
class Feeder
{
public String name;
Feeder(String name)
{
this.name = name;
}
public void feedAnimal(Animal an)
{
an.eat();
}
}
abstract class Animal
{
public abstract void eat();
}
class Lion extends Animal
{
public void eat()
{
System.out.println("我不吃肉誰敢吃肉!");
}
}
class Monkey extends Animal
{
public void eat()
{
System.out.println("我什麽都吃,尤其喜歡香蕉。");
}
}
class Pigeon extends Animal
{
public void eat()
{
System.out.println("我要減肥,所以每天只吃一點大米。");
}
}
①進一步優化餵養一群動物
package zoo3;
public class Zoo
public static void main(String args[]) {
Feeder f = new Feeder("小李");
Animal[] ans = new Animal[16];
//飼養員小李餵養一只獅子
ans[0] = new Lion();
//飼養員小李餵養十只猴子
for (int i = 0; i < 10; i++) {
ans[1 + i] = new Monkey();
}
//飼養員小李餵養5只鴿子
for (int i = 0; i < 5; i++) {
ans[11 + i] = new Pigeon();
}
f.feedAnimals(ans);
}
}
class Feeder {
public String name;
Feeder(String name) {
this.name = name;
}
public void feedAnimals(Animal[] ans) {
for (Animal an : ans) {
an.eat();
}
}
}
abstract class Animal {
public abstract void eat();
}
class Lion extends Animal {
public void eat() {
System.out.println("我不吃肉誰敢吃肉!");
}
}
class Monkey extends Animal {
public void eat() {
System.out.println("我什麽都吃,尤其喜歡香蕉。");
}
}
class Pigeon extends Animal {
public void eat() {
System.out.println("我要減肥,所以每天只吃一點大米。");
}
}
④第二次重構之後,Feeder類的feedAnimals()方法接收的是一個Animal數組,這有一個限制,就是只能創建固定個數的數組,無法動態地增減動物個數。
想想以下場景:
(1)動物園新進了一些動物
(2)某動物生病不幸死亡
(3)……
我們的代碼能否應付以上的場景?
import java.util.Vector;
public class Zoo {
public static void main(String args[]) {
Feeder f = new Feeder("小李");
Vector<Animal> ans = new Vector<Animal>();
//飼養員小李餵養一只獅子
ans.add(new Lion());
//飼養員小李餵養十只猴子
for (int i = 0; i < 10; i++) {
ans.add(new Monkey());
}
//飼養員小李餵養5只鴿子
for (int i = 0; i < 5; i++) {
ans.add(new Pigeon());
}
f.feedAnimals(ans);
}
}
class Feeder {
public String name;
Feeder(String name) {
this.name = name;
}
//Vector<T>是JDK中提供的一個對象集合,可以隨時向其中加入或移除對象
public void feedAnimals(Vector<Animal> ans) {
for (Animal an : ans) {
an.eat();
}
}
}
abstract class Animal {
public abstract void eat();
}
class Lion extends Animal {
public void eat() {
System.out.println("我不吃肉誰敢吃肉!");
}
}
class Monkey extends Animal {
public void eat() {
System.out.println("我什麽都吃,尤其喜歡香蕉。");
}
}
class Pigeon extends Animal {
public void eat() {
System.out.println("我要減肥,所以每天只吃一點大米。");
}
}
總結:
多態編程有兩種主要形式:
(1)繼承多態:示例程序使用的方法
(2)接口多態:使用接口代替抽象基類。
使用多態最大的好處是:
當你要修改程序並擴充系統時,你需要修改的地方較少,對其它部分代碼的影響較小!千萬不要小看這兩個“較”字!程序規模越大,其優勢就越突出。
課堂作業06