黑马程序员——高新技术4——泛型

来源:互联网 发布:360浏览器mac版下载 编辑:程序博客网 时间:2024/06/11 11:40

------- android培训java培训、期待与您交流! ----------

泛型入门

下面是一段代码用来体验泛型:


第一段程序,如果不带泛型,程序只会警告,但是编译时会报异常,如果使用泛型,写代码的时候就会标上红叉,并且不像12行,第18行不需要强制类型转换。

由上总结泛型的优点是:编译时的强类型检查,消除强制类型转换。

基本概念:


类型参数可以是任意的非基本类型,如类,接口,数组等,但不能是基本数据类型,如int,boolean等。

泛型的内部原理

泛型是给编译器使用的,让编译器挡住源程序中的非法输入,编译器生成的字节码是不包含泛型的。如下程序所示:

import java.util.ArrayList;public class GenericPrinciple {public static void main(String[] args) {ArrayList<Integer> list1=new ArrayList<Integer>();ArrayList<String> list2=new ArrayList<String>();System.out.println(list1.getClass()==list2.getClass());//true}}
那是不是说加了泛型的集合就不能接收其他类型的参数了呢,不是的。用反射还是可以给加了泛型的集合添加其他类型数据。如下程序演示:

public static void demo2() {ArrayList<Integer> list3=new ArrayList<Integer>();try {//list3.add("abc");list3.getClass().getMethod("add", Object.class).invoke(list3, "abc");} catch (Exception e) {e.printStackTrace();}System.out.println(list3.get(0));//abc}
ArrayList<Integer>与ArrayList兼容,既ArrayList<Integer> list=new ArrayList();与ArrayList list=new ArrayList<Integer>();都是对的,但是编译器会警告。但ArrayList<Object>与ArrayList<String>不兼容,既ArrayList<Object> list=new ArrayList<String>();与ArrayList<String> list=new ArrayList<Object>();都是错误的,无法通过编译。

ArrayList list1=new ArrayList<String>();

ArrayList<Object> list2=list1;

上面两行代码不会报错,因为编译器是一行一行编译。编译器认为list2是ArrayList类型。

泛型的通配符

泛型中有一个通配符“?”,它表示ArrayList<E>中的E可以为任意数据类型都可以,如下面一个程序:
定义一个方法,该方法用于打印出任意泛型集合的所有数据
public static void demo3() {ArrayList<Integer> list1=new ArrayList<Integer>();list1.add(1);list1.add(2);list1.add(3);printCollection(list1);ArrayList<String> list2=new ArrayList<String>();list2.add("abc");list2.add("你好");list2.add("123");printCollection(list2);}public static void printCollection(Collection<?> collection){for (Object obj : collection) {System.out.println(obj);}}

限定通配符extends表示自己及其子类,例如? extends Number表示Number类型及其子类,在泛型中用法如下:
ArrayList<? extends Number> list=new ArrayList<Integer>();正确
ArrayList<? extends Number> list=new ArrayList<String>();错误
extends实际上是给泛型参数添加了一个上边界,为什么说是上边界,请看上图,如果把类层次结构如此画的话,? extends Number正好表示红线一下的类,所以说是画了个上边界。
super给泛型参数添加了一个下边界,例如:
ArrayList<? super Integer> list=new ArrayList<Number>();正确
ArrayList<? super Integer> list=new ArrayList<Byte>();错误
?通配符主要用于引用,也就是说一般为与赋值表达式左侧或为方法参数。

泛型集合的综合应用案例

遍历一个HashMap集合并打印其中的所有键值对:
public static void demo4(){HashMap<String, Integer> maps=new HashMap<String,Integer>();maps.put("zhangsan", 28);maps.put("lisi", 21);maps.put("wangwu",17);Set<Map.Entry<String, Integer>> entrySet=maps.entrySet();for (Map.Entry<String, Integer> entry : entrySet) {System.out.println(entry.getKey()+":"+entry.getValue());}}
运行结果:
wangwu:17
lisi:21
zhangsan:28

自定义泛型方法

类型参数的命名规范:(Java SE API遵循的规范)
E-Element:表示集合的元素
K-Key:键
V-Value:值
N-Number:数字类型
T-Type:任意类型
S,U,V:第二个类型参数,第三个类型参数,第四个类型参数
泛型方法就是有自己类型参数的方法。类型参数必须紧跟在方法返回值前边
例如下面的程序:用泛型交换一个任意类型的数组的指定两个元素。
public static void demo5(){//交换字符串数组String[] a1=new String[]{"1","2","3"};System.out.print(Arrays.toString(a1)+"→");swap(a1, 1, 2);System.out.println(Arrays.toString(a1));Integer[] a2=new Integer[]{1,2,3};System.out.print(Arrays.toString(a2)+"→");swap(a2, 0, 1);System.out.println(Arrays.toString(a2));}public static <T> void swap(T[] a,int i,int j){T temp=a[i];a[i]=a[j];a[j]=temp;}
运行结果:
[1, 2, 3]→[1, 3, 2]
[1, 2, 3]→[2, 1, 3]

泛型方法练习题

1.编写一个泛型方法,自动将Object类型的对象转换成其他类型。
public static void exercise1(){String i="1";String str=convertType(i);System.out.println(str);}public static <T> T convertType(Object obj){return (T)obj;}
2.定义一个方法,可以将任意类型的数组中的所有元素填充为相应类型的某个对象。
public static void exercise2(){Integer[] a=new Integer[]{1,2,3};System.out.println(Arrays.toString(a));fillArray(a,(Integer)1);System.out.println(Arrays.toString(a));}public static <T> void fillArray(T[] a,T obj){for (int i = 0; i < a.length; i++) {a[i]=obj;}}
运行结果:
[1, 2, 3]
[1, 1, 1]

3.打印参数化类型集合中的所有内容
public static void exercise3() {ArrayList<Integer> list=new ArrayList<Integer>();list.add(1);list.add(2);printCollection(list);}public static <T> void printCollection(Collection<T> collection){for (T t : collection) {System.out.println(t);}}

类型推断


类型推断是Java编译器的一种能力,通过考察每一次方法调用和相应的声明来决定类型参数,以使编译通过。推断算法试图找出最准确的类型,可以使所有的类型参数都起作用。
例如:下面的代码,类型推断算法确定T为Serializable。[1]
static <T> T pick(T a1, T a2) { return a2; }Serializable s = pick("d", new ArrayList<String>());

推断算法是根据所有的类型变量,在泛型方法被调用时,实际传入的类型来确定。如果所有实际传入的类型有一个非Object的相同父类,则为相同父类,如果没有则为公共接口。

参考资料:
[1]Java SE Tutorials,2012-02-28,Generics(Updated) Type Inference

自定义泛型类

泛型类,就是带类型参数的类,声明一个泛型类的例子如下:

//DAO:Data Access Object,数据访问类public class GenericDAO<E> {//查找数据public E findById(int id){return null;}public E findByUserName(String name){return null;}public Set<E> findByConditions(String where){return null;}//增加数据public void add(E obj){}//修改数据public void update(E obj){}//删除数据public void delete(E obj){}}
使用泛型类的例子:
public class GenericClass {public static void main(String[] args) {GenericDAO<String> dao=new GenericDAO<String>();dao.add("测试");dao.delete("测试");}}
注意,上面的GenericDAO类中如果有静态方法,静态方法是不能够使用类的类型参数的,可以使用方法自己的类型参数。
//错误public static void update1(E obj){}//正确public static <E> void update1(E obj){}

通过反射获取泛型的实际类型参数

代码如下:
import java.lang.reflect.Method;import java.lang.reflect.ParameterizedType;import java.lang.reflect.Type;import java.util.Date;import java.util.Vector;public class GenericReflectionDemo {public static void main(String[] args) throws Exception{//反射这个这个方法的参数,然后或得这个参数的类型参数Method applyMethod=GenericReflectionDemo.class.getMethod("applyVector", Vector.class);Type[] types=applyMethod.getGenericParameterTypes();ParameterizedType pType=(ParameterizedType)types[0];System.out.println(pType.getActualTypeArguments()[0]);}public static void applyVector(Vector<Date> v1){}}
运行结果:
class java.util.Date














0 0