强哥说Java--Java的泛型

2023-10-11 21:50
文章标签 java 泛型 强哥

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

Java 泛型

  • Java 泛型
    • 前言
    • 学习目标
    • 1. 什么是泛型
    • 2. 为什么需要泛型
    • 3. 如何使用泛型
      • 3.1 泛型使用
      • 3.2 自定义泛型类
        • 3.2.1 Java 源码中泛型的定义
        • 3.2.2 自定义泛型类实例1
        • 3.2.3 自定义泛型类实例2
      • 3.3 自定义泛型方法
    • 4. 泛型类的子类
      • 4.1 明确类型参数变量
      • 4.2 不明确类型参数变量
    • 5. 类型通配符
      • 5.1 无限定通配符
      • 5.2 extends 通配符
      • 5.3 super 通配符
    • 6. 小结

Java 泛型

前言

8/28

编程不能停止,每天发一篇~

不要捉急往后学,前面基础打的越牢固后面进阶越好进阶

基础不牢,地动山摇

学习目标

什么是泛型

为什么需要泛型

如何使用泛型

如何自定义泛型

类型通配符等知识

1. 什么是泛型

泛型不只是 Java 语言所特有的特性,泛型是程序设计语言的一种特性。

允许程序员在强类型的程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须做出声明。

Java 中的集合类是支持泛型的,它在代码中是这个样子的

请添加图片描述

代码中的<Integer>就是泛型,我们把类型像参数一样传递,尖括号中间就是数据类型,我们可以称之为实际类型参数,这里实际类型参数的数据类型只能为引用数据类型。

那么为什么需要泛型呢?

2. 为什么需要泛型

我们在使用ArrayList实现类的时候,如果没有指定泛型,IDEA会给出警告,代码似乎也是可以顺利运行的。请看如下实例:

import java.util.ArrayList;public class testDemo1 {public static void main(String[] args) {ArrayList arrayList = new ArrayList();arrayList.add("Hello");String str1 = (String) arrayList.get(0);System.out.println("str=" + str1);}}

运行结果:

str1=Hello

虽然运行时没有发生任何异常,但这样做有两个缺点:

  1. 需要强制类型转换: 由于ArrayList内部就是一个Object[]数组,在get()元素的时候,返回的是Object类型,所以在ArrayList外获取该对象,需要强制类型转换。其它的CollectionMap如果不使用泛型,也存在这个问题;
  2. 可向集合中添加任意类型的对象,存在类型不安全风险。例如如下代码中,我们向列表中既添加了Integer类型,又添加了String类型:
package com.caq.oop.demo08;import java.util.ArrayList;
import java.util.List;public class Test {public static void main(String[] args) {//实例化一个空列表List arrayList = new ArrayList<>();arrayList.add(123);arrayList.add("sad");String str = (String) arrayList.get(0);}
}

Exception in thread “main” java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at com.caq.oop.demo08.Test.main(Test.java:12)

由于我们的“疏忽”,列表第 1 个元素实际上是整型,但被我们强制转换为字符串类型,这是行不通的,因此会抛出ClassCastException异常。

使用泛型可以解决这些问题。泛型有如下优点:

  1. 可以减少类型转换的次数,代码更加简洁;
  2. 程序更加健壮:只要编译期没有警告,运行期就不会抛出ClassCastException异常;
  3. 提高了代码的可读性:编写集合的时候,就限定了集合中能存放的类型。

3. 如何使用泛型

3.1 泛型使用

在代码中,这样使用泛型:

List<String> list = new ArrayList<String>();
// Java 7 及以后的版本中,构造方法中可以省略泛型类型:
List<String> list = new ArrayList<>();
外币巴伯

要注意的是,变量声明的类型必须与传递给实际对象的类型保持一致,下面是错误的例子:

List<Object> list = new ArrayList<String>();
List<Number> numbers = new ArrayList(Integer);

3.2 自定义泛型类

3.2.1 Java 源码中泛型的定义

在自定义泛型类之前,我们来看下java.util.ArrayList是如何定义的:

img

类名后面的<E>就是泛型的定义,E不是 Java 中的一个具体的类型,它是 Java 泛型的通配符(注意是大写的,实际上就是Element的含义),可将其理解为一个占位符,将其定义在类上,使用时才确定类型

此处的命名不受限制,但最好有一定含义,例如java.lang.HashMap的泛型定义为HashMap<K,V>K表示KeyV表示Value

3.2.2 自定义泛型类实例1

下面我们来自定义一个泛型类,自定义泛型按照约定俗成可以叫<T>,具有Type的含义,实例如下:

实例演示

package com.caq.List;public class Generic01<T> {private T abc;//定义在类上的泛型,在类内部可以使用public T getAbc() {return abc;}public void setAbc(T abc) {this.abc = abc;}public static void main(String[] args) {//实例化对象,指定元素类型为整型Generic01<Integer> integerGeneric01 = new Generic01<>();//调用方法integerGeneric01.setAbc(100);System.out.println("integerGeneric01="+ integerGeneric01.getAbc());//实例化对象,指定元素类型为长类型Generic01<Long> longGeneric01 = new Generic01<>();longGeneric01.setAbc(200L);System.out.println("longGeneric01="+ longGeneric01.getAbc());// 实例化对象,指定元素类型为双精度浮点型Generic01<Double> doubleGeneric01 = new Generic01<>();doubleGeneric01.setAbc(300.0);System.out.println("doubleGeneric01="+ doubleGeneric01.getAbc());}
}

运行结果:

integerGeneric01=100
longGeneric01=200
doubleGeneric01=300.0

我们在类的定义处也定义了泛型:Generic01<T>;在类内部定义了一个T类型的abc变量,并且为其添加了settergetter方法。

解释:对于泛型类的使用也很简单,在主方法中,创建对象的时候指定T的类型分别为IntegerLongDouble,类就可以自动转换成对应的类型了。

3.2.3 自定义泛型类实例2

上面我们知道了如何定义含有单个泛型的类,那么对于含有多个泛型的类,如何定义呢?

我们可以看一下HashMap类是如何定义的。如下是 Java 源码的截图:

img

参照HashMap<K,V>类的定义,下面我们来看看如何定义含有两个泛型的类

package com.caq.List;public class Generic02<K, V> {//这次是定义两个泛型在类上//定义类型为K的key属型private K key;//定义类型为V的value属型private V value;//封装里的知识,通过Getter和Setter方法来设置和获取私有属型的值public K getKey() {return key;}public void setKey(K key) {this.key = key;}public V getValue() {return value;}public void setValue(V value) {this.value = value;}public static void main(String[] args) {//实例化对象,分别指定类型为整型,长整型Generic02<Integer, Long> integerLongGeneric02 = new Generic02<>();//实例化对象,分别指定类型为浮点型、字符串类型Generic02<Float, String> floatStringGeneric02 = new Generic02<>();integerLongGeneric02.setKey(100);integerLongGeneric02.setValue(200L);System.out.println("key=" + integerLongGeneric02.getKey());System.out.println("value=" + integerLongGeneric02.getValue());floatStringGeneric02.setKey(0.9f);floatStringGeneric02.setValue("巴啦啦能量");System.out.println("key=" + floatStringGeneric02.getKey());System.out.println("value=" + floatStringGeneric02.getValue());}
}

运行结果:

key=100value=200key=0.9value=巴啦啦能量

3.3 自定义泛型方法

前面我们知道了如何定义泛型类,在类上定义的泛型,在方法中也可以使用。下面我们来看一下如何自定义泛型方法。

泛型方法不一定写在泛型类当中。当类的调用者总是关心类中的某个泛型方法,不关心其他属性,这个时候就没必要再整个类上定义泛型了。

直接在方法上设置泛型(generic)

package com.caq.List;
public class Generic03 {public <T> void test(T t){System.out.println(t);}    public static void main(String[] args) {Generic03 generic03 = new Generic03();generic03.test("Monkey");generic03.test(1);generic03.test(1.00000);generic03.test(1L);    }
}

运行结果:

Monkey11.01

实例中,使用<T>来定义test方法的泛型,它接收一个泛型的参数变量并在方法体打印;调用泛型方法也很简单,在主方法中实例化对象,调用对象下的泛型方法,可传入不同类型的参数。

4. 泛型类的子类

泛型类也是一个 Java 类,它也具有继承的特性。

泛型类的继承可分为两种情况:

  1. 子类明确泛型类的类型参数变量;
  2. 子类不明确泛型类的类型参数变量。

4.1 明确类型参数变量

例如,有一个泛型接口:

package com.caq.List;public interface GenericInterface01<T> {    default void show(T t) {            }}

泛型接口的实现类如下:

package com.caq.List;public class GenericInterfaceImple implements GenericInterface01<String> {    @Override    public void show(String s) {        System.out.println(s);    }}

子类实现明确了泛型的参数变量为String类型。因此方法show()的重写也将T替换为了String类型。

4.2 不明确类型参数变量

当实现类不确定泛型类的参数变量时,实现类需要定义类型参数变量,调用者使用子类时,也需要传递类型参数变量。

如下是GenericInterface接口的另一个实现类:

package com.caq.List;public class GenericInterfaceImple<T> implements GenericInterface01<T> {@Overridepublic void show(T t) {System.out.println(t);}
}

在主方法中调用实现类的show()方法:

package com.caq.List;public class GenericInterfaceImple<T> implements GenericInterface01<T> {@Overridepublic void show(T t) {System.out.println(t);}public static void main(String[] args) {GenericInterfaceImple<Integer> integerGenericInterfaceImple = new GenericInterfaceImple<>();integerGenericInterfaceImple.show(100);}
}100

5. 类型通配符

我们先来看一个泛型作为方法参数的实例:

package com.caq.List;
/*** 遍历并打印集合中的每一个元素* 遍历是二叉树上最重要的运算之一,是二叉树上进行其它运算之基础。 树的遍历是树的一种重要的运算。 * 所谓遍历是指对树中所有结点的信息的访问,即依次对树中每个结点访问一次且仅访问一次。* @param list 要接收的集合*/
public class Generic04 {public void printListElement(List<object> list){for (Object o :list) {System.out.println(o);}}
}

观察上面的代码,参数list的限定的泛型类型为Object, 也就是说,这个方法只能接收元素为Object类型的集合,如果我们想传递其他元素类型的集合,是行不通的。例如,如果传递装载Integer元素的集合,程序在编译阶段就会报错:

请添加图片描述

Tips: 泛型中的List<Object>并不是List<Integer>的父类,它们不满足继承关系。

5.1 无限定通配符

想要解决这个问题,使用类型通配符即可,修改方法参数处的代码,将<>中间的Object改为?即可:

    public void printListElement(List<?> list){

此处的?就是类型通配符,表示可以匹配任意类型,因此调用方可以传递任意泛型类型的列表

实例演示

package com.caq.List;import java.util.ArrayList;
import java.util.List;public class Generic04 {
//List<?>可以理解为列表的类型,可以是整数型列表,也可以是字符串类型列表,list代表的是遍历的元素代表public void printListElement(List<?> list){for (Object o : list) {System.out.println(o);}}public static void main(String[] args) {//实例化一个整型列表List<Integer> interger = new ArrayList<>();//加元素interger.add(1);interger.add(2);interger.add(2222);//实例化对象Generic04 generic04 = new Generic04();generic04.printListElement(interger);//实例化一个字符串类型列表ArrayList<String> strings = new ArrayList<>();strings.add("element1");strings.add("element2");strings.add("element3");generic04.printListElement(strings);}
}

运行结果:

1
2
2222
element1
element2
element3

5.2 extends 通配符

extends通配符用来限定泛型的上限。什么意思呢?依旧以上面的实例为例,我们来看一个新的需求,我们希望方法接收的List 集合限定在数值类型内(float、integer、double、byte 等),不希望其他类型可以传入(比如字符串)。此时,可以改写上面的方法定义,设定上界通配符:

public void printListElement(List<? extends Number> list) {

这样的写法的含义为:List集合装载的元素只能是Number自身或其子类(Number类型是所有数值类型的父类),完整实例如下:

import java.util.ArrayList;
import java.util.List;public class Generic04 {public void printListElement(List<? extends Number> list) {for (Object o : list) {System.out.println(o);}}public static void main(String[] args) {// 实例化一个整型的列表List<Integer> integers = new ArrayList<>();// 添加元素integers.add(1);integers.add(2);integers.add(3);GenericDemo4 generic04 = new Generic04();// 调用printListElement()方法generic04.printListElement(integers);}
}

运行结果:

1
2
3

5.3 super 通配符

既然已经了解了如何设定通配符上界,也就不难理解通配符的下界了,可以限定传递的参数只能是某个类型的父类。

语法如下:

<? super Type>

6. 小结

  • 使用泛型可以避免强制类型转换,也可以避免运行期就抛出的ClassCastException异常
  • 在使用泛型时,要注意变量声明的泛型类型要匹配传递给实际对象的类型, Java 7 及以后的版本中,构造方法中可以省略泛型类型,推荐直接省略
  • 如何自定义泛型类和泛型方法,在实际的开发中,我们想要编写比较通用的代码就避免不了使用泛型,慢慢体会星弟们~~~
  • 另外,泛型也是可以继承的
  • 类型通配符的概念和使用场景

这篇关于强哥说Java--Java的泛型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定