一、什么是泛型

  泛型,即“参数化类型”,在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

二、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学习之==>泛型的更多相关文章

  1. Java学习之——泛型

    1.概要 generics enable types (classes and interfaces) to be parameters when defining classes, interfac ...

  2. 5 Java学习之 泛型

    1. 基本概念          泛型是Java SE 1.5的新特性,泛型的本质是 参数化类型 ,也就是说所操作的 数据类型 被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为 ...

  3. Java学习笔记--泛型

    一个泛型类就是具有一个或者多个类型变量的类. 我们可以只关注泛型,而不会为数据存储的细节而烦恼 . java泛型(一).泛型的基本介绍和使用 http://blog.csdn.net/lonelyro ...

  4. Java学习笔记——泛型

    假定T不仅要指定接口的类继承.使用下面的方式: public class some<T extends Iterable<T> & Comparable<T>&g ...

  5. Java学习点滴——泛型

    基于<Java编程思想>第四版 前言 虽然Java的泛型在语法上和C++相比是类似的,但在实现上两者是全然不同的. 语法 Java只需要一个<>就可定义泛型.在<> ...

  6. JAVA学习之泛型

    ArrayList<E>类定义和ArrayList<Integer>类引用中涉及的术语:1.整个ArrayList<E>称为泛型类型 2.ArrayList< ...

  7. Java学习之泛型和异常

    泛型 1,设计原则或目的:只要代码在编译的时候没有错误,就不会抛异常.  2,泛型通配符  :类型通配符一般是使用 ? 代替具体的类型实参.注意了,此处是类型实参,而不是类型形参!相当于(父类作用)L ...

  8. Java学习_泛型

    什么是泛型. Java标准库提供的ArrayList内部就是一个Object[]数组,配合存储一个当前分配的长度,就可以充当"可变数组". public class ArrayLi ...

  9. Thinking in Java学习笔记-泛型和类型安全的容器

    示例: public class Apple { private static long counter; private final long id = counter++; public long ...

随机推荐

  1. pickle和JSON的序列化

    Pickle和JSON的序列化 Python的pickle模块允许我们把对象只节存储成一个特殊的存储格式,它本质上是把一个对象转换成一种可以存储到文件或者类文件对象或者一个字节字符串的格式: > ...

  2. 009(1)-saltstack之salt-ssh的使用及配置管理LAMP状态的实现

    1 salt-ssh的使用 1. 安装salt-ssh[root@slave1 .ssh]# yum install -y salt-ssh 2. 配置salt-ssh # Sample salt-s ...

  3. 多线程与UI操作(一)

    C#中禁止跨线程直接访问控件,InvokeRequired是为了解决这个问题而产生的,当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它. 此时它将会在内部调用n ...

  4. UVA - 11107 Life Forms (广义后缀自动机+后缀树/后缀数组+尺取)

    题意:给你n个字符串,求出在超过一半的字符串中出现的所有子串中最长的子串,按字典序输出. 这道题算是我的一个黑历史了吧,以前我的做法是对这n个字符串建广义后缀自动机,然后在自动机上dfs,交上去AC了 ...

  5. 在哪里查看java的jar包版本?

    jar包根目录里的META-INF目录下的MANIFEST.MF文件里一般有会记录版本信息,可以到这个文件里查看 .

  6. vue - router + iView 的使用(简单例子)

    所使用的工具:谷歌浏览器.Nodejs(自带npm).HBuilder 0.要先安装Nodejs,下载安装即可 0-1.安装vue-cli,打开cmd 输入 npm install -g @vue/c ...

  7. 【转载】awk入门

    作者: 阮一峰 http://www.ruanyifeng.com/blog/2018/11/awk.html awk是处理文本文件的一个应用程序,几乎所有 Linux 系统都自带这个程序. 它依次处 ...

  8. 基于TCP的客户端、服务器端socket编程

    一.实验目的 理解tcp传输客户端服务器端通信流程 二.实验平台 MAC OS 三.实验内容 编写TCP服务器套接字程序,程序运行时服务器等待客户的连接,一旦连接成功,则显示客户的IP地址.端口号,并 ...

  9. 测试网站接口,nginx篇

    nginx是反向代理,怎么通过nginx反向代理要测试接口的线上网站呢. 这里自我提供了一个方法,仅供参考!建议不要用于刷接口等非常规的用途,后果会很严重. 首先 用node express创建一个项 ...

  10. 【leetcode】745. Prefix and Suffix Search

    题目如下: Given many words, words[i] has weight i. Design a class WordFilter that supports one function, ...