Java笔记 - Jack你的泛型那么厉害,Jason知道么

发布于 2014-01-21 | 更新于 2020-09-20

1、与C++的比较

Java中的泛型参考了C++的模板,Java的界限是Java泛型的局限。

2、简单泛型

Mike

Jason,你知道什么是泛型吗?

Jason

不知道。

Mike

下面我就举个例子吧,我们在一个类中保存一个Object引用,这样就可以通过这个Object应用存储任何类型的对象了:

先来声明一下要使用到的类:

public interface Animal {
public void speak();
}
public class Cat implements Animal{
@Override
public void speak() {
System.out.println(“miao~ miao~”);
}
}
public class Dog implements Animal{
@Override
public void speak() {
System.out.println(“wong~ wong~”);
}
}

接下来就可以创建一个类来保存Object引用,存入任何的内容了:

public class Holder {

private Object obj;
public Holder(Object obj){
    this.obj = obj;
}
public void set(Object obj){
    this.obj = obj;
}
public Object get(){
    return obj;
}
public static void main(String[] args){
    Holder h1 = new Holder(new Cat());
    Cat cat = (Cat)h1.get();
    h1.set(new Dog());
    Dog dog = (Dog)h1.get();
}

}

Mike

现在,我还不确定要存储什么对象,只是先要写一个这样的存储结构,当需要使用时,再告诉编译器要存储什么类型的对象,这时就可以通过泛型来实现了:

public class Holder2 {

private T a;
public Holder2(T a){
    this.a = a;
}
public void set(T a){
    this.a = a;
}
public T get(){
    return a;
}
public static void main(String[] args){
    Holder2<Cat> h2 = new Holder2<Cat>(new Cat());
    Cat cat = h2.get(); // 使用了泛型,无序手动转换
}

}

Mike

嘻嘻,我厉害吧。

Jason

你这么厉害,你家里人知道么。。。

2.1、一个元组类库

Mike

hi,Jason,你知道怎么在一个方法中返回多个对象吗,默认的return语句好像只允许返回单个对象。

Jason

有什么好办法哇,麦克?

Mike

20140120-001

Mike

嘿嘿,这里我就介绍下吧,可以创建一个对象,然后用它来持有想要返回的多个对象。这个概念称为元组,元组是这样的东西:将一组对象直接打包存储于其中的一个单一对象,这个容器运行读取其中的元素,但是不允许向其中存放新的对象(通过final关键字来确保的)。(这个概念也称为数据传送对象或信使)。而有了泛型之后,我们创建元组则更加方便了。下面是简单创建一个二维元组的代码:

public class TwoTuple<A, B> {
public final A a;
public final B b;
public TwoTuple(A a, B b){
this.a = a;
this.b = b;
}
public String toString(){
return “[”+ a + “, " + b +”]";
}
}

Mike

你知道为什么要用final吗?

Jason

虽然表面上看起来成员变量使用public,违反了Java编程的安全性原则,但是final关键字使得成员变量不可修改,这种方式显得更加安全,如果程序员想要使用具有不同元素的元组,这个时候就要求他们必须重新创建一个新的TwoTuple对象。

Mike

真聪明!如果想创建三维元组,使用继承机制也是很容易实现的:

public class ThreeTuple<A, B, C> extends TwoTuple<A, B>{
public final C c;
public ThreeTuple(A a, B b, C c) {
super(a, b);
this.c = c;
}
public String toString(){
return “[”+ a + “, " + b + “, " + c +”]”;
}
}

下面我们创建一个方法f(),返回一个三维元组,如下:

public class TupleTest {

static ThreeTuple<Cat, Integer, String> f(){
    return new ThreeTuple<Cat, Integer, String>(new Cat(), 1, "miao");
}
public static void main(String[] args){
    ThreeTuple<Cat, Integer, String> tt = f();
    System.out.println(tt);
}

}

2.2、一个堆栈类

2.3、RandomList

3、泛型接口

Mike

泛型在接口中也是可以使用的,生成器就是一个例子,什么是生成器呢?

Jason

类似于工厂方法模式的那种?

Mike

是的,是工厂模式的一种应用,不过使用生成器创建新的对象的时候,不需要任何的参数,而工厂方法一般需要参数。下面我就来创建一个生成器来展示泛型在接口中的使用场景:

首先来创建一下生成器的接口,这个接口就使用到了泛型,实现这个接口的类传入真正需要用到的相关类,有一个next()方法,用于产生新的对象:

public interface Generator {
T next();
}

下面创建一个生成器,实现Generator接口,随机生成不同类型的Animal,同事也实现了Iterable接口,以实现迭代器模式,方便迭代输出生成的对象:

public class AnimalGenerator implements Generator, Iterable{

// 生成器可生成的对象数组
private Class[] types = {Cat.class, Dog.class};
private static Random rand = new Random(47);
public AnimalGenerator(){}
private int size = 0;
public AnimalGenerator(int size){
    this.size = size;
}
// 随机生成一个对象
public Animal next(){
    try {
        return (Animal)types[rand.nextInt(types.length)].newInstance();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

class AnimalIterator implements Iterator<Animal>{
    int count = size;
    public boolean hasNext(){return count > 0;}
    public Animal next(){
        count --;
        return AnimalGenerator.this.next();
    }
    // 未实现
    public void remove(){
        throw new UnsupportedOperationException();
    }
}

@Override
public Iterator<Animal> iterator() {
    return new AnimalIterator();
}

public static void main(String[] args){
    AnimalGenerator gen = new AnimalGenerator();
    for(int i=0; i<5; i++){
        // 直接调用next()生成对象
        System.out.println(gen.next());
    }
    // 通过AnimalGenerator的迭代方法iterator迭代输出对象
    for(Animal a : new AnimalGenerator(5)){
        System.out.println(a);
    }
}

}

可见这里提供了两种生成对象的方法,直接调用AnimalGenerator的next()方法,或者通过AnimalGenerator的iterator()方法迭代生成输出对象。

4、泛型方法

Jason

Mike,泛型方法可以用到哪个场合呢?

Mike

泛型方法使得该方法能够独立于类而产生变化,尽可能的使用泛型方法,如果没有必要将整个类泛型化,这样使得事情更加清楚明白。另外,static方法无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使之成为泛型方法。

下面是一个泛型方法的示例:

public class GenericMethods {

public <T> void f(T x){
    System.out.println(x.getClass().getName());
}

public static void main(String[] args){
    GenericMethods gm = new GenericMethods();
    gm.f(1);
    gm.f("Jay");
}

}

输出结果为:

java.lang.Integer
java.lang.String

Jason

型方法和泛型类的使用好像有点不一样?

Mike

是的,使用泛型类是,在创建对象的时候必须制定类型参数的值,而使用泛型方法则不用,编译器会帮我们找到,这称为类型参数推断(type argument inference)。

Jason

不是说基本数据类型不能作为类型参数的吗?

Mike

是的,上面的泛型方法并没有使用基本类型作为参数,而是传递了一个基本类型到泛型方法中,通过自动打包机制将基本对象封装为了对应的对象。泛型方法和自动打包避免了许多以前我们不得不自己编写出来的代码。

4.1、杠杆利用类型参数推断

Jason

Mike,这个杠杆利用类型参数推断是个什么概念,谁翻译的哇,翻译的这么别扭。。。

Mike

下面直接上代码吧。

首先是一个静态方法:

public class New {
public static <K, V> Map<K, V> map(){
return new HashMap<K, V>();
}
}

然后可以这样创建一个Map:

public static void main(String[] args){
Map<String, List> catMap = New.map();
}

可以发现,右边的不用再按照以前的这种写法了:

Map<String, List> catMap = new HashMap<String, List>();

左边声明部分的类型为右边提供了一种推断,使得编译器可以直接创造出来具体的类了。不过,这种场景没有声明直接使用New.map()是编译不通过的,因为没有像这里左边的可以推断的依据了,如下面的,加入f()是一个方法,需要传入一个Map,如下的写法是编译不通过的:

f(New.map());

如果确实还是想按照上面那样使用,则可以考虑使用显示类型说明了,在操作符与方法名直接插入尖括号显示的指明类型,代码如下:

F(New.<Person, List>map());

不过这种方式很少使用。

也就是说,在编写非复制语句时,才要这样的说明,而使用不了杠杆利用类型推断。

我们为了方便,可以使用同样的方式创建其他的容器了,可惜JDK本身没有提供这样的类:

public class New {

public static <K, V> Map<K, V> map(){
    return new HashMap<K, V>();
}

public static <T> List<T> list(){
    return new ArrayList<T>();
}

public static <T> LinkedList<T> llist(){
    return new LinkedList<T>();
}

public static <T> Set<T> set(){
    return new HashSet<T>();
}

public static <T> Queue<T> queue(){
    return new LinkedList<T>();
}

public static void main(String[] args){
    Map<String, List<Cat>> catMap = New.map();
}

}

4.2、可变参数与泛型方法

可变参数也是可以私用泛型声明类型的:

public class GenericVarargs {

public static <T> List<T> makeList(T... args){
    List<T> result = new ArrayList<T>();
    for(T item : args){
        result.add(item);
    }
    return result;
}
public static void main(String[] args){
    List<String> ls = makeList("Jay", "Mike");
}

}

4.3、用于Generator的泛型方法

通过使用泛型方法,封装更加抽象的方法,比如下面的fill(),然后在使用的时候才传入需要使用的的具体对象:

public class GenericGenerator {

public static <T> Collection<T> fill(
        Collection<T> coll, Generator<T> gen, int n){
    for(int i=0; i<n; i++){
        coll.add(gen.next());
    }
    return coll;
}
public static void main(String[] args){
    Collection<Animal> animals = fill(new ArrayList<Animal>(), new AnimalGenerator(), 2);
    for(Animal a : animals){
        System.out.println(a);
    }
}

}

4.4、一个通用的Generator

通过使用泛型类,我们更创建一个更加通用的生成器Generator。

public class BasicGenerator implements Generator {

private Class<T> type;
public BasicGenerator(Class<T> type){
    this.type = type;
}

@Override
public T next() {
    try {
        return type.newInstance();
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
public static <T> Generator<T> create(Class<T> type){
    return new BasicGenerator<T>(type);
}

}

由于使用了newInstance()方法,所以这里生产的类必须要提供一个默认的无参构造函数。

下面试验一下,创建一个对象,为了标示是新创建的对象,在类里面保存一个static的计数器,每创建一个对象就加1:

public class CountObject {

private static long counter = 0;
private final long id = counter++;
public long id(){
    return id;
}
public String toString(){
    return "countObject" + id;
}
public static void main(String[] args){
    Generator<CountObject> gen = BasicGenerator.create(CountObject.class);
    for(int i=0; i<5; i++){
        System.out.println(gen.next());
    }
}

}

输入结果如下:

countObject0
countObject1
countObject2
countObject3
countObject4

4.5、简化元组的使用

我们可以发现之前创建的元组,在使用的时候都传入了一长串具体的类型,通过杠杆利用类型推断参数,我们其实可以直接省略掉那一长串具体的类型了,添加一个static方法,可以使该方法成为更通用的类库的方法了:

public class TupleTest2 {

public static<A,B,C> ThreeTuple<A,B,C> tuple(A a, B b, C c){
    return new ThreeTuple<A,B,C>(a, b ,c);
}
public static void main(String[] args){
    // 根据左边的类型自动判断右边的类型,无需手动创建时指明类型了
    ThreeTuple<Cat, Integer, String> tt = tuple(new Cat(), 1, "Jason");
    System.out.println(tt);
}

}

4.6、一个Set实用工具

本文作者: arthinking

本文链接: https://www.itzhai.comjava-notes-jack-your-generic-so-powerful-jason-know-what.html

版权声明: 版权归作者所有,未经许可不得转载,侵权必究!联系作者请加公众号。

×
IT宅

关注公众号及时获取网站内容更新。