一、通配符
1.通配符的概念
前面我们学习的泛型都已经解决了很多问题了,但是我们在使用的时候还是指定了类型的。比如泛型类,我们在创建这个类的对象的时候就指定了是什么类型,然后就只能创建这个类型的对象,那么我们有时候想要创建任意类型的对象的时候我们可以使用通配符。
1.1无解通配符"?"
class Message<T> {
private T messgae;
public T getMessage() {
return messgae;
}
public void setMessage(T messgae) {
this.messgae = messgae;
}
}
public class TestTongpeifu {
//1.通配符的使用
public static void main(String[] args) {
Message<Integer> message=new Message();//在这可以接收整型的数据
message.setMessage(55);
Message<String> message1=new Message<>();//也可以接收字符串
message1.setMessage("你好");
Message<Double> message2=new Message<>();//也可以接收双精度的小数
message2.setMessage(12.35);
fun(message);
fun(message1);
fun(message2);
}
public static void fun(Message<?> temp){//这个地方就使用了通配符
//但是由于通配符不是具体的类型,所以我们不能修改值-> temp.setMessage(100);//error
System.out.println(temp.getMessage());
//当我们使用了通配符以后就可以接收任意类型的值了,可以体统给用户的方法是它们可以任意的修改
}
}
从上面的例子我们可以看出来,使用通配符以后,我们的同一个方法可以接收不同类型的数据,这样的话我们的泛型显得就更加的有用了。但是一定需要注意的是:在使用通配符的时候我们的数据是不可以更改的,因为在通配符中不知道具体的类型是什么,所以不可以修改。
2.受限泛型
我们由通配符又有了两个子通配符:
extends类:设置泛型上限
super类:设置泛型下限
package com.wschase.fanxing;
/**受限泛型
* 1.? extends 类名:设置泛型上限
* 2.? extends 类名:设置泛型下限
* Author:WSChase
* Created:2019/1/16
*/
//1.设置泛型上限
class Message2<T extends Number>{
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message ) {
this.message = message;
}
}
//2.设置泛型下限
class Message1<T>{
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class TestShouxian {
public static void main(String[] args) {
//1.泛型上限
Message2<Integer> message=new Message2<>();
message.setMessage(33);
fun2(message);
//2.泛型下限
Message1<String> message1=new Message1<>();
message1.setMessage("hello world");
System.out.println("经过修改以后的值为:");
fun1(message1);
}
//1.设置泛型上限
//使用通配符来定义方法
public static void fun2(Message2<? extends Number> temp){
//在这是不可以修改值的,因为泛型上限传入的是Number及其子类,如果后面调用方法的时候传入的是其子类,那么需要实现向下转型
//我们前面学习过类型的转,实现向下转型是需要强制类型转化的,所以这里行不通,在设置泛型上限的时候不可以修改值。
System.out.println(temp.getMessage());
}
//2.设置泛型下限
public static void fun1(Message1<? super String> temp){
//此时可以修改值,因为泛型下限是它及其父类——实现的是向上转型
temp.setMessage("hello");
System.out.println(temp.getMessage());
}
}
从上面的例子,我们知道我们在泛型受限这块需要掌握的是设置暗星上限、设置泛型下限,并且知道在设置泛型上限和泛型下限的时候泛型上限的方法里面是不可进行修改值的,但是在泛型下限的方法里面可以修改,以及它们的原因我么也需要掌握。
二、泛型接口
泛型出了可以定义在类中,也可以定义在接口中。我们称这种该接口为泛型接口。
1.定义泛型接口的格式:
interface IMessage{
public void print(T t);
}
下面是对泛型接口的实现,一共有三种方法:
(1)匿名内部类实现
(2)在子类定义时继续使用泛型
(3)在子类实现接口的时候明确给出具体类型
具体实现如下:
public interface IMessage<T>{
void print(T t);
}
public class TestInterface {
public static void main(String[] args) {
// (1)匿名内部类实现:在创建接口的对象的时候我们将指定具体的类型
IMessage<String> iMessage =new IMessage<String>() {
@Override
public void print(String s) {
System.out.println(s);
}
};
}
}
// (2)在子类定义时继续使用泛型
class IMessage1<T> implements IMessage{
@Override
public void print(T t) {
System.out.println(t);
}
}
// (3)在子类实现接口的时候明确给出具体类型
class IMessage2 implements IMessage<String>{
@Override
public void print(String s) {
System.out.println(s);
}
}
三、类型擦出
泛型只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息都会被擦出掉,即是类型擦出。也就是说,泛型类和普通类对于JVm而言是一样的。
class Myclass<T>{
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
public void testMethod(T t){
System.out.println(t);
}
}
public class Cachu {
public static void main(String[] args) {
Myclass<String> m1=new Myclass<>();
Myclass<Integer> m2=new Myclass<>();
System.out.println(m1.getClass()==m1.getClass());
}
}
上面的例子我们看到的是最后打印的结果是true,说明我们MyClass和MyClass在JVLM中的Class都是MyClass.class。
1.验证泛型类型在运行时被擦出了的两种方法
(1)instanceof
(2)getClass
具体如下:
class Myclass<T>{
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
public void testMethod(T t){
System.out.println(t);
}
}
public class Cachu {
public static void main(String[] args) {
Myclass<String> m1=new Myclass<>();
Myclass<Integer> m2=new Myclass<>();
//泛型类型擦出检验方法
// //1. instanceof
// System.out.println(message1 instanceof Message);//true
// System.out.println(message2 instanceof Message);//true
//
// //2. getClass
// System.out.println(message1.getClass().getName());//message1对象实例化的类型
// System.out.println(message2.getClass().getName());
}
}
2.对于泛型类型经过擦出以后在运行时候的类型
//class Myclass<T>{
// private T message;
//
// public T getMessage() {
// return message;
// }
//
// public void setMessage(T message) {
// this.message = message;
// }
// public void testMethod(T t){
// System.out.println(t);
// }
//}
//public class Cachu {
// public static void main(String[] args) {
// Myclass<String> m1=new Myclass<>();
// Myclass<Integer> m2=new Myclass<>();
// System.out.println(m1.getClass()==m1.getClass());
// }
//}
结论:
(1)上面这种泛型的类型如果在最后没有指定具体的类型,那么在最后经过擦出以后,在运行的时候我们的类型就是Object类型。
(2)但是如果我们有上限的通配符,那么就算没有指定类型最后的类型就是上限。
四、泛型的总结
-
泛型类
1.1 className<T,S> 使用时指定具体的类型
1.2 ? extends classType 指定类型参数的上限
1.3 ? super classType 指定类型参数下限 -
泛型方法
2.1 returnValue method(T args)
2.2 泛型方法和泛型类是相互独立
2.3 ? , ? extends classType(不能改内容) , ? super classType(可以改内容) -
泛型接口
3.1 interfaceName
3.2 className implements interfaceName
3.2 className implements interfaceName
3.3 结合匿名内部类使用 -
泛型擦除
4.1 泛型信息存在于编译阶段,运行时类型擦除
4.2 未指定类型参数上限时,类型统一设置Object
4.3 指定类型参数上限时,类型统一设置为上限类型