1.java泛型是java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
2.java泛型可以让你消除代码中的强制类型转换,同时获得一个附加的类型检查层,该检查层可以防止有人将错误类型的键或值保存在集合中。这就是泛型所做的工作。
先来看看以下代码,
public static void main(String[] args) { List arrayList = new ArrayList(); arrayList.add("abc"); arrayList.add(123); arrayList.add(23.32); String abc = (String) arrayList.get(0);------------------------------------------1 Integer abd = (Integer) arrayList.get(0);---------------------------------------2
}
注意看看位置1,2的代码出现的问题。这段代码编译时没错,但运行时报错
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer at shipin38.Test1.main(Test1.java:28)
ArrayList集合在没有用泛型前,它什么都能装,当调用时就要进行强制转换,如果不注意就会出现将String类元素,转化为Intege元素,但是编译器又不会报错,运行时才会报错。这样存在安全隐患。所以我们需要泛型。
java语言中引入泛型是一个较大的功能增强。不仅语言、类型系统、编译器有了较大的变化,用以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了。
1.类型安全。泛型的主要目标是提高Java程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(如果幸运的话,在存在于代码注释中)。
2.消除强制类型转换。泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。(更利于团队的合作开发。)
3.潜在的性能收益。泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的JVM(虚拟机)的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要更改JVM或类文件。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。
总结:Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
泛型是给javac(编译器)使用的,泛型可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,(比如向ArrayList<String>集合中添加一个数字这样的错误操作),但是当javac编译带类型说明的集合时,会去掉类型信息,(类型信息:类型信息我的理解就是泛型数组<>中的元素,不如<String><Integer><Object>等)。擦除类型信息是为了让程序的运行效率不受影响。为了更直观的了解擦除类型信息,你可以在程序中用ArrayList<String>和ArrayList<Integer>,并调用他们的getClass()方法,然后对比这2个的返回值,如果相同,那说明类型信息被擦除了。
由于编译器编译文件后,会擦除类型信息,所以如果我们想办法跳过编译器,就可以向ArrayList<String>中添加数字。所以泛型是给编译器使用的。
示例代码:
public static void main(String[] args) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { //一般的做法,无法跳过编译器,编译报错。 ArrayList<String> a = new ArrayList(); a.add("abc"); a.add(123);//这点编译报错 //用反射的方法,跳过编译器,并向字符串数组添加数字 Class temp = a.getClass(); temp.getMethod("add", Object.class).invoke(a, 123); for (Object string : a) { System.out.println(string); }
}
打印输出:
abc
123
术语(terminology)是在特定学科领域用来表示概念的称谓的集合,术语是通过语音或文字来表达或限定科学概念的约定性语言符号,是思想和认识交流的工具。
了解术语是为了便于交流,同一样事物,a同学有a同学的叫法,b同学有b同学的叫法,当讨论同一样事物时会有多种叫法,那样大家到底在说的什么就不是很明确,但是当统一了术语以后,大家就方便交流和讨论。
整个ArrayList<E>表达式称着什么? 泛型类型
整个ArrayList<String>表达式称着什么? 参数化的泛型类型
ArrayList<E>表达式中E称着什么? 泛型类型参数或泛型类型变量
ArrayList<String>表达式中的String称着什么? 泛型类型参数的实例或实际泛型类型参数
ArrayList称着什么? 原始类型
Collection<String> c = new Vector();//小问题:这里为什么不是,Collection<String> c = new Collection<String>();呢?因为Collection是接口,接口不能实例化。
解释:在JDK1.5以前,是没有泛型概念的,程序员们都是用原始类型来写程序的,到了JDK1.5和以后出现了泛型概念,为了让以前的程序能运行,所以就支持了上面的写法。(题外话:可不可以,还不是编译器说了算,泛型是给编译器使用的。)
Collection c = new Vector(String);
同理,这种写法是可以的。
3-1、Vector<String> a = new Vector<Object>();//写法错误,如果不写右边的<Object>,那右边就变成了原始类型,写法就正确。
原因:先单独看赋值符号"="的左边,Vector<String> a,表示a集合中只能装String类型的元素。再单独看赋值符号"="的右边,Vector<Object>();,表示集合里面什么元素都能装,那我装个数字23进去也是可以的。现在把赋值符号"="两边联系起来看,整个表达式就是把Vector<Object>();里面的元素赋值给Vector<String>,而Vector<Object>()里面又装得有数字23,如果上面的写法可以的话,就会发生想字符串集合Vectro<String> a中放入数字23的错误,所以上面的写发编译器不让通过。
3-2、Vector<Object> v = new Vector<String>();//也错误
原因:Vector<Object>会指向new Vector<String>()的地址,如果可以会发生Integer(说Integer对象是因为Vector<Object>集合中可以装Integer对象)对象元素指向String对象元素的错误。所以上面的写法编译器同样不让编译通过-----------(关于这个推理我是很不明白。)
Vector<Integer>[] vec = new Vector<Integer>[10];//这个写法知道会错就行,就算不知道,写出来后编译器会报错,(自从有了编译器,java变成了一门安全的程序语言)。
Vector v = new Vector<String>();//不会报错,原始类型引用一个参数化类型对象 Vector<Object> v1 = v; //不会报错,参数化类型引用一个原始类型对象
还有一点,javac(编译器)是一行一行的编译代码的。
要求把下面的方法 public static void printCollection(Collection<String> collection){ for (Object string : collection) { System.out.println(string); } }
改写,使之能打印出任意参数化类型的集合中的所有数据。还有个要求就是只能改Collection<String>中的<>里面的参数类型的实例,其他地方不能改。
有的人会改成 public static void printCollection(Collection<Object> collection){ for (Object string : collection) { System.out.println(string); } } 如果这样是可以的话,那我们在main方法中来调用看看 public static void main(String[] args) { Collection<Integer> a = new ArrayList<Integer>(); a.add(1); a.add(2); printCollection(a);-----------------5 } } 当我在-----5那调用方法时,程序相当于把a集合赋值给collection,就像这样 Collection<Object> collection = Collection<Integer>() a;这样就犯了上面所说的(指向型)错误,所以编译报错。
为了解决上面的问题,JDK1.5提供了泛型通配符"?",正确改写 public static void printCollection(Collection<?> collection){ for (Object string : collection) { System.out.println(string); } } ?代表了,?可以指向任意类型,你不管传什么过来,collection都能接收。
完整的示例代码: public static void main(String[] args) { Collection<Integer> a = new ArrayList<Integer>(); a.add(1); a.add(2); printCollection(a); Collection<String> b = new ArrayList<String>(); b.add("a"); b.add("b"); printCollection(b); } public static void printCollection(Collection<?> collection){ for (Object string : collection) { System.out.println(string); } } 打印输出 1 2 a b
public static void printCollection(Collection<?> collection){ for (Object string : collection) { System.out.println(string); } collection.add("abc"); --------------1 collection.add(123); --------------2 collection.size(); --------------3 } 上面1,2和3位置的代码对吗?为什么? 1,2不对,由于?表示不管调用者传什么类型的集合,它都接受,就产生了不确定因素,如果调用者传Integer类型的集合呢?你用Integer类型的集合去添加String对象的元素,那位置1的代码肯定是错的。如果调用者传String类型的集合呢?同理,位置2的代码也是错的。3是对的,因为3位置的方法和参数化无关。不管什么样的类型都有个size()方法。
在帮助文档中Collection的add()方法和size()方法对比。
public static void main(String[] args) { Collection<Integer> a = new ArrayList<Integer>(); a.add(1); a.add(2); printCollection(a,"String");-----------1 Collection<String> b = new ArrayList<String>(); b.add("a"); b.add("b"); printCollection(b,"abc");--------------2 } public static <T> void printCollection(Collection<T> collection,T a){ for (T string : collection) { System.out.println(string); } collection.add(a); } 看看位置1和2的代码的不同,为什么位置1的代码编译报错。 因为:位置1中的a集合,里面只能装Integer对象的元素,而位置1添加的却是String对象的元素,所以编译报错。
public static void printCollection(Collection<?> collection){ for (Object string : collection) { System.out.println(string); } collection = new HashSet<String>();----------1 collection = new Collection<String>();-------2 }
位置1和位置2的代码对吗? 1是对的,因为“?”接受任意类型,你重新让他指向String类的集合是可以的,调用者传递东西进来也等于是赋值。2是错的,因为Collection是一个接口,接口是不能实例化的。接口没有构造方法。
使用“?”通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
Vector<? extends Number> x = new Vector<Number>();//正确 Vector<? extends Number> x = new Vector<Integer>();//正确 Vector<? extends Number> x = new Vector<Double>();//正确 Vector<? extends Number> x = new Vector<String>();//错误 extends代表Number和Number的子类
Vector<? super Number> x = new Vector<Number>();//正确 Vector<? super Number> x = new Vector<Object>();//正确 Vector<? super Number> x = new Vector<Byte>();//错误 super代表Number和Number的父类
提示:限定通配符总是包括自己
问题一:Class<String> x = Class.forName("java.lang.String");这样的写法为什么错。
Class.forName("java.lang.String");的返回值是Class<?>;
上面的表达式类似于Class<String> x = Class<?>;//指向型错误
Map集合用得已经很熟悉了,老师说的东西我在看帮助文档的情况下也能弄明白,就不再做笔记了。
在c++中想要达到一个加法运算,并且能实现任意Number类元素的相加,在不使用c++模板函数的情况下要这样写 int add(int x ,int y){ return x+y; } float add(float x ,float y){ return x+y; } double add(double x ,double y){ return x+y; } 这样写很麻烦,会产生许多方法。代码量也很大。 于是c++用模板函数来解决这一问题,只写一个通用的方法,就可以适应各种类型。 template<Class T> T add(T x,T y){ return(T)(x+y); } T代表类型不详,调用的时候,你传什么类型过来,T就是什么类型。
java的泛型借鉴了上面的方法
1.Java中的泛型类型(或者泛型)类似于c++中的模板。但是这种相似性仅限于表面,Java语言中的泛型基本上完全是在编译器中实现,用于编译器执行类型检查和类型判断,然后生成普通的非泛型的字节码,这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除)。这是因为要扩展虚拟机指令集来支持泛型被认为是无法接受的,工作量巨大,所以,java的泛型采用了可以完全在编译器中实现的擦除方法。
一个泛型类(generic class)就是具有一个或多个类型变量的类。如
/** * 泛型类 * * @author Terry * @date 2014-5-19 * */ public class GenericClass<T> {
/** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub }
}
GenericClass类引入了一个类型变量T,用尖括号(<>)括起来,并放在类名的后面。泛型类可以有多个类型变量。例如,
public class GenericClass<T,U>{...}
类型变量使用大写形式,且比较短,这是很罕见的。在Java类库中,使用变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型。T(需要时还可以用临近的字母U和S)表示“任意类型“。
/** * CRUD(增删查改) * * @author Terry * @date 2014-5-21 * */ public class GenericsDao { public <T> void add(T a){ } public <T> T findById(Object id){ return null; } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub GenericsDao dao = new GenericsDao(); dao.add(23);//在这里我添加一个数字进去--------------------------------1 String find = dao.findById(1);//把刚刚填进去的23找出来--------------2 } }
在这里我添加了一个数字进去,并把该数字查找出来,前面用的是String类来转载的,编译没报错,但运行的时候就会报错。此时add方法中的T和findByid中的T实际上是两个相对独立的泛型变量,为了让他们之间有联系着时候就需要泛型类来完成。例:
/** * CRUD(增删查改) * * @author Terry * @date 2014-5-21 * */ public class GenericsDao<T> { public void add(T a){ } public T findById(Object id){ return null; }
public static void add2(T a){ }---------------------------------------------------这里编译报错,因为此静态方法比GenericsDao先存在,而泛型变量T又和GenericsDao相关。 public static <T> void add3(T a){ }---------------------------------------------这里编译通过,因为此时的泛型参数T是独立的,它和GenericsDao类无关。 /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub GenericsDao dao = new GenericsDao(); dao.add(23);//在这里我添加一个数字进去 String find = dao.findById(1);//把刚刚填进去的23找出来------------------1这里编译报错,错误提示:Type mismatch: cannot convert from Object to String
类型不匹配:不能转换的对象为String Object find1 = dao.findById(1);----------------------------------------------------这里编译通过 GenericsDao<Integer> dao1 = new GenericsDao<Integer>(); dao1.add("String");------------------------------------------------------------------2这里编译报错,错误提示:The method add(Integer) in the type GenericsDao<Integer> is not
applicable for the arguments (String)
dao1.add(13);--------------------------------------------------------------------------这里编译通过 String find2= dao1.findById(1);---------------------------------------------------3这里编译报错,错误提示:Type mismatch: cannot convert from Integer to String Integer find3 = dao1.findById(1);--------------------------------------------------这里编译通过 } }
从这里可以看出当泛型类GenericsDao,没有指定泛型参数时,泛型类里面的T,编译器把他看成Object了。这里是从上面位置1的错误提示推断出来的。
示例:
/** * 泛型方法 * * @author Terry * @date 2014-5-20 * */ public class GenericClass { /** * 泛型方法 * @param a * @return */ public static <T> T getMiddle(T[] a){ return a[a.length/2]; }
}
这个方法是在普通类中定义的,而不是在泛型类中定义的,然而,这是一个泛型方法,可以从尖括号和类型变量看出这一点。注意,类型变量放在修饰符的后面,返回类型的前面。泛型方法可以定义在普通类中,也可以定义在泛型类中。
问题一、
public static void main(String[] args) throws Exception { int[] c = new int[]{1,2,3,4,5}; String[] d = new String[]{"a","b","c","d","e"}; exchange(c,1,2);-------------------------------------1 exchange(d,1,2); }
为什么位置1的代码编译报错?
答:因为位置一传入的类型为基本数据类型,而泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。所以报错
/** * 自动将Object类型的对象转换成其他类型 * @param a:要转的Object对象 * @return 转好后的对象 */ public static <T> T automaticConversion(Object a) { return (T) a; }
/** * 可以将任意类型的数组中的所有元素填充为相应类型的某个对象。 * @param a:要填充的数组 * @param b:要填充的元素 */ public static <T,U> void fillTheElements(List a,U b){ for (int i = 0; i < a.size(); i++) { a.remove(i); a.add(i, b); } }
/** * 打印任意元素或任意数组中的元素的方法 * @param a:可以是数组,也可以是单个元素 */ public static <T extends Exception,U> void print(Collection<U> a)throws T{ for (U t : a) { System.out.print(t+"\t"); } try { } catch (Exception e) { // TODO: handle exception } }
public static void main(String[] args) throws Exception {
List<String> b = new ArrayList<String>(); b.add("1"); b.add("2"); fillTheElements(b,"hah"); print(b);
}
打印输出:
hah hah
在这种情况下,前面的?通配符方案要比泛型方法更有效。当一个类型变量用来表达两个参数之间或参数和返回值之间的关系时,既同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用而不是仅在签名的时候使用,才需要使用泛型方法。
/** * 打印任意元素或任意数组中的元素的方法,最后在添加一个T(任意类型)类型的元素, * 未用通配符。添加成功。 * */ public static <T> void print1(Collection<T> a,T b){ for (T t : a) { System.out.print(t+"\t"); } a.add(b);//--------------编译成功 }
/** * 打印任意元素或任意数组中的元素的方法,最后在添加一个T(任意类型)类型的元素, * 用通配符。添加失败。 * */ public static <T> void print2(Collection<?> a,T b){ for (Object t : a) { System.out.print(t+"\t"); } //a.add(b);//-------------编译报错 }
/** * 把任意参数类型的集合中的数据安全地复制到相应类型的数组中。 * @param a:要复制的集合 * @param b:相应的数组 * @return 装好的数组 * @throws Exception */ public static <T> T[] copy(Collection<T> a,T[] b) throws Exception{ int i = 0; for (T t : a) { b[i] = (T) t; i++; } return b; }
/** * 打印任意元素或任意数组中的元素的方法 * @param a:可以是数组,也可以是单个元素 */ public static <T extends Exception,U> void print(U[] b)throws T{ for (U t : b) { System.out.println(t); } try { } catch (Exception e) { // TODO: handle exception } }
public static void main(String[] args) throws Exception {
ArrayList<String> a = new ArrayList<String>(); a.add("a"); a.add("b"); String[] a1 = new String[a.size()]; print(copy(a, a1)); ArrayList<Integer> b = new ArrayList<Integer>(); b.add(1); b.add(2); b.add(3); Integer[] b1 = new Integer[b.size()]; print(copy(b, b1));
}
运行打印输出:
a b 1 2 3
关于这点,看了视频也不是很明白,我感觉这应该不是很重要吧,为了节约时间先不管了,若以后觉得重要的话,再回来看看。这里只做个引子好了。(相关视频:张孝祥_java基础加强_第二部分视频41_12分)
示例代码:
public static void main(String[] args) throws Exception { // TODO Auto-generated method stub applyVector(new Vector<Date>()); }
/** * 通过反射来拿到泛型里面的实际类型(Vector<Date> 中的Date) * 获取参数化泛型中的泛型参数的实例(Vector<Date> 中的Date) * @param v1 * @throws Exception */ public static void applyVector(Vector<Date> v1) throws Exception{ //获得applyVector方法 Method applyMethod = Generics.class.getMethod("applyVector", Vector.class); //获得方法运行时,传过来的参数 Type[] types = applyMethod.getGenericParameterTypes(); //ParameterizedType 表示参数化类型,如 Collection<String>。 //Parameterized:参数化的 //Type:类型 ParameterizedType pType = (ParameterizedType)types[0]; System.out.println(pType.getActualTypeArguments()[0]); //小细节:注意方法的返回值 }
public void a(Vector<Integer> a){} public void a(Vector<String> a){}
这两个方法是重载吗?
答:不是,因为编译过后会查出类型信息,查处后就是这样
public void a(Vector a){} public void a(Vector a){}
而这2个方法不属于重载。
1.泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
2.同一泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类型实例是不兼容的。
3.泛型的类型参数可以有多个。
4.泛型的参数类型可以使用extends语句,例如<T extends superclass>。习惯上称为”有界类型“。
5.泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName(Java.lang.String);
小技巧:
1、如果要使用T extends A & B,或T super A & B,为了提高效率,应该将标签(tagging)接口放在边界列表的末尾。-----------来至书:Java核心技术卷一,P531的注释。
2、如果一个类中的多个方法使用泛型,那就用类级别的泛型。------来至张孝祥_Java基础加强_第二部分_视频42_17分54秒
相关资源:python入门教程(PDF版)