Java泛型

泛型基础

泛型规范

T1,T2,T3...泛型可以随便写吗,可以随便写,但我们追求规范。

-E — Element,常用在java Collection里,如:List<E>,Iterator<E>,Set<E>
- K,V — Key,Value,代表Map的键值对
- N — Number,数字
- T — Type,类型,如String, Integer等等

泛型使用方式

泛型类

我们首先定义一个简单的Box类:

public class Box {
    private String object;
    public void set(String object) { this.object = object; }
    public String get() { return object; }
}

这是最常见的做法,这样做的一个坏处是Box里面现在只能装入String类型的元素,今后如果我们需要装入Integer等其他类型的元素,还必须要另外重写一个Box,代码得不到复用,使用泛型可以很好的解决这个问题。

public class Box<T> {
    // T stands for "Type"
    private T t;
    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

这样我们的Box类便可以得到复用,我们可以将T替换成任何我们想要的类型:

Box<Integer> integerBox = new Box<Integer>();
Box<Double> doubleBox = new Box<Double>();
Box<String> stringBox = new Box<String>();
  • 多泛型变量定义
public class ObjClass<T,U> 

泛型接口

JDK1.5之后,不仅可以声明泛型类,也可以声明泛型接口,与声明泛型类基本相似,在接口后面加上<T>,如下所示:

[访问权限public]interface 接口名称<泛型标识T,也可以和类一样有多个泛型变量T,U>{}

在接口上定义泛型与在类中定义泛型是一样的,代码如下:

interface Information<T> {
    public T getInfo() ;
}
泛型接口的两种实现方式

定义子类:在子类的定义上也声明泛型类型

interface Information<T> {
    public T getMsg() ;
}
class InfoImpl<T> implements Information<T> {
    private T msg;
    public T getMsg() {
        return msg;
    }

    public static void main(String arsg[]){  
        Information<String> info = null;        // 声明接口对象  
        info = new InfoImpl<String>("李兴华") ; // 通过子类实例化对象  
        System.out.println("内容:" + info.getMsg()) ;  
    } 
}

如果现在实现接口的子类不想使用泛型声明,则在实现接口的时候直接指定好其具体的操作类型即可:

interface Information<T> {
    public T getMsg() ;
}
class InfoImpl implements Information<String> {
    private String msg;
    public String getMsg() {
        return msg;
    }

    public static void main(String arsg[]){  
        Information info = null;        // 声明接口对象  
        info = new InfoImpl("李兴华") ; // 通过子类实例化对象  
        System.out.println("内容:" + info.getMsg()) ;  
    } 
}

泛型方法

static方法
  • 对于static方法而言,无法访问泛型类的类型参数,因此,如果static方法需要使用泛型,则必须使其成为泛型方法。
  • 如果你定义了一个泛型(类、接口),那么Java规定,你不能在所有的静态方法、静态初块等所有静态内容中使用泛型的类型参数!!例如:
class A<T> {  
    public static void func(T t) { // 错误!在所有静态内容中不得使用泛型的类型参数 

    }  
}
定义泛型方法
  • 1 格式:修饰符 <类型参数列表> 返回类型 方法名(形参列表) { 方法体 }

    • 例如:public static <T, S> int func(List<T> list, Map<int, S> map) { ... } // 就表示,在我func这个方法的作用域中,T和S是我的泛型类型参数
  • 2 作用域
    比如class A<T> { ... }T的作用域就是整个A,而public <T> func(...) { ... }T的作用域就是方法func

class A<T> { // A已经是一个泛型类,其类型参数是T  
    public static <T> void func(T t) { // 再在其中定义一个泛型方法,该方法的类型参数也是T  

    }  
} 

当上述两个类型参数冲突时,在方法中,方法的T会覆盖类的T,即和普通变量的作用域一样,内部覆盖外部,外部的同名变量是不可见的!

  • 3 泛型方法的类型参数也可以指定上限
    • 类型上限必须在类型参数声明的地方定义上限!不能在方法参数中定义上限
    • 例如:
    • a. <T extends Xxx> void func(List<T> list); // 正确
    • b. <T extends Xxx> void func(T t); // 必然正确
    • c. <T> void func(List<T extends Xxx> list); // 编译错误
    • 规定了上限就只能在规定范围内指定类型实参,超出这个范围就会直接编译报错!
  • 4 调用泛型方法

    • 显式指定方法的类型参数,类型参数要写在尖括号中并放在方法名之前,例如:obj.<String>func(...);
    • 隐式地自动推断:那就是不指明泛型参数,让编译器根据传入的实参类型来自动推断类型参数是什么;

      • a. 最简单的例如:<T> void func(T t); 这样调用的话,obj.func("lala"); // 那么就会根据”lala”的类型String推断出类型参数T的类型是String
      • b. 但是一定要避免歧义,例如:<T> void func(T t1, T t2); 如果这样调用的话,obj.func("lala", 15); 虽然编译不会报错,但是仍然会有很大隐患,T到底应该是String还是Integer存在歧义
      • c. 但是有些歧义Java是会直接当成编译错误的,即所有和泛型参数有关的歧义,例如:<T> void func(List<T> l1, List<T> l2); 如果这样调用的话,obj.func(new List<String>(), new List<Integer>()); 这里当然会有歧义,编译器无法知道T到底应该是String还是Integer,但是这种歧义会直接报错的!!编译都无法通过;

      即泛型要比普通类型的检查要严很多,很多在普通类型上行得通的擦边球,在泛型上都无法通过!

  • 5 完整例子

public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}
public class Pair<K, V> {
    private K key;
    private V value;
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey()   { return key; }
    public V getValue() { return value; }
}

我们可以像下面这样去调用泛型方法:

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);

或者在Java1.7/1.8利用type inference,让Java自动推导出相应的类型参数:

Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.compare(p1, p2);

通配符VS泛型方法

<?> 无限制通配符
<? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类;
<? super E> super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类

’PECS总结
+ Producer Extends - 如果你需要一个只读List,用它来produce T,那么使用? extends T
+ Consumer Super - 如果你需要一个只写List,用它来consume T,那么使用? super T
+ 如果需要同时读取以及写入,那么我们就不能使用通配符了。

详细解释见 《Java泛型 通配符》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值