今日头条看到一篇文章写泛型很好,记录下来..........
泛型的英文是generic,generic的意思是通用,而翻译成中文,泛意为广泛,型是类型,所以泛型就是广泛适用的类型。但是泛型有一种较为准确的说法就是为列如参数化类型,或者是可以将类型当做参数传递给一类或者是方法。
那么如何解释类型参数化呢?
public class Cache{
Object value;
public Object getValue(){
return value
}
public void setValue(Object value){
this.value = value;
}
}
假设Cache能后存取任何类型的值,于是,我们可以这样使用它:
Cache cache = new Cache();
cache.setValue(123);
int value = (int) cache.getValue();
cache.setValue("Hello World");
String value1 = (String) cache.getValue();
使用这种方法很简单,只要我们做正确的强制类型转换即可,但是泛型却给我们带来了不一样的编程体验哦:
public class Cache<T> {
T value;
public Object getValue(){
return value;
}
public void setValue(T value){
this.value = value;
}
}
以上代码就是泛型,他讲value这个属性的类型也参数化了,这就是所谓的参数化类型了,使用方法:
Cache<String> cache = new Cache<String>();
cache.setValue("123");
String value = cache.getValue();
Cache<Integer> cacheInte = new Cache<Integer>();
cacheInte.setValue(123);
int valueInt = cacheInte.getValue();
显而易见的好处就是它不在需要对获取到的结果值再强制类型转换了,还有另外一点不同的:
Cache<String> cache1 = new Cache<String>();
cache1.setValue("123");
String value1 = cache1.getValue();
cache.setValue(123);
如上代码,泛型除了可以将类型参数化外,而参数一旦确认好了,如果类型不匹配,编译器就不通过。
上面最后一行代码,无法将一个Integer对象设置到cache1中,因此这里的泛型只接受String的类型。
所以,综上所述,得出以下结论:
1、与普通的Object代表一切类型这样简单粗暴而言,泛型是的数据的类型可以像参数一样有外部传递进来。它提供了一种扩展能力,它更符合面向抽象开发的软件编程宗旨。
2、当具体的类型确定后,泛型有提供了一种类型检测的机制,只有相匹配的数据才能正常的赋值,否则编译器就不通过,所以说,他是一种类型安全检测机制,一定程度上提高了软件的安全性,防止出现低级的错误。
3、泛型提高了程序代码的可读性,不必要等到运行的时候才去强制转换,在定义或者实例化阶段,应为Cache<String> 这个类型显示的效果,程序员能够一目了然猜测出代码要操作的数据类型。
泛型的定义和使用:
泛型按照使用情况可以分为3种:
1、泛型类
2、泛型方法
3、泛型接口
泛型类:
我们可以这样定义一个泛型类
public class Test<T>{
T field;
}
尖括号<>中的T呗称作是类型参数,用于指代任何类型,事实上,T只是一种习惯性写法,如果你愿意,你可以这样写:
public class Test<Hello>{
Hellp field;
}
但是出于规范的目的,java还是建议我们用单个大写字母来代表了类型参数,常见的如:
1、T代表一般的任何类
2、E代表Element的意思,或者Exception异常的意思
3、K代表Key的意思
4、V代表Value的意思
5、S代表Subtype的意思,后续会介绍
如果一个类被<T>的形式定义,那么它就被定义为泛型类。
泛型类使用:
Test<Stirng> testStr = new Test<>():
Test<Integer> testInt = new Test<>();
只要在对泛型类创建实例的时候,在尖括号中赋值相应的类型即可,T就会被替换成对应的类型,如:String或者是Integer,你可以想象一下,当一个泛型类被创建时,内部自动扩展成下面的代码:
public class Test<String>{
String field;
}
当然,泛型类不至接收一个参数类型,它还可以这样接收多个类型参数:
public class MultiType<E,T>{
E value1;
T value2;
public E getValue1(){
return value1;
}
public T getValue2(){
return value2;
}
}
泛型方法使用:
public class Test1{
public <T> void resrMethod(T t){
}
}
泛型类和泛型方法不同之处是:类型参数也就是尖括号那一部分是写在返回值前面的,<T> 中的T被称为类型参数,而方法中的T被称为参数化类型,它不是运行时真正的参数。当然,声明的类型参数,其实也是可以当做返回值的类型的。
public <T> T testMethod(T t){
return null;
}
泛型类和泛型方法的共存现象
public class Test<T> {
public void testMethod(T t){
System.out.println(t.getClass().getName());
}
public <T> T testMethod1(T t){
return t;
}
}
上面代码中,Test<T> 是泛型类,testMethod是泛型类种的普通方法,而testMethod1是一个泛型方法,而泛型类中的类型参数与泛型方法中的类型参数是没有相应的联系的,泛型方法始终以自己定义的类型参数为准。
所以针对上面的代码,我们可以这样编写测试代码:
Test<String> t = new Test();
t.testMethod("generic");
Integer i = t.testMethod1(new Integer(1));
泛型类实际上类型参数是String,而传递给泛型方法的类型参数是Integer,两者互不相关。
但是,避免混淆,如果在一个泛型类中存在泛型方法,那么两者的类型参数最好不要同名,比如:Test<T> 代码可以更改为这样:
public class Test<T>{
public void testMethod(T t){
System.out.println(t.getClass().getname());
}
public <E> E testMethod1(E e){
return e;
}
}
泛型接口用法:
public interface interable<T>{
}
截止以上为泛型的介绍,下面看下通配符的使用。
通配符 ?
除了用<T> 表示泛型外,还有<?>这种形式,? 被称为通配符。
可能有童鞋认为,已经有了<T> 的形式,为什么还要引进<?> 这样的概念呢?
class Base{}
class Sub extends Base{}
Sub sun = new Sub();
Base base = sub;
上面代码显示,Base是Sub的父类,他们之间是继承关系,所以Sub的实例可以给一个Base引用赋值,那么
List<Sub> iSub = new ArrayList<>();
List<Base> iBase = iSub;
以上最后一行代码成立吗?编译是否可以通过?
答案肯定是 ”否“,编译器不会让他通过的,Sub是Base的子类,不代表List<Sub> 和List<Base>有继承关系。
但是,在现实编码中,确实有这样的需求,希望泛型能够处理某一范围内的数据类型,比如:某个类和它的子类,对此java引入了通配符这个概念。
所以:通配符的出现是为了指定泛型中的类型范围。
通配符有3中形式:
1、<?>被称作无限定的通配符
2、<? extends T> 被称作有上线的通配符
3、<? super T> 被称作有下线的通配符
无限定通配符
public void testWildCards(Collection<?> collection){
}
上面的代码中,方法内的参数是被无限定通配符修饰的Collection对象,它隐略地表达了一个意图或者可以说是限定的,那就是testWidCards()这个方法内部无需关注Collection中的真正类型,因为他是未知的,所以,你只能调用Collection中与类型无关的方法。
我们可以看到上面代码,当<?>存在时,Collection对象丧失了add()方法的功能,编译器不能通过。
我们再看以下代码:
List<?> wildList = new ArrayLisy<String>();
wildList.add(123);//编译不通过
有人说,<?> 提供了只读的功能,也就是他删减了增加具体类型元素的能力,只保留与具体类型无关的功能。它不管装载在这个容器内的元素是什么类型,它之关系元素的数量,容器是否为空,我想这样的需求很常见的。
有童鞋可能会想<?>既然作用这么渺小,那为什么还要引用它呢?
个人认为,提高了代码的可读性,程序员看到这段代码时,就能够迅速的对此建立极其简介的印象,能够快速的推断源码作者的意图。
<? extends T>
<?> 代表类型的未知,但是我们的确需要对于类型的描述再精确一点,我们希望在一个范围内确定类别,比如类型A及类型A的子类都可以。
public void testSub(Collection<? extends Base> para){
}
上面代码中,para这个Collection接受Base及Base的子类的类型。
但是,它任然丧失了写操作的能力,这就是说:
para.add(new Sub());
para.add(new Base());
任然不能编译通过。
没有关系,我们不知道具体类型,但是我们至少清楚了类型的范围。
<? super T>
这个和<? extends T> 想对应,代表T及T的超类
public void testSuper(Collection<? super Sub> para){
}
<? super T> 神奇的地方在于,它拥有一定程度的写操作的能力。
public void testSuper(Collection<? super Sub> para){
para.add(new Sub());//编译通过
para.add(new Base());//编译不通过
}
以上介绍了通配符,接下来介绍下通配符和类型参数的区别
通配符和类型参数的区别
一般而言,通配符能干的事情都可以用类型参数替换
如:
public void testWildCards(Collection<?> collection){}
可以被
public <T> void testWildCards(Collection<T> collection){}
替换。
值得注意的是,如果用泛型方法取代通配符,那么上面代码中collection是能够进行写操作的,只不过要进行强制转换。
public <T> void testWildCards(Collection<T> collection){
collection.add((T) new Integer(12));
collection.add((T) "123");
}
需要特别注意的是,类型参数适用于参数间的类型依赖关系,举例如下:
public class Test2<T, E extends T>{
T value1;
E value2;
}
public <D,S extend D> void test1(D d,S s){
}
E类型是T类型的子类,显示这种情况类型参数更合适。有一种情况是,通配符和类型参数一起使用
public <T> void test(T t, Collection<? extends T> collection){
}
如果一个方法的返回类型依赖于参数的类型,那么通配符也无能为力
public T test(T t){
return value;
}
类型檫除
泛型是java.1.5版本才引进的概念,在这之前是没有泛型的概念的,但显然,泛型能够和之前版本的代码很好的兼容。这是因为:泛型信息只存在于代码编译阶段,在进入JVM之前,于泛型相关的信息会被檫除掉,专业术语叫做类型檫除。
通俗的讲:泛型类和普通类在java虚拟机内是没有什么特别的地方,如下代码:
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());
结果为true,因为List<String>和List<Integer>在jvm中的class都是List.class。泛型信息被察除了。
有部分童鞋会问,那本String和Integer怎么办?
答案是泛型转译。
public class Erasure<T>{
T object;
public Erasure(T object){
this.object = object;
}
}
Erasure是一个泛型类,我们查看他在运行时的状态信息可以通过反射。
Erasure<String> erasure = new Erasure<String>("hello");
Class eclz = erasure.getClass();
System.out.println("erasure class is : " + eclz.getName());
打印结果:erasure class is : com.frank.test.Erasure