在学习泛型之前我们先回顾下JAVA的数据类型以及涉及到的一些概念。
Java数据类型
Java的两大数据类型分为基础类型和引用类型。基本类型的数值不是对象,不能调用对象的toString()、hashCode()、getClass()、equals()等方法。
自动装箱
把基本类型用它们对应的引用类型包装起来,使它们具有对象的特质,可以调用toString()、hashCode()、getClass()、equals()等方法。
例如:
//自动装箱
Integer i=1;
而实际上编译器会调用static Integer valueOf(int i)这个方法,返回一个表示指定int值的Integer对象。Integer i=1; ->. Integer i=Integer.valueOf(1);
拆箱
跟自动装箱的方向相反,将引用类型转换为基本类型。
//拆箱
int i = new Integer(1);
自动装箱和拆箱是由编译器来完成的,编译器会在编译期根据语法决定是否进行装箱和拆箱动作。
假如我们定义一个类来表示坐标,要求类中基础类型可以为整数 、小数、字符串,例如:
Object x=116,y=54;
Object x=116.92,y=54.31;
Object x="经度116.92",y="纬度54.31";
我们知道,基本数据类型可以自动装箱,被转换成对应的包装类。Object 是所有类的祖先类,任何一个类的实例都可以向上转型为 Object 类型,例如:
int -> Integer -> Object
double -> Double -> Object
String -> Object
泛型的使用
如果要取出坐标值就需要向下转型,向下转型存在着风险,而且编译期间不容易发现,只有在运行期间才会抛出异常,所以要尽量避免使用向下转型。例如下面的实例:
public class Test {
public static void main(String[] args){
Point point = new Point();
//int -> Integer -> Object
point.setX(116);
point.setY(54);
//向下转型
int x = (Integer) point.getX();
int y = (Integer) point.getY();
System.out.println("Point :x="+x+" y="+y);
//double -> Double -> Object
point.setX(116.92);
point.setY("纬度54.32");
//向下转型
Double x1 = (Double) point.getX();
//会抛出ClassCastException异常
Double y1 = (Double) point.getY();
System.out.println("Point :x1="+x1+" y1="+y1);
}
}
class Point{
Object x = null;
Object y = null;
public Object getX() {
return x;
}
public void setX(Object x) {
this.x = x;
}
public Object getY() {
return y;
}
public void setY(Object y) {
this.y = y;
}
}
那么Java中如何避免这样的情况发生呢?
Java中泛型出现就是解决出现这样的问题,所谓的“泛型”就是任意的数据类型。以下代码将利用泛型解决上面的问题。
public class Test {
public static void main(String[] args){
//实例化泛型
Point<Integer,Integer> point = new Point<Integer,Integer>();
point.setX(116);
point.setY(54);
int x = point.getX();
int y = point.getY();
System.out.println("Point :x="+x+" y="+y);
Point<Double,String> point1 = new Point<Double,String>();
point1.setX(116.92);
point1.setY("纬度54.32");
Double x1 = point1.getX();
String y1 = point1.getY();
System.out.println("Point :x1="+x1+" y1="+y1);
}
}
//定义泛型类
class Point<T1,T2>{
T1 x = null;
T2 y = null;
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
}
实例中的T1和T2叫类型参数,类型参数是用来表示自定义标识符,用来传递数据的类型。Java中传值参数用小括号包围,泛型参数用尖括号包围,多个参数用逗号间隔,例如:
<T>或<T,E>
类型参数需要在类名后面给出。一旦给出了类型参数,就可以在类中使用了。类型参数必须是一个合法的标识符,习惯上使用单个大写字母,通常情况下,K 表示键,V 表示值,E 表示异常或错误,T 表示一般意义上的数据类型。
泛型类在实例化时必须指出具体的类型,也就是向类型参数传值,格式为:
className variable<dataType1, dataType2> = new className<dataType1, dataType2>();
也可以省略等号右边的数据类型,但是会产生警告,即:
className variable<dataType1, dataType2> = new className();
泛型的优点:使用泛型类时指明了数据类型,赋给其他类型的值会抛出异常,既不需要向下转型,也没有潜在的风险。
除了定义泛型类,还可以定义泛型接口和泛型方法,使用泛型方法时不必指明参数类型,编译器会根据传递的参数自动查找出具体的类型。
限制泛型的可用类型
通过 extends 关键字可以限制泛型的类型
public <T extends Number> T getMax(T array[]){
T max = null;
for(T element : array){
max = element.doubleValue() > max.doubleValue() ? element : max;
}
return max;
}
<T extends Number> 表示 T 只接受 Number 及其子类,传入其他类型的数据会报错。这里的限定使用关键字 extends,后面可以是类也可以是接口。但这里的 extends 已经不是继承的含义了,应该理解为 T 是继承自 Number 类的类型。