Java泛型

1、为什么需要泛型

package com.xiya.generic;

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

public class GenericTest {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("abc");
        list.add(111);//可以通过编译

        for (Object o : list) {
            String value = (String) o;//抛出ClassCastException异常(java.lang.Integer cannot be cast to java.lang.String)
            System.out.println(value);
        }
    }
}

如上,我们向List类型集合中加入了一个String类型的值和一个Integer类型的值(这样是合法的,因为此时list默认的存储类型为Object类型)。
List add(Object o)
上面的代码会在运行时抛出ClassCastException异常,因为它尝试将一个Integer转换为String java.lang.Integer cannot be cast to java.lang.String。接着,来看一下从java5开始,Collection的用法:

package com.xiya.generic;

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

public class GenericTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("abc");
        list.add("def");
        //list.add(111);//不可以通过编译

        for (String s : list) {
            System.out.println(s);//无需任何强制类型转换
        }
    }
}

注意到,List的创建增加了类型参数String,因此只能向List中添加String类型对象,添加其他对象会抛出编译异常;
同样可以注意到,foreach循环不需要再添加任何强制类型转换,也就移除了运行时的ClassCastException异常。
泛型的两个好处:
- 编译时类型检查。
- 避免强制类型转换。


2、泛型类与泛型接口

考虑以下场景:您希望开发一个用于在应用中传递对象的容器。但对象类型并不总是相同。因此,需要开发一个能够存储各种类型对象的容器。
鉴于这种情况,要实现此目标,显然最好的办法是开发一个能够存储和检索 Object 类型本身的容器,然后在将该对象用于各种类型时进行类型转换。

package com.xiya.generic;

class ObjectContainer {
    private Object obj;

    /**
     * @return the obj
     */
    public Object getObj() {
        return obj;
    }

    /**
     * @param obj the obj to set
     */
    public void setObj(Object obj) {
        this.obj = obj;
    }
}

public class GenericTest {
    public static void main(String[] args) {
        ObjectContainer objectContainer = new ObjectContainer();
        objectContainer.setObj("Hello World");
        System.out.println(objectContainer.getObj());
    }
}

原始类的定义,容易引发ClassCastException。
现在来看一下泛型类来重新定义ObjectContainer使用<T>指定泛型参数,如下:

package com.xiya.generic;

class ObjectContainer<T> {
    private T obj;

    /**
     * @return the obj
     */
    public T getObj() {
        return obj;
    }

    /**
     * @param obj the obj to set
     */
    public void setObj(T obj) {
        this.obj = obj;
    }
}

public class GenericTest {
    public static void main(String[] args) {
        ObjectContainer<String> objectContainer = new ObjectContainer<>();
        objectContainer.setObj("Hello World");
        //objectContainer.setObj(111);//无法通过编译
        String str = objectContainer.getObj();
        System.out.println(str);
    }
}

接口的泛型应用和类的泛型应用很类似,如下:

public interface List <E> {
     void add(E x);
     Iterator<E> iterator();
}

public interface Iterator<E> {
     E next();
     boolean hasNext();
}

类似的,可以将此应用到自定义的接口与类当中。


3、泛型的命名规范

为了更好地去理解泛型,我们也需要去理解java泛型的命名规范。为了与java关键字区别开来,java泛型参数只是使用一个大写字母来定义。各种常用泛型参数的意义如下:
E — Element,常用在java Collection里,如:List< E >,Iterator< E >,Set< E >
K,V — Key,Value,代表Map的键值对
N — Number,数字
T — Type,类型,如String,Integer等等
S,U,V etc. - 2nd, 3rd, 4th 类型,和T的用法一样


4、泛型方法

定义泛型方法语法格式如下:
声明泛型方法

调用泛型方法语法格式如下:

public class GenericTest {
    public static <T> T getObject(Class<T> c) throws IllegalAccessException, InstantiationException {
        return c.newInstance();
    }
    public static void main(String[] args) {
        try {
            Person person = getObject(Person.class);
            person.setName("lgh");
            person.setAge(25);
            System.out.println(person);
        } catch (IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
    }
}

<T>T的区别?
其实说白了就是先声明后使用,和变量的使用没有本质区别。


5、泛型参数的界限

有时候,你会希望泛型类型只能是某一部分类型,比如操作数据的时候,你会希望是Number或其子类类型。这个想法其实就是给泛型参数添加一个界限。其定义形式为:
<T extends BoundingType>
此定义表示T应该是BoundingType的子类型(subtype)。T和BoundingType可以是类,也可以是接口。另外注意的是,此处的extends表示的子类型,不等同于继承。

public class GenericTest {
    public static <T extends Number> void test(List<T> list) {
        System.out.println(list);
    }
    public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
        List<Number> list2 = Arrays.asList(6, 7, 8, 9, 10);
        List<String> list3 = Arrays.asList("111", "222");
        test(list1);
        test(list2);
        //test(list3);//报错
    }
}

此外,泛型只在编译阶段有效。运行时会进行类型擦除。
List<String> list = new ArrayList<>(); 编译后变成
List list = new ArrayList<>();

package com.xiya.generic;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class GenericTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("111");
        list.add("222");
//        list.add(333);
        try {
            Method method = list.getClass().getMethod("add", Object.class);
            method.invoke(list, 333);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println(list);
    }
}

参考:
http://peiquan.blog.51cto.com/7518552/1302898
https://www.ziwenxie.site/2017/03/01/java-generic/
http://www.oracle.com/technetwork/cn/articles/java/juneau-generics-2255374-zhs.html
http://www.infoq.com/cn/articles/cf-java-generics
https://www.ibm.com/developerworks/cn/java/j-lo-gj/index.html
http://oldratlee.com/278/tech/java/study-material-of-java-generic.html
http://blog.csdn.net/LonelyRoamer/article/category/1212099

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

N3verL4nd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值