JAVA - 泛型
一、JAVA泛型的历史
泛型是程序设计语言的一种特性。允许程序员在编写强类型程序设计语言代码时定义一些可变部分,那些部分在使用前必须作出指明。 (百度)
JAVA 泛型(generics)是 JDK 5 中引入的一个新特性, 允许程序员在编程时指定类型参数,使编译器可以在编译代码时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
二、为什么要用泛型
这要从面向对象的特性说起
- 封装:是一个过程,将客观的对象封装为抽象的类
- 继承:是一种表现,继承者可以使用被继承对象拥有的功能
- 多态:是一种能力,它可以修改所继承的功能
我们在写代码时经常会遇到对象的类型转换(转型)问题,面向对象的转型发生在具有继承关系的对象之间(类或接口)
![image-20200815141556728](https://i-blog.csdnimg.cn/blog_migrate/809a856827167a59624080c5d43fc0dd.png)
-
向上转型:子类型向父类型转换,是一种平滑过渡,不需要强制类型转换。
@Test public void castTo(){ B b = new B(); A a1 = b; }
-
向下转型:父类型向子类型转换,是一种非常不安全的操作(父类是子类的实例才行),需要强制类型转换。
@Test public void castTo(){ A b = new B(); B b1 = (B)b; }
问题:如果父类不是子类的实例或者没有继承关系的对象之间做转型会抛出异常,并且异常无法在编译时发现。
@Test
public void castTo(){
A a1 = new A1();
B b = (B)a1; //java.lang.ClassCastException
}
泛型主要用于检查类型转换(转型)错误,在编写代码时,参数化声明使用对象的类型,让编译器可以帮助我们检查类型转换异常。
三、使用 <泛型>
3.1 泛型类
在类名后紧跟泛型声明,代表此类中拥有的泛型数量
声明
/**
* class 类名 <泛型>{
* …..
* }
*/
class GenericClass<T>{
private T key;
public T getKey(){
return key;
}
public void setKey(T key){
this.key = key;
}
}
使用
/**
* 类名<类型> 变量名 = new 类名<类型> (); // 1.5
* 类名<类型> 变量名 = new 类名 (); // 1.7+
*/
GenericClass<String> g = new GenericClass();
GenericClass<Integer> g2 = new GenericClass();
g.setKey("123");
g2.setKey(123);
3.2 多泛型的泛型类
在类名的泛型声明可以写多个用逗号分隔
声明
/**
*class 类名 <泛型1,泛型2>{
*…..
*}
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
class MultiGenericClass<T,U>{
private T prop;
private U key;
}
使用
/**
* 类名<类型1,类型2> 变量名 = new 类名<类型1,类型2> (); // 1.5
* 类名<类型1,类型2> 变量名 = new 类名 (); // 1.7+
*/
MultiGenericClass<String,Integer> mg = new MultiGenericClass();
mg.setKey(50);
mg.setProp("age");
MultiGenericClass<Double, BigDecimal> mg2 = new MultiGenericClass();
mg2.setProp(0.08);
mg2.setKey(new BigDecimal(10));
3.3 泛型接口
规则与泛型类一样
声明
/**
*interface 接口名 <泛型>{
*…..
*}
*/
interface IMessage<T>{
void print(T t);
}
使用
类实现接口
/**
*class 类名 implements 接口名<类型>{
*……
*}
*/
class MessageImpl implements IMessage<String>{
@Override
public void print(String o) {
System.out.println(o);
}
}
类实现接口,同时本身还要是一个泛型类
/**
*class 类名<泛型> implements 接口名<类型>{
*……
*}
*/
//这个T代表了本类中有一个泛型T
class MessageImpl<T> implements IMessage<String>{
private T code;
@Override
public void print(String o) {
System.out.println(o);
}
}
或者
/**
*class 类名<泛型> implements 接口名<泛型>{
*……
*}
*/
//类上的T代表了本类中有一个与接口上中相同T的泛型
class MessageImpl<T> implements IMessage<T>{
@Override
public void print(T o) {
System.out.println(o);
}
}
3.4 多泛型的泛型接口
声明
/**
*interface 接口名 <泛型1,泛型2>{
*…..
*}
*/
interface IMessage2<T,U>{
void print(T t);
U trans(U u);
}
使用
/**
*class 类名<泛型1,泛型2> implements 接口名<泛型1,泛型2>{
*……
*}
*/
class Message2Impl<T,U> implements IMessage2<T,U>{
@Override
public void print(T t) {
System.out.println(t);
}
@Override
public U trans(U u) {
return u;
}
}
/**
*class 类名 implements 接口名<类型1,类型2>{
*……
*}
*/
class Message2Impl implements IMessage2<String,Integer>{
@Override
public void print(String t) {
System.out.println(t);
}
@Override
public Integer trans(Integer u) {
return u;
}
}
3.5 泛型方法
泛型声明写在方法的返回类型声明之前,代表方法参数或返回对象中的泛型数量
声明
/**
*修饰符 <泛型> 返回泛型 方法名(参数泛型){
*…..
*}
*/
public <T> T say(T t){
log.info("class = "+t.getClass().getName());
return t;
}
使用
//类型 变量 = 方法(值);
String s = this.say("hello");
log.info("返回值:"+s);
Integer i = this.say(100);
log.info("返回值:"+i);
3.6 多泛型的泛型方法
声明
/**
*修饰符 <泛型1,泛型2> 返回泛型 方法名(参数泛型,参数泛型2){
*…..
*}
*/
public <T,U> T say(U u,T t){
log.info("class U = "+u.getClass().getName());
log.info("class T = "+t.getClass().getName());
return t;
}
使用
//类型 变量 = 方法(值1,值2);
String s = this.say(100,"ok");
log.info("返回值:"+s);
Integer i = this.say(0.5,20);
log.info("返回值:"+i);
四、泛型类的继承(接口同理)
-
当父类被继承时,父类的泛型会被擦除。
-
子类如果要使用泛型,必须在子类定义上声明。
-
子类如果要覆盖父类的泛型,必须在子类与继承父上同时声明。
-
如果实现类想要更多的泛型类型,必须在类的泛型声明中先保留接口中的泛型再添加自己的泛型(不需要保证顺序,但为了保证代码的可读性一定要保证顺序统一)
比如实现类除了泛型T之外还要追加一个泛型U
//这样写是编辑不通过的,第一个泛型必须是接口中的T class SubGenericClass<U> implements GenericClass<T>{ private U code; @Override public T getKey() { return super.getKey(); } }
//要保留接口中的泛型T,在T之后追加新的泛型U,T和U的顺序先后无所谓 class SubGenericClass<T,U> implements GenericClass<T>{ private U code; @Override public T getKey() { return super.getKey(); } }
五、泛型的上下界
5.1 上界
上界限定符: extends
![image-20200815141556728](https://i-blog.csdnimg.cn/blog_migrate/809a856827167a59624080c5d43fc0dd.png)
- 限定类型为指定类型的子类
class WildcardSubClass<T extends Number>{
Integer addForInt(T t1,T t2){
return t1.intValue()+t2.intValue();
}
}
public void test(){
WildcardSubClass<Double> w1 = new WildcardSubClass();
Integer integer = w1.addForInt(1.2, 1.6);
log.info("result = {}",integer);
WildcardSubClass<Float> w2 = new WildcardSubClass();
Integer integer2 = w2.addForInt(1.1f,2.2f);
log.info("result2 = {}",integer2);
}
- 支持getter,返回上界类型,不支持setter(null除外)
@Data
class TestClass<T>{
private T item;
}
public void test(){
TestClass<? extends Number> t = new TestClass();
Number item = t.getItem();
Integer value = 11;
t.setItem(value);//报错
t.setItem(null);//不报错
}
5.2 下界
下界限定符: super
![image-20200815141556728](https://i-blog.csdnimg.cn/blog_migrate/809a856827167a59624080c5d43fc0dd.png)
-
不能用于泛型类或接口声明,所有类都继承Object,所以传入一个Object类型总是对的,因此super的类声明限定没有意义。
支持setter,getter只能返回Object
@Data class TestClass<T>{ private T item; } public void test(){ TestClass<? super Integer> t = new TestClass(); Number item = t.getItem();//报错 Object obj = t.getItem(); Integer value = 11; t.setItem(value); }
注意:setter方法接受super界定类的子类,并不是父类
GenericM<? super B> gm = new GenericM(); gm.setM(new C()); gm.setM(new A());//报错
总结:如果想让你的泛型对象有get的能力用extends,如果想要有set的能力用super,下面用一段Collections.copy(List dest,List src)
方法的代码为例,这个方法是把src列表中的元素替换到dest列表中的对应上
//super限定符可以让dest拥有set的能力,extends让src有get的能力
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
...
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
...
}
实际操作一下
@Test
public void testCopy(){
List<A> lista = Arrays.asList(new A(),new A(),new A(),new A1());
List<B> listb = Arrays.asList(new B(),new B(),new C());
Collections.copy(lista,listb);
}
操作结果
六、补充的一些内容
-
在声明泛型对象变量时,如果有用到界定符(extends,super),界定符右侧是类型的说明,” ?”加在界定符左侧代替声明时的变量符号。extends界定符可以解决通配符getter问题;super界定符可以解决通配符setter问题。
-
假设B继承A,而TestClass<A>与TestClass<B>没有继承关系,只能通过super或extends建立继承关系。
-
泛型的类型必须是引用类型,同一泛型类的不同泛型实例在赋值之前equals总相等。
@Test public void testEquals(){ GenericClass<String> g = new GenericClass(); GenericClass<Integer> g2 = new GenericClass(); log.info("g1 equals g2 ? {}",g.equals(g2)); //true g.setKey("123"); g2.setKey(123); log.info("g1 equals g2 ? {}",g.equals(g2)); //false }
-
不能通过泛型类的实例参数实现方法重载
class ClassReload{ Integer addIntValue(GenericClass<Integer> t){ return 0; } //方法定义报错 Integer addIntValue(GenericClass<Long> t){ return 0; } }
JVM运行时会忽略泛型声明,所以在JVM看来Integer addIntValue(GenericClass<Long> t)与Integer addIntValue(GenericClass<Integer> t)是同一方法
-
类的泛型方法可以使用泛型类定义的泛型,也可以在单独定义;静态泛型方法无法使用泛型类定义的泛型符号,静态泛型方法需要单独定义泛型(泛型的变量符号声明避免与泛型类的变量符号相同)