Java中泛型使用的簡單方法介紹

 更新時間:2019-08-11 12:01:59   作者:佚名   我要評論(0)

一. 泛型是什么


“泛型”,顧名思義,“泛指的類型”。我們提供了泛指的概念,但具體執行的時候卻可以有具體的規則來約束,比如我們用的非常多的ArrayList

一. 泛型是什么

“泛型”,顧名思義,“泛指的類型”。我們提供了泛指的概念,但具體執行的時候卻可以有具體的規則來約束,比如我們用的非常多的ArrayList就是個泛型類,ArrayList作為集合可以存放各種元素,如Integer, String,自定義的各種類型等,但在我們使用的時候通過具體的規則來約束,如我們可以約束集合中只存放Integer類型的元素,如List<Integer> iniData = new ArrayList<>()。

二. 使用泛型有什么好處

以集合來舉例,使用泛型的好處是我們不必因為添加元素類型的不同而定義不同類型的集合,如整型集合類,浮點型集合類,字符串集合類,我們可以定義一個集合來存放整型、浮點型,字符串型數據,而這并不是最重要的,因為我們只要把底層存儲設置了Object即可,添加的數據全部都可向上轉型為Object。 更重要的是我們可以通過規則按照自己的想法控制存儲的數據類型。

我們以ArrayList為例,假如我們要將本月截至今天的日期放到ArrayList中,如果我們不使用泛型,此時我們定義一個ArrayList.

List monthDays = new ArrayList();

我們向其中加入1號到4號日期

public static List addMonthDays(){
 List monthDays = new ArrayList();
 monthDays.add(LocalDate.now().withDayOfMonth(1));
 monthDays.add(LocalDate.now().withDayOfMonth(2));
 monthDays.add(LocalDate.now().withDayOfMonth(3));
 monthDays.add(new Date());
 return monthDays;
}

這樣有沒有問題?大家也看出來了,當然有,雖然都可以表示日期,但卻用了Date,LocalDate,我們調用方法直接打印出來,就是這樣

public static void main(String[] args) {
 List monthDays = addMonthDays();
 for(Object day : monthDays){
  System.out.println(day);
 }
}

2019-08-01
2019-08-02
2019-08-03
Sun Aug 04 10:27:10 CST 2019

我們肯定不想要這樣的結果,我們想要的是

2019-08-01
2019-08-02
2019-08-03
2019-08-04

如果存儲的元素類型只是這兩種(假如我們知道),這個時候我們就手動判斷一下

public static void main(String[] args) {
 List monthDays = addMonthDays();
 for(Object day : monthDays){
  if (day instanceof Date){
   Date date = (Date) day;
   System.out.println(LocalDate.of(date.getYear(), date.getMonth(), date.getDay()));
  }else {
   System.out.println(day);
  }
 }
}

這個時候我們就可以達成上述目的了,但大家也知道,這種寫法問題問題很大

  • 我們無法控制存儲的元素到底是否和日期相關,如我們存儲了“1”,65536等非日期,定義的方法也不會報錯,但在調用進行類型轉換的時候必然會報錯;
  • 我們不知道集合中到底存儲了哪些類型的元素,比如還有“2019/08/04”這種日期字符串、java.sql.Date這種類型呢,我們很難保證可以窮盡;
  • 代碼過于復雜,太難維護;

這時泛型就提供了很好的解決方案,從源頭上就制定好規則,只能添加LocalDate類型,那么上述的問題就都得以解決了。

public static List<LocalDate> addFormatMonthDays(){
 List<LocalDate> monthDays = new ArrayList<>();
 monthDays.add(LocalDate.now().withDayOfMonth(1));
 monthDays.add(LocalDate.now().withDayOfMonth(2));
 monthDays.add(LocalDate.now().withDayOfMonth(3));
 monthDays.add(new Date());//這個代碼在編譯期間就無法通過了
 return monthDays;
}

不僅提高了代碼的可讀性,一眼即可看出我們存儲的是LocalDate類型。同時編譯器也可很好的利用該信息,編譯期間就可進行類型檢查,保證了安全性,在get的時候,無需進行強制類型轉換。

三. 泛型類

為使類適應更多的情況,具備更好的擴展性,我們可以將其設置為泛型類,即具有一個或多個類型變量的類,寫法如下:

public class ClassName<泛型標識,可以是字母、中文字符等,不過一般用大寫英文字符> {
  private 泛型標識 property;
}

對于泛型標識符,一般有一些約定俗稱的寫法,如果是表示集合的元素類型,一般用字母E,如我們常用的ArrayList,public class ArrayList<E>,我們自定義如下:

//車庫
public class Garage<E> {
  //向車庫中添加車
  public void add(E car){
    //...
  }
}

我們還可以用字符K和V表示關鍵字和值的類型,如我們常用的HashMap,public class HashMap<K,V>,我們也可以自定義如下:

//映射關系
//K,V: 蔬菜,是否有機; 水果,產地; 服裝,類型; 汽車,品牌
public class Mapping<K, V> {
  
  private K key;
  
  private V value;
}

我們還經常用一個字符T表示類型

public class Person<T> {
  private T t;
  public Person(T t){
    this.t = t;
  }
  public void run(){
    System.out.println(t);
  }
}

如何使用泛型類,類型如何實例化,我們只需要保證傳入的實參類型和泛型參數類型相同即可。

//正常用法
Person<String> s1 = new Person<String>("張三");
//jdk7.0后可以省略后面的參數類型
Person<String> s2 = new Person<>("張三");
s2.run();

//當然泛型的定義是可以幫助我們按照某種規則去做事,如果不做限制,也不會編譯錯誤,但泛型就毫無意義了
Person t = new Person(111);
t.run();

//泛型的類型不能是八大基本類型,下面會編譯出錯
Person<int> s = new Person<>(1);

四. 泛型接口

泛型接口和泛型類的定義基本一致,定義如下:

public interface Person<T> {
  public T parent();
  public String eat();
}

當我們定義了一個類要實現該接口時,那么該類的泛型類型必須和接口類的泛型類型一致,未傳遞實參的情況下,繼續使用泛型類型T,傳遞了實參的情況下,泛型類型必須使用實參類型

public class Teacher<T> implements Person<T> {
  @Override
  public T parent() {
    return null;
  }

  @Override
  public String eat() {
    return null;
  }
}
//Teacher不必再定義類型了,因為泛型類型在Person處已經定義好了
public class Teacher implements Person<Integer> {
  //這里的返回類型必須為Integer,否則必須出錯
  @Override
  public Integer parent() {
    return null;
  }

  @Override
  public String eat() {
    return null;
  }
}

五. 泛型方法

泛型方法可以定義在普通類和泛型類中,比如泛型類更為常用,一般能用泛型方法解決的問題優先使用泛型方法而不使用泛型類,類型變量放在修飾符的后面,如public static ,public final等的后面。

public class Teacher {
  public static <T> T println(T t){
    System.out.println(t);
    return t;
  }
}

調用很簡單,很一般方法調用是一樣的,更方便的是類型不像一般方法做了限定。

String s = Teancher.println("str");

另外需要說明的是,定義在泛型類中的泛型方法的泛型變量之間是沒有關系的,如這樣的代碼

public class Teacher<T> {
  T teacher;
  public Teacher(T t){
    this.teacher = t;
  }
  public <T> T println(T t){
    System.out.println(t);
    return t;
  }
}
Teacher<String> teacher = new Teacher<>("張三");
Integer in = teacher.println(123456);

類泛型類型為String,方法的泛型類型為Integer,雖然都是用T來表示的。

同時關于泛型方法需要說明的是:

在修飾符public xx與方法名之間非常重要,有< T >這樣的才算是泛型方法;僅僅使用了泛型變量并不算是泛型方法。

六. 限定類型變量

不論是泛型類還是泛型方法,目前來說其實都是沒有做類型限定,無論我們傳遞什么樣類型的變量進去都可以,因為我們在處理邏輯中并沒有使用到該類型特有的東西(成員變量、方法等)。假如我們想傳遞的參數類型僅僅是某個大類(父類)下面的一些小類(子類),那么怎么做呢?

public class ArrayFlag {
  public static <T> T getMax(T[] array){
    if(array == null || array.length == 0){
      return null;
    }
    T maxValue = array[0];
    for(int i = 0; i < array.length; i++){
      if(array[i].compareTo(maxValue) > 0){
        maxValue = array[i];
      }
    }
    return maxValue;
  }
}

大家也看到了我們在getMax方法中使用了compareTo方法進行比較,但如果我們傳入的類型T沒有compareTo方法呢,豈不是要報錯,因此我們需要做限定,只要限定了是Comparable接口的必然具備compareTo方法,那么改造后就成了這樣

public class ArrayFlag {
  public static <T extends Comparable> T getMax(T[] array){
    if(array == null || array.length == 0){
      return null;
    }
    T maxValue = array[0];
    for(int i = 0; i < array.length; i++){
      if(array[i].compareTo(maxValue) > 0){
        maxValue = array[i];
      }
    }
    return maxValue;
  }
}

同時需要說明的是,此處用的是extends關鍵字,extends在這里是表示的是綁定了Comparable接口及其子類型,是“綁定、限定”的意思,非“繼承”的意思,后面也可以是接口或者類,如果有多個限制,可以使用&分隔,如:

public static <T extends Comparable & Serializable> T getMax(T[] array)

七. 泛型通配符

舉個例子,定義了一個書籍類和一個小說類

//定義了一個書籍類
public class Book {}
//定義了一個小說書籍類繼承書籍類
public class Novel extends Book {}

我們再定義一個書柜類用來裝書籍以及小說

//定義了一個書柜類用來裝書
public class Bookcase<T> {
  T b; 
  public Bookcase(T t){
    this.b = t;
  }
  public void set(T t) {
    b=t;
  } 
  public T get() {
    System.out.println(b.getClass());
    return b;
  }
}

下面我們就用書柜來裝小說

//以前的寫法,無法編譯通過,提示Incompatible types, Required Book Found Novel
Bookcase<Book> bc = new Bookcase<Novel>(new Novel());

但在jdk7.0后,new Bookcase的時候是可以不用給出泛型類型的,省略的類型可以從變量的類型推斷得出,因此如果下面這種寫法

Bookcase<Book> bc = new Bookcase<>(new Novel());
System.out.println(bc.getClass());
bc.get();

此時可以編譯通過,我們執行后得出的結果是:

class generic.Bookcase
class generic.Novel

當然我們還可以通過通配符來解決該問題,通配符包括以下幾種:

上界通配符、下界通配符、無限定通配符

7.1 上界通配符

上界通配符定義方式如下:用extends 關鍵字,含義是該書柜只能放置小說類書籍(如什么都市小說、愛情小說、玄幻小說都可以),但不能放置父類書籍、其他類如史書、職場類書籍、財經類書籍等,是在使用的時候進行限定,如:

Bookcase<? extends Book> bc = new Bookcase<Novel>(new Novel());

這種定義方式就不會編譯錯誤了。另外關于上界通配符的特點,對上有限制,根據java多態向上造型的原則,不適合頻繁插入數據,適合頻繁讀取數據的場景。

7.2 下界通配符

下界通配符定義方式如下:用super關鍵字,含義就是書柜放置設置了下限,我們只能放置Book書籍以及Novel書籍,卻無法再將細分的都市小說、愛情小說類書籍放進去

Bookcase<? super Novel> bc = new Bookcase<Novel>(new Novel());

另外關于下界通配符的特點,和上界通配符正好相反,不適合頻繁讀取數據,適合頻繁插入數據的場景。

7.3 無限定通配符

無限定通配符意味著可以使用任何對象,因此使用它類似于使用原生類型。但它是有作用的,原生類型可以持有任何類型,而無限定通配符修飾的容器持有的是某種具體的類型。

舉個例子:

List<?> list = new ArrayList<>();
//無法編譯通過
list.add(new Object());

//下面這樣的卻可以添加任何類型
List<Object> list = new ArrayList<>();
list.add(new Object());

再說一下< T > 和< ? >之間的區別,初看好像他們都可以表示泛型變量,都可以extends,但它們確實有不同的使用場景

  • 類型參數< T >聲明一個泛型類或泛型方法
  • 無限定通配符< ? >使用泛型類或泛型方法

八. 總結

泛型在java中可以說很常用,我們前面提到的集合類,如ArrayList,HashSet,以及Map都使用到了泛型,泛型也是也是我們再進行一些組件封裝經常用到的,本文主要介紹了泛型基本概念,使用泛型的好處,泛型類、接口、方法、通配符的簡單介紹以及使用方法,最后泛型一般和反射集合使用,通過泛型可以進行類型的靈活傳遞,通過反射可獲取到實體以及類的數據信息,從而實現一些框架、組件的封裝,若有不對之處,請批評指正,望共同進步,謝謝!

好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。

您可能感興趣的文章:

  • Java 獲取泛型的類型實例詳解
  • Java中的泛型詳解
  • 多個java泛型示例分享
  • 解析Java的Jackson庫中對象的序列化與數據泛型綁定
  • 基于java中泛型的總結分析
  • JAVA利用泛型返回類型不同的對象方法
  • Java編程思想里的泛型實現一個堆棧類 分享
  • java泛型學習示例
  • 淺談java中定義泛型類和定義泛型方法的寫法
  • Java中泛型的用法總結

相關文章

  • Java中泛型使用的簡單方法介紹

    Java中泛型使用的簡單方法介紹

    一. 泛型是什么 “泛型”,顧名思義,“泛指的類型”。我們提供了泛指的概念,但具體執行的時候卻可以有具體的規則來約束,比如我們用的非常多的ArrayList
    2019-08-11
  • 淺談javascript錯誤處理

    淺談javascript錯誤處理

    當 JavaScript 引擎執行 JavaScript 代碼時,會發生各種錯誤:可能是語法錯誤,通常是程序員造成的編碼錯誤或錯別字;可能是拼寫錯誤或語言中缺少的功能(可能
    2019-08-11
  • SpringBoot如何優雅地處理全局異常詳解

    SpringBoot如何優雅地處理全局異常詳解

    前言 之前用springboot的時候,只知道捕獲異常使用try{}catch,一個接口一個try{}catch,這也是大多數開發人員異常處理的常用方式,雖然屢試不爽,但會造成一
    2019-08-11
  • thinkPHP和onethink微信支付插件分享

    thinkPHP和onethink微信支付插件分享

    thinkPHP和微支付實現的微信支付插件,在微信中調用微信jssdk實現支付,分享給大家參考下 //實現的Wxpay鉤子方法 public function Wxpay($param){
    2019-08-11
  • Angular8基礎應用之表單及其驗證

    Angular8基礎應用之表單及其驗證

    一、前提 必要性:特別必要 意義:很有意義 二、正文 (一)、新建表單(模板表單) 1、新建名稱為formValidator的ng項目——命令行輸入ng new form
    2019-08-11
  • PHP利用DWZ.CN服務生成短網址

    PHP利用DWZ.CN服務生成短網址

    使用DWZ.CN生成短網址 <&#63;php /** * FunctionHelper */ class FunctionHelper { // ----------------------------------------------------------
    2019-08-11
  • C#并發實戰記錄之Parallel.ForEach使用

    C#并發實戰記錄之Parallel.ForEach使用

    前言: 最近給客戶開發一個伙食費計算系統,大概需要計算2000個人的伙食。需求是按照員工的預定報餐計劃對消費記錄進行檢查,如有未報餐有刷卡或者有報餐沒刷
    2019-08-11
  • Nginx代理axios請求以及注意事項詳解

    Nginx代理axios請求以及注意事項詳解

    前言 近期寫個小demo,因為用到某大廠的在線數據,接口做了跨域限制,所以利用Nginx代理來解決這些問題。 1. nginx.conf 配置信息 由于nginx.conf配置信
    2019-08-11
  • 簡單談談MySQL數據透視表

    簡單談談MySQL數據透視表

    我有一張這樣的產品零件表: 部分 part_id part_type product_id -------------------------------------- 1 A 1 2 B 1 3
    2019-08-11
  • Linux curl表單登錄或提交與cookie使用詳解

    Linux curl表單登錄或提交與cookie使用詳解

    前言 本文主要講解通過curl 實現表單提交登錄。單獨的表單提交與表單登錄都差不多,因此就不單獨說了。 說明:針對curl表單提交實現登錄,不是所有網站都適用
    2019-08-11

最新評論

买宝宝用品赚钱吗