JAVA 泛型
JAVA泛型
泛型的好处
- 编译器的检查(类型安全)
- 减少了数据类型转换(消除了强制类型转换)
泛型类
泛型类如果没有指定类型,就要按照Object类型来操作;
泛型类不支持基本数据类型,只支持包装类;在编译器的时候,我们会将泛型转换成Object,然后再使用成员的时候,在适当的时候将Object 转换为我们传进来的类型;基本数据类型 不是继承自Object,所以在编译的过程中,底层没办法将int转换成Object 类型来处理。
同一泛型类根据不同的数据类型创建的对象,本质上是同一类型。(内存地址一样)
- 定义泛型类的语法
package com.hrkj.main.common;
/**
* 泛型类
* @param <T> 泛型标识 - 类型参数形参
*/
public class Generic<T> {
// T 是外部使用类的时候来指定
private T 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 类名<>();
泛型类注意事项
泛型类,如果没有指定具体的数据类型,此时操作类型是Object
泛型类的类型参数只能是类类型,不能是基本数据类型
泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型(内存地址是一样的)
泛型类派生子类
-
子类也是泛型类,子类和父类的泛型类型要一致;(子类泛型可以扩展)
class GhildGeneric<T> extends Generic<T> // 扩展为 class GhildGeneric<T,K,V> extends Generic<T> // 但是一定要存在T
-
子类不是泛型类,父类要明确泛型的数据类型
class GhildGeneric extends Generic<String>
泛型接口
泛型接口的使用
-
实现类不是泛型类,接口需要明确数据类型
// 泛型接口 public interface Generate<T>{ T getKey(); }
// 接口实现类不是泛型类,需要明确实现泛型接口的数据类型 public class Apple implements Generate<String>{ @Override public String getKey(){ return "hello!"; } }
// 接口实现类是泛型类,要保证实现接口的泛型类泛型标识包含泛型接口的泛型标识(可以扩展) public class Pair<T,K> implements Generate<T>{ private T key; private K value; public Pair(T key,E value){ this.key = key; this.value = value; } @Override public T getKey(){ return key; } @Override public K getValue(){ return value; } }
-
实现类也是泛型类,实现类和接口的泛型类型要一致
泛型方法
语法结构
修饰符 <T,E,...> 返回值类型 方法名(形参列表){
方法体....
}
public
与返回值中间的<T>
非常重要,可以理解为声明此方法为泛型方法- 只有声明了
<T>
的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。 <T>
表明该方法将使用泛型类型T
此时才可以在方法中使用泛型类型T
- 与泛型类的定义一样,此处T可以随便写为任意标识,常见的如:T、E、K、V等形式的参数常用于表示泛型
// 定义一个泛型方法;list泛型参数;E泛型标识,具体类型由调用方法的时候来指定
public <E> E getProduct(ArrayList<E> list){
return list.get(1);
}
即使定义的泛型方法的泛型标识和泛型类的泛型标识是一致的,在使用过程中,这两者是没有任何关系的,是独立存在的。
普通的成员方法如果采用了类的泛型,是不能被声明成静态方法的;如果是泛型方法,可以被声明成静态
// 成员方法使用了类的泛型,不能被定义成静态的 会编译报错
public static T getProduct(ArrayList<T> list){
return list.get(0);
}
// 泛型方法声明成静态方法,是允许的
public static <T> T getProduct(ArrayList<T> list){
return list.get(0);
}
//静态的泛型方法,采用多个泛型类型
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());
}
// 调用 测试
类名.printType(100,"java",true);
//泛型可变参数的定义
public static <E> void print(E... e){
for(int i=0;i<e.length;i++){
System.out.println(e[i]);
}
}
// 调用 测试
类名.print(1,2,3,4,5,6,7,8);
泛型方法总结:
1: 泛型方法能使方法独立于类而产生变化
2: 如果static方法要使用泛型能力,就必须使其成为泛型方法
类型通配符
什么是类型通配符?
类型通配符一般是用 “?” 来代替具体的类型实参。
所以 类型通配符是类型实参,而不是类型形参。
类对象中的泛型类型不能用多态和继承的思想去理解;比如 Integer extends Number
申明的泛型是Number
时 不能传入Integer
。使用泛型通配符?
来定义就可以传入任意类型。
// 使用类型通配符来 配置,可以传入任意类型
public static void act(Person<?> person){
// 方法体
}
类型通配符上限
什么是类型通配符上限?
语法:类/接口<? extends 实参类型>
要求该泛型的类型只能是该实参类型,或实参类型的子类型。
类型通配符的上限 是不可以填充元素的,就算是符合条件的元素也不可以添加,会报错。
因为:采用的上限通配符 在方法中无法确定类型。
// 泛型上限通配符,传递的集合类型只能是父类及其子类的类型;不能是父类的父类类型。
public static void act(ArrayList<? extends Number> list){
// 在此处需要注意的是:类型通配符的上限 是不可以填充元素的,就算是符合条件的元素也不可以添加,会报错。
list.add(new Number());// 此处不允许 会报错
// 方法体
}
类型通配符的下限
语法:
类/接口<? super 实参类型>
要求该泛型的类型只能是是参类型,或实参类型的父类型。
// 类型通配符的下限,要求集合只能是Person或Person的父类类型
public static void act(ArrayList<? super Person> list){
// 方法体
}
扩展:举例JDK
中TreeSet
的用法
源码截图:
@Test
public void test02(){
// TreeSet<Cat> treeSet = new TreeSet<>(new Comparator2()); 正确的
//TreeSet<Cat> treeSet = new TreeSet<>(new Comparator1()); 正确的
TreeSet<Cat> treeSet = new TreeSet<Cat>(new Comparator3()); // 报错
treeSet.add(new Cat("jerry",15));
treeSet.add(new Cat("amy",20));
treeSet.add(new Cat("frank",12));
treeSet.add(new Cat("jim",189));
treeSet.add(new Cat("jack",6));
for (Cat cat : treeSet) {
System.out.println(cat);
}
}
@Data
class Animal{
public String name;
public Animal(String name){
this.name =name;
}
}
class Cat extends Animal{
public Integer age;
public Cat(String name,Integer age) {
super(name);
this.age = age;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class MiniCat extends Cat{
public Integer level;
public MiniCat(String name, Integer age,Integer level) {
super(name, age);
this.level = level;
}
}
// 根据Animal的名字进行比较
class Comparator1 implements Comparator<Animal> {
@Override
public int compare(Animal o1, Animal o2) {
return o1.name.compareTo(o2.name);
}
}
// 根据 Cat的age 来进行比较
class Comparator2 implements Comparator<Cat>{
@Override
public int compare(Cat o1, Cat o2) {
return o1.age-o2.age;
}
}
// 根据MiniCat 的Level 来进行比较
class Comparator3 implements Comparator<MiniCat>{
@Override
public int compare(MiniCat o1, MiniCat o2) {
return o1.level-o2.level;
}
}
上方 传入
new Comparator3()
报错的原因是: 我们采用下限通配符,下限通配符只能传入该类的父类,而不能传入该类的子类;因为我们在构建子类对象的时候一般先构建父类对象。根据父类对象去比较是可以成功的。但是不会初始化子类对象,所以会导致空引用报错。
泛型擦除
泛型是
JAVA1.5
版本才引进的概念,在此之前是没有泛型的,但是泛型代码能够很好的兼容之前的代码,是因为泛型信息只存在于代码阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为--类型擦除
泛型与数组
可以声明带泛型的数组引用,但是补鞥呢直接创建带泛型的数组对象
可以通过
java.lang .reflect.Array
和new Instance(Class<T>,int)
创建Int
数组