六、泛型——4-泛型方法

4-泛型方法

1.定义泛型方法

(1)泛型方法的方法签名比普通方法的方法签名多了泛型声明,其格式如下:

  修饰符 <T,s> 返回值类型 方法名(形参列表){

    //...方法体

  }

(2)泛型方法的简单使用:

import java.util.ArrayList;
import java.util.Collection;

public class Test4 {
    //声明一个泛型方法,在该方法中携带一个泛型参数T
    public static <T> void test(T[] a,Collection<T> c){
        for(T o : a){
            c.add(o);
        }
    }
    
    public static void main(String[] args) {
        Object[] oa = new Object[100];
        Collection<Object> co = new ArrayList<>();
        //T代表Object类型
        test(oa,co);
        
        String[] sa = new String[100];
        Collection<String> cs = new ArrayList<>();
        //T代表String
        test(sa,cs);
        
        Integer[] ia = new Integer[100];
        Float[] fa = new Float[100];
        Number[] na = new Number[100];
        
        Collection<Number> cn = new ArrayList<>();
        
        //T代表Number类型
        test(ia,cn);
        //T代表Number类型
        test(fa,cn);
        //T代表Number类型
        test(na,cn);
        
        //T代表Object类型
        test(na,co);
        
        //T代表String类型,但na是个Number数组
        //因为Number既不是String类型,也不是它的子类
        //所以出现编译错误
//        test(na,cs);
    }
}

(3)在泛型方法中定义的泛型参数只在方法中有效,在类、接口中声明的泛型参数则可以在整个接口、类中使用;

(4)方法中的泛型参数无须显式的传入实际类型参数,因为编译器根据实参来推断类型形参的值,他通常推断出最直接的类型参数。

2.泛型方法和类型通配符的区别

(1)使用情况:

  1)虽然在泛型方法和类型通配符之间,两者可以相互转换,如下方法

    使用通配符:public void test(List<?> list){...}

    使用泛型方法:public <T> void test(List<T> list){...}

  虽然两者效果相同,但是如上情况选择使用通配符,因为参数单一,和返回值或其他参数并没有依赖的关系,使用通配符更加清晰和精准;

  2)当参数和返回值或者其他参数具有类型的依赖关系时,选择泛型方法可以使逻辑更加清晰

    使用通配符:

    public interface MyInterface<E>{

      public void test(List<? extends E>);

    }

    使用泛型方法:

    public interface MyInterface<E>{

      public <T extends E> void test(List<T>);

    }

(2)类型通配符既可以在方法签名中定义形参类型,也可以用于定义变量的类型;但泛型方法中的类型形参必须在对应方法中显式声明。

3.泛型构造器

(1)Java允许在构造器签名中声明类型形参,这就是泛型构造器;

(2)只要定义了泛型构造器,在调用构造器时不仅可以让Java根据数据参数的类型来“推断”类型形参的类型,而且也可以显式的指定构造器中类型形参的实际类型:

class Foo{
    //为构造器的参数指定泛型
    public <T> Foo (T t){
        System.out.println(t);
    }
}

public class Test5 {
    public static void main(String[] args) {
        //隐式指定构造器中参数类型
        new Foo("String");    //指定T为String类型
        new Foo(100);    //指定类型为Integer类型
        
        //显式指定构造器中参数类型
        new <String> Foo ("String");    //指定T为String类型
        new <Double> Foo (12.5);    //指定T为Double类型
        
        //指定类型与实际类型不符合的错误
        //new <Integer> Foo("String");
    }
}

 

(3)如果程序显式的指定了泛型构造器的参数类型,则不可以使用“菱形”语法:

class MyClazz<E>{
    //为构造器参数指定泛型
    public <T> MyClazz(T t){
        System.out.println(t);
    }
}
public class Test6 {
    public static void main(String[] args) {
        //MyClazz类声明中的E形参是String类型
        //泛型构造器中声明的T则是Integer类型
        //隐式指定了泛型构造器的参数类型
        //可以使用菱形语法
        MyClazz<String> ms = new MyClazz<>(10);
        
        //显式指定泛型构造器中的参数类型为Integer
        //则不可以使用菱形语法
        MyClazz<String> ms2 = new <Integer> MyClazz<String>(3);
        
        //当显式指定泛型构造器中的参数类型时,再使用菱形语法则出现错误
        //MyClazz<String> ms3 = new <Integer> MyClazz<>(3);
    }
}

 

 

4.设定通配符下限

  通配符的上限<? extends T>:表示?为T本身或者T的子类;

  通配符的下限<? super T>:表示?为T本身或者T的父类;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class MyUtil {
    //下面的dest集合元素的类型必须是src集合元素类型的父类,或者是src集合元素本身
    public static <T> T copy(Collection<? super T> dest,Collection<T> src){
        T last = null;
        for(T ele : src){
            last = ele;
            dest.add(ele);
        }
        return last;
    }
    
    public static void main(String[] args) {
        List<Number> ln = new ArrayList<>();
        List<Integer> li = new ArrayList<>();
        li.add(5);
        
        //此处可准确的知道最后一个被复制的元素是Integer类型
        //与src集合元素的类型相同
        Integer last = copy(ln, li);
        System.out.println(ln);
    }
}

 

5.泛型方法和方法重载

  因为泛型既可以设定通配符的上限,也可以设定通配符下限,所以允许在一个类中包含如下两个方法定义:

  public class MyUtil{

    public static <T> void copy(Collection<T> dest,Collection<? extends T> src){

      //...

    }  //方法1

    public static void copy(Collection<? extends T> dest,Collection<T> src){

      //...

    }  //方法2

  }

  如果只是在类中定义这两个方法是没有任何错误的,但只要调用这个方法就会引起编译错误:

  List<Number> li = new ArrayList<>();

  List<Integer> ln = new ArrayList<>();

  copy(ln,li);

  因为编译器无法确定到底调用的是哪一个copy()方法。

6.类型推断

(1)类型推断主要有两个方面:

  1)通过调用方法的上下文来推断类型参数的目标类型;

  2)可在方法调用链中,将推断得到的类型参数传递到最后一个方法;

(2)类型推断简单例子:

class MyUtil2<E>{
    public static <Z> MyUtil2<Z> nil(){
        return null;
    }
    public static <Z> MyUtil2<Z> cons(Z head,MyUtil2<Z> tail){
        return null;
    }
    E head(){
        return null;
    }    
}
public class InterfaceTest<E> {
    public static void main(String[] args) {
        //可通过方法赋值的目标参数来推断类型参数为String
        MyUtil2<String> ls = MyUtil2.nil();
        //无须使用下面的语句在调用nil()时指定类慈宁宫参数的类型
        MyUtil2<String> ms = MyUtil2.<String>nil();
        
        //可调用cons()方法所需参数类型来推断类型为Integer
        MyUtil2.cons(42, MyUtil2.nil());
        //无须使用下面语句在调用nil()方法时指定类型参数类型
        MyUtil2.cons(42,MyUtil2.<Integer>nil());
    }
}

 

 

 

posted @ 2017-08-02 20:13  丶theDawn  阅读(248)  评论(0编辑  收藏  举报