2020-09-04--Java--day02【泛型,斗地主案例】

主要內(nèi)容

  • 3.泛型
  • 4.斗地主案例

3.泛型

3.1 泛型概述

在前面學習集合時,我們都知道集合中是可以存放任意對象的,只要把對象存儲集合后,那么這時他們都會被提升成Object類型。當我們在取出每一個對象,并且進行相應的操作,這時必須采用類型轉(zhuǎn)換。

大家觀察下面代碼:

public class Person {
    public static void main(String[] args) {
        // 1.創(chuàng)建集合對象
        Collection coll = new ArrayList<>();
        coll.add("zhangsan");
        coll.add("lisi");
        coll.add(1);
        //可以添加,因為集合沒有聲明泛型,但是在之后向下轉(zhuǎn)型時,回拋出ClassCastException,不能把Integer類型轉(zhuǎn)換為String類型

        for (Object o : coll) {
            System.out.println(o);   //zhangsan  lisi
            //使用Object類型接收
            Object st = o;
            // 向下轉(zhuǎn)型為String對象
            String str = (String) st;
            //調(diào)用String類的方法
            System.out.println(str.length());   //8  4
        }
    }
}

程序在運行時發(fā)生了問題java.lang.ClassCastException。

  1. 為什么會發(fā)生類型轉(zhuǎn)換異常呢?
    我們來分析下:由于集合中什么類型的對象都可以存儲。導致取出時強轉(zhuǎn)引發(fā)運行時 `ClassCastException。

2.怎么來解決這個問題呢?
Collection雖然可以存儲各種對象,但實際上通常Collection只存儲同一類型對象。例如都是存儲字符串對象。因此在JDK5之后,新增了泛型(Generic)語法,讓你在設(shè)計API時可以指定類或方法支持泛型,這樣我們使用API的時候也變得更為簡潔,并得到了編譯時期的語法檢查。

  • 泛型:可以在類或方法中預支地使用未知的類型。

tips:一般在創(chuàng)建對象時,將未知的類型確定具體的類型。當沒有指定泛型時,默認類型為Object類型。

圖例:

3.2 使用泛型的好處

上一節(jié)只是講解了泛型的引入,那么泛型帶來了哪些好處呢?

利端:

  • 將運行時期的ClassCastException,轉(zhuǎn)移到了編譯時期變成了編譯失敗。
  • 避免了類型強轉(zhuǎn)的麻煩。

弊端:

  • 泛型是什么類型,只能存儲什么類型的數(shù)據(jù)。

實例:

public class Person {
    public static void main(String[] args) {
        // 1.創(chuàng)建集合對象,使用泛型-->String
        Collection<String> coll = new ArrayList<>();
        coll.add("zhangsan");
        coll.add("lisi");
//        coll.add(1);     直接編譯報錯

        for (Object o : coll) {
            // o為Object類型,需要向下轉(zhuǎn)型為String對象
            String str = (String) o;
            //調(diào)用String類的方法
            System.out.println(str+":"+str.length());   //8  4
        }

        Iterator<String> it = coll.iterator();
        while (it.hasNext()) {
            // 直接使用String類型變量接收
            String str = it.next();
            System.out.println(str+":"+str.length());
        }
    }
}

coll.add(1)在編寫帶碼時出現(xiàn)錯誤,因為Collection集設(shè)置了泛型為String類型,只能存儲String類型的數(shù)據(jù)。

  • 在使用增強for進行遍歷時,需要向下轉(zhuǎn)型為String,遍歷變量oObject類型。
  • 使用迭代器對象的hasNext()next()遍歷元素時,next()的返回值就是該集合對象的所設(shè)置的泛型,直接String接收即可,無需類型轉(zhuǎn)換。

3.3 含有泛型的類

泛型 :用來靈活地將數(shù)據(jù)類型應用到不同的類、方法、接口當中。將數(shù)據(jù)類型作為參數(shù)進行傳遞。

意思就是在在創(chuàng)建類對象時,可以設(shè)置其創(chuàng)建對象的屬性類型。

  • 泛型是一個未知的數(shù)據(jù)類型,當我們不確定什么什么數(shù)據(jù)類型的時候,可以使用泛型。
  • 泛型可以接收任意的數(shù)據(jù)類型,可以使用Integer,String,Student...
  • 創(chuàng)建類的對象的時候確定泛型的數(shù)據(jù)類型。
定義格式
修飾符 class 類名<泛型類型>{
        // ...
}
實例
public class Test<MVP> {
    private MVP name;

    public MVP getName() {
        return name;
    }

    public void setName(MVP name) {
        this.name = name;
    }

main:

    public static void main(String[] args) {
        // 不適用泛型,那么該類的對象的泛默認為Object,但JDK會根據(jù)參數(shù)類型自動識別
        Test test1 = new Test();
        test1.setName("lisi");
        System.out.println(test1.getName());    //lisi
        System.out.println(test1.getName().getClass().getName());  //java.lang.String
        Object name = test1.getName();


        // 使用Integer作為泛型,該對象參數(shù)只能傳遞Integer類型的數(shù)據(jù),其他數(shù)據(jù)編譯錯誤
        Test<Integer> test2 = new Test<>();
        test2.setName(123);
        System.out.println(test2.getName());     //123
        System.out.println(test2.getName().getClass().getName());  //java.lang.Integer
        Integer name1 = test2.getName();

        // 使用String作為該對象的泛型,只能傳遞并返回String類型的數(shù)據(jù),其他數(shù)據(jù)編譯錯誤
        Test<String> test3 = new Test<>();
        test3.setName("234");
        System.out.println(test3.getName());  //234
        System.out.println(test3.getName().getClass().getName());  //java.lang.String
        String name2 = test3.getName();
    }
}

分析:

  1. 定義一個泛型為MVP的類,因為我們不確定該類在創(chuàng)建對象時的數(shù)據(jù)類型是什么?
  2. 創(chuàng)建類對象時,可以不使用泛型,默認為<Object>,最好寫上。其在創(chuàng)建對象時將泛型傳遞到類中,相當于將該類改造為:
public class Test<Object> {
    private Object name;

    public Object getName() {
        return name;
    }
    public void setName(Object name) {
        this.name = name;
    }

該對象在在可以傳遞任何數(shù)據(jù)類型(Object類為所有類的父類),并且數(shù)據(jù)類型都為Object類型的數(shù)據(jù),但是JDK會自動根據(jù)傳入?yún)?shù)類型進行判斷。

  1. 泛型設(shè)置為Integer/String,將該類改造后的結(jié)果不在贅述,替換MVP的位置即可。該類對象只能傳遞Integer/String類型的參數(shù),并且返回同樣類型的參數(shù),其他類型傳遞時會編譯錯誤。

3.4 含有泛型的方法

定義格式
修飾符 static/() <表示泛型的變量> 返回值類型 方法名(參數(shù)類型 參數(shù)){ 
        // ...
 }
  • 表示泛型的變量可以為任意的標識符(只要符合Java標識符規(guī)則),作用就是:當我們不確定該方法傳進來的參數(shù)類型時,可以將該方法變?yōu)榉盒头椒ǎ趨?shù)列表中的參數(shù)類型設(shè)置為標識泛型的變量,這樣在調(diào)用時數(shù)據(jù)類型不在固定。

當然參數(shù)類型可以設(shè)置為已有的數(shù)據(jù)類型,例如:int/String,這樣的在調(diào)用方法時只能傳遞相應的數(shù)據(jù)類型,那么該含有泛型的方法與普通的方法一樣了,沒有必要定義泛型方法。

實例
public class Test2 {

    // 定義一個泛型的靜態(tài)方法
    public <S>void Method(S m) {
        System.out.println(m);
        System.out.println(m.getClass().getName());
    }

    public static void main(String[] args) {

        new Test2().Method(13);
        //13
        //java.lang.Integer
    }
}

可以看到輸出結(jié)果為13,以及對應被包含的包裝類。

3.5 含有泛型的接口

當我們不確定接口的實現(xiàn)類在重寫接口中的方法時,可以設(shè)置接口的泛型

定義格式:

修飾符 interface接口名<代表泛型的變量> { 
      // ...
 }
定義和使用

例如定義MyInterface接口,泛型為I:

public interface MyInterface<I> {
    void Method(I i);
}

其有兩種方式可以指定其泛型:

1. 定義實現(xiàn)類時,確定泛型類型

public class GenericInterfaceImpl1 implements MyInterface<String>{
    @Override
    public void method(String s)   {
        System.out.println(s);
    }
    public static void main(String[] args) {

        GenericInterfaceImpl1 gi = new GenericInterfaceImpl1();
        gi.method("123");
    }
}

GenericInterfaceImpl1類指定了接口的泛型IString。

2.創(chuàng)建實現(xiàn)類對象時,指定接口泛型(常用)

public class Person<I> implements MyInterface<I> {
    @Override
    public void Method(I s) {
        System.out.println(s);
    }

    public static void main(String[] args) {
        Person<Integer> mi = new Person<>();
        mi.Method(123);
    }
}

這樣的方式相當于把接口的泛型繼承下來,實現(xiàn)類自己也會不知道自己的泛型具體是什么。
最后在Main中創(chuàng)建實現(xiàn)類對象時,指定泛型為Integer,那么這個類中的Method(Integer s)方法的參數(shù)類型也會變?yōu)?code>Integer。

注意上述兩種寫法的不同:

  • 實現(xiàn)類確定泛型,在接口中直接傳遞泛型的"實參",在創(chuàng)建對象時,與普通類創(chuàng)建對象一樣的。
    類似于Scanner類:


    創(chuàng)建方式:
    Scanner sc = new Scanner();

  • 實現(xiàn)類對象確定泛型(常用),類中泛型寫法與接口泛型一致,在創(chuàng)建對象時可以使用普通類的創(chuàng)建對象的方式(不推薦),盡量指定泛型類的泛型。
    例如ArrayList類:


    創(chuàng)建對象的方式:
    ArrayList<String> arr = new ArrayList<>();

  • 實現(xiàn)類中實現(xiàn)接口時如果只是普通的實現(xiàn),實現(xiàn)類接口都沒有泛型的聲明,那么接口中定義的泛型默認為Object類型。

3.6 泛型通配符

當使用泛型類或者接口時,傳遞的數(shù)據(jù)中,泛型類型不確定,可以通過通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object類中的共性方法(因為類型不確定,所以在使用后得到的都是Object類型,如果相導轉(zhuǎn)會原來的類型,只能向下轉(zhuǎn)型),集合中元素自身方法無法使用。

1. 通配符基本使用

泛型的通配符:不知道使用什么類型來接收的時候,此時可以使用?。
?表示未知通配符。

public static void main(String[] args) {
    Collection<Intger> list1 = new ArrayList<Integer>();
    getElement(list1);
    Collection<String> list2 = new ArrayList<String>();
    getElement(list2);
//  ArrayList<?> list03 = new ArrayList<?>();
}
public static void getElement(Collection<?> coll){
  }
//?代表可以接收任意類型

<?>只能作為方法的參數(shù)使用,表示該方法接收的參數(shù)為不確定的類型,不能作為創(chuàng)建對象使用,直接編譯報錯。

tips:泛型不存在繼承關(guān)系 Collection<Object> list = new ArrayList<String>();這種是錯誤的。

2. 通配符高級使用----受限泛型

之前設(shè)置泛型的時候,實際上是可以任意設(shè)置的,只要是類就可以設(shè)置。但是在JAVA的泛型中可以指定一個泛型的上限下限。

泛型的上限

  • 格式類型名稱 <? extends 類 > 對象名稱
  • 意義只能接收該類型及其子類

泛型的下限

  • 格式類型名稱 <? super 類 > 對象名稱
  • 意義只能接收該類型及其父類型

也就是設(shè)置了泛型通配符<?>的范圍,使之在某一個范圍內(nèi)有效。

比如:現(xiàn)已知Object類,String 類,Number類,Integer類,其中NumberInteger的父類,Object類是所有類的父類。

示例:

public class Demo06Generic {
    public static void main(String[] args) {
        Collection<Integer> list1 = new ArrayList<Integer>();
        Collection<String> list2 = new ArrayList<String>();
        Collection<Number> list3 = new ArrayList<Number>();
        Collection<Object> list4 = new ArrayList<Object>();

        getElement1(list1);
        //getElement1(list2);//報錯
        getElement1(list3);
        //getElement1(list4);//報錯

        //getElement2(list1);//報錯
        //getElement2(list2);//報錯
        getElement2(list3);
        getElement2(list4);

        /*
            類與類之間的繼承關(guān)系
            Integer extends Number extends Object
            String extends Object
         */

    }
    // 泛型的上限:此時的泛型?,必須是Number類型或者Number類型的子類
    public static void getElement1(Collection<? extends Number> coll){}
    // 泛型的下限:此時的泛型?,必須是Number類型或者Number類型的父類
    public static void getElement2(Collection<? super Number> coll){}
}

4.斗地主案例

4.1 案例介紹

按照斗地主的規(guī)則,完成洗牌發(fā)牌的動作。

具體規(guī)則:

使用54張牌打亂順序,三個玩家參與游戲,三人交替摸牌,每人17張牌,最后三張留作底牌。

4.2 案例分析

  • 準備牌:

    牌可以設(shè)計為一個ArrayList<String>,每個字符串為一張牌。
    每張牌由花色數(shù)字兩部分組成,我們可以使用花色集合數(shù)字集合嵌套迭代完成每張牌的組裝。牌由Collections類的shuffle方法進行隨機排序。

  • 發(fā)牌

    將每個人以及底牌設(shè)計為ArrayList<String>,將最后3張牌直接存放于底牌,剩余牌通過對3取模依次發(fā)牌。

  • 看牌

    直接打印每個集合。

4.3 代碼實現(xiàn)

1.生成牌

準備54張牌存到集合中,其中有:

  • 特殊牌:大王,小王
  • 普通牌:定義數(shù)組/集合存儲花色,定義數(shù)組/集合存儲牌的大小序號,嵌套循環(huán)組裝牌。
/*
        1.準備牌
        */
        //定義一個存儲54張牌的ArrayList集合,泛型使用String
        ArrayList<String> poker = new ArrayList<>();
        //定義兩個數(shù)組,一個數(shù)組存儲牌的花色,一個數(shù)組存儲牌的序號
        String[] colors = {"?","?","?","?"};
        String[] numbers = {"2","A","K","Q","J","10","9","8","7","6","5","4","3"};
        //先把大王和小王存儲到poker集合中
        poker.add("大王");
        poker.add("小王");
        //循環(huán)嵌套遍歷兩個數(shù)組,組裝52張牌
        for(String number : numbers){
            for (String color : colors) {
                //System.out.println(color+number);
                //把組裝好的牌存儲到poker集合中
                poker.add(color+number);
            }
        }
        //System.out.println(poker);

其生成的牌時按照花色和序號順序排列的。

2.洗牌

使用集合的工具類Collections中的方法
static void shuffle(List<?> list) 使用默認隨機源對指定列表進行置換。

Collections.shuffle(poker);
        //System.out.println(poker);

這時牌的順序是打亂的。

3.發(fā)牌

發(fā)牌的思路:
因為牌的順序是打亂的,所以直接按順序發(fā)牌即可,跟我們平時真實打牌時是一樣的,也就是將牌按照順序分為三份。

  1. 首先床架三個數(shù)組對象作為玩家村存儲牌的容器,在創(chuàng)建一個容器數(shù)組存儲三張底牌。
  2. 我們使用對牌數(shù)組索引%3的方式,這樣每次產(chǎn)生的值都是0,1,2,
  3. 由于牌的總數(shù)是54,索引值到53,當索引大于50時,將底牌存入創(chuàng)建好的數(shù)組中。
/*
            3.發(fā)牌
         */
        //定義4個集合,存儲玩家的牌和底牌
        ArrayList<String> player01 = new ArrayList<>();
        ArrayList<String> player02 = new ArrayList<>();
        ArrayList<String> player03 = new ArrayList<>();
        ArrayList<String> diPai = new ArrayList<>();

        /*
            遍歷poker集合,獲取每一張牌
            使用poker集合的索引%3給3個玩家輪流發(fā)牌
            剩余3張牌給底牌
            注意:
                先判斷底牌(i>=51),否則牌就發(fā)沒了
         */
        for (int i = 0; i < poker.size() ; i++) {
            //獲取每一張牌
            String p = poker.get(i);
            //輪流發(fā)牌
            if(i>=51){
                //給底牌發(fā)牌
                diPai.add(p);
            }else if(i%3==0){
                //給玩家1發(fā)牌
                player01.add(p);
            }else if(i%3==1){
                //給玩家2發(fā)牌
                player02.add(p);
            }else if(i%3==2){
                //給玩家3發(fā)牌
                player03.add(p);
            }
        }
4.看牌

由于ArrayList重寫了toString方法,所以直接打印三個玩家的數(shù)組以及存儲底牌的數(shù)組即可。

//4.看牌
        System.out.println("劉德華:"+player01);
        System.out.println("周潤發(fā):"+player02);
        System.out.println("周星馳:"+player03);
        System.out.println("底牌:"+diPai);

運行結(jié)果:

4.4 完整代碼

import java.util.ArrayList;
import java.util.Collections;

/*
    斗地主綜合案例:
        1.準備牌
        2.洗牌
        3.發(fā)牌
        4.看牌
 */
public class DouDiZhu {
    public static void main(String[] args) {
        /*
        1.準備牌
        */
        //定義一個存儲54張牌的ArrayList集合,泛型使用String
        ArrayList<String> poker = new ArrayList<>();
        //定義兩個數(shù)組,一個數(shù)組存儲牌的花色,一個數(shù)組存儲牌的序號
        String[] colors = {"?","?","?","?"};
        String[] numbers = {"2","A","K","Q","J","10","9","8","7","6","5","4","3"};
        //先把大王和小王存儲到poker集合中
        poker.add("大王");
        poker.add("小王");
        //循環(huán)嵌套遍歷兩個數(shù)組,組裝52張牌
        for(String number : numbers){
            for (String color : colors) {
                //System.out.println(color+number);
                //把組裝好的牌存儲到poker集合中
                poker.add(color+number);
            }
        }
        //System.out.println(poker);

        /*
            2.洗牌
            使用集合的工具類Collections中的方法
            static void shuffle(List<?> list) 使用默認隨機源對指定列表進行置換。
         */
        Collections.shuffle(poker);
        //System.out.println(poker);

        /*
            3.發(fā)牌
         */
        //定義4個集合,存儲玩家的牌和底牌
        ArrayList<String> player01 = new ArrayList<>();
        ArrayList<String> player02 = new ArrayList<>();
        ArrayList<String> player03 = new ArrayList<>();
        ArrayList<String> diPai = new ArrayList<>();

        /*
            遍歷poker集合,獲取每一張牌
            使用poker集合的索引%3給3個玩家輪流發(fā)牌
            剩余3張牌給底牌
            注意:
                先判斷底牌(i>=51),否則牌就發(fā)沒了
         */
        for (int i = 0; i < poker.size() ; i++) {
            //獲取每一張牌
            String p = poker.get(i);
            //輪流發(fā)牌
            if(i>=51){
                //給底牌發(fā)牌
                diPai.add(p);
            }else if(i%3==0){
                //給玩家1發(fā)牌
                player01.add(p);
            }else if(i%3==1){
                //給玩家2發(fā)牌
                player02.add(p);
            }else if(i%3==2){
                //給玩家3發(fā)牌
                player03.add(p);
            }
        }

        //4.看牌
        System.out.println("劉德華:"+player01);
        System.out.println("周潤發(fā):"+player02);
        System.out.println("周星馳:"+player03);
        System.out.println("底牌:"+diPai);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容