一. 泛型简介
泛型,即“参数化类型”。
作为Java中常用且重要的一个概念,泛型帮我们实现了代码重用,也保证了类型安全。但关于它的详细内容,目前很多同学还不清楚,所以接下来就带各位来学习这个重要的知识点。
- 背景
为了能够让大家更好地理解泛型的作用,在我们开始学习泛型之前,先给大家提个开发需求:
我们现在有一个需求,要求你编写一个对数组进行排序的方法,该方法能够对浮点型数组、整型数组、字符串数组或者是其他任何类型的数组进行排序,你该如何实现?
有的小伙伴会说,这很简单啊,我可以利用方法重载,针对每种类型的数组分别编写一个排序方法,需要为几种类型的数组排序,我就定义几个排序方法。如果你是这么实现的,只能哈哈哈了,这种做法明显不好,代码可重用性太差。
又有的小伙伴说了,可以定义一个方法,里面设置一个Object[]类型的参数,这样无论是哪种类型都可以处理了。这样定义方法,比上面那个同学的想法要稍好一点,但此时我们需要在Object类型和整型、String类型或其他类型之间进行强制类型转换。所以这样做就无法保证集合中元素的类型安全,稍一不慎就可能会导致 ClassCastException类型转换异常。
so,这也不行,那也不行,到底该怎么办?这不,为了解决这些问题,所以Java中就产生了泛型这个技术。
- 概念
泛型(generics) 这个技术是在JDK 5中引入的新特性,它的本质其实是类型参数化, 利用泛型可以实现一套代码对多种数据类型的动态处理,保证了更好的代码重用性。并且泛型还提供了编译时对类型安全进行检测的机制,该机制允许我们在编译时就能够检测出非法的类型, 提高了代码的安全性。
这种特性,使得泛型成了一种 “代码模板” ,让我们利用一套代码就能实现对各种类型的套用。也就是说,我们只需要编写一次代码,就可以实现万能匹配,这也是”泛型“这个概念的含义,你可以将其理解为”广泛的类型“、”非特定的类型“。咱们上面的那个需求,利用泛型就能轻松实现,还不需要进行类型的强制转换,并且也保证了数据的类型安全。
- 作用
所以根据上面泛型的概念,我们可以提取出泛型的核心作用:
泛型可以在编译时对类型进行安全检测,使得所有的强制转换都是自动隐式实现的,保证了类型的安全性;
泛型作为”代码模板“,实现了 一套代码对各种类型的套用, 提高了代码的可重用性。
- 使用场景
基于泛型的这些特性和作用,我们可以把泛型用在很多地方,在这里给大家做了一个总结,通常情况下,泛型可以用在如下场景中:
1.泛型集合:在各种集合中使用泛型,保证集合中元素的类型安全;2.泛型方法:在各种方法中使用泛型,保证方法中参数的类型安全;3.泛型类:在类的定义时使用泛型,为某些变量和方法定义通用的类型;4.泛型接口:在接口定义时使用泛型,为某些常量和方法定义通用的类型;5.泛型加反射:泛型也可以结合反射技术,实现在运行时获取传入的实际参数等功能。
但是我们要注意,无论我们在哪个地方使用泛型,泛型都不能是基本类型, 关于这一点,我会在讲解泛型擦除时再细说。
总之,泛型的应用场景有很多,以上只是给大家总结的几个重点使用场景,接下来就这几个场景分别给大家进行讲解。
二. 泛型集合
- 简介
泛型最常见的一个用途,就是在集合中对数据元素的类型进行限定。集合作为一个容器,主要是用来容纳保存数据元素的,但集合的设计者并不知道我们会用集合来保存什么类型的对象,所以他们就把集合设计成能保存任何类型的对象。这就要求集合具有很好的通用性,内部可以装载各种类型的数据元素。集合之所以可以实现这一功能,主要是集合的源码中已经结合泛型做了相关的设计,我们来看看Collection的源码,如下图所示:
而Collection的子类List中也增加了对泛型的支持,如下图所示:
上面的源码中,集合中的< E >就是泛型,至于泛型的名字为什么叫做”E“,后面再跟大家细说。但不管如何,从这些源码中我们就可以看出,Java的集合本身就支持泛型了。我们先不管集合底层是如何设计的,咱们先从基本用法开始学起。
- 语法
在集合中使用泛型其实比较简单,我们以List集合为例,其基本语法如下:
上面的语法,其含义是说我们定义了一个ArrayList集合,但该集合不能随便添加数据元素,只能添加String类型的元素。也就是说,在上面的语法中,我们通过泛型,限定了ArrayList集合的元素类型。当我们定义List集合时,如果已经限定了泛型类型,但后面添加元素时你非得违背这个类型,Java就会在编译阶段报错,如下图所示:
我们在定义集合时,可以省略后面ArrayList里的String,编译器可以自动根据前面< >里的类型,推断出后面< >里使用的泛型类型。另外Set和Map集合的用法,与List集合类似,我们可以通过下面这个案例来体会一下集合泛型的魅力。
- 代码案例
在本案例中,我们可以给List、Set、Map等集合设置泛型,从而限定集合中数据元素的类型。
在这个案例中,我们在集合中通过泛型限定了集合元素的数据类型。如果元素的类型与要求的不一致,在编译阶段就会检测出有错误,不需要进入到运行阶段才能发现类型不一致。而且我们 在获取集合中的元素时,也不需要进行强制类型转换,程序会自动进行隐式转换, 这就保证了数据的安全性,也提高了代码的执行效率。
另外我们所使用的泛型参数,也被称为类型变量,是用于指定泛型类型名称的标识符。我们可以根据需要,在集合、类、接口、方法等地方定义一个或多个泛型参数,这些泛型化的类型参数也被称为参数化的类或参数化的类型。
三. 泛型接口
我们除了可以在集合中使用泛型,还可以在定义接口时使用泛型,这也是泛型的常用形式之一。
- 语法
在定义接口时使用泛型的基本语法格式如下:
大家注意,这里泛型的名称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.1 定义泛型接口
接下来我们再通过一个案例来学习一下接口泛型如何使用,这里我们定义一个泛型接口ICompute,内部定义了一个用于计算的方法,如下所示:
2.2 实现泛型接口
接下来我们把这个接口进行实现,代码如下:
这里直接利用匿名内部类的写法进行实现,大家也可以编写一个类实现ICompute接口。我这里传入了两个Integer类型的具体参数,分别取代M和N,当然我们也可以根据需要,在实现时传入Float/Double等其他类型。
四. 泛型类
其实Java的类和接口在很多地方都很类似,所以我们在定义接口时可以使用泛型,也可以在定义类时使用泛型,泛型类常用于类中的属性类型不确定的情况下,这也是泛型的常用形式之一。
- 语法
其实泛型类的声明和普通类的声明类似,只是在类名后面多添加了一个关于泛型的声明。并且泛型类的类型参数部分,可以包含一个或多个类型参数,多个参数间用逗号隔开。一般我们在定义泛型类时,需要在类名后添加类型参数,语法格式与泛型接口一致,如下所示:
泛型类的要求和泛型接口完全一样,这里就不再赘述了。
- 代码案例
2.1 定义泛型类
接下来定义一个泛型类Pair,它包含两个类型相同的成员变量:
在上述代码中,我们定义了一个泛型类Pair,它有两个类型相同的成员变量first和second,以及一个构造函数和两个访问成员变量的方法。在定义Pair类时,我们使用了类型参数T来代表类型,而在实例化该泛型类时,需要指明泛型类中的类型参数,并赋予泛型类属性相应类型的值,比如指定T是
String/Integer/Student/Person等任意类型。
2.2 使用泛型类
接下来是使用Pair类的具体代码:
在上述代码中,我们使用了Pair类,并将类型参数指定为String类型。然后我们创建了一个Pair对象,并通过getFirst和getSecond方法访问了成员变量。
五. 继承泛型类和实现泛型接口
在Java中,泛型不仅可以用于类、方法的定义,还可以用于类和接口的继承与实现。接下来就给大家详细介绍一下,该如何继承泛型类和实现泛型接口。
- 简介
大家要注意,一个被定义为泛型的类和接口,也可以被子类继承和实现。例如下面的示例代码,就给大家演示了如何继承一个泛型类。
但是如果我们想要SonClass类在继承FatherClass类时,能够保留父类的泛型类型,则需要在继承时就指定。否则直接使用extends FatherClass语句进行继承操作时,T1、T2 和 T3都会自动变为Object类型,所以一般情况下都是将父类的泛型类型保留。
接下来会分别给大家介绍一下如何继承泛型类和实现泛型接口。
- 继承泛型类
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"));
静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
- 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 RestClientExceptionpublic <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientExceptionpublic <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientExceptionpublic <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientExceptionpublic void put(String url, @Nullable Object request, Object... uriVariables) throws RestClientExceptionpublic void delete(String url, Object... uriVariables) throws RestClientExceptionpublic <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientExceptionpublic <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 RestClientExceptionpublic 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);//调用方式1Integer 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);//调用方式1Integer 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);//调用方式1Person 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();}