泛型
每次创建一个集合,我们都需要指定这个集合里头能放什么元素,而放其他元素就会classcastexception;为了解决这个问题,我们可以创建类型为Object的集合,但是此时虽然我们什么类型都可以放了,但是正由于什么类型都能放,第一:元素会很乱;第二:用指定类型的变量接收集合返回的数据时,我们还需要进行强制类型转换,很麻烦.
泛型类❓
- 语法:
class 类名 <泛型标识符,泛型标识符...>{
private 泛型标识 变量名;
...
}
常见的泛型标识:T/E/K/V
2:使用语法(利用泛型类实例化对象)
类名<具体的数据类型> 对象名=new 类名<具体的数据类型>();
//jdk1.7之后,后面的<>里的类型可省略不写
3:注意事项
- 泛型类在创建对象时,如果没有指定具体类型,将按Object类型来操作
- 泛型类不支持基本数据类型(int char…)
- 同一个泛型类,根据不同的数据类型所穿件的对象,本质是同一个类型
public class Test {
public static void main(String[] args) {
ArrayList<String> arrayList1=new ArrayList<>();
ArrayList<Integer> arrayList2=new ArrayList<>();
/**
* 一般情况下,getclass()方法和class()方法是等价的
* 两者最直接的区别就是,getClass()是一个类的实例所具备的方法
* 而class()方法是一个类的方法。
* 另外getClass()是在运行时才确定的
* 而class()方法是在编译时就确定了。
*/
System.out.println(arrayList1.getClass() == arrayList2.getClass());//打印true
}
}
使用泛型类实现一个小案例💎
class ProductGetter<T>{
Random random=new Random();
//奖品
private T product;
//奖池
private ArrayList<T> list=new ArrayList<>();
//添加奖品
public void addProduct(T t){
list.add(t);
}
//抽奖
public T getProduct(){
int index=random.nextInt(list.size());
return list.get(index);
}
}
public class Test {
public static void main(String[] args) {
ProductGetter<String> gift=new ProductGetter<>();
String[] gifts={"苹果手机","小米手环","扫地机器人","500元现金"};
//为奖池添加奖品
for(int i=0;i<4;i++){
gift.addProduct(gifts[i]);
}
//开始抽奖
String ret=gift.getProduct();
System.out.println(ret);
}
}
- 实例化泛型类时,将类型作为了参数,即类型参数化
- 从该集合中获取到的元素,不需要像Object集合那样,每次都要强转,这里编译器可自动为我们完成强转
泛型类派生子类🍢
-
子类也是泛型类,子类和父类的泛型类型要一致;即使子类的泛型标识有多个,也要保证子类的泛型标识中至少有一个和父类的泛型标识一致
class child<T> extends parent<T>
-
子类不是泛型类,那此时该子类继承一个泛型类时,就要明确想继承什么类型的父类了
class child extends parent<String>
泛型接口🍰
-
接口注定是被实现类去实现的,所以实现类具体是不是泛型类,并且有什么要求,和泛型类派生子类是一样的规定
-
语法
interface 接口名称 <泛型标识符,泛型标识符...>{ 泛型标识 方法名();//自带public abstarct 修饰的抽象方法 }
-
具体
interface Generator<T>{ T getKey(); } class Apple implements Generator<String>{ //重写的方法,从泛型集合中获取不需要强转,说明类型就是我们所指定的String,所以为了 //形成重写,这里的返回值类型应当写成我们所指定的类型 public String getKey(){ return "hello generic"; } }
interface Parent<T>{ T getKey(); } //保证至少一个相同,是因为构造子类对象时,类型参数的其中若干个也可以为泛型父类提供类型参数信息,进而构造出具体的父类 class child<T,E> implements Parent<T>{ private T key; private E value; public child(T key, E value) { this.key = key; this.value = value; } @Override public T getKey() { return key; } public E getValue(){ return value; } }
泛型方法⚡️
在调用方法的时候,才会指明这个方法带有的泛型标识代表的具体是什么类型.
-
语法:
修饰符 <泛型标识,泛型标识...> 返回值类型 方法名(形参列表){ 方法体; }
-
必须要求有<泛型标识>存在,否则不叫泛型方法,要区别于前述泛型类里定义的带有泛型标识的方法,但是没带<>,所以它们都不是泛型方法.
-
泛型标识也可以随便写,比如:T/E/V/K等
-
使用
class ProductGetter<T>{ Random random=new Random(); //奖品 private T product; //奖池 private ArrayList<T> list=new ArrayList<>(); //添加奖品 public void addProduct(T t){ list.add(t); } //抽奖 //泛型方法 public <E> E getProduct(ArrayList<E> list){ int index=random.nextInt(list.size()); return list.get(index); } } public class Test { public static void main(String[] args) { ArrayList<String> list1=new ArrayList<>(); ArrayList<Integer> list2=new ArrayList<>(); list1.add("苹果手机"); list1.add("扫地机器人"); list1.add("小米手环"); list1.add("500元现金"); list2.add(1000); list2.add(2000); list2.add(3000); ProductGetter<String> gift=new ProductGetter<>(); String product = gift.getProduct(list1); System.out.println(product); System.out.println(gift.getProduct(list2)); } }
可以看出,使用泛型方法,就不用先填充奖池,再抽奖了,其次可以发现,泛型方法使用的类型可以独立于泛型类的指定类型.还要知道,普通类里也能写泛型方法!
-
注意事项
- 只是使用了泛型标识的普通方法,不能被static修饰
- 泛型方法可以被static修饰
- 泛型方法可以定义在普通类中
- 可以了解一下可变参数的泛型方法的使用
类型通配符❎
- 类型通配符一般用 ? 表示
- 类型通配符代表的是类型实参,不是类型形参
- 思考一个问题:考虑为什么box2传参已经编译出错了!?
class Box<T>{
private T first;
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
}
public class Test2 {
public static void main(String[] args) {
Box<Number> box1=new Box<>();
box1.setFirst(100);
showBox(box1);
Box<Integer> box2=new Box<>();
box2.setFirst(200);
showBox(box2);
}
private static void showBox(Box<Number> box){
Number first=box.getFirst();
System.out.println(first);
}
}
答:box2和showBox的形参类型不匹配.我们一开始的思想可能想用向上转型去理解,但是这里并不构成父子关系.那如何改进,看下面这个方法可行?
class Box<T>{
private T first;
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
}
public class Test2 {
public static void main(String[] args) {
Box<Number> box1=new Box<>();
box1.setFirst(100);
showBox(box1);
Box<Integer> box2=new Box<>();
box2.setFirst(200);
showBox(box2);
}
private static void showBox(Box<Number> box){
Number first=box.getFirst();
System.out.println(first);
}
private static void showBox(Box<Integer> box){
Number first=box.getFirst();
System.out.println(first);
}
}
答:改成上述写法,可能我们的想法是既然向上转型不可行,那我利用方法的重载总可以了吧!?结果是否定的,重载的要求是:返回值情况不做要求,函数名相同,形参列表不同,但是,这里的形参列表本质是相同的,所以并没有构成方法的重载.那往下引出类型通配符
class Box<T>{
private T first;
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
}
public class Test2 {
public static void main(String[] args) {
Box<Number> box1=new Box<>();
box1.setFirst(100);
showBox(box1);
Box<Integer> box2=new Box<>();
box2.setFirst(200);
showBox(box2);
}
private static void showBox(Box<?> box){
Object first=box.getFirst();
System.out.println(first);
}
}
这时可正常打印了;也就是说:指定了类型的泛型类类型作为形参,实参必须是与形参一致;其次通配符的出现,可允许实参被指定各种类型.
通配符的上限💅
类/接口 <? extends 实参类型>
要求该泛型的类型,只能是实参类型或者实参类型的子类类型.
所以可以将上述代码改成:
class Box<T>{
private T first;
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
}
public class Test2 {
public static void main(String[] args) {
Box<Number> box1=new Box<>();
box1.setFirst(100);
showBox(box1);
Box<Integer> box2=new Box<>();
box2.setFirst(200);
showBox(box2);
}
private static void showBox(Box<? extends Number> box){
Number first=box.getFirst();//Number作为了上限,用这个类型接收,即使向上转型也没关系
System.out.println(first);
}
}
通配符的下限Ⓜ️
类/接口 <? super 实参类型>
要求该泛型的类型,只能是实参类型,或者实参类型的父类类型
所以上述代码可以改成:
class Box<T>{
private T first;
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
}
public class Test2 {
public static void main(String[] args) {
Box<Number> box1=new Box<>();
box1.setFirst(100);
showBox(box1);
Box<Integer> box2=new Box<>();
box2.setFirst(200);
showBox(box2);
}
private static void showBox(Box<? super Integer> box){
Object first=box.getFirst();//上限不定,只能用Object接收了
System.out.println(first);
}
}
类型擦除✌️
jdk1.5引入的,泛型代码能够很好的与之前的版本代码兼容,这是因为泛型信息只是存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除,称类型擦除.
-
无限制的类型擦除
public class Erasure<T>{ private T key; public T getKey(){ return key; } public void setKey(T key){ this.key=key; } } //擦除完: public class Erasure{ private Object key; public Object getKey(){ return key; } public void setKey(Object key){ this.key=key; } }
-
有限制的类型擦除
就是说泛型类带了上限,此时就是把上面的Object擦成上限类型即可.
-
泛型方法的类型擦除
与泛型类擦除方法一致.
泛型数组📦
- 可以申明带泛型的数组引用,但不能直接实例化一个泛型数组
- 可以通过java.lang.reflect.Array的newInstance(Class ,int)来创建T[]数组
- 后续介绍反射时,再重点介绍泛型数组的构建