,有时候需要更多灵活性,就可以通过上下边界通配符来做提升。
/** * 集合工具类 */public class CollectionUtils{ /** * 复制集合-泛型 */ public List listCopy(Collection collection){ List newCollection = new ArrayList<>(); for (T t : collection) { newCollection.add(t); } return newCollection; } }
上面声明了一个CollectionUtils类,拥有listCopy方法,传入任意一个集合返回新的集合,看似没有什么问题,也很灵活,那再看看下面这段代码。
public static void main(String[] agrs){ CollectionUtils collectionUtils = new CollectionUtils<>(); List list = new ArrayList<>(); //list.add.... List listTwo = new ArrayList<>(); //listTwo.add.... List listThree = new ArrayList<>(); //listThree.add.... List list1 = collectionUtils.listCopy(list); list1 = collectionUtils.listCopy(listTwo);//err 编译异常 list1 = collectionUtils.listCopy(listThree);//err 编译异常}
创建CollectionUtils类,泛型的类型参数为Number,listCopy函数入参的泛型填充为Number,此时listCopy只支持泛型为Number的List,如果要让它同时支持泛型为Number子类的List,就需要使用上边界通配符,我们再追加一个方法
/** * 集合工具 */public class CollectionUtils{ /** * 复制集合-泛型 */ public List listCopy(Collection collection){ List newCollection = new ArrayList<>(); for (T t : collection) { newCollection.add(t); } return newCollection; } /** * 复制集合-上边界通配符 */ public List listCopyTwo(Collection collection){ List newCollection = new ArrayList<>(); for (T t : collection) { newCollection.add(t); } return newCollection; }}public static void main(String[] agrs){ CollectionUtils collectionUtils = new CollectionUtils<>(); List list = new ArrayList<>(); //list.add.... List listTwo = new ArrayList<>(); //listTwo.add.... List listThree = new ArrayList<>(); //listThree.add.... List list1 = collectionUtils.listCopyTwo(list); list1 = collectionUtils.listCopyTwo(listTwo); list1 = collectionUtils.listCopyTwo(listThree);}
现在使用listCopyTwo就没有问题,listCopyTwo对比listCopy它的适用范围更广泛也更灵活,listCopy能做的listCopyTwo能做,listCopyTwo能做的listCopy就不一定能做了,除此之外,细心的小伙伴肯定发现了,使用上边界通配符的collection在函数内只使用到了读操作,遵循了只读不写原则。
看完了上边界通配符,再来看看下边界通配符,依然是复制方法
/** * 儿子 */public class Son extends Father{}/** * 父亲 */public class Father extends Grandpa{}/** * 爷爷 */public class Grandpa {}/** * 集合工具 */public class CollectionUtils{ /** * 复制集合-泛型 * target目标 src来源 */ public void copy(List target,List src){ if (src.size() > target.size()){ for (int i = 0; i < src.size(); i++) { target.set(i,src.get(i)); } } } }
定义了3个类,分别是Son儿子、Father父亲、Grandpa爷爷,它们是继承关系,作为集合元素,还声明了一个CollectionUtils类,拥有copy方法,传入两个集合,目标集合与来源集合,把来源集合元素复制到目标集合中,再看看下面这段代码
public static void main(String[] agrs){ CollectionUtils collectionUtils = new CollectionUtils<>(); List fatherTargets = new ArrayList<>(); List fatherSources = new ArrayList<>(); //fatherSources.add... collectionUtils.copy(fatherTargets,fatherSources); //子类复制到父类 List sonSources = new ArrayList<>(); //sonSources.add... collectionUtils.copy(fatherTargets,sonSources);//err 编译异常 }
创建CollectionUtils类,泛型的类型参数为Father父亲,copy函数入参的泛型填充为Father,此时copy只支持泛型为Father的List,也就说,只支持泛型的类型参数为Father之间的复制,如果想支持把子类复制到父类要怎么做,先分析下copy函数,copy函数的入参src在函数内部只涉及到了get函数,即读操作(泛型只作为get函数返回类型),符合只读不写原则,可以采用上边界通配符,调整代码如下
/** * 集合工具 */public class CollectionUtils{ /** * 复制集合-泛型 * target目标 src来源 */ public void copy(List target,List src){ if (src.size() > target.size()){ for (int i = 0; i < src.size(); i++) { target.set(i,src.get(i)); } } }}public static void main(String[] agrs){ CollectionUtils collectionUtils = new CollectionUtils<>(); List fatherTargets = new ArrayList<>(); List fatherSources = new ArrayList<>(); //fatherSources.add... collectionUtils.copy(fatherTargets,fatherSources); //子类复制到父类 List sonSources = new ArrayList<>(); //sonSources.add... collectionUtils.copy(fatherTargets,sonSources); //把子类复制到父类的父类 List grandpaTargets = new ArrayList<>(); collectionUtils.copy(grandpaTargets,sonSources);//err 编译异常}
src入参调整为上边界通配符后,copy函数传入List sonSources就没问题了,此时的copy函数比相较之前的更加灵活了,支持同类与父子类复制,接着又发现了一个问题,目前能复制到上一级父类,如果是多级父类,还无法支持,继续分析copy函数,copy函数的入参target在函数内部只涉及到了add函数,即写操作(只作为add函数入参),符合只写不读原则,可以采用下边界通配符,调整代码如下
/** * 集合工具 */public class CollectionUtils{ /** * 复制集合-泛型 * target目标 src来源 */ public void copy(List target,List src){ if (src.size() > target.size()){ for (int i = 0; i < src.size(); i++) { target.set(i,src.get(i)); } } }}public static void main(String[] agrs){ CollectionUtils collectionUtils = new CollectionUtils<>(); List fatherTargets = new ArrayList<>(); List fatherSources = new ArrayList<>(); //fatherSources.add... collectionUtils.copy(fatherTargets,fatherSources); //子类复制到父类 List sonSources = new ArrayList<>(); //sonSources.add... collectionUtils.copy(fatherTargets,sonSources); //把子类复制到父类的父类 List grandpaTargets = new ArrayList<>(); collectionUtils.copy(grandpaTargets,sonSources);}
copy函数终于是完善了,可以说现在是真正支持父子类复制,不难发现copy函数的设计还是遵循通配符原则的,target作为目标集合,只做写入,符合只写不读原则,采用了下边界通配符,src作为来源集合写入到target目标集合,只做读取,符合只读不写原则,采用了上边界通配符,最后设计出来的copy函数,它的灵活性与适用范围是远超方式设计的。
最后什么时候用通配符,如果参数泛型类即要读也要写,那么就不推荐使用,使用正常的泛型即可,如果参数泛型类只读或写,就可以根据原则采用对应的上下边界 ,是不是十分简单,最后再说一次读写的含义,这块确实很容易晕
读:所谓读是指参数泛型类,泛型只作为该参数类的函数返回类型,那这个函数就是读,List作为参数泛型类,它的get函数就是读 写:所谓写是指参数泛型类,泛型只作为该参数类的函数入参,那这个函数就是写,List作为参数泛型类,它的add函数就是读 最后可以推荐下大家可以思考下Stream的forEach函数与map函数的设计,在Java1.8 Stream中是大量用到了通配符设计
-----------------------------------------------------------------/** * 下边界通配符 */void forEach(Consumer action);public interface Consumer { //写方法 void accept(T t);}-----------------------------------------------------------------/** * 上下边界通配符 */ Stream map(Function mapper)public interface Function { //读写方法,T只作为入参符合写,R只作为返回值,符合读 R apply(T t);}-----------------------------------------------------------------//代码案例public static void main(String[] agrs) { List fatherList = new ArrayList<>(); Consumer action = new Consumer() { @Override public void accept(Father father) { //执行father逻辑 } }; //下边界通配符向上转型 Consumer actionTwo = new Consumer() { @Override public void accept(Grandpa grandpa) { //执行grandpa逻辑 } }; Function mapper = new Function() { @Override public Grandpa apply(Father father) { //执行father逻辑后返回Grandpa return new Grandpa(); } }; //下边界通配符向上转型,下边界通配符向下转型 Function mapperTwo = new Function() { @Override public Son apply(Grandpa grandpa) { //执行grandpa逻辑后,返回Son return new Son(); } }; fatherList.stream().forEach(action); fatherList.stream().forEach(actionTwo); fatherList.stream().map(mapper); fatherList.stream().map(mapperTwo); }-----------------------------------------------------------------
有限制泛型场景 有限制泛型很简单了,应用场景就是你需要对泛型的参数类型做限制,就可以使用它,比如下面这段代码
public class GenericClass { public void test(T t){ //.... }}public static void main(String[] agrs){ GenericClass grandpaGeneric = new GenericClass<>(); grandpaGeneric.test(new Grandpa()); grandpaGeneric.test(new Father()); grandpaGeneric.test(new Son()); GenericClass fatherGeneric = new GenericClass<>(); fatherGeneric.test(new Father()); fatherGeneric.test(new Son()); GenericClass sonGeneric = new GenericClass<>(); sonGeneric.test(new Son()); GenericClass ObjectGeneric = new GenericClass<>();//err 编译异常 }GenericClass泛型参数化类型被限制为Grandpa或其子类,就这么简单,千万不要把有限制泛型与上边界通配符搞混了,这两个不是同一个东西( != ),不需要遵循上边界通配符的原则,它就是简单的泛型参数化类型限制,而且没有super的写法。
递归泛型场景 在有限制泛型的基础上,又可以衍生出递归泛型,就是自身需要使用到自身,比如集合进行自定义元素大小比较的时候,通常会配合Comparable接口来完成,看看下面这段代码
public class Person implements Comparable
{ private int age; public Person(int age) { this.age = age; } public int getAge() { return age; } @Override public int compareTo(Person o) { // 0代表相等 1代表大于 <0代表小于 return this.age - o.age; }}/** * 集合工具 */public class CollectionUtils{ /** * 获取集合最大值 */ public static > E max(List list){ E result = null; for (E e : list) { if (result == null || e.compareTo(result) > 0){ result = e; } } return result; }}public static void main(String[] agrs){ List
personList = new ArrayList<>(); personList.add(new Person(12)); personList.add(new Person(19)); personList.add(new Person(20)); personList.add(new Person(5)); personList.add(new Person(18)); //返回年龄最大的Person元素 Person max = CollectionUtils.max(personList);}
重点关注max泛型函数,max泛型函数的目标是返回集合最大的元素,内部比较元素大小,取最大值返回,也就说需要和同类型元素做比较,>含义是,泛型E必须是Comparable或其子类/实现类,因为比较元素是同类型,所以Comparable泛型也是E,最终接收的List泛型参数化类型必须实现了Comparable接口,并且Comparable接口填充的泛型也是该参数化类型,就像上述代码一样。
关于我 这里是阿星,一个热爱技术的Java程序猿,在公众号 「程序猿阿星」 里将会定期分享操作系统、计算机网络、Java、分布式、数据库等精品原创文章,2021,与您在 Be Better 的路上共同成长!。
非常感谢各位人才能 看到这里,原创不易,文章有帮助可以「点个赞」或「分享与评论」,都是支持(莫要白嫖)!
愿你我都能奔赴在各自想去的路上,我们下篇文章见!