Java 泛型

1 泛型方法

无论在何时,只要你能做到,你就应该尽量使用泛型方法。

定义泛型方法:将泛型参数置于返回值之前:

public <T> void f(T x){}

1.1 类型参数推断

当使用泛系类时,必须在创建对象的时候指定类型参数值,而使用泛系方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型。这称为类型参数推断。

参数推断只对赋值操作有效,其他时候并不起作用[jdk6环境,该结论不成立]。

    public <T> T f(T t) {
        return t;
    }

    public <T> void  f2(T t) {
        System.out.println(t);
    }

    public static void main(String[] args) {
        Methods methods = new Methods();
        methods.f2(methods.f("Hello"));
        methods.f2(methods.f(3));
        methods.f2(methods.f(new ArrayList()));
//        运行结果:
//        Hello
//        3
//        []
    }

2 擦除

类型参数将擦除到它的第一个边界。“边界就是发生动作的地方。”

2.1 边界

extends 关键字在泛型边界上下文环境中和普通情况下所具有的意义是完全不同的。

public class Boundary {

    static class Paper {
        String content;
    }

    interface Pen {
        void write();
    }

    interface Horn {
        void speak();
    }

    static class Student<T extends Paper & Pen & Horn> {

        T entity;

        public Student(T entity) {
            this.entity = entity;
        }

        public T getEntity() {
            return entity;
        }

        void write() {
            entity.write();
        }

        void speak() {
            entity.speak();
        }

    }

    static class Media extends Paper implements Pen, Horn {

        @Override
        public void write() {
            content = "为中华崛起而读书";
        }

        @Override
        public void speak() {
            System.out.println(content);
        }

    }

    public static void main(String[] args) {

        Student<Media> student = new Student<>(new Media());
        student.write();
        student.speak();;
//        运行结果:
//        为中华崛起而读书
    }

}

2.2 通配符

    static class Fruit {}
    static class Apple extends Fruit {}
    static class Jonathan extends Apple {}
    static class Orange extends Fruit {}

    @Test
    public void test02() {
        List< ? extends Fruit > fruits = new ArrayList<>();
//        fruits.add(new Apple());  Required type: capture of ? extends Fruit
//        fruits.add(new Fruit()) Required type: capture of ? extends Fruit
        fruits.add(null);
        Fruit fruit = fruits.get(0);

        fruits = Arrays.asList(new Apple(),new Fruit(),new Orange());
        Apple apple = (Apple) fruits.get(0);

    }

List<? extends Fruit> 可以合法地指向一个List<Orange>。一旦执行这种类型的向上转型,你就将丢失掉向其中传递任何对象的能力,甚至是传递Object也不行。

另一方面,如果你调用一个返回Fruit的方法,则是安全的,因为你知道在这个List中的任何对象至少具有Fruit类型,因此编译器将允许这么做。

2.2.1 逆变

超类型通配符(下界通配符),可以声明通配符是由某个特定类的任何基类来界定的。<? super MyClass>或<? super T>。不能对泛型参数给出一个超类型边界,即不能声明<T super MyClass>

    @Test
    public void test03() {
        List< ? super Apple > fruits = new ArrayList<Fruit>();
        fruits.add(new Apple());
        fruits.add(new Jonathan());
//        fruits.add(new Fruit()); error

        Object object = fruits.get(0);
        Jonathan jonathan = (Jonathan) fruits.get(1);
        System.out.println("object:" + object.getClass() + ",jonathan:" + jonathan.getClass());
//        运行结果:
//        object:class com.juxinma.article.Wildcard$Apple,jonathan:class com.juxinma.article.Wildcard$Jonathan
    }

List<? super Apple> list = new ArrayList<Fruilt>() //正确,下界是Apple

list.add(new Apple()); //正确,子类型的引用可以赋值给父类对象的引用

2.2.2 无界限通配符

无界限通配符<?> 等价于 <? extends Object>

Map map;

Map<?,?> map;

Map<String,?> map;

    static class Holder<T> {
        T t;
        public Holder(T t) {
            this.t = t;
        }

        public T getT() {
            return t;
        }
    }

    static <T> void  f1(Holder<T> holder) {
        T t = holder.getT();
        System.out.println(t.getClass().getSimpleName());
    }

    static void f2(Holder<?> holder) {
        f1(holder);
    }

    public static void main(String[] args) {
        Holder holder = new Holder(1);
        f1(holder); //warnings, Unchecked assignment:
        f2(holder); // no warnings
    }

如果向一个使用<?>的方法传递原生类型,对于编译器来说,可能会推断出实际的类型参数,使得这个方法可以回转并调用另一个使用确切类型的方法,它被称为捕获转换

3 自限定的类型

3.1 古怪的循环泛型

不能直接继承一个泛型参数,但是,可以继承在其自己的定义中使用这个泛型参数的类:

class Fruit<T> {}

class Apple extends Fruit<Apple> {}

作用:能产生使用导出类作为其参数和返回类型的基类。还能将导出类型用作其域类型,甚至那些将被擦擦为Object的类型。

public class SelfLimit {

    static class BasicHolder<T> {
        T element;

        public T getElement() {
            return element;
        }

        public void setElement(T element) {
            this.element = element;
        }

        void f() {
            System.out.println(element.getClass().getSimpleName());
        }

    }

    static class Subtype extends BasicHolder<Subtype> {}

    public static void main(String[] args) {
        Subtype st1 = new Subtype();
        Subtype st2 = new Subtype();
        st1.setElement(st2);
        Subtype st3 = st1.getElement();
        st1.f();;
//        运行结果:
//        Subtype
    }

}

3.2 参数协变

方法的参数类型会随子类而改变,协变返回类型是Java SE5引入的。

public class SelfLimit2 {

    static class Base {}
    static class Derived extends Base {}

    interface BaseGetter {
        Base get();
    }

    interface DerivedGetter extends BaseGetter{
        Derived get();
    }

    static class DerivedGetterImpl implements DerivedGetter {
        @Override
        public Derived get() {
            System.out.println("derivedGetter.get");
            return null;
        }
    }

    void testDerivedGetter(DerivedGetter derivedGetter) {
        Derived derived = derivedGetter.get();
        Base base = derivedGetter.get();
    }

    @Test
    public void test1() {
        DerivedGetterImpl derivedGetter = new DerivedGetterImpl();
        testDerivedGetter(derivedGetter);
//        运行结果:
//        derivedGetter.get
//        derivedGetter.get
    }

    interface CovarianceSetter<T extends CovarianceSetter<T>> {
        void set(T t);
    }

    interface Setter extends CovarianceSetter<Setter> {}

    static class GetterImpl implements Setter {
        @Override
        public void set(Setter setter) {
            System.out.println(setter.getClass().getSimpleName());
        }
    }

    void setterTest(Setter setter, Setter setter2, CovarianceSetter covarianceSetter) {
        setter.set(setter2);
//        setter.set(covarianceSetter); error
    }

}

4 动态类型安全

因为可以向Java SE5之前的代码传递泛型容器,所以旧式代码仍旧有可能会破话你的容器,Collections的静态方法:checkedCollection、checkedList、checkedMap、checkedSet、checkedSortedMap和checkedSortedSet。会将你希望动态检查的容器当做第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。

受检查的容器在你试图插入类型不正确的对象时抛出ClassCastException。

public class DynamicSafe {

    public void addToList(List list,Object object) {
        list.add(object);
    }

    @Test
    public void test() {
        List<Double> doubleList = new ArrayList<>();
        addToList(doubleList,2.0d); //正常
        addToList(doubleList,"hello"); //正常

        List<Double> checkedDoubleList = Collections.checkedList(doubleList, Double.class);
        addToList(checkedDoubleList, 3.0d); //正常
        addToList(checkedDoubleList,"java");//报异常,java.lang.ClassCastException
    }

}

5 异常

catch语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。泛型类也不能直接或间接继承自Throwable。

但是,类型参数可能会在一个方法的throws子句中用到。

interface Processor<T,E extends Exception> {

      void process(List<T> result) throws E;

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值