相关文章推荐
性感的枇杷  ·  lDA困惑度是负的 lda ...·  6 月前    · 
打盹的酸菜鱼  ·  scipy的中值滤波_scipy中值滤波-C ...·  9 月前    · 
有胆有识的小狗  ·  什么是业务规则? | IBM·  1 年前    · 
俊逸的青蛙  ·  pexpect(4.6.0)文档_pexpe ...·  1 年前    · 
坏坏的骆驼  ·  通过主机名字获取与端口号获取对应的IP地址_ ...·  1 年前    · 
Code  ›  Java泛型学习记录_required type: t provided: object
string arraylist 通配符 泛型
https://blog.csdn.net/yudan505/article/details/114782683
个性的骆驼
8 月前
        • 1 什么是泛型(Generic Type)
        • 2 泛型怎么用
          • 2.1 泛型类
          • 2.2 泛型接口
          • 2.3 泛型方法
        • 3 什么时候使用泛型
          • 3.1 类型检查和自动转型
          • 3.2 类型约束
        • 4 声明static就不能用泛型了吗
          • 4.1 静态变量 不能是泛型
          • 4.2 静态内部类 可以使用泛型
          • 4.3 静态代码块 不能使用泛型
          • 4.4 静态方法 可以使用自身定义的泛型,而不能使用类定义的泛型
        • 5 泛型的 协变(Covariant) 与 逆变(Contravariant) 以及 不变(Invariance)
          • 5.1 泛型具有不变性
          • 5.2 让泛型具有协变性
          • 5.3 让泛型具有逆变性
          • 5.4 小结
        • 6 什么是类型擦除
        • 7 为什么Java使用擦除法实现泛型
          • 7.1 来看看c++的泛型
          • 7.2 为什么Java使用擦除法实现泛型
          • 7.3 那类型擦除会引发什么问题
            • 7.3.1 在运行时无法获取泛型的类型
            • 7.3.2 导致泛型具有不变性
          • 8 泛型面试问题整理(持续完善中)
          class A < T extends Object & Serializable > { } A < String > a = new A < > ( ) ; A < Aa > aa = new A < > ( ) ; //报错!!!-> 限制A不单要继承Object还需要可序列化 //限制传入参数是同样的类型 < G > void get ( List < G > list1 , List < G > list2 )
          4 声明static就不能用泛型了吗

          记住 :泛型是使用的时候才确定类型,所以一开始就需要确定类型的地方就不能用泛型了。

          class a{
            static class A<T> {
            	//报错 -> '...A.this' cannot be referenced from a static context
            	static T t; 
            	//报错 -> '...A.this' cannot be referenced from a static context
            	static {
               	T tt; 
            	//报错 -> '...A.this' cannot be referenced from a static context      
            	static void set(T t){}
            	static <G> void get(G g){}
          
          4.1 静态变量 不能是泛型

          静态变量:是随着类加载时被完成初始化的,它在内存中仅有一个,且JVM也只会为它分配一次内存,同时类所有的实例都共享静态变量,可以直接通过类名来访问它。
          实例变量:它是伴随着实例的,每创建一个实例就会产生一个实例变量,它与该实例同生共死。

          由于静态变量在类加载就已经初始化了,所以必须是明确的类型。无法等到实例创建才确定类型。

          4.2 静态内部类 可以使用泛型

          静态内部类跟普通类差不多,都是需要实例化的。在实例的时候给泛型指定具体类型就可以了。

          4.3 静态代码块 不能使用泛型

          跟静态变量一样,静态代码块只在类加载的时候会调一次。实例化是不会调用的,所有无法给泛型指定类型。

          4.4 静态方法 可以使用自身定义的泛型,而不能使用类定义的泛型

          方法中声明的泛型,是在调用的时候才确定泛型的类型。所有静态方法是可以使用自身定义的泛型。

          但是静态方法不能使用类中声明的泛型,因为静态方法会随着类的定义而被分配和装载入内存中,无法访问实例方法以及实例变量。

          5 泛型的 协变(Covariant) 与 逆变(Contravariant) 以及 不变(Invariance)

          协变与逆变用来描述类型转换(type transformation)后的继承关系:A、B表示类型;f(·)表示类型转换;A<=B表示A为B的子类。

          那么则存在:
          f(·)是协变性:当A<=B ,f(A)<=f(B)成立
          f(·)是逆变性:当A<=B ,f(A)>=f(B)成立
          f(·)是不变性:当A<=B ,f(A) 和f(B)不存在继承关系

          //定义几个类
          static class 生物 {}
          static class 动物 extends 生物 {}
          static class 猪 extends 动物 {}
          static class 人 extends 动物 {}
          static class 赛亚人 extends 人 {}
          static class 橡胶人 extends 人 {}
          
          5.1 泛型具有不变性
          //报错 -> Required type: ArrayList<动物>  Provided: ArrayList<人>
          ArrayList<动物> a = new ArrayList<人>();
          //报错 -> Required type: ArrayList<人>  Provided: ArrayList<动物>
          ArrayList<人> b = new ArrayList<动物>();
          

          当人<=动物,f(人) 和f(动物)不存在继承关系

          Why? 因为类型擦除。在运行时泛型信息会被擦除掉。擦除掉后,就不知道它到底是人还是动物了。

          5.2 让泛型具有协变性

          先来看看数组的协变性吧!

          //数组是具有协变性的,因为数组的类型不会被擦除
          //这样会导致 array[1] = 1 可以编译通过,因为int是Object的子类。但这样的操作是有问题的,而这个问题在代码执行到这里的时候才会出现。
          Object[] array = new String[5];
          array[0] = "llk";
          array[1] = 1; //抛异常:java.lang.Integer cannot be stored in an array of type java.lang.String[]
          

          再看看下边的代码

          ArrayList<? extends 动物> list = new ArrayList<人>();
          list.add(new 动物()); //报错 -> Required type: capture of ? extends 动物  Provided: 动物
          list.add(new 人()); //报错 -> Required type: capture of ? extends 动物  Provided: 人
          list.add(new 猪()); //报错 -> Required type: capture of ? extends 动物  Provided: 猪
          动物 a = list.get(0);
          生物 b = list.get(1);
          人 c = list.get(2); //报错 -> Required type: 人  Provided: capture of ? extends 动物
          

          使用上限通配符(?extends Class)
          当人<=动物,f(人) <=f(动物)成立

          为什么无法往list里边添加任何数据呢?
          ?extends 动物,限制了list只能存放继承自动物的子类,由于无法确定是哪个子类(有可能是人也有可能是猪),这种情况为了保证安全直接报错提醒。防止出现像上边数组的问题,那是非常危险的。

          那list存放的是子类为什么 人 c = list.get(2) 会报错?
          由于无法确定里边存是什么子类,所以只能读取出动物以及动物的父类。

          使用了上限通配符后,list相当于变成了只读。

          5.3 让泛型具有逆变性
          ArrayList<? super 人> list = new ArrayList<动物>()
          
          
          
          
              
          ;
          list.add(new 赛亚人());
          list.add(new 橡胶人());
          list.add(new 人());
          list.add(new 猪()); //报错 -> Required type: capture of ? super 人  Provided: 猪
          list.add(new 动物()); //报错 -> Required type: capture of ? super 人  Provided: 动物
          人 a = list.get(0); //报错 -> Required type: 人  Provided: capture of ? super 人
          Object b = list.get(1);
          

          使用下限通配符(?super Class)
          当人<=动物,f(人) >=f(动物)成立

          list能添加数据了而且也是new ArrayList<动物>,为什么不能添加动物类了呢?
          ? super 人,限制了list只支持添加人以及人的子类,但是list里边存的却是人以及人的父类。

          为什么取值只能返回Object?
          由于无法确定存的是哪个父类,所有取值是只能返回万物的鼻祖Object。

          使用了下限通配符后,该list相当于变成了 只写。

          5.4 小结

          ?extends T 存放的类型一定为T及其子类,但是获取要用T或者其父类引用。转型一致性
          ?super T 存放的类型一定为T的父类,但添加一定为T和其子类对象。转型一致性
          ?extends T 进行add(T子类)编译出错:因为无法确定到底是哪个子类
          ?super T get()对象,都是Object类型,因为T的最上层父类是Object,想要向下转型只能强转。

          6 什么是类型擦除

          泛型信息只存在于代码编译阶段,在运行时泛型相关的信息会被擦除掉。

          //java中
          List<String> l1 = new ArrayList<String>();
          List<Integer> l2 = new ArrayList<Integer>();
          System.out.println(l1.getClass() == l2.getClass()); //输出true
          //class中 
          //在运行时所有的<>以及里边的内容都会被擦除掉,
          ArrayList var1 = new ArrayList();
          ArrayList var2 = new ArrayList();
          System.out.println(var1.getClass() == var2.getClass());
          
          7 为什么Java使用擦除法实现泛型
          7.1 来看看c++的泛型
          //c++模板
          class Test {
          public:
              void dosomething() {}
          template<class T> class AClass {
          public:
              AClass(T tt) { 
                  t = tt; 
              void dosomething() {
                  t.dosomething(); 
          int main(int argc, const char * argv[]) {
              Test test;
              AClass<Test> aClass(test);
              aClass.dosomething();
              return 0;
          

          c++编译器用实参来为我们推断模板实参,并为我们实例化一个特定版本的代码。当编译器实例化一个模板时,它使用实际的模板实参代替对应的模板参数来创建出模板的一个新“实例”。被编译器生成的版本通常称为模板的实例。在编译时,编译器会将T替换成Test并生成模板实例。

          而java的泛型就不太一样。Java采用的是擦除法实现的伪泛型。

          7.2 为什么Java使用擦除法实现泛型

          主要原因:兼容旧版本
          因为Java最开始的版本是不支持泛型的,Java5才支持。为了兼容旧版让旧版Jdk编译出来的class也能在新版本虚拟机上继续使用。

          7.3 那类型擦除会引发什么问题
          7.3.1 在运行时无法获取泛型的类型

          泛型在运行时会被擦除,但是在字节码里泛型的定义是不会被擦除的。所有可以通过获取类信息的方法来获取泛型信息。

           class A<T, K> { }
          //栗子1
          //报错:Cannot select from parameterized type
          System.out.println("A Info -> " + A<String, Integer>.class);
          //栗子2
          //输出:A info -> class com.llk.kt.A
          System.out.println("A info -> " + A.class);
          //栗子3 泛型会在运行时被擦除,所有打印跟栗子2一样。
          //输出:A info -> class com.llk.kt.A
          System.out.println("A info -> " + new A<String, Integer>().getClass());
          //栗子4
          Type type = new A<String, Integer>(){}.getClass().getGenericSuperclass();
          //输出:A info -> com.llk.kt.A<java.lang.String, java.lang.Integer>
          System.out.println("A info -> " + type);
          ParameterizedType pt = (ParameterizedType) type;
          //输出:A generic info -> [class java.lang.String, class java.lang.Integer]
          System.out.println("A generic info -> " + Arrays.toString(pt.getActualTypeArguments()));
          

          为什么栗子4就可以获取到泛型信息?

          因为加了{}这个语法糖后java会帮我们创建一个匿名类并且实例化它。我们通过这个匿名类就能拿到泛型信息。

          //javac 编译后,发现除了Test.class、Test$A.class文件外,还多出了Test$1.class文件
          //Test$1.class文件
          class Test$1 extends A<String, Integer> {
              Test$1(Test var1) {
                  this.this$0 = var1;
          //Test.class文件
          System.out.println("A Info -> " + Test.A.class);
          PrintStream var10000 = System.out;
          Test.A var10001 = new Test.A();
          var10000.println("A Info -> " + var10001.getClass());
          //没错这里是创建了匿名类Test$1,然后通过匿名类获取类中的泛型信息。因为class中的泛型信息是不会被擦除的。
          Type var1 = (new Test.A<String, Integer>() {}).getClass().getGenericSuperclass();
          System.out.println("A info -> " + var1);
          ParameterizedType var2 = (ParameterizedType)var1;
          System.out.println("A generic info -> " + Arrays.toString(var2.getActualTypeArguments()));
          

          实际应用例子:Gson库的TypeToken

          //为了强制让你加上{},还特意将构造函数设置为protected
          //如果忘记加{},就会报错:'TypeToken()' has protected access in 'com.google.gson.reflect.TypeToken'
          TypeToken<A<String, Integer>> typeToken = new TypeToken<A<String, Integer>>(){};
          System.out.println("A Info -> " + typeToken.getType());
          //TypeToken源码
          public class TypeToken<T> {
            ...
            final Type type;
            protected TypeToken() {
              this.type = getSuperclassTypeParameter(getClass());
              ...
            static Type getSuperclassTypeParameter(Class<?> subclass) {
              Type superclass = subclass.getGenericSuperclass();
              if (superclass instanceof Class) {
                throw new RuntimeException("Missing type parameter.");
              ParameterizedType parameterized = (ParameterizedType) superclass;
              return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
          
          7.3.2 导致泛型具有不变性

          因为类型擦除,导致泛型失去类型的协变性与逆变性。

          解决方法:通过使用上限通配符以及下限通配符实现。

          8 泛型面试问题整理(持续完善中)

          8.1 下列哪些是错误的

          //反正运行时泛型都会被擦除,String放哪里都没关系。在运行时,A、B、C都是一样的。
          //这个没问题,在实例化的时候给泛型指定类型。
          A、ArrayList list = new ArrayList<String>();
          //Java7的类型推断,可以省略右边的<String>
          B、ArrayList<String> list2 = new ArrayList();
          //add啥都不会报错,因为里边的泛型被实例化成了Object
          //list4.add("1"); 
          //list4.add(1); 
          C、ArrayList list3 = new ArrayList();
          //报错,原因是泛型具有不变性。
          //如果想要不报错,用上限通配符让泛型支持协变就ok了 -> ArrayList<? extends Object> list4 = ...
          D、ArrayList<Object> list4 = new ArrayList<String>();
          //报错,原因是泛型具有不变性。
          //如果想要不报错,用下限通配符让泛型支持逆变就ok了 -> ArrayList<? super String> list5 = ...
          E、ArrayList<String> list5 = new ArrayList<Object>();
          

          8.2 使用泛型实现lru缓存
          LinkedHashMap实现:
          使用泛型实现(LRU)缓存
          大佬实现:具有泛型和O(1)操作的Java中的LRU缓存

          8.+ 相关面试题的文章
          Java高级面试 —— Java的泛型实现机制是怎么样的?

          参考文章
          图解java泛型的协变和逆变
          Java泛型(一)类型擦除
          Java泛型(二) 协变与逆变

          1 什么是泛型(Generic Type)“参数化类型” 就是所操作的数据类型被指定为一个参数,然后在使用/调用时传入具体的类型。2 泛型怎么用2.1 泛型类class A&lt;T&gt;{ T t; public void set(T t){ this.t = t; } public T get(){ return t; }}//泛型类的使用A&lt;String&gt; a = new A&lt;&gt;();2.2 泛型接口in
          这里写自定义目录标题基本概念泛型的好处类型擦除泛型的使用定义泛型泛型类泛型接口泛型方法非静态方法静态方法泛型方法与可变参数通配符和上下限定无界通配符通配符的上限限定通配符的下限限定总结 泛型本质是**“数据类型的参数化”。我们可以把泛型理解为数据类型的一个占位符,即告诉编译器,在调用泛型时必须传入实际类型。这种参数类型可以用在类,接口和方法中。分别被称为泛型类,泛型接口,泛型方法**。 参数化类型:1、把类型当做是参数一样传递。2、数据类型只能是引用类型。 泛型的好处 代码可读性更好,不用强
          Java泛型为什么我们需要泛型?例子一例子二例子分析泛型的好处:泛型类、泛型接口和泛型方法什么是泛型泛型类泛型接口泛型方法如何限定类型变量?限定类型变量泛型使用中的约束和局限性不能用基本类型实例化类型参数运行时类型查询只适用于原始类型泛型类的静态上下文中类型变量失效不能创建参数化类型的数组不能实例化类型变量不能捕获泛型类的实例泛型类型能继承吗?泛型中的通配符类型? extends X? super X无限定的通配符 ?虚拟机是如何实现泛型的? 为什么我们需要泛型? 实际开发中,经常有数值类型求和的
          泛型是一种未知的数据类型,当我们不知道使用什么数据类型的时候,可以使用泛型 泛型也可以看成是一个变量,用来接收数据类型 public class ArrayList<E>{ …} ,<E>就是一个泛型 public class Demo01Generic { public static void main(String[] args) { show01(); show02(); short a = 10; short b = 2; // 报错:Inconvertible types; cannot cast 'int' to 'java.lang.String' System.out.println((String)(a + b));
          1.今天使用JdbcTemplate中的queryForObject(String sql, Class&lt;T&gt; requiredType, Object... args)方法时总是报以下错误 org.springframework.dao.EmptyResultDataAccessException: Incorrect result size: expected 1, actual...
 
推荐文章
性感的枇杷  ·  lDA困惑度是负的 lda 困惑度_mob6454cc61981e的技术博客_51CTO博客
6 月前
打盹的酸菜鱼  ·  scipy的中值滤波_scipy中值滤波-CSDN博客
9 月前
有胆有识的小狗  ·  什么是业务规则? | IBM
1 年前
俊逸的青蛙  ·  pexpect(4.6.0)文档_pexpect.readlines_zhangchenxiang_的博客-CSDN博客
1 年前
坏坏的骆驼  ·  通过主机名字获取与端口号获取对应的IP地址_boost获取ip_柳亓的博客-CSDN博客
1 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号