导语:泛型,为Java的一种语法糖,在jdk1.5版本之后发布。主要的作用就是,在编译阶段,把所有的泛型替换为Object类型。确保数据的安全,有效避免运行时发生强制类型转换带来的问题,泛型主要有以下几种表现方式:泛型类,泛型方法,泛型接口等等。泛型的本质,就是对类型的参数化。
泛型初体验:
public class Test {
/**
* 初始化一个数组
*/
public Object[]objects =new Object[10];
/**
* 设置值
* 下标@param a
* 需要添加的值@param object
*/
public void setObjects(int a,Object object){
objects[a]=object;
}
/**
* 获取元素
* 下标@param a
* 对象@return
*/
public Object getObject(int a){
return objects[a];
}
public static void main(String[] args) {
Test test=new Test();
//未添加泛型,此时可以存放任意的数据类型,但是,代码的可维护性特别差
test.setObjects(0,1);
test.setObjects(1,"苹果");
test.setObjects(2,'啊');
//如果想取出来,就有可能报强制类型转换存我
String name= (String) test.getObject(1);
}
}
传统的体验:如果类型为Object,那么任意类型都可以往数组当中存放,可维护性极差
取出元素的时候,需要自己判断元素的类型。-->引出泛型?是否可以在类初始化的时候,就指定传入的类型数据,进而有效避免类型的强制转换错误?
/**加了<T>:表示
* @author 25043
*/
public class Test<T> {
/**
* 下面是不会报错的做法,但是不推荐
*/
public T [] objects=(T[]) new Object[10];
public void setValue(int a,T value){
objects[a]=value;
}
public T getValue(int a){
return objects[a];
}
public static void main(String[] args) {
//此时指定了类型
Test<Integer> integerTest=new Test<>();
//设置值,此时仅仅可以存放Integer类型,否则就会编译报错
integerTest.setValue(0,2);
//编译报错
integerTest.setValue(1,"hehe");
//在存放元素的时候,会进行类型检查,在取出元素的时候,会自动进行类型的转换,不再需要进行类型的强制转换
//泛型主要作用于编译时候,是编译时候的机制
Integer a=integerTest.getValue(0);
//笔记:泛型主要是编译时候的一种机制,这种机制,被称为擦除机制
}
}
上述代码。实现了一个非常简单的泛型类。可以体会到泛型的作用:指定了类的类型,让编译器自动完成转换。泛型:在编译阶段自动完成类型的转化,在运行的时候,是不存在泛型的。即:运行的时候,已经把泛型替换成Object类型了。
补充:泛型一定是类类型,不可以是八大基本数据类型。
泛型的擦除机制
在编译的过程当中,把所有的泛型都替换成了Object,这种机制被称为擦除机制。也就是说,在虚拟机当中,所有的泛型都被替换成Object类型了。在字节码的文件当中,所有的泛型都会被替换为Object。
擦除机制:
/**加了<T>:表示
* @author 25043
*/
public class Test<T> {
/**
* 下面是不会报错的做法,但是不推荐
*/
public T [] objects=(T[]) new Object[10];
public void setValue(int a,T value){
objects[a]=value;
}
public T getValue(int a){
return objects[a];
}
public T[] getObjects(){
return objects;
}
public static void main(String[] args) {
//此时指定了类型
Test<Integer> integerTest=new Test<>();
//设置值,此时仅仅可以存放Integer类型,否则就会编译报错
integerTest.setValue(0,2);
//看似没有错误,其实是由于,编译时候已经擦除成了Object类型,因此不可以返回Integer类型的数组。
Integer [] integers=integerTest.getObjects();
}
}
泛型通配符:?可以用来接受任意的类型
/**通配符
* @author 25043
*/
public class Test4 {
public static void main(String[] args) {
Message<String> message=new Message<>();
message.setContent("hello");
func(message);
Message<Integer> message1=new Message<>();
message1.setContent(1);
//会报错
func(message1);
//但是如果改成Message<?>就可以了
Message<Number> message2=new Message<>();
message2.setContent(1);
func2(message2);
}
/**
* 此处可以接受任意的类型,类型不确定,因此使用问号代替
* 消息@param message
*/
private static void func(Message<?> message) {
Object integer=message.getContent();
}
/**
* 泛型通配符:此处指定了泛型的上下界:下界是Integer,传入的类型必须是Integer及其父类
* 消息@param message1
*/
private static void func2(Message<? super Integer> message1){
//不能直接取出数据,因为不知道取出的数据类型是什么
Object test= message1.getContent();
}
/**
* 泛型通配符:此处指定了泛型的上下界:上界是Integer,传入的类型必须是Integer及其父类
* message@param message1
*/
private static void func3(Message<? extends Integer> message1){
//不能直接取出数据,因为不知道取出的数据类型是什么
Object test= message1.getContent();
}
}
泛型方法:
public <M> void say(M m){ System.out.println(m); }
定义的格式:方法访问限定修饰符 +<泛型>+ 返回值+方法名称(...参数列表)
此时就是一个简单的泛型方法的定义,其中参数列表或者返回值为都可以为M类型的数据
但是需要注意:
以上代码会编译报错:原因:
”在java中泛型只是一个占位符,必须在传递类型后才能使用就泛型而言,类实例化时才能正真的的传递类型参数,由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数静态的方法就已经加载完成了
因此,必须要在此方法之前再定义一下泛型
此时就以方法定义的泛型为准。
经典面试题:
一、泛型和Object的区别是什么?
①Object是一个具体的类型,它代表的的是所有类的父类;而泛型,代表的是一个具体的类型,例如出现在泛型类,泛型方法,泛型接口当中的<T>,<M>等等,这些都代表某一个具体的类型;
②泛型不可以直接使用new关键字来实例化,而Object类可以使用new关键字来直接实例化。因为编译器也无法确定具体的T代表什么样的类型;
③泛型,可以有效避免在运行过程中存取数据时候发生强制类型转化异常----->ClassCastException;而使用Object类型无法完成类型的自动检查,容易发生强制类型转换异常。