泛型:只允许接受类,所有基本类型必须使用包装类。
①泛型类:
指在类定义时不会设置类中的属性或方法中参数的具体类型(Object),而是在类使用的时候再定义。
优点:不需强转,需要什么类型,使用时定义什么类型即可。若类型不匹配,编译时就会报错,避免了运行时异常。(因为Object类需要强转或向下转型,若没有做到,只能在运行时报出异常,非常不安全)
语法:
class 类名<T>{
private T num;}
T被称为类型参数,用于指代任何类型。
T:代表一般类
E:代表element 常用于泛型类中的属性
K、V:键值对,用于Map集合:
S:代表subtype,用于表示子类
若类中的成员变量需要不同的类型,则在类定义泛型时定义多个类型参数,用逗号分隔
使用时前写后省Person person=new<> Person();
public class Point<T, R> {
private T x;
private R y;
public Point(T x) {
this.x = x;
}
public Point(T x, R y) {
this.x = x;
this.y = y;
}
public void getInfo() {
System.out.println(x);
}
public static void main(String[] args) {
//创建泛型类的对象时(使用泛型)必须声明指定的类型
Point<String,Integer> p = new Point<>("张三");
//定义类时声明的T,R可以代表任意类型 ,若是基本数据类型必须是包装类
Point<String,Double> p1 = new Point<String, Double>("张三",24.5);
p.getInfo();
p1.getInfo();
}
}
②泛型方法:
public void method(T t){
}
是个占位符,表示其是泛型方法,与返回值无关
当泛型类与泛型方法共存时,泛型类中的类型参数与泛型方法中的类型参数没有关系,泛型方法始终以自己定义的类型参数为准。
规范起见:泛型方法类型参数与泛型类的类型参数不能同名。
//泛型方法
public <T> void tell(T t){
System.out.println(t);
}
泛型方法于泛型类共存:
泛型方法始终以自己定义的类型参数为准,与类定义的泛型无关,为避免混淆,,如果在一个泛型类中存在泛型方法,那么两者的类型参数好不要同名。
public class MethodTest<T> {
//泛型方法
public <T> T tell(T t){
return t;
}
public void talk(T t){
System.out.println(t);
}
public static void main(String[] args) {
MethodTest<Integer> my=new MethodTest<>();
//方法签名上无泛型声明的方法,其类型与类定义的一致
my.talk(12);
//方法签名上有泛型声明的方法,尽管<>中的参数与类的一致,但是使用时其类型与类无关联
my.tell("你好");
}
}
通配符:
含有泛型的类作为参数时,需指定其类型,因此就可能会出现多个方法重载,因此
(1)采用通配符 <?> 在方法参数中使用,表示参数可以接收任意类型的参数。
使用通配符的类参数,只能取得类中数据,不能修改数据,因为类型不确定,无法设置确定值。
public class Wildcard {
public static void main(String[] args) {
Message<Integer> me=new Message<>();
me.setMess(23);//使用确定的泛型既可以调用set也可以调用get
me.getMess();
Message<?> me2=new Message<>();
// me2.setMess(23);使用通配符的对象不能调用set方法吧,因为不确定类型
me2.getMess();//使用通配符的对象可以调用使用范型的属性
me2.setNaem("啥");//确定类型的属性可以调用set方法.
}
}
class Message<T>{
private T mess;
private String naem;
public String getNaem() {
return naem;
}
public void setNaem(String naem) {
this.naem = naem;
}
public T getMess() {
return mess;
}
public void setMess(T mess) {
this.mess = mess;
}
}
(2)采用通配符<? extends 类名>,表示设置泛型的类上限,即只能接收此类及其子类,如 ? extends Number 表示泛型必须是Number及其子类
只能取得类中属性,不能修改值(发生父类到子类的向下转型,需强转,由于子类不确定,因此无法转型)
泛型的类上限既可以用在定义类的时候,也可以用在使用类的时候
public class WildcardUP {
public static void main(String[] args) {
//使用设置了上限的泛型类
//可以设置Number,及其子类
PMessage<Number> message = new PMessage<>();
message.getMes();
message.setMes(34);
//PMessage<String> message1=new PMessage<String>();//error,String 不是Number的子类
Message<? extends Number> message1=new Message<>();
// message1.setMess(23);//error,设置泛型上限的对象不能调用set方法
}
}
//泛型的上限在泛型类定义的时候使用
class PMessage<T extends Number> {
private T mes;
public T getMes() {
return mes;
}
public void setMes(T mes) {
this.mes = mes;
}
}
(3)<?super 类名>表示设置泛型的类下限,只能接收此类及其父类,如<? super String>表示只能接收String 以及其父类Object.
可以设置属性值,因为子类到父类是会自动向上转型
泛型类的下限只能用在使用类的时候
public static void main(String[] args) {
//在使用泛型的类的时候使用类的下限
Message<? super String> message = new Message<>();
message.setMess("hello");//可以设置属性内容
message.getMess();//可以获取属性内容
}
③泛型接口
interface 接口名
子类实现接口可以保留泛型,继续成为泛型类
class 子类 implements 接口名{}
子类实现接口确定好类型
class 子类 implements 接口名{}
public interface IMessage <T>{
void print(T x);
//1.匿名内部类实现接口的泛型方法
IMessage<String> imessage=new IMessage<String>() {
@Override
public void print(String str) {
System.out.println(str);
}
};
}
//2.指定泛型参数的类型
class IMessage2 implements IMessage<String> {
@Override
public void print(String o) {
System.out.println(o);
}
}
//3.继续保留泛型参数
class IMessage1<T> implements IMessage<T> {
@Override
public void print(T t) {
System.out.println(t);
}
}
(面)
类型擦除(语法糖):仅存在源码阶段,编译后就消失不见。如泛型,自动拆装箱、String类中的+
泛型信息仅存在于代码编译阶段,进入JVM之前,与泛型相关信息会被擦除掉,专业术语:类型擦除
换句话说:泛型与普通类在Java虚拟机中没有任何区别
泛型类进入JVM之前会进行类型擦除,之前泛型类的类型参数若没有指定上限,会被擦除为Object类型。如果指定上限,则类型参数会被擦除为相应类型上限。
public class Wipe<T> {
public static void main(String[] args) {
Wipe<Integer> integerWipe=new Wipe<>();
Wipe<String> stringWipe= new Wipe<>();
System.out.println(integerWipe.getClass().getName());//com.sweet.generics.Wipe
System.out.println(stringWipe.getClass().getName());// com.sweet.generics.Wipe
}
}