Java学习之==>泛型
一、什么是泛型
泛型,即“参数化类型”,在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
二、Java中为何要引入泛型
因为继承和多态的出现,在操作一些容器类时,需要大量的对象类型判断。先来看看下面这两段代码:
public class User {
private Integer id;
private String name;
private Integer age;
private User() {
return;
}
private User(String name) {
this();
this.name = name;
}
private User(Integer id, String name) {
this(name);
this.id = id;
}
private User(Integer id, String name, Integer age) {
this(id,name);
this.age = age;
}
public static User of() {
return new User();
}
public static User of(String name) {
return new User(name);
}
public static User of(Integer id, String name) {
return new User(id, name);
}
public static User of(Integer id, String name, Integer age) {
return new User(id, name, age);
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
User
public class Demo01 {
public static void main(String[] args) {
List list = new ArrayList();
// 插入User对象
list.add(User.of(1,"大叔",40));
list.add(User.of(1,"木木",30));
// 插入其他类型的对象
list.add(123);
list.add("abb");
printUser(list);
}
public static void printUser(List list) {
for (int i = 0; i < list.size(); i++) {
User user = (User) list.get(i);
System.out.println(user);
}
}
}
printUser() 方法目的是遍历打印User对象,但在 main 方法中除了可以插入 User 对象,也可以插入其他类型的对象,执行的时候必然会报错。然后我们就得在代码中加入如下判断:
public static void printUser(List list) {
for (int i = 0; i < list.size(); i++) {
Object obj = list.get(i);
if (obj instanceof User) {
User user = (User) obj;
System.out.println(user);
}
}
}
这样写代码的话就非常麻烦,代码中需要添加很多判断,使用起来非常不方便,这样泛型就应运而生了,我们只需要把代码改成如下这种形式:
public class Demo01 {
public static void main(String[] args) {
// 这里使用泛型的作用是:list里面只能装User对象
List<User> list = new ArrayList<>();
// 插入User对象
list.add(User.of(1,"大叔",40));
list.add(User.of(1,"木木",30));
// 如果上面List不使用泛型,这里编译时不会报错,运行时才会报错
// list.add("Hello");
// list.add("World");
printUser(list);
}
public static<T> void printUser(List<T> list) {
for (int i = 0; i < list.size(); i++) {
Object obj = list.get(i);
System.out.println(obj);
}
}
}
从以上代码可以看出,有了泛型以后,我们在代码中只需要在定义容器时给它指定一种类型,那么这个容器就只能存放该类型的对象,在业务代码中就不再需要对对象的类型进行判断,简化了很多代码的编写。泛型还可以认为是一种约定,为了使用方便,约定一个容器中只能存放某一种类型的对象。
三、泛型的使用
泛型有三种使用方式,分别是:泛型类、泛型接口和泛型方法。
1、泛型类
泛型类用于类的定义当中,通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
/**
* 此处 K,V 可以随便写为任意标识,常见的如K、V、T、E等形式的参数常用于表示泛型
* 在实例化泛型类时,必须指定T的具体类型,如:String、Integer等。
*/
public class Generic<K, V> { /**
* key,val这个成员变量的类型分别为 K,V 这两个类型由外部指定
*/
private K key;
private V val; /**
* 泛型类的构造方法的形参 k和v 的类型也为 K,V,同样由外部指定
*/
public Generic(K k, V v) {
this.key = k;
this.val = v;
} /**
* 泛型中普通方法的返回值类型 K,V 同样由外部指定
* 注意:以下这种不是泛型方法
*/
public K getKey() {
return key;
}
public V getVal() {
return val;
}
}
泛型类

但是,在使用泛型类时就一定要传入类型实参吗?在语法上是不一定的,可以不传,但是使用时最好按照约定传递,否则我们定义泛型类就没有意义了。如果不传入类型实参的话,就可以往容器内添加任何类型的对象,这样还说得在业务代码种进行类型判断。
泛型类的使用还有一种方式,使用 extends 和 super 关键字来限制我们使用传入参数的类型,如下:
/**
* 此处 V extends Person 限制了 V 的类型只能使用 Person和它的子类
*/
public class Generic<K, V extends Person> { private K key;
private V val; public Generic(K k, V v) {
this.key = k;
this.val = v;
} public K getKey() {
return key;
}
public V getVal() {
return val;
}
}

2、泛型接口
泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:
public interface Generic<K, V> {
public K test01();
public V test02();
}
当实现泛型接口的类,未传入泛型实参时:
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:public class testGeneric<K, V> implements Generic<K, V>
* 如果不声明泛型,如:public class testGeneric implements Generic,编译器会报错
*/
public class testGeneric<K, V> implements Generic<K, V> { @Override
public K test01() {
return null;
} @Override
public V test02() {
return null;
}
}
当实现泛型接口的类,传入泛型实参时:
/**
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
*/
public class testGeneric<String, Integer> implements Generic<String, Integer> { @Override
public String test01() {
return null;
} @Override
public Integer test02() {
return null;
}
}
3、泛型方法
在java中,泛型类的定义非常简单,但是泛型方法就比较复杂了。
泛型类,是在实例化类的时候指明泛型的具体类型。泛型方法,是在调用方法的时候指明泛型的具体类型 。
public class Generic<K, V> {
private K k;
private V v;
/**
* 泛型方法
* public 与 返回值中间的 <T> 非常重要,可以理解为声明此方法为泛型方法。
* <T>表明该方法将使用泛型类型 T,此时才可以在方法中使用泛型类型 T。
* 与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
* 此处的 T 与上面泛型类定义的 K 和 V 没有任何关系,可以一样,也可以不一样
* 泛型方法可以在泛型类种定义,也可以在普通类当中定义
*/
public <T> T getType(T type){
return type;
}
/**
* 静态泛型方法
*/
public static <S, T> Generic<S, T> of() {
/**
* 类型推断
* return new Generic<S, T>() -> return Generic Pair<>()
*/
return new Generic<>();
}
/**
* 返回值或者参数带有泛型的不是泛型方法
*/
public K getK() {
return k;
}
public V getV() {
return v;
}
}
泛型方法能使方法独立于类而产生变化,如果能做到,你就该尽量使用泛型方法。
泛型方法中同样支持使用 extends 和 super 关键字来限制我们使用传入参数的类型,如下:
public class testGeneric {
public static void main(String[] args) {
Person person = null;
Str str = null;
User user = null;
getType(person);
getType(str);
// User不是Person或其子类,所以会报错
getType(user);
}
/**
* 泛型方法
*/
public static <T extends Person> T getType(T type){
return type;
}
}
User不是Person或其子类,所以会报错。Str是Person的子类,所以不会报错。
4、泛型通配符
泛型通配符只能作为方法的形参使用,如下:
public class App {
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("hello");
list1.add("world");
List<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
print(list1);
print(list2);
}
public static void print(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
}
?代表可以接收任何类型。通配符同样可以通过 extends 和 super 关键字来限制接收的类型
public class App {
public static void main(String[] args) {
List<Person> list1 = new ArrayList<>();
list1.add(new Person());
list1.add(new Person());
List<Str> list2 = new ArrayList<>();
list2.add(new Str());
list2.add(new Str());
List<User> list3 = new ArrayList<>();
list3.add(new User());
list3.add(new User());
print(list1);
print(list2);
print(list3); // 报错
}
public static void print(List<? extends Person> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
}
四、类型擦除
泛型是 Java 1.5 版本才引进的概念,在这之前没有泛型的概念,但泛型代码能够很好地和之前版本的代码很好地兼容,是因为:泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。通俗地讲,泛型类和普通类在 Java 虚拟机内是没有什么特别的地方。我们来看看下面这段代码:
public class App {
public static void main(String[] args) {
List<Person> list1 = new ArrayList<>();
list1.add(new Person());
List<User> list2 = new ArrayList<>();
list2.add(new User());
System.out.println("list1 = " + list1.getClass());
System.out.println("list2 = " + list2.getClass());
System.out.println(list1.getClass() == list2.getClass());
}
}
运行结果:

显然,List<Person> 和 List<User> 在虚拟机中指向的类都是 ArrayList ,泛型信息被擦除了。
在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 <T> 则会被转译成普通的 Object 类型,如果指定了上限如 <T extends String>则类型参数就被替换成类型上限 String。
类型擦除带来的局限性:
类型擦除,它会抹掉很多继承相关的特性,这是它带来的局限性。理解类型擦除有利于我们绕过开发当中可能遇到的雷区,同样理解类型擦除也能让我们绕过泛型本身的一些限制。

正常情况下,因为泛型的限制,编译器不让最后一行代码编译通过,因为类似不匹配。但是,基于对类型擦除的了解,利用反射,我们可以绕过这个限制。
public interface List<E> extends Collection<E>{
boolean add(E e);
}
上面是 List 和其中的 add() 方法的源码定义。
因为 E 代表任意的类型,所以类型擦除时,add 方法其实等同于:
boolean add(Object obj);
那么,利用反射,我们绕过编译器去调用 add 方法
public class App {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(123);
try {
Method method = list.getClass().getDeclaredMethod("add", Object.class);
method.invoke(list,"abc");
method.invoke(list,55.5f);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
for (Object obj : list) {
System.out.println(obj);
}
}
}
运行结果是:

可以看到,根据类型擦除的原理,使用反射的手段就绕过了正常开发中编译器不允许的操作限制。
注意:
- 泛型类或泛型方法中,不接受8中基本数据类型;
- 需要使用他们的包装类;
Java学习之==>泛型的更多相关文章
- Java学习之——泛型
1.概要 generics enable types (classes and interfaces) to be parameters when defining classes, interfac ...
- 5 Java学习之 泛型
1. 基本概念 泛型是Java SE 1.5的新特性,泛型的本质是 参数化类型 ,也就是说所操作的 数据类型 被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为 ...
- Java学习笔记--泛型
一个泛型类就是具有一个或者多个类型变量的类. 我们可以只关注泛型,而不会为数据存储的细节而烦恼 . java泛型(一).泛型的基本介绍和使用 http://blog.csdn.net/lonelyro ...
- Java学习笔记——泛型
假定T不仅要指定接口的类继承.使用下面的方式: public class some<T extends Iterable<T> & Comparable<T>&g ...
- Java学习点滴——泛型
基于<Java编程思想>第四版 前言 虽然Java的泛型在语法上和C++相比是类似的,但在实现上两者是全然不同的. 语法 Java只需要一个<>就可定义泛型.在<> ...
- JAVA学习之泛型
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及的术语:1.整个ArrayList<E>称为泛型类型 2.ArrayList< ...
- Java学习之泛型和异常
泛型 1,设计原则或目的:只要代码在编译的时候没有错误,就不会抛异常. 2,泛型通配符 :类型通配符一般是使用 ? 代替具体的类型实参.注意了,此处是类型实参,而不是类型形参!相当于(父类作用)L ...
- Java学习_泛型
什么是泛型. Java标准库提供的ArrayList内部就是一个Object[]数组,配合存储一个当前分配的长度,就可以充当"可变数组". public class ArrayLi ...
- Thinking in Java学习笔记-泛型和类型安全的容器
示例: public class Apple { private static long counter; private final long id = counter++; public long ...
随机推荐
- service与pod关联
当我们创建pod时,仅仅是创建了pod,要为其创建rc(ReplicationController),他才会有固定的副本,然后为其创建service,集群内部才能访问该pod,使用 NodePort ...
- Linux运维课程体系大纲
Linux入门: Linux系统管理: Linux服务及安全管理: httpd,lamp,lnmp Cache:memcached,varnish(缓存系统) ...
- Circle HDU - 6550 (数学)
在半径为 1 的圆上有 n 个点,它们也是圆的 n 等分点,将每个相邻的 n 等分点相连,组成了一个正 n边形,现在你可以在圆上再增加一个点,使得新的 n + 1 边形的面积最大,请输出最大面积. I ...
- 安装tidb数据库
1.下载压缩包 安装tar包路径 命令:wget http://download.pingcap.org/tidb-latest-linux-amd64.tar.gz 命令:wget http://d ...
- noi.ac NA537 【Graph】
本来以为过了...然后FST了... 吐槽:nmdGraph为什么不连通... 这题想法其实非常\(na\ddot{\imath}ve\),就是对于一个连通块先钦点一个点为根,颜色是\(1\),考虑到 ...
- poj2279 Mr. Young's Picture Permutations[勾长公式 or 线性DP]
若干人左对齐站成最多5行,给定每行站多少个,列数从第一排开始往后递减.要求身高从每排从左到右递增(我将题意篡改了便于理解233),每列从前向后递增.每个人身高为1...n(n<=30)中的一个数 ...
- C# socket 与网页通讯
class Program { static Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, Pr ...
- install oh my zsh on ubuntu 16.04
first,install zsh #安装zsh sudo apt-get install zsh #是否安装成功 cat /etc/shells #/bin/sh #/bin/bash #/bin/ ...
- Unity3D_(数据)LitJson创建和解析Json
LitJson github: 传送门 JsonUtility创建和解析Json 传送门 LitJson.dll百度云盘 传送门 密码:p1py 加载LitJson.dll到Unity中 在Asset ...
- 半径R覆盖最多点
struct point { double x, y; }; point p[N]; struct alpha { double v; bool flag; bool friend operator ...