泛型之不变、协变、逆变

2024-02-23 14:20
文章标签 泛型 不变 逆变 协变

本文主要是介绍泛型之不变、协变、逆变,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

什么是泛型

早期在java1.5之前,java集合类是没有泛型的、集合内存储的对象都是object。这就导致了几个问题。
1.存储的元素取出来、需要类型强制转换、而且不能保证其准确性、如下图,改元素第一个需要转换成String 类型、第二个元素要转成Integer类型。代码编写很容易出问题

        ArrayList<Object> objects = new ArrayList();objects.add("hello world");objects.add(new Integer(1));// 需要强转Object o = objects.get(0);//此处运行报错Integer index0 = (Integer)objects.get(0);

2.存储元素不能在编译时,提示错误,直到运行时才能抛出错误
为了解决以上问题,java1.5之后就提出了泛型的概念、泛型是一种标记在类,或者方法上的类型,他并不代表真实的类型,代表着一种行为约束

泛型的作用

        ArrayList<CharSequence> charSequences = new ArrayList<CharSequence>();charSequences.add("hello world");//编译报错charSequences.add(1.2);CharSequence charSequence = charSequences.get(0);

1.上面示例代码可以看出、在编译时,如果类型不是泛型类型,直接编译不通过
2.取出元素、不需要强制转换
3. 可以让代码抽象程度更高

java中的泛型为何要在编译后做泛型擦出

1、java1.5之后的版本,需要兼容之前没有泛型的版本、编译之后和1.5之前编译之后的字节码保持一致

        ArrayList<CharSequence> charSequences = new ArrayList<CharSequence>();ArrayList<String> charSequences = new ArrayList<String>();

编译后字节码一致
2、擦出之后如何避免强制转换的
如下是list get方法的源码、直接强制转换成泛型类型在这里插入图片描述

泛型的上界 和下界、对泛型做约束

java 中 使用的 super ,extends
kotlin 中使用 :
如果需要约定多个类型kotlin 可以使用where 关键字

// 泛型 上界约束
open  class Fruit(val weight: Double)
class Apple(weight: Double) : Fruit(weight)
class Banana(weight: Double) : Fruit(weight)open interface Animal{}
class Fish( weight: Double):Animal
class Cat( weight: Double):Animalclass Monkey(weight: Double): Fruit(weight),Animal// 容器约定必须为fruit 子类
class Container<T:Fruit>(t:T)//只能继承一个类,实现多个接口
class  Box<T> (t:T) where T:Fruit , T:Animal

在这里插入图片描述

什么是不变

泛型不变,就类、或者方法泛型、泛型的类型 定义,不可以使用其子类或者父类来接受、java中的集合都是泛型不变的,例如 CharSequence 是String 类型的父类

  ArrayList<CharSequence> charSequences = new ArrayList<CharSequence>();// 不允许// ArrayList<CharSequence> charSequences2 = new ArrayList<String>();

什么是协变

java 中典型的例子就是数组、Fruit 是Apple 的父类、子类的类型可以用父类的类型接受
// 协变 A是B 的父类 ,那么 泛型就是泛型的父类

     // 允许Fruit[] arr =  new Apple[10];

在koltin 中、我们可以使用 out 关键字,打破集合的不变,实现集合的协变
在这里插入图片描述
在这里插入图片描述
这里使用out 关键字,可以使用父类泛型接受子类类型

什么是逆变

    // 协变  A是B 的父类  ,那么  范型A 就是泛型B的子类
// 容器约定必须为fruit 子类
class Container2<in T>(t:T)class Container3<in T>(t:T)//只能继承一个类,实现多个接口
class  Box<T> (t:T) where T:Fruit , T:Animalfun <T> hello(t:T) where T:Fruit ,T:Animal{}fun check(){// outvar father = Container<Fruit>(Fruit(1.2))father =  Container<Apple>(Apple(1.2))// in 子类类型接受父类类型var son = Container2<Banana>(Banana(1.2))son =  Container2<Fruit>(Fruit(1.2))var fish = Container3<Fish>(Fish(1.2))fish =  Container3<Animal>(object :Animal{})
}

泛型形变的应用场景

1.需求:将double 数组拷贝到另外一个数组、代码实现如下

//拷贝double
fun copy(dest:Array<Double?>,src:Array<Double>){if (dest.size< src.size){throw IndexOutOfBoundsException()}else{src.forEachIndexed  { index, d -> dest[index] = d }}
}

2.需求:如果上述拷贝的是Int ,是不是要写一个Int 类型的方法,显然不合适,于是呼我们使用泛型写出如下代码,这样拷贝任意类型都可以使用如下方法

//拷贝任意类型
fun <T> copyUseTypeRefrence(dest:Array<T?>,src:Array<T>){if (dest.size< src.size){throw IndexOutOfBoundsException()}else{src.forEachIndexed  { index, d -> dest[index] = d }}
}

3.需求:如果拷贝子类,用父类接受,比如拷贝apple数组 到 Fruit 数组中、我们就需要用到变型

//拷贝任意类型
// 拷贝父子类型 协变 out 版本 、返回值,提供者
fun <T> copyUseOut(dest:Array<T?>,src:Array< out T>){if (dest.size< src.size){throw IndexOutOfBoundsException()}else{src.forEachIndexed  { index, d -> dest[index] = d }}
}// 拷贝父子类型 逆变in 版本 ,入参、消费者 
fun <T> copyUseIn(dest:Array< in T?>,src:Array<  T>){if (dest.size< src.size){throw IndexOutOfBoundsException()}else{src.forEachIndexed  { index, d -> dest[index] = d }}
}

测试代码

fun main() {val src = arrayOf(1.0, 2.0, 3.0)val dest = arrayOfNulls<Double>(3)copy(dest, src )println("普通拷贝")dest.forEach { print(it)}println()val src2 = arrayOf(1.0, 2.0, 3.0)val dest2 = arrayOfNulls<Double>(3)copyUseTypeRefrence(dest2, src2 )println("泛型拷贝")dest2.forEach { print(it)}println()val src3 = arrayOf(Apple(1.0), Apple(2.0), Apple(3.0))val dest3 = arrayOfNulls<Fruit>(3)copyUseOut(dest3, src3 )println("泛型拷贝out")dest3.forEach { print(it)}println()val src4 = arrayOf(Apple(1.0), Apple(2.0), Apple(3.0))val dest4 = arrayOfNulls<Fruit>(3)copyUseIn(dest4, src4 )println("泛型拷贝in")dest4.forEach { print(it)}println()}

测试结果:

/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=64273:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_341.jdk/Contents/Home/jre/lib/rt.jar:/Users/yang/gitcode/kotlin/out/production/kotlin:/Users/yang/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.7.10/kotlin-stdlib-jdk8-1.7.10.jar:/Users/yang/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib/1.7.10/kotlin-stdlib-1.7.10.jar:/Users/yang/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib-common/1.7.10/kotlin-stdlib-common-1.7.10.jar:/Users/yang/.m2/repository/org/jetbrains/annotations/13.0/annotations-13.0.jar:/Users/yang/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.7.10/kotlin-stdlib-jdk7-1.7.10.jar TypeParamKt
普通拷贝
1.02.03.0
泛型拷贝
1.02.03.0
泛型拷贝out
Apple(weight=1.0)Apple(weight=2.0)Apple(weight=3.0)
泛型拷贝in
Apple(weight=1.0)Apple(weight=2.0)Apple(weight=3.0)Process finished with exit code 0

代码:https://gitcode.net/mid120/kotlin/-/commit/718964acc4d7ce36970564ff0755ec47f6939edf?spm=1033.2243.3001.5872

这篇关于泛型之不变、协变、逆变的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/738925

相关文章

Java中JSON字符串反序列化(动态泛型)

《Java中JSON字符串反序列化(动态泛型)》文章讨论了在定时任务中使用反射调用目标对象时处理动态参数的问题,通过将方法参数存储为JSON字符串并进行反序列化,可以实现动态调用,然而,这种方式容易导... 需求:定时任务扫描,反射调用目标对象,但是,方法的传参不是固定的。方案一:将方法参数存成jsON字

Java泛型类型解析

解析泛型类型 获取字段泛型类型 **java.lang.reflect.Field#getGenericType**: 作用:返回字段的泛型类型。返回类型:Type。如果字段是一个泛型类型,这个方法将返回一个表示这个泛型类型的 Type 对象,比如 ParameterizedType,TypeVariable 等等。如果字段不是泛型类型,这个方法将返回字段的具体类型,即 Class 对象。示例

【数据结构】--初识泛型

1. 包装类 在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。 1.1 基本数据类型和对应的包装类 除了 Integer 和 Character, 其余基本类型的包装类都是首字母大写。 1.2 (自动)装箱和(自动)拆箱 装箱(装包): 把 基本数据类型 变为 包装类类型 的过程 叫做装箱。 反汇编指

泛型参Class、Class、Class的对比区别

1.原文链接 泛型参Class、Class、Class的对比区别 https://blog.csdn.net/jitianxia68/article/details/73610606 <? extends T>和<? super T> https://www.cnblogs.com/drizzlewithwind/p/6100164.html   2.具体内容: 泛型参数Class、

Java|泛型的使用

文章目录 概述泛型的基本概念泛型类示例 泛型方法示例 泛型接口示例 泛型的类型参数约束示例 通配符(Wildcard)上界通配符(`? extends T`)下界通配符(`? super T`) 泛型的类型擦除类型擦除的影响 泛型中的常见限制泛型的优点 总结 概述 泛型(Generic)是 Java 5 引入的一项特性,它允许类、接口和方法可以处理任何类型的数据,而不必指定具体

hibernate泛型Dao,让持久层简洁起来

【前言】hibernate作为持久层ORM技术,它对JDBC进行非常轻量级对象封装,使得我们可以随心所欲的使用面向对象的思想来操作数据库。同时,作为后台开发的支撑,的确扮演了一个举足轻重的角色,那么我们在项目中如何灵活应用hibernate,也会给项目维护以及项目开发带来便利,下面我将展示我们项目中是如何来对hibernate进行应用和操作。 【目录】              -

AI时代产品经理面临的变与不变:0经验求职产品经理要注意哪些细节?

AI时代,各种产品形态、业务的变化,让市场也对产品经理提出了新的要求,产品经理要有哪些变与不变呢?现在入行产品经理是好时机么?没有技术背景、没有学历有优势如何入行做产品经理?今天我们一起探讨一下! 产品人究竟需要具备哪些能力?看这个最新的能力模型图就知道了。 随着当前市场的细分,不同行业和领域对产品经理的能力要求已经从单一的具备产品专业能力演变成了兼具产品专业技能+行业/业务知识

dart 中的 泛型 怎么写??

import 'package:flutterdemo/bean/MyUser.dart';class BaseResp<T> {int code;String str;T data;BaseResp({this.code, this.str, this.data});BaseResp.fromJson(Map<String, dynamic> jsonStr) {code = jsonStr['

泛型第四课,自定义实现迭代器、深入迭代器、迭代器原理,面向对象

package com.pkushutong.genericity4;import java.util.Iterator;/*** 简化迭代器原理* @author dell**/public class Array2 implements java.lang.Iterable<String>{private String[] elem = {"a","b","c","d","e","f","g

泛型第三课,自定义泛型、无多态、通配符、无泛型数组

泛型没有多态 package com.pkushutong.genericity4;/*** 多态的两种形式* 注:泛型没有多态* @author dell**/public class Test01 {public static void main(String[] args) {Fruit f = new Fruit();test(new Apple());}//形参使用多态publi