文章目录
- 一、泛型
- 1.1 泛型概述
- 1.2 集合泛型的使用
- 1.2.1 未使用泛型
- 1.2.2 使用泛型
- 1.3 泛型类
- 1.3.1 泛型类的使用
- 1.2.2 泛型类的继承
- 1.4 泛型方法
- 1.5 泛型通配符
- 1.5.1 通配符的使用
- 1)参数列表带有泛型
- 2)泛型通配符
- 1.5.2 泛型上下边界
- 1.6 泛型的擦除
- 1.6.1 限制擦除
- 1.6.2 泛型的桥接方法
一、泛型
1.1 泛型概述
泛型定义:把类型明确的工作延迟到创建对象或调用方法的时候才去明确的特殊的类型;
例如,我们知道集合是可以存储任意元素的,那么这样一想,add方法上的参数应该是Object(所有类的父类),但是这样会引入一个新的问题,我们知道,子类都是比父类强大的,我们在使用的时候肯定是希望获取的是当初存进去的具体子类对象;因此我们每次都需要进行强制转换;
但add方法真的是Object吗?
查看ArrayList的add方法:
class ArrayList<E>{
public boolean add(E e){ }
public E get(int index){ }
....
}
Collection类:
public interface Collection<E> extends Iterable<E> {
}
上面的E就是泛型,集合的定义者也不知道我们需要存储什么元素到集合中,具体的类型只能延迟到创建对象时来决定了;
1.2 集合泛型的使用
1.2.1 未使用泛型
定义一个Province对象:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Province {
private String name; // 名称
private String shortName; // 简称
private String location; // 所属区域
public void intro() {
System.out.println("名称: " + name + ",简称: " + shortName + ",所属地区: " + location);
}
@Override
public String toString() {
return "Province{" +
"name='" + name + '\'' +
", shortName='" + shortName + '\'' +
", location='" + location + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getShortName() {
return shortName;
}
public void setShortName(String shortName) {
this.shortName = shortName;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public Province() {
}
public Province(String name, String shortName, String location) {
this.name = name;
this.shortName = shortName;
this.location = location;
}
}
测试类:
package com.dfbz.demo01;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_泛型问题引出 {
public static void main(String[] args) {
Collection list = new ArrayList();
// 往集合存储对象
list.add(new Province("台湾", "台","华东"));
list.add(new Province("澳门", "澳","华南"));
list.add(new Province("香港", "港","华南"));
list.add(new Province("河北", "冀","华北"));
// 获取迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
// 集合中的元素都被提升为Object对象了
Object obj = iterator.next();
// 强制转换为子类
Province province = (Province) obj;
// 调用子类特有的功能
province.intro();
}
}
}
我们没有给泛型进行明确的定义,对象存储到集合中都被提升为Object类型了,取出来时需要强制转换为具体子类,非常麻烦;
不仅如此,这样的代码还存在这隐藏的风险,集合中可以存储任意的对象,如果往集合中存储其他对象呢?
定义个Book对象:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Book {
private String name;
private String author;
public void detail() {
System.out.println("书名: " + name + ",作者: " + author);
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Book() {
}
public Book(String name, String author) {
this.name = name;
this.author = author;
}
}
测试类:
package com.dfbz.demo01;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_泛型出现的问题 {
public static void main(String[] args) {
Collection list = new ArrayList();
// 添加几个省份
list.add(new Province("山西", "晋","华北"));
list.add(new Province("河南", "豫","华中"));
list.add(new Province("江西", "赣","华东"));
// 添加几本书
list.add(new Book("《史记》","司马迁"));
list.add(new Book("《三国志》","陈寿"));
// 获取迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
// 集合中的元素都被提升为Object对象了
Object obj = iterator.next();
// 强制转换为子类
Province province = (Province) obj; // 不可以将Book转换为Province(存在隐藏问题)
// 调用子类特有的功能
province.intro();
}
}
}
运行结果:
上述代码编译时是没有什么问题,但运行时出现了类型转换异常:ClassCastException
,代码存在一定的安全隐患;
1.2.2 使用泛型
- 查看List源码:
public interface List<E> extends Collection<E> {
boolean add(E e);
....
}
集合的定义者发现,无法在定义集合类时就确定该集合存储的具体类型,因此使用泛型进行占位,使用者创建集合时明确该泛型的类型;
- 指定泛型后:
class List<Province>{
boolean add(Province province);
....
}
Tips:在创建对象时指定泛型的类型,泛型一旦指定了具体的类型,原来泛型的占位符(E),都将变为此类型;
如何指定泛型类类型?
格式如下:
List<泛型的具体类型> list=new ArrayList();
测试类:
package com.dfbz.demo01;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03_使用泛型来存储元素 {
public static void main(String[] args) {
// 创建集合,并确定好泛型(存储的类型)
List<Province> list = new ArrayList();
// 往集合存储对象
list.add(new Province("甘肃", "甘|陇","西北"));
list.add(new Province("陕西", "陕|秦","西北"));
list.add(new Province("贵州", "贵|黔","西南"));
list.add(new Province("云南", "云|滇","西南"));
list.add(new Province("四川", "川|蜀","西南"));
// 获取City类型的迭代器
Iterator<Province> iterator = list.iterator();
while (iterator.hasNext()){
// 直接获取province类型
Province province = iterator.next();
province.intro();
}
}
}
1.3 泛型类
1.3.1 泛型类的使用
很明显,Collection、List、Set以及其下的子类都是泛型类,我们根据使用情况也可以定义泛型类;让泛型类的类型延迟到创建对象的时候指定;
- 使用格式:
修饰符 class 类名<代表泛型的变量> {}
例如,API中的List接口:
class List<E>{
boolean add(E e);
....
}
在创建对象的时候确定泛型
例如,List<String> list = new ArrayList<String>();
此时,变量E的值就是String类型,那么我们的类型就可以理解为:
public interface List<String> extends Collection<String> {
boolean add(String e);
...
}
再例如,ArrayList<Integer> list = new ArrayList<Integer>();
此时,变量E的值就是Integer类型,那么我们的类型就可以理解为:
public interface List<Integer> extends Collection<Integer> {
boolean add(Integer e);
...
}
举例自定义泛型类:
package com.dfbz.demo01;
public class GetClass<P> {
// 使用泛型类型,P具体的类型还不明确,等到创建GetClass对象时再明确P的类型
private P p;
public P getC() {
return p;
}
public void setC(P p) {
this.p = p;
}
}
使用:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo04_自定义泛型类 {
public static void main(String[] args) {
// 创建对象,并明确泛型类型为City
GetClass<Province> getClass = new GetClass<>();
// setC(Province province)
getClass.setC(new Province("新疆","新","西北"));
// Province getC()
Province province = getClass.getC();
province.intro();
}
}
1.2.2 泛型类的继承
定义一个父类,带有泛型:
public class 类名<泛型类型> { }
例如,
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Fu<E> {
public void show(E e) {
System.out.println(e.toString());
}
}
使用格式:
- 1)定义类时确定泛型的类型
例如
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro: 在定义子类时就确定好泛型的类型
*/
public class Zi1 extends Fu<Province> {
@Override
public void show(Province province) {
System.out.println("zi1: " + province);
}
}
此时,泛型E的值就是Province类型。
- 2)始终不确定泛型的类型,直到创建对象时,确定泛型的类型
例如
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro: 子类也变为泛型类,泛型类型具体到创建对象的时候再确定
*/
public class Zi2<P> extends Fu<P> {
@Override
public void show(P p) {
System.out.println("zi2: " + p);
}
}
确定泛型:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo05_测试泛型类的继承 {
public static void main(String[] args) {
// 注意: Zi1类以及不是一个泛型类了,不能够指定泛型
// Zi1<Book> zi1=new Zi1(); // 报错
// 创建子类时确定泛型类型
Zi2<Province> zi_a = new Zi2<>();
zi_a.show(new Province("青海", "青", "西北"));
Zi2<Book> zi_b = new Zi2<>();
zi_b.show(new Book("《说岳全传》", "钱彩"));
}
}
1.4 泛型方法
泛型类是在创建类时定义泛型类型,在创建对象时确定泛型类型;泛型方法则是在创建方法是定义泛型类型,在调用方法时确定泛型类型;
- 定义格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){}
例如:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class GetClassMethod {
// 定义泛型方法
public <P> P GetClass(P p) {
return p;
}
}
使用格式:调用方法时,确定泛型的类型
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo06_测试泛型方法 {
public static void main(String[] args) {
GetClassMethod getClassMethod = new GetClassMethod();
// Province getClass(Province province)
Province province = getClassMethod.GetClass(new Province("西藏", "藏", "西北"));
province.intro();
// Book getClass(Book book)
Book book = getClassMethod.GetClass(new Book("《天龙八部》", "金庸"));
book.detail();
}
}
1.5 泛型通配符
1.5.1 通配符的使用
1)参数列表带有泛型
泛型在程序运行中全部会被擦除,我们把这种现象称为泛型擦除;但编译时期在使用泛型类进行方法传参时,不仅要匹配参数本身的类型,还要匹配泛型的类型;
Number是所有数值类的父类:
- 定义一个泛型类:
package com.dfbz.泛型通配符;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class TestClass<E> {
private E e;
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
}
- 测试带有泛型的方法参数列表:
package com.dfbz.泛型通配符;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_方法参数带有泛型的问题 {
public static void main(String[] args) {
TestClass<Number> t1 = new TestClass<>();
test1(t1);
TestClass<Integer> t2 = new TestClass<>();
// 符合方法的参数类型(TestClass),但不符合泛型类型(Number),编译报错
// test1(t2);
}
public static void test1(TestClass<Number> testClass) {
// 可以获取具体的泛型对象
Number number = testClass.getE();
System.out.println("test1...");
}
/*
和上面的方法冲突了,因为泛型在运行期间会被擦除
相当于: public static void test1(TestClass testClass)
*/
/*
public static void test1(TestClass<Integer> testClass) {
System.out.println("test1...");
}
*/
}
2)泛型通配符
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
- 示例:
package com.dfbz.泛型通配符;
import com.dfbz.demo01.Book;
import com.dfbz.demo01.Province;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_泛型通配符的使用 {
public static void main(String[] args) {
TestClass<String> t1 = new TestClass<>();
test1(t1);
TestClass<Integer> t2 = new TestClass<>();
test1(t2);
TestClass<Number> t3 = new TestClass<>();
test1(t3);
TestClass<Province> t4 = new TestClass<>();
test1(t4);
TestClass<Book> t5 = new TestClass<>();
test1(t5);
}
// ?: 代表可以接收任意泛型
public static void test1(TestClass<?> testClass) {
// 被?接收的泛型都将提升为Object
Object obj = testClass.getE();
System.out.println("test1...");
}
}
Tips:需要注意的是,被<?>接收过的类型都将提升为Object类型;
1.5.2 泛型上下边界
利用泛型通配符?
可以接收任意泛型,但是随之而然带来一个问题,就是所有类型都提升为Object;范围太广了,使用起来非常不方便,因此为了让泛型也可以利用多态的特点,泛型的上下边界的概念由此引出;
利用泛型的通配符可以指定泛型的边界;
泛型的上限:
- 格式: 类型名称
<? extends 类>
对象名称 - 含义: 只能接收该类型及其子类
- 功能: 在使用时,可以使用上边界类来接收泛型类型。因为能够传递进来的都是上边界的子类;因此可以使用上边界类来接收泛型类型;
泛型的下限:
- 格式: 类型名称
<? super 类>
对象名称 - 含义: 只能接收该类型及其父类型
- 功能: 在使用时,只能使用Object类型来接收泛型类型。因为传递进来的必定是下边界的父类,而下边界的父类可以有N多个,因此只能使用Object来接收泛型类型;
可以看到基本数据类型的包装类都是继承与Number类;
- 测试泛型上下边界:
package com.dfbz.泛型通配符;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03_泛型的上下边界 {
public static void main(String[] args) {
List<A> list1 = new ArrayList();
List<B> list2 = new ArrayList();
List<C> list3 = new ArrayList();
List<Object> list4 = new ArrayList();
// method1(list1); // 不能
// method1(list2); // 能
// method1(list3); // 能
// method1(list4); // 不能
// method2(list1); // 能
// method2(list2); // 能
// method2(list3); // 不能
// method2(list4); // 能
}
public static void method1(List<? extends B> list) {
// 上边界的好处: 可以使用上边界的对象
for (B b : list) {
System.out.println(b);
}
}
public static void method2(List<? super B> list) {
// 下边界则必须使用Object来接收
for (Object obj : list) {
System.out.println(obj);
}
}
}
class A {
}
class B extends A {
}
class C extends B {
}
1.6 泛型的擦除
Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程称为类型擦除。
1.6.1 限制擦除
泛型在擦除过程中有限制擦除和无限制擦除;
- 有限制擦除:
- 无限制擦除:将泛型类型提升为Object类型
1.6.2 泛型的桥接方法
在接口使用泛型时,在运行期间接口中的泛型也一样会被擦除;
那如果编写了实现类来实现这个泛型接口,实现类中的泛型在运行期间也会被擦除,这样一来就会出现接口的方法并没有在实现类中得到实现:
好在JVM进行了特殊处理,如果我们编写的类实现了一个带有泛型的接口时,在运行期期间JVM会在实现类中帮我们自动的生产一个方法来帮助我们实现泛型接口中被擦除过后的那个方法,这个方法被称为桥接方法;
- 如图所示: