java中的泛型详解
来源:互联网 发布:淘宝鬼脚七微信号 编辑:程序博客网 时间:2024/06/05 03:14
1.什么是泛型
泛型实用的另一个名字叫 “ 参数化类型 ” 。一般参数的类型在java的方法中或者说在类中,要么是具体的类型,要么是自定义的类型,再就是基本类型了。而此处的限制对于我们的java代码是一种束缚,不便于我们编写随意任意多种类型的代码。使用泛型,我们可以有效地避免强制类型转换。使得编译器在编译时期通过保留的参数类型,从而去执行参数类型检查,然后进行参数类型转换,在编译时期就确定了参数的类型,使得java类型安全提高了。然而常常容器类和泛型一起出场的概率很高。从两者一起使用来看,泛型也无形中给容器中的参数类型增加的约束。简而言之,泛型就是为我们在编译时指明了参数的类型(初识)。
2.初识java泛型
泛型类
拥有单个泛型的类的例程:
public class Coffee<T>{private T t;public Coffee(T t) {this.t = t;}public T getT() {return t;}public void setT(T t) {this.t = t;}public static void main(String[] args) {Coffee<String> coffee=new Coffee<String>("单身汪");String tVal =coffee.getT();System.out.println(tVal);}}解释:和正常的类的操作是一样的,只是指明了类中构造器的参数类型按需输入。
拥有多个泛型的类的例程:
有的时候,我们想使用一个方法调用返回多个对象,那么我们可能会使用集合容器来解决。对于return只能返回单个对象而言,但是我们有时也想创建一个对象,用它来持有想要返回的多个对象,这就需要使用多个泛型了。
在java中会使用元组这个概念,就是它是将一组对象直接打包存储于其中的一个单一对象。然而这个对象允许读取其中的元素,不允许向其中存放新的对象。
例程:
class HolderPlus<T,K>{private T t;private K k;public HolderPlus(T t,K k){this.t=t;this.k=k;}@Overridepublic String toString() {return "HolderPlus [t=" + t + ", k=" + k + "]";}}如果还想要增加泛型中的类型个数,那么就应该使用类的继承。
例程:
public class Holder<T,K,H> extends HolderPlus<T,K>{public final H h;public Holder(T t,K k,H h){super(t,k);this.h=h;}@Overridepublic String toString() {return "Holder [h=" + h + ", toString()=" + super.toString() + "]";}public static void main(String[] args) {Holder<String,Integer,Boolean> holder =new Holder<String,Integer,Boolean>("abc",123,true);System.out.println(holder);}}结果:
泛型接口:
泛型在接口中也是经常使用的。譬如我们的生成器。就是设计模式中创建模式类目中工厂方法模式的一种应用。只是在使用生成器创建新的对象时,不需要知道创建了什么类型的对象就可以创建新的对象。而这也是泛型的本质。
如果对工厂方法模式不清楚,那么看我的博客: 工厂方法模式(java版)
工厂生产咖啡的例子(工厂方法模式+泛型):
咖啡产品:
public class Coffee{public void makeCoffee(){System.out.println("制造出了咖啡!");}}解释:这个咖啡产品类就是所谓的具体产品,这里就不设计抽象产品接口了。
工厂接口:
public interface IFactory<T> {public T produceCoffee();}解释:工厂接口,这个就是那个所谓的抽象工厂,里面使用了,至于工厂生产什么类,并不清楚。
工厂实现类:
public class ConcreteFactory implements IFactory<Coffee>{public static void main(String[] args) {IFactory<Coffee> factory =new ConcreteFactory();factory.produceCoffee().makeCoffee();}@Overridepublic Coffee produceCoffee() {// TODO Auto-generated method stubreturn new Coffee();}}解释:具体的工厂类,实现了抽象的工厂接口,对接口进行了具体实现,并且完成了生产咖啡同时也提供了对外生产咖啡的接口。
泛型方法:
泛型方法其实就是独立于类而产生变化,而在我看来其实,泛型方法对操作相比泛型类来说细粒度更高了。那么如果我们可以采用泛型方法完成所需要的功能,那么我们就无需采用泛型类来完成。这样就可以使问题更加清晰。
例程:
public class Number{ public static <T> void printNum(T t){ System.out.println(t); } public static void main(String[] args) { String strArr="abc"; printNum(strArr); Integer id=45; printNum(id); }}注意:
原来我们使用泛型类时,必须在创建对象时指定类的参数的类型,但是我们使用泛型方法的时候,不用指定参数的类型。就像上面的例程我们既可以打印String数组也可以打印Integer数组。这样编译器就为我们找出了具体的类型,叫做 “ 类型参数推断 ” 。
3.深入了解泛型
泛型的擦除
什么是泛型的擦除?
泛型的擦除就是当你在使用泛型的时候,任何具体的类型信息都会被擦除,以至于你只知道你只是在使用一个对象而已。
例程(泛型擦除设计策略现象):
public class GenericInfo {public static void main(String[] args) {List<String> list =new ArrayList<String>();Map<String,Integer> map=new HashMap<String,Integer>();System.out.println(Arrays.toString(list.getClass().getTypeParameters()));System.out.println(Arrays.toString(map.getClass().getTypeParameters()));}}结果:
解释:Class.getTypeParameters()方法,返回一个类型数组,里面装的都是泛型类型变量声明的泛型 类型 。所以得到的结果是在 “ 泛型的内部,你无法获取有关泛型参数类型的信息 ” 。而这正是泛型使用擦除来实现的(当你使用泛型的类型时,任何具体的泛型类型信息都会给擦除掉)。所以你只能知道它是一个对象而已了。
泛型的擦除引发编程中的问题?
例程(擦除引发的问题):
解释:从例程中可以看出,程序检查报错,意思是方法f()被T类型的调用不可以,无法将T类型解析出HasF类型。这个原因正是由于
泛型的擦除,使得编译器无法将T映射到HasF类型。而这里也说明T是没有边界的,需要引入边界的概念,让T可以映射到HasF。
解决办法:
解释:这里使用泛型继承的方式,让T有了边界,那就是必须得继承HasF这个类。而编译器实际上就会把类型参数替换为它的擦除。T擦除到了HasF,就好像HasF替换了T。
泛型的兼容性:
由于泛型擦除的出现,泛型类型会被当作第二类类型。为啥这么说呢。除非泛型类型只有在静态类型检查才出现,否则程序中的泛型都会被擦除,然后替换为它们的非泛型的上界。(一般普通的类型变量在未指定边界时都会擦除为Object)。
泛型的擦除存在的问题?
(1)不能创建泛型数组?
在创建数组的时候,必须要知道数组中元素的类型,然后会记住这个类型,如果往数组里插入元素,那么就会做类型检查。但是不巧的是java泛型具有擦除的特性,在运行时,参数类型会被擦除掉。导致无法确定数组的类型了,所以不能创建泛型数组了。(这个地方就不写错误例程了)。
解决创建泛型数组的策略:
Array.newInstance例程:
public class GenericArray<T>{private Class<T> kind;public GenericArray(Class<T> kind) {super();this.kind = kind;}T[] create(int size){return (T[]) Array.newInstance(kind, size);}public static void main(String[] args) {GenericArray<String> n =new GenericArray<String>(String.class);String[] strArr=n.create(9);System.out.println(Arrays.toString(strArr));}}
泛型的擦除问题的解决办法
(1)显式地传递类型
public class ClassTypeCapture<T>{Class<T> kind;public ClassTypeCapture(Class<T> kind) {super();this.kind = kind;}public boolean f(Object arg){return kind.isInstance(arg);}public static void main(String[] args) {ClassTypeCapture<Building> ctt1 =new ClassTypeCapture<Building>(Building.class);System.out.println(ctt1.f(new Building()));System.out.println(ctt1.f(new House()));ClassTypeCapture<House> ctt2 =new ClassTypeCapture<House>(House.class);System.out.println(ctt2.f(new Building()));System.out.println(ctt2.f(new House()));}}class Building{}class House extends Building{}结果:
true
true
false
true
编译器保证了类型标签与泛型参数匹配。上面kind.isInstance(arg);方法就是arg是否可以向kind进行转化。Building类是House类的父类。说明Building类型是House类型的上界,转化是没问题的。所以结果是true。反之错误。
泛型的边界
什么是泛型的边界?
泛型的边界实际上就是泛型的参数类型的设置的限制条件。如前面介绍的继承关系中的上界等等。
边界在类级别上的应用
public interface IMake {//制造产品public void makeProduct();}
public class MachineImpl implements IMake{@Overridepublic void makeProduct() {// TODO Auto-generated method stubSystem.out.println("制造产品");}}
public class Mobile<T extends MachineImpl & IMake>{private T t;public Mobile(T t) {super();this.t = t;}public T getT() {return t;}public static void main(String[] args) {Mobile<MachineImpl> mobile =new Mobile<MachineImpl>(new MachineImpl());mobile.getT().makeProduct();}}
边界在方法级别上的应用
public interface ICharge {//给产品充电public void chargeProduct();}
public interface IMake {//制造产品public void makeProduct();}
public class MachineImpl implements IMake,ICharge{@Overridepublic void chargeProduct() {// TODO Auto-generated method stubSystem.out.println("为产品充电");}@Overridepublic void makeProduct() {// TODO Auto-generated method stubSystem.out.println("制造产品");}}
public class Mobile {static <T extends IMake> void make(T t){t.makeProduct();}static <T extends ICharge & IMake> void charge(T t){t.chargeProduct();t.makeProduct();}public static void main(String[] args) {MachineImpl machine =new MachineImpl();make(machine);charge(machine);}}解释:可以看到泛型作为函数的参数可以让ICharge和IMake接口作为边界。
通配符
通配符在协变中的应用
协变实际就是小范围的类型代替大范围的类型。
数组的协变性
例程:
public class CovariantArray{public static void main(String[] args) {Number[] nums =new Integer[10];nums[0] =Integer.valueOf(1);try{nums[1] =new Float(1.0);}catch(Exception e){e.printStackTrace();}try{nums[2] =Byte.valueOf((byte)1);}catch(Exception e){e.printStackTrace();}}}结果:
解释:可以看到数组具有协变性。
集合框架的协变性
可以看到集合框架是不支持泛型的协变的。所以使用通配符来模拟协变的实现。
public class CovariantList {public static void main(String[] args) {List<? extends Fruit> list =new ArrayList<Apple>();}}class Fruit{}class Apple extends Fruit{}
通配符在逆变中的应用
逆变实际上就是大范围类型代替小范围类型。java泛型中也同样不支持逆变,所以使用通配符模拟逆变实现。
public class CovariantList {public static void main(String[] args) {List<? super Apple> list =new ArrayList<Fruit>();}}class Fruit{}class Apple extends Fruit{}
- java中的泛型详解
- 详解Java中的泛型
- Java中的泛型详解(1):基本使用
- Java中的泛型详解(2):高级进阶
- java中的static详解
- Java中的HashMap详解
- java中的static详解
- Java中的finalize详解
- java中的static详解
- java中的static详解
- Java中的finalize详解
- java中的static详解
- Java中的static详解
- java中的native详解
- java中的异常详解
- Java中的finalize详解
- java中的static详解
- 详解Java中的clone
- 机器学习系列(6)_从白富美相亲看特征预处理与选择(下)
- tensorflow安装:续caffe安装
- *[Lintcode]带环链表 II
- Struts2 常用的常量配置
- linux常用命令集
- java中的泛型详解
- 老生常谈之UITableView的性能优化
- spring事务管理机制
- html 盒子模型基础(文档流,浮动,页面布局)(五)
- Spring基础知识汇总
- 设置session失效的几种方法
- 微信支付
- JDK源码阅读——LinkedList
- thinkphp核心源码注释|Log.class.php