magneto2錯誤提示Warning Invalid argument supplied for foreach() 的解決方法
文章目錄
Java集合、泛型和列舉
Java泛型
Java 集合有個缺點,就是把一個物件“丟進”集合裡之後,集合就會“忘記”這個物件的資料型別,當再次取出該物件時,該物件的編譯型別就變成了 Object 型別(其執行時型別沒變)
Java 集合之所以被設計成這樣,是因為集合的設計者不知道我們會用集合來儲存什麼型別的物件,所以他們把集合設計成能儲存任何型別的物件,只要求具有很好的通用性,但這樣做帶來如下兩個問題:
- 集合對元素型別沒有任何限制,這樣可能引發一些問題。例如,想建立一個只能儲存 Dog 物件的集合,但程式也可以輕易地將 Cat 物件“丟”進去,所以可能引發異常。
- 由於把物件“丟進”集合時,集合丟失了物件的狀態資訊,集合只知道它盛裝的是 Object,因此取出集合元素後通常還需要進行強制型別轉換。這種強制型別轉換既增加了程式設計的複雜度,也可能引發 ClassCastException 異常。
所以為了解決上述問題,從 Java 1.5 開始提供了泛型。泛型可以在編譯的時候檢查型別安全,並且所有的強制轉換都是自動和隱式的,提高了程式碼的重用率。本節將詳細介紹 Java 中泛型的使用
泛型集合
泛型本質上是提供型別的“型別引數”,也就是引數化型別。我們可以為類、介面或方法指定一個型別引數,通過這個引數限制操作的資料型別,從而保證型別轉換的絕對安全
下面將結合泛型與集合編寫一個案例實現圖書資訊輸出
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.Map; public class Test16 { public class Book { private int Id; // 圖書編號 private String Name; // 圖書名稱 private int Price; // 圖書價格 public Book(int id, String name, int price) { // 構造方法 this.Id = id; this.Name = name; this.Price = price; } public String toString() { // 重寫 toString()方法 return this.Id + ", " + this.Name + "," + this.Price; } } public void test2() { // 建立3個Book物件 Book book1 = new Book(1, "唐詩三百首", 8); Book book2 = new Book(2, "小星星", 12); Book book3 = new Book(3, "成語大全", 22); Map<Integer, Book> books = new HashMap<Integer, Book>(); books.put(1001, book1); books.put(1002, book2); books.put(1003, book3); for(Integer bookId:books.keySet()) { System.out.println(bookId + " " + books.get(bookId)); } List<Book> bookList = new ArrayList<Book>(); // 定義泛型的 List 集合 bookList.add(book1); bookList.add(book2); bookList.add(book3); System.out.println("泛型List儲存的圖書資訊如下:"); for (int i = 0; i < bookList.size(); i++) { System.out.println(bookList.get(i)); // 這裡不需要型別轉換 } } public static void main(String[] args) { new Test16().test2(); } }
在該示例中,第 7 行程式碼建立了一個鍵型別為 Integer、值型別為 Book 的泛型集合,即指明瞭該 Map 集合中存放的鍵必須是 Integer 型別、值必須為 Book 型別,否則編譯出錯
泛型類
除了可以定義泛型集合之外,還可以直接限定泛型類的型別引數。語法格式如下:
public class class_name<data_type1,data_type2,…>{}
其中,class_name 表示類的名稱,data_ type1 等表示型別引數。Java 泛型支援宣告一個以上的型別引數,只需要將型別用逗號隔開即可。
泛型類一般用於類中的屬性型別不確定的情況下。在宣告屬性時,使用下面的語句:
private data_type1 property_name1;
private data_type2 property_name2;
該語句中的 data_type1 與類宣告中的 data_type1 表示的是同一種資料型別。
在例項化泛型類時,需要指明泛型類中的型別引數,並賦予泛型類屬性相應型別的值。例如,下面的示例程式碼建立了一個表示學生的泛型類,該類中包括 3 個屬性,分別是姓名、年齡和性別。
public class Test17 {
public class Stu<N, A, S> {
private N name; // 姓名
private A age; // 年齡
private S sex; // 性別
// 建立類的建構函式
public Stu(N name, A age, S sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 下面是上面3個屬性的setter/getter方法
public N getName() {
return name;
}
public void setName(N name) {
this.name = name;
}
public A getAge() {
return age;
}
public void setAge(A age) {
this.age = age;
}
public S getSex() {
return sex;
}
public void setSex(S sex) {
this.sex = sex;
}
}
public void test1() {
Stu<String, Integer, Character> stu = new Stu<String, Integer, Character>("張曉玲", 28, '女');
String name = stu.getName();
Integer age = stu.getAge();
Character sex = stu.getSex();
System.out.println("學生資訊如下:");
System.out.println("學生姓名:" + name + ",年齡:" + age + ",性別:" + sex);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
new Test17().test1();
}
}
泛型方法
是否擁有泛型方法,與其所在的類是不是泛型沒有關係
泛型方法使得該方法能夠獨立於類而產生變化。如果使用泛型方法可以取代類泛型化,那麼就應該只使用泛型方法。另外,對一個 static 的方法而言,無法訪問泛型類的型別引數。因此,如果 static 方法需要使用泛型能力,就必須使其成為泛型方法
定義泛型方法的語法格式如下:
public static List<T> find(Class<T>class,int userId){}
一般來說編寫 Java 泛型方法,其返回值型別至少有一個引數型別應該是泛型,而且型別應該是一致的,如果只有返回值型別或引數型別之一使用了泛型,那麼這個泛型方法的使用就被限制了
public static <T> void List(T book) { // 定義泛型方法
if (book != null) {
System.out.println(book);
}
}
public void test2() {
String name = "zeng1";
String name1 = "zeng2";
List(name);
List(name1);
Integer age1 = 10;
Integer age2 = 11;
List(age1);
List(age2);
}
泛型的高階用法
限制泛型可用型別
在 Java 中預設可以使用任何型別來例項化一個泛型類物件。當然也可以對泛型類例項的型別進行限制,語法格式如下:
class 類名稱<T extends anyClass>
其中,anyClass 指某個介面或類。使用泛型限制後,泛型類的型別必須實現或繼承 anyClass 這個介面或類。無論 anyClass 是介面還是類,在進行泛型限制時都必須使用 extends 關鍵字。
在下面的示例程式碼中建立了一個 ListClass 類,並對該類的型別限制為只能是實現 List 介面的類
public class ListClass<T extends List> {
}
public void test3() {
// 例項化使用ArrayList的泛型類ListClass,正確
ListClass<ArrayList> arrayList = new ListClass<ArrayList>();
// 例項化使用LinkedList的泛型類LlstClass,正確
ListClass<LinkedList> linkedList = new ListClass<LinkedList>();
// 例項化使用HashMap的泛型類ListClass,錯誤,因為HasMap沒有實現List介面
// ListClass<HashMap> hashMap = new ListClass<HashMap>();
}
使用型別萬用字元
Java 中的泛型還支援使用型別萬用字元,它的作用是在建立一個泛型類物件時限制這個泛型類的型別必須實現或繼承某個介面或類。
使用泛型型別萬用字元的語法格式如下:
泛型類名稱<? extends List>a = null;
其中,“<? extends List>”作為一個整體表示型別未知,當需要使用泛型物件時,可以單獨例項化。
例如,下面的示例程式碼演示了型別萬用字元的使用
A<? extends List>a = null;
a = new A<ArrayList> (); // 正確
b = new A<LinkedList> (); // 正確
c = new A<HashMap> (); // 錯誤
繼承泛型類和實現泛型介面
定義為泛型的類和介面也可以被繼承和實現。例如下面的示例程式碼演示瞭如何繼承泛型類
public class FatherClass<T1>{}
public class SonClass<T1,T2,T3> extents FatherClass<T1>{}
如果要在 SonClass 類繼承 FatherClass 類時保留父類的泛型型別,需要在繼承時指定,否則直接使用 extends FatherClass 語句進行繼承操作,此時 T1、T2 和 T3 都會自動變為 Object,所以一般情況下都將父類的泛型型別保留
Java列舉
在 JDK 1.5 之前沒有列舉型別,那時候一般用介面常量來替代。而使用 Java 列舉型別 enum 可以更貼近地表示這種常量。
宣告列舉
如果沒有顯式地宣告基礎型別的列舉,那麼意味著它所對應的基礎型別是 int
下面程式碼定義了一個表示性別的列舉型別 SexEnum 和一個表示顏色的列舉型別 Color
public enum SexEnum {
male,female;
}
public enum Color {
RED,BLUE,GREEN,BLACK;
}
之後便可以通過列舉型別名直接引用常量,如 SexEnum.male、Color.RED
使用列舉還可以使 switch 語句的可讀性更強,例如以下示例程式碼:
enum Signal {
// 定義一個列舉型別
GREEN, YELLOW
}
public void test1(Signal color) {
switch(color) {
case GREEN:
System.out.println("GREEN");
break;
case YELLOW:
System.out.println("GREEN");
break;
default:
break;
}
}
列舉類
Java 中的每一個列舉都繼承自 java.lang.Enum 類。當定義一個列舉型別時,每一個列舉型別成員都可以看作是 Enum 類的例項,這些列舉成員預設都被 final、public, static 修飾,當使用列舉型別成員時,直接使用列舉名稱呼叫成員即可
所有列舉例項都可以呼叫 Enum 類的方法,常用方法如下表所示
方法名稱 | 描述 |
---|---|
values() | 以陣列形式返回列舉型別的所有成員 |
valueOf() | 將普通字串轉換為列舉例項 |
compareTo() | 比較兩個列舉成員在定義時的順序 |
ordinal() | 獲取列舉成員的索引位置 |
通過呼叫列舉型別例項的 values( ) 方法可以將列舉的所有成員以陣列形式返回,也可以通過該方法獲取列舉型別的成員。
下面的示例建立一個包含 3 個成員的列舉型別 Signal,然後呼叫 values() 方法輸出這些成員
public class Test18 {
enum Signal {
// 定義一個列舉型別
GREEN, YELLOW
}
public void test2() {
for(Signal value:Signal.values()) {
System.out.println(value.compareTo(Signal.YELLOW));
}
}
public static void main(String[] args) {
new Test18().test2();
}
}
為列舉新增方法
Java 為列舉型別提供了一些內建的方法,同時列舉常量也可以有自己的方法。此時要注意必須在列舉例項的最後一個成員後新增分號,而且必須先定義列舉例項;
Java 中的 enum 還可以跟 Class 類一樣覆蓋基類的方法。下面示例程式碼建立的 Color 列舉型別覆蓋了 toString() 方法
public class Test18 {
enum WeekDay {
Mon("Monday"),Tue("Tuesday"),Wed("Wednesday"),Thu("Thursday"),Fri("Friday"),Sat("Saturday"),Sun("Sunday");
// 以上是列舉的成員,必須先定義,而且使用分號結束
private final String day;
private WeekDay(String day) {
this.day = day;
}
public static void printDay(int i) {
switch(i) {
case 1:
System.out.println(WeekDay.Mon);
break;
case 2:
System.out.println(WeekDay.Tue);
break;
case 3:
System.out.println(WeekDay.Wed);
break;
case 4:
System.out.println(WeekDay.Thu);
break;
case 5:
System.out.println(WeekDay.Fri);
break;
case 6:
System.out.println(WeekDay.Sat);
break;
case 7:
System.out.println(WeekDay.Sun);
break;
default:
System.out.println("wrong number!");
}
}
// 覆蓋方法
@Override
public String toString() {
return this.age + " " + this.name;
}
}
public void test3() {
for(WeekDay weekDay:WeekDay.values()) {
System.out.println(weekDay+"====>" + weekDay.getDay());
}
WeekDay.printDay(5);
}
public static void main(String[] args) {
new Test18().test3();
}
}
EnumMap 與 EnumSet
為了更好地支援列舉型別,java.util 中添加了兩個新類:EnumMap 和 EnumSet。使用它們可以更高效地操作列舉型別。
EnumMap 類
EnumMap 是專門為列舉型別量身定做的 Map 實現。雖然使用其他的 Map(如 HashMap)實現也能完成列舉型別例項到值的對映,但是使用 EnumMap 會更加高效
HashMap 只能接收同一列舉型別的例項作為鍵值,並且由於列舉型別例項的數量相對固定並且有限,所以 EnumMap 使用陣列來存放與列舉型別對應的值,使得 EnumMap 的效率非常高
// 定義資料庫型別列舉
public enum DataBaseType {
MYSQUORACLE,DB2,SQLSERVER
}
// 某類中定義的獲取資料庫URL的方法以及EnumMap的宣告
private EnumMap<DataBaseType,String>urls = new EnumMap<DataBaseType,String>(DataBaseType.class);
public DataBaseInfo() {
urls.put(DataBaseType.DB2,"jdbc:db2://localhost:5000/sample");
urls.put(DataBaseType.MYSQL,"jdbc:mysql://localhost/mydb");
urls.put(DataBaseType.ORACLE,"jdbc:oracle:thin:@localhost:1521:sample");
urls.put(DataBaseType.SQLSERVER,"jdbc:microsoft:sqlserver://sql:1433;Database=mydb");
}
//根據不同的資料庫型別,返回對應的URL
// @param type DataBaseType 列舉類新例項
// @return
public String getURL(DataBaseType type) {
return this.urls.get(type);
}
EnumSet 類
EnumSet 是列舉型別的高效能 Set 實現,它要求放入它的列舉常量必須屬於同一列舉型別。EnumSet 提供了許多工廠方法以便於初始化,如下表所示
方法名稱 | 描述 |
---|---|
allOf(Class element type) | 建立一個包含指定列舉型別中所有列舉成員的 EnumSet 物件 |
complementOf(EnumSet s) | 建立一個與指定 EnumSet 物件 s 相同的列舉型別 EnumSet 物件, |
幷包含所有 s 中未包含的列舉成員
copyOf(EnumSet s) | 建立一個與指定 EnumSet 物件 s 相同的列舉型別 EnumSet 物件,
並與 s 包含相同的列舉成員
noneOf(<Class elementType) | 建立指定列舉型別的空 EnumSet 物件
of(E first,e…rest) | 建立包含指定列舉成員的 EnumSet 物件
range(E from ,E to) | 建立一個 EnumSet 物件,該物件包含了 from 到 to 之間的所有枚
舉成員
EnumSet 作為 Set 介面實現,它支援對包含的列舉常量的遍歷。
for(Operation op:EnumSet.range(Operation.PLUS,Operation.MULTIPLY)) {
doSomeThing(op);
}