1. 程式人生 > 程式設計 >淺談Java中向上造型向下造型和介面回撥中的問題

淺談Java中向上造型向下造型和介面回撥中的問題

最近回顧了一下java繼承中的問題,下面貼程式碼:

public class Base {

 protected String temp = "base";
 public void fun(){
 System.out.print("BASE fun()");
 }

 public static void main(String[] args) {
 Base b =new Base();//例項化Base物件
 b.fun();   //呼叫父類中fun()的方法
 System.out.println(b.temp);//訪問父類中的成員變數temp 
 
    /*****************************************************/ 
 System.out.println("/***************/");
 Son son = new Son();//例項化Son物件
 son.fun();  //呼叫son的fun方法
 System.out.println(son.temp);//訪問son的成員變數temp
 
 Son S_son=new Son();    //例項化Son物件
 Base B_s =(Base)S_son;   //向上造型-----------相當於Base s =new Son();
 B_s.fun();      //呼叫子類的fun()方法
 System.out.println(B_s.temp);//訪問父類的成員變數temp 
 
    /******************************************************/
 System.out.println("/***************/");
 Base s =new Son();//向上造型
 s.fun();     //呼叫子類的方法
 System.out.println(s.temp);//呼叫父類的成員變數temp
 
 Base ba =new Son();
 Son so = (Son)ba;      //向下造型
 so.fun();       //呼叫子類的fun()方法
 System.out.println(so.temp);//訪問子類的成員變數temp 
 }
}
class Son extends Base{
 protected String temp = "Son";
 public void fun(){
 System.out.print("SON fun()");
 }
}

執行結果:

BASE fun()base
/***************/
SON fun()Son
SON fun()base
/***************/
SON fun()base
SON fun()Son

結論總結:

1、父類的引用變數可以指向子類物件,子類的引用變數不能指向父類物件。

2、向上造型(upcasting):把子類物件直接賦給父類引用,向上轉型不用強制轉型。如:

Base base = new Son(); 向上造型–隱式

3、向下造型(downcasting):把指向子類物件的父類引用賦給子類引用,要強制轉型。

Base b= new Son();

Son s = (Son)base;----------向下造型–必須強制轉換,且必須有繼承關係

4、upcasting 會丟失子類特有的方法,但是子類overriding 父類的方法,子類物件去 呼叫方法有效

5、呼叫方法或成員變數的規律:

前提:子類存在重寫方法,父類和子類中同時有相同名稱的成員變數。

(1)當進行了向上造型的引用型變數—父類引用變數只能呼叫子類的重寫方法,但父類的引用變數只能訪問父類中的成員變數

(2)當進行了向下造型的引用型變數—子類引用變數只調用子類重寫方法,子類的引用變數只能訪問子類的成員變數。

(3)自己想了個通俗的說法,呼叫方法,得當前的引用變數指向的物件;呼叫變數,得看當前引用變數的型別。

即,調方法,看物件,調變數,看型別。(可以試驗一下)

介面回撥與向上造型

/**
 * 介面回撥,和向上造型的對比
 * @author Character_Painter
 *
 */
public class Inter extends Father implements Person,Teacher{
 public void study(){
 System.out.println("學習");
 }
 public void sleep(){
 System.out.println("子類sleep()方法");
 }
 
public static void main(String[] args) {
 Father f = new Inter();
 f.sleep();//向上轉型---呼叫
 
 Person p=new Inter();
 p.eat(); //介面回撥方法
 
 Teacher t = new Inter();
 t.teach(); //介面回撥方法

}

@Override
public void eat() {
 System.out.println("重寫eat()方法"); 
}

@Override
public void teach() {
 System.out.println("重寫teache()方法"); 
 
}

}
 
interface Person{
 public void eat();
}
interface Teacher{
 public void teach();
}
class Father{
 public void sleep(){
 System.out.println("父類sleep()方法");
 }
}

執行結果

子類sleep()方法

重寫eat()方法

重寫teache()方法

總結:

使用介面方式,其目的應該是實現多個父型別的方法實現,強調一個多種實現,而向上造型,針對的是一對一的繼承關係,向上造型後,可以呼叫子類的重寫的方法。這就是我認為他們的區別。

補充知識:Java向下轉型的意義與塑型的三個方面

一開始學習 Java 時不重視向下轉型。一直搞不清楚向下轉型的意義和用途,不清楚其實就是不會,那開發的過程肯定也想不到用向下轉型。

其實向上轉型和向下轉型都是很重要的,可能我們平時見向上轉型多一點,向上轉型也比較好理解。

但是向下轉型,會不會覺得很傻,我是要用子類例項物件,先是生成子類例項賦值給父類引用,在將父類引用向下強轉給子類引用,這不是多此一舉嗎?我不向上轉型也不向下轉型,直接用子類例項就行了。

我開始學習Java時也是這麼想的,這誤區導致我覺得向下轉型就是沒用的。

隨著技術的提升,我在看開源的專案學習,發現很多地方都用了向下轉型的技術,這就讓我重視了起來,想要重新來複習(學習)這個知識點。也是搜尋了許多部落格文章,但都沒具體說明向下轉型,只是給了例子演示怎麼使用,反而是向上轉型講了一堆(可能是我沒找到)。

這篇部落格就是講向下轉型的,那我們就來學習下向下轉型,瞭解下這種特性的意義和使用場景

新建一個電子產品介面,如下:

public interface Electronics{

}

很簡單,什麼方法都沒有。

新建一個Thinkpad筆記本類,並實現電子產品介面:

public class Thinkpad implements Electronics{

  //Thinkpad引導方法
  public void boot(){
    System.out.println("welcome,I am Thinkpad");    
  }

  //使用Thinkpad程式設計 
  public void program(){
    System.out.println("using Thinkpad program");
  }
}

新建一個Mouse滑鼠類,並實現電子產品介面:

public class Mouse implements Electronics{

  //滑鼠移動
  public void move(){
    System.out.println("move the mouse");    
  }

  //滑鼠點選 
  public void onClick(){
    System.out.println("a click of the mouse");
  }
}

新建一個Keyboard鍵盤類,並實現電子產品介面:

public class Keyboard implements Electronics{

  //使用鍵盤輸入  
  public void input(){
    System.out.println("using Keyboard input");
  }
}

這裡子類比較多,是為了更好的理解。每個類的方法的邏輯實現也很簡單。列印了一行資訊

接下來,我們想象一個情景:我們去商城買電子產品,電子產品很多吧,比如膝上型電腦,滑鼠,鍵盤,步步高點讀機哪裡不會點哪裡,我們用的手機,等等,這些都屬於電子產品。電子產品是抽象的。好,那麼我們決定買一臺Thinkpad,一個滑鼠和一個鍵盤。

這時,我們需要一個購物車來裝這些電子產品吧。我們可以新增進購物車,然後通過購物車還能知道存放的電子產品數量,能拿到對應的電子產品。

那麼,一個購物車類就出來了,如下:

import java.util.ArrayList;
import java.util.List;
public class ShopCar{

  private List<Electronics> mlist = new ArrayList<Electronics>();
  public void add(Electronics electronics){
    mlist.add(electronics);
  }

  public int getSize(){
    return mlist.size();
  }

  public Electronics getListItem(int position){
    return mlist.get(position);
  }
}

List 集合是用來存放電子產品的,add 方法用來新增電子產品到購物車,getSize 方法用來獲取存放的電子產品數量,getListItem 方法用來獲取相應的電子產品。

可以看到 List<Electronics> 用了泛型的知識,至於為什麼要用泛型?這個不做介紹了,泛型很重要的。

而我覺得比較疑惑的是為什麼是放 Electronics 的泛型,而不是放Thinkpad,Mouse,Keyboard,Phone等?

那麼如果是List<Thinkpad>,肯定是放不進滑鼠Mouse的吧,難道要生成3個集合?這裡是定義了3個電子產品類,但是我如果有100種電子產品呢,要定義100個集合?

這太可怕了。所以之前,我們寫了一個Electronics介面,提供了一個Electronics的標準,然後讓每一個Electronics子類都去實現這個介面。

實際上這裡又涉及到了向上轉型的知識點,我們雖然在add 方法將子類例項傳了進來存放,但子類例項在傳進去的過程中也進行了向上轉型

所以,此時購物車裡存放的子類例項物件,由於向上轉型成Electronics,已經丟失了子類獨有的方法,以上述例子來分析,Thinkpad例項就是丟失了boot() 和program() 這兩個方法,而Mouse例項就是丟失了move()和onClick()這兩個方法

但是實際使用Thinkpad或Mouse或Keyboard時,這種情況肯定不是我們想要的

接著我們寫一個測試類 Test 去測試購物車裡的電子產品。

測試類 Test 如下:

public class Test{

  public static final int THINKPAD = 0;
  public static final int MOUSE = 1;
  public static final int KEYBOARD = 2;

  public static void main(String[] args){

    //新增進購物車
    ShopCar shopcar = new ShopCar();
    shopcar.add(new Thinkpad());
    shopcar.add(new Mouse());
    shopcar.add(new Keyboard());

    //獲取大小
    System.out.println("購物車存放的電子產品數量為 ——> "+shopcar.getSize());


    //開始測試thinkpad電腦
    Thinkpad thinkpad = (Thinkpad)shopcar.getListItem(THINKPAD);
    thinkpad.boot();
    thinkpad.program();

    System.out.println("-------------------");

    //開始測試Mouse滑鼠
    Mouse mouse = (Mouse)shopcar.getListItem(MOUSE);
    mouse.move();
    mouse.onClick();

    System.out.println("-------------------");

    //開始測試Keyboard鍵盤
    Keyboard keyboard = (Keyboard)shopcar.getListItem(KEYBOARD);
    keyboard.input();
  }

}

執行截圖:

淺談Java中向上造型向下造型和介面回撥中的問題

舉個例子分析就好

//開始測試thinkpad電腦
Thinkpad thinkpad = (Thinkpad)shopcar.getListItem(THINKPAD);
thinkpad.boot();
thinkpad.program();

shopcar.getListItem(THINKPAD)

這句程式碼是獲取到Electronics型別的例項。不是Thinkpad的例項

通過向下轉型,賦值給子類引用

Thinkpad thinkpad = (Thinkpad)shopcar.getListItem(THINKPAD);

這樣子類例項又重新獲得了因為向上轉型而丟失的方法(boot 和program)

總結一下吧,很多時候,我們需要把很多種類的例項物件,全部扔到一個集合。(這句話很重要)

在這個例子裡就是把Thinkpad筆記本,Mouse滑鼠,KeyBoard鍵盤等例項物件,全部扔到一個Shopcar購物車集合。

但是肯定不可能給他們每個種類都用一個獨立的集合去存放吧,這個時候我們應該尋找到一個標準,介面就是一個標準。這些都是各種電子產品,抽象成電子產品。然後一個Electronics介面就出來了。

在回到剛才,我們把很多種類的例項物件全部扔到一個集合。或許這樣比較好理解:把很多種類的子類例項物件全部扔到存放父類例項的集合。

經過了這個過程,子類例項已經賦值給了父類引用(即完成了向上轉型),但很遺憾的丟失了子類擴充套件的方法。

很好的是Java語言有個向下轉型的特性,讓我們可以重新獲得丟失的方法,即強轉回子類

所以我們需要用到子類例項的時候,就從那個父類集合裡拿出來向下轉型就可以了,一樣可以使用子類例項物件

……

我在搜尋java向下轉型的意義時,得到一個比較好的答案是這樣的:

最大的用處是java的泛型程式設計,用處很大,Java的集合類都是這樣的。

而在Android開發中,我們在Layout資料夾,用xml寫的控制元件。為什麼能在Activity等元件中通過 findViewById() 方法找到呢?為什麼 findViewById(R.id.textview) 方法傳入TextView的id後,還要轉型為TextView呢?這就是 Java 向下轉型的一個應用

以上這篇淺談Java中向上造型向下造型和介面回撥中的問題就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。