深入了解JAVA泛型

什么是泛型

泛型的概念:Java泛型(generics)是JDK1.5中引入的一个新特性,泛型提供了编译时的类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。

泛型的本质就是类型参数化,也就是所操作的数据类型被指定为一个参数。

使用泛型的好处:

1  在编译期间提供了类型检查

2  取数据时无须进行类型装换

泛型类、接口

泛型类

语法:

class 类名称 <泛型标识,泛型标识,泛型标识,...> {
 private 泛型标识 变量名;
 // ...
}

常用的泛型标识:T、E、K、V

使用语法:

类名 <具体的数据类型> 对象名 = new 类名<具体的数据类型>();

JDK 1.7 之后,后面的 <> 中的具体的数据类型可以省略不写。

定义一个简单的泛型类:

/**
 * 泛型类 T:类型形参,在类创建对象时,指定具体的数据类型
 * @author rainszj
 * 2020/3/19
 */
public class GenericDemo01<T> {

 private T value;
 public GenericDemo01() {
 }

 public GenericDemo01(T value) {
  this.value = value;
 }

 @Override
 public String toString() {
  return "GenericDemo01{" +
    "value=" + value +
    '}';
 }

 public T getValue() {
  return value;
 }

 public void setValue(T value) {
  this.value = value;
 }
}

测试一下:

public class Test {

 public static void main(String[] args) {
  // 在创建对象时指定具体的数据类型
  GenericDemo01<String> genericDemo01 = new GenericDemo01<>("java");
  // 泛型类不支持基本数据类型,但可以使用基本类型对应的包装类
  GenericDemo01<Integer> genericDemo02 = new GenericDemo01<>(1);
  // 在泛型类对象时,不指定具体的数据类型,将会使用Object类型来接收

  // 同一个泛型类,根据不同数据类型创建的对象,本质上是同一类型,公用同一个类模板
  // class com.rainszj.GenericDemo01
  System.out.println(genericDemo01.getClass());
  // class com.rainszj.GenericDemo01
  System.out.println(genericDemo02.getClass());
  // true
  System.out.println(genericDemo01.getClass() == genericDemo02.getClass());

 }
}

注意事项:

泛型类,如果没有指定具体的数据类型,按Object类型来接收

泛型的类型参数只能是类类型,也就是引用数据类型,不能是基本数据类型

泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型

/**
 * 抽奖池
 *
 * @author rainszj
 * 2020/3/19
 */
public class ProductGetter<T> {
 // 奖品
 private T product;

 private ArrayList<T> list = new ArrayList<>();

 /**
  * 添加奖品
  *
  * @param product
  */
 public void addProduct(T product) {
  list.add(product);
 }

 /**
  * 抽取随机奖品
  *
  * @return
  */
 public T getProduct() {
  return list.get(new Random().nextInt(list.size()));
 }

 @Override
 public String toString() {
  return "ProductGetter{" +
    "product=" + product +
    '}';
 }
}

public static void main(String[] args) {
 ProductGetter<String> productGetter1 = new ProductGetter<>();
 // 奖品类型 礼物
 String[] products1 = {"华为手机", "苹果手机", "扫地机器人", "微波炉"};
 // 添加奖品
 for (int i = 0, length = products1.length; i < length; i++) {
 productGetter1.addProduct(products1[i]);
 }
 // 获取奖品
 String product1 = productGetter1.getProduct();
 System.out.println("恭喜您抽中了," + product1.toString());

 ProductGetter<Integer> productGetter2 = new ProductGetter<>();
 // 奖品类型 money
 Integer[] products2 = {1000, 3000, 10000, 500};
 for (Integer money : products2) {
 productGetter2.addProduct(money);
 }
 Integer product2 = productGetter2.getProduct();
 System.out.println("恭喜您抽中了," + product2.toString());
}

从泛型类派生子类

子类也是泛型类,子类的泛型标识 T 要和父类的泛型标识 T 保持一致,或者是包含关系,子类的泛型标识包含父类的泛型标识

class ChildGeneric<T> extends ParentGeneric<T>
class ChildGeneric<T, E> extends ParentGeneric<T>

子类不是泛型类,父类要明确泛型的数据类型

class ChildGeneric extends ParentGeneric<String>

泛型接口

语法:

interface 接口名称 <泛型标识,泛型标识,...> {
 泛型标识 方法名();
}

实现泛型接口的类,不是泛型类,需要明确实现泛型接口的数据类型

public class Apple implements Generic<String> {}

实现类也是泛型类,实现类和接口的泛型类型要一致,或者是包含关系,实现类的泛型标识包含泛型接口的泛型标识

public class Apple<K> implements Generic<K> {}
public class Apple<K, V> implements Generic<K> {}

定义一个泛型接口

public interface Generic<K> {
 K getKey();

}

实现其中方法:

/**
 * 泛型接口的实现类,是一个泛型类,
 * 那么要保证实现接口的泛型类的泛型标识包含泛型接口的泛型标识
 */
public class Pair<K, V> implements Generic<K> {
 private K key;
 private V value;

 public Pair() {
 }

 public Pair(K key, V value) {
  this.key = key;
  this.value = value;
 }

 @Override
 public K getKey() {
  return key;
 }

 public V getValue() {
  return value;
 }

 @Override
 public String toString() {
  return "Pair{" +
    "key=" + key +
    ", value=" + value +
    '}';
 }
}

测试:

public class MyTest {
 public static void main(String[] args) {
  Pair<String, Integer> pair = new Pair<>("数学", 100);
  System.out.println(pair.toString());
  // Pair{key=数学, value=100}
 }
}

泛型方法

普通泛型方法

泛型类,是在实例化类时指明泛型的具体类型。

泛型方法,是在调用方法时,指明泛型的具体类型。

语法:

修饰符 <T,E,...> 返回值类型 方法名(形参列表) {
 // 方法体...
}

public 与返回值中间 <T,E,...> (泛型列表)非常重要,可以理解为声明此方法为泛型方法。

只有声明了 <T,E,...> 的方法才是泛型方法,泛型类中使用了泛型的成员方法并不是泛型方法

<T> 表明该方法将使用泛型类型 T,此时才可以在方法中使用泛型类型 T。

public class ProductSetter<T> {

 private T product;
 private Random random= new Random();
 private ArrayList<T> list = new ArrayList<>();

 public void addProduct(T product) {
  list.add(product);
 }

 /**
  * @param list
  * @param <E> 泛型方法的类型,是在调用泛型方法时确定的
  * @return
  */
 public <E> E getProduct(ArrayList<E> list) {
  return list.get(random.nextInt(list.size()));
 }

 public T getProduct() {
  return list.get(random.nextInt(list.size()));
 }

 @Override
 public String toString() {
  return "ProductSetter{" +
    "product=" + product +
    '}';
 }
}

测试:

public static void main(String[] args) {
 ProductSetter<String> productSetter = new ProductSetter<>();
 String[] products1 = {"华为手机", "苹果手机", "扫地机器人", "微波炉"};
 for (int i = 0; i < products1.length; i++) {
  productSetter.addProduct(products1[i]);
 }
 System.out.println(productSetter.getProduct());

 ArrayList<String> list1 = new ArrayList<>();
 list1.add("华硕电脑");
 list1.add("苹果电脑");
 list1.add("华为电脑");
 String product1 = productSetter.getProduct(list1);
 System.out.println(product1 + "\t" + product1.getClass().getSimpleName());
 // 华为电脑 String
 ArrayList<Integer> list2 = new ArrayList<>();
 list2.add(1);
 list2.add(2);
 list2.add(3);

 Integer product2 = productSetter.getProduct(list2);
 System.out.println(product2 + "\t" + product2.getClass().getSimpleName());
 // 2 Integer
}

静态泛型方法

public static <T, E, K> void pringType(T k1, E k2, K k3) {
 System.out.println(k1 + "\t" + k1.getClass().getSimpleName());
 System.out.println(k2 + "\t" + k2.getClass().getSimpleName());
 System.out.println(k3 + "\t" + k3.getClass().getSimpleName());
}
// 方法的调用
ProductSetter.pringType(1, "hello", false);

// 输出结果
1 Integer
hello String
false Boolean

注意:

// 在泛型类中无法添加静态的 带有泛型成员方法,但可以添加静态的 泛型方法
public class Test<T> {

 // 带有泛型的成员方法
	// 错误
	public static T getKey(T key) {
	 return key;
	}

 // 泛型方法
	// 正确
	public static <E> E getKey(E key) {
	 return key;
	}
}

泛型方法中的可变参数

public class MyTest {
 public static void main(String[] args) {
  MyTest.print(1, 2, 3);
 }

 /**
  * 泛型方法中的可变长参数
  * @param value
  * @param <E>
  */
 public static <E> void print(E ... value) {
  for (int i = 0; i < value.length; i++) {
   System.out.println(value[i]);
  }
 }
}

总结:

泛型方法能使方法独立于类而产生变化。

如果 static 方法要使用泛型能力,就必须使其成为泛型方法。

类型通配符

类型通配符一般是使用 ? 代替具体的类型实参。

类型通配符是类型实参,而不是类型形参。

我们先来定义一个简单的泛型类:

public class Box<T> {
 private T width;
 public static void showBox(Box<Number> box) {
  Number width = box.getWidth();
  System.out.println(width);
 }

 public T getWidth() {
  return width;
 }

 public void setWidth(T width) {
  this.width = width;
 }
}

main方法:

public static void main(String[] args) {
 Box<Number> box1 = new Box<Number>();
 box1.setWidth(100);
 showBox(box1);
}

当我们在 main 方法中增加这一段代码时,就会报错

Box<Integer> box2 = new Box<>();
box2.setWidth(200);
showBox(box2);

虽然 Integer 类继承自 Number 类,但在类型通配符中不存在继承这一概念!

也许你会使用方法的重载,但是 在同一个泛型类中,根据不同数据类型创建的对象,本质上是同一类型,公用同一个类模板,所以无法通过方法的重载,传递不同的泛型类型。

这时可以使用类型通配符 ?,来代表具体的类型实参!

public static void showBox(Box<?> box) {
 Object width = box.getWidth();
 System.out.println(width);
}

类型通配符的上限

在我们上面的showBox()代码中,发现 box.getWidth()得到的还是Object类型,这和我们不使用类型通配符,得到的结果是一样的。这时我们可以使用类型通配符的上限。

语法:

类/接口 <? entends 实参类型>

要求该泛型的类型,只能是实参类型,或者是实参类型的子类类型。

public static void showBox(Box<? extends Number> box) {
 Number width = box.getWidth();
 System.out.println(width);
}

public static void main(String[] args) {
 Box<Integer> box2 = new Box<>();
 box2.setWidth(200);
 showBox(box2);
}

使用类型通配符的下限,无法得知该类型具体是指定的类型,还是该类型的子类类型,因此无法在 List 集合中执行添加该类或者该类子类的操作!

public static void showAnimal(List<? extends Cat> list) {
  // 错误
  list.add(new Cat());
 	 list.add(new MiniCat());
}

类型通配符的下限

语法

类/接口 <? super 实参类型>

要求该泛型的类型,只能是实参类型,或者是实参类型的父类类型。

下面通过 TreeSet 集合中的一个构造方法来进一步理解 类型通配符的下限

public TreeSet(Comparator<? super E> comparator) {
 this(new TreeMap<>(comparator));
}

首先是一个Animal类,只有一个 name 属性

public class Animal {
 private String name;

 public Animal(String name) {
  this.name = name;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 @Override
 public String toString() {
  return "Animal{" +
    "name='" + name + '\'' +
    '}';
 }
}

然后它的一个子类,Cat添加一个属性:age

public class Cat extends Animal {
 private int age;

 public Cat(String name, int age) {
  super(name);
  this.age = age;
 }

 public int getAge() {
  return age;
 }

 public void setAge(int age) {
  this.age = age;
 }

 @Override
 public String toString() {
  return "Cat{" +
    "age=" + age +
    '}';
 }
}

最后是 Cat 的子类,MiniCat,再添加一个属性 level

public class MiniCat extends Cat {
 private int level;

 public MiniCat(String name, int age, int level) {
  super(name, age);
  this.level = level;
 }

 public int getLevel() {
  return level;
 }

 public void setLevel(int level) {
  this.level = level;
 }

 @Override
 public String toString() {
  return "MiniCat{" +
    "level=" + level +
    '}';
 }
}

测试,首先我们要在MyTest类通过静态内部类的方式,实现比较的接口,在构造TreeSet时,传递比较器

public class MyTest {
 public static void main(String[] args) {
  	// 正常
  // TreeSet<Cat> animals = new TreeSet<Cat>(new Comparator1());
  // 正常
  TreeSet<Cat> animals = new TreeSet<Cat>(new Comparator2());
  	// 报错
				// TreeSet<Cat> animals = new TreeSet<Cat>(new Comparator3());
  List<Cat> list = Arrays.asList(new Cat("a", 12), new Cat("c", 9), new Cat("b", 20));
  animals.addAll(list);

  animals.forEach(System.out::println);

 }

 public static class Comparator1 implements Comparator<Animal> {

  @Override
  public int compare(Animal o1, Animal o2) {
   return o1.getName().compareTo(o2.getName());
  }
 }

 public static class Comparator2 implements Comparator<Cat> {

  @Override
  public int compare(Cat o1, Cat o2) {
   return o1.getAge() - o2.getAge();
  }
 }

 public static class Comparator3 implements Comparator<MiniCat> {

  @Override
  public int compare(MiniCat o1, MiniCat o2) {
   return o1.getLevel() - o2.getLevel();
  }
 }
}

结论:

通过以上的比较,我们可以看出,类型通配符的下限,只能传递实参类型的或者实参类型的父类类型。

我们每次比较使用的都是 Cat 类型,但在 Comparator1比较的是 Animal 中的 name 属性,这是因为 我们在初始化 Cat 对象的时候,一定会先初始化 Animal 对象,也就是创建子类对象的时候,一定会先创建父类对象,所以才可以进行比较。

如果是使用 类型通配符的上限,在创建对象时,比较的是该类的子类对象中的属性,就会造成空指针异常!也就是Comparator3无法使用的原因, 所以在 TreeSet 中才会使用 <? super E> ,类型通配符的下限。

类型擦除

泛型是Java 1.5 引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前的代码兼容。那是因为,泛型信息只存在编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,我们称之为——类型擦除

无限类型擦除

先定义一个泛型类:

public class Erasure<T> {
 private T key;

 public T getKey() {
 return key;
 }

 public void setKey(T key) {
 this.key = key;
 }
}

输出结构:

public static void main(String[] args) {
 Erasure<Integer> erasure = new Erasure<>();
 Class<? extends Erasure> cls = erasure.getClass();
 Field[] fields = cls.getDeclaredFields();
 for (Field field : fields) {
 System.out.println(field.getName() + ":" + field.getType().getSimpleName()); // key:Object
 }
}

可以发现在编译完成后的字节码文件中,T --> Object 类型

有限类型擦除

还是刚才的泛型类,只不过加了泛型的上限

public class Erasure<T extends Number> {// ...}

测试不变,输出结果:

key:Number

当我们指定了泛型的上限时,它会将我们的泛型擦除为上限类型

同样对泛型方法,也是一样的道理

// 泛型方法
public <E extends List> E test(E t) {
 return t;
}
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
 System.out.println(method.getName() + ":" + method.getReturnType().getSimpleName());
}

// 输出结果
// getKey:Number
// test:List
// setKey:void

桥接方法

泛型接口

public interface Info<T> {

 T test(T value);
}

泛型接口的实现类

public class InfoImpl implements Info<Integer> {
 @Override
 public Integer test(Integer value) {
  return value;
 }
}

测试

public static void main(String[] args) {
 Class cls = InfoImpl.class;
 Method[] methods = cls.getDeclaredMethods();
 for (Method method : methods) {
  System.out.println(method.getName() + ":" + method.getReturnType().getSimpleName());
 }
}

// 输出结果:
// test:Integer
// test:Object

原本 InfoImpl 中只是实现了 Info 接口中的一个方法,但通过反射却拿到了两个方法。其中返回值为 Object 的方法就是桥接方法。

在编译完成后,类型擦除的结果是这样的:

public interface Info {

 Object test(Object value);
}
public class InfoImpl implements Info {

 public Integer test(Integer value) {
  return value;
 }

 	// 桥接方法:保持接口和类的实现关系
 @Override
 public Object test(Object value) {
  return (Integer)value;
 }
}

泛型数组

开发中,一般常用的是泛型集合

泛型数组的创建:

可以声明带泛型的数组引用,但是不能直接创建带泛型数组对象。

可以通过 java.lang.reflect.Array newInstance(Class<T>, int)创建 T[ ] 数组。

// 可以创建带泛型的数组引用
ArrayList<String>[] arrayLists1 = new ArrayList[3];
// 无法创建带泛型的数组对象
ArrayList<String>[] arrayLists2 = new ArrayList<String>[3];

简单使用 java.lang.reflect.Array newInstance(Class<T>, int)创建 T[ ] 数组。 封装一个泛型数组

public class GenericArray<T> {
 private T[] array;

 public GenericArray(Class cls, int length) {
  this.array = (T[]) Array.newInstance(cls, length);
 }

 public void put(int index, T item) {
  this.array[index] = item;
 }

 public T get(int index) {
  return this.array[index];
 }

 public T[] getArray() {
  return this.array;
 }

 public static void main(String[] args) {
  GenericArray<String> ga = new GenericArray<>(String.class, 3);
  ga.put(0, "白虎");
  ga.put(1, "青龙");
  ga.put(2, "朱雀");

  System.out.println(Arrays.toString(ga.getArray()));

 }
}

泛型和反射

反射常用的泛型类:

Class

Constructor

通过反射创建对象,带泛型和不带泛型

Class<Cat> catClass1 = Cat.class;
try {
 Constructor<Cat> c1 = catClass1.getConstructor();
 Cat cat = c1.newInstance();
} catch (Exception e) {
 e.printStackTrace();
}

Class catClass2 = Cat.class;
try {
 Constructor c2 = catClass2.getConstructor();
 Object cat2 = c2.newInstance();
} catch (Exception e) {
 e.printStackTrace();
}

以上就是深入了解JAVA泛型的详细内容,更多关于JAVA泛型的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java中泛型使用的简单方法介绍

    一. 泛型是什么 "泛型",顾名思义,"泛指的类型".我们提供了泛指的概念,但具体执行的时候却可以有具体的规则来约束,比如我们用的非常多的ArrayList就是个泛型类,ArrayList作为集合可以存放各种元素,如Integer, String,自定义的各种类型等,但在我们使用的时候通过具体的规则来约束,如我们可以约束集合中只存放Integer类型的元素,如List<Integer> iniData = new ArrayList<>().

  • 浅谈三分钟学习Java泛型中T、E、K、V、?的含义

    泛型是Java中一个非常重要的内容,对于Java进阶学习是必须要掌握的知识点之所以说这个知识点重要,如果你有过阅读过一些开源框架的代码,那你一定会看到源码中有很多地方使用到了泛型. 随便举两个例子,一个List,一个Map. 看了上面的源码,简单聊一下泛型,也就是回顾一下泛型的相关知识,来源百度百科. [泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法.Java语

  • Java泛型的用法及T.class的获取过程解析

    这篇文章主要介绍了Java泛型的用法及T.class的获取过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 胡乱总结泛型的四点作用: 第一是泛化,可以拿个T代表任意类型. 但GP是被C++严苛的静态性逼出来的,落到Java.C#这样的花语平原里----所有对象除几个原始类型外都派生于Object,再加上Java的反射功能,Java的Collection库没有范型一样过得好好的. 第二是泛型 + 反射,原本因为Java的泛型拿不到T.cla

  • JAVA泛型的继承和实现、擦除原理解析

    这篇文章主要介绍了JAVA泛型的继承和实现.擦除原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 很多的基础类设计会采用泛型模式,有些应用在使用的时候处于隔离考虑,会进行继承,此时子类如何继承泛型类就很讲究了,有些情况下需要类型擦除,有些情况下不需要类型擦除,但是大多数情况下,我们需要的是保留父类的泛型特性.因为类型被擦除后,得到的对象会是Object,此时会导致编码或反序列化失败. 如下所示: 如果子类也继承父类的泛型化,则类型不会被擦

  • java泛型常用通配符实例解析

    这篇文章主要介绍了java泛型常用通配符实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 今天在看ArrayList原码是看到这样的一个符号,好奇怪. ?表示通配符,表示的意思是匹配E或E的子类,具体类型未知. 1.限定通配符 编写一个类似于ArrayList的动态数据 public class Gys<T> { private final static int default_capacity =10; private int end

  • Java泛型类与泛型方法的定义详解

    本文实例讲述了Java泛型类与泛型方法的定义.分享给大家供大家参考,具体如下: Java泛型类的定义 一 点睛 泛型类定义的语法如下: [访问修饰符] class 类名称 <T> 泛型类的主要作用在于类被实例化后,传入具体的类型参数,对类的成员属性的类型和成员方法的参数类型和返回值类型进行替换. 二 代码 public class Base<T> { T m; Base(T t) { m = t; } public T getM(){ return m; } public void

  • Java泛型extends关键字设置边界的实现

    本文主要介绍在泛型定义中的< >中的占位符如何配合extends关键字使用,形如<T extends Integer>.泛型定义存在于这三种形式中:泛型类.泛型接口.泛型方法. 一般的泛型定义中的<T>,相当于<T extends Object>,而类型擦除则会将类型参数擦除成T的上界,即Object.则在泛型定义中作为T类型的对象可以调用Object的函数和属性. 使用了extends的泛型定义中的<T extends Integer>,其上界

  • Java泛型变量如何添加约束

    有时,类或方法需要对类型变量加以约束.下面是一个典型的例子,我们要寻找数组中的最小元素: public class ArrayAlg { public static <T extends Comparable> T min(T[] array){ if (array == null || array.length == 0){ return null; } T smallest = array[0]; for (int i=0;i<array.length;i++){ if (small

  • Java封装数组之改进为泛型数组操作详解

    本文实例讲述了Java封装数组之改进为泛型数组操作.分享给大家供大家参考,具体如下: 前言:通过上一节我们对我们需要封装的数组,进行了基本的增删改查的封装,但只局限于int类型的操作,为了能提供多种类型数组的操作,我们可以将其进一步封装为泛型数组. 1.定义泛型数组相关概念 (1)泛型数组让我们可以存放任何数据类型 (2)存放的类型不可以是基本数据类型,只能是类对象 基本类型: boolean.byte.char.short.int.long.float.double (3)每个基本数据类型都有

  • Java泛型初学者之上、下界通配符的深入理解

    泛型的由来 为什么需要泛型 Java的数据类型一般都是在定义时就需要确定,这种强制的好处就是类型安全,不会出现像弄一个ClassCastException的数据给jvm,数据安全那么执行的class就会很稳定.但是假如说我不知道这个参数要传什么类型的,因为公司需求在变,如果写死的那就只能便以此需求就改一次,很麻烦.sun公司也注意到这个问题,这样会让代码的灵活性降低,他们就研究出了泛型. 泛型初识 什么是泛型,可以字面理解就是一个泛泛的类型,他是不确定的,在Java代码编译的时候用泛型是不会出错

随机推荐