前言

为跳槽面试做准备,今天开始进入 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)

接口:

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、不能捕获泛型类型限定的异常但可以将泛型限定的异常抛出

四、源码地址

如果看到这里,喜欢这篇文章的话,帮忙 " 转发 "或者点个" 在看 ",行吗?祝你们 2020 暴富。微信搜索「一个优秀的废人」,欢迎关注。

回复「1024」送你一套完整的 java、python、c++、go、前端、linux、算法、大数据、人工智能、小程序以及英语教程。

回复「电子书」送你 50+ 本 java 电子书。

Java 基础(一)| 使用泛型的正确姿势的更多相关文章

  1. Java基础教程:泛型基础

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

  2. Java基础 -- 深入理解泛型

    一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类.如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大. 而泛型很好的解决了这个问题,这也是Java SE5的重大 ...

  3. Java 基础篇之泛型

    背景 在没有泛型前,一旦把一个对象丢进集合中,集合就会忘记对象的类型,把所有的对象都当成 Object 类型处理.当程序从集合中取出对象后,就需要进行强制类型转换,这种转换很容易引起 ClassCas ...

  4. Java基础教程(21)--泛型

    一.为什么使用泛型   泛型意味着编写的代码可以被很多不同类型的对象所重用.例如,我们不希望为存放String和Integer对象的集合设计不同的类.现在的ArrayList类可以存放任何类型的对象, ...

  5. JAVA基础_自定义泛型

    泛型的来源 在Java中,泛型借鉴了C++的模版函数,从而引入了泛型. C++泛型 int add(int x,int y){ return x + y; } float add(float x.fl ...

  6. 【java基础学习】泛型

    泛型 1. 泛型类(声明的泛型类型静态方法不能使用) class Tools<T>{ private T t; public void set(T t){ this.t = t; } pu ...

  7. 【Java基础总结】泛型

    泛型实现了参数化类型的概念,使代码可以应用于多种类型. 1. 泛型类 声明的泛型类型静态方法不能使用 class Tools<T>{ private T t; public void se ...

  8. Java 基础(四)| IO 流之使用文件流的正确姿势

    为跳槽面试做准备,今天开始进入 Java 基础的复习.希望基础不好的同学看完这篇文章,能掌握泛型,而基础好的同学权当复习,希望看完这篇文章能够起一点你的青涩记忆. 一.什么是 IO 流? 想象一个场景 ...

  9. Java 基础(三)| IO流之使用 File 类的正确姿势

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

随机推荐

  1. HTML5中Js多线程编程

    Web Worker Web Worker是HTML5提出的新标准,为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行.在主线程运行的同时,Work ...

  2. linux Completions 机制

    内核编程的一个普通模式包括在当前线程之外初始化某个动作, 接着等待这个动作结束. 这个动作可能是创建一个新内核线程或者用户空间进程, 对一个存在着的进程的请求, 或 者一些基于硬件的动作. 在这些情况 ...

  3. Linux数据对齐

    编写可移植代码而值得考虑的最后一个问题是如何存取不对齐的数据 -- 例如, 如何读取 一个存储于一个不是 4 字节倍数的地址的 4 字节值. i386 用户常常存取不对齐数据项, 但是不是所有的体系允 ...

  4. maven仓库总结,maven私服搭建,批量mvn eclipse:eclipse

    配置pom.xml依赖包时在这里找包的描述: http://search.maven.org/#browse 以java为根目录. mvn archtype:generate -DgroupId=zt ...

  5. dotnet Framework 源代码 类库的意思

    本文告诉大家 dotnet framework 的源代码类库的意思 下面列出来 dotnet framework 源代码的各个类库的作用. System System 命名空间包含基本类和基类,这些类 ...

  6. koa2+koa-art-template利用日期管道实现在jat模板中将时间戳转为日期时间

    var sp = require("silly-datetime"); var render = require("koa-art-template"); va ...

  7. linux查看文件内容跳到文件底部和回到文件顶部的快捷键

    有时候需要查看一些日志文件,然后要从底部开始查看的话 可以按 shift+g  即可跳到文件底部 要返回文件顶部的时候 按 gg即可

  8. 使用Sklearn-train_test_split 划分数据集

    使用sklearn.model_selection.train_test_split可以在数据集上随机划分出一定比例的训练集和测试集 1.使用形式为: from sklearn.model_selec ...

  9. 支撑百万级并发,Netty如何实现高性能内存管理

    Netty作为一款高性能网络应用程序框架,实现了一套高性能内存管理机制 通过学习其中的实现原理.算法.并发设计,有利于我们写出更优雅.更高性能的代码:当使用Netty时碰到内存方面的问题时,也可以更高 ...

  10. 【阿里云IoT+YF3300】10.快速开发188协议设备驱动

    188协议的全称为CJ-T188-2004 <户用计量仪表数据传输技术条件>,是针对水表.燃气表.热量表和其他集中采集的一个国家行业标准协议. YFIOs就是YFSoft I/O Serv ...