泛型编程
泛型的产生条件
Java推出泛型以前,程序员可以构建一个元素类型为Object的集合,该集合能存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,否则会引发ClassCastException异常
package com.Liang.Test;
import java.util.ArrayList;
/**
* @author Mr Liang
* @create 2023-05-07-9:58
*/
public class GenericTest {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("java");
list.add(100);
for (int i = 0; i < list.size(); i++) {
String o = (String)list.get(i);//强制类型转换
System.out.print(o+" ");
}
}
}
Object能存储任何类型的对象,在对集合中元素进行强制类型转换的时候,可能会产生运行时错误,很容易造成项目上线后发生异常
如果对集合中每个元素进行类型转换,也是可以实现的。但是只局限于集合中元素数据少的情况,集合元素过多时显然是不适用的,针对这种情况,就引入了泛型
泛型的概念
Java泛型是JDK5引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许在编译时捡到到非法的类型数据结构,在项目上线之前将错误处理完毕
泛型是指在编写代码时,不需要指定数据类型,而是在使用时再指定具体的数据类型。这样就可以提高代码的复用性和灵活性
泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数
ArrayList<String> strlist=new ArrayList<>();
规定传入的参数为String类型,传入其他数据类型会报错
泛型提供了一个编译时的检查
获取集合的类型返回值就是所规定的泛型,不需要再进行强制类型转换,避免了运行时可能产生的错误
ArrayList<String> strlist=new ArrayList<>();
strlist.add("a");
strlist.add("b");
for (int i = 0; i < strlist.size(); i++) {
String s = strlist.get(i);
通过定义一个ArrayList泛型类,可以操作不同的数据类型
泛型类
泛型类是指使用泛型定义的类,其中泛型参数可以用在类的成员变量、成员方法、构造方法中
泛型类的定义语法
class 类名称 <泛型标识,泛型标识,...>{
private 泛型标识 变量名;
......
}
常用的泛型标识:T、E、K、Y
定义一个泛型类
package com.Liang.Test;
/**
* @author Mr Liang
* @create 2023-05-07-11:23
* @param <T> 泛型标识一个类型形参
* T创建对象的时候指定具体的数据类型
*
*/
public class Generic<T> {
//T由外部使用类的时候来指定
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
@Override
public String toString() {
return "Generic{" +
"key=" + key +
'}';
}
}
泛型类的使用语法
类名<具体的数据类型> 对象名 = new 类名·<具体的数据类型>();
Java1.7以后,<>中的具体数据类型可以省略不写
类型<具体的数据类型> 对象名 = new 类名<>();
使用泛型类定义对象
Generic<String> Str=new Generic<>("A");
String key = Str.getKey();
System.out.println("key="+key);
Generic<Integer> intGeneric = new Generic<>(100);
int key1 = intGeneric.getKey();//自动拆箱
System.out.println("key1="+key1);
//泛型类在创建对象的时候,没有指定类型,将object类型来操作
Generic generic = new Generic("ABC");
泛型类不支持基本数据类型,基本数据类型不继承Object类,无法进行强制类型转换
同一泛型类,根据不同的数据类型创建的对象,本质上是同一类型
System.out.println(intGeneric.getClass());
System.out.println(str.getClass());
System.out.println(intGeneric.getClass()==str.getClass());
接下来设计一个抽奖系统来具体应用泛型类
定义实体类
package com.Liang.Test.Demo3;
import java.util.ArrayList;
import java.util.Random;
/**
* @author Mr Liang
* @create 2023-05-07-14:48
* 定义一个奖品类,使用泛型类模拟一个抽奖系统
*/
public class Product<T> {
private T product;
ArrayList<T> list=new ArrayList<>();
public T getProduct() {
this.product = list.get(new Random().nextInt(list.size()));
return product;
}
public void addProduct(T product) {
list.add(product);
}
}
在主类中使用泛型类
package com.Liang.Test.Demo3;
/**
* @author Mr Liang
* @create 2023-05-07-14:48
*/
public class MainClass {
public static void main(String[] args) {
Product<String> StrP = new Product<>();
String[] strProducts={"iphone15","小米运动手环","华为mate60","农夫山泉一瓶"};
for (int i = 0; i < strProducts.length; i++) {
StrP.addProduct(strProducts[i]);
}
String product1 = StrP.getProduct();
System.out.println("恭喜您抽中了:"+product1);
Product<Integer> intP = new Product<>();
int[] intproducts={500,200,300,1000};
for (int i = 0; i < intproducts.length; i++) {
intP.addProduct(intproducts[i]);
}
int product2 = intP.getProduct();
System.out.println("恭喜您抽中了"+product2+"元");
}
}
这样就借助泛型类达到了对不同数据类型的操作
泛型类派生类
泛型类派生类是指使用泛型定义的类的子类,其中子类可以继续使用父类中定义的泛型类型
从泛型类派生子类遵循以下规则
● 若子类也是泛型类,子类和父类的泛型类型要一致
class ChildGeneric<T> extends Generic<T>
● 若子类不是泛型类,父类要明确泛型的数据类型
class ChildGeneric extends Generic<String>
带泛型子类
带泛型子类是指在继承泛型类时,子类也要使用泛型类型
定义父类
package com.Liang.Test.Demo4;
/**
* @author Mr Liang
* @create 2023-05-07-15:48
*/
public class Parent<E> {
private E value;
public E getValue() {
return value;
}
public void setValue(E value) {
this.value = value;
}
}
定义子类去继承父类
可以看到子类的通配符为T,父类使用原先的标识符会爆红,要求子类和父类泛型类型要一致
原因:子类的数据类型会返回到父类,要生成子类需要先创建父类,如果子类和父类的泛型不一致,就无法得到具体的数据类型
派生子类获取父类属性和调用父类方法
package com.Liang.Test.Demo4;
/**
* @author Mr Liang
* @create 2023-05-07-16:20
*/
public class MainClass {
public static void main(String[] args) {
ChildFirst<String> ChildFirst = new ChildFirst<>();
ChildFirst.setValue("abc");
String value = ChildFirst.getValue();
System.out.println(value);
}
}
如果子类的泛型通配符有多个,则父类的通配符至少应该一个和子类中的对应
public class ChildFirst<T,E,k> extends Parent<T> {
}
不带泛型子类
不带泛型子类是指在继承泛型类时,子类不使用泛型类型
在创建子类时,无法指定父类是什么样的数据类型,父类指定泛型通配符会爆红
定义时应指定具体的父类泛型
package com.Liang.Test.Demo4;
/**
* @author Mr Liang
* @create 2023-05-07-16:06
*/
public class ChildSecond extends Parent<String>{
@Override
public String getValue() {
return super.getValue();
}
@Override
public void setValue(String value) {
super.setValue(value);
}
}
子类的数据类型已经明确
ChildSecond child = new ChildSecond();
child.setValue("123");
String value1 = child.getValue();
System.out.println(value1);
泛型接口
泛型接口是指使用泛型定义的接口,其中泛型参数可以用在接口的方法中
泛型接口的定义语法
interface 接口名称 <泛型标识,...>{
泛型标识 方法名();
......
}
泛型接口的使用
● 若实现类不是泛型类,接口要明确数据类型
定义接口
public interface Generator<T>{
T getkey();
}
定义实现接口的类
public class impl implements Generator<String>{
@Override
public String getkey() {
return "hello generic";
}//如果不设置泛型类型,默认为Object
}
● 若实现类也是泛型类,实现类和接口的泛型类型要一致
与泛型子类的定义类似
public class impl<T,E> implements Generator<T>{
private T key;
private E value;
public impl(T key, E value) {
this.key = key;
this.value = value;
}
@Override
public T getkey() {
return key;
}
public E getValue() {
return value;
}
}
public class MainClass {
public static void main(String[] args) {
impl<Integer, String> imp = new impl<>(1,"令狐冲");
Integer key = imp.getkey();
String value = imp.getValue();
System.out.println(imp.toString());
}
}
泛型方法
泛型类是在实例化类的时候指明泛型的具体类型,而泛型方法是在调用该方法的时候指明泛型的具体类型
使用语法
修饰符<T,E,...> 返回值类型 方法名(形参列表){
方法体...
}
只有声明了的方法才是泛型方法,而泛型类中使用了泛型的成员方法并不是泛型方法,例如
与泛型类的定义一样,泛型通配符可以随便写为任意标识
定义一个泛型方法
测试泛型方法
package com.Liang.Test.Demo6;
import com.Liang.Test.Demo3.Product;
import java.util.ArrayList;
/**
* @author Mr Liang
* @create 2023-05-07-19:30
*/
public class MainTest {
public static void main(String[] args) {
Product<Integer> intProduct = new Product<>();
ArrayList<String> strlist = new ArrayList<>();
strlist.add("石头一块");
strlist.add("玉米一根");
strlist.add("巴掌一个");
String product = intProduct.getProduct(strlist);//对泛型方法的调用,在调用方法的时候指定泛型
System.out.println(product+"\t"+product.getClass());
}
}
静态的泛型方法
Product.printType(1,"王明",true);
可变参数的泛型方法
● 泛型方法使方法独立于类而产生变化,使用泛型方法更加灵活
● 如果static方法要使用泛型,必须使其成为泛型方法
使用泛型属性而不使用泛型方法会报错
类型通配符
类型通配符是指在定义泛型时使用的一种特殊符号,用于表示不确定的类型
引出类型通配符
类型通配符可以用于引出泛型类型参数的上限或者下限
类型通配符一般是使用"?"代替具体的类型实参,不是类型形参
定义一个Box类
package com.Liang.Test.Demo7;
/**
* @author Mr Liang
* @create 2023-05-09-14:54
*/
public class Box<E>{
private E first;
public E getFirst() {
return first;
}
public void setFirst(E first) {
this.first = first;
}
}
创建泛型类对象
package com.Liang.Test.Demo7;
/**
* @author Mr Liang
* @create 2023-05-09-14:59
*/
public class Test07 {
public static void main(String[] args) {
Box<Number> box1 = new Box<>();
box1.setFirst(100);
showBox(box1);
}
public static void showBox(Box<Number> box){
Number first = box.getFirst();
System.out.println(first);
}
以上代码是能够正常输出的,如果再创建另一个泛型类对象
package com.Liang.Test.Demo7;
/**
* @author Mr Liang
* @create 2023-05-09-14:59
*/
public class Test07 {
public static void main(String[] args) {
Box<Number> box1 = new Box<>();
box1.setFirst(100);
showBox(box1);
Box<Integer> box2 = new Box<>();
box2.setFirst(200);
showBox1(box2);
}
public static void showBox(Box<Number> box){
Number first = box.getFirst();
System.out.println(first);
}
}
很明显这是错误的,因为showBox指定传入的泛型类型是Number
在这里,Interger类是继承自Number类型的
按照多态的思想,Interger可以作为参数传入,在这里不能应用到泛型中,同样也就不能使用Object类型泛型作为参数使用
也不能通过方法的重载来实现
public static void showBox(Box<Integer> box){
Number first = box.getFirst();
System.out.println(first);
泛型的本质是同一个类型,在这里本质上就是同一个Box类
因此不能够进行重载
使用泛型通配符来解决上述问题
让泛型通配符来代表任意类型
public static void showBox(Box<?> box){
Object first=box.getFirst();//?代表任意类型,在这里就使用Object类型泛型
System.out.println(first);
}
类型通配符的上限
类型通配符的上限是指使用 extends 关键字限制泛型类型参数的范围,表示泛型类型参数必须是某个类型的子类或者实现类
语法
类/接口<? extends 实参类型>
要求该泛型的类型,只能是实参类型,或者是实参类型的子类型
public static void showBox(Box<? extends Number> box){
Number first = box.getFirst();
System.out.println(first);
通过类型通配符继承Number类,可以传Number和Number的子类,可以用一个父类Number来接收(自动类型转换 ,类型范围小的变量,可以直接赋值给类型范围大的变量)
通过实例来使用上限通配符
package com.Liang.Test.Demo8;
import java.util.ArrayList;
/**
* @author Mr Liang
* @create 2023-05-09-15:52
*/
public class Test08 {
public static void main(String[] args) {
ArrayList<Animal> animals = new ArrayList<>();
ArrayList<Cat> cats = new ArrayList<>();
ArrayList<MiniCat> minicats = new ArrayList<>();
//将三个参数传递到方法中
// showAnimal(animals);
showAnimal(cats);
showAnimal(minicats);
}
/**
* 泛型上限通配符,传递的集合类型只能是Cat或Cat的子类
* @param list
*/
public static void showAnimal(ArrayList<? extends Cat> list) {
for (int i = 0; i < list.size(); i++) {
Cat cat = list.get(i);//上限为Cat,拿Cat接收
System.out.println(cat);
}
}
}
如果传入的类型是Animal类型,则报错
showAnimal(animals);
因为类型通配符上限为Cat类,而Animal类是Cat类的父类
方法中的list是不能添加元素的,因为类型通配符的类型并不是确定的
查看ArrayList继承List中的方法
可以看到addAll()方法中指定了一个类型通配符上限,通过此方法来添加数据
类型通配符的下限
类型通配符的下限是指使用 super 关键字限制泛型类型参数的范围,表示泛型类型参数必须是某个类型的父类或者超类
语法
类/接口<? super 实参类型>
要求该泛型的类型,只能是实参类型,或者是实参类型的父类型
public static void showAnimal(ArrayList<? super Cat> list){//Cat作为下限
for(Object o:list){//遍历集合
System.out.println(o);
}
下限通配符集合中是可以添加元素的,但是并不保证数据类型的一个约束要求
下限通配符元素的遍历都是通过Object类来接收的,因为类型通配符为Cat类的父类,Object类是所有类的父类
类型通配符下限的具体使用
在TreeSet中有以下构造方法,由比较器传入的下限方法
Animal类
package com.Liang.Test.Demo8;
/**
* @author Mr Liang
* @create 2023-05-09-15:50
*/
public class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
Cat类
package com.Liang.Test.Demo8;
/**
* @author Mr Liang
* @create 2023-05-09-15:52
*/
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 com.Liang.Test.Demo8;
/**
* @author Mr Liang
* @create 2023-05-09-15:52
*/
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 + '\'' +
'}';
}
}
定义构造器来实现TreeSet排序
package com.Liang.Test.Demo8;
import java.util.Comparator;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListSet;
/**
* @author Mr Liang
* @create 2023-05-10-11:56
*/
public class Test08 {
public static void main(String[] args) {
//TreeSet<Cat> treeSet = new TreeSet<>(new Comparator2());传入构造器指定排序规则
//TreeSet<Cat> treeSet = new TreeSet<>(new Comparator3());不是Cat类型的上限,报空指针异常
TreeSet<Cat> treeSet = new TreeSet<>(new Comparator1());//Cat类型的上限
treeSet.add(new Cat("Alice", 20));
treeSet.add(new Cat("Bob", 19));
treeSet.add(new Cat("frank", 21));
treeSet.add(new Cat("jerry", 22));
for (Cat cat : treeSet) {
System.out.println(cat);
}
}
}
class Comparator1 implements Comparator<Animal> {//重写Comparator方法
public int compare(Animal o1, Animal o2) {
return o1.name.compareTo(o2.name);
}
}
class Comparator2 implements Comparator<Cat> {//重写Comparator方法
public int compare(Cat o1, Cat o2) {
return o1.age - o2.age;
}
}
class Comparator3 implements Comparator<MiniCat> {//重写Comparator方法
public int compare(MiniCat o1, MiniCat o2) {
return o1.level - o2.level;
}
}
类型擦除
泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,称之为;泛型擦除
package com.Liang.Test.Demo9;
import java.util.ArrayList;
/**
* @author Mr Liang
* @create 2023-05-11-11:09
*/
public class Test09 {
public static void main(String[] args) {
ArrayList<String> strlist = new ArrayList<>();
ArrayList<Integer> intlist = new ArrayList<>();
System.out.println(strlist.getClass());
System.out.println(intlist.getClass());
System.out.println(strlist.getClass()==intlist.getClass());
}
}
在编译阶段指定了int和string的泛型,编译结束后,泛型被擦除,数据类型都是ArrayList类型
无限制类型的擦除
无限制类型的擦除是指在泛型类型参数没有明确指定上限或者下限时,将其擦除为 Object 类型
编译生成字节码文件后,T类型的泛型被擦除,全部用Object类型代替
定义Erasure类
package com.Liang.Test.Demo9;
/**
* @author Mr Liang
* @create 2023-05-11-14:11
*/
public class Erasure<T> {
private T key;
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
}
获取编译后对象的类型
package com.Liang.Test.Demo9;
import java.lang.reflect.Field;
import java.util.ArrayList;
/**
* @author Mr Liang
* @create 2023-05-11-11:09
*/
public class Test09 {
public static void main(String[] args) {
Erasure<Integer> erasure = new Erasure<>();
//利用反射,获取Erasure类的字节码文件的对象
Class<? extends Erasure> clz = erasure.getClass();
//获取所有的成员变量
Field[] declaredFields = clz.getDeclaredFields();
//打印成员变量的名称和类型
for (Field declaredField : declaredFields) {
System.out.println(declaredField.getName() + ":" + declaredField.getType().getSimpleName());
}
}
}
印证了上面的说法
有限制类型擦除
有限制类型擦除是指在泛型类型参数有明确指定上限或者下限时,将其擦除为上限或者下限
在编译成字节码文件后,T泛型转换成它的上限Number类型
将泛型类型修改为带上限的通配符
再次查看编译后的尘成员变量类型
擦除泛型方法中类型定义的参数
在泛型方法中,如果定义了泛型类型参数,则在编译期间也会进行类型擦除
同样的,编译后转换为泛型的上限类型
定义一个泛型方法
public <T extends List> T show(T t) {
return t;
}
//获取erasure下的所以方法
Method[] declaredMethods = clz.getDeclaredMethods();
//打印方法名和返回值类型
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod.getName()+":"+declaredMethod.getReturnType().getSimpleName());
}
show方法类型擦除转换为了上限List类型
下限的擦除也是同样的道理,编译转换为Object类型
桥接方法
在泛型类或者泛型接口中,如果有泛型方法,则在编译期间会自动生成桥接方法来确保类型安全
定义泛型接口,实现接口的方法,在类型擦除后,接口的泛型T转换为Object类型,还会生成一个桥接方法,桥接方法的返回值是Object类型,桥接方法用来保持接口和类的实现关系,表示由类具体实现的方法的返回值类型与接口中的泛型对应,传入给接口Interger类型在编译后被擦除后转为Object类型,而这个过程用一个重写的方法来表示出来,用来表示编译执行后,接口的返回值类型是Object类型
定义接口类
package com.Liang.Test.Demo9;
/**
* @author Mr Liang
* @create 2023-05-12-14:58
* 泛型接口
*/
public interface Info<T> {
T info(T t);
}
实现接口类
public class infompl implements Info<Integer> {
@Override
public Integer info(Integer value) {
return value;
}
}
接下来通过反射获取实现类所有的方法
Class<?> clz1 = Class.forName("com.Liang.Test.Demo9.infompl");
Method[] declaredMethods1 = clz1.getDeclaredMethods();
for (Method method : declaredMethods1) {
System.out.println(method.getName() + ":" + method.getReturnType().getSimpleName());
}sss
保持一个泛型的info方法,编译后字节码文件多了一个桥接方法,接口实现类的泛型被擦除,生成返回值类型为Object类型的info方法
泛型数组
泛型数组是指使用泛型定义的数组,其中数组元素的类型为泛型类型参数
● 可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象
ArrayList<String>[] list=new ArrayList<>[5];
像这样的创建方式就是错误的
引用list数组,赋给带泛型的数组[ 数组对象引用到泛型数组对象的方式 ]
//先创建list集合对象的数组
ArrayList[] list=new ArrayList[5];//先创建ArrayList集合对象的数组
//将对象赋給泛型数组,对list进行引用
ArrayList<String>[] listArr=list;
这样的方式存在局限性,会产生类型转换的错误
创建泛型为Integer类型的泛型集合ArrayList,intlist集合元素赋值给list数组,list再赋給ArrayList泛型集合数组,指定的泛型与传入的集合元素类型不一致,这是不安全的
● 可以通过java.lang.reflect.Array的newInstance(Class,int)创建T[]数组
通过反射获取数组对象的实例
源代码:
package com.Liang.Test.Demo10;
import java.lang.reflect.Array;
/**
* @author Mr Liang
* @create 2023-05-13-15:35
*/
public class Fruit<T> {
private T[] array;
public Fruit(Class<T> clz, int length) {//字节码文件,数据长度
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];
}
public T[] getArray() {
return array;
}
}
Fruit<String> fruit = new Fruit<>(String.class, 3);
//填充数据
fruit.put(0, "apple");
fruit.put(1, "melon");
fruit.put(2, "banana");
System.out.println(Arrays.toString(fruit.getArray()));
//获取数据
String s1 = fruit.get(2);
System.out.println(s1);
泛型与反射
泛型与反射的结合可以实现动态创建泛型类型对象、获取泛型类型信息等功能
反射常用的泛型类
● Class
● Constructor
package com.Liang.Test.Demo11;
import java.lang.reflect.Constructor;
/**
* @author Mr Liang
* @create 2023-05-16-19:59
*/
public class Test11 {
public static void main(String[] args) throws Exception {
/* // 原生的反射机制
Class personClass = Person.class;
Constructor constructor = personClass.getConstructor();
Object o = constructor.newInstance();//实例
System.out.println(o);*/
//带泛型的反射
Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getConstructor();
Person person = constructor.newInstance();
System.out.println(person.getClass().getSimpleName());
}
}