一、为什么需要泛型?
泛型是JDK1.5后增加的属性,主要的目的是解决ClassCastException的问题。
我们知道Object可以接收任意类型的数据,例如整型、浮点型、字符串都可以用Object来接收,因为存在以下的装换关系:
- 基本数据类型--------->转换为包装类--------->自动向上转型为Object
以上是向上转型,没有任何问题,但是当向下转型时就会出现问题。例如:
class Ball {
private Object x;
private Object y;
public void setX() {
this.x = x;
}
public void setY() {
this.y = y;
}
public Object getX() {
return this.x;
}
public Object getY() {
return this.y;
}
}
public class Test {
public static void main (String[] args){
Ball ball = new Ball();
ball.setX(90);
ball.setY("字符串");
int a = (Integer) ball.getX();
int b = (Integer) ball.getY();
}
}
以上代码在编译时虽然不会报错,但是执行时就会报ClassCastException,如下:
尽管在set时没有问题,定义的y是Object类型,即使传入字符串也没问题,但是后面将Object类型强转为Integer类型就会报错。这样编译时正常但是执行时报错的错误很容易被忽略,有安全隐患,最好的做法是避免这种强制转换。泛型就是来解决这个安全隐患的,使用泛型以后,在编译阶段就能查找出这类的错误,从而避免运行时才报错的尴尬。
二、泛型定义
泛型用“<T>”来指定实例化对象的类型,T代表任意类型(可以用T也可以使用具体的类型),上面的代码可以用泛型改写如下:
在实例化Ball的对象时,指定了T为Integer类型,此时若再给y设置字符串值,即使没有运行但编译器已经报错。
所以泛型是在实例化对象时的指定类型,这样于此泛型对应的属性、变量、方法和返回值等都将绑定这个指定的类型,一旦与该
类型不符就会出现错误提示,同时避免了强制向下转型,提高安全性。
三、泛型通配符
先看以下代码:
class Ball<T> {
private T info;
public void setInfo(T info) {
this.info = info;
}
public T getInfo() {
return info;
}
}
public class Test {
public static void main(String[] args) {
Ball<String> ball = new Ball<>();
ball.setInfo("hahaha");
sout(ball);
}
public static void sout(Ball<String> tempBall){
System.out.println(tempBall.getInfo());
}
}
以上代码中,可以在实例化Ball类的对象时确定泛型T的类型,虽然实现了接收任意类型的Ball,但是sout()这个方法处的参数类型就写死了,只能是String,如果此时要换一种参数类型就会出错。此时你可能会想到重载一下sout()这个方法不就好了,如下:
当你想重载sout方法时,没有执行编译器已经报错,会报:“both methods have same erasure”,这是因为泛型类型在编译后,会做类型擦除,最终无论是String还是Integer都会被Object代替,因此认为这两个是同一个方法,不构成重载而报错。
此时我们只需要在方法的参数处使用泛型通配符就可以实现接收任意类型数据且不用修改或重载方法,泛型通配符一般定义为:
类名<?> 变量名
以上代码可以用泛型通配符改写为:
class Ball<T> {
private T info;
public void setInfo(T info) {
this.info = info;
}
public T getInfo() {
return info;
}
}
public class Test {
public static void main(String[] args) {
Ball<String> ballA = new Ball<>();
Ball<Integer> ballB = new Ball<>();
ballA.setInfo("hahaha");
ballB.setInfo(100);
sout(ballA);
sout(ballB);
}
public static void sout(Ball<?> tempBall){
System.out.println(tempBall.getInfo());
}
}
此时在sout()方法的传参采用Ball类+泛型通配符的方式,就实现了接收任意类型数据的目的。
三、泛型上限与下限
以上泛型通配符还提供两个小通配符:
- ?extends 类 :设置泛型上限
- ?super 类 :设置泛型下限
例如:
?extends Number 表示该反省只允许设置Number及其子类
?super String 表示该泛型只能使用String及其父类
class Ball<T extends Number> {
private T info;
public void setInfo(T info) {
this.info = info;
}
public T getInfo() {
return info;
}
}
public class Test {
public static void main(String[] args) {
Ball<Integer> ball = new Ball<>();
ball.setInfo(100);
sout(ball);
}
public static void sout(Ball<? extends Number> tempBall){
System.out.println(tempBall.getInfo());
}
}
class Ball<T> {
private T info;
public void setInfo(T info) {
this.info = info;
}
public T getInfo() {
return info;
}
}
public class Test {
public static void main(String[] args) {
Ball<String> ball = new Ball<>();
ball.setInfo("100");
sout(ball);
}
public static void sout(Ball<? super String> tempBall){
System.out.println(tempBall.getInfo());
}
}
四、泛型接口
泛型还可以定义在接口中,泛型接口定义如下:
interface Ball<T> {
public String test(T t);
}
public class Test {
public static void main(String[] args) {
}
}
既然是接口就需要实现它,实现泛型接口有两种方式:
- (1)在实现类中继续设置泛型
interface Ball<T> {
public String test(T t);
}
class ImBall<S> implements Ball<S> {
@Override
public String test(S s) {
return "s== " + s;
}
}
public class Test {
public static void main(String[] args) {
Ball<String> ball = new ImBall<>();
System.out.println(ball.test("haha"));
}
}
- (2)在实现类中定义具体的泛型类型
interface Ball<T> {
public String test(T t);
}
class ImBall implements Ball<String> {
@Override
public String test(String s) {
return "s== " + s;
}
}
public class Test {
public static void main(String[] args) {
Ball<String> ball = new ImBall();
System.out.println(ball.test("haha"));
}
}
五、泛型方法
泛型可以定义在类、接口上,也可以定义在方法上。
public class Test22 {
public static void main(String[] args) {
// 传入了String类型,泛型就是String类型
String strs [] = info("haha", "gege", "didi");
for (String str:strs){
System.out.println(str);
}
}
public static <T> T[] info(T ... args) {
return args;
}
}
六、泛型注意事项
- 泛型中设置的类型必须是引用类型,因此若是操作基本数据类型就需要先转换成包装类;
- 在遇到强制转型时,可以考虑引入泛型设计。