Java中泛型使用的简单方法介绍

一. 泛型是什么

“泛型”,顾名思义,“泛指的类型”。我们提供了泛指的概念,但具体执行的时候却可以有具体的规则来约束,比如我们用的非常多的ArrayList就是个泛型类,ArrayList作为集合可以存放各种元素,如Integer, String,自定义的各种类型等,但在我们使用的时候通过具体的规则来约束,如我们可以约束集合中只存放Integer类型的元素,如List<Integer> iniData = new ArrayList<>()。

二. 使用泛型有什么好处

以集合来举例,使用泛型的好处是我们不必因为添加元素类型的不同而定义不同类型的集合,如整型集合类,浮点型集合类,字符串集合类,我们可以定义一个集合来存放整型、浮点型,字符串型数据,而这并不是最重要的,因为我们只要把底层存储设置了Object即可,添加的数据全部都可向上转型为Object。 更重要的是我们可以通过规则按照自己的想法控制存储的数据类型。

我们以ArrayList为例,假如我们要将本月截至今天的日期放到ArrayList中,如果我们不使用泛型,此时我们定义一个ArrayList.

List monthDays = new ArrayList();

我们向其中加入1号到4号日期

public static List addMonthDays(){
 List monthDays = new ArrayList();
 monthDays.add(LocalDate.now().withDayOfMonth(1));
 monthDays.add(LocalDate.now().withDayOfMonth(2));
 monthDays.add(LocalDate.now().withDayOfMonth(3));
 monthDays.add(new Date());
 return monthDays;
}

这样有没有问题?大家也看出来了,当然有,虽然都可以表示日期,但却用了Date,LocalDate,我们调用方法直接打印出来,就是这样

public static void main(String[] args) {
 List monthDays = addMonthDays();
 for(Object day : monthDays){
  System.out.println(day);
 }
}

2019-08-01
2019-08-02
2019-08-03
Sun Aug 04 10:27:10 CST 2019

我们肯定不想要这样的结果,我们想要的是

2019-08-01
2019-08-02
2019-08-03
2019-08-04

如果存储的元素类型只是这两种(假如我们知道),这个时候我们就手动判断一下

public static void main(String[] args) {
 List monthDays = addMonthDays();
 for(Object day : monthDays){
  if (day instanceof Date){
   Date date = (Date) day;
   System.out.println(LocalDate.of(date.getYear(), date.getMonth(), date.getDay()));
  }else {
   System.out.println(day);
  }
 }
}

这个时候我们就可以达成上述目的了,但大家也知道,这种写法问题问题很大

  • 我们无法控制存储的元素到底是否和日期相关,如我们存储了“1”,65536等非日期,定义的方法也不会报错,但在调用进行类型转换的时候必然会报错;
  • 我们不知道集合中到底存储了哪些类型的元素,比如还有“2019/08/04”这种日期字符串、java.sql.Date这种类型呢,我们很难保证可以穷尽;
  • 代码过于复杂,太难维护;

这时泛型就提供了很好的解决方案,从源头上就制定好规则,只能添加LocalDate类型,那么上述的问题就都得以解决了。

public static List<LocalDate> addFormatMonthDays(){
 List<LocalDate> monthDays = new ArrayList<>();
 monthDays.add(LocalDate.now().withDayOfMonth(1));
 monthDays.add(LocalDate.now().withDayOfMonth(2));
 monthDays.add(LocalDate.now().withDayOfMonth(3));
 monthDays.add(new Date());//这个代码在编译期间就无法通过了
 return monthDays;
}

不仅提高了代码的可读性,一眼即可看出我们存储的是LocalDate类型。同时编译器也可很好的利用该信息,编译期间就可进行类型检查,保证了安全性,在get的时候,无需进行强制类型转换。

三. 泛型类

为使类适应更多的情况,具备更好的扩展性,我们可以将其设置为泛型类,即具有一个或多个类型变量的类,写法如下:

public class ClassName<泛型标识,可以是字母、中文字符等,不过一般用大写英文字符> {
  private 泛型标识 property;
}

对于泛型标识符,一般有一些约定俗称的写法,如果是表示集合的元素类型,一般用字母E,如我们常用的ArrayList,public class ArrayList<E>,我们自定义如下:

//车库
public class Garage<E> {
  //向车库中添加车
  public void add(E car){
    //...
  }
}

我们还可以用字符K和V表示关键字和值的类型,如我们常用的HashMap,public class HashMap<K,V>,我们也可以自定义如下:

//映射关系
//K,V: 蔬菜,是否有机; 水果,产地; 服装,类型; 汽车,品牌
public class Mapping<K, V> {

  private K key;

  private V value;
}

我们还经常用一个字符T表示类型

public class Person<T> {
  private T t;
  public Person(T t){
    this.t = t;
  }
  public void run(){
    System.out.println(t);
  }
}

如何使用泛型类,类型如何实例化,我们只需要保证传入的实参类型和泛型参数类型相同即可。

//正常用法
Person<String> s1 = new Person<String>("张三");
//jdk7.0后可以省略后面的参数类型
Person<String> s2 = new Person<>("张三");
s2.run();

//当然泛型的定义是可以帮助我们按照某种规则去做事,如果不做限制,也不会编译错误,但泛型就毫无意义了
Person t = new Person(111);
t.run();

//泛型的类型不能是八大基本类型,下面会编译出错
Person<int> s = new Person<>(1);

四. 泛型接口

泛型接口和泛型类的定义基本一致,定义如下:

public interface Person<T> {
  public T parent();
  public String eat();
}

当我们定义了一个类要实现该接口时,那么该类的泛型类型必须和接口类的泛型类型一致,未传递实参的情况下,继续使用泛型类型T,传递了实参的情况下,泛型类型必须使用实参类型

public class Teacher<T> implements Person<T> {
  @Override
  public T parent() {
    return null;
  }

  @Override
  public String eat() {
    return null;
  }
}
//Teacher不必再定义类型了,因为泛型类型在Person处已经定义好了
public class Teacher implements Person<Integer> {
  //这里的返回类型必须为Integer,否则必须出错
  @Override
  public Integer parent() {
    return null;
  }

  @Override
  public String eat() {
    return null;
  }
}

五. 泛型方法

泛型方法可以定义在普通类和泛型类中,比如泛型类更为常用,一般能用泛型方法解决的问题优先使用泛型方法而不使用泛型类,类型变量放在修饰符的后面,如public static ,public final等的后面。

public class Teacher {
  public static <T> T println(T t){
    System.out.println(t);
    return t;
  }
}

调用很简单,很一般方法调用是一样的,更方便的是类型不像一般方法做了限定。

String s = Teancher.println("str");

另外需要说明的是,定义在泛型类中的泛型方法的泛型变量之间是没有关系的,如这样的代码

public class Teacher<T> {
  T teacher;
  public Teacher(T t){
    this.teacher = t;
  }
  public <T> T println(T t){
    System.out.println(t);
    return t;
  }
}
Teacher<String> teacher = new Teacher<>("张三");
Integer in = teacher.println(123456);

类泛型类型为String,方法的泛型类型为Integer,虽然都是用T来表示的。

同时关于泛型方法需要说明的是:

在修饰符public xx与方法名之间非常重要,有< T >这样的才算是泛型方法;仅仅使用了泛型变量并不算是泛型方法。

六. 限定类型变量

不论是泛型类还是泛型方法,目前来说其实都是没有做类型限定,无论我们传递什么样类型的变量进去都可以,因为我们在处理逻辑中并没有使用到该类型特有的东西(成员变量、方法等)。假如我们想传递的参数类型仅仅是某个大类(父类)下面的一些小类(子类),那么怎么做呢?

public class ArrayFlag {
  public static <T> T getMax(T[] array){
    if(array == null || array.length == 0){
      return null;
    }
    T maxValue = array[0];
    for(int i = 0; i < array.length; i++){
      if(array[i].compareTo(maxValue) > 0){
        maxValue = array[i];
      }
    }
    return maxValue;
  }
}

大家也看到了我们在getMax方法中使用了compareTo方法进行比较,但如果我们传入的类型T没有compareTo方法呢,岂不是要报错,因此我们需要做限定,只要限定了是Comparable接口的必然具备compareTo方法,那么改造后就成了这样

public class ArrayFlag {
  public static <T extends Comparable> T getMax(T[] array){
    if(array == null || array.length == 0){
      return null;
    }
    T maxValue = array[0];
    for(int i = 0; i < array.length; i++){
      if(array[i].compareTo(maxValue) > 0){
        maxValue = array[i];
      }
    }
    return maxValue;
  }
}

同时需要说明的是,此处用的是extends关键字,extends在这里是表示的是绑定了Comparable接口及其子类型,是“绑定、限定”的意思,非“继承”的意思,后面也可以是接口或者类,如果有多个限制,可以使用&分隔,如:

public static <T extends Comparable & Serializable> T getMax(T[] array)

七. 泛型通配符

举个例子,定义了一个书籍类和一个小说类

//定义了一个书籍类
public class Book {}
//定义了一个小说书籍类继承书籍类
public class Novel extends Book {}

我们再定义一个书柜类用来装书籍以及小说

//定义了一个书柜类用来装书
public class Bookcase<T> {
  T b;
  public Bookcase(T t){
    this.b = t;
  }
  public void set(T t) {
    b=t;
  }
  public T get() {
    System.out.println(b.getClass());
    return b;
  }
}

下面我们就用书柜来装小说

//以前的写法,无法编译通过,提示Incompatible types, Required Book Found Novel
Bookcase<Book> bc = new Bookcase<Novel>(new Novel());

但在jdk7.0后,new Bookcase的时候是可以不用给出泛型类型的,省略的类型可以从变量的类型推断得出,因此如果下面这种写法

Bookcase<Book> bc = new Bookcase<>(new Novel());
System.out.println(bc.getClass());
bc.get();

此时可以编译通过,我们执行后得出的结果是:

class generic.Bookcase
class generic.Novel

当然我们还可以通过通配符来解决该问题,通配符包括以下几种:

上界通配符、下界通配符、无限定通配符

7.1 上界通配符

上界通配符定义方式如下:用extends 关键字,含义是该书柜只能放置小说类书籍(如什么都市小说、爱情小说、玄幻小说都可以),但不能放置父类书籍、其他类如史书、职场类书籍、财经类书籍等,是在使用的时候进行限定,如:

Bookcase<? extends Book> bc = new Bookcase<Novel>(new Novel());

这种定义方式就不会编译错误了。另外关于上界通配符的特点,对上有限制,根据java多态向上造型的原则,不适合频繁插入数据,适合频繁读取数据的场景。

7.2 下界通配符

下界通配符定义方式如下:用super关键字,含义就是书柜放置设置了下限,我们只能放置Book书籍以及Novel书籍,却无法再将细分的都市小说、爱情小说类书籍放进去

Bookcase<? super Novel> bc = new Bookcase<Novel>(new Novel());

另外关于下界通配符的特点,和上界通配符正好相反,不适合频繁读取数据,适合频繁插入数据的场景。

7.3 无限定通配符

无限定通配符意味着可以使用任何对象,因此使用它类似于使用原生类型。但它是有作用的,原生类型可以持有任何类型,而无限定通配符修饰的容器持有的是某种具体的类型。

举个例子:

List<?> list = new ArrayList<>();
//无法编译通过
list.add(new Object());

//下面这样的却可以添加任何类型
List<Object> list = new ArrayList<>();
list.add(new Object());

再说一下< T > 和< ? >之间的区别,初看好像他们都可以表示泛型变量,都可以extends,但它们确实有不同的使用场景

  • 类型参数< T >声明一个泛型类或泛型方法
  • 无限定通配符< ? >使用泛型类或泛型方法

八. 总结

泛型在java中可以说很常用,我们前面提到的集合类,如ArrayList,HashSet,以及Map都使用到了泛型,泛型也是也是我们再进行一些组件封装经常用到的,本文主要介绍了泛型基本概念,使用泛型的好处,泛型类、接口、方法、通配符的简单介绍以及使用方法,最后泛型一般和反射集合使用,通过泛型可以进行类型的灵活传递,通过反射可获取到实体以及类的数据信息,从而实现一些框架、组件的封装,若有不对之处,请批评指正,望共同进步,谢谢!

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • 解析Java的Jackson库中对象的序列化与数据泛型绑定

    Jackson对象序列化 这里将介绍将Java对象序列化到一个JSON文件,然后再读取JSON文件获取转换为对象.在这个例子中,创建了Student类.创建将有学生对象以JSON表示在一个student.json文件. 创建一个名为JacksonTester在Java类文件在 C:\>Jackson_WORKSPACE. 文件: JacksonTester.java import java.io.File; import java.io.IOException; import org.codeh

  • Java中的泛型详解

    所谓泛型:就是允许在定义类.接口指定类型形参,这个类型形参在将在声明变量.创建对象时确定(即传入实际的类型参数,也可称为类型实参) 泛型类或接口 "菱形"语法 复制代码 代码如下: //定义   public interface List<E> extends Collection<E>    public class HashMap<K,V> extends AbstractMap<K,V>  implements Map<K,V

  • Java编程思想里的泛型实现一个堆栈类 分享

    觉得作者写得太好了,不得不收藏一下. 对这个例子的理解: //类型参数不能用基本类型,T和U其实是同一类型. //每次放新数据都成为新的top,把原来的top往下压一级,通过指针建立链接. //末端哨兵既是默认构造器创建出的符合end()返回true的节点. 复制代码 代码如下: //: generics/LinkedStack.java// A stack implemented with an internal linked structure.package generics; publi

  • 基于java中泛型的总结分析

    要我直接说出泛型是个what我还真讲不出来,这里先由一道问题引入: 定义一个坐标点类,要求能保存各种类型的数据,如:整形,浮点型,和字符串类型 既然变量类型起先不确定,那么很容易想到就是用所有类型的父类,也就是Object类来代替 不废话了,用代码来体现 实例1:用Object来实现不确定的数据类型输入 复制代码 代码如下: //这是定义的坐标点类class Point {    private Object x;    private Object y; //用Object来表示不确定的类型 

  • 多个java泛型示例分享

    1.泛型类 1.1普通泛型 复制代码 代码如下: package test.lujianing;/** * 泛型类 * @param <T> */class Test<T>{    private T obj;    public void setValue(T obj){        this.obj =obj;    }    public T getValue(){        System.out.println(obj.getClass().getName());  

  • Java 获取泛型的类型实例详解

    Java 获取泛型的类型实例详解 Java 泛型实际上有很多缺陷,比如不能直接获取泛型的类型,不能获取带泛型类等. 以下方式是不正确的: ①.获取带泛型的类的类型 Class lstUClazz = List<User>.class ②获取局部变量泛型的类型 List<User> listUser = new ArrayList<User>(); Type genType = listUser.getClass().getClass().getGenericSuperc

  • 浅谈java中定义泛型类和定义泛型方法的写法

    1.方法中的泛型 public static <T> T backSerializable(Class<T> clazz , String path ,String fileName){ FileInputStream fis = null; ObjectInputStream ois = null; Object obj = null; try { fis = new FileInputStream(path + fileName); ois = new ObjectInputS

  • Java中泛型的用法总结

    本文实例总结了Java中泛型的用法.分享给大家供大家参考.具体如下: 1 基本使用 public interface List<E> { void add(E); Iterator<E> iterator(); } 2 泛型与子类 Child是Parent的子类,List<Child>却不是List<Parent>的子类. 因此:List<Object> list = new ArrayList<String>()是错误的. 如果上面

  • java泛型学习示例

    Java泛型(Generics)是JDK5开始引入的一个新特性,允许在定义类和接口的时候使用类型参数(Type Parameter).声明的类型参数在使用时用具体的类型来替换,现在泛型最主要的应用是在JDK5中的新集合类框架中,Map, List均有用到.其中的优点不言而喻,我们可以横向扩展更多的类,缺点呢,其实也就是他的优点,因为这需要我们在使用泛型类的时候,要很清楚自己的代码目地,不能使用错误的类型. 最基本的泛型类 复制代码 代码如下: package com.garinzhang.jav

  • JAVA利用泛型返回类型不同的对象方法

    有时需要在方法末尾返回类型不同的对象,而return 语句只能返回一个或一组类型一样的对象.此时就需要用到泛型. 首先先解释个概念, 元组:它是将一组对象直接打包存储于其中的一个单一对象,这个容器对象允许读取其中元素,但不能修改. 利用泛型创建元组 public class ReturnTwo<A,B> { public final A first; public final B second; public ReturnTwo(A a,B b) { first = a; second = b

随机推荐