5.2 自定义泛型方法
附注:如何在IDEA中找出一个类的所有子类?
六、类型变量的上限
我们在声明<T>等类型变量时,可以给它限定“上限”。
语法格式:
<T extends 上限> : 表示T的类型必须是<=上限,即要么是上限本身,要是继承上限类或实现了上限接口的类型。
举例如下:
学生类,包含姓名和成绩。
现在成绩类型不确定,但是要求成绩必须是Number或Number的子类对象。
java.Lang.Number表示数值类型,例如它的子类有。
代码如下:
在上面的案例需求中再加一个要求,成绩不仅要求是Number类或它的子类,还要求实现Comparable接口
一个类型变量的上限可以是1个,也可以是多个。但是要求如果有多个的话,类只能有一个,其他的都是接口类型。而且类在左边,其他的类型在右边。
如果自定义的方法实现了类型变量的下限,那么传参给方法的实参中的类也必须实现该接口。此举意在编译前提醒检测类型变量的类型转换问题
6.1 定义泛型类的类型变量时指定上限
代码举例如下:
6.2 定义泛型方法的类型变量时指定上限
编写一个数组工具类,包含可以给任意对象数组进行从小到大排序,调用元素对象的compareTo方法比较元素的大小关系。要求数组的元素类型必须是java.lang.Comparable<T>接口类型。
代码举例如下:
public class MyArrays { public static <T extends Comparable<T>> void sort(T[] arr){ for (int i = 1; i < arr.length ; i++) { for (int j = 0; j < arr.length-i ; j++) { if (((Comparable)(arr[j])).compareTo(arr[j+1])>0){ T temp=arr[j]; arr[j]=arr[j+1]; arr[j+1]=temp; } } } } }
import org.junit.Test; public class TestArrays { @Test public void test01(){ Integer[] nums={4,7,1,2,89,34,67}; System.out.println("排序前:"); for (Integer num : nums) { System.out.print(num+"\t"); } System.out.println(); MyArrays.sort(nums); System.out.println("排序后:"); for (Integer num : nums) { System.out.print(num+"\t"); } } }
运行效果:
七、泛型擦除
如果用户在使用泛型类或泛型接口时,没有主动指定泛型的类型,就会发生泛型的擦除。
泛型擦除后,类型变量按照哪个类型处理? 是统一按照object处理吗?
不是
答案:泛型擦除后,自动按照类型变量声明时的第一个上限处理,如果类型变量上面时没有指定上限,那么按照Object处理
代码举例如下:
八、泛型通配符
8.1 为什么要使用泛型通配符?
当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Comparator<T>类型,但是我们仍然无法确定这个泛型类或泛型接口的类型变量<T>的具体类型,此时我们考虑使用类型通配符 ?
代码举例如下:
import java.util.ArrayList; import java.util.Collection; public class TestWildcard { public static void m4(Collection<?> coll){ for (Object o : coll) { System.out.println(o); } } public static void main(String[] args) { //右边泛型指定为任意类型或不指定都可以 m4(new ArrayList<Object>());//Collection<?> coll = new ArrayList<Object>(); m4(new ArrayList<>());//Collection<?> coll = new ArrayList<>(); m4(new ArrayList());//Collection<?> coll = new ArrayList(); m4(new ArrayList<String>());//Collection<?> coll = new ArrayList<String>(); } }
在Java中,泛型的指定是必须左右两边一致的
8.2 类型通配符的三种使用形式
- <?>:完整形式为:类名<?> 或接口名<?>,此时?代表任意类型。
- <? extends 上限>:完整形式为:类名<? extends 上限类型> 或接口名<? extends 上限类型>,此时?代表上限类型本身或者上限的子类,即?代表 <= 上限的类型。
- <? super 下限>:完整形式为:类名<? super 下限类型> 或接口名<? super 下限类型>,此时?代表下限类型本身或者下限的父类,即?代表>= 下限的类型。
案例:
声明一个集合工具类MyCollections,要求包含:
- public static boolean different(Collection<?> c1, Collection<?> c2):比较两个Collection集合,此时两个Collection集合的泛型可以是任意类型,如果两个集合中没有相同的元素,则返回true,否则返回false。
- public static <T> void addAll(Collection<? super T> c1, T... args):可以将任意类型的多个对象添加到一个Collection集合中,此时要求Collection集合的泛型指定必须>=元素类型。
- public static <T> void copy(Collection<? super T> dest,Collection<? extends T> src):可以将一个Collection集合的元素复制到另一个Collection集合中,此时要求原Collection泛型的类型<=目标Collection的泛型类型。
代码演示如下:
import java.util.Collection; public class MyCollections { public static boolean different(Collection<?> c1, Collection<?> c2){ return c1.containsAll(c2) && c2.containsAll(c1); } public static <T> void addAll(Collection<? super T> c1, T... args){ for (int i = 0; i < args.length; i++) { c1.add(args[i]); } } public static <T> void copy(Collection<? super T> dest,Collection<? extends T> src){ for (T t : src) { dest.add(t); } } }
public class MyCollectionsTest { public static void main(String[] args) { Collection<Integer> c1 = new ArrayList<Integer>(); MyCollections.addAll(c1,1,2,3,4,5); System.out.println("c1 = " + c1); Collection<String> c2 = new ArrayList<String>(); MyCollections.addAll(c2,"hello","java","world"); System.out.println("c2 = " + c2); System.out.println("c1 != c2 " + MyCollections.different(c1, c2)); Collection<Object> c3 = new ArrayList<>(); MyCollections.copy(c3,c1); MyCollections.copy(c3,c2); System.out.println("c3 = " + c3); } }
运行效果如下:
8.3 使用类型通配符来指定类型参数的问题
(1)如果把"泛型类<T>"指定为"泛型类<?>";那么该泛型类中所有参数是T类型的方法或成员都无法正常使用。参数类型不是T类型的方法照常使用。
代码举例如下:
import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; public class TestProblem { @Test public void test01(){ Collection<?> coll = new ArrayList<>(); // coll.add("hello"); // coll.add(1); // coll.add(1.0); /* 上面所有添加操作都报错。 为什么? 因为<?>表示未知的类型,集合的元素是不确定的,那么添加任意类型对象都有风险。 void add(E t)方法无法正常使用 因为此时E由?表示,即表示直到add方法被调用时,E的类型仍然不确定,所以该方法无法正常使用 */ Collection<?> coll2 = Arrays.asList("hello","java","world"); for (Object o : coll2) { System.out.println(o); } } }
(2)如果把"泛型类<T>"指定为"泛型类<? extends 上限>":那么该泛型类中所有参数是T类型的方法或成员都无法正常使用。参数类型不是T类型的方法照常使用。
代码举例如下:
个人理解:如果把"泛型类<T>"指定为"泛型类<? extends 上限>",之所以无法正常使用,是因为
Java编译器在编译时无法确定添加到集合coll中的元素的类型是否<= Number,泛型类<? extends 上限> 中只说了要<=Number[上限],Number类以下的部分或者说它的子类,编译器是不知道的。
(3)如果把"泛型类<Tdd>"指定为"泛型类<? super 下限>":那么该泛型类中所有参数是T类型的方法或成员都可以使用,但是有要求。参数类型不是T类型的方法照常使用。