泛型

public interface Foo<E> {}

public interface Bar<T> {}

public interface Zar<?> {}

上面的代码有什么区别?

泛型初探

1、为何引入泛型?

Java 泛型也是一种语法糖,使用泛型可以在代码编译阶段完成类型的转换,避免代码在运行时强制转换而出现ClassCastException的异常。

网络搜索出来一大堆的名称解释,我们先看英文Generic type,从英文大概也能明白,Generic 这里可以理解为普通的,一般的,或者我们可以说通用的。

其实可以理解为Java中的一种类型,通用类型。

Java从1.5的版本就开始支持泛型,不过很多小伙伴对泛型还是模凌两可,今天大概讲讲泛型,基础好的小伙伴,就当复习复习。

在1.5版本以前

public static void main(String[] args){
    List list = new ArrayList();
    list.add("兔子托尼啊");
    list.add(1234);
    //正常运行
    System.out.println((String)list.get(0));
    //❌运行时报错
    System.out.println((String)list.get(1));
}

从上面的代码可以看出了,第一句打印不报错,第二句打印会报错的。

List默认是Object的类型的,向List里面存数据都是没有问题的,但是取数据的时候,必须要要进行类型的转换。

List集合get数据的时候并不清楚里面存放的什么数据类型,默认取出来的都是Object的类型,如果取数据的时候转换的类型和原始存放存的类型不一样,会报ClassCastException的异常。

2、引入了泛型

看代码

List<String> list = new ArrayList<String>();
list.add("兔子托尼啊");
//❌编译时错误
list.add(1234);
//不需要再进行转换了
String str = list.get(0);

3、泛型带来好处

  • 这在编码的时候就给我们解决了,类型转换的问题,可以放心写代码。

  • 取数据的时候再也不要考虑我前面存的什么类型,我应该转换为什么类型,不怕类型转换报错。

类型擦除

上面讲了泛型,泛型虽然带来了好处,但是泛型也带了一个问题叫做类型擦除。

什么是类型擦除?

Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。

Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。

class GenericU {
    public void foo() {
        System.out.println("GenericU.foo()");
    }
}
public class Operater<T> {
    private T obj;
    public Operater(T obj) {
        this.obj = obj;
    }
    public void doIt() {
        //❌报错,提示找不到foo方法
        obj.foo(); 
    }
   public static void main(String[] args) {
        GenericU genericU  = new GenericU();
        Operater<GenericU> operater = new Operater<>(genericU);
        operater.doIt();
    }
}

上面的代码就是因为泛型擦除,带来编译就报错了,代码中的obj不知道是什么类型?

正确的代码应该是什么,只要指定T的类型就好

 class Operater2<T extends GenericU> {
    private T obj;
    public Operater2(T obj) { 
      this.obj = obj; 
      }
    public void doIt() {
      //正确☑️
      obj.foo(); 
    }
}

区分在Operater2<T extends GenericU>Operater<T>
必须指定泛型的类型。

上面的例子是运用在类上面的,方法中是什么效果呢?

class Foo{
  //定义泛型方法..
  public <T> void show(T t) {
      System.out.println(t);
  }
}

调用方法

public static void main(String[] args) {
    //创建Foo对象
    Foo foo = new Foo();
    //不同的类型参数
    foo.show("兔子托尼啊");
    foo.show(1234);
    foo.show(12.34);
}

通配符与上下界

我们大家在java的源码中肯定看到这样的例子。一个下限,一个上限

? extends T VS ? super T

  • ? extends T - 这里的?表示类型T的任意子类型,包含类型T本身。
  • ? super T - 这里的?表示类型T的任意父类型,包含类型T本身。

上限通配符 可以代表未知的T类型,或者通过关键字 extends 所继承的T类的任何一个子类。

同样,下限通配符 可以代表未知的T类型,或者通过关键字super出来的的T类的任何一个父类。

通配符和泛型方法

//通配符
public  void foo1(List<?> list) {
}

//使用泛型方法
public <T> void  foo2(List<T> t) {
}

问: 上面两种代码都是可以的,但是什么场合用那种呢?

  • 如果当参数之间有依赖关系,或者返回的参数有依赖关系则用泛型,反之则用通配符。

问:关于 ? extends T? super T 什么场景下用呢?

我从网上搜索了下

当你需要从一个数据结构中获取数据时(get),那么就使用 ? extends T;如果你需要存储数据(put)到一个数据结构时,那么就使用 ? super T; 如果你又想存储数据,又想获取数据,那么就不要使用通配符 ? ,即直接使用具体泛型T。

最后

泛型大概就讲了上面的内容,你看明白了吗?希望你又学到了,每天学一点,进步一点。升职加薪就是你了。

码字不易,关注后送福利,求关注。

唬人的Java泛型并不难的更多相关文章

  1. Java泛型的历史

    为什么Java泛型会有当前的缺陷? 之前的章节里已经说明了Java泛型擦除会导致的问题,C++和C#的泛型都是在运行时存在的,难道Java天然不支持“真正的泛型”吗? 事实上,在Java1.5在200 ...

  2. 浅析Java 泛型

    泛型是JavaSE5引入的一个新概念,但是这个概念在编程语言中却是很普遍的一个概念.下面,根据以下内容,我们总结下在Java中使用泛型. 泛型使用的意义 什么是泛型 泛型类 泛型方法 泛型接口 泛型擦 ...

  3. Java:泛型基础

    泛型 引入泛型 传统编写的限制: 在Java中一般的类和方法,只能使用具体的类型,要么是基本数据类型,要么是自定义类型.如果要编写可以应用于多种类型的代码,这种刻板的限制就会束缚很多! 解决这种限制的 ...

  4. java泛型基础

    泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 这种参数类型可以用在类.接口和方法的创建中, 分别称为泛型类.泛型接口.泛型方法.  Ja ...

  5. 使用java泛型设计通用方法

    泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 因此我们可以利用泛型和反射来设计一些通用方法. 现在有2张表, 一张user表和一张stu ...

  6. 关于Java泛型的使用

    在目前我遇到的java项目中,泛型应用的最多的就属集合了.当要从数据库取出多个对象或者说是多条记录时,往往都要使用集合,那么为什么这么使用,或者使用时有什么要注意的地方,请关注以下内容. 感谢Wind ...

  7. 初识java泛型

    1 协变数组类型(covariant array type) 数组的协变性: if A IS-A B then A[] IS-A B[] 也就是说,java中的数组兼容,一个类型的数组兼容他的子类类型 ...

  8. 【Java心得总结四】Java泛型下——万恶的擦除

    一.万恶的擦除 我在自己总结的[Java心得总结三]Java泛型上——初识泛型这篇博文中提到了Java中对泛型擦除的问题,考虑下面代码: import java.util.*; public clas ...

  9. 【Java心得总结三】Java泛型上——初识泛型

    一.函数参数与泛型比较 泛型(generics),从字面的意思理解就是泛化的类型,即参数化类型.泛型的作用是什么,这里与函数参数做一个比较: 无参数的函数: public int[] newIntAr ...

随机推荐

  1. Mule自带例子之stockquote

    1 配置效果图 2 配置文件 <?xml version="1.0" encoding="UTF-8"?> <mule version=&qu ...

  2. 查看laravel版本

    方法1: 使用php artisan --version ,只要能看懂这个命令的人一定已经具有初步的Laravel知识.再介绍一种不需要命令,直接去文件中去查看的方法. 方法2: 在项目文件中找ven ...

  3. 最短路算法(floyed+Dijkstra+bellman-ford+SPFA)

    最短路算法简单模板 一.floyed算法 首先对于floyed算法来说就是最短路径的动态规划解法,时间复杂度为O(n^3) 适用于图中所有点与点之间的最短路径的算法,一般适用于点n较小的情况. Flo ...

  4. 2018.11.16 浪在ACM 集训队第五次测试赛

    2018.11.16 浪在ACM 集训队第五次测试赛 整理人:李继朋 Problem A : 参考博客:[1]朱远迪 Problem B : 参考博客: Problem C : 参考博客:[1]马鸿儒 ...

  5. Linux 内核引用计数的操作

    一个 kobject 的其中一个关键函数是作为一个引用计数器, 给一个它被嵌入的对象. 只 要对这个对象的引用存在, 这个对象( 和支持它的代码) 必须继续存在. 来操作一个 kobject 的引用计 ...

  6. HBuilder如何与真机连接

    之前因为前端这边要做测试, 同时兼容ios和安卓方面. 但是因为一直苦恼无法仿真机连接测试,从而每次测试提出来一次,修改一次. 为了解决这个弊端,所以自己在这里分享一下连接的方法: 一:Android ...

  7. 关于react打包之后静态资源加载错误的问题

    之前在打包react项目时发现一些问题,打包出来后我的一部分png图标加载不出来,开发者模式发现他们的路径中莫名其妙混入了我在react-router路由中使用<Browserrouter> ...

  8. git 上传当前分支

    因为我现在的分支是的名很长,每次需要上次当前分支需要写很多代码,是不是有很简单方法上传当前分支. 如果要上传一个分支到仓库 origin 那么就需要使用下面的命令 git push origin 分支 ...

  9. jenkins+Git+Gitlab+Ansible实现持续集成自动化部署静态网站(二)

    引言:首先我们可以实现一键部署网站,但在实际生产环境网站部署完成之后,我们的开发隔三差五要修改下网站的内容,难道都要我们运维手动执行命令吗?没有一种方法使得开发人员修改完代码自己测试,部署上线呢,那这 ...

  10. UE4 中的 C++ 与 蓝图交互

    1.Unreal 引擎提供了两种创建新 Gameplay 元素的方法:C++ 和 蓝图视觉脚本. 通过 C++,程序员构建基础游戏系统:设计师可以基于此系统为场景 / 游戏创建自定义的游戏玩法. 这种 ...