大家好,这篇文章与大家分享的是Java的泛型机制。我们都知道在Java中一般的类和方法,只能使用具体的类型:要么为基本类型,要么是类。但是在有些情况下我们需要让我们的代码同时使用于多种类型的参数。这时如果每个类型都实现一套的话,就会产生很多的重复代码。

大家可能要说,我可以使用继承来扩展我的程序,通过将方法的参数类型设置为基类,方法就可以使用任何非final类。或者使用接口,将方法的参数类型设置为接口,方法就可以使用任意实现这一接口的类。

但是尽管我们可以通过单继承体系或接口来实现泛化,对程序的约束还是过强。因为一旦使用了接口或基类,我们的代码必须使用指定的接口或类。但我们希望我们的代码更为通用,并不基于某种特别的类或接口。这时我们就需要解耦类型、方法、接口与所使用的类型之间的约束,也就是使用Java的泛型机制。

泛型类

可能大家知道,Java中有一组容器类。它可以存放各种类型的对象,而它就是使用了泛型类来帮助实现这一特性。

public class Container<T> {
    private T variable;

    public Container<T>() {
        variable = null;
    }

    public Container<T>(T varialbe) {
        this.variable = variable;
    }

    public T get() {
        return varible; 
    }

    public void set(T variable) {
        this.variable = variable;    
    }
}

我们实例化Container对象时,只需设置它使用的类型,如:

Container<String> stringContainer = new Container<String>();
stringContainer.set("something");

之后实例化对象后,对象即可以使用<>中设置的类作为某个方法的参数或类的成员变量。

泛型类支持多个类型。比方说我们需要实现一个DTO(数据传输类),存储三个类型的变量,我们可以实现如下:

public class DTO<A, B, C> {
    private A a;
    private B b;
    private C c;

    public A getA() {
        return a;
    }

    public void setA(A a) {
        this.a = a;
    }

    public B getB() {
        return b;
    }

    public void setB(B b) {
        this.b = b;
    }

    public C getC() {
        return c;
    }

    public void setC(C c) {
        this.c = c;
    }

}

使用时:

DTO<String, Integer, Float> dto = new DTO<String, Integer, Float>();
dto.setA("something");
dto.setB(1);
dto.setC(1.0f);

编译器会根据你的实例化代码以及泛型代码来生成运行时代码。

泛型接口

泛型也可以应用于接口。很多人都使用过容器,容器中有个比较重要的元素叫做迭代器,我们来看下如何通过泛型接口来实现迭代器:

/**
 * 首先是迭代器接口
 * @author XingLiang
 *
 * @param <T> 容器存储变量的类型
 */
public interface Iterator<T> {
    /**
     * 是否还有下一个元素
     * @return
     */
    boolean hasNext();
    
    /**
     * 返回下一个元素,并指向下一个元素
     * @return
     */
    T next();

    /**
     * 删除当前元素,指向下一个元素
     */
    void remove();
}

/**
 * 接下来是允许容器进行迭代的接口,也就是容器类需要实现的接口
 * 
 * @author XingLiang
 * 
 * @param <T>
 */
public interface Iterable<T> {
    public Iterator<T> iterator();
}

import java.util.ConcurrentModificationException;
import java.util.NoSuchElementException;

/**
 * 实现Iterable接口的容器
 * 
 * @author XingLiang
 * 
 * @param 容器存储类型
 */
public class ArrayList<T> implements Iterable<T> {
    private T[] elementData;

    private int size;

    public void remove(int i) {
        if (i >= size) {
            throw new NoSuchElementException();
        }
        for (int j = i; j < size - 1; j++) {
            elementData[j] = elementData[j + 1];
        }
        size--;
    }

    @Override
    public Iterator<T> iterator() {
        return new Iterator<T>() {

            int cursor;
            int lastRet = -1;

            public boolean hasNext() {
                return cursor != elementData.length;
            }

            @SuppressWarnings("unchecked")
            public T next() {
                int i = cursor;
                if (i >= elementData.length)
                    throw new NoSuchElementException();
                Object[] elementData = ArrayList.this.elementData;
                if (i >= elementData.length)
                    throw new ConcurrentModificationException();
                cursor = i + 1;
                return (T) elementData[lastRet = i];
            }

            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                try {
                    ArrayList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }

        };
    }
}

上述代码同时使用了泛型类及泛型接口,我们也可以定义一个非泛型类来实现泛型接口,如:

import java.util.ConcurrentModificationException;
import java.util.NoSuchElementException;

public class IntegerList implements Iterable<Integer> {

    private Integer[] integers;

    private int size;

    public void remove(int i) {
        if (i >= size) {
            throw new NoSuchElementException();
        }
        for (int j = i; j < size - 1; j++) {
            integers[j] = integers[j + 1];
        }
        size--;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {

            int cursor;
            int lastRet = -1;

            public boolean hasNext() {
                return cursor != integers.length;
            }

            @SuppressWarnings("unchecked")
            public Integer next() {
                int i = cursor;
                if (i >= integers.length)
                    throw new NoSuchElementException();
                Object[] elementData = IntegerList.this.integers;
                if (i >= elementData.length)
                    throw new ConcurrentModificationException();
                cursor = i + 1;
                return integers[lastRet = i];
            }

            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                try {
                    IntegerList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
        };
    }
}

泛型方法

泛型也可以应用在一个方法中,不要求这个方法所属的类为泛型类。例如我们要获取一个对象的类名称:

public <T> String getClassName(T object) {
    if (object != null) {
        return object.getClass().getName();
    }
    return null;
}

提示

与泛型类和泛型接口不同的是泛型方法需要在方法返回值前用尖括号声明泛型类型名,这样才能在方法中使用这个标记作为返回值类型或参数类型。

泛型的运行时问题

在泛型代码内部,无法获得任何有关泛型参数类型的信息,也就是说我们无法在运行时获得这个一个容器中所存储对象的类型。 这是因为Java泛型是使用擦除来实现的,也就是说List以及List在运行时实际上是相同的类型List。

我们无法在运行时获取对象所采用的泛型类型,我们就无法在运行时知道这个类是否拥有某个方法。在Java中我们可以采取边界的手法进行限制,通过来声明T必须是Object类型或Object类导出的类,来确保我们可以调用T中的某个方法。

擦除使我们失去了在泛型代码中执行某些操作的能力,任何在运行时需要知道确切信息的操作都无法工作。如果我们一定要知道某个类的话,我们可以采用显式传递Class对象的方式或者重新考虑设计。

登录发表评论 注册

反馈意见