基本概念 泛型顾名思义就是广泛的类型。这里的类型可以是基本数据类型的包装类(但不能是基本数据类型),可以是引用数据类型。但如果是基本类型的数组,因为数组本身是引用类型。泛型在定义时并不明确是什么类型,而是需要到使用时才会确定对应的泛型类型。泛型就是定义一种模板,例如ArrayList,然后在代码中为用到的类创建对应的ArrayList<类型>
泛型将数据类型的确定控制在了编译阶段,在编写代码的时候就能明确泛型的类型,如果类型不符合,将无法通过编译!编写泛型时,需要定义泛型类型;静态方法不能引用泛型类型,必须定义其他类型(例如)来实现静态泛型方法;泛型可以同时定义多种类型,例如Map<K, V>。
Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”
基本使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Main { public static void main (String[] args) throws CloneNotSupportedException { Score<String> score = new Score<String>("计算机网络" , "EP074512" , "优秀" ); String value = score.value; System.out.println(value); } }class Score <T > { String name; String id; T value; Score(String name, String id, T value) { this .name = name; this .id = id; this .value = value; } }
泛型通配符案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public class Main { public static void main (String[] args) throws CloneNotSupportedException { Generic<Integer> intGeneric = new Generic<Integer>(123 ); Generic<Number> numGeneric = new Generic<Number>(456 ); showKeyValue(intGeneric); showKeyValue(numGeneric); Notepad<String,Integer> t = null ; t = new Notepad<String,Integer>() ; t.setKey("汤姆" ) ; t.setValue(20 ) ; System.out.print("姓名;" + t.getKey()) ; System.out.print(",年龄;" + t.getValue()) ; } static void showKeyValue (Generic<?> obj) { System.out.println("key value is " + obj.getKey()); } }class Notepad <K ,V > { private K key ; private V value ; public K getKey () { return this .key ; } public V getValue () { return this .value ; } public void setKey (K key) { this .key = key ; } public void setValue (V value) { this .value = value ; } }class Generic <T > { private T key; public Generic (T key) { this .key = key; } public T getKey () { return key; } }
通配符区别 ? 表示不确定的 java 类型 T (type) 表示具体的一个java类型 K V (key value) 分别代表java键值中的Key Value E (element) 代表Element T和?的区别 1.T是一个确定的类型,通常用于泛型类和泛型方法的定义,?是一个不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
1 2 3 4 5 T t = operate(); ? car = operate();
2.通过T来确保泛型参数的一致性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class Main { public static void main (String[] args) throws CloneNotSupportedException { Generic<Integer> intGeneric = new Generic<Integer>(123 ); Generic<Number> numGeneric = new Generic<Number>(456 ); showKeyValueAny(intGeneric,numGeneric); } static <T extends Number> void showKeyValueT (Generic<T> dest, Generic<T> src) { System.out.println("dest key value is " + dest.getKey()); System.out.println("src key value is " + src.getKey()); } static void showKeyValueAny (Generic<? extends Number> dest, Generic<? extends Number> src) { System.out.println("dest key value is " + dest.getKey()); System.out.println("src key value is " + src.getKey()); } }class Generic <T > { private T key; public Generic (T key) { this .key = key; } public T getKey () { return key; } }
3.类型参数T可以多重限定而通配符不行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Main { public static void main (String[] args) throws CloneNotSupportedException { System.out.println(maxMum(1 ,2 ,3 )); System.out.println(maxMum(1.1 ,2.2 ,3.3 )); } public static <T extends Number & Comparable<T>> T maxMum (T x, T y, T z) { T max = x; if (y.compareTo(max) > 0 ) { max = y; } if (z.compareTo(max) > 0 ) { max = z; } return max; } }
4.通配符可以使用超类限定而类型参数不行
类型参数T只具有 一种 类型限定方式: 但是通配符 ? 可以进行 两种限定: 泛型的界限 引入原因 为了解决泛型中隐含的转换问题,Java泛型加入了类型参数的上下边界机制。<? extends A>表示该类型参数可以是A(上边界)或者A的子类类型。编译时擦除到类型A,即用A类型代替类型参数。这种方法可以解决开始遇到的问题,编译器知道类型参数的范围,如果传入的实例类型B是在这个范围内的话允许转换,这时只要一次类型转换就可以了,运行时会把对象当做A的实例看待。
如果参数化类型表示一个 T 的生产者,使用 < ? extends T>; 如果它表示一个 T 的消费者,就使用 < ? super T>; 如果既是生产又是消费,那使用通配符就没什么意义了,因为你需要的是精确的参数类型。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class Main { public static void main (String[] args) throws CloneNotSupportedException { Score<? extends Integer> score = new Score<>("数据结构与算法" , "EP074512" , 60 ); Number o = score.getValue(); System.out.println(o); Score<? super Number> scoreDown = new Score<>("数据结构与算法基础" , "EP074512" , 10 ); Object o1 = scoreDown.getValue(); System.out.println(o1); } }class Score <T extends Number > { private final String name; private final String id; private final T value; public Score (String name, String id, T value) { this .name = name; this .id = id; this .value = value; } public T getValue () { return value; } }
类型擦除 泛型信息只存在于编译前,编译后的字节码中是不包含泛型中的类型信息的。因此,编译器在编译时去掉类型参数,叫做类型擦除。
擦拭法决定了泛型:
不能是基本类型,例如:int; 不能获取带泛型类型的Class,例如:Pair.class; 不能判断带泛型类型的类型,例如:x instanceof Pair; 不能实例化T类型,例如:new T()。 利用反射绕过类型擦除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.lang.reflect.Method;import java.util.ArrayList;import java.util.List;public class Main { public static void main (String[] args) throws CloneNotSupportedException { List<Integer> list = new ArrayList<>(); list.add(123 ); try { Method method = list.getClass().getDeclaredMethod("add" , Object.class); method.invoke(list, "string" ); method.invoke(list, true ); method.invoke(list, 45.6 ); } catch (Exception e) { e.printStackTrace(); } System.out.println(list); } }
泛型数组 部分反射API是泛型,例如:Class,Constructor;
可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型;
可以通过Array.newInstance(Class, int)创建T[]数组,需要强制转型;
同时使用泛型和可变参数时需要特别小心。
函数式接口 供给型函数式 这个接口是专门用于供给使用的,其中只有一个get方法用于获取需要的对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Main { private static final Supplier<Student> STUDENT_SUPPLIER = Student::new ; public static void main (String[] args) throws CloneNotSupportedException { Student student = STUDENT_SUPPLIER.get(); student.hello(); } }@FunctionalInterface interface Supplier <T > { T get () ; }class Student { public void hello () { System.out.println("我是学生!" ); } }
消费型函数式接口 这个接口专门用于消费某个对象的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import java.util.Objects;public class Main { private static final Consumer<Student> STUDENT_CONSUMER = student -> System.out.println(student+" 真好吃!" ); public static void main (String[] args) throws CloneNotSupportedException { Student student = new Student(); STUDENT_CONSUMER .andThen(stu -> System.out.println("我是吃完之后的操作!" )) .andThen(stu -> System.out.println("好了好了,吃饱了!" )) .accept(student); } }@FunctionalInterface 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); }; } }class Student { public void hello () { System.out.println("我是学生!" ); } }
函数型函数式接口 这个接口消费一个对象,然后会向外供给一个对象(前两个的融合体)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import java.util.Objects;public class Main { private static final Function<Integer, String> INTEGER_STRING_FUNCTION = Object::toString; public static void main (String[] args) throws CloneNotSupportedException { String str = INTEGER_STRING_FUNCTION .compose((String s) -> s.length()) .apply("lbwnb" ); System.out.println(str); Boolean strBol = INTEGER_STRING_FUNCTION .andThen(String::isEmpty) .apply(10 ); System.out.println(strBol); Function<String, String> function = Function.identity(); System.out.println(function.apply("俺老孙来也" )); } }@FunctionalInterface 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; } }
断言型函数式接口 接收一个参数,然后进行自定义判断并返回一个boolean结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import java.util.Objects;public class Main { private static final Predicate<Student> STUDENT_PREDICATE = student -> student.score >= 60 ; public static void main (String[] args) throws CloneNotSupportedException { Student student = new Student(); student.score = 80 ; if (STUDENT_PREDICATE.test(student)) { System.out.println("过了过了" ); } else { System.out.println("垃圾题,淦!" ); } student.score = 80 ; boolean flag = STUDENT_PREDICATE .and(stu -> stu.score > 90 ) .test(student); if (!flag) System.out.println("卷不过,卷不过" ); Predicate<String> predicate = Predicate.isEqual("Hello World" ); System.out.println(predicate.test("Hello World" )); } }@FunctionalInterface 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); } }class Student { public int score; }
判空包装 Java8还新增了一个非常重要的判空包装类Optional,这个类可以很有效的处理空指针问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import java.util.Optional;public class Main { public static void main(String [] args) throws CloneNotSupportedException { String value = "Hello,World" ; String nulValue = null ; isNull(value); isNull(nulValue); } static void isNull(String str ){ Integer i = Optional .ofNullable(str ) .map (String ::length) .orElse(-1 ); System.out.println (i); } }
参考文章 聊一聊-JAVA 泛型中的通配符 T,E,K,V,? Java全栈知识体系