泛型的定义:
在定义类的时候并不会设置类的属性和方法的参数的具体类型,而是在类实际用的时候再定义。它存在的意义是帮我们在编译期间检查我们的类型是否正确。
//定义一个Point类class Point{private Object x;private Object y;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;}}public class Generic {public static void main(String[] args) {//创建Point类对象Point point = new Point();//定义X和Ypoint.setX("东经80度");point.setY("北纬20度");//转成字符串类型String x = (String) point.getX();String y = (String) point.getY();//打印System.out.println("x = "+x);System.out.println("y = "+y);}}
我们先定义一个Point类,其中的X和Y属性我们都用定义为Object类,在使用的时候转成字符串,再进行打印。
但是当我们setY的时候给了它一个整数,但是后面转换成字符串的时候,编译器并没有个我们提示类型错误,只有到运行的时候在产生报错。
告诉我们34行产生类型转化错误,所以就产生类泛型,目的是可以帮助我们在编译器期间就可以告诉我们类型错误,及时修改!
泛型类:
基本语法:
class exampleClass<T>{
}
这里的尖括号中的T表示类型参数,可以指代任何类型。
这里的T可以用任意符号代替,但是java中会给出了一些标准。
T:代表一般的类
E:代表Element元素和Exception异常
K:代表key
V:代表Value,通常会配合K一起使用(只能出现一个K,K有它对应的Value,键值对)
S:代表Subtype子类型
我们利用泛型类将上面的代码再写一遍
//定义一个泛型Point类class Point<T>{private T x;private T y;public T getX() {return x;}public void setX(T x) {this.x = x;}public T getY() {return y;}public void setY(T y) {this.y = y;}}public class Generic {public static void main(String[] args) {//创建Point类对象Point<String> point = new Point<String>();//定义X和Ypoint.setX("东经80度");point.setY("北纬30度");//转成字符串类型String x = (String) point.getX();String y = (String) point.getY();//打印System.out.println("x = "+x);System.out.println("y = "+y);}}
和之前的代码没有太多的差距,但是我们再将Y定义成一个int类型的数据看一下效果。
编译器提醒我们在32行出现了错误需要我们进行修改!这就是泛型存在的意义。
泛型方法:
基本语句:
class exampleClass<T>{
public <E> E exampleMethod(E e){
System.out.println(e);
return e;
}
}
第一个<E>表示这个方法是一个泛型方法;
第二个E表示方法的返回值类型是E;
括号中的E表示传入的参数的类型是E,这个E被称为是参数化类型。
泛型类和泛型方法是可以共存的,在泛型类中有泛型方法,他们俩尖括号中的内容不是一定要相同的,泛型方法的参数类型是单独的,与泛型类无关!
泛型接口:
基本语法:
interface IMessage<T>{
public void print(T t);
}
子类实现这个接口:
(1)子类仍然是泛型类:
//定义一个泛型接口interface IMessage<T>{//抽象方法public void print(T t);}//定义一个类实现泛型接口class MessageImpl1<T> implements IMessage<T>{//覆写泛型接口的方法@Overridepublic void print(T t) {System.out.println(t);}}
(2)子类给出具体类型(子类就不是泛型类了):
//给出具体类型的类就不是泛型类了class MessageImpl2 implements IMessage<Integer>{//覆写接口方法@Overridepublic void print(Integer integer) {}}
通配符:
问号通配符:<?>
当我们定义了一个泛型类,在写一个方法的时候,要将这个泛型类当做参数传入,但是我们这是不能确定这个泛型类的具体类型,所以就有了问号通配符,帮我们接受这个泛型类包含的所有数据类型。
//泛型类class exampleClass2<T>{T t;//定义fun方法//利用问号通配符,接收泛型类所包含的所有数据类型public static void fun(exampleClass2<?> exampleClass){System.out.println(exampleClass);}}
但是用问号通配符的方法不可以修改泛型类对象的值,因为我们在这是不知道该属性的类型,所有不能进行修改。我们只能获取它的值,不能修改!
这里77行我们试图修改,编译器就会提醒有错误。
上限通配符:<? extends A>
//泛型类class exampleClass3<T>{T t;//上限通配符public static void fun(exampleClass3<? extends Number> exampleClass3){System.out.println(exampleClass3);}}
上限通配符与问号通配符的不同就是让问号继承了一个父类,这个这个方法只会接受这个父类及其子类的类型。
这里也是不清楚该方法具体传入的参数的类型,所以也是只能获取不能修改。
95行修改时报错。
下限通配符:<? super B>
//泛型类class exampleClass4<T>{T t;//下限通配符public static void fun(exampleClass4<? super Number> exampleClass4){System.out.println(exampleClass4);}}
下限通配符表示方法只能接收该类及其所有的父类的类型。
这里我们清楚所有的传入数据不是当下这个就是它的父类,所以可以通过向上转型进行修改,这里是一个天然的向上转型。
这里我们在修改的时候就不会报错。
三种通配符的使用特点:
1、三种通配符都可以用在方法上
2、只有上限通配符还可以用在泛型类和泛型接口的声明上
3、问号通配符和上限通配符只能获取数据不能修改
4、只有下限通配符可以对元素进行修改
类型擦除:
泛型只存在于编译阶段,当进入JVM中所有的泛型内容都会被擦除。普通泛型擦除为Object;有泛型上限的擦除为相应的泛型上限;有泛型下限的,擦除为相应的反省下限。反省的存在就只是为了在编译阶段帮助程序员检查类型错误的,所以在运行是它的意义就失去了。