Generics
什么是泛型(generics)
- 是在定义类、接口和方法时,可以在声明时通过一定的格式指定其参数类型
- 使用时再指定具体的类型,从而使得类、接口和方法可以被多种类型的数据所实例化或调用
- 这种可以在编译时进行参数类型检查的技术被称为泛型,是JDK5中引入的一个新特性
- 本质是参数化类型给类型指定一个参数,在使用时再指定此参数具体的值,那这个类型就可以在使用时决定
Generics的优点
- 把运行时的错误,提前到编译时,这样就可以在编译时把错误提示出来,避免了运行时出现错误
- 使用泛型可以提高代码的复用性,因为它可以支持多种类型的数据。
为什么要用泛型(generics)
- 在没有泛型之前,从集合中读取到的每一个对象都必须进行类型转换
- 如果插入了错误的类型对象,在运行时的转换处理就会出错
- 集合容器里面如果没指定类型默认都是Object类型,那什么到可以插入
- 减少了源代码中的强制类型转换,代码更加可读
作用域
-
泛型类
public class soulboy <泛型类型1,泛型类型2……> { …… }
-
泛型接口
interface soulboy <泛型类型1,泛型类型2……> { 泛型类型 method1( ); …… }
-
泛型方法
public <T,E,...> 返回值类型 soulboy( ){ …… }
泛型字母通常类型参数都使用单个大写字母表示
字母名 | 含义 |
---|---|
T | 任意类型type |
E | 集合中元素的类型element |
K | key-value形式key |
V | key-value形式value |
泛型类
- 泛型类型必须是引用类型,即类类型(不能使用基本数据类型)
- 在类名后添加一对尖括号,并在尖括号中填写类型参数
- 如果参数可以有多个,多个参数使用逗号分隔
public class 类名 <泛型类型,…> {
private 泛型类型 变量名;
public 泛型类型 方法名( ) { }
public 返回值 方法名(泛型类型 t) { }
……
}
泛型使用(JDK1. 7后,结尾的具体类型不用写)
SoulBoy<具体数据类型> soulBoy = new SoulBoy<>( );
注意点
- 泛型类创建的使用没有指定类型,则默认是object类型
- 泛型类型从逻辑上看是多个类型,实际都是相同类型
- Java 可以创建对应的泛型对象和泛型数组引用,但不能直接创建泛型对象和泛型数组
- Java 有类型擦除,任何泛型类型在擦除之后就变成了Object 类型
- 因此创建泛型对象就相当于创建了一个 Object 类型的对象
- 所以直接创建泛型对象和泛型数组也的行为被编译器禁止
示例:栈结构
package com.soulboy.structure;
public class CustomArrayStack <T> {
private Object [] arr;
private int top;
public CustomArrayStack(int capacity) {
arr = new Object[capacity];
top = -1;
}
public static void main(String[] args) {
CustomArrayStack<String> stringCustomArrayStack = new CustomArrayStack<>(2);
stringCustomArrayStack.push("soulboy.com");
stringCustomArrayStack.push("abc1024.tech");
System.out.println(stringCustomArrayStack.peek()); //abc1024.tech
System.out.println(stringCustomArrayStack.peek()); //abc1024.tech
System.out.println(stringCustomArrayStack.pop()); //abc1024.tech
System.out.println(stringCustomArrayStack.peek()); //soulboy.com
CustomArrayStack<Integer> integerCustomArrayStack = new CustomArrayStack<>(2);
integerCustomArrayStack.push(8);
integerCustomArrayStack.push(88);
System.out.println(integerCustomArrayStack.peek()); //88
System.out.println(integerCustomArrayStack.peek()); //88
System.out.println(integerCustomArrayStack.pop()); //88
System.out.println(integerCustomArrayStack.peek()); //8
System.out.println(stringCustomArrayStack.getClass()); //class com.soulboy.structure.CustomArrayStack
System.out.println(integerCustomArrayStack.getClass()); //class com.soulboy.structure.CustomArrayStack
}
/**
* 入栈
*/
public void push(T value) {
if (top == arr.length - 1) {
throw new RuntimeException("栈满了!");
}
// ++ top会先执行
arr[++top] = value;
}
/**
* 出栈
*/
public T pop() {
if(top == -1) {
throw new RuntimeException("栈空了!无法出栈!");
}
// top -- 会后执行
return (T)arr[top--];
}
/**
* 查看栈顶元素
*/
public T peek(){
if (top == -1) {
throw new RuntimeException("栈为空,无法查看栈顶!");
}
return (T)arr[top];
}
}
泛型派生类
父类
public class Parent <T>{
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
- 如果子类不是泛型类,集成父类的时候要指明实际类型,否则默认传递Object对象
public class Child extends Parent<String>{ }
- 如果泛型类的子类也是泛型类,那父类和子类的类型要一致
public class Child<T> extends Parent<T>{ }
- 如果子类泛型有多个,那需要包括父类的泛型类型
public class Child<T,E,F> extends Parent<T>{ }
通过idea生成Override方法理解泛型传递
传递泛型(子类也是泛型类)
package com.soulboy.structure;
public class Child<T> extends Parent<T>{
@Override
public T getValue() {
return super.getValue();
}
@Override
public void setValue(T value) {
super.setValue(value);
}
}
子类不是泛型,父类需要明确泛型类型
public class Child extends Parent<String>{
@Override
public String getValue() {
return super.getValue();
}
@Override
public void setValue(String value) {
super.setValue(value);
}
}
子类不是泛型,如果不明确父类的泛型类型,默认会传递Object类
public class Child extends Parent{
@Override
public Object getValue() {
return super.getValue();
}
@Override
public void setValue(Object value) {
super.setValue(value);
}
}
泛型接口
接口
public interface IPay<T> {
T pay();
}
传递规则和泛型类是一样的
- 如果实现类是泛型类,那接口和实现类的泛型类型要一致
public class WechatPay<T> implements IPay<T>{ @Override public T pay() { return null; } }
- 如果实现类泛型有多个,那需要包括接口的泛型类型
public class WechatPay<T,E,A> implements IPay<T>{ @Override public T pay() { return null; } }
- 如果实现类不是泛型类,那接口要明确泛型类的类型
public class WechatPay implements IPay<String>{ @Override public String pay() { return null; } }
- 如果实现类不是泛型类,也不明确接口的泛型,默认会传递Object
public class WechatPay implements IPay { @Override public Object pay() { return null; } }
泛型方法
调用方法的时候指定泛型的具体类型,格式如下 :
修饰符 <T,E,…> 返回值类型 方法名(参数列表) {
方法体
}
注意
- 修复符和返回值中间的有<T,E..>才是泛型方法,泛型类里面的普通返回值类型不是泛型方法
- 泛型类和泛型方法毫不相干,就算使用一样的泛型字母都没任何关联
package com.soulboy.structure;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
public class CustomArrayStack <T> {
private Object [] arr;
private int top;
public CustomArrayStack(int capacity) {
arr = new Object[capacity];
top = -1;
}
public static void main(String[] args) {
List<String> stringListlist = Arrays.asList("springboot", "springclould", "mysql");
CustomArrayStack<Integer> stringCustomArrayStack = new CustomArrayStack<>(2);
String randomElement = stringCustomArrayStack.getRandomElement(stringListlist);//
System.out.println(randomElement);//springboot
}
/**
* 入栈
*/
public void push(T value) {
if (top == arr.length - 1) {
throw new RuntimeException("栈满了!");
}
// ++ top会先执行
arr[++top] = value;
}
/**
* 出栈
*/
public T pop() {
if(top == -1) {
throw new RuntimeException("栈空了!无法出栈!");
}
// top -- 会后执行
return (T)arr[top--];
}
/**
* 查看栈顶元素
*/
public T peek(){
if (top == -1) {
throw new RuntimeException("栈为空,无法查看栈顶!");
}
return (T)arr[top];
}
/**
* 随机返回一个元素
*/
public <E> E getRandomElement(List<E> list) {
Random random = new Random();
return list.get(random.nextInt(list.size()));
}
}
-
使用了类泛型的成员方法,不能定义为静态方法
-
使用了泛型方法的才可以定义为静态方法
-
可变参数的泛型方法
泛型通配符
Java泛型的通配符是用于解决泛型之间引用传递问题的特殊语法
通用类型通配符 <?>,如List<?>
- 主要作用就是让泛型能够接受未知类型的数据
- 可以把?看成所有泛型类型的父类,是一种真实的类型,类型通配符是实参,不是形参
# 表示类型参数可以是任何类型 public class CustomCollection<?> { }
? 太过于通用,传入任何类型的都可以
package com.soulboy.structure;
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 main(String[] args) {
NumberCollection<Integer> integerNumberCollection = new NumberCollection<>(1);
NumberCollection<Long> longNumberCollection = new NumberCollection<>(999L);
printLong(longNumberCollection); //999
printInteger(integerNumberCollection); //1
printNumber(longNumberCollection); //999
printNumber(integerNumberCollection); //1
}
private static void printInteger(NumberCollection<Integer> collection) {
Integer collectionValue = collection.getValue();
System.out.println(collectionValue);
}
private static void printLong(NumberCollection<Long> collection) {
Long collectionValue = collection.getValue();
System.out.println(collectionValue);
}
private static void printNumber(NumberCollection<?> collection) {
Object collectionValue = collection.getValue();
System.out.println(collectionValue);
}
}
固定上边界的通配符 采用<?extends E>的形式
- 使用固定上边界的通配符的泛型,只能够接受指定类及其子类类型的数据。
- 采用<?extends E>的形式,这里的E就是该泛型的上边界
- 注意:这里虽然用的是extends关键字,却不仅限于继承了父类E的子类,也可以代指显现了接口E的类
# 表示类型参数必须是A或者是A的子类 public class CustomCollection<T extends A> { }
Integer和Long是Number的子类,但是String并不是Number的子类,限定了上边界。
固定下边界的通配符,采用<?super E>的形式
- 使用固定下边界的通配符的泛型,只能够接受指定类及其父类类型的数据。
- 采用<?super E>的形式,这里的E就是该泛型的下边界,可以为一个泛型指定上边界或下边界,但是不能同时指定上下边界
- 可以为一个泛型指定上边界或下边界,但是不能同时指定上下边界
# 表示类型参数必须是A或者是A的超类型 public class CustomCollection<T supers A> { }
Number是Integer的父类,但是Long和Integer是同级,所以传入Long在编译时候会显示语法错误
泛型类型擦除
泛型是jdk1.5后出现的,但泛型代码和常规版本代码可以兼容主要原泛型信息是在代码编译阶段。
代码编译完成后进入JVM运行前,相关的泛型类型信息会被删除,这个即泛型类型擦除。
作用范围:类泛型,接口泛型,方法泛型。
无限制类型擦除,擦除后都是Object类型
package com.soulboy.generic;
import java.lang.reflect.Field;
public class GenericTest<T, K> {
private T age;
private K name;
public static void main(String[] args) {
//实例化GenericTest类的对象
GenericTest<Integer, String> genericTest = new GenericTest<>();
//通过反射技术获取字节码class对象
Class<? extends GenericTest> aClass = genericTest.getClass();
//拿到全部成员变量
Field[] declaredFields = aClass.getDeclaredFields();
//遍历成员变量,输出每个成员变量的名称和类型
for (Field declaredField : declaredFields) {
// age,类型=Object
// name,类型=Object
System.out.println(declaredField.getName() + ",类型=" + declaredField.getType().getSimpleName());
}
}
}
有限制类型擦除,按指定的类型进行擦除
package com.soulboy.generic;
import java.lang.reflect.Field;
public class GenericTest<T extends Number, K extends String> {
private T age;
private K name;
public static void main(String[] args) {
//实例化GenericTest类的对象
GenericTest<Integer, String> genericTest = new GenericTest<>();
//通过反射技术获取字节码class对象
Class<? extends GenericTest> aClass = genericTest.getClass();
//拿到全部成员变量
Field[] declaredFields = aClass.getDeclaredFields();
//遍历成员变量,输出每个成员变量的名称和类型
for (Field declaredField : declaredFields) {
// age,类型=Number
// name,类型=String
System.out.println(declaredField.getName() + ",类型=" + declaredField.getType().getSimpleName());
}
}
}
如何创建泛型数组
- 在Java 中是不能直接创建泛型对象和泛型数组的
- 主要原因是Java 有类型擦除,任何泛型类型在擦除之后就变成了 Object 类型或者对应的上限类型
- 那定义的类中如果需要用到泛型数组,如何解决这个问题?
- 需求:创建一个类里面支持泛型数组和返回全部数组的方法
方法一:泛型类的泛型传递
package com.soulboy.generic;
import java.lang.reflect.Array;
import java.util.Arrays;
public class GenericArray<T> {
private T[] array;
public GenericArray(Class<T> cls, int capacity) {
//创建泛型数组
array = (T[]) Array.newInstance(cls, capacity);
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return (T)array[index];
}
public T[] getArray() {
return array;
}
public static void main(String[] args) {
GenericArray<String> genericArray = new GenericArray(String.class, 3);
genericArray.put(0, "soulboy");
genericArray.put(1, "Howard");
genericArray.put(2, "wade");
System.out.println(genericArray.get(0)); //soulboy
System.out.println(Arrays.toString(genericArray.getArray())); //[soulboy, Howard, wade]
}
方法二:list.toArray(本质上是反射)
package com.soulboy.generic;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
public class GenericArrayTwo<T> {
private Object[] array;
public GenericArrayTwo(int capacity) {
//创建泛型数组
array = new Object[capacity];
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return (T)array[index];
}
//不能通过方法直接返回数组,由于泛型的擦除,不能将Object[]数组转型为具体类型的数组
//Object[] 无法转成 String[]
public T[] getArray() {
return (T[])array;
}
public static void main(String[] args) {
// GenericArrayTwo<String> genericArray = new GenericArrayTwo( 3);
// genericArray.put(0, "soulboy");
// genericArray.put(1, "Howard");
// genericArray.put(2, "wade");
//
// System.out.println(genericArray.get(0));
// String[] array1 = genericArray.getArray();
// System.out.println(array1);
// //通过反射技术获取字节码class对象
// Class<? extends GenericArrayTwo> aClass = genericArray.getClass();
// //拿到全部成员变量
// Field[] declaredFields = aClass.getDeclaredFields();
// //遍历成员变量,输出每个成员变量的名称和类型
// for (Field declaredField : declaredFields) {
// // array,类型=Object[]
// System.out.println(declaredField.getName() + ",类型=" + declaredField.getType().getSimpleName());
// }
ArrayList<Object> list = new ArrayList<>();
list.add("soulboy");
list.add("Howard");
list.add("wade");
String[] arr = new String[list.size()];
list.toArray(arr);
System.out.println(Arrays.toString(arr)); //[soulboy, Howard, wade]
}
}