首页 > 编程语言 >Java中泛型详解,非常详细

Java中泛型详解,非常详细

时间:2023-05-31 10:14:04浏览次数:50  
标签:Java 定义 接口 类型 详解 泛型 集合 public 中泛

前言

在前面的几篇文章中,详细地给大家介绍了Java里的集合。但在介绍集合时,我们涉及到了泛型的概念却并没有详细学习,所以今天我们要花点时间给大家专门讲解什么是泛型、泛型的作用、用法、特点等内容。

有些粉丝朋友,在之前就一直很好奇,比如List< String >中的 < String > 部分到底是什么?有啥用?为什么要加这个< >?这一部分有没有什么特别的用法?总之,你的疑问可能会有很多,别急,今天就带你一点点认识Java里的泛型!


全文大约【6000】 字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考...

一. 泛型简介

作为Java中常用且重要的一个概念,泛型帮我们实现了代码重用,也保证了类型安全。但关于它的详细内容,目前很多同学还不清楚,所以接下来就带各位来学习这个重要的知识点。

1. 背景

为了能够让大家更好地理解泛型的作用,在我们开始学习泛型之前,先给大家提个开发需求:

我们现在有一个需求,要求你编写一个对数组进行排序的方法,该方法能够对浮点型数组、整型数组、字符串数组或者是其他任何类型的数组进行排序,你该如何实现?

有的小伙伴会说,这很简单啊,我可以利用方法重载,针对每种类型的数组分别编写一个排序方法,需要为几种类型的数组排序,我就定义几个排序方法。如果你是这么实现的,只能哈哈哈了,这种做法明显不好,代码可重用性太差

又有的小伙伴说了,可以定义一个方法,里面设置一个Object[]类型的参数,这样无论是哪种类型都可以处理了。这样定义方法,比上面那个同学的想法要稍好一点,但此时我们需要在Object类型和整型、String类型或其他类型之间进行强制类型转换。所以这样做就无法保证集合中元素的类型安全,稍一不慎就可能会导致 ClassCastException类型转换异常

so,这也不行,那也不行,到底该怎么办?这不,为了解决这些问题,所以Java中就产生了泛型这个技术。

2. 概念

泛型(generics) 这个技术是在JDK 5中引入的新特性,它的本质其实是类型参数化, 利用泛型可以实现一套代码对多种数据类型的动态处理,保证了更好的代码重用性。并且泛型还提供了编译时对类型安全进行检测的机制,该机制允许我们在编译时就能够检测出非法的类型, 提高了代码的安全性

这种特性,使得泛型成了一种 “代码模板” ,让我们利用一套代码就能实现对各种类型的套用。也就是说,我们只需要编写一次代码,就可以实现万能匹配,这也是”泛型“这个概念的含义,你可以将其理解为”广泛的类型“、”非特定的类型“。咱们上面的那个需求,利用泛型就能轻松实现,还不需要进行类型的强制转换,并且也保证了数据的类型安全。

3. 作用

所以根据上面泛型的概念,我们可以提取出泛型的核心作用:

  • 泛型可以在编译时对类型进行安全检测,使得所有的强制转换都是自动隐式实现的,保证了类型的安全性;
  • 泛型作为”代码模板“,实现了 一套代码对各种类型的套用, 提高了代码的可重用性。

4. 使用场景

基于泛型的这些特性和作用,我们可以把泛型用在很多地方,在这里给大家做了一个总结,通常情况下,泛型可以用在如下场景中:

  • 泛型集合:在各种集合中使用泛型,保证集合中元素的类型安全;
  • 泛型方法:在各种方法中使用泛型,保证方法中参数的类型安全;
  • 泛型类:在类的定义时使用泛型,为某些变量和方法定义通用的类型;
  • 泛型接口:在接口定义时使用泛型,为某些常量和方法定义通用的类型;
  • 泛型加反射:泛型也可以结合反射技术,实现在运行时获取传入的实际参数等功能。

但是我们要注意,无论我们在哪个地方使用泛型,泛型都不能是基本类型, 关于这一点,我会在讲解泛型擦除时再细说。

总之,泛型的应用场景有很多,以上只是给大家总结的几个重点使用场景,接下来就这几个场景分别给大家进行讲解。

5. 配套视频

与本节内容配套的视频链接如下:戳我直达

二. 泛型集合

1. 简介

泛型最常见的一个用途,就是在集合中对数据元素的类型进行限定。集合作为一个容器,主要是用来容纳保存数据元素的,但集合的设计者并不知道我们会用集合来保存什么类型的对象,所以他们就把集合设计成能保存任何类型的对象。这就要求集合具有很好的通用性,内部可以装载各种类型的数据元素。集合之所以可以实现这一功能,主要是集合的源码中已经结合泛型做了相关的设计,我们来看看Collection的源码,如下图所示:

image.png

而Collection的子类List中也增加了对泛型的支持,如下图所示:

image.png

上面的源码中,集合中的< E >就是泛型,至于泛型的名字为什么叫做”E“,后面再跟大家细说。但不管如何,从这些源码中我们就可以看出,Java的集合本身就支持泛型了。我们先不管集合底层是如何设计的,咱们先从基本用法开始学起。

2. 语法

在集合中使用泛型其实比较简单,我们以List集合为例,其基本语法如下:

//可以省略后面ArrayList里的String,编译器可以自动根据前面<>里的类型,推断出后面<>里使用的泛型类型
List<String> list = new ArrayList<>();

上面的语法,其含义是说我们定义了一个ArrayList集合,但该集合不能随便添加数据元素,只能添加String类型的元素。也就是说,在上面的语法中,我们通过泛型,限定了ArrayList集合的元素类型。当我们定义List集合时,如果已经限定了泛型类型,但后面添加元素时你非得违背这个类型,Java就会在编译阶段报错,如下图所示:

image.png

我们在定义集合时,可以省略后面ArrayList里的String,编译器可以自动根据前面< >里的类型,推断出后面< >里使用的泛型类型。另外Set和Map集合的用法,与List集合类似,我们可以通过下面这个案例来体会一下集合泛型的魅力。

3. 代码案例

在本案例中,我们可以给List、Set、Map等集合设置泛型,从而限定集合中数据元素的类型。

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Demo01 {
	public static void main(String[] args) {
		//定义集合泛型
		//此时的集合只能接受String类型元素,后面ArrayList<>中的<>,里面的String可写可不写
		List<String> list = new ArrayList<>();
		//如果类型不一致,在编译阶段就会检测出有错误,保证了数据的安全性
		//list.add(100);
		list.add("Hello");
		String strValue = list.get(0);
		System.out.println("list value="+strValue);
		
		Set<Integer> set=new HashSet<>();
		//set.add("hello");
		set.add(200);
		Iterator<Integer> iterator = set.iterator();
		while(iterator.hasNext()) {
			Integer nextValue = iterator.next();
			System.out.println("set value="+nextValue);
		}
		
		//限定Map集合的key是String类型,value是Long类型
		Map<String,Long> map=new HashMap<>();
		//map.put("number", "10000");
		map.put("number", 10000L);
		Long value = map.get("number");
		System.out.println("map value="+value);
	}
}

在这个案例中,我们在集合中通过泛型限定了集合元素的数据类型。如果元素的类型与要求的不一致,在编译阶段就会检测出有错误,不需要进入到运行阶段才能发现类型不一致。而且我们 在获取集合中的元素时,也不需要进行强制类型转换,程序会自动进行隐式转换, 这就保证了数据的安全性,也提高了代码的执行效率。

另外我们所使用的泛型参数,也被称为类型变量,是用于指定泛型类型名称的标识符。我们可以根据需要,在集合、类、接口、方法等地方定义一个或多个泛型参数,这些泛型化的类型参数也被称为参数化的类或参数化的类型。

4. 配套视频

与本节内容配套的视频链接如下:戳我直达

三. 泛型接口

我们除了可以在集合中使用泛型,还可以在定义接口时使用泛型,这也是泛型的常用形式之一。

1. 语法

在定义接口时使用泛型的基本语法格式如下:

//在接口名称后面紧跟泛型<>
public interface InterfaceName<T> {
    // 接口的方法定义
}

//可以同时定义多个泛型,多个泛型用","逗号分割
public interface InterfaceName2<M,N> {
    // 接口的方法定义
}

大家注意,这里泛型的名称T/M/N,其实是我们随意写的,我们并不一定非要使用T,也可以使用M、N、E等任意名称。而之所以使用T,只是采用了Type类型这个单词的首字母而已。虽然如此,但我们在实际开发时,为了尽量做到见名知意,请大家还是要尽量采用有意义的名称,通常会使用如下几个常用字母:

  • E - Element(表示集合元素,常在集合中使用);
  • T - Type(表示Java类,常用在类和接口中);
  • K - Key(表示键);
  • V - Value(表示值);
  • N - Number(表示数值类型);
  • - 表示不确定的Java类型。

另外,这里的T只是一种类型参数,你可以把它理解成是一个”表面的占位符“。在真正赋值时,它可以用任何实际的类型来替代,如Integer、String、自定义类型等。并且我们在定义接口时,可以根据实际需要,同时定义多个泛型,多个泛型之间用","逗号分割。而在实际使用时,我们需要在该接口名的后面加上一对尖括号,用来传入实际的类型。

2. 代码案例

2.1 定义泛型接口

接下来我们再通过一个案例来学习一下接口泛型如何使用,这里我们定义一个泛型接口ICompute,内部定义了一个用于计算的方法,如下所示:


public interface ICompute<M,N> {

	//定义一个加法计算的方法
	M add(M m,N n);
}

2.2 实现泛型接口

接下来我们把这个接口进行实现,代码如下:

public class Demo02 {
	public static void main(String[] args) {
		//这里壹哥直接利用匿名内部类的写法进行实现,大家也可以编写一个类实现ICompute接口
		//我这里传入了两个Integer类型的具体参数,分别取代M和N
		ICompute<Integer, Integer> iCompute = new ICompute<Integer, Integer>() {
			@Override
			public Integer add(Integer m, Integer n) {
				return m+n;
			}
		};
		
		//调用上面实现的方法
		Integer result = iCompute.add(100, 200);
		System.out.println("result="+result);
	}
}

这里直接利用匿名内部类的写法进行实现,大家也可以编写一个类实现ICompute接口。我这里传入了两个Integer类型的具体参数,分别取代M和N,当然我们也可以根据需要,在实现时传入Float/Double等其他类型。

3. 配套视频

与本节内容配套的视频链接如下:戳我直达

四. 泛型类

其实Java的类和接口在很多地方都很类似,所以我们在定义接口时可以使用泛型,也可以在定义类时使用泛型,泛型类常用于类中的属性类型不确定的情况下,这也是泛型的常用形式之一。

1. 语法

其实泛型类的声明和普通类的声明类似,只是在类名后面多添加了一个关于泛型的声明。并且泛型类的类型参数部分,可以包含一个或多个类型参数,多个参数间用逗号隔开。一般我们在定义泛型类时,需要在类名后添加类型参数,语法格式与泛型接口一致,如下所示:

public class ClassName<T> {
    // 类的成员变量和方法定义
}

泛型类的要求和泛型接口完全一样,这里就不再赘述了。

2. 代码案例

2.1 定义泛型类

接下来定义一个泛型类Pair,它包含两个类型相同的成员变量:

public class Pair<T> {

	//我们可以直接把泛型当成一个java的“类型”来用,Java类怎么用,泛型就可以怎么用
	//直接利用泛型来定义成员变量
	private T first;
    private T second;

    //构造方法中使用泛型
    public Pair(T first, T second) {
        this.first = first;
        this.second = second;
    }

    //方法中使用泛型
    public T getFirst() {
        return first;
    }

    public T getSecond() {
        return second;
    }
}

在上述代码中,我们定义了一个泛型类Pair,它有两个类型相同的成员变量first和second,以及一个构造函数和两个访问成员变量的方法。在定义Pair类时,我们使用了类型参数T来代表类型,而在实例化该泛型类时,需要指明泛型类中的类型参数,并赋予泛型类属性相应类型的值,比如指定T是String/Integer/Student/Person等任意类型。

2.2 使用泛型类

接下来是使用Pair类的具体代码:


public class Demo03 {
	public static void main(String[] args) {
		//调用泛型类
		Pair<String> pair = new Pair<>("Hello", "World");
		// 输出 "Hello"
		System.out.println("first="+pair.getFirst()); 
		// 输出 "World"
		System.out.println("last="+pair.getSecond()); 
	}
}

在上述代码中,我们使用了Pair类,并将类型参数指定为String类型。然后我们创建了一个Pair对象,并通过getFirstgetSecond方法访问了成员变量。

3. 配套视频

与本节内容配套的视频链接如下:戳我直达

五. 继承泛型类和实现泛型接口

在Java中,泛型不仅可以用于类、方法的定义,还可以用于类和接口的继承与实现。接下来就给大家详细介绍一下,该如何继承泛型类和实现泛型接口。

1. 简介

大家要注意,一个被定义为泛型的类和接口,也可以被子类继承和实现。例如下面的示例代码,就给大家演示了如何继承一个泛型类。

public class FatherClass<T1>{}

public class SonClass<T1,T2,T3> extents FatherClass<T1>{}

但是如果我们想要SonClass类在继承FatherClass类时,能够保留父类的泛型类型,则需要在继承时就指定。否则直接使用extends FatherClass语句进行继承操作时,T1、T2 和 T3都会自动变为Object类型,所以一般情况下都是将父类的泛型类型保留。

接下来会分别给大家介绍一下如何继承泛型类和实现泛型接口。

2. 继承泛型类

2.1 定义泛型父类

在Java中,我们可以通过继承一个泛型类来实现泛型的重用。子类可以继承父类中定义的泛型类型,并根据自己的需要,增加、修改泛型类型的参数,从而实现泛型类的个性化定制。下面是一个泛型类的示例:

public class GenericClass<T1> {

	private T1 data;

	public GenericClass(T1 data) {
		this.data = data;
	}

	public T1 getData() {
		return data;
	}
}

2.2 泛型子类继承父类

我们可以通过继承GenericClass类,来创建一个新的泛型类SonGenericClass,并增加新的泛型类型:

public class SonGenericClass<T1,T2> extends GenericClass<T1>{

	private T2 otherData;
    
    public SonGenericClass(T1 data, T2 otherData) {
        super(data);
        this.otherData = otherData;
    }
    
    public T2 getOtherData() {
        return otherData;
    }
}

在上面的示例中,SonGenericClass类继承了GenericClass类,并增加了一个新的泛型类型T2。在构造方法中,调用父类的构造方法,并传入T1类型的数据,然后再将T2类型的数据赋值给类的成员变量otherData。通过这种方式,我们可以创建一个具有更多泛型参数的类,并且保留了原始泛型类的特性。我们来看看最终的测试结果:

public class Demo08 {

	public static void main(String[] args) {
		SonGenericClass<Integer,String> son=new SonGenericClass<>(100, "hello");
        //子类从父类中继承来的泛型
		Integer data = son.getData();
		String otherData = son.getOtherData();
		System.out.println("t1---data="+data+",t2---data="+otherData);
	}
}

这样,子类通过继承父类,也自动获得了父类中的泛型。

3. 实现泛型接口

3.1 定义泛型接口

类似于继承泛型类,我们也可以通过实现泛型接口,来定义具有多个泛型参数的接口。实现泛型接口的过程与实现普通接口的过程相同,我们只需要在接口名后面添加 < T > 这样的泛型参数声明即可。下面是一个泛型接口的示例:

public interface GenericInterface<T1> {
	
	public void doSomething(T1 data);
}

3.2 两种实现方式

我们在实现泛型接口时,可以采用两种实现方式:

  1. 指定具体类型:就是在实现接口时,明确指定泛型参数的具体类型;
  2. 保留泛型参数:在实现接口时,不明确指定泛型参数的具体类型,而是保留泛型参数。

如果是通过指定具体类型的方式进行实现,一般形式如下:

public class StringPair implements Pair<String> {
    .....
}

在这种方式中,我们定义了一个Pair接口,然后让子类StringPair进行实现,但在实现时就明确指定了具体的泛型参数为String。这样,我们在使用StringPair对象时,就明确知道了类内部的数据类型。

如果是通过保留泛型参数的方式进行实现,一般形式如下:

public class NumberPair<T extends 父类型> implements Pair<T> {
    ......
}

在这种方式中,我们定义了一个泛型接口Pair< T >,然后定义一个实现字类NumberPair,可以在实现时保留泛型参数。

3.3 实现泛型接口

接下来,我们再编写一个SubGenericInterface类,并通过保留泛型参数的方式,来实现GenericInterface接口,并增加一个新的泛型类型T2,代码如下:

public class SubGenericClass<T1,T2> implements GenericInterface<T1>{

	private T2 otherData;
	
	@Override
	public void doSomething(T1 data) {
		System.out.println("t1="+data);
	}
    
    public SubGenericClass(T2 otherData) {
        this.otherData = otherData;
    }
    
    public T2 getOtherData() {
        return otherData;
    }

}

这样泛型子类就实现了泛型父类,并在子类中增加了一个新的泛型,最终的结果如下所示:

public class Demo09 {

	public static void main(String[] args) {
		SubGenericClass<Integer,String> sub=new SubGenericClass<>("hello");
		sub.doSomething(100);
		String otherData = sub.getOtherData();
		System.out.println("t2---data="+otherData);
	}
}

其实,实现泛型接口和继承泛型类都很简单,我们只需要在类定义中使用相同的泛型类型参数,然后实现接口的方法或覆盖超类的方法即可。

以上就是关于泛型的概念、作用、泛型接口、泛型类等相关的内容,其实泛型的内容还有很多,比如泛型方法、泛型擦除和泛型中的通配符等。但受限于篇幅,会在下一篇文章中继续给大家讲解这些内容,敬请继续关注哦。


五. 结语

至此,在本文中就把泛型的概念、作用、泛型接口和泛型类给大家介绍完了,本文重点内容如下:

  • 泛型是一种类型参数,可以编写模板代码来适应任意类型;
  • 泛型在使用时不必对类型进行强制转换,它可以通过编译器在编译阶段对类型进行检查;
  • 使用泛型时可以把泛型参数< T >替换成想要的class类型,例如 ArrayList< String> ArrayList< Number >等;
  • 编译器可以根据前面的泛型,在后面自动推断出类型,例如List< String > list = new ArrayList<>();
  • 如果我们在使用时不指定泛型参数类型时,编译器会给出警告,且只能将 < T >视为Object类型;
  • 我们可以在接口和类中定义泛型类型,实现此接口的类必须传入正确的泛型类型;
  • 我们可以同时定义多个泛型,例如 Map<K, V>
  • 可以继承泛型类和实现泛型接口。

标签:Java,定义,接口,类型,详解,泛型,集合,public,中泛
From: https://www.cnblogs.com/qian-fen/p/17445243.html

相关文章

  • java实现泛型加法
    之前实践中实现数据的加法,很繁琐,比如下面,每一种类型都要写一遍,能不能用泛型方法实现呢?publicLonggetSum(Long...args){longinit=0L;for(Longarg:args){if(arg==null){arg=0L;}ini......
  • java 对象字段名转化——@SerializedName
    有时调用第三方接口返回的字段名和我们接收对象字段名不一致或不规范,可以使用@SerializedName这个注解进行转换;直接上代码:@DatapublicclassxxxVo{//将别名product_name转为productName@SerializedName("product_name")privateStringproductName;......
  • java中线程的启动方式
     1.继承Thread类重写run方法publicclassTreadTest01extendsThread{@SneakyThrows@Overridepublicvoidrun(){for(inti=0;i<100;i++){Thread.sleep(100);System.out.println(Thread.currentThread().getNam......
  • java中线程的状态
    一:从操作系统上说可以分为5种 新建:线程被创建出来时就绪:此时cpu拥有可执行权,但是未被真正执行运行中:线程正在执行等待:被阻塞了(sleepwait方法等)结束:整个线程结束二:从java源码中分为6种状态从Thread源码中我们可以看到一个枚举类:JAVA给出了以下6种状态NEW  ......
  • Java中如何中断线程
    在Java中,可以使用以下方法中断线程:1.使用`interrupt()`方法:每个线程对象都有一个`interrupt()`方法,用于中断该线程。当调用线程的`interrupt()`方法时,它会设置线程的中断状态为"中断",但并不会立即停止线程的执行。线程在执行过程中可以通过检查中断状态来决定是否终止执行。2.......
  • TCPIP详解-地址解析协议ARP
    TCP/IP详解-地址解析协议ARPIP协议的设计目标是为跨越不同类型物理网络的分组交换提供互操作。这需要网络层软件使用的地址和底层网络硬件使用的地址之间进行转换。网络接口硬件通常有一个主要的硬件地址定位到正确的接口;否则,无法传输数据。但是,一个传统IPv4网路偶需要使用自己的......
  • Java并发(七)----线程sleep、yield、线程优先级
    1、sleep与yieldsleep调用sleep会让当前线程从Running进入TimedWaiting状态(阻塞)其它线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException睡眠结束后的线程未必会立刻得到执行建议用TimeUnit的sleep代替Thread......
  • Hadoop之YARN详解
    YARN的由来从Hadoop2开始,官方把资源管理单独剥离出来,主要是为了考虑后期作为一个公共的资源管理平台,任何满足规则的计算引擎都可以在它上面执行。所以YARN可以实现HADOOP集群的资源共享,不仅仅可以跑MapRedcue,还可以跑Spark、Flink。YARN架构分析咱们之前部署Hadoop集群的时候也......
  • 基于JAVA的springboot+vue学生综合测评系统,附源码+数据库+论文+PPT
    1、项目介绍本学生综合测评系统以springboot作为框架,b/s模式以及MySql作为后台运行的数据库,同时使用Tomcat用为系统的服务器。本系统主要包括首页,个人中心,学生管理,试题信息管理,测评试题管理,管理员管理,综合测评管理,系统管理,综合考试管理等功能,通过这些功能的实现基本能够满足日常......
  • Spring中@DependsOn 使用详解
    一、注解源码@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceDependsOn{String[]value()default{};}二、基础概念  @DependsOn是Spring框架用来指定bean之间依赖关系的注解之一,即可用户......