Java的泛型和通配符泛型:

xiaoxiao2021-02-28  82

一:泛型出现的背景

在java代码里,你会经常发现类似下边的代码:

public class Test { public static void main(String[] args) { List list = new ArrayList(); list.add("hah"); //list.add(new Test()); // list.add(1); for (Object object : list) { String s1 = (String)object; //.....如果是你你该如何拿出list的值,如果list中放着上边的不同类型的东西。无解 } } }

  编码的时候,不加泛型是可以的,但是 你从容器中拿出来的时候必须强制类型转换,第一是多敲很多代码,第二极容易发生类型转换错误,这个运行时异常 比如你把上边

注释的代码放开,程序在获取容器的地方就会报运行时异常 ClassCasrException

Java语言的设计者引入了泛型,暂时先不追究它内在是怎么实现的。只需要知道,如果我们像下边这么写,我们就不需要强制类型转换。我们也不需要担心运行是异常了。

List<String> newList = new ArrayList<String>(); newList.add("hhe"); newList.add("123"); String s1 = newList.get(0);//不需要强制类型转换,因为我加了泛型,我就认为它里边一定都是String 回到顶部

二: 泛型的语法使用

1:使用具体的泛型类型: 尖括号内带有具体的类型。可以限定这个Map的key和value只能是字符串

Map<String, String> map = new HashMap<String, String>(); map.put("key","value"); String value = map.get("key")

从面向对象的角度看,使用对象的时候,泛型内传入的具体的类型。声明的时候采用尖括号内加占位符的形式,比如这是HashMap的源码

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable{ ... public HashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); putAllForCreate(m); } ... public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); } }

 2:方法声明的时候 : public  <T> T getValue(){...}

  在上边的代码中,我们可以看到在类上如何定义泛型,也看到了类上定义的占位符在类的普通方法上可以直接使用。但是如果想在静态方法上定义泛型,这需要单独的处理  。下面我们单独对方法上如何定义

和使用泛型进行介绍(注意:方法上是否定义泛型和类上是否定义没有必然的联系)

比如Web项目中,泛型是修饰类型的,在方法上,一般就是返回值和参数列表

  返回值类型:可以定义为List<String>等形式,但是实际开发中,一般都是不知道具体类型,定义形式如下  <T> List<T> test(T t){...} ,前边的<T>可以理解为泛型的声明,你只有声明了T,你才可以在              方法中用到T,这一具体的类型, List<T>是具体的返回值类型。  方法传参: 可以用占位符限定的容器 比如 List<T>,或者直接是占位符 T   public class BaseServiceImpl implements BaseService { protected <T> List<T> calcPage(String hql, PageContext pageContext, Object... params) { int total = getDataTotalNum(hql, params); pageContext.setTotal(total); List<T> list = (List<T>) getPageDataByHQL(hql, pageContext.getRows(), pageContext.getPage(), pageContext.getTotal(), params); return list; } @Override @Sync public void deleteBatchVO(final List<?> dataList) throws ServiceException { baseDAO.deleteBatchVO(dataList); } @Override public List<?> getPageDataByHQL(final String hql, final Map<String, Object> filter) throws ServiceException { return baseDAO.getPageDataByHQL(hql, filter); } }

简单的例子:

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

 方法定义的时候,泛型是这样设计,在使用的时候,代码如下:

List<TaclUserinfo> list = calcPage(hqluser1.toString(), pageContext, taclRole.getRoleid(), taclRole.getTenantId()); //返回值类型 是<T>List<T>的,j接收的时候,我直接用List<具体类>

3 :类或者接口使用泛型  interface Collection<V> {..}

  上边的HashMap代码中,也看到了在类上使用泛型的具体例子。在真正的项目上,一些基础的公共类经常定义泛型,如下:

1 public interface GenericDao<T, ID extends Serializable> { 2 3 public abstract void saveOrUpdate(T t) throws DataAccessException; 4 5 public abstract T get(ID id) throws DataAccessException; 6 7 public abstract List<T> query(String queryString) throws DataAccessException; 8 9 public abstract Serializable save(T t) throws DataAccessException; 10 11 public abstract void saveOrUpdateAll(Collection<T> entities) throws DataAccessException; 12 13 public abstract List<T> loadAll() throws DataAccessException; 14 15 public abstract void merge(T t) throws DataAccessException; 16 17 }

接口的实现类: 传入参数为T,实现类中也可以继续用T,返回为T也可以用T;实现的时候 可以用 extends限定泛型的边界。

public abstract class GenericDaoImpl<T extends BaseEntity, ID extends Serializable> extends HibernateDaoSupport implements GenericDao<T, ID> { public void merge(T t) throws DataAccessException { TenantInterceptor.setTenantInfoToEntity(t); getHibernateTemplate().merge(t); } public T get(ID id) throws DataAccessException { // getHibernateTemplate().setCacheQueries(true); T load = (T) getHibernateTemplate().get(getEntityClass(), id); return load; } }

具体使用的时候:

public class UserDao extends GenericDaoImpl<User, Serializable> { ...//比如get() merge()这些方法不需要在单独编写,直接调用 }

4:  声明带边界的泛型   class userDao<T extends BaseEntity>

  Java中泛型在运行期是不可见的,会被擦除为它的上级类型。如果你是无界的泛型参数类型,就会被替换为Object. 

public class RedColored<T extends Color> { public T t; public void color(){ t.getColor();//T的边界是Color,所以可以调用getColor(),否则会编译报错 } } abstract class Color{ abstract void getColor(); }

   类似这样的定义形式:GenericDaoImpl<T extends BaseEntity, ID extends Serializable> ,java重载了extends,标注T的边界就是BaseEntity。如果需求是继承基类,那么边界定义在子类上

类似 

class Colored2<T extends Color> extends RedColor<T>

5:用于通配符  <?>

   参考于( Java 通配符解惑  )泛型类型的子类型的不相关性。比如 现在List<Cat>并不是List<Anilmal>是两种不同的类型;且无继承关系 。那么,我们像想要传入的参数既可能是List<Cat>

也有可能是List<Annimal>

public class AnimalTrainer { public void act(List<? extends Animal> list) { //备注:如果用 List<Animal> 作为形参列表,是无法传入List<Cat>for (Animal animal : list) { animal.eat(); } } }

  act(List<? extends Animal> list),当中“?”就是通配符,而“? extends Animal”则表示通配符“?”的上界为Animal,换句话说就是,“? extends Animal”可以代表Animal或其子类,可代表不了Animal的父类(如Object),因为通配符的上界是Animal。

所以,泛型内是不存在父子关系,但是利用通配符可以产生类似的效果:

假设给定的泛型类型为G,(如List<E>中的List),两个具体的泛型参数X、Y,当中Y是X的子类(如上的Animal和Cat))

G<? extends Y> 是 G<? extends X>的子类型(如List<? extends Cat> 是 List<? extends Animal>的子类型)。G<X> 是 G<? extends X>的子类型(如List<Animal> 是 List<? extends Animal>的子类型)G<?> 与 G<? extends Object>等同,如List<?> 与List<? extends Objext>等同

 

代码演示

1 package genericity; 2 3 public class Demo1 { 4 5 class A<T>{ 6 private T t; 7 public T fun1(){ 8 return t; 9 } 10 11 public void fun2(T t){ 12 13 } 14 15 }//是泛型类 16 17 class B extends A<String>{}//B就不是泛型类 18 19 class C<E> extends A<Integer>{}//也是泛型类 20 21 class D<E> extends A<E>{}//也是泛型类 22 23 public void fun1(){ 24 D<String> d = new D<String>();//此时class D 和 class E 中的泛型都被String给替换了 25 } 26 }

 

 

1 package genericity; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 import org.junit.Test; 7 8 public class Demo2 { 9 10 @Test 11 public void fun1(){//集合和数组的较量 12 /* 13 * 第一次较量 14 * 数组:我可以创建一个什么都可以存在的10空间 15 * 集合:我可以创建一个什么都放而且无限的空间 16 */ 17 Object[] arr1 = new Object[10]; 18 List list1 = new ArrayList(); 19 20 /* 21 * 第二次较量 22 * 数组:我可以创建一个只存放String类型的10空间 23 * 集合:在以前我不行,不过现在我也可以,我可以创建一个只存放String类型的无限空间。 24 */ 25 String[] arr2 = new String[10]; 26 List<String> list2 = new ArrayList<String>(); 27 28 /* 29 * 第三次较量 30 * 数组:我可以使用Object[]来存在String[],但是arr3[0] = new Integer(100);//编程不报错,运行报:ArrayStoreException 31 * 集合:因为泛型的擦除,直接不给我编译通过 List<Object> list3 = new ArrayList<String>() 32 */ 33 Object[] arr3 = new String[10]; 34 arr3[0] = new Integer(100);//编程不报错,运行报:ArrayStoreException 35 36 // List<Object> list3 = new ArrayList<String>(); 37 /** 38 * 上面的代码报错,因为泛型只有编译器认识,而JVM对泛型根本不识别,泛型会在 39 * 运行时擦除,如果上面代码不报错,而且运行时泛型又会擦除,那么就好出现下面的笑话 40 * list.add(new Integer(100)),那么数组就好笑话集合,你什么东西都能放, 41 * 还有什么限制,笑话。 42 * 43 * 然后我们把这个问题放大,对一个打印集合里面数据的方法, 44 */ 45 } 46 47 public void fun2(){ 48 List<Integer> intList = new ArrayList<Integer>(); 49 50 List<String> strList = new ArrayList<String>(); 51 // print1(intList);//直接报错,原因和上面的一样,因为有一个实参向形参赋值的过程,编译器直接不让通过 List<Object> list= intList=new ArrayList<Integer>(); 52 //思考:那每个不同类型的集合都需要不同的打印方法,那方法是也太多了,所以就有了通配符的出现 53 54 //这样就可以使用通用的打印方法了 55 print2(intList); 56 print2(strList); 57 } 58 59 public void print1(List<Object> list){ 60 61 } 62 63 /** 64 * 这里的?就是通配符 65 * @param list 66 */ 67 public void print2(List<?> list){ 68 /* 69 * 思考:虽然都可以调用了,但是却带来了一些参数使用上面的限制 70 */ 71 // list.add(new Integer(100));//报错,因为并不知道传递进来的到底是上面,如果是String,那编程通过就笑话了,add()作废 72 Object obj = list.get(0);//其实这个参数可以使用的原因是因为Object为所有类的父类,不让这个get()方法也作废 73 74 /* 75 * 小结: 76 * 1、当使用通配符时,对泛型类中的参数为泛型的方法起到了副作用,不能再使用! 77 * 2、当使用通配符时,泛型类中返回值为泛型的方法,也作废了! 78 * 通配符的好处:可以使泛型类型更加通用!尤其是在方法调用时形参使用通配符! 79 */ 80 } 81 82 public void fun3(){ 83 List<Integer> intList = new ArrayList<Integer>(); 84 List<Long> longList = new ArrayList<Long>(); 85 print3(intList); 86 print3(longList); 87 } 88 89 /** 90 * 子类统配,必须是Number及Number的子类才可以传参 91 * 这样的缺点是:降低了参数的灵活性,但是关闭一扇大门就会打开一扇大门 92 * 因为所有累都是Number的子类,所有返回值可以使用Number来接受,get()方法获得解放,即返回值为泛型的方法可以使用了 93 * @param list 94 */ 95 public void print3(List<? extends Number> list){ 96 Number nu = list.get(0);//正确 97 // list.add(new Integer(100));//但add()方法还是被废,以为不知道具体传入的哪一个子类,如果传入的是Long,加入Integer就笑话了 98 } 99 100 /** 101 * 父类统配,只允许Integer传递参数 102 * 这样的缺点是:降低了参数的灵活性,但是关闭一扇大门就会打开一扇大门 103 * 好处是因为所有类都是Integer的父类,参数为泛型的所有方法都可以使用了 104 * 但是相反的,返回值为泛型类型的方法就不能使用,因为子类不能接收父类的值 105 * @param list 106 */ 107 public void print4(List<? super Integer> list){ 108 list.add(new Integer(100));//正确 109 /* 110 * 但返回值为泛型的方法就不能使用了 111 */ 112 // Integer nu = list.get(0);//报错 113 } 114 115 116 }
转载请注明原文地址: https://www.6miu.com/read-2350354.html

最新回复(0)