作者:学Java的冬瓜
博客主页:☀冬瓜的主页🌙
专栏:【Java 数据结构与算法】
一、什么是泛型?
1、非泛型
分析:
- ArrayList是动态顺序表类型。
- 每一个数据存入ArrayList和取出来,都是Object类型。但我们需要明确的数据类型来取用。
- 而且非泛型方法有必要时还需要强转(但强转时类型不一致就会抛出ClassCastException异常,属于运行时异常,而不是编译时异常),所以非泛型的方法就不方便,所以引入了泛型。
代码:
package demo1;
public class Test {
public static void main(String[] args) {
//不用泛型:
//1、接收不同任意类型的数据
//2、类型不一致时,需要强制类型转换
System.out.println("非泛型:");
ArrayList arrayList = new ArrayList();
arrayList.add("小黑子");
arrayList.add(123);
arrayList.add(true);
for (int i = 0; i < arrayList.size(); i++) {
//注意:每一个数据取出来,都是Object类型,但我们需要明确的数据类型来取用,所以非泛型的方法就不适合
Object o = arrayList.get(i);
System.out.println(o);
}
}
}
结果演示:
非泛型:
小黑子
123
true
2、泛型
分析:
- 类型安全,编译时自动检查类型是否匹配
- 消除了强制类型转换(或者说自动执行)
代码:
package demo1;
public class Test {
public static void main(String[] args) {
//泛型的好处:
//1、类型安全,编译时自动检查类型是否匹配
//2、消除了强制类型转换
System.out.println("泛型ArrayList<String>:");
ArrayList<String> list = new ArrayList<>();
list.add("小黑子");
list.add("苏珊");
list.add("树脂");
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
System.out.println("----------------------");
System.out.println("泛型ArrayList<Integer>:");
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(666);
//Integer/int均可 Integer=》int 自动拆箱
Integer integer = list1.get(0);
System.out.println(integer);
}
}
结果演示:
泛型ArrayList<String>:
小黑子
苏珊
树脂
----------------------
泛型ArrayList<Integer>:
666
3、泛型的使用
说明:常用的泛型标识符:T,E,K,V。
@ 泛型类
public class 类名 <泛型标识,泛型标识...> {
private 泛型标识 变量名;
......
}
@ 泛型接口
public interface 接口名 <泛型标识> {
泛型标识 方法名();
......
}
@ 泛型方法
public <泛型标识> 返回类型 方法名 (泛型标识 变量名) {
}
二、泛型类
1、 泛型类
@ 正确使用分析
分析:
- 重点1:泛型类在创建对象的时候,来指定操作的具体数据类型
- 重点2:同一泛型类,根据不同数据类型创建的对象,本质上是同一类型(它们的地址相同)
代码:
Generic: 泛型类
说明:
1、设置带参和不带参数的构造方法
2、设置get和set方法
package demo2;
public class Generic <T> {
private T key;
//构造方法
public Generic(){
}
public Generic(T key) {
this.key = key;
}
//set和get方法
public void setKey(T key) {
this.key = key;
}
public T getKey() {
return key;
}
//重写toString
@Override
public String toString() {
return "Generic{" +
"key=" + key +
'}';
}
}
Main Class:
说明:
1、泛型类在创建对象的时候,来指定操作的具体数据类型
2、同一泛型类,根据不同数据类型创建的对象,本质上是同一类型(它们的地址相同)
过程:
1、创建泛型类对象,并指定数据类型
2、通过泛型类对象访问set方法,get方法
package demo2;
public class Main {
public static void main(String[] args) {
System.out.println("重点1:泛型类在创建对象的时候,来指定操作的具体数据类型");
System.out.println("Generic<String>::");
//重点1:泛型类在创建对象的时候,来指定操作的具体数据类型
Generic<String> strGeneric = new Generic<>();
System.out.println("setKey+getKey:");
strGeneric.setKey("你们食不食人啊,盗窃会不会!");
String key1 = strGeneric.getKey();
System.out.println(key1);
System.out.println("--------------------------");
System.out.print("Generic<Integer>::\nsetKey+getKey:\n树脂");
Generic<Integer> intGeneric = new Generic<>();
intGeneric.setKey(666);
Integer key2 = intGeneric.getKey();
System.out.println(key2);
System.out.println("-----------------------------------------");
System.out.println("重点2:同一泛型类,根据不同数据类型创建的对象,本质上是同一类型(它们的地址相同)");
//重点2:同一泛型类,根据不同数据类型创建的对象,本质上是同一类型(它们的地址相同)
System.out.println("strGeneric.getClass() == intGeneric.getClass()::");
System.out.println(strGeneric.getClass() == intGeneric.getClass());
}
}
结果演示:
重点1:泛型类在创建对象的时候,来指定操作的具体数据类型
Generic<String>::
setKey+getKey:
你们食不食人啊,盗窃会不会!
--------------------------
Generic<Integer>::
setKey+getKey:
树脂666
-----------------------------------------
重点2:同一泛型类,根据不同数据类型创建的对象,本质上是同一类型(它们的地址相同)
strGeneric.getClass() == intGeneric.getClass()::
true
@ 错误使用分析
分析:
- 重点3:泛型类在创建对象时,没有指定类型,将按照Object类型操作
- 重点4:泛型类不支持基本数据类型。只能是类类型
代码:
Main Class:
说明:
3、泛型类在创建对象时,没有指定类型,将按照Object类型操作
4、泛型类不支持基本数据类型,如Generic < int >
package demo2;
public class Main {
public static void main(String[] args) {
System.out.println("--------------------------");
System.out.println("重点3:泛型类在创建对象时,没有指定类型,将按照Object类型操作");
System.out.println("Generic generic1::");
//重点3:泛型类在创建对象时,没有指定类型,将按照Object类型操作
Generic generic1 = new Generic("荔枝");
Object key = generic1.getKey();
System.out.println(key);
System.out.println("--------------------------");
System.out.println("重点4:泛型类不支持基本数据类型。只能是类类型");
//重点4:泛型类不支持基本数据类型。
//Generic<int> generic2 = new Generic<int>(); 错误写法
结果演示:
重点3:泛型类在创建对象时,没有指定类型,将按照Object类型操作
Generic generic1::
荔枝
--------------------------
重点4:泛型类不支持基本数据类型。只能是类类型
2、泛型类实现抽奖器
代码:
ProductGetter: 抽奖器
说明:在抽奖器ProductGetter类中设置:
1、奖品
2、奖品池
3、添加奖品的方法
4、随机获取奖品的方法。
package demo3;
/**
* 抽奖器
* @param <T>
*/
public class ProductGetter <T> {
Random random = new Random(); //获取随机数
//1、奖品
private T product;
//2、奖品池
ArrayList<T> list = new ArrayList<>();
//3、添加奖品
public void addProduct(T t) {
// 数组中添加数据的方法 list.add()
list.add(t);
}
//4、抽奖
public T getProduct() {
// 获取数组元素的方法 list.get()
product = list.get(random.nextInt(list.size()));
return product;
}
@Override
public String toString() {
return "ProductGetter{" +
"product=" + product +
'}';
}
}
Main Class:
说明:
1、创建泛型类对象,指定数据类型
2、在抽奖器中填充可选择的奖品
3、用创建的对象访问抽奖方法,得到随机奖品。
package demo3;
public class Main {
public static void main(String[] args) {
//1、创建抽奖对象,确定数据类型
ProductGetter<String> stringProductGetter = new ProductGetter<>();
String[] strProducts = {"手机","电脑","冰箱"};
//2、在抽奖器中,填充奖品
for (int i = 0; i < strProducts.length; i++) {
stringProductGetter.addProduct(strProducts[i]);
}
//3、随机抽奖
String product1 = stringProductGetter.getProduct();
System.out.println("恭喜你,抽中了:"+product1);
//第二个例子
System.out.println("================");
ProductGetter<Integer> integerProductGetter = new ProductGetter<>();
int[] intProducts = {5000,3000,1000,500};
for (int i = 0; i < intProducts.length; i++) {
integerProductGetter.addProduct(intProducts[i]);
}
Integer product2 = integerProductGetter.getProduct();
System.out.println("恭喜你,抽中了:"+product2);
}
}
结果演示:
恭喜你,抽中了:冰箱
================
恭喜你,抽中了:5000
3、泛型类派生子类
@ 泛型类派生子类
- 子类是泛型类,那么子类的泛型标识符至少有一个与父类相同
- 子类不是泛型类,那么父类的数据类型必须明确
代码:
Parent: 泛型类父类
package demo4;
public class Parent <T>{
private T value;
//set和get方法
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
第一种
ChildFirst:
public class 子类名 <泛型标识,泛型标识…> extends 父类名 <泛型标识>
注意:子类是泛型类,子类的泛型标识包含父类的泛型标识
package demo4;
//以下是实现代码
public class ChildFirst <T> extends Parent <T>{
public T getValue() {
return super.getValue();
}
}
第二种
ChildSecond:
public class 子类名 extends 父类名 <派生标识>
注意:子类不是泛型类,父类的派生标识必须明确数据类型
package demo4;
//以下是实现代码
public class ChildSecond extends Parent <String>{
//子类非泛型,父类泛型,父类必须明确数据类型 (子类从父类继承)
@Override
public String getValue() {
return super.getValue();
}
}
Main Class:
过程:
1、创建子类对象
2、子类是泛型时,创建子类对象时指定数据类型。
3、子类不是泛型时,把子类当作普通类使用,但子类的类中要明确继承的父类的数据类型。
4、通过子类对象,先找子类中的方法,找不到再找父类的方法
5、通过子类对象,访问子类的方法
package demo4;
public class Main {
public static void main(String[] args) {
//第1种、子类泛型,父类泛型,泛型的标识符至少有一个一定相同(即使子类泛型扩展)
System.out.println("ChildFirst <T> extends Parent <T>::");
ChildFirst<Integer> intChildFirst = new ChildFirst<>();
//注意点1:通过子类对象调用从父类继承的父类方法,将值放入父类中
intChildFirst.setValue(12);
//注意点2:通过子类对象调用重写的父类方法,发生动态绑定,获取父类存储的数据。
Integer value1 = intChildFirst.getValue();
System.out.println(value1);
System.out.println("=======================");
//第2种、子类非泛型,父类泛型,父类的数据类型必须明确
System.out.println("ChildSecond extends Parent <String>::");
ChildSecond strChildSecond = new ChildSecond(); //普通类
strChildSecond.setValue("树脂666");
String value3 = strChildSecond.getValue();
System.out.println(value3);
结果演示:
ChildFirst <T> extends Parent <T>::
12
=======================
ChildSecond extends Parent <String>::
树脂666
@ 非泛型
- 子类泛型,父类非泛型,以Object操作
- 子类非泛型,父类非泛型。以操作Object
ChildThird:
说明:父类是非泛型,那么创建子类对象时传数据类型,也还是用Object操作。
public class Main {
public static void main(String[] args) {
//第3种、子类泛型,父类非泛型,以Object操作
System.out.println("ChildThird <T> extends Parent::");
ChildThird<String> strChildThird = new ChildThird<>();
strChildThird.setValue("小黑子");
//注意,get出来用Object类接收
Object value2 = strChildThird.getValue();
System.out.println(value2);
//第4种、子类非泛型,父类非泛型。那子类操作Object
}
}
三、泛型接口
说明:和派生类泛型极其相似。
代码:
Generator: 泛型接口
/**
* 泛型接口
* @param <T>
*/
public interface Generator<T>{
T getKey();
}
第一种:泛型类实现泛型接口
- 泛型类实现泛型接口。这个泛型类的泛型标识,包含泛型接口的泛型标识。
- 在创建泛型类对象的时候确定数据类型。
代码:
Pair:
package demo5;
/**
* 实现泛型接口的泛型类
* @param <T>
* @param <K>
*/
public class Pair<T,K> implements Generator<T> {
private T key;
private K value;
//构造方法
public Pair(T key, K value) {
this.key = key;
this.value = value;
}
//get方法
@Override
public T getKey() {
return key;
}
public K getValue() {
return value;
}
}
第二种:普通类实现泛型接口
- 普通类实现泛型接口,这个普通类在定义时必须指定泛型接口的数据类型类型
代码:
Apple:
package demo5;
/**
* 实现泛型接口的类
*/
public class Apple implements Generator<String>{
public String key;
public void setKey(String key) {
this.key = key;
}
public String getKey() {
return key;
}
}
Main Class: 第一第二种演示:
package demo5;
public class Main {
public static void main(String[] args) {
// 第一种:实现泛型接口的类,是泛型类。那这个泛型类的泛型标识,包含泛型接口的泛型标识
System.out.println("Pair<T> implements Generator<T>::");
// 1、创建实现泛型接口的这个泛型类的对象时,要明确传入的数据类型
Pair<String,Integer> integerPair = new Pair<>("小黑子,树脂",666);
// 2、以创建泛型类对象传入的数据类型操纵。
String key1 = integerPair.getKey();
Integer value1 = integerPair.getValue();
System.out.println("key:" + key1 + " value:" + value1);
System.out.println("---------------------------------------");
System.out.println("Apple implements Generator<String>::");
// 第二种:实现泛型接口的类,不是泛型类,在这个类中指定泛型接口的类型
// 1、创建这个类的对象时,把这个类当作普通类使用
Apple apple = new Apple();
// 2、以定义实现接口的类中,指定的接口类型来操作
apple.setKey("张三李四");
String key2 = apple.getKey();
System.out.println(key2);
}
}
结果演示:
Pair<T> implements Generator<T>::
key:小黑子,树脂 value:666
---------------------------------------
Apple implements Generator<String>::
张三李四
四、泛型方法
1、泛型方法
- 泛型方法独立于泛型类,泛型方法的数据类型不是通过泛型类确定,而是在调用泛型方法的时候,从参数确定,因此
- 非泛型类里可以创建泛型方法
- 只有声明了< T >的方法才是泛型方法,泛型类中使用了泛型的成员的方法不是泛型方法。
- 只有泛型方法才能用static修饰,非泛型方法不行。调用泛型方法时用类名.的方式,同时确定该泛型方法的类型
第一种:泛型类里的泛型方法
ProductGetter:
package demo6;
import java.util.ArrayList;
import java.util.Random;
/**
* 抽奖器
* @param <T>
*/
public class ProductGetter <T> {
Random random = new Random();
//1、奖品
private T product;
//2、奖品池
ArrayList<T> list = new ArrayList<>();
//3、添加奖品
public void addProduct(T t) {
// 数组中添加数据的方法 list.add()
list.add(t);
}
// //demo3里用泛型类做的抽奖器
// //4、抽奖,用泛型类创建的对象的普通的get方法
// public T getProduct() {
// // 获取数组元素的方法 list.get()
// product = list.get(random.nextInt(list.size()));
// return product;
// }
/**
* 定义泛型方法
* @param list 参数
* @return
* @param <E> 泛型标识,具体类型由调用方法的时候指定
*/
//4、抽奖,用泛型方法获取数据,和上面注释的使用泛型而本身不是泛型的方法作对比
public <E> E getProduct(ArrayList<E> list) {
return list.get(random.nextInt(list.size()));
}
}
Main:
package demo6;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
System.out.println("public <E> E getProduct(ArrayList<E> list)::");
//1、创建一个抽奖器的(泛型)类对象
ProductGetter<String> stringProductGetter = new ProductGetter<>();
ArrayList<Integer> integers = new ArrayList<>();
integers.add(578);
integers.add(245);
integers.add(297);
//2、通过创建的(泛型)类对象访问泛型方法
Integer product1 = stringProductGetter.getProduct(integers);
//注解:打印数据类型:变量.getClass().getSimpleName()
System.out.println(product1 + "\t" + product1.getClass().getSimpleName());
}
}
结果演示:
public <E> E getProduct(ArrayList<E> list)::
245 Integer
第二种:非泛型类里的泛型方法
说明:
1、可以在非泛型类里创建泛型方法
2、而且和泛型类实现泛型方法一样。非泛型类的,泛型方法的数据类型也是在调用泛型方法时确定。
2、静态泛型方法
- 用static修饰,调用时是通过类名调用,然后直接传参
- 调用传参时确定数据类型
实现:
package demo6;
public static <T,E,K> void printType(T t, E e, K k) {
System.out.println(t + "\t" + t.getClass().getSimpleName());
System.out.println(e + "\t" + e.getClass().getSimpleName());
System.out.println(k + "\t" + k.getClass().getSimpleName());
}
调用: 类名.方法名
ProductGetter.printType("小黑子",666,true);
结果演示:
小黑子 String
666 Integer
true Boolean
3、泛型可变参数的定义
- 和普通的泛型方法一样,它需要先创建类对象
- 再进一步用这个类对象调用泛型方法,在调用方法时根据传参确定数据类型。
- 可变的参数类型是 (E… e) 。代表参数可变,可根据以下方法使用。
@ 普通泛型方法可变参数
实现:
package demo6;
/**
* 泛型可变参数的定义
* @param e
* @param <E>
*/
public <E> void print(E... e) {
for (int i = 0; i < e.length; i++) {
System.out.println(e[i]);
}
}
调用:
package demo6;
//1、创建一个抽奖器的(泛型)类对象
ProductGetter<String> stringProductGetter = new ProductGetter<>();
//2、在动态数组integers中添加数据
ArrayList<Integer> integers = new ArrayList<>();
integers.add(578);
integers.add(245);
integers.add(297);
//3、调用用普通泛型方法可变参数,在此时由integers的类型Integer决定泛型方法的数据类型
stringProductGetter.print(integers);
结果演示:
[578, 245, 297]
@ 静态泛型方法可变参数
- 大部分和普通泛型方法可变参数一样
- 区别在于:1、用static修饰,2、调用时是通过类名调用,然后直接传参,3、传参时确定数据类型
五、类型通配符
1、错误示例
说明:
当我们用父类作为参数创建泛型类,在使用这个泛型类时,我们会想到将子类作为参数创建子类对象,但这种想法是错误的。泛型不能用继承和多态的思想,而且使用泛型的方法也不能重载,因为会发生类型擦除。所以引入了通配符。
Box: 泛型类
package demo7;
public class Box<E> {
private E first;
public E getFirst() {
return first;
}
public void setFirst(E first) {
this.first = first;
}
}
Main:
package demo7;
public class Main {
public static void main(String[] args) {
Box<Number> box1 = new Box<>();
box1.setFirst(100);
showBox(box1);
// 1、解释错误一:Integer继承了Number类
// 但是不能用继承,多态的思想,以子类作为参数这样理解。只能严格按照传泛型方法相对应的数据类型(这里参数是个泛型类)
// Box<Integer> box2 = new Box<>();
// box2.setFirst(300);
// showBox(box2); //调用showBox方法报错,Integer继承自Number,但不能用多态的思想理解,所以就引入了通配符
}
public static void showBox(Box<Number> box) {
Number first = box.getFirst();
System.out.println(first);
}
// 2、解释错误二:不能用方法重载的思想
// 因为两个方法传入的参数虽然不同,但会发生类型擦除
// 其实Main中的showBox会是同一个方法,所以无法重载
// public static void showBox(Box<Integer> box) {
// Integer first = box.getFirst();
// System.out.println(first);
// }
}
2、类型通配符
- 类型通配符用 “?” 代替具体的类型实参
- 类型通配符是类型实参,而不是形参
代码:
Box: 泛型类
package demo8;
public class Box <E>{
private E first;
public E getFirst() {
return first;
}
public void setFirst(E first) {
this.first = first;
}
}
Main: 创建泛型类对象,然后传参
package demo8;
public class Main {
public static void main(String[] args) {
System.out.println("showBox(box1) box1类型为 Box<String>::");
Box<String> box1 = new Box<>();
box1.setFirst("哈喽");
showBox(box1);
System.out.println("--------------------------------------");
System.out.println("showBox(box2) box1类型为 Box<Integer>::");
Box<Integer> box2 = new Box<>();
box2.setFirst(666);
//注意:给方法传不同类型参数,方法可以全盘接收
showBox(box2);
}
/**
* 类型通配符用"?"代替具体的类型实参,下面这个方法就可以接收任意类的类型
* 类型通配符是类型实参,而不是形参,?代表任意类型
* @param box
*/
public static void showBox(Box<?> box) {
Object first = box.getFirst(); //Object类,接收任意类型的类
System.out.println(first);
}
}
结果演示:
showBox(box1) box1类型为 Box<String>::
哈喽
--------------------------------------
showBox(box2) box1类型为 Box<Integer>::
666
3、通配符上限
定义:
//要求传入参数类型,是实参类型或实参类型的子类类型,在下面例子中,实参类型是Cat
类/接口<? extends 实参类型>
例子:
Cat继承Animal类,MiniCat继承Cat类:
说明:
- 泛型通配符上限由Cat规定的方法,被调用时传入的集合泛型参数只能是Cat或它的子类
- 泛型通配符上限规定Cat的方法,不能在该方法中给传入的集合添加类。因为不确定添加的是什么类型,若参数是父类Animal类型,就相当于Animal强制类型转换为Cat,很明显是错误的。
- 因为规定最高只能是实参类型,所以叫做通配符上限。
代码:
Main:
package demo8;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
//list.add(new Animal()); //报错,Animal不是Cat或者Cat的子类,不能作为参数
ArrayList<Animal> animals = new ArrayList<>();
//showAnimal(animals); //报错
//Cat本身和Cat的子类MiniCat可以传参
ArrayList<Cat> cats = new ArrayList<>();
showAnimal(cats);
ArrayList<MiniCat> miniCats = new ArrayList<>();
showAnimal(miniCats);
}
//类型通配符上限 ArrayList<? extends Cat>
//注意:如果这里参数不是一个类,而是普通的数据类型,那就不能用对象去调用方法
public static void showAnimal(ArrayList<? extends Cat> list) {
for (int i = 0; i < list.size(); i++) {
Cat cat = list.get(i);
System.out.println(cat);
}
}
}
4、通配符下限
@ 通配符下限的理解
定义:
//要求传入参数类型,是实参类型或实参类型的父类类型,在下面例子中,实参类型是Cat
类/接口 <? super 实参类型>
例子:
Cat继承Animal类,MiniCat继承Cat类
说明:
- 通配符下限:规定了方法传参的下限,要求只能是Cat或Cat的父类类型作参去调用该函数。
- 通配符下限方法中可以在传入参数的集合里添加实参类型Cat和它的子类MiniCat类型,但不能添加实参类型Cat的父类类型Animal
- 通配符下限方法中集合元素是用Object处理的
代码:
Main:
package demo8;
import java.util.ArrayList;
public class Main {
//通配符下限
public static void main(String[] args) {
//注意:存放Cat和它的父类Animal类型的集合作参,可以调用该函数。而Cat的子类MiniCat类型不能
//因为showAnimal方法的实参类型是Cat。
//且用了下限通配符,规定了Cat类为下限,所以不能用MiniCat作为参数调用该函数
ArrayList<Animal> animals = new ArrayList<>();
showAnimal(animals);
ArrayList<Cat> cats = new ArrayList<>();
showAnimal(cats);
ArrayList<MiniCat> miniCats = new ArrayList<>();
//showAnimal(miniCats); //报错
}
/**
* 通配符下限:规定了方法传参的下限,要求只能是Cat和Cat的父类类型去调用该函数
* @param list
*/
public static void showAnimal(ArrayList<? super Cat> list) {
//注意2:通配符下限方法中可以添加实参类型Cat和它的子类MiniCat类型,但不能添加实参类型Cat的父类类型Animal
//list.add(new MiniCat());
//list.add(new Animal()); //报错
//注意3:通配符下限方法中集合元素是用Object处理的
for (Object o : list) {
System.out.println(o);
}
}
}
@ 通配符下限实现排序
说明:
- new TreeSet(),里面传入一个实现接口Comparator的比较器,就可完成类类型对象的排序。
- Comparator是一个使用通配符下限的传泛型参数的接口,在使用实现Comparator接口的比较器时,只能调用当前对象或者当前对象的父类创建的比较器。
- 调用比较器时,放在创建的泛型对象的括号里,如:TreeSet< Cat> cat1 = new TreeSet<>(new Comparator2());
例子:
Cat类继承Animal类,MiniCat类继承Cat类
代码:
Animal:
package demo9;
public class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
'}';
}
}
Cat:
package demo9;
public class Cat extends Animal{
public int age;
public Cat(String name, int age) {
super(name);
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
MiniCat:
package demo9;
public class MiniCat extends Cat{
public int level;
public MiniCat(String name, int age, int level) {
super(name, age);
this.level = level;
}
@Override
public String toString() {
return "MiniCat{" +
"level=" + level +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
Main:
package demo9;
import java.util.Comparator;
import java.util.TreeSet;
public class Main {
public static void main(String[] args) {
//Cat类型数据,用它的父类Animal类型的比较器,可以正常使用,完成排序
// 因为Comparator是一个使用通配符下限的接口
TreeSet<Cat> cat1 = new TreeSet<>(new Comparator2());
cat1.add(new Cat("shuzhi",6));
cat1.add(new Cat("zhengxiatou",1));
cat1.add(new Cat("baojing",3));
System.out.println(cat1.toString());
//Cat类型数据,用Cat类型的比较器可以完成排序
TreeSet<Cat> cats2 = new TreeSet<>(new Comparator2());
//报错,因为Comparator3实现了Comparator这个接口,而这个接口用了通配符下限。
//在这个例子里下限就是Cat类型的比较器,可以选择Cat或者它父类Animal类型的比较器
// 而Comparator3是以Cat的子类MiniCat为类型做的比较器,所以错误
//TreeSet<Cat> cat3 = new TreeSet<>(new Comparator3());
}
}
//比较器1:比较Animal类型里的name元素
class Comparator1 implements Comparator<Animal> {
//比较器实现用通配符下限设置的接口Comparator。
//重写compare方法后,compareTo已经被重写,所以能比较字符串,而不是比较数值或地址。
@Override
public int compare(Animal o1, Animal o2) {
return o1.name.compareTo(o2.name);
}
}
//比较器2:比较Cat类型及其父类里的age元素
class Comparator2 implements Comparator<Cat> {
@Override
public int compare(Cat o1, Cat o2) {
return o1.age - o2.age;
}
}
//比较器3:比较MiniCat类型及其父类里的level元素
class Comparator3 implements Comparator<MiniCat> {
@Override
public int compare(MiniCat o1, MiniCat o2) {
return o1.level - o2.level;
}
}
结果演示:
调用1:Cat类型数据使用Comparator1接口(以姓名排序)
TreeSet<Cat> cat1 = new TreeSet<>(new Comparator2());
cat1.add(new Cat("shuzhi",6));
cat1.add(new Cat("zhengxiatou",1));
cat1.add(new Cat("baojing",3));
System.out.println(cat1.toString());
结果:
[Cat{age=3, name='baojing'}, Cat{age=6, name='shuzhi'}, Cat{age=1, name='zhengxiatou'}]
调用2:Cat类型数据调用Comparator2接口(以年龄排序)
TreeSet<Cat> cat1 = new TreeSet<>(new Comparator1());
cat1.add(new Cat("shuzhi",6));
cat1.add(new Cat("zhengxiatou",1));
cat1.add(new Cat("baojing",3));
System.out.println(cat1.toString());
结果:
[Cat{age=1, name='zhengxiatou'}, Cat{age=3, name='baojing'}, Cat{age=6, name='shuzhi'}]
六、类型擦除
概念:
泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉。所以Java1.5才引入的泛型代码可以与之前的版本兼容。
1、证明类型擦除
示例:
//main内:
//发生类型擦除证明1:String和Integer都被擦除
ArrayList<String> strArrayList = new ArrayList<>();
ArrayList<Integer> intArrayList = new ArrayList<>();
System.out.println(strArrayList.getClass().getSimpleName());
System.out.println(intArrayList.getClass().getSimpleName());
//发生类型擦除证明2:发生类型擦除,所以两个对象的.class文件是同一个
System.out.println(strArrayList.getClass() == intArrayList.getClass());
结果演示:
ArrayList
ArrayList
true
2、无限制类型擦除
概念:把泛型类型都擦除为Object类型。包括无通配符泛型和有通配符下限的泛型。
代码证明:
一、用反射去获取对象的成员:1、获取成员变量(2、也可以获取成员方法证明)
//注意:用反射去获取对象的成员
//一、获取成员变量
//1、创建泛型类对象
Erasure<Integer> intErasure = new Erasure<>();
//2、通过泛型类对象获取泛型类对象的.class文件
Class<? extends Erasure> intClass = intErasure.getClass();
//3、通过.class文件获取全部成员变量
Field[] declaredFields = intClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
//4、打印全部成员变量名 + 成员变量类型
System.out.println(declaredField.getName() + ":" + declaredField.getType().getSimpleName());
}
//属于泛型类部分
package demo10;
public class Erasure <T>{
private T val;
public T getVal() {
return val;
}
public void setVal(T val) {
this.val = val;
}
}
结果演示:
val:Object //无限制类型擦除
3、有限制类型擦除
概念:将泛型类型擦除为通配符上限类型。
第一种:由成员变量证明
- 成员变量的类型由定义的类型决定
代码:
无限制类型擦除中的代码只改变泛型定义为:public class Erasure < T extends Number>
结果演示:
val:Number //有限制类型擦除
第二种:由成员方法证明
- 泛型方法的返回值类型由泛型方法本身的通配符上限决定
- 非泛型方法的返回值类型由方法本身是否有返回值和泛型类传入的实参决定。
代码:
//注意:用反射去获取对象的成员
//二、获取成员方法
//1、创建泛型对象
Erasure<Integer> intErasure = new Erasure<>();
//2、获取该对象的.class文件
Class<? extends Erasure> intClass = intErasure.getClass();
//3、获取该.class文件下的全部方法
Method[] declaredMethods = intClass.getDeclaredMethods();
//4、打印每一个方法名+返回值类型
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod.getName() + ":" + declaredMethod.getReturnType().getSimpleName());
}
//泛型类和泛型方法部分
package demo10;
//有通配符上限泛型类的定义
public class Erasure <T extends Number>{
private T val;
public T getVal() {
return val;
}
public void setVal(T val) {
this.val = val;
}
//有通配符上限泛型方法的定义
public <E extends List> E show(E e) {
return e;
}
}
结果演示:
setVal:void //setVal方法无返回值
show:List //show方法的返回值类型由泛型方法本身的通配符上限决定
getVal:Number //getVal方法的返回值类型由泛型类的传入数据类型决定
4、桥接方法
- 用类实现接口时,会多出一个桥接方法,来保持接口和类的实现关系,原来的方法则保持不变。
代码:
//1、创建一个实现接口的类的对象
InfoImpl imp = new InfoImpl();
//2、获取这个对象的字节码文件
Class<? extends InfoImpl> impClass = imp.getClass();
//3、获取字节码文件下的每一个方法
Method[] declaredMethods = impClass.getDeclaredMethods();
//3、打印每个方法的方法名 + 返回值类型名
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod.getName() + ":" + declaredMethod.getReturnType().getSimpleName());
}
结果演示:
info:Integer
info:Object //桥接方法:保持接口和类的实现关系
七、泛型和数组
1、创建泛型的数组引用
- 先创建泛型数组引用,泛型对象要单独创建
- 正确使用可以检查类型是否匹配,错误的使用会传任意类型,不对类型加以限制。
@ 正确方式
//1、声明泛型集合,创建非泛型集合(顺序表集合),并把顺序表集合赋值给泛型集合
ArrayList<String>[] listArr = new ArrayList[5]; //创建泛型数组引用,对象要单独创建
//ArrayList<Integer> intList = new ArrayList<>();
//intList.add(25);
//listArr[0] = intList; //报错,发生泛型检查,检查出类型不匹配
//2、声明泛型集合,创建泛型对象(顺序表)
ArrayList<String> strList = new ArrayList<>();
strList.add("zhangsan");
//3、在泛型集合中填入顺序表
listArr[0] = strList; //类型检查后匹配,所以正常使用
System.out.println(listArr[0]);
@ 错误方式
//说明:报错。可以声明带泛型的引用,但是不能直接创建带泛型的数组对象。
//ArrayList<String>[] listArr = new ArrayList<String>[3];
//1、创建非泛型ArrayList集合(顺序表集合)
ArrayList[] list = new ArrayList[4];
//2、声明泛型集合接收非泛型集合
ArrayList<String>[] strList = list;
//3、创建一个Integer类型的顺序表
ArrayList<Integer> intList = new ArrayList<>();
intList.add(25);
intList.add(36);
//4、将Integer类型的顺序表赋值给了<String>[]类型的泛型集合第一个位置
// 但是类型不匹配却不报错,所以这种方法不合适
list[0] = intList;
System.out.println(strList[0]);
2、利用newInstance创建泛型数组
- 在Java.lang.reflect.Array的 newInstance(class< T>, int) 创建T[] 数组
- 使用泛型声明,创建泛型类对象的同时调用构造方法,从而创建泛型数组
- 但是尽量不要使用泛型数组,而是使用泛型集合去代替泛型数组。
代码:
//1、使用泛型声明,创建泛型类对象的同时调用构造方法,从而创建泛型数组
Fruit<String> strFruit = new Fruit<>(String.class,4); //String.class要和泛型引用类型相对应,这里是String类型,4个长度的泛型数组
//2、使用泛型对象里的方法,填充数据
strFruit.put(0,"苹果");
strFruit.put(1,"香蕉");
strFruit.put(2,"葡萄");
//3、接收使用泛型对象里的方法返回的数据,并转为字符串打印
String[] array = strFruit.getArray();
System.out.println(Arrays.toString(array));
//4、获取泛型数组单个元素
System.out.println(strFruit.get(3));
//以下是创建的泛型类以及用来创建泛型数组的构造方法和使用的方法
package demo11;
import java.lang.reflect.Array;
public class Fruit<T> {
//创建泛型数组对象引用
private T[] array;
//根据传入的参数,创建泛型数组
public Fruit(Class<T> clz, int length) {
//利用Array下的newInstance和构造方法,创建泛型对象
array = (T[])Array.newInstance(clz,length);
}
/**
* 填充数组元素
* @param index
* @param item
*/
public void put(int index, T item) {
array[index] = item;
}
/**
* 获取数组单个元素
* @param index
* @return
*/
public T get(int index) {
return array[index];
}
/**
* 获取整个数组
* @return
*/
public T[] getArray() {
return array;
}
}
结果展示:
[苹果, 香蕉, 葡萄, null]
null
八、泛型和反射
反射常用的泛型类 Class< T> 和 Constructor< T> :
//1、获取Person类的字节码文件
Class<Person> personClass = Person.class;
//2、通过字节码文件获取构造函数
Constructor<Person> constructor = personClass.getConstructor();
//3、通过构造函数创建对象
Person person = constructor.newInstance();
//以下是Person的泛型类
package demo12;
public class Person <T>{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}