泛型的初步认识(1)

前言~🥳🎉🎉🎉

hellohello~,大家好💕💕,这里是E绵绵呀✋✋ ,如果觉得这篇文章还不错的话还请点赞❤️❤️收藏💞 💞 关注💥💥,如果发现这篇文章有问题的话,欢迎各位评论留言指正,大家一起加油!一起chin up!👍👍

💥个人主页:E绵绵的博客
💥所属专栏:JAVA知识点专栏 JAVA题目练习 c语言知识点专栏c语言题目练习

❤️❤️ **这篇文章我们将初步介绍下泛型,其知识点可能有点复杂,还请各位认真看完整篇文章。**那么我们开始出发吧 !


参考文章:Java 中的泛型(两万字超全详解)_java 泛型-CSDN博客

什么是泛型

泛型的标志通常使用尖括号 "<>" 来表示,尖括号中可以包含一个或多个 类型参数或类型形参。

泛型类型形参一般使用一个大写字母表示,常用的名称有:T E K V .

泛型类型参数只能是引用类型,不能是基本类型。如果需要使用基本类型,可以使用对应的包装类如Integer,Double。通俗讲就是<>内部只能时引用类型。

通过泛型指定的类型参数来控制泛型形参的具体类型,一旦传入了具体的类型参数后,泛型形参的类型将被限制为这个具体的类型参数,而之后如果出现将不匹配的数据类型配给限制后的类型参数,编译器就会直接报错。这个被称之为类型检查:泛型提供了编译时类型检查,可以在编译时捕获类型错误,避免在运行时出现类型不匹配的错误。

泛型可以应用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法,之后将一一介绍。

我们使用泛型的好处在于可以提高代码的重用性和安全性,避免了类型转换的错误和运行时异常。泛型可以让代码更加简洁、清晰,同时也可以提高代码的可读性和可维护性。

泛型类

泛型类的定义

❤️❤️ <>用于类的定义中,则该类被称为泛型类.

复制代码
class 类名<类型参数1, 类型参数2, ...> {
    // 类的成员和方法
}

在类的成员和方法中,可以使用这些类型参数来定义变量、方法参数、返回值等。

泛型类的使用

复制代码
public class Main<T> { 
 
    private T key;

    public Generic(T key) { 
        this.key = key;
    }

    public T getKey(){ 
        return key;
    }
}

假设有个泛型类如上,在使用该泛型类时,我们通过实例化该泛型类对象去指定具体的类型来替换泛型参数。

例如,Main<Integer> myObj = new Main<Integer>();表示创建了一个Main泛型类的对象myObj,并指定该对象的泛型参数为Integer。当指定该对象为 Integer 类型时,原泛型类可以想象它会自动扩展,用它创建出的对象的类型参数会被替换为Integer。

🎯🎯扩展:

1. 我们还可以用以下方式创建泛型类对象:MyArray<Integer> list = new MyArray<>(); 此时编译器可以根据上下文推导出类型实参,所以可以省略类型实参的填写 ,在这例子中可以推导出实例化需要的类型实参为 Integer。

Java的类型推导是指在编程过程中,编译器能够根据上下文自动推断出变量的类型,而无需显式地指定类型。类型推导的优点是可以减少冗余的代码,提高代码的可读性。但也需要注意不要滥用类型推导,应该在保证代码可读性的前提下使用。
**2.在泛型类的创建中我们还可以在<>中什么都不传入,此时则默认是 < Object >**如 MyArray list = new MyArray()相当于MyArray<Object> list = new MyArray<Object>()。

泛型类的注意事项

🎯🎯泛型类中的静态方法和静态变量不可以使用泛型类所声明的类型参数,否则会报错。

这是因为泛型类中的类型参数的确定是在创建泛型类对象的时候,而静态变量和静态方法在类加载时已经初始化,直接使用类名调用;在泛型类的类型参数未确定时,静态成员有可能被调用,因此泛型类的类型参数是不能在静态成员中使用的。

泛型接口

泛型接口的定义

泛型接口和泛型类的定义差不多,基本语法如下:

复制代码
public interface 接口名<类型参数> {
    ...
}

举例如下:

复制代码
public interface Inter<T> {
    public abstract void show(T t) ;
}

泛型接口的注意事项

🎯🎯在泛型接口中,静态成员也不能使用泛型接口定义的类型参数。

接口的成员变量默认都是static类型,所以不能使用类型参数,而在接口的三大方法中, default方法和抽象方法都能使用类型参数,静态方法自然不能使用类型参数。

复制代码
interface IUsb<U, R> {

    int n = 10;
    U name;// 报错! 接口中的属性默认是静态的,因此不能使用类型参数声明

    R get(U u);// 抽象方法中,可以使用类型参数
   R static put(U u){
         ; 
}//报错!不能在静态方法中使用类型参数
   

    // 在jdk8 中,可以在接口中使用默认方法, 默认方法可以使用泛型接口的类型参数
    default R method(U u) {
        return null;
    }
}

泛型接口的使用

❤️❤️因为接口不能被实例化,所以该泛型接口中的类型参数,是在在该接口被继承或者被实现时确定。使用如下:
**❤️❤️
**我们先定义一个泛型接口

复制代码
interface IUsb<U, R> {
    R get(U u);
    void hi(R r);
    default R method(U u) {
        return null;
    }
}

1.我们能定义一个接口 IA 继承了 泛型接口 IUsb,在 接口 IA 定义时必须确定泛型接口 IUsb 中的类型参数。

复制代码
// 在继承泛型接口时,必须确定泛型接口的类型参数
interface IA extends IUsb<String, Double> {
	...
}

// 当去实现 IA 接口时,因为 IA 在继承 IUsu 接口时,指定了类型参数 U 为 String,R 为 Double
// 所以在实现 IUsb 接口的方法时,使用 String 替换 U,用 Double 替换 R
class AA implements IA {
    @Override
    public Double get(String s) {
        return null;
    }
    @Override
    public void hi(Double d) {
		...
    }
}

2.定义一个类 BB 实现了 泛型接口 IUsb,在 类 BB 定义时需要确定泛型接口 IUsb 中的类型参数。

复制代码
// 实现接口时,需要指定泛型接口的类型参数
// 给 U 指定 Integer, 给 R 指定了 Float
// 所以,当我们实现 IUsb 方法时,会使用 Integer 替换 U, 使用 Float 替换 R
class BB implements IUsb<Integer, Float> {
    @Override
    public Float get(Integer integer) {
        return null;
    }
    @Override
    public void hi(Float afloat) {
		...
    }
}

3.定义一个类 CC 实现了 泛型接口 IUsb 时,若是没有确定泛型接口 IUsb 中的类型参数,则默认为 Object。

复制代码
// 实现泛型接口时没有确定类型参数,则默认为 Object
// 建议直接写成 IUsb<Object, Object>
class CC implements IUsb {//等价 class CC implements IUsb<Object, Object> 
    @Override
    public Object get(Object o) {
        return null;
    }
    @Override
    public void hi(Object o) {
    	...
    }
}

4.如果定义一个类 DD 实现了 泛型接口 IUsb 时,若是没有确定泛型接口 IUsb 中的类型参数,也可以将 DD 类也定义为泛型类,泛型类声明的类型参数必须有接口 IUsb 中的类型参数。

复制代码
// DD 类定义为 泛型类,则不需要确定 接口的类型参数
// 但 DD 类定义的类型参数必须要有接口中类型参数
class DD<U, R> implements IUsb<U, R> { 
	...
}

同理我们的接口继承接口也是一样的道理,也可以泛型接口继承泛型接口,规则跟上面一样,这里就不写代码了。

泛型方法

泛型方法的定义

当在一个方法签名中的返回值前面声明了一个 < T > 时,该方法就被声明为一个泛型方法。< T >表明该方法声明了一个类型参数 T,并且这个类型参数 T 只能在该方法中使用。当然,泛型方法中也可以使用泛型类定义的类型参数。

复制代码
public <类型参数> 返回类型 方法名(类型参数 变量名) {
    ...
}

1.只有在方法中声明了< T >的方法才是泛型方法,仅使用了泛型类定义的类型参数的方法并不是泛型方法
2.泛型方法中可以同时声明多个类型参数。
3.泛型方法中也可以使用泛型类中定义的泛型参数。

需要注意的是泛型类中定义的类型参数和泛型方法中定义的类型参数是相互独立的,它们一点关系都没有。

如果泛型方法和泛型类出现同名的类型参数,在泛型方法内部则优先选择泛型方法的类型参数。为了避免混淆,如果在一个泛型类中存在泛型方法,那么两者的类型参数最好不要同名
4.前面在泛型类的定义中提到,在静态成员中不能使用泛型类定义的类型参数,但我们可以将静态成员方法定义为一个泛型方法,自己内部去使用自己的类型参数。

复制代码
public class Test2<T> {   
	// 泛型类定义的类型参数 T 不能在静态方法中使用
	// 但可以将静态方法声明为泛型方法,方法中便可以使用其声明的类型参数了
    public static <E> E show(E one) {     
        return null;    
    }    
}  

泛型方法的使用

泛型类,在创建类的对象的时候确定类型参数的具体类型;
泛型方法,在调用方法的时候确定类型参数的具体类型。

当调用泛型方法时,根据外部传入的实际对象的数据类型,编译器就可以判断出类型参数所代表的具体数据类型。

复制代码
public class Demo {  
  public static void main(String args[]) {  
    GenericMethod d = new GenericMethod(); // 创建 GenericMethod 对象  
    
    String str = d.fun("汤姆"); // 给GenericMethod中的泛型方法传递字符串  
    int i = d.fun(30);  // 给GenericMethod中的泛型方法传递数字,自动装箱  
    System.out.println(str); // 输出 汤姆
    System.out.println(i);  // 输出 30

	GenericMethod.show("Lin");// 输出: 静态泛型方法 Lin
  }  
}

class GenericMethod {
	// 普通的泛型方法
	public <T> T fun(T t) { // 可以接收任意类型的数据  
    	return t;
  	} 

	// 静态的泛型方法
	public static <E> void show(E one){     
         System.out.println("静态泛型方法 " + one);
    }
}  

泛型方法中的类型推断

泛型方法中的类型推断是指编译器根据方法的参数类型来推断泛型类型参数的具体类型。

所以在调用泛型方法的时候,我们可以显式地指定类型参数,也可以不指定。
当泛型方法的形参列表中有多个类型参数时,在不指定类型参数的情况下,方法中声明的的类型参数为泛型方法中的几种类型参数的共同父类的最小级,直到 Object。

在指定了类型参数的时候,传入泛型方法中的实参的数据类型必须为指定数据类型或者其子类。

我们直接看实例更方便理解以上知识点:

复制代码
public class Test {

	// 这是一个简单的泛型方法  
    public static <T> T add(T x, T y) {  
        return y;  
    }

    public static void main(String[] args) {  
        // 一、不显式地指定类型参数

//(1)传入的两个实参都是int类型,经过编译器推断出类型为Integer,所以泛型方法中的<T>为<Integer>
        int i = Test.add(1, 2);
    

        //(2)传入的两个实参一个是 Integer,另一个是 Float,
        // 所以<T>取共同父类的最小级,<T> == <Number>
		Number f = Test.add(1, 1.2);

		// 传入的两个实参一个是 Integer,另一个是 String,
		// 所以<T>取共同父类的最小级,<T> == <Object>
        Object o = Test.add(1, "asd");
  

        // 二、显式地指定类型参数
        //(1)指定了<T> = <Integer>,所以传入的实参只能为 Integer 对象    
        int a = Test.<Integer>add(1, 2);
		
		//(2)指定了<T> = <Integer>,所以不能传入 Float 对象
        int b = Test.<Integer>add(1, 2.2);// 编译错误
        
        //(3)指定<T> = <Number>,所以可以传入 Number 对象
        // Integer 和 Float 都是 Number 的子类,因此可以传入两者的对象
        Number c = Test.<Number>add(1, 2.2); 
    }  
}

泛型的上界

泛型的上界定义是指在泛型类型参数声明时,通过使用extends关键字来限制该类型参数必须是某个特定类型或其子类型。这样可以确保在使用泛型时,只能传入符合上界要求的类型。 语法:<类型参数 extends 具体引用类型>
例如,假设我们有一个泛型类Box<T>,我们可以使用上界定义来限制T只能是Number类及其子类:

复制代码
class Box<T extends Number> { 
               private T value; 
               public void setValue(T value) {
                 this.value = value;
        } 
               public T getValue() {
                  return value; 
} } 

在上述代码中,T extends Number表示T必须是Number类或其子类。这样,在创建Box对象时,只能传入Number类及其子类的实例作为泛型参数。
所以这就是我们泛型的上界的 定义与使用。

而对于泛型既然有上界,那么肯定有下界,对于下界它在泛型的进阶中,我们打算数据结构结尾部分再讲泛型的进阶部分。 现在先把泛型的基础打牢再说。

总结

所以以上就是泛型的初步认识(1),这部分的知识点比较简单,较容易理解。到了第二部分的泛型知识点,层面就比较深了,很晦涩难懂。所以第一部分的知识点还请大家认真消化吸收,下篇文章将带来泛型第二部分的讲解。还希望各位大佬们能给个三连,点点关注,点点赞,发发评论呀,感谢各位大佬~❤️❤️💕💕🥳🎉🎉🎉

相关推荐
liulilittle2 分钟前
C++ 浮点数封装。
linux·服务器·开发语言·前端·网络·数据库·c++
小马爱打代码8 分钟前
Spring AI:搭建自定义 MCP Server:获取 QQ 信息
java·人工智能·spring
郭涤生10 分钟前
QT 架构笔记
java·数据库·系统架构
daidaidaiyu13 分钟前
基于LangGraph开发复杂智能体学习一则
java·ai
天问一28 分钟前
使用 Vue Router 进行路由定制和调用的示例
前端·javascript·vue.js
失散1339 分钟前
Python——1 概述
开发语言·python
萧鼎41 分钟前
Python 图像哈希库 imagehash——从原理到实践
开发语言·python·哈希算法
小小8程序员1 小时前
STL 库(C++ Standard Template Library)全面介绍
java·开发语言·c++
立志成为大牛的小牛1 小时前
数据结构——五十六、排序的基本概念(王道408)
开发语言·数据结构·程序人生·算法
a努力。1 小时前
Redis Java 开发系列#2 数据结构
java·数据结构·redis