Java中的泛型

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

  1. 背景

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

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

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

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

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

  1. 概念

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

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

  1. 作用

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

泛型可以在编译时对类型进行安全检测,使得所有的强制转换都是自动隐式实现的,保证了类型的安全性;

泛型作为”代码模板“,实现了 一套代码对各种类型的套用, 提高了代码的可重用性。

  1. 使用场景

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

1.泛型集合:在各种集合中使用泛型,保证集合中元素的类型安全;

2.泛型方法:在各种方法中使用泛型,保证方法中参数的类型安全;

3.泛型类:在类的定义时使用泛型,为某些变量和方法定义通用的类型;

4.泛型接口:在接口定义时使用泛型,为某些常量和方法定义通用的类型;

5.泛型加反射:泛型也可以结合反射技术,实现在运行时获取传入的实际参数等功能。

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

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

二. 泛型集合

  1. 简介

泛型最常见的一个用途,就是在集合中对数据元素的类型进行限定。集合作为一个容器,主要是用来容纳保存数据元素的,但集合的设计者并不知道我们会用集合来保存什么类型的对象,所以他们就把集合设计成能保存任何类型的对象。这就要求集合具有很好的通用性,内部可以装载各种类型的数据元素。集合之所以可以实现这一功能,主要是集合的源码中已经结合泛型做了相关的设计,我们来看看Collection的源码,如下图所示:
在这里插入图片描述
而Collection的子类List中也增加了对泛型的支持,如下图所示:
在这里插入图片描述
上面的源码中,集合中的< E >就是泛型,至于泛型的名字为什么叫做”E“,后面再跟大家细说。但不管如何,从这些源码中我们就可以看出,Java的集合本身就支持泛型了。我们先不管集合底层是如何设计的,咱们先从基本用法开始学起。

  1. 语法

在集合中使用泛型其实比较简单,我们以List集合为例,其基本语法如下:
在这里插入图片描述
上面的语法,其含义是说我们定义了一个ArrayList集合,但该集合不能随便添加数据元素,只能添加String类型的元素。也就是说,在上面的语法中,我们通过泛型,限定了ArrayList集合的元素类型。当我们定义List集合时,如果已经限定了泛型类型,但后面添加元素时你非得违背这个类型,Java就会在编译阶段报错,如下图所示:
在这里插入图片描述
我们在定义集合时,可以省略后面ArrayList里的String,编译器可以自动根据前面< >里的类型,推断出后面< >里使用的泛型类型。另外Set和Map集合的用法,与List集合类似,我们可以通过下面这个案例来体会一下集合泛型的魅力。

  1. 代码案例

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

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

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

  1. 语法

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

E - Element(表示集合元素,常在集合中使用);

T - Type(表示Java类,常用在类和接口中);

K - Key(表示键);

V - Value(表示值);

N - Number(表示数值类型);

? - 表示不确定的Java类型。

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

  1. 代码案例
    2.1 定义泛型接口

接下来我们再通过一个案例来学习一下接口泛型如何使用,这里我们定义一个泛型接口ICompute,内部定义了一个用于计算的方法,如下所示:
在这里插入图片描述
2.2 实现泛型接口

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

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

  1. 语法

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

  1. 代码案例
    2.1 定义泛型类

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

2.2 使用泛型类

接下来是使用Pair类的具体代码:
在这里插入图片描述
在上述代码中,我们使用了Pair类,并将类型参数指定为String类型。然后我们创建了一个Pair对象,并通过getFirst和getSecond方法访问了成员变量。

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

  1. 简介

大家要注意,一个被定义为泛型的类和接口,也可以被子类继承和实现。例如下面的示例代码,就给大家演示了如何继承一个泛型类。
在这里插入图片描述
但是如果我们想要SonClass类在继承FatherClass类时,能够保留父类的泛型类型,则需要在继承时就指定。否则直接使用extends FatherClass语句进行继承操作时,T1、T2 和 T3都会自动变为Object类型,所以一般情况下都是将父类的泛型类型保留。

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

  1. 继承泛型类
    2.1 定义泛型父类

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

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

6、泛型方法

在java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。

尤其是我们见到的大多数泛型类中的成员方法也都使用了泛型,有的甚至泛型类中也包含着泛型方法,这样在初学者中非常容易将泛型方法理解错了。

泛型类:是在实例化类的时候指明泛型的具体类型;
泛型方法:是在调用方法的时候指明泛型的具体类型 。

/**
 * 泛型方法的基本介绍
 * @param tClass 传入的泛型实参
 * @return T 返回值为T类型
 * 说明:
 *     1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
 *     2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
 *     3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
 *     4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
 */
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
  IllegalAccessException{
        T instance = tClass.newInstance();
        return instance;
}

调用,返回

Object obj = genericMethod(Class.forName("com.test.test"));

静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。

  1. Java 泛型擦除
Java 泛型的参数只可以代表类,不能代表个别对象。
由于 Java 泛型的类型参数之实际类型在编译时会被消除,所以无法在运行时得知其类型参数的类型。Java 编译器在编译泛型时会自动加入类型转换的编码,故运行速度不会因为使用泛型而加快。

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。

Java中泛型其实是伪泛型;
例如:在编译阶段,泛型类Box会被实例化为Box和Box。编译器会根据需要插入类型转换代码来保证类型安全。
然而,在运行时,所有的泛型类型参数T都会被擦除,stringBox和integerBox实际上是相同的类型Box。因此,存放在盒子中的数据在运行时都被当作Object类型处理。
最后会多做一步强转,通过类型转换为泛型类型,这样可以确保获取到正确的数据类型。

JVM并不知道泛型的存在,因为泛型在编译阶段就已经被处理成普通的类和方法;
处理机制是通过类型擦除,擦除规则:

1.若泛型类型没有指定具体类型,用Object作为原始类型;
2.若有限定类型< T exnteds XClass >,使用XClass作为原始类型;
3.若有多个限定< T exnteds XClass1 & XClass2 >,使用第一个边界类型XClass1作为原始类型;

示例:

在Springboot中,调用RESTful api时常用的方法主要有两种:

通过自带的RestTemplate 或者 自己写http客户端访问工具来实现服务调用

基本上RestTemplate已经可以满足需要了

RestTemplate其实是对http请求中一些模块化代码的封装,比如建立连接、构造请求头 请求体、解析响应信息、关闭连接等,是Springboot对HttpClient的封装,简化了http请求过程,减少冗余代码。

主要方法:
RestTemplate封装了常用http请求,比如GET、POST、PUT、DELETE等,可以方便的调用,主要请求方法如下:

public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException
 
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException
 
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException
 
public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException
 
public void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientException
 
public void delete(String url, Object... uriVariables) throws RestClientException
 
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException
 
public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException

这些方法使用起来是比较简单的,其中xxForObject返回的是接口返回值,xxForEntity返回的除了接口返回值 还包括http返回信息。

对于exchange和execute使用起来就比较灵活了,里面有个参数 HttpMethod,可以使用各种请求方式进行请求,比如用HttpMethod.GET方法进行GET请求。

HttpMethod是一个枚举:
在这里插入图片描述

RestTemplate exchange方法使用:
1、先新建一个springboot工程,端口取默认 8080,然后新建一个Controller,如下:

@RestController
public class RemoteRestController {
    @GetMapping("rest")
    public String usedForRemoteRest(){
        return "Hi there, from remote";
    }
}

如果调用成功,会返回一个字符串

2、再新建一个springboot工程,端口取8081,工程名比如取ex,方便辨认

server.port=8081

3、在新建的ex工程中,新建一个Controller,准备调用RestTemplate方法,去访问第一个工程中的接口:

@RestController
public class RestTestController {
    private static final String REMOTE_URL = "http://localhost:8080/";
 
    @GetMapping("rtest")
    public String restGetValue(HttpServletRequest req){
        RestTemplate restTemplate = new RestTemplate();
        String ret = restTemplate.getForObject(REMOTE_URL + "rest", String.class);
        System.out.println("====================ret: " + ret);
 
        ResponseEntity<String> retEntity = restTemplate.getForEntity(REMOTE_URL + "ret", String.class);
        return "";
    }
}

分别运行两个spring工程,在浏览器中输入http://localhost:8081/rtest ,可以看到返回:
在这里插入图片描述
在ex工程中下断点,可以看到getForObject和getForEntity返回值的区别:
在这里插入图片描述
在这里插入图片描述
可以看到getForEntity返回信息会包括http的信息。

上面是简单的使用
在源码中我们可以看到,xxForObject, xxForEntity都是有返回值,而对于put, delete访问需要得到返回值,使用:

public void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientException

public void delete(String url, Object... uriVariables) throws RestClientException

这两个方法就无能为力了

这时我们可以使用exchange方法来使用,具体如下:

在测试springboot工程中,写一个put方法的controller:

@PutMapping("restput")
    public String putMethod(){
        return "PUT method invoke";
    }

可以封装一个exchange方法:

private <T, A> T exchange1(String url, HttpMethod method, Class<T> responseBodyType, A requestBody) {
        RestTemplate template = new RestTemplate();
        // 请求头
        HttpHeaders headers = new HttpHeaders();
        MimeType mimeType = MimeTypeUtils.parseMimeType("application/json");
        MediaType mediaType = new MediaType(mimeType.getType(), mimeType.getSubtype(), Charset.forName("UTF-8"));
        // 请求体
        headers.setContentType(mediaType);
        // 发送请求
        HttpEntity<A> entity = new HttpEntity<>(requestBody, headers);
        ResponseEntity<T> resultEntity = template.exchange(url, method, entity, responseBodyType);
        return resultEntity.getBody();
    }

responseBody: 代表返回值类型

requestBody: 代表请求体中的body参数

在ex工程中写一个测试请求接口:

@GetMapping("rput")
    public String restPUTValue(HttpServletRequest req){
        String ret = exchange(REMOTE_URL + "restput", HttpMethod.PUT, String.class, null);
        return "RETURN: " + ret;
    }

在浏览器中输入:

http://localhost:8081/rput

会发现返回结果显示出来了:
在这里插入图片描述
exchange方法返回值中有泛型类型情况:
前面介绍的exchange方法是处理RestTemplate自带的put, delete方法中没有返回值的情况,但是包括get, post等方法,如果返回值中有泛型值,RestTemplate自带的getForEntity, getForObject等其实也是处理不了的,这时也可以使用exchange来代替,具体如下:

可以对上面封装的exchange方法进行改造,传入一个ParameterTypeReference对象:

public static <T, A> T exchange2(String url, HttpMethod method, ParameterizedTypeReference<T> responseBodyType, A requestBody) {
        RestTemplate restTemplate = new RestTemplate();
        // 请求头
        HttpHeaders headers = new HttpHeaders();
        MimeType mimeType = MimeTypeUtils.parseMimeType("application/json");
        MediaType mediaType = new MediaType(mimeType.getType(), mimeType.getSubtype(), Charset.forName("UTF-8"));
        // 请求体
        headers.setContentType(mediaType);
        // 发送请求
        HttpEntity<A> entity = new HttpEntity<>(requestBody, headers);
        ResponseEntity<T> resultEntity = restTemplate.exchange(url, method, entity, responseBodyType);
        return resultEntity.getBody();
    }

测试代码:

两个springboot工程分别,服务提供方、服务消费方:

场景1:泛型 返回字符串

服务提供方代码:
@PostMapping("/rpctop")
    public String rpctop(@RequestBody UserVO userVO){
        return "张贵";
    }

服务消费方代码:
public String getUserName() throws JsonProcessingException {


        HttpHeaders headers = new HttpHeaders();
        headers.add("x-auth-token","123");
        headers.setContentType(MediaType.APPLICATION_JSON);
        ObjectMapper objectMapper = new ObjectMapper();

        Map<String, Object> map = new HashMap<>();
        map.put("name", "1111");
        map.put("age", "19");

        HttpEntity requestEntity = new HttpEntity(map, headers);

		//调用方式1
		Integer response = restTemplate.exchange("http://127.0.0.1:8080/ccbServer/tx/rpctop", HttpMethod.POST, String.class, map);
       
		//调用方式2
        //ParameterizedTypeReference<String> responseBodyType = new ParameterizedTypeReference<String>() {};
        //Person response = restTemplate.exchange("http://127.0.0.1:8080/ccbServer/tx/rpctop", HttpMethod.POST, responseBodyType, map);

        return response;
    }

场景2:泛型 返回Integer数据

服务提供方代码:
@PostMapping("/rpctop")
    public Integer rpctop(@RequestBody UserVO userVO){
        return 666;
    }

服务消费方代码:
public Integer getUserName() throws JsonProcessingException {
        HttpHeaders headers = new HttpHeaders();
        headers.add("x-auth-token","123");
        headers.setContentType(MediaType.APPLICATION_JSON);
        ObjectMapper objectMapper = new ObjectMapper();

        Map<String, Object> map = new HashMap<>();
        map.put("name", "1111");
        map.put("age", "19");

        HttpEntity requestEntity = new HttpEntity(map, headers);

		//调用方式1
		Integer response = restTemplate.exchange("http://127.0.0.1:8080/ccbServer/tx/rpctop", HttpMethod.POST, Integer.class, map);       

		//调用方式2
        //ParameterizedTypeReference<Integer> responseBodyType = new ParameterizedTypeReference<Integer>() {};
        //Person response = restTemplate.exchange("http://127.0.0.1:8080/ccbServer/tx/rpctop", HttpMethod.POST, responseBodyType, map);

        return response;
    }

场景3:泛型 返回Person实体

服务提供方代码:
@PostMapping("/rpctop")
    public Person rpctop(@RequestBody UserVO userVO){
        Person person = new Person();
        person.setName("zjg");
        person.setAge(10);
        return person;
    }

服务消费方代码:
public Integer getUserName() throws JsonProcessingException {
        HttpHeaders headers = new HttpHeaders();
        headers.add("x-auth-token","123");
        headers.setContentType(MediaType.APPLICATION_JSON);
        ObjectMapper objectMapper = new ObjectMapper();

        Map<String, Object> map = new HashMap<>();
        map.put("name", "1111");
        map.put("age", "19");

        HttpEntity requestEntity = new HttpEntity(map, headers);

		//调用方式1
		Person response = restTemplate.exchange("http://127.0.0.1:8080/ccbServer/tx/rpctop", HttpMethod.POST, Person.class, map);
		
		//调用方式2
        //ParameterizedTypeReference<Person> responseBodyType = new ParameterizedTypeReference<Person>() {};
        //Person response = restTemplate.exchange("http://127.0.0.1:8080/ccbServer/tx/rpctop", HttpMethod.POST, responseBodyType, map);

        return response.getName();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值