考虑自己实现一个容器类,可以支持对不同类型数据的增删改查。
JDK 1.5之前,由于不支持泛型,所以需要使用Object
创建数组作为容器。获取item时,需要显式地进行强制类型转换。
class MyList {
public static final int LENGTH = 10;
private Object[] list = new Object[LENGTH];
private int size = 0;
public void add(Object object) {
list[size++] = object;
}
public Object get(int index) {
return list[index];
}
public int size() {
return size;
}
}
// 使用方式
String str = (String) list.get(1);
针对上述代码,java编译时不会检查非法的强制类型转换,在运行时才会报错。
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at ObjectGeneric.main(ObjectGeneric.java:8)
总结:
Object
来实现参数的任意化,带来的缺点是需要进行显式地强制类型转换。ClassCastException
。Object
实现参数的任意化存在安全隐患。public interface List<E> extends Collection<E>
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
泛型的意思就是泛指的类型(参数化类型),即所操作的数据类型可以以参数的形式进行指定。
class Printer<T>{
private T item;
public Printer(T item){
this.item=item;
}
public void print(){
System.out.println(item.getClass().getName());
}
}
Java 泛型(generics)是 JDK 5 中引入的一个新特性,:
class ClassName<泛型通配符identify>{
// 泛型通配符可以是A-Z的任意大写字母以及'?'
private identify item; // 使用泛型通配符定义成员变量
public identify getItem(){
// 返回泛型值的方法,并非是泛型方法
return item;
}
...
}
泛型类在Java的容器(Collection
和Map
)中最常见,通过泛型实现了数据类型的任意化,灵活、安全、易维护。
泛型类的简单实例:
class MyGeneric<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
// 使用泛型类
public static void main(String[] args) {
// 创建泛型类对象时不指定类型,使用时与Object定义的类一样,需要进行显式转换
MyGeneric generic1 = new MyGeneric();
generic1.setItem(new Double(12.5));
Double number = (Double) generic1.getItem();
System.out.println("number: " + number);
// 创建泛型类对象是指定类型,可以充分利用泛型的优势
MyGeneric<Integer> generic2=new MyGeneric<>();
// generic2.setItem(12.3); // 编译无法通过,提示double类型无法转换成Integer类型
generic2.setItem(12);
if (generic2.getItem() instanceof Integer){
System.out.println("Item is Integer");
}
}
泛型方法: 在权限修饰符和返回值之间增加了泛型通配符,就让普通方法成为了泛型方法。
// 有参数的泛型方法
public <E> void printInfo(E input){
System.out.println(input);
}
// 调用泛型方法
obj.printInfo("sunrise");
// 无参数的泛型方法
public <E> List<E> createList(){
return new ArrayList<>();
}
// 调用泛型方法
List<String> list = obj.createList(); // 创建的是String类型的list
注意:
T
作为通配符,泛型方法也使用T
。实际使用时,泛型类传入类型为String
,而泛型方法可以为任意类型,不受泛型类的限制。Java方法中,通常使用...
来定义可变长度的参数。
public void printNames(String... names) {
for (String name : names) {
System.out.print(name + " ");
}
}
// 传入任意数量的参数
generic.printNames("lucy", "grace", "john", "张三");
泛型参数会被编译器转型为一个数组,因此可以当做普通的数组进行遍历。
public void printNames(String... names) {
for (int i=0;i<names.length;i++){
System.out.print(names[i] + " ");
}
}
可变参数规定了传入的参数类型必需是一致的,可以将泛型方法与可变参数结合,传入不同类型的参数。
public <T> void printNumbers(T... args) {
for (T arg : args) {
System.out.print(arg + " ");
}
}
// 方法的调用
generic.printNumbers("12.3", 24, 24.5, 2.4f);
泛型接口与泛型的定义基本一致
//定义一个泛型接口
public interface Generator<T> {
public T next();
}
如何实现泛型接口?
泛型类在实现泛型接口时,未向泛型接口传入泛型实参。则在定义泛型类时,需要为该类加入泛型声明。
class GenericClass2<T> implements GenericInterface<T>{
@Override
public T next() {
return null;
}
}
泛型类在实现泛型接口时,向泛型接口传入了泛型实参。则在定义泛型类时是,无需为该类加入泛型声明。
class GenericClass1 implements GenericInterface<String>{
@Override
public String next() {
return null;
}
}
泛型通配符可以使用A-Z
的任意一个字符,不影响泛型的效果,但会影响理解。
class GenericClass2<A> implements GenericInterface<A>{
@Override
public A next() {
return null;
}
}
通常使用的通配符包括T
、K
、V
、E
和?
,它们有一些约定俗成的含义:
?
表示不确定的 Java 类型。T
(type) 表示具体的一个Java类型K
和V
,即key、value,表示Java键值中的健和值。E
(element) 代表?
?
,它可以接收所有未知类型的泛型public void printList(List<?> list){
list.add("2845"); // 编译报错
list.add(null); // 运行时错误,java.lang.UnsupportedOperationException
String j = list.get(0); //编译期报错
for (Object obj:list){
System.out.print(obj+" ");
}
}
?
使用注意事项:List>
里添加null
,但运行时会报错。List>
中元素的具体类型,只能对其进行读操作,且读取的元素应作为Object
对象进行操作。 extends T>
例如, extends Number>
传入的参数必需是Number
及其子类。
List<Integer> list1=Arrays.asList(1,2,3,4);
List<String> list2=Arrays.asList("sunrise","john","grace");
getList(list1);
getList(list2); // 编译报错,类型不兼容
public static List getList(List<? extends Number> list){
return list;
}
PESC原则: 上界通配符,一般用于读取的场景。
T
类型或者其子类。null
。T
类或者其超类。 super T>
super T>
表示传入的泛型参数必需是类T或类T的父类。List super Integer>
传入的参数必须是Integer
或其父类。List<Number> list=Arrays.asList(12,13,45);
getSuperList(list);
List<String> list2=Arrays.asList("sunrise","john","grace");
getSuperList(list2); // 编译报错
public static List getSuperList(List<? super Integer> list){
return list;
}
T
,或者其超类。T
类型,或者其子类。 extends T>
不能往里存,只能往外取,适合频繁往外面读取内容的场景。 super T>
不影响往里存,但往外取只能放在Object对象里,适合经常往里面插入数据的场景。后续学习:
?
和T
的区别:聊一聊-JAVA 泛型中的通配符 T,E,K,V,?