Java 基础(一)| 使用泛型的正确姿势

泛型

前言

为跳槽面试做准备,今天开始进入 Java 基础的复习。希望基础不好的同学看完这篇文章,能掌握泛型,而基础好的同学权当复习,希望看完这篇文章能够起一点你的青涩记忆。

一、什么是泛型

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?

顾名思义,就是将类型由原来的具体的类型参数化(动词),类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),

然后在使用/调用时传入具体的类型(类型实参)。

泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中。

操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

参考:https://www.cnblogs.com/coprince/p/8603492.html

1.1常见的泛型类型变量:

E:元素(Element),多用于 java 集合框架
K:关键字(Key)
N:数字(Number)
T:类型(Type)
V:值(Value)

二、为什么要使用泛型

回答这个问题前,首先举两个栗子,我想打印字符串到控制台,如下代码:

package com.nasus.generic;

import java.util.ArrayList;
import java.util.List;

/**
 * Project Name:review_java <br/>
 * Package Name:com.nasus.generic <br/>
 * Date:2019/12/28 20:58 <br/>
 *
 * @author <a href="turodog@foxmail.com">chenzy</a><br/>
 */
public class Show {

    public static void main(String[] args) {
        List list=new ArrayList();
        list.add("一个优秀的废人");
        list.add("java 工程师");
        list.add(666);
        for (int i = 0; i < list.size(); i++) {
            String value= (String) list.get(i);
            System.out.println(value);
        }
    }
}

本身我的 list 是打算装载 String 去打印的,但是大家发现没有?我传入 int 型时(编译期),Java 是没有任何提醒的(顶多是 IDEA 警告)。直到我循环调用(运行期)打印方法,打印 int 型时,Java 才报错:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
一个优秀的废人
    at com.nasus.generic.Show.main(Show.java:23)
java 工程师

第二栗子,我想实现一个可以操作各种类型的加法,如下代码:

package com.nasus.generic.why;

/**
 * Project Name:review_java <br/>
 * Package Name:com.nasus.generic <br/>
 * Date:2019/12/28 21:18 <br/>
 *
 * @author <a href="turodog@foxmail.com">chenzy</a><br/>
 */
public class Add {

    private static int add(int a, int b) {
        System.out.println(a + "+" + b + "=" + (a + b));
        return a + b;
    }

    private static float add(float a, float b) {
        System.out.println(a + "+" + b + "=" + (a + b));
        return a + b;
    }

    private static double add(double a, double b) {
        System.out.println(a + "+" + b + "=" + (a + b));
        return a + b;
    }

    // 一个泛型方法
    private static <T extends Number> double add(T a, T b) {
        System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
        return a.doubleValue() + b.doubleValue();
    }

    public static void main(String[] args) {
        Add.add(1, 2);
        Add.add(1f, 2f);
        Add.add(1d, 2d);
        System.out.println("--------------------------");
        // 以下三个都是调用泛型方法
        Add.add(Integer.valueOf(1), Integer.valueOf(2));
        Add.add(Float.valueOf(1), Float.valueOf(2));
        Add.add(Double.valueOf(1), Double.valueOf(2));
    }
}

这个加法可以操作 int、float、double 类型,但相应的也必须重写对应的加法,而此时我其实可以就用一个泛型方法就实现了上面三个重载方法的功能。

1+2=3
1.0+2.0=3.0
1.0+2.0=3.0
--------------------------
1+2=3.0
1.0+2.0=3.0
1.0+2.0=3.0

所以使用泛型原因有三个:

  • 提高可读性
  • 使 ClassCastException 这种错误在编译期就检测出来
  • 适用于多种数据类型执行相同的代码(代码复用)

参考:https://www.jianshu.com/p/986f732ed2f1

三、泛型详解

3.1泛型类

由我们指定想要传入泛型类中的类型,把泛型定义在类上,用户使用该类的时候,才把类型明确下来,比如:定义一个万能的实体数据暂存工具类。

注意:泛型类在初始化时就把类型确定了

package com.nasus.generic.how;

/**
 * Project Name:review_java <br/>
 * Package Name:com.nasus.generic.how <br/>
 * Date:2019/12/28 21:35 <br/>
 *
 * @author <a href="turodog@foxmail.com">chenzy</a><br/>
 */
public class EntityTool<T> {

    private T entity;

    public T getEntity() {
        return entity;
    }

    public void setEntity(T entity) {
        this.entity = entity;
    }

    public static void main(String[] args) {
        // 创建对象并指定元素类型
        EntityTool<String> stringTool = new EntityTool<>();
        stringTool.setEntity("一个优秀的废人");
        String s = stringTool.getEntity();
        System.out.println(s);


        // 创建对象并指定元素类型
        EntityTool<Integer> integerTool = new EntityTool<>();
        // 此时,如果这里传入的还是 String 类型,那就会在编译期报错
        integerTool.setEntity(10);
        int i = integerTool.getEntity();
        System.out.println(i);
    }
}

3.2泛型方法

有时候我们只想在方法中使用泛型,可以这么定义:

值得注意的是:

  • 与泛型类不同,泛型方法在调用时才确定最终类型
  • 若有返回值,返回值不需要强转
package com.nasus.generic.how;

/**
 * Project Name:review_java <br/>
 * Package Name:com.nasus.generic.how <br/>
 * Date:2019/12/28 21:46 <br/>
 *
 * @author <a href="turodog@foxmail.com">chenzy</a><br/>
 */
public class Show {

    public static  <T> T show(T t) {
        System.out.println(t);
        return t;
    }

    public static void main(String[] args) {
        // 返回值不用强转,传进去是什么,返回就是什么
        String s = show("一个优秀的废人");
        int num1 = show(666);
        double num2 = show(666.666);
        System.out.println("------------------------");
        System.out.println(s);
        System.out.println(num1);
        System.out.println(num2);
    }
}

3.3泛型接口

泛型接口分两种实现方法:

一是实现类不明确泛型接口的类型参数变量,这时实现类也必须定义类型参数变量(比如下面 Showimpl<T>)

接口:

public interface Show<T> {
    void show(T t);
}
public class ShowImpl<T> implements Show<T>{

    @Override
    public void show(T t) {
        System.out.println(t);
    }

    public static void main(String[] args) {
        ShowImpl<String> stringShow = new ShowImpl<>();
        stringShow.show("一个优秀的废人");
    }
}

二是明确泛型接口的类型参数变量

public class ShowImpl2 implements Show<String>{

    @Override
    public void show(String s) {
        System.out.println("一个优秀的废人");
    }
}

3.5 限定泛型类型变量

限定泛型类型上限

其实就是相当于指定了泛型类的父类
声明类:类名<泛型标识 extends 类>{}

在类中使用:

// 用在类上
public class Show<T extends Number> {

    private T show(T t){
        System.out.println(t);
        return t;
    }

    public static void main(String[] args) {
        // 初始化时指定类型
        Show<Integer> show = new Show<>();
        show.show(6666666);

        // 报错,该类只接受继承于 Number 的泛型参数
        // Show<String> stringShow = new Show<>();
    }
}

方法中使用:

定义对象:类名<泛型标识 extends 类> 对象名称

public class Info<T> {

    // 定义泛型变量
    private T var;

    public void setVar(T var) {
        this.var = var;
    }

    public T getVar() {
        return this.var;
    }

    public String toString() {
        return this.var.toString();
    }
}
public class ShowInfo {

    // 用在方法上,只能接收 Number 及其子类
    public static void showInfo(Info<? extends Number> t) {
        System.out.print(t);
    }

    public static void main(String args[]) {
        Info<Integer> i1 = new Info<>();
        Info<Float> i2 = new Info<>();
        i1.setVar(666666666);
        i2.setVar(666666.66f);
        showInfo(i1);
        showInfo(i2);
    }

}

限定泛型类型下限

定义对象:类名<泛型标识 extends 类> 对象名称

与指定上限相反,指定下限定很简单,就是相当于指定了泛型类的子类,不再赘述。

public class ShowInfo {

    // 只接受 String 的父类
    public static void showInfo(Info<? super String> t) {
        System.out.println(t);
    }

    public static void main(String args[]) {
        Info<String> stringInfo = new Info<>();
        Info<Object> objectInfo = new Info<>();
        stringInfo.setVar("一个优秀的废人");
        objectInfo.setVar(new Object());
        showInfo(stringInfo);
        showInfo(objectInfo);
    }

}

3.6 通配符类型

  • <? extends Parent> 指定了泛型类型的上限
  • <? super Child> 指定了泛型类型的下届
  • <?> 指定了没有限制的泛型类型

3.7 泛型擦除

泛型是提供给 javac 编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的 java 程序后,生成的 class 文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为 “擦除”。

3.8 泛型的使用规范

1、不能实例化泛型类
2、静态变量或方法不能引用泛型类型变量,但是静态泛型方法是可以的
3、基本类型无法作为泛型类型
4、无法使用 instanceof 关键字或 == 判断泛型类的类型
5、泛型类的原生类型与所传递的泛型无关,无论传递什么类型,原生类是一样的
6、泛型数组可以声明但无法实例化
7、泛型类不能继承 Exception 或者 Throwable
8、不能捕获泛型类型限定的异常但可以将泛型限定的异常抛出

最后

推荐下阿里云的服务器,新用户购买服务器 89 元 / 年、229 元 / 3 年。买个用来搭建项目(比如个人博客)准备面试、熟悉技术栈、学习 Linux 都可以。不是新用户也没关系,借用家人朋友身份证重新注册新用户(我用了我妹妹的😂)有需要的复制下面的链接注册购买就是最低价。

https://www.aliyun.com/minisite/goods?userCode=u1o37uph&share_source=aliyun_app

我这还有使用教程「Linux 系列」阿里云服务器的使用及安装 mysql、tomcat、jdk 三件套

如果看到这里,说明你喜欢这篇文章,请转发、点赞。微信搜索「一个优秀的废人」,关注后回复「1024」送你一套完整的 java 教程。

教程节选
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容