黑馬程序員_面向對象(三)

------- android培訓java培訓、期待與您交流! ----------


一、單例設計模式


1、什麼是單例設計模式?
    對於單例模式(Singleton Pattern)是一個比較簡單的模式,他的定義如下:
        Ensure a class has only one instance,and provide a global point of access to it.
        意思是確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。
        好處:保證對象的唯一性
        使用場景:比如多個程序都要使用一個配置文件中的數據,而且要實現數據共享和交換。必須要將多個數據封裝到一個對象中。而且多個程序操作的是同一個對象。那也就是說必須保證這個配置文件對象的唯一性。

        如何能保證對象的唯一性?
            一個類只要提供了構造方法,就可以產生多個對象。完全無法保證唯一。既然數量不可    控,乾脆不讓其他程序建立對象。

        不讓其他程序創建,對象何在?
            自己在本類中創建一個對象,好處,對象可控。

        創建完成後,是不是要給其他程序提供訪問的方式?
            怎麼實現這個步驟?
                    怎麼就能不讓其他程序創建對象呢?
                        直接私有化構造方法,不讓其他程序創建的對象存在。
                    直接在本類中new一個本類對象
                    定義一個功能,其他程序可以通過這個功能獲取到本類對象。
        

2、單例模式分成兩種:懶漢式、餓漢式

3、下面就通過代碼來看看這三種單例模式的區別
餓漢式:
//餓漢式
class Singleton_1{
    private static Singleton_1 s = new Singleton_1();
    private Singleton_1(){}
    public static Singleton_1 getInstance(){
        return s;
    }
    
}


懶漢式:(容易引發線程安全問題)
//懶漢式(單例模式的延遲加載方式),面試最多的是懶漢式
class Singleton_2{
    private static Singleton_2 s = null;
    private Singleton_2(){}
    public static Singleton_2 getInstance(){
        if(s == null){
            s = new Singleton_2();
        }
        return s;
    }
}


二、繼承

1、假設有兩個類,一個類是Student類,另外一類是Worker類,兩個類中都有屬性name和age,那麼既然有共同的屬性,我們能不能對他們進行抽取呢?
          答案:可以,可以將兩個類中的name屬性和age屬性都抽取取來,放到另外一個類,這個類就是Person,因爲學生是人,工人也是人,所以就抽取出來再建立Person類。
繼承的關鍵字:extends

代碼體現:
package day08.itcast01;
public class ExtendsDemo {
    public static void main(String[] args) {
        Student s = new Student();
        s.name = "林青霞";
        s.age = 16;
        s.show();
        
        Worker w = new Worker();
        w.name = "小二";
        w.age = 28;
        w.show();
    }
}
class Student extends Person{//定義一個Student類,並提供一個成員方法
    public void study(){
        System.out.println("我在學習");
    }
}
class Worker extends Person{  //定義一個Worker類,並提供一個成員方法
    public void work(){
        System.out.println("我在工作");
    }
}
class Person{   //定義一個Person類,由Student類和Worker類抽取而來,是Worker類和Student類的父類(基類、超類)
    String name;
    int age;
    public void show(){
        System.out.println(name+"---"+age);
    }
}

繼承的好處:提高了代碼的複用性,爲面向對象另一個特徵多態提供了前提條件

什麼時候定義繼承?
必須保證類與類之間的所屬(is a)關係,XX是XXX的一種
比如:狗是動物的一種,學生是人的一種

Java當中允許單繼承,不允許多繼承
單繼承:一個子類只會有一個父類
多繼承:一個子類會有多個父類

2、繼承中子父類成員的特點:
    1、成員變量
            特殊情況:當子類和父類定義了同名的成員變量的時候,如何在子類中訪問父類中的變量?
            通過關鍵字super來完成
            super的用法與this的用法類似,
                this代表的是本類對象的引用。
                super代表的是父類的內存空間
    2、成員方法
            當子類和父類定義了一模一樣的的方法時,當子類調用該方法時,運行的是子類中的方法
            這種情況在子父類中被稱之爲方法的重寫。
            何時需要重寫方法?
                當子類的方法有自己的特有的功能的時候就需要重寫。
        子類重寫父類的方法必須保證權限要大於或者等於父類的權限
        靜態只能覆蓋靜態的
        寫法上需要注意的:必須一模一樣,方法的返回值類型 方法名 參數列表都要一樣。
代碼體現:
package day09.itcast01;
public class ExtendsDemo1 {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.age = 15;
        zi.des();
    }
}
class Fu{
    int age;
    public void des(){
        System.out.println("父類的des方法"+"---父類的成員變量---"+age);
    }
}
class Zi extends Fu{
    int age;
    public void des(){
        super.age = 10;
        super.des();
        System.out.println("子類的des方法"+"---子類的成員變量---"+age);
    }
    
}


    3、子父類中構造方法的的特點
        
package day09.itcast02;
public class ExtendsDemo2 {
    public static void main(String[] args) {
        Son son = new Son();
    }
}
class Father{
    Father(){
        System.out.println("Father is running");
    }
}
class Son extends Father{
    Son(){
        System.out.println("Son is running");
    }
}

分析運行結果:
因爲在子類的所有構造方法中的第一行都默認有一個super();它會調用父類的構造方法
 爲什麼會有super();呢?
因爲在子類進行初始化的時候,先要對父類進行初始化,只有對父類進行初始化完成後,才能使用父類的一些方法

當父類沒有空參的構造方法時,需要使用super關鍵字去調用相應的構造方法

如果在子類的第一行使用了this調用本類的其他構造方法,還會有super();嗎?
沒有,因爲this()或者super()只能定義在構造方法的第一行

父類的構造方法中是否有super();
有,因爲所有類的構造方法的第一行都有一個super();此時父類調用的是所有類的父類Object類。

如果默認的隱式super語句沒有對應的構造函數,必須在構造函數中通過this或者super的形式明確調用的構造函數。

三、final關鍵字

繼承的缺點:打破封裝性,如何能保證繼有繼承又不會打破封裝性呢?
就不讓其他類繼承該類,就不會重寫方法。這時就需要用到final關鍵字
final的意思是最終,它用於修飾類,方法或者變量(成員變量、局部變量、靜態變量)

final的特點:
1、final修飾的類是一個最終類,不能再派生子類
    如果一個類中的方法部分需要重寫,部分不需要,就對不需要被重寫的方法使用final修飾
2、final修飾的方法是最終方法,該方法不能被重寫
3、final修飾的變量是一個常量,只能被賦值一次

什麼時候需要在程序中定義final常量呢?
當程序中一個數據使用時是固定不變的,這時爲了增加閱讀性,可以給該數據起個名字。
這就是變量,爲了保證這個變量的值不被修改,加上final修飾,這就是一個閱讀性很強的常量。
書寫規範:被final修飾的常量名所有的字母都是大寫,如果該變量名是由多個單詞組成的,每個字母都大寫,並且單詞之間使用"_"連接。



四、抽象類

對與狗和狼這兩種動物他們都有一個吼叫的行爲,而且他們還屬於動物,對他們的共性進行向上抽取,可以使用繼承,但是狗和狼吼叫的行爲又不同。這時使用繼承就顯得不合適了,這時就需要使用另外一個關鍵字abstract(抽象的)對父類進行修飾。

抽象類的特點:抽象類和抽象方法都需要使用abstract修飾,抽象方法一定要定義抽象類中,抽象類中的方法不一定都是抽象方法。

只有覆蓋了抽象類中的所有抽象方法後,其子類纔可以實例化。否則該子類還是一個抽象類。

抽象類要實例化的話需要通過子類對父類進行實例化。(多態)

細節:
抽象類一定是一個父類
是的,因爲抽象類就是子類的功能不斷抽取出來的

抽象類中是否有構造方法?
有,不能給自己的對象實例化,可以給子類的對象進行初始化。

抽象類和普通類的異同點?
相同:它們都是用來描述事物的,它們之間都可以定義屬性和行爲

不同:一般類可以具體的描述事物,抽象類描述的事物信息不具體
抽象類可以多定義一個成員:抽象方法。
一般類可以創建對象,而抽象類不能創建對象。

抽象類中是否可以定義普通方法?
可以,如果抽象類中定義了普通方法,那麼其抽象類的作用就是不能對該類進行實例化。

抽象關鍵字abstract不能與哪些關鍵字共存?
final
private
static

代碼體現:
package day09;
public class AbstractDemo {
    public static void main(String[] args) {
        Dog d = new Dog();
        d.show();
        d.speak();
        
    }
}
abstract class Animal{
    public void show(){
        System.out.println("Animal");
    }
    public abstract void speak();
}
class Dog extends Animal{
    public void speak(){
        System.out.println("小狗叫");
    }
}


案例:
需求:公司中程序員有姓名,工號,薪水,工作內容。
項目經理除了有姓名,工號,薪水,還有獎金,工作內容。
分析:程序員和項目經歷都屬於公司裏的員工,而且他們都有共性:姓名、工號、薪水以及工作內容
package day09.itcast02;
public class AbstractDemo {
    public static void main(String[] args) {
        Programer p = new Programer("張三","448",5000);
        Manager m = new Manager("李四","500",5000,2500);
        p.content();
        p.show();
        m.content();
        m.show();
        
    }
}
abstract class Employee{
    String name;
    String number;
    int salary;
    public Employee(String name,String number,int salary){
        this.name = name;
        this.number = number;
        this.salary = salary;
    }
    public abstract void content();
    public abstract void show();
    
}
class Programer extends Employee{
    public Programer(String name,String number,int salary){
        super(name,number,salary);
    }
    public void content(){
        System.out.println("敲代碼");
    }
    public void show(){
        System.out.println(name+"---"+number+"---"+salary);
    }
}
class Manager extends Employee{
    int pay;//獎金
    public Manager(String name,String number,int salary,int pay){
        super(name,number,salary);
        this.pay = pay;
    }
    public void content(){
        System.out.println("寫規劃");
    }
    public void show(){
        System.out.println(name+"---"+number+"---"+salary+"---"+pay);
    }
    
}


五、接口

當一個抽象類中的方法都是抽象方法的時候,這個抽象類就有另外一個表現方式,叫做接口(interface)
定義接口使用關鍵字interface,格式:interface 接口名{}

接口中的成員已經被限定爲固定的幾種。
接口中的定義格式(兩種)
    1、定義變量,但是變量必須有固定的修飾,public static final,所以接口中的變量也被稱之爲常量。
    2、定義方法,方法也有固定的修飾符,public abstract
        接口中的成員都是公共的。

特點:
接口不可以創建對象
子類必須覆蓋掉接口中的所有抽象方法後,子類纔可以被實例化。否則子類是一個抽象類。

定義接口的子類,類與類之間的關係是繼承,而子類與接口之間的關係是實現(implements)。
格式:
interface Animal{  // 定義一個動物接口
}
class Dog implements Animal{ //定義犬類實現了動物的接口
}

接口解決了多繼承中調用不明確的弊端,講多繼承機制在java中通過多實現完成了
    
接口的出現避免了單繼承的侷限性
父類中定義事物的基本功能。
接口中定義事物的擴張功能。

類與類之間是繼承(is a)關係,類與接口之間是實現(like a)關係.

接口與接口之間是繼承關係,而且可以多繼承。

抽象類和接口的區別:
接口中定義的方法都是抽象方法,不允許定義普通方法。
抽象類中可以允許有普通方法,但是抽象方法一定定義在抽象類中。

接口的思想:
1、對功能實現了擴展。
2、定義了規則。
3、降低了耦合性。

三、多態

什麼是多態?
舉例子:學生和工人,但是他們都是人中的某一類,這就是多態。多態就是一種事物的不同體現。
多態的體現:
父類的引用或者接口的引用指向了自己的子類對象。
好處:提高了程序的擴展性。
弊端:通過父類的引用操作子類對象時,只能使用父類中已有的方法,不能操作子類特有的方法
多態的前提:繼承或者實現。
多態通常都有重寫操作。
代碼體現:
package day10.itcast01;
public class DuotaiDemo1 {
    public static void main(String[] args) {
        Person p = new Student();
        p.eat(p);
        Person p1 = new Worker();
        p1.eat(p1);
        
    }
}
abstract class Person{
    public void eat(Person p){
        if(p instanceof Student ){
            System.out.println("喫飯");
            p.method();
        }else if( p instanceof Worker){
            System.out.println("喫飯");
            p.method();
        }
    }
    public abstract void method();
}
class Student extends Person{
    public void method(){
        System.out.println("學習");
    }
}
class Worker extends Person{
    public void method(){
        System.out.println("工作");
    }
}



2、多態調用子類的特有方法

Person p = new Studnet();
或者Person p = new Worker();
父類引用指向了子類的對象,這是讓子類對象進行類型的提升(向上轉型)
好處:提高了擴展性,隱藏了子類,弊端:不能使用子類的特有方法。

如果想要使用子類的特有方法,只有子類的對象才能使用,這時就需要使用向下轉型。
向下轉型屬於強制類型轉換,向上轉型是自動類型轉換。
package day10.itcast01;
public class DuotaiDemo1 {
    public static void main(String[] args) {
        Person p = new Student();
        p.eat(p);
//        p.write();  報錯
        ((Student)p).writer();
        Person p1 = new Worker();
        p1.eat(p1);
        ((Worker)p1).work();
        
    }
}
abstract class Person{
    public void eat(Person p){
        if(p instanceof Student ){
            System.out.println("喫飯");
            p.method();
        }else if( p instanceof Worker){
            System.out.println("喫飯");
            p.method();
        }
    }
    public abstract void method();
}
class Student extends Person{
    public void method(){
        System.out.println("學習");
    }
    public void writer(){
        System.out.println("學生的特有方法:寫作業");
    }
}
class Worker extends Person{
    public void method(){
        System.out.println("工作");
    }
    public void work(){
        System.out.println("工人的特有方法:幹活");
    }
}

注意:無論是向上轉型還是向下轉型,最終都是子類對象做着類型的變化。
【向下轉型的注意事項】:
Person p = new Studnet();
Worker w = (Worker)p;
以上代碼是不允許出現的,會導致ClassCastException異常
爲了避免出現類轉換異常,在進行類轉換時常使用instanceof關鍵字對對象進行類型判斷
格式:對象名 instanceof 類名

3、子父類成員的調用問題
        1、成員變量:當子父類中出現了同名的成員變量時
                編譯時期:參考的是引用型變量所屬的類是否有被調用的成員變量,沒有,編譯失敗。   
                運行時期:參考的是引用型變量所屬類中的成員變量。
                口訣:編譯運行看左邊。           
        2、成員方法:當子父類中出現了同名的成員方法時
                編譯時期:參考左邊,如果沒有,編譯失敗
                運行時期:參考右邊
                口訣:編譯看左邊,運行看右邊。
                對於成員方法是動態綁定到對象上。
        3、靜態方法
                編譯和運行都參考左邊
                靜態方法是靜態綁定到類上。

4、題目
看一下代碼分析打印結果(通過畫圖分析)
package day10.itcast02;
public class DuoTaiTest3
{
    public static void main(String[] args)
    {
        Fu f = new Zi();
        System.out.println("main :" +f.getNum());
    }
}
class Fu
{
    int x = 4;
    Fu()
    {
        System.out.println("A fu() : "+getNum());
    }
    int getNum()
    {
        System.out.println("B fu getnum run...."+x);
        return 100;
    }
}
class Zi extends Fu
{
    int x = 5;
    Zi()
    {    //super();默認存在並調用父類的無參構造
        System.out.println("C zi() : "+getNum());
    }
    int getNum()
    {
        System.out.println("D zi getnum run...."+x);
        return 200;
    }
}

結果分析:
分析這道題首先從main方法開始,Fu f = new Zi(); 程序首先會調用子類的構造方法,進入到子類的構造方法時,因爲在子類的構造方法中第一行都會有默認的super();調用父類的構造方法,所以程序進入到 父類的構造方法之中,在父類的構造方法中,有一條輸出語句,輸出語句中調用了getNum()方法,那麼這時候調用的是哪個呢?因爲在main方法中,使 用了多態,使父類的對象引用指向了子類,所以調用的是子類的getNum方法,在getNum方法中輸出了一個x,因爲從程序開始一直到現在都還未對x進 行顯示的初始化,所以x的值爲0,所以最想打印的是D zi getnum run....0,接着getNum方法返回了200到父類的構造方法中,所以接着打印了A fu() : 200,當打印了兩條語句之後,又回到了子類的構造方法中,這時子類的構造方法中同樣有一條輸出語句,在語句中調用了getNum()方法,這個 getNum方法同樣是子類中的方法,因爲此時對子類中的x已經完成了顯式初始化,所以打印了D zi getnum run....5,打印完成後又回到了子類的構造方法中執行了輸出語句,此時打印了C zi() : 200,至此Fu f = new Zi()完成了所有的操作,最後mian方法中的輸出語句中同樣調用的是子類中的getNum()方法,所以打印的是main :200
綜合以上的分析,可以得出最後的結果爲:
D zi getnum run....0
A fu() : 200
D zi getnum run....5
C zi() : 200
main :200

 六、object類

object是所有的類的父類或者間接父類
在object類中有兩個常用的方法
equlas和toString方法
equals:通常用來比較兩象是個對否相等。
toString:返回對象的字符串表現形式,Object 類的 toString 方法返回一個字符串,該字符串由類名(對象是該類的一個實例)、at 標記符“@”和此對象哈希碼的無符號十六進制表示組成。換句話說,該方法返回一個字符串,它的值等於:
getClass().getName() + '@' + Integer.toHexString(hashCode())

一般來說equals和toString方法都需要被重寫
案例:
package cn.test;
public class EqualsDemo {
    public static void main(String[] args) {
        Student s1 = new Student("謝羣",18);
        Student s2 = new Student("謝羣",22);
        boolean  result = s1.equals(s2);
        System.out.println("result:"+result);
        System.out.println(s2.toString());
        s2.setAge(18);
        result = s1.equals(s2);
        System.out.println("result:"+result);
        System.out.println(s2.toString());
        
    }
}
class Student{
    private String name;
    private int age;
    public Student(String name, int age){
        this.name = name;
        this.age = age;
    }
    public Student(){}
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    public void setAge(int age){
        this.age = age;
    }
    public int getAge(){
        return age;
    }
    public boolean equals(Object s){
        if(s == null){
            return false;
        }
        if(!(s instanceof Student)){
            return false;
        }
        if(s == this){
            return true;
        }
        Student s1 = (Student)s;
        return this.name.equals(s1.name) && this.age == s1.age;
    }
    public String toString(){
        return this.name +"----"+this.age;
    }
    
    
}

















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章