浅谈下java泛型
2015-08-31 08:56
453 查看
关于泛型,大家也许在java中经常听到这个词语,上周在面试百度的时候问到了这个术语,却发现了解的少之又少,居然连定义都给不出来,于是下来恶补了下,准备面试果然自我提高的一种方法。
什么是泛型呢?我的理解是这样的,所谓泛型,其实是类型参数化的体现(就是类型也变成了参数,往往我们一般定义方法的参数的时候,形参的表示形式是 int a),这时候a 是形参实体的占位符,如果我们使用泛型了?传递参数的时候 T a,这时候,连类型都变成了占位符。泛型就是干了这样一件事情,把类型都变成了占位符。所以,综上所述,java泛型是对 Java 语言的类型系统的一种扩展,支持把类型作为一种运行时才确定的变量。
java为什么要提出泛型呢?其实是为了类型安全着想,比如下面一段代码
这段代码里面,大家可以看到在第四行会出现这样一种类型转换的错误。这就是java引入泛型的原因:不希望在运行的时候出现类型转换错误的异常。
引入了泛型之后,就是这样一种情况:
在把类型转换不安全的问题放到了编译期,程序本身编译就不通过,根本不会产生运行时异常,这样就可以大大减少由于程序员的疏忽带来的类型转换不安全问题了。
java 泛型的基础:
如果大家要看java泛型的基础应用的话,这个链接可以给大家展示下,java泛型的一些基础应用:http://lichaozhangobj.iteye.com/blog/476911
这里简单说下java常用的泛型类和泛型方法:
所谓泛型类,就是运行时才确定其泛型的具体类型的类,就是泛型类,泛型方法,就是在运行是才确定方法体中的参数的类型的方法,就是泛型方法。简单是示例比如大家看一下的代码:
上述代码里面,Pair 表示一个泛型类,大家可以看到<>是泛型声明列表比如class A
答案是不可以的,原因也很简单,泛型是在运行才确定的类型,根本不能这样实例化。编译器压根不知道你需要什么类型。
2.泛型是否存在数组?比如:
为什么不能存在泛型数组呢?
我们首先要明白两个前提:
1.这个问题涉及java的泛型其实不是真泛型,java的泛型是在编译器的层次做的,而jvm是不认识的泛型的。在运行的时候,所有的泛型都会(如果没有上界的话)就会转化为Object对象(详细解释见之后的类型擦除)2.在Java中,Object[]数组可以是任何数组的父类,或者说,任何一个数组都可以向上转型成它在定义时指定元素类型的父类的数组,这个时候如果我们往里面放不同于原始数据类型 但是满足后来使用的父类类型的话,编译不会有问题,但是在运行时会检查加入数组的对象的类型,于是会抛ArrayStoreException。比如以下的代码:
好,现在我们了解了这些问题之后,想想,如果允许泛型数组是什么情况呢?那么上面那条代码就会变成这样:
然后我们可以让这些数组变成Object[],然后放入Point类型。这样做不但编译器不能发现类型错误,就连运行时的数组存储检查对它也无能为力。本来Point 里面是String,却存储了Integer类型。运行结果不言而喻会怎么样。所以Java的泛型没有数组。
3.List 里面可以放入int类型吗?
一般想来是不可以的,因为在编译器上那一关就不让你过。
但是,java的泛型的假泛型,没有涉及到jvm那一层,所以,我们可以通过反射来实现这个“功能”:
java的反射本来就是运行来获取对象的字段,方法等,然后通过反射调用,既然java的泛型是假泛型,所以运行是类型擦除,自然可以添加int类型了啊。不过这段代码在虽然编译通过了,但是在运行的时候会抛出ClassCastException的异常。
4.List<?> list 为啥不让add添加任何元素?
其实很简单,?是一个通配符,代表一群类型,编译器不知道程序员向对象中添加的任何类型,所以add的方法在编译器看来不安全,所以不会add方法通过编译。
5. 以下程序什么编译不通过:
原因是java的泛型是不支持协变的(java的数组是支持协变的)。至于为什么不支持协变,我个人觉得是编译器不让吧,网上查了下 ,C#就是支持的。
以上就泛型的一些问题,我们每次提到泛型的时候都说了下java的泛型是编译器认识,但是jvm不认识,到底是什么意思?这就涉及泛型的实现方式:类型擦除。
java的泛型其实是在编译器层次做的,而jvm在运行的时候是不认识具体的类型的,所有在编译期申明的泛型都会被抹去,运行的时候根本没有具体的类型。
举个例子:
这个类在运行的时候,其实变成了这样:
但是,那运行的时候的实际类型是怎么弄出来的呢?编译器悄悄地做了类型转换,比如Pair 的setValue方法,编译器做了this.value=(String)value, getValue方法:return(String)this.value; 因为程序员在申明这个类的时候,应经把泛型定位了String,所以编译器按照程序员的方法私下做得类型转换是合理的,一般不会发生ClassCastException的异常。这也是java的泛型的初衷,不让程序出现类型转换的异常。
总的来说,java的泛型通过类型擦除来实现的,类型擦除就让java的泛型只是在编译器层次实现。所以java的泛型是假泛型。
接下来说一个泛型和多态的冲突:
泛型的类型擦除会出现什么问题呢?请看一下代码:
乍一看好像没有什么问题啊,但是通过类型擦除分析呢?父类pair类变成了:
这样的看来的话,子类StringBean的方法不应该是覆写吧,应该是重载才对(因为子类的StringBean的setVlaue方法的参数是String),但是真的是重载吗?我们试一试:
然而程序告诉我们,这个并不是重载,真的是覆盖,那么这个到底是怎么实现的呢?这个就是桥方法,什么是桥方法呢?我们通过看这个问题是怎么解决的
其实编译器是这样的做的:
生成了两个方法:
这两个方法方法就是编译自己悄悄写的桥方法,这两个方法才是真正的覆写了父类的方法(但是调用者是没有办法调用到的),可以看到,这两个桥方法就是悄悄地调用了子类的方法,不过做了下类型转换,子类真正覆盖掉父类的方法就是这两个桥方法。(注:编译器写的桥方法是我们不能调用到的)
所以,桥方法就是编译器写的,用来解决多态的方法。这个方法其实就是调用了子类的方法,来实现对父类的方法的覆盖:所以我们会看到一种假象,认为子类StringBean的setValue和getValue对父类进行了覆盖。
好了,以上的就是我对java泛型的理解。虽然理解的仍然不深刻,但是总结下:java的泛型就是类型的参数化,要到运行的时候才确定的类型,这样做最大的好处就是使得让程序在类型转换的时候更加安全,保证了java的类型安全问题。
如果大家对java泛型有其他问题,可以参考这篇博客http://blog.csdn.net/lonelyroamer/article/details/7868820
什么是泛型呢?我的理解是这样的,所谓泛型,其实是类型参数化的体现(就是类型也变成了参数,往往我们一般定义方法的参数的时候,形参的表示形式是 int a),这时候a 是形参实体的占位符,如果我们使用泛型了?传递参数的时候 T a,这时候,连类型都变成了占位符。泛型就是干了这样一件事情,把类型都变成了占位符。所以,综上所述,java泛型是对 Java 语言的类型系统的一种扩展,支持把类型作为一种运行时才确定的变量。
java为什么要提出泛型呢?其实是为了类型安全着想,比如下面一段代码
HashMap map=new HashMap(); map.put("1","e"); String str=(String)map.get("1"); int a=(int)map.get("1")//ClassCastException
这段代码里面,大家可以看到在第四行会出现这样一种类型转换的错误。这就是java引入泛型的原因:不希望在运行的时候出现类型转换错误的异常。
引入了泛型之后,就是这样一种情况:
HashMap<String,String> map=new HashMap<String,String>(); map.put("1","ae"); String str=map.get("1"); int a=map.get("1")//编译不通过
在把类型转换不安全的问题放到了编译期,程序本身编译就不通过,根本不会产生运行时异常,这样就可以大大减少由于程序员的疏忽带来的类型转换不安全问题了。
java 泛型的基础:
如果大家要看java泛型的基础应用的话,这个链接可以给大家展示下,java泛型的一些基础应用:http://lichaozhangobj.iteye.com/blog/476911
这里简单说下java常用的泛型类和泛型方法:
所谓泛型类,就是运行时才确定其泛型的具体类型的类,就是泛型类,泛型方法,就是在运行是才确定方法体中的参数的类型的方法,就是泛型方法。简单是示例比如大家看一下的代码:
public class Pair<T> { protected T value; public void setValue(T value){ System.out.println("pair method"); this.value=value; } public T getValue(){ return(this.value); } }
上述代码里面,Pair 表示一个泛型类,大家可以看到<>是泛型声明列表比如class A
T t=new T();
答案是不可以的,原因也很简单,泛型是在运行才确定的类型,根本不能这样实例化。编译器压根不知道你需要什么类型。
2.泛型是否存在数组?比如:
public class Piont<T> { private T var; public T getVar(){ return(this.var); } public void setVar(T var){ this.var=var; } } Piont<String>[] piont=new Piont<String>[10];//编译不通过
为什么不能存在泛型数组呢?
我们首先要明白两个前提:
1.这个问题涉及java的泛型其实不是真泛型,java的泛型是在编译器的层次做的,而jvm是不认识的泛型的。在运行的时候,所有的泛型都会(如果没有上界的话)就会转化为Object对象(详细解释见之后的类型擦除)2.在Java中,Object[]数组可以是任何数组的父类,或者说,任何一个数组都可以向上转型成它在定义时指定元素类型的父类的数组,这个时候如果我们往里面放不同于原始数据类型 但是满足后来使用的父类类型的话,编译不会有问题,但是在运行时会检查加入数组的对象的类型,于是会抛ArrayStoreException。比如以下的代码:
String [] strArr=new String[10]; Object [] objArr=strArr; objArr[0]=1;// arraystoreexception
好,现在我们了解了这些问题之后,想想,如果允许泛型数组是什么情况呢?那么上面那条代码就会变成这样:
Point<Object> point =new Point<Object>[]();
然后我们可以让这些数组变成Object[],然后放入Point类型。这样做不但编译器不能发现类型错误,就连运行时的数组存储检查对它也无能为力。本来Point 里面是String,却存储了Integer类型。运行结果不言而喻会怎么样。所以Java的泛型没有数组。
3.List 里面可以放入int类型吗?
一般想来是不可以的,因为在编译器上那一关就不让你过。
但是,java的泛型的假泛型,没有涉及到jvm那一层,所以,我们可以通过反射来实现这个“功能”:
List<String> list=new ArrayList<String>(); list.add("ae"); list.getClass().getMethod("add",Object.class).invoke(list,1);//通过反射在运行是添加
java的反射本来就是运行来获取对象的字段,方法等,然后通过反射调用,既然java的泛型是假泛型,所以运行是类型擦除,自然可以添加int类型了啊。不过这段代码在虽然编译通过了,但是在运行的时候会抛出ClassCastException的异常。
4.List<?> list 为啥不让add添加任何元素?
其实很简单,?是一个通配符,代表一群类型,编译器不知道程序员向对象中添加的任何类型,所以add的方法在编译器看来不安全,所以不会add方法通过编译。
5. 以下程序什么编译不通过:
ArrayList<String> list=new ArrayList<String>(); List<Object> list1=list;//eroor
原因是java的泛型是不支持协变的(java的数组是支持协变的)。至于为什么不支持协变,我个人觉得是编译器不让吧,网上查了下 ,C#就是支持的。
以上就泛型的一些问题,我们每次提到泛型的时候都说了下java的泛型是编译器认识,但是jvm不认识,到底是什么意思?这就涉及泛型的实现方式:类型擦除。
java的泛型其实是在编译器层次做的,而jvm在运行的时候是不认识具体的类型的,所有在编译期申明的泛型都会被抹去,运行的时候根本没有具体的类型。
举个例子:
public class Pair<T> { private T value; public void setValue(T value){ this.value=value; } public T getValue(){ return(this.value); } }
这个类在运行的时候,其实变成了这样:
public class Pair { private Object value; public void setValue(Object value){ this.value=value; } public Object getValue(){ return(this.value); } }
但是,那运行的时候的实际类型是怎么弄出来的呢?编译器悄悄地做了类型转换,比如Pair 的setValue方法,编译器做了this.value=(String)value, getValue方法:return(String)this.value; 因为程序员在申明这个类的时候,应经把泛型定位了String,所以编译器按照程序员的方法私下做得类型转换是合理的,一般不会发生ClassCastException的异常。这也是java的泛型的初衷,不让程序出现类型转换的异常。
总的来说,java的泛型通过类型擦除来实现的,类型擦除就让java的泛型只是在编译器层次实现。所以java的泛型是假泛型。
接下来说一个泛型和多态的冲突:
泛型的类型擦除会出现什么问题呢?请看一下代码:
public class Pair<T> { protected T value; public void setValue(T value){ this.value=value; } public T getValue(){ return(this.value); } } public class StringBean extends Pair<String> { @Override public void setValue(String value){ super.setValue(value); this.value=value; } @Override public String getValue(){ return (this.value); } }
乍一看好像没有什么问题啊,但是通过类型擦除分析呢?父类pair类变成了:
public class Pair { private Object value; public void setValue(Object value){ this.value=value; } public Object getValue(){ return(this.value); } }
这样的看来的话,子类StringBean的方法不应该是覆写吧,应该是重载才对(因为子类的StringBean的setVlaue方法的参数是String),但是真的是重载吗?我们试一试:
StringBean stringBean=new StringBean(); stringBean.setValue("ae"); stringBean.setValue(new Object());//编译错误
然而程序告诉我们,这个并不是重载,真的是覆盖,那么这个到底是怎么实现的呢?这个就是桥方法,什么是桥方法呢?我们通过看这个问题是怎么解决的
其实编译器是这样的做的:
生成了两个方法:
public void setValue(Object value){ this.setValue((String)value); } public Object getValue(){ return(this.getValue()); }
这两个方法方法就是编译自己悄悄写的桥方法,这两个方法才是真正的覆写了父类的方法(但是调用者是没有办法调用到的),可以看到,这两个桥方法就是悄悄地调用了子类的方法,不过做了下类型转换,子类真正覆盖掉父类的方法就是这两个桥方法。(注:编译器写的桥方法是我们不能调用到的)
所以,桥方法就是编译器写的,用来解决多态的方法。这个方法其实就是调用了子类的方法,来实现对父类的方法的覆盖:所以我们会看到一种假象,认为子类StringBean的setValue和getValue对父类进行了覆盖。
好了,以上的就是我对java泛型的理解。虽然理解的仍然不深刻,但是总结下:java的泛型就是类型的参数化,要到运行的时候才确定的类型,这样做最大的好处就是使得让程序在类型转换的时候更加安全,保证了java的类型安全问题。
如果大家对java泛型有其他问题,可以参考这篇博客http://blog.csdn.net/lonelyroamer/article/details/7868820
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树
- [原创]java局域网聊天系统