深入了解JAVA泛型

网友投稿 215 2022-12-05


深入了解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 {

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 genericDemo01 = new GenericDemo01<>("java");

// 泛型类不支持基本数据类型,但可以使用基本类型对应的包装类

GenericDemo01 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 {

// 奖品

private T product;

private ArrayList 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 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 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 extends ParentGeneric

class ChildGeneric extends ParentGeneric

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

class ChildGeneric extends ParentGeneric

泛型接口

语法:

interface 接口名称 <泛型标识,泛型标识,...> {

泛型标识 方法名();

}

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

public class Apple implements Generic {}

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

public class Apple implements Generic {}

public class Apple implements Generic {}

定义一个泛型接口

public interface Generic {

K getKey();

}

实现其中方法:

/**

* 泛型接口的实现类,是一个泛型类,

* 那么要保证实现接口的泛型类的泛型标识包含泛型接口的泛型标识

*/

public class Pair implements Generic {

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 pair = new Pair<>("数学", 100);

System.out.println(pair.toString());

// Pair{key=数学, value=100}

}

}

泛型方法

普通泛型方法

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

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

语法:

修饰符 返回值类型 方法名(形参列表) {

// 方法体...

}

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

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

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

public class ProductSetter {

private T product;

private Random random= new Random();

private ArrayList list = new ArrayList<>();

public void addProduct(T product) {

list.add(product);

}

/**

* @param list

* @param 泛型方法的类型,是在调用泛型方法时确定的

* @return

*/

public E getProduct(ArrayList 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 productSetter = new ProductSetter<>();

String[] products1 = {"华为手机", "苹果手机", "扫地机器人", "微波炉"};

for (int i = 0; i < products1.length; i++) {

productSetter.addProduct(products1[i]);

}

System.out.println(productSetter.getProduct());

ArrayList list1 = new ArrayList<>();

list1.add("华硕电脑");

list1.add("苹果电脑");

list1.add("华为电脑");

String product1 = productSetter.getProduct(list1);

System.out.println(product1 + "\t" + product1.getClass().getSimpleName());

// 华为电脑 String

ArrayList 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 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

注意:

// 在http://泛型类中无法添加静态的 带有泛型成员方法,但可以添加静态的 泛型方法

public class Test {

// 带有泛型的成员方法

// 错误

public static T getKey(T key) {

return key;

}

// 泛型方法

// 正确

public static E getKey(E key) {

return key;

}

}

泛型方法中的可变参数

public class MyTest {

public static void main(String[] args) {

MyTest.print(1, 2, 3);

}

/**

* 泛型方法中的可变长参数

* @param value

* @param

*/

public static void print(E ... value) {

for (int i = 0; i < value.length; i++) {

System.out.println(value[i]);

}

}

}

总结:

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

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

类型通配符

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

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

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

public class Box {

private T width;

public static void showBox(Box 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 box1 = new Box();

box1.setWidth(100);

showBox(box1);

}

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

Box 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 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 animals = new TreeSet(new Comparator1());

// 正常

TreeSet animals = new TreeSet(new Comparator2());

// 报错

// TreeSet animals = new TreeSet(new Comparator3());

List 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 {

@Override

public int compare(Animal o1, Animal o2) {

return o1.getName().compareTo(o2.getName());

}

}

public static class Comparator2 implements Comparator {

@Override

public int compare(Cat o1, Cat o2) {

return o1.getAge() - o2.getAge();

}

}

public static class Comparator3 implements Comparator {

@Override

public int compare(MiniCat o1, MiniCat o2) {

return o1.getLevel() - o2.getLevel();

}

}

}

结论:

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

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

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

类型擦除

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

无限类型擦除

先定义一个泛型类:

public class Erasure {

private T key;

public T getKey() {

return key;

}

public void setKey(T key) {

this.key = key;

}

}

输出结构:

public static void main(String[] args) {

Erasure 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 {// ...}

测试不变,输出结果:

key:Number

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

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

// 泛型方法

public 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 test(T value);

}

泛型接口的实现类

public class InfoImpl implements Info {

@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, int)创建 T[ ] 数组。

// 可以创建带泛型的数组引用

ArrayList[] arrayLists1 = new ArrayList[3];

// 无法创建带泛型的数组对象

ArrayList[] arrayLists2 = new ArrayList[3];

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

public class GenericArray {

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 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 catClass1 = Cat.class;

try {

Constructor 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泛型的资料请关注我们其它相关文章!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:SpringSceurity实现短信验证码功能的示例代码
下一篇:Java kafka如何实现自定义分区类和拦截器
相关文章

 发表评论

暂时没有评论,来抢沙发吧~