您的位置:首页 > 编程语言 > Java开发

Java泛型详解

2015-10-23 21:14 405 查看

java泛型的了解


        java泛型是1.5的新特性,我们先通过一个例子来简单了解一下泛型。

泛型了解:


//在java 1.5之前,集合是这样处理的。
List c1 = new ArrayList();
c1.add("abc");
c1.add(1);
String s1 = (String) c1.get(0);
String s2 = (String) c1.get(1);


        在编译的时候,这样是可以编译通过的,在java1.5之前,但是,在运行的时候会出现错误,因为List中存储的是object,是开发者自己做的类型转化,如上述代码,就把Integer转化成了String。

而在java1.5之后,我们可以这样做。

//在java 1.5之后
List<String> c1 = new ArrayList<String>();
c1.add("abc");
c1.add(1);


        这样,使用泛型后,编译的时候是有错误的,说了里面只能存String,结果存了Integer。

    

        简单总结一下:没有使用泛型的时候,只要是对象,不管是什么类型,都可以存储进同一个集合,使用泛型集合后,可以将集合中的元素限定为某一种类型,这样更安全。在从集合中取元素的时候,不需要进行强制转化,编译器已经知道了集合中存的是什么类型。

进一步了解泛型:
        泛型的一些术语介绍:

        ArrayList<E>    ArrayList<String>

        整个ArrayList<E>称为泛型类型。中间这个<E>称为类型变量,(类型参数)

        整个ArrayList<String>称为参数化的类型,String称为实际类型参数或者类型参数的实例。

        ArrayList称为原始类型。


泛型的一些思考:


        一:

        这样看来,泛型让运行过程中可能出现的错误在编译的过程中暴露出来,更加安全的使用集合,那么,泛型是不是就可以说是给编译器用的,因为,编译器会提示我们写的代码是不是安全的。如果编译完之后,这个泛型还有作用吗?

List<String> c1 = new ArrayList<String>();
List<Integer> c2 = new ArrayList<Integer>();
System.out.println(c1.getClass() == c2.getClass());


        看一下打印结果:

true


        这说明上面的c1和c2是同一份字节码,因此看来,泛型在编译过后,已经不存在了,这就是“去类型化”。

        既然,字节码中已经没有了类型信息的限制,那通过反射,就可以往集合中放入其它类型的实例了。看一下如下代码:

List<String> c1 = new ArrayList<String>();
try {
Method method = c1.getClass().getMethod("add", Object.class);
method.invoke(c1, 123);
Object object = c1.get(0);
System.out.println(object);
} catch (Exception e) {
e.printStackTrace();
} 


打印结果:

123


        并没有报错,存的是Object,取的是Object。通过去类型化的特性,在只能存string的集合中存了c1.由此,更证明了泛型在编译完之后就消失了,泛型是给编译器看的。

        二:

        参数化类型和原始类型的兼容性:

//可以编译通过
List<String> c1 = new ArrayList();
//可以编译通过
List c2 = new ArrayList<String>();
//不可以编译通过
List<String> c3 = new ArrayList<Object>();
//不可以编译通过
List<Object> c3 = new ArrayList<String>();


        这个很好理解,现在有了泛型,那么前辈们写的代码我们还要继续使用,如一个方法一直返回一个ArrayList,现在来个一个参数化类型,能不能调用这个函数,当然可以,同样,你传递一个参数化的类型,原来接收一个集合,也应该可以使用。 因为,泛型是给编译器用的,编译器肯定兼容之前的,肯定能通过了。参数化类型是不考虑继承关系的,所以,第三种和第四种写法是错误的。

泛型的通配符:


        在api文档中,经常会看到这样的“<?>”这样的问号在类的后面跟着。先通过一个需求来了解问号。

        定义一个方法,可以打印任意List集合中的元素。(List<Integer>,List<String>......)这样的参数化类型都可以打印。

public void print(List<?> list){
for (Object object : list) {
System.out.println(object);
}
}


        ? 就是通配符,代表任意类型参数。小例子:

public void method1(Collection<Object> c){
//可以编译通过,add的是Object
c.add("abc");
c.add(123);
//错误,前面说过,参数化类型是不考虑继承的。所以,错误
c = new ArrayList<String>();
}
public void method2(Collection<?> c){
//并没有声明具体类型就放String?错误
c.add("abc");
//把一个?(可以匹配任意类型),让它类型参数实例为String
c = new ArrayList<String>();
}


        泛型中通配符的扩展。

        1:限定通配符的上边界

List<? extends Number> list = new ArrayList<Integer>();


        2;限定通配符下边界

//正确
List<? super Integer> list1 = new ArrayList<Number>();
//错误
List<? super Integer> list2 = new ArrayList<String>();


//正确,可以把一个类型给?
List<?> list1 = new ArrayList<Number>();
//错误,不可以把一个?给一个具体类型
List<Number> list2 = new ArrayList<?>();


自定义泛型:
        java的泛型是从c++泛型借鉴过来的,但没有c++的强大,但java还是尽可能的去模仿c++的泛型。

        同样,看一个需求,写一个方法,可以实现2个数的相加,但类型不同,先看看c++的实现

template<class T>
T add(T x,T y){
return (T)x+y;
}


        在看看java的实现。

/**
 * 如何定义一个类型,返回值之前,并紧挨着返回值。
 */
public <T> T add(T x,T y){
//The operator + is undefined for the argument type(s) T, T
//对于T来说,可能没有+这个操作。因此,在java中不能实现c++这样的功能,但仍然了解了java中泛型在方法上的使用。
//return (T)x+y;
return null;
}


泛型的类型推断:
        在上面的add方法中,尝试着这样的调用。

add(1, 2);
add(1, 2.0);
add(1, "abc");


        这样的调用,是不会出错的,既然参数类型是都是T,然而第2个一个是int,一个是float,第三个一个是int,一个是String都没有报错。通过编辑器自动生成返回值看看。

Integer add = add(1, 2);
Number add2 = add(1, 2.0);
Object add3 = add(1, "abc");


        由此可以推断,类型推断中,取了2个参数的共同父类,当作了实际类型参数。

        类型推断的传播性:如下

public static <T> void copy(Collection<T> c,T[] a) {
}
//错误
copy(new ArrayList<Float>(), new Integer[10]);


        既然类型变量取了父类,为什么这里没有取Number?原因是在对ArrayList参数化的时候,已经指定了T就是Float,这就是类型推断的传播性。

        简单总结一下:
        a:在返回值或者参数列表中的一个参数中使用了参数变量,那么传递的参数类型就是实际类型参数。

        b:在参数列表中多个地方使用了参数变量,在实际调用的过程中,如果传递一样的,很好判断,如果不一样,就会取几个的最大交集,如(Float,Integer,则实际类型参数就是Number)

        c:在参数列表多个地方和返回值都使用了,会根据返回值的类型来确定实际类型参数。

        d:传播性。

自定义泛型方法&自定义泛型类
    1:自定义泛型方法。如上面的add方法一样。

    例子:写一个方法,可以交换一个数组中的2个位置上的值。

public static <T> void swap(T[] arr,int x,int y){
T temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
}


        试着使用swap这个方法。

 //正确
swap(new String[]{"aaa","bbb"},0, 1);
swap(new Integer[]{111,222},0, 1);
//错误
swap(new int[]{1,2}, 0, 1);


        注意:泛型的实际类型不能是基本数据类型。因为,数组本来就是一个对象了,不会进行自动装箱,所以这里会报错。并且,数组中的元素不能使用参数化类型。如:

//正确
ArrayList[] a1 = new ArrayList[10];
//错误:Cannot create a generic array of ArrayList<String>
ArrayList<String>[] a2 = new ArrayList<String>[10];


        类型变量也可以表示异常。

public static <T extends Exception> void method() throws T{
try {
} catch (Exception e) {
throw (T)e;
}
}


        如果有多个类型变量,可以在<>中指出多个,用逗号隔开。如Map集合。

        2:自定义泛型类 

public class GenericStudy<T> {
 
}


        这样,就可以在方法中使用泛型类型了,因为在实际中,毕竟对一个类的操作希望对应的都是某一种类型,并不是多个,那么就可以在类上定义了,注意:静态方法不能使用这个类型参数,因为对象还没有?哪里来的类型参数实例?静态方法,就自己在方法上定义了。


获取类型参数实例:


        由于泛型的去类型化,在编译成字节码后已经看不到这个到底是什么类型了?如何获取?

public void apply(List<String> l){
}
try {
Method method = GenericStudy.class.getMethod("apply", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
ParameterizedType parameterizedType = (ParameterizedType) genericParameterTypes[0];
Type type = parameterizedType.getActualTypeArguments()[0];
Type rawType = parameterizedType.getRawType();
System.out.println(rawType+":"+type);
} catch (Exception e) {
e.printStackTrace();
} 
}


 打印结果:

interface java.util.List:class java.lang.String
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 泛型