【Java基础】JavaCore核心-泛型技术
1.什么是泛型
- 是在定义类、接口和方法时,可以在声明时通过一定的格式指定其参数类型
- 使用时再指定具体的类型,从而使得类、接口和方法可以被多种类型的数据所实例化或调用
- 这种可以在编译时进行参数类型检查的技术被称为泛型,是 JDK 5 中引入的一个新特性
- 本质是参数化类型,给类型指定一个参数,在使用时再指定此参数具体的值,那这个类型就可以在使用时决定
- 优点
- 把运行时的错误,提前到编译时,这样就可以在编译时把错误提示出来,避免了运行时出现错误
- 使用泛型可以提高代码的复用性,因为它可以支持多种类型的数据。
2.为什么要用泛型
- 在没有泛型之前,从集合中读取到的每一个对象都必须进行类型转换
- 如果插入了错误的类型对象,在运行时的转换处理就会出错
- 集合容器里面如果没指定类型,默认都是Object类型,那什么到可以插入
- 减少了源代码中的强制类型转换,代码更加可读
- 案例
3.泛型的分类
可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法
泛型字母通常类型参数都使用大写的单个字母
- T:任意类型 type
- E:集合中元素的类型 element
- K:key-value形式 key
- V: key-value形式 value
4.泛型类和泛型接口案例实战
(1)什么是泛型类
泛型类型必须是引用类型,即类类型(不能使用基本数据类型)
在类名后添加一对尖括号,并在尖括号中填写类型参数
如果参数可以有多个,多个参数使用逗号分隔
(2)泛型类的定义
public class 类名 <泛型类型,...> {
private 泛型类型 变量名
public 泛型类型 方法名(){ }
public 返回值 方法名(泛型类型 t){ }
....
}
- JDK1.7后,结尾的具体类型不用写
类名<具体数据类型> 对象名 = new 类名< >();
注意
泛型类创建的使用没有指定类型,则默认是object类型
泛型类型从逻辑上看是多个类型,实际都是相同类型
Java 可以创建对应的泛型对象和泛型数组引用,但不能直接创建泛型对象和泛型数组
Java 有类型擦除,任何泛型类型在擦除之后就变成了 Object 类型
因此创建泛型对象就相当于创建了一个 Object 类型的对象
所以直接创建泛型对象和泛型数组也的行为被编译器禁止
(3)案例实战(T是外部使用的时候来指定的类型)
package com.lixiang;
public class GenericTest<T> {
private Object[] arr;
private int num;
public GenericTest(int size){
arr = new Object[size];
num = 0;
}
public T get(int index){
if(index>=arr.length){
return null;
}
return (T) arr[index];
}
public void put(T data){
if(num>arr.length){
return;
}
arr[num++] = data;
}
public static void main(String[] args) {
GenericTest<String> genericTest = new GenericTest<>(5);
genericTest.put("lixiang");
genericTest.put("lixiang");
System.out.println(genericTest.get(0));
}
}
如果泛型类的子类也是泛型类,那父类和子类的类型要一致;
如果子类泛型有多个,那需要包括父类的泛型类型
(4)泛型接口
规则和泛型类一样
如果实现类是泛型类,那接口和实现类的泛型类型要一致;如果实现类泛型有多个,那需要包括接口的泛型类型
如果实现类不是泛型类,那接口要明确泛型类的类型
格式
interface 接口名称 <泛型类型1,...> {
泛型类型 方法名();
....
}
5.泛型方法案例实战
(1)什么是泛型方法
调用方法的时候指定泛型的具体类型
格式
修饰符 <T,E,...> 返回值类型 方法名( 参数列表 ){
方法体
....
}
- 修复符和返回值中间的有<T,E …> 才是泛型方法 泛型类里面的普通返回值类型不是
(2)声明泛型方法
泛型类的类型和泛型方法的类型是互相独立的,同名也不影响
声明了【泛型方法】在参数列表和方法体里面才可以用对应的泛型
public static <E> E getRandomElement(List<E> list){
Random random = new Random();
return list.get(random.nextInt(list.size()));
}
//测试
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李祥");
list.add("王武");
String randomElement = getRandomElement(list);
System.out.println(randomElement);
}
- 注意
- 使用了类泛型的成员方法,不能定义为静态方法;使用了泛型方法的才可以定义为静态方法
(3)可变参数的泛型方法
public static <T> void print(T...arr){
for(T t:arr){
System.out.println(t);
}
}
//测试
public static void main(String[] args) {
print("李祥","张三","李四");
}
6.泛型通配符案例实战
(1)什么是泛型通配符
- Java泛型的通配符是用于解决泛型之间引用传递问题的特殊语法
//表示类型参数可以是任何类型
public class CustomCollection<?>{}
//表示类型参数必须是A或者是A的子类
public class CustomCollection<T extends A>{}
//表示类型参数必须是A或者是A的超类型
public class CustomCollection<T supers A>{}
分类
通用类型通配符 < ? >,如List < ? >
- 主要作用就是让泛型能够接受未知类型的数据
- 可以把 ?看成所有泛型类型的父类,是一种真实的类型,类型通配符是实参,不是形参
固定上边界的通配符 采用<? extends E>的形式
- 使用固定上边界的通配符的泛型, 只能够接受指定类及其子类类型的数据。
- 采用<? extends E>的形式, 这里的E就是该泛型的上边界
- 注意: 这里虽然用的是extends关键字, 却不仅限于继承了父类E的子类, 也可以代指显现了接口E的类
固定下边界的通配符,采用<? super E>的形式
- 使用固定下边界的通配符的泛型, 只能够接受指定类及其父类类型的数据。
- 采用<? super E>的形式, 这里的E就是该泛型的下边界.
- 可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界
(2)泛型通配符案例实战
//使用泛型通配符,复用性更强
public static void print(NumberCollection<?> collection){
Object collectionValue = collection.getValue();
System.out.println(collectionValue);
}
public class NumberCollection<T> {
private T value;
public NumberCollection(T value){
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
//使用泛型通配符,复用性更强
public static void print(NumberCollection<?> collection){
Object collectionValue = collection.getValue();
System.out.println(collectionValue);
}
public static void main(String[] args) {
NumberCollection<Integer> integerNumberCollection = new NumberCollection<>(1);
NumberCollection<Long> longNumberCollection = new NumberCollection<>(23L);
//通用性更强
print(integerNumberCollection);
print(longNumberCollection);
}
}
(3)泛型上边界通配符
//使用泛型通配符, 固定上边界的通配符,不能任意元素,只能是Number的子类
public static void printUp(NumberCollection<? extends Number> collection){
Number collectionValue = collection.getValue();
System.out.println(collectionValue);
}
//字符串类型,测试报错
NumberCollection<String> stringNumberCollection = new NumberCollection<>("springboot");
printUp(stringNumberCollection);
(4)泛型下边界通配符
//只是是integer或者integer的父类
public static void printDown(NumberCollection<? super Integer> collection){
Object object = collection.getValue();
System.out.println(object);
}
//测试
NumberCollection<Long> longNumberCollection = new NumberCollection<>(23L);
//报错,类型不支持,需要ineter或其父类
// printDown(longNumberCollection);
NumberCollection<Integer> integerNumberCollection = new NumberCollection<>(1);
printDown(integerNumberCollection);
NumberCollection<Number> numberCollection = new NumberCollection<>(55L);
printDown(numberCollection);
7.泛型类型擦除
(1)什么是泛型类型擦除
- 泛型是jdk1.5后出现的,但泛型代码和常规版本代码可以兼容,主要原因是泛型信息是在代码编译阶段
- 代码编译完成后进入JVM运行前,相关的泛型类型信息会被删除,这个即泛型类型擦除
- 作用范围:类泛型,接口泛型,方法泛型
(2)无类型限制泛型擦除测试
//没指定类型则擦除后是Object最顶级父类
public class Generic <T,K> {
private T age;
private K name;
public static void main(String[] args) {
Generic generic = new Generic<Integer,String>();
//反射获取字节码文件class对象
Class<? extends Generic> aClass = generic.getClass();
//获取所有成员变量
Field[] declaredFields = aClass.getDeclaredFields();
for(Field field : declaredFields){
//获取每个属性名称和类型
System.out.println(field.getName() +",类型="+field.getType().getSimpleName());
}
}
}
(3)有类型限制泛型参数测试
//T 需要是 Number的子类,所以擦除后就是Number类型,没指定类型则擦除后是Object最顶级父类
public class Generic <T extends Number,K> {
private T age;
private K name;
public static void main(String[] args) {
Generic generic = new Generic<Integer,String>();
//反射获取字节码文件class对象
Class<? extends Generic> aClass = generic.getClass();
//获取所有成员变量
Field[] declaredFields = aClass.getDeclaredFields();
for(Field field : declaredFields){
//获取每个属性名称和类型
System.out.println(field.getName() +",类型="+field.getType().getSimpleName());
}
}
}
8.泛型数组的创建
在 Java 中是不能直接创建泛型对象和泛型数组的
主要原因是 Java 有类型擦除,任何泛型类型在擦除之后就变成了 Object 类型或者对应的上限类型
那定义的类中如果需要用到泛型数组,如何解决这个问题?
需求:创建一个类里面支持泛型数组和返回全部数组的方法
public class GenericsArray<T> {
private T[] array;
public GenericsArray(Class<T> clz ,int capacity) {
array = (T[]) Array.newInstance(clz,capacity);
}
public T[] getAll() {
return array;
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return (T)array[index];
}
public static void main(String[] args) {
GenericsArray<String> genericsArray = new GenericsArray(String.class,3);
genericsArray.put(0,"springcloud");
genericsArray.put(1,"springboot");
String value = genericsArray.get(0);
System.out.println(value);
//下面代代码运行不报错,虽然有泛型的擦除,但在构造器中传递了类型标记Class,从擦除中恢复,使得可以创建实际类型的数组
String[] all = genericsArray.getAll();
System.out.println(all);
}
}
- 点赞
- 收藏
- 关注作者
评论(0)