泛型
目錄
泛型
引入
泛型類
泛型與多態
泛型方法
泛型的界限
類型擦除
函數式接口
Supplier供給型函數式接口:
Consumer消費型函數式接口:
Function函數型函數式接口:
Predicate斷言式函數式接口:
判空包裝
引入
學生成績可能是數字類型,也可能是字符串類型,如何存放可能出現的兩種類型呢:
public class Score {String name;String id;Object value; //因為Object是所有類型的父類,因此既可以存放Integer也能存放Stringpublic Score(String name,String id,Object value){this.name=name;this.id=id;this.value=value;}
}
以上方法雖然解決了多種類型存儲的問題,但是Object類型在編譯階段并不具有良好的類型判斷能力,很容易出現:
public static void main(String[] args) {Score score=new Score("數學","aa","優秀"); //是String類型的Integer number=(Integer) score.getValue();//獲取成績需要進行強制類型轉換,雖然并不是一開始的類型,但是編譯不會報錯}
由于是Object類型,所以并不能直接判斷存儲的到底是String還是Integer,取值只能進行強制類型轉換,顯然無法在編譯期確定類型是否安全,項目中代碼量非常大,進行類型比較又會導致額外的開銷和增加代碼量,如果不比較又容易出現類型轉換異常,代碼的健壯性有所欠缺。
為了解決以上問題,JDK5新增了泛型,它能夠在編譯階段檢查類型安全,大大提升開發效率。
泛型類
定義泛型類:
public class Score<T> { //泛型類需要使用<>,在里面添加1-N個類型變量String name;String id;T value; //T會根據使用時提供的類型自動變成對應類型public Score(String name,String id,T value){ //這里的T可以是任何類型,但是一旦確定就不能修改了this.name=name;this.id=id;this.value=value;}
}
public static void main(String[] args) {Score<String> score=new Score<>("數學","aa","優秀");//使用時跟上<>并在其中填寫明確要使用的類型}
泛型將數據類型控制在了編譯階段, 在編寫代碼時就能明確泛型的類型,類型不符合將無法編譯通過。
1、因為是具體使用對象時才會明確具體類型,所以說靜態方法中不能用。
2、方法中使用待確定類型的變量時,因為不明確類型則會默認這個變量是一個Object類型的變量(即不能使用String等類型中的方法)。可對其進行強制類型轉換但沒必要。
3、不能通過這個不確定的類型變量直接創建對象和對應的數組。
4、具體類型不同的泛型類變量,不能使用不同的變量進行接收。
5、如果要讓某個變量支持引用確定了任意類型的泛型,可以使用?通配符。?
public static void main(String[] args) {Score<String> score=new Score<>("數學","aa","優秀");Score<?>score1=score;}
?如果使用通配符,由于類型不確定,所以說具體類型同樣會變成Object。
6、泛型變量可以定義多個,多個類型變量用,隔開。在使用時需要將這三種類型都進行明確指令。
7、泛型只能確定為一個引用類型,不支持基本類型。
要存放基本數據類型的值,我們只能使用對應的包裝類。
如果是基本類型的數組,因為數組本身是引用類型,所以是可以的。
泛型與多態
不只是類,包括接口、抽象類都可以支持泛型:
public static void main(String[] args) {Score<String> score=new Score<>("數學","aa","優秀");Score<?>score1=score;}
當子類實現此接口時,我們可以選擇在實現類明確泛型類型:
public static void main(String[] args) {A a=new A();Integer i=a.study();}static class A implements Study<Integer>{
//在實現接口或是繼承父類時,如果子類是一個普通類,那么可以直接明確對應類型@Overridepublic Integer study() {return 0;}}
也可以繼續使用泛型:
public static void main(String[] args) {A<Integer> a=new A<>();Integer i=a.study();}static class A<T> implements Study<T> {
//讓子類繼續為一個泛型類,那么可以不明確@Overridepublic T study() {return null;}}
?繼承:
static class A<T>{}static class B extends A<String>{}
?
泛型方法
泛型變量不僅僅在泛型類中使用,也可以定義泛型方法。
當某個方法(無論是靜態方法還是成員方法)需要接受的參數類型不確定時,我們可以使用泛型來表示:
public static void main(String[] args) {String str=test("10");}public static <T>T test(T t){ //在返回值類型前添加<>并填寫泛型變量表示這是一個泛型方法return t;}
泛型方法會在使用時自動確定泛型類型,比如我們定義的是類型T作為參數,同樣的類型T作為返回值,實際傳入的參數是一個字符串類型的值,那么T就會自動變成String類型,因此返回值也是String類型。
泛型方法在很多工具類中也有,比如說Arrays的排序方法:
public static void main(String[] args) {Integer[] arr = {1, 3, 2, 7, 4, 9, 0};//不能比較基本數據類型intArrays.sort(arr, new Comparator<Integer>() {//通過創建泛型接口的匿名內部類,來自定義排序規則,因為匿名內部類就是接口的實現類,所以這里就明確了類型@Overridepublic int compare(Integer o1, Integer o2) { //這個方法會在執行排序時被調用(別人調用我們的實現)//想要讓數據從大到小排列:return o2-o1;//compare方法要求返回一個int來表示兩個數的大小關系,大于0表示大于,小于0表示小于//如果o2比o1大,那么應該排在前面,所以說返回正數表示大于}});System.out.println(Arrays.toString(arr));}
可替換為Lambda表達式:
public static void main(String[] args) {Integer[] arr = {1, 3, 2, 7, 4, 9, 0};Arrays.sort(arr, (o1, o2) -> o2-o1);System.out.println(Arrays.toString(arr));}
泛型的界限
若現在沒有String類型的成績了,但是成績依然可能是整數或小數,我們不希望將泛型指定為除數字類型外的其他類型,就需要使用到泛型的上界定義。
只需要在泛型變量的后面添加extends關鍵字即可指定上界:
public class Score<T extends Number> { //設定類型參數上界,必須是Number或Number的子類String name;String id;T value;public Score(String name,String id,T value){this.name=name;this.id=id;this.value=value;}public T getValue() {return value;}
}
泛型通配符也支持泛型的界限:
public static void main(String[] args) {Score<? extends Integer>score=new Score<>("xm","11",10);}
下界只適用于通配符,對于類型變量來說是不支持的。
public static void main(String[] args) {Score<? super Object>score=new Score<>("xm","11",10);}
entends定義的只能存放它自己及其子類,super定義的只能存放它自己及其父類。
限定上界后使用這個對象的泛型成員:
public static void main(String[] args) {Score<? extends Number>score=new Score<>("xm","11",10);Number o=score.getValue(); //此時雖然使用的是通配符,但是不再是Object類型,而是對應的上界}
?限定下界的話,因為還有可能是Object,所以說依然和之前一樣:
public static void main(String[] args) {Score<? super Number>score=new Score<>("xm","11",10);Object o=score.getValue();}
?
類型擦除
實際上在Java中并不是真的有泛型類型,因為所有的對象都是一個普通的類型,一個泛型類型編譯之后,實際上會直接使用默認的類型。
在編譯的過程當中,將所有的T替換為Object這種機制,我們稱為:擦除機制。
如果我們給類型變量設定了上界,那么會從默認類型變成上界定義的類型。
泛型其實僅僅是在編譯階段進行類型檢查,當程序在運行時,并不會真的去檢查對應類型,所以哪怕我們不指定類型也可以使用。
擦除機制其實就是為了方便使用后面集合類(否則每次都要強制類型轉換)同時為了向下兼容采取的方案,因此泛型的使用會有一些限制:
首先,在進行類型判斷時,不允許使用泛型,只能使用原始類型:
public static void main(String[] args) {Test<String> test =new Test<>();System.out.println(test instanceof Test);}
其次,泛型不支持創建參數化類型數組的:
只不過只是把它當做泛型類型的數組還是可以用的:
函數式接口
@FunctionalInterface 函數式接口都會打上這樣的注解
滿足Lambda表達式的需求有且僅有一個需要去實現(未實現)的方法。
函數式接口就是JDK1.8專門提供好的用于Lambda表達式的接口,這些接口都可以直接使用Lambda表達式。以下主要介紹四個主要的函數式接口:
Supplier供給型函數式接口:
這個接口是專門用于供給使用的,其中只有一個get方法用于獲取需要的對象。
@FunctionalInterface
public interface Supplier<T> {/*** Gets a result.** @return a result*/T get(); //實現此方法,實現供給功能
}
public static void main(String[] args) {Supplier<Student> studentSupplier= Student::new;studentSupplier.get().hello();}public static class Student{public void hello(){System.out.println("我是學生");}}
Consumer消費型函數式接口:
這個接口專門用于消費某個對象。
@FunctionalInterface
public interface Consumer<T> {void accept(T t); //這個方法用于消費,沒有返回值default Consumer<T> andThen(Consumer<? super T> after) { //默認實現,這個方法便于我們連續使用此消費接口Objects.requireNonNull(after);return (T t) -> { accept(t); after.accept(t); };}
}
public class Main {
//專門消費Student對象的Consumerprivate static final Consumer<Student> STUDENT_COMPARATOR=student->System.out.println(student+"看不懂");public static void main(String[] args) {Student student=new Student();STUDENT_COMPARATOR.accept(student);}public static class Student{public void hello(){System.out.println("我是學生");}}
}
public static void main(String[] args) {Student student=new Student();STUDENT_COMPARATOR //可以使用andThen方法繼續調用,將消費之后的操作以同樣的方式預定好.andThen(student1 -> System.out.println("后續操作")).accept(student);}//輸出
com.test.Main$Student@404b9385看不懂
后續操作
Function函數型函數式接口:
這個接口消費一個對象,然后會向外供給一個對象(前兩個的融合體)
@FunctionalInterface
public interface Function<T, R> {R apply(T t); //這里一共有兩個類型參數,其中一個是接受的參數類型,另一個是返回的結果類型default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {Objects.requireNonNull(before);return (V v) -> apply(before.apply(v));}default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {Objects.requireNonNull(after);return (T t) -> after.apply(apply(t));}static <T> Function<T, T> identity() {return t -> t;}
}
apply方法:?
//這里實現了一個簡單的功能,將傳入的int參數轉換為字符串的形式private static final Function<Integer,String>INTEGER_STRING_FUNCTION=Objects::toString;
//Integer輸入,String輸出?public static void main(String[] args) {String str=INTEGER_STRING_FUNCTION.apply(10);System.out.println(str);}
使用compose將指定函數式的結果作為當前函數式的實參:(compose是前置工作)
public static void main(String[] args) {String str=INTEGER_STRING_FUNCTION.compose((String s)->s.length()) //將此函數式的返回值作為當前實現的實參.apply("aaa"); //傳入上面函數式需要的參數System.out.println(str);//String ->Integer ->String// aaa -> 3 -> "3"}//輸出3
andThen可以將當前實現的返回值進行進一步的處理,得到其他類型的值:(后續工作)
public static void main(String[] args) {Boolean str=INTEGER_STRING_FUNCTION.andThen(String::isEmpty) //在執行完后,返回值作為參數執行andThen內的函數式,最后得到的結果就是最終的結果了.apply(10);System.out.println(str);}
//輸出false
還提供了一個將傳入參數原樣返回的實現:
public static void main(String[] args) {Function<String,String>function=Function.identity();System.out.println(function.apply("aaaa"));}
//輸出aaaa
Predicate斷言式函數式接口:
接收一個參數,然后進行自定義并返回一個boolean結果。
@FunctionalInterface
public interface Predicate<T> {boolean test(T t); //要實現的方法default Predicate<T> and(Predicate<? super T> other) {Objects.requireNonNull(other);return (t) -> test(t) && other.test(t);}default Predicate<T> negate() {return (t) -> !test(t);}default Predicate<T> or(Predicate<? super T> other) {Objects.requireNonNull(other);return (t) -> test(t) || other.test(t);}static <T> Predicate<T> isEqual(Object targetRef) {return (null == targetRef)? Objects::isNull: object -> targetRef.equals(object);}
}
import java.util.function.Predicate;public class Main {private static final Predicate<Student> STUDENT_PREDICATE=student -> student.score>=60;public static void main(String[] args) {Student student=new Student();student.score=80;if (STUDENT_PREDICATE.test(student)){ //test方法的返回值是一個boolean結果System.out.println("及格了");}else {System.out.println("不及格");}}public static class Student{int score=100;public void hello(){System.out.println("我是學生");}}
}
判空包裝
判空包裝類Optional,這個類可以很有效的處理空指針問題。
public static void main(String[] args) {test(null);}public static void test(String str){ //傳入字符串,如果不是空串就打印長度if(str == null)return; //沒有這句若傳入null會出現空指針異常錯誤if(!str.isEmpty()){System.out.println("長度為"+str.length());}}
用Optional類處理上述問題:
public static void test(String str){Optional.ofNullable(str) //將傳入的對象包裝進Optional中.ifPresent(s -> System.out.println("長度為"+s.length()));//如果不為空(ifPresent)則執行這里的Consumer實現}
其他的一些使用:
//不為空輸出字符串,為空...public static void test(String str){String s1=Optional.ofNullable(str).orElse("為null的備選情況");System.out.println(s1);}
//將包裝的類型直接轉換為另一種類型public static void main(String[] args) {test("aaaaa");}public static void test(String str){Integer i=Optional.ofNullable(str).map(s -> s.length()).get();System.out.println(i);}
//輸出5
......