1. 程式人生 > >[Java基礎]Java萬用字元

[Java基礎]Java萬用字元

轉自:http://peiquan.blog.51cto.com/7518552/1303768

本以為這會是一篇比較基礎的部落格,可一旦深究的時候,才發現很多有意思的東西,也發現了很多令人迷惑的地方。萬用字元是一個有趣的東西,如果你掌握了,會使你的程式碼更為通用(健壯性更強)。首先本文是在建立在java泛型基礎之上的,如果你對泛型並不瞭解,可以點選這裡。同時為了對萬用字元的瞭解更為透切,定義如下幾個類。

public class Animal {
	private String name;

	public Animal(String name) {
		this.name = name;
	}
	
	public void eat() {
		System.out.println(getName() + " can eat.");
	}
	
	public String getName(){
		return name;
	}
}
public class Cat extends Animal {

	public Cat(String name) {
		super(name);
	}

	public void jump(){
		System.out.println(getName() + " can jump.");
	}
}
public class Bird extends Animal {

	public Bird(String name) {
		super(name);
	}

	public void fly(){
		System.out.println(getName() + " can fly.");
	}
}
public class Magpie extends Bird {

	public Magpie(String name) {
		super(name);
	}

	public void sing(){
		System.out.println(getName() + 
				" can not only eat,but sing");
	}
}


    首先我們看一下無萬用字元的使用示例,如下:

public class AnimalTrainer {
	public void act(List<Animal> list) {
		for (Animal animal : list) {
			animal.eat();
		}
	}
}

     測試程式碼如下:

public class TestAnimal {
	public static void main(String[] args) {
		AnimalTrainer animalTrainer = new AnimalTrainer();
		//Test 1
		List<Animal> animalList = new ArrayList<>();
		animalList.add(new Cat("cat1"));
		animalList.add(new Bird("bird1"));
		
		animalTrainer.act(animalList);	//可以通過編譯
		
		//Test 2
		List<Cat> catList = new ArrayList<>();
		catList.add(new Cat("cat2"));
		catList.add(new Cat("cat3"));
		
		animalTrainer.act(catList);		//無法通過編譯
	}
}

    如上,Test 1 的執行應該可以理解的,不過順帶提醒一下的是,因為cat1和bird1都是Animal物件,自然可以新增List<Animal>裡,具體解釋可參考java泛型基礎 。對於Test 2,無法通過編譯是因為List<Cat>並不是List<Animal>子類,傳入引數有誤,也就無法通過編譯了。現在嘗試去修改AnimalTrainer.act()程式碼,讓它變得更為通用的一點,即不僅僅是接受List<Animal>引數,還可以接受List<Bird>等引數。那如何更改呢??

一、萬用字元的上界

    既然知道List<Cat>並不是List<Anilmal>的子型別,那就需要去尋找替他解決的辦法, 是AnimalTrianer.act()方法變得更為通用(既可以接受List<Animal>型別,也可以接受List<Cat>等引數)。在java裡解決辦法就是使用萬用字元“?”,具體到AnimalTrianer,就是將方法改為act(List<? extends Animal> list),當中“?”就是萬用字元,而“? extends Animal”則表示萬用字元“?”的上界為Animal,換句話說就是,“? extends Animal”可以代表Animal或其子類,可代表不了Animal的父類(如Object),因為萬用字元的上界是Animal。如下,為改進之後的AnimalTrianer

public class AnimalTrainer {
	public void act(List<? extends Animal> list) {
		for (Animal animal : list) {
			animal.eat();
		}
	}
}

    再來測試一下,如下,發現Test 2 可以通過編譯了:

public class TestAnimal {
	public static void main(String[] args) {
		AnimalTrainer animalTrainer = new AnimalTrainer();
		//Test 1
		List<Animal> animalList = new ArrayList<>();
		animalList.add(new Cat("cat1"));
		animalList.add(new Bird("bird1"));
		
		animalTrainer.act(animalList);	//可以通過編譯
		
		//Test 2
		List<Cat> catList = new ArrayList<>();
		catList.add(new Cat("cat2"));
		catList.add(new Cat("cat3"));
		
		animalTrainer.act(catList);		//也可以通過編譯
	}
}

    經過上述分析,可以知道List<Animal>和List<Cat>都是List<? extends Animal>的子型別,類似有List<Bird>,List<Magpie>也是List<? extends Animal>的子型別。現總結如下,對於萬用字元的上界,有以下幾條基本規則:(假設給定的泛型型別為G,(如List<E>中的List),兩個具體的泛型引數X、Y,當中Y是X的子類(如上的Animal和Cat))

  • G<? extends Y> 是 G<? extends X>的子型別(如List<? extends Cat> 是 List<? extends Animal>的子型別)。
  • G<X> 是 G<? extends X>的子型別(如List<Animal> 是 List<? extends Animal>的子型別)
  • G<?> 與 G<? extends Object>等同,如List<?> 與List<? extends Objext>等同。

    學到這裡,可能會遇到一些疑惑的地方,或者說事理解不透的地方,先觀察如下兩段程式碼片段,判斷一下其是否可行??

public void testAdd(List<? extends Animal> list){
		//....其他邏輯
		list.add(new Animal("animal"));
		list.add(new Bird("bird"));
		list.add(new Cat("cat"));
	}
List<? extends Animal> list = new ArrayList<>();
list.add(new Animal("animal"));
list.add(new Bird("bird"));
list.add(new Cat("cat"));

     先分析如下:因為“? extends Animal”可代表Animal或其子類(Bird,Cat),那上面的操作應該是可行的。事實上是”不行“,即無法通過編譯。為什麼呢??

     在解釋之前,再來重新強調一下已經知道的規則:在List<Aimal> list裡只能新增Animal類物件及其子類物件(如Cat和Bird物件),在List<Bird>裡只能新增Bird類和其子類物件(如Magpie),可不能新增Animal物件(不是Bird的子類),類似的在List<Cat>和List<Magpie>裡只能新增Cat和Bird物件(或其子類物件,不過這沒有列出)。現在再回頭看一下testAdd()方法,我們知道List<Animal>、List<Cat等都是List<? extends Animal>的子型別。先假設傳入的引數為為List<Animal>,則第一段程式碼的三個“add”操作都是可行的;可如果是List<Bird>呢??則只有第二個“add”可以執行;再假設傳入的是List<Tiger>(Tiger是想象出來的,可認為是Cat的子類),則三個“add”操作都不能執行。

     現在反過來說,給testAdd傳入不同的引數,三個“add”操作都可能引發型別不相容問題,而傳入的引數是未知的,所以java為了保護其型別一致,禁止向List<? extends Animal>新增任意物件,不過卻可以新增 null即list.add(null)是可行的。有了上面談到的基礎,再來理解第二段程式碼就不難了,因為List<? extends Animal>的型別“? extends Animal”無法確定,可以是Animal,Bird或者Cat等,所以為了保護其型別的一致性,也是不能往list新增任意物件的,不過卻可以新增 null

    先總結如下:不能往List<? extends Animal> 新增任意物件,除了null。

    另外提醒大家注意的一點是,在List<? extends Animal> 可以是Animal類物件或Bird物件等(只是某一類物件),反過來說,在List<? extends Animal> list裡的都是Animal物件,即Bird也是Animal物件,Cat也是Animal物件(用java的語言來說就是子類可以指向父類,父類卻不能指向子類),那麼在Animal裡的所有方法都是可以呼叫的,如下:

for (Animal animal : list) { animal.eat(); }

二、萬用字元的下界

    既然有了萬用字元的上界,自然有著萬用字元的下界。可以如此定義萬用字元的下界 List<? super Bird>,其中”Bird“就是萬用字元的下界。注意:不能同時宣告泛型萬用字元申明上界和下界。

    在談注意細節之前,我們先看一下萬用字元的使用規則——對於萬用字元的上界,有以下幾條基本規則:(假設給定的泛型型別為G,(如List<E>中的List),兩個具體的泛型引數X、Y,當中Y是X的子類(如上的Animal和Cat))

  • G<? super X> 是 G<? super Y>的子型別(如List<? super Animal> 是 List<? super Bird>的子型別)。
  • G<X> 是 G<? super X>的子型別(如List<Animal> 是 List<? super Animal>的子型別)

   現在再來看如下程式碼,判斷其是否符合邏輯:

public void testAdd(List<? super Bird> list){
		list.add(new Bird("bird"));
		list.add(new Magpie("magpie"));
	}
List<? super Bird> list = new ArrayList<>();
list.add(new Bird("bird"));
list.add(new Magpie("magpie"));
list.add(new Animal("animal"));

     看第一段程式碼,其分析如下,因為”? super Bird”代表了Bird或其父類,而Magpie是Bird的子類,所以上訴程式碼不可通過編譯。而事實上是”“,為什麼呢?2?

     在解疑之前,再來強調一個知識點,子類可以指向父類,即Bird也是Animal物件。現在考慮傳入到testAdd()的所有可能的引數,可以是List<Bird>,List<Animal>,或者List<Objext>等等,發現這些引數的型別是Bird或其父類,那我們可以這樣看,把bird、magpie看成Bird物件,也可以將bird、magpie看成Animal物件,類似的可看成Object物件,最後發現這些新增到List<? supe Bird> list裡的物件都是同一類物件(如本文剛開篇提到的Test 1),因此testAdd方法是符合邏輯,可以通過編譯的。:

     現在再來看一下第二段程式碼對於,第二、三行程式碼的解釋和上文一樣,至於最後一行“list.add(new Animal("animal"))”是無法通過編譯的,為什麼的??為了保護型別的一致性,因為“? super Bird”可以是Animal,也可以是Object或其他Bird的父類,因無法確定其型別,也就不能往List<? super Bird>新增Bird的任意父類物件。

    既然無法確定其父類物件,那該如何遍歷List<? super Bird> ? 因為Object是所有類的根類,所以可以用Object來遍歷。如下,不過貌似其意義不大。

for (Object object : list) {//...}

    那“? super BoundingType”可以應用在什麼地方呢??“? super BoundingType”應用相對廣泛,只不過是混合著用。下面舉個簡單的例子。先假設有以下兩個Student和CollegeStudent,當中CollegeStudent繼承Student,如下:

public class Student implements Comparable<Student>{
	private int id;

	public Student(int id) {
		this.id = id;
	}

	@Override
	public int compareTo(Student o) {
		return (id > o.id) ? 1 : ((id < o.id) ? -1 : 0);
	}
}
public class CollegeStudent extends Student{
	public CollegeStudent(int id) {
		super(id);
	}
}


    先需要根據他們的id對他們進行排序(注意此處是對陣列物件進行排序),設計方法如下,(n指陣列元素的個數):

public static <T extends Comparable<? super T>> 
			void selectionSort(T[] a,int n)

    先理解此方法含義,首先<T extends Comparable<T>>規定了陣列中物件必須實現Comparable介面,Comparable<? Super T>表示如果父類實現Comparable介面,其自身可不實現,如CollegeStudent。先假設有一個CollegeStudent的陣列,如下:

CollegeStudent[] stu = new CollegeStudent[]{
   new CollegeStudent(3),new CollegeStudent(2),
   new CollegeStudent(5),new CollegeStudent(4)};


    執行方法 selectionSort(stu,4)是完全可以通過的。可如果定義的selectionSort方法如下:

public static <T extends Comparable<T>> 
			void selectionSort(T[] a,int n)

    則方法selectionSort(stu,4)不能執行,因為CollegeStudent沒有實現Comparable<CollegeStudent>介面。換句話就是“? super T”使selectionSort方法變得更為通用了。selectionSort完整程式碼的實現可參考本文的末尾。

三、無界萬用字元

    知道了萬用字元的上界和下界,其實也等同於知道了無界萬用字元,不加任何修飾即可,單獨一個“?”。如List<?>,“?”可以代表任意型別,“任意”也就是未知型別。無界萬用字元通常會用在下面兩種情況:1、當方法是使用原始的Object型別作為引數時,如下:

public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}

    可以選擇改為如下實現:

public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

     這樣就可以相容更多的輸出,而不單純是List<Object>,如下:

List<Integer> li = Arrays.asList(1, 2, 3);
List<String>  ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);

    2、在定義的方法體的業務邏輯與泛型型別無關,如List.size,List.cleat。實際上,最常用的就是Class<?>,因為Class<T>並沒有依賴於T。

    最後提醒一下的就是,List<Object>與List<?>並不等同,List<Object>是List<?>的子類。還有不能往List<?> list裡新增任意物件,除了null。

附錄:selectionSort的程式碼實現:(如果需要實現比較好的輸出,最好重寫Student的toString方法)

public class SortArray {
	
	//對一組陣列物件運用插入排序,n指陣列元素的個數
	public static <T extends Comparable<? super T>> 
					void selectionSort(T[] a,int n) {
		for (int index = 0; index < n-1; index++) {
			int indexOfSmallest = getIndexOfSmallest(a,index,n-1);
			swap(a,index,indexOfSmallest);
		}
	}
	
	public static <T extends Comparable<? super T>> int getIndexOfSmallest(T[] a, int first, int last) {
		T minValue = a[first]; // 假設第一個為minValue
		int indexOfMin = first; // 取得minValue的下標
		for (int index = first + 1; index <= last; index++) {
			if (a[index].compareTo(minValue) < 0) {
				minValue = a[index];
				indexOfMin = index;
			}
		}

		return indexOfMin;
	}
	
	public static void swap(Object[] a,int first,int second) {
		Object temp = a[first];
		a[first] = a[second];
		a[second] = temp;
	}
	
	public static void main(String[] args) {
		CollegeStudent[] stu = new CollegeStudent[]{
				new CollegeStudent(3),
				new CollegeStudent(2),
				new CollegeStudent(5),
				new CollegeStudent(4)};
		selectionSort(stu, 4);
		for (Student student : stu) {
			System.out.println(student);
		}
	}
}

相關推薦

java基礎-泛型/字元

泛型 泛型的意義: a:可以對型別進行自動檢查。 並不是替換,只是編譯期間進行檢查。 b:自動對型別進行轉換。 泛型到底是怎麼編譯的? 型別的擦除機制:》向上擦除》Object(最高到) 在編譯器編譯期間,把泛型全部擦除為object型別。 用jmap命

Java基礎Java字元

轉自:http://peiquan.blog.51cto.com/7518552/1303768 本以為這會是一篇比較基礎的部落格,可一旦深究的時候,才發現很多有意思的東西,也發現了很多令人迷惑的地方。萬用字元是一個有趣的東西,如果你掌握了,會使你的程式碼更為通用(健壯性更強)。首先本文是在建立在java泛

Java 泛型和字元

Java 泛型和萬用字元        很多時候,反射並不能滿足業務的需求,Java 泛型和萬用字元與反射配合使用可使得反射的作用發揮到完美的地步,從而實現真正的通用。 直接上程式碼 import java.

Java基礎Java總結篇

本文來自於清華大神(瀟澗)的Java總結,已得到其本人允許轉載 1.JVM JVM記憶體模型: PC(程式計數器),虛擬機器棧,本地方法棧,Java堆,方法區 PC:位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的

Java基礎系列(三十七):泛型繼承,字元,泛型反射

泛型型別的繼承規則 首先,我們來看一個類和它的子類,比如 Fruit 和 Apple。但是Pair<Apple>是Pair<Fruit>的一個子類麼?並不是。比如下面的這段程式碼就會編譯失敗: Apple[] apples = ...; Pair<F

java基礎總結 --- 泛型 擦除、邊界、字元

* 擦除的問題 * 為什麼要擦除: 1.5版本才出現泛型 為了相容之前地程式碼 * 它使得泛化的客戶端可以用非泛化的類庫來使用。 * 以及不破壞現有類庫的情況下,將泛型融入java語言。 * 擦除使得現有的非泛型客戶端程式碼能夠在不改變的情況繼續使用,直至客戶端準

java泛型中的字元?問題

本文是經過網上查詢的資料整合而來,給自己做個筆記,也分享給大家!需要你對泛型有一定的基礎瞭解。 package Test8_8; import java.util.ArrayList; import java.util.List; class Animal { privat

Java泛型方法和型別字元的區別

泛型方法VS型別萬用字元(兩者可以混用):      1)你會發現所有能用型別萬用字元(?)解決的問題都能用泛型方法解決,並且泛型方法可以解決的更好: 最典型的一個例子就是:          

java使用Access資料庫中字元的一些坑

java使用Access資料庫中萬用字元的一些坑 在使用access資料庫寫一個條件為like的語句: select mess_id ,mess_name,mess_note,mess_txt from messtable where 1=1 and mess_name like ‘*

Java字元+註解

一、泛型的萬用字元 1.萬用字元使用的場景     方法的引數 2.萬用字元的優點     使方法更加通用 3.萬用字元分類 無界通配:? 子類限定:? extends Object 父類限定:?  super Integer 4.萬用字元的缺點 使變數使用上

java:集合框架(泛型高階之字元)

* A:泛型萬用字元<?>     * 任意型別,如果沒有明確,那麼就是Object以及任意的Java類了 * B:? extends E     * 向下限定,E及其子類 * C:? su

Effective Java 泛型 第28條:利用有限制字元來提升API的靈活性

如第25條所述,引數化型別是 不可變的(invariant)。換句話說,對於任何兩個截然不同的型別tyle1和type2來說,List< Type1>既不是List< Type2>的子型別,也不是他的超型別。雖然List< Stri

java遺珠之泛型字元

我們在之前說過明確指定了泛型型別的引數之後,會把引數限制的很嚴格,萬用字元的作用就是放寬這種限制,有上限有界萬用字元<? extends A>,下限有界萬用字元<? extends B>,無界萬用字元<?>。上限有界和無界經常

leetcode 44 ---- 動態規劃(困難) :字元匹配(java

1.  問題:給定一個字串 (s) 和一個字元模式 (p) ,實現一個支援 '?' 和 '*' 的萬用字元匹配。 '?' 可以匹配任何單個字元。 '*' 可以匹配任意字串(包括空字串)。 兩個字串完全匹配才算匹配成功。 說明: s 可能為空,且只包含從 a-z 的小

java泛型<? extends E> 有上限字元與<? Super E>有上限字元

萬用字元?,?表示佔位,表明將來使用的時候在指明型別  <?>無限定的萬用字元, 是讓泛型能夠接受未知型別的資料 <? extends E> 有上限萬用字元,能夠接受指定類及其子類型別的資料,E就是該泛型的上邊界 

Java 泛型總結(三):字元的使用

簡介 前兩篇文章介紹了泛型的基本用法、型別擦除以及泛型陣列。在泛型的使用中,還有個重要的東西叫萬用字元,本文介紹萬用字元的使用。 這個系列的另外兩篇文章: Java 泛型總結(一):基本用法與型別擦除 Java 泛型總結(二):泛型與陣列 陣列的協變 在瞭解萬用字

java 泛型中 T 和 問號(字元)的區別

型別本來有:簡單型別和複雜型別,引入泛型後把複雜型別分的更細了; 現在List<Object>, List<String>是兩種不同的型別;且無繼承關係; 泛型的好處如: 開始版本 public void write(Integer

Java泛型--上界字元和下界字元

轉自:Java泛型中extends和super的區別? 另,問題來源:Java 泛型 <? super T> 中 super 怎麼 理解?與 extends 有何不同?   <? extends T>和<? super T>是Java泛型中的

java字元的詳解

在java中,?代表萬用字元。 萬用字元用法 1. 在例項化物件的時候,不確定泛型引數的具體型別時,可以使用萬用字元進行物件定義 2. <? extends Object>代表上邊界限定萬用字元 3. <? super Objec

Java泛型中T和問號(字元)的區別

型別本來有:簡單型別和複雜型別,引入泛型後把複雜型別分的更細了. 概述 泛型是Java SE 1.5的新特性,泛型的本質是引數化型別,也就是說所操作的資料型別被指定為一個引數。這種引數型別可以用在類、介面和方法的建立中,分別稱為泛型類、泛型介面、泛型方法。 Java語