泛型方法
1、引出泛型方法
需求:将Object[]中的元素,全部添加到Collection集合中
public class Demo {
public static void main(String[] args) {
String[] arr = {"hello","world"};
List<Object> list = new ArrayList<>(); //如果将参数类型Object改为String,Integer..等, 调用方法时都会出现编译错误
fromArrayToCollection(arr,list);
System.out.println(list); // [hello, world]
}
private static void fromArrayToCollection(Object[] arr, Collection<Object> coll) {
// 1.遍历数组
for(Object obj : arr){
// 2.将数组中的元素添加到集合中
coll.add(obj);
}
}
}
如上代码,看上去是没有问题,但是这个方法设计的挺局限的,因为只能去接受类型参数的类型是Object的,否则就是不行的,那怎么办呢?有人就会说用类型通配符来改进啊。好,我们来看看
1.1:迭代一
private static void fromArrayToCollection(Object[] arr, Collection<?> coll) {
for(Object obj : arr){
coll.add(obj); // 这时这里会出现编译错误,不能把对象放进一个未知类型的集合中去!
}
}
发现使用类型通配符,也并不能解决问题,我们分析一下,假如泛型的参数类型我传递什么,它就变成什么类型该多好,这时泛型方法就可以完美解决了!
1.2:迭代二
public class Demo {
public static void main(String[] args) {
String[] arr1 = {"hello","world"};
Integer[] arr2 = {10,20};
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
fromArrayToCollection(arr1,list1);
fromArrayToCollection(arr2,list2);
}
// 注意: T[] arr的类型,是根据Collection<T>,这个参数类型变化而变化的!!
private static <T> void fromArrayToCollection(T[] arr, Collection<T> coll) {
for(T t : arr){
coll.add(t);
}
}
}
这时将无论传递什么参数类型过来,泛型方法都可以接收,就没有将参数类型写死,比较灵活多变。
2、不要给泛型方法造成迷惑
public class Demo {
public static void main(String[] args) {
List<String> strList = new ArrayList<>();
List<Object> objList = new ArrayList<>();
List<Object> objList2 = new ArrayList<>();
/**
* 编译报错:因为你给系统制造了迷惑,方法中两个参数的,类型参数应该是一致的,
* 而你传的不一致,导致程序不知该使用哪个,导致编译错误!
*/
//test(strList, objList); // no
test2(strList, objList); // yes
test2(objList, objList2); // yes
}
private static <T> void test(Collection<T> c1, Collection<T> c2) {
for(T t : c1){
c2.add(t);
}
}
// 对test方法进行改进, ?<=T
public static <T> void test2(Collection<? extends T> c1, Collection<T> c2){
for(T t : c1){
c2.add(t);
}
}
}
3、泛型方法和类型通配符的区别?
大多数时候泛型方法都可以代替泛型通配符!如Jdk中的Collection接口
public interface Collection<E>{
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
}
//写成泛型方法的形式!
public interface Collection<E>{
boolean <T> containsAll(Collection<T> c);
boolean <T extends E> addAll(Collection<T> c);
}
1.那jdk中Collection接口中的containsAll() 和 addAll()方法为什么使用类型通配符而不是泛型方法呢
2.那什么时候使用泛型方法呢?
如果类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系这时就可以采用泛型方法,没有这样的依赖关系就不应该使用泛型方法!
- jdk中Collections.copy()方法,如果有需要,泛型方法会和泛型通配符一起使用!
public static <T> void copy(List<T> dest, List<? extends T> src)
注: 上面的dest和src有着明显的依赖关系,从源List集合复制出来的元素,必须可以丢进目标dest集合中,所以src集合中的元素只能是T,或者T的子类, 但是jdk它定义src的时候并没有用泛型方法,而是使用的类型通配符,这是因为该方法无需向src中添加元素(类型通配符的上限是不能随意添加的!,唯一可以添加的是null),所以就直接使用了泛型通配符!
- 将上面的copy(),用泛型方法的形式写一遍也是可以的!
public static <T, S extends T> void copy(List<T> dest, List<S> src)
4、设定通配符的下限(TreeSet构造方法)
需求:将集合A中的元素复制到集合B中去,还需返回最后一个被复制的元素
// src<=dest
public static <T> T copy(Collection<T> dest, Collection<? extends T> src){
T last = null;
for(T ele : src){
last = ele;
dest.add(ele);
}
return last;
}
@Test
public void testCopy(){
//需要:将集合A中的元素复制到集合B中去,还需返回最后一个被复制的元素
List<Number> dest = new ArrayList<>(Arrays.asList(1));
List<Integer> src = new ArrayList<>(Arrays.asList(2));
// 按道理最终返回的应该是Integer,而不是Number类型,这就是说在复制集合元素的过程中,丢失了src集合的元素类型
//IntegerlastEle = copy(dest, src); //编译错误
Number lastEle = copy(dest, src);
System.out.println(lastEle);
}
当遍历src的集合时,src的元素的类型是不确定的(但可以肯定它是T或者T的子类),所以程序只能用T来笼统的表示src集合中元素的类型!
// dest>=src
public static <T> T copy2(Collection<? super T> dest, Collection<T> src){
T last = null;
for(T ele:src){
last = ele;
dest.add(ele);
}
return last;
}
@Test
public void testCopy2(){
//需要:将集合A中的元素复制到集合B中去,还需返回最后一个被复制的元素
List<Number> dest = new ArrayList<>(Arrays.asList(1));
List<Integer> src = new ArrayList<>(Arrays.asList(2));
Integer ele = copy2(dest, src); //①
System.out.println(ele);
}
/**
* 总结:他是根据方法参数中<T>去决定的,<T>这个T是什么类型,最终返回值就是什么类型, 类型通配符<? extends T>也是根据T去变化的!
* 上面copy()方法中,dest形参对应的实参是List<Number>,所以最终返回值就是Number,(T的决定权在dest上)
* 而copy2()方法中,src形参对应的实参是 List<Integer>,所以最终返回值就是Integer,(T的决定权在src上)
*
* 其实上面方法的作用本质上是一样的,只是从不同的角度来描述了!!
* 例子:2比1大
* 从两个角度描述:
* a. 2比1大
* b. 1比2小
*/
使用通配符下限就可以推出①处调用后最后一个被复制的类型是Integer,而不是笼统的Number类型!
5、补充
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Apple<T> {
private T name;
private T color;
/**
* 只有在修饰符与返回值,加上<T,E,K,V>这些标识时,才会是一个泛型方法,
* 泛型方法声明的类型,与类<T>,不冲突!!
*
* 特点:
* 1.泛型方法还可以加静态! static
* 2.泛型方法的,多个类型形参: 可以代表任意类型!
* 3.泛型方法和可变参数! 那不是可以代替Object[]
*
* 总结:
* a.泛型方法能使方法独立于类而产生变化,
* b.如果static方法要使用泛型能力,那必须使其称为泛型方法!
* 就算Apple类,类型参数指定的是Integer类型
* Apple<Integer>{
*
* public <T> T getListElement(List<T> list){
* return list.get(new Random().nextInt(list.size()));
* }
* }
*/
public static <T> T getListElement(List<T> list){ //这里的<T>,跟类<T>,是不一样的,它俩互不影响!
return list.get(new Random().nextInt(list.size()));
}
//1.为泛型方法指定多个类型形参
public static <E,K,V> void printObj(E e, K k, V v){
System.out.println(e+"\t"+e.getClass().getSimpleName());
System.out.println(e+"\t"+k.getClass().getSimpleName());
System.out.println(e+"\t"+v.getClass().getSimpleName());
}
//2.泛型方法和可变参数
public static <T> void test(T... t){
for (int i=0; i<t.length; i++){
System.out.println(t[i]+"\t"+t[i].getClass().getSimpleName());
}
}
public static void testArray(Object[] obj){
for (int i=0; i<obj.length; i++){
System.out.println(obj[i]+"\t"+obj[i].getClass().getSimpleName());
}
}
//遍历所有List集合!,List<Object>并不是List<String>的父类!
//Object[]才是String[],要想表示所有List<T>,要用一个List<?> ?:类型通配符!!
public static void iterator(List<?> list){ //List<?> 不能添加(除了null)只能获取,因为不管获取到了什么它总是Object的子类
for(Object obj:list){
System.out.println(obj);
list.add(null);
}
}
public static void main(String[] args) {
Apple<Integer> apple = new Apple<>();
String str = apple.getListElement(Arrays.asList("1", "2", "3"));
System.out.println("元素:"+str +"\t"+"元素类型:"+str.getClass().getSimpleName());
//泛型方法可以加static修饰, 也就代表直接可以用类.方法
String s = Apple.getListElement(Arrays.asList("1", "2", "3"));
printObj("hello",1998,true);
Integer[] arr = {99,98,97};
test(arr);
System.out.println();
testArray(arr);
}
}