泛型:JDK1.5以后引入的新特性之一,其作用是检测编译期参数类型设置问题,取消向下转型带来的安全隐患。用于泛型类、泛型方法以及泛型接口。
一、泛型类:
泛型类指的就是在类的定义时并不会设置类中的属性或方法中参数的具体类型,而是在类使用时再进行定义。
泛型类的基本语法:
class A<T> {
T count;
}
尖括号中的“T”被称为泛型参数,用于指代任何类型。如果一个类被泛型参数定义,那么这个类就成为泛型类。
如何使用泛型类?
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;
}
@Override
public String toString() {
return "Point [x=" + x + ", y=" + y + "]";
}
}
public class Test{
public static void main(String[] args){
Point<Integer> p = new Point<Integer>();
p.setX(20);
p.setY(60);
//这里避免了向下转型
int x = p.getX();
int y = p.getY();
System.out.println(p);//Point [x=20, y=60]
}
}
引入泛型后,如果明确设置了类型,则为设置类型;如果没有设置类型,则默认为Object类型。
二、泛型方法
泛型方法跟泛型类的不同是:泛型方法将尖括号那一部分写在方法的返回值类型前面,而且在泛型方法这里不叫泛型参数,叫做参数化类型。
class MyClass{
public <T> void testMethod(T t) {
System.out.println(t);
}
}
public class Test{
public static void main(String[] args){
MyClass myClass = new MyClass();
myClass.testMethod(40);
}
}
当然T也可以作为方法的返回值类型:
class MyClass{
public <T> T testMethod(T t) {
System.out.println(t);
return t;
}
}
public class Test{
public static void main(String[] args){
MyClass myClass = new MyClass();
myClass.testMethod(40);
}
}
三、通配符(?):
class Message<T>{
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class Test{
public static void main(String[] args){
Message<Integer> message = new Message();
message.setMessage(20);
fun(message);
}
public static void fun(Message<?> temp) {
System.out.println(temp.getMessage());
}
}
这里的这个通配符“?”可以指代任意类型,但是正因为指代的是任意类型,所以无法对参数进行修改,只能取得参数。如果强行进行修改,编译器会直接报错。如下图对上面程序多加一条语句:
在“?”的基础上,java提供了两个子通配符:
1、?extends:设置泛型的上限。例如:?extends Number,表示只能够设置Number或其子类。可用于类和方法参数,与“?”一样,只能取值,不能修改。
2、?super:设置泛型下限:例如:?super String,表示能够设置String类及其父类Object。只能用于方法参数,可读可写。
三、泛型接口
被泛型定义的接口称为泛型接口:
interface IMessage<T>{
public void print(T t);
}
class Message1<T> implements IMessage<T>{
@Override
public void print(T t) {
System.out.println(t);
}
}
class Message2 implements IMessage<String>{
@Override
public void print(String t) {
System.out.println(t);
}
}
public class Test{
public static void main(String[] args){
IMessage<String> iMessage1 = new Message1<String>();
iMessage1.print("hello world");
IMessage<String> iMessage2 = new Message2();
iMessage2.print("hello China");
}
}
上面代码中,使用了子类实现接口的两种方法:Message1在子类定义时继续使用泛型;Message2实现接口时明确给出了具体类型。
四、类型擦除
泛型信息只存在于代码的编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,即类型擦除。也就是说,泛型类与普通类在Java虚拟机中是没有什么特别的地方。
在泛型被擦出的时候,如果之前的类型参数部分没有指定上限,会被直接转译成普通的Object类,如果指定了类型上限,会被替换成相应的的类型上限。
我认为Java里面的“类型擦出”与C语言里的“宏替换”是类似的,实际的可执行文件都没有定义的宏和泛型。