泛型(JDK5引入)

概述

  • 泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
  • 泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
  • 是一种把明确类型的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型。
  • 好处
    • 把运行时期的问题提前到了编译期间
    • 避免了强制类型转换
    • 优化了程序设计,解决了黄色警告线

不用泛型的问题

  • 早期的时候,我们使用Object来代表任意的类型。

  • 向上转型是没有任何问题的,但是在向下转型的时候其实隐含了类型转换的问题。

  • 也就是说这样的程序其实并不是安全的。所以Java在JDK5后引入了泛型,提高程序的安全性。

  • 定义类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class ObjectTool {
    private Object obj;

    public Object getObj() {
    return obj;
    }

    public void setObj(Object obj) {
    // Object obj = new Integer(30);要传入基本数据
    this.obj = obj;
    }
    }
  • 使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 正常使用
    ObjectTool ot = new ObjectTool();

    ot.setObj(new Integer(27));
    Integer i = (Integer) ot.getObj();
    System.out.println("年龄是:" + i); //年龄是:27

    ot.setObj(new String("上官柳丝"));
    String s = (String) ot.getObj();
    System.out.println("姓名是:" + s); //姓名是:上官柳丝

    ot.setObj(new Integer(30));
    // ClassCastException
    String ss = (String) ot.getObj();
    System.out.println("姓名是:" + ss); // ClassCastException

泛型引入

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 创建集合
    ArrayList array = new ArrayList();

    // 添加元素
    array.add("hello");
    array.add("world");
    array.add("java");
    array.add(10); // JDK5以后的自动装箱
    // 等价于:array.add(Integer.valueOf(10));

    // 遍历
    Iterator<String> it = array.iterator();
    while (it.hasNext()) {
    // ClassCastException
    // String s = (String) it.next();
    String s = it.next();
    System.out.println(s);
    }
  • ClassCastException解释

    • 因为我们开始存储的时候,存储了String和Integer两种类型的数据。
    • 在遍历的时候,我们把他们都当做String类型处理,所以报错
    • 但是呢,它在编译期间却没有告诉我们集合中放的是字符串。
  • 如果集合也模仿着数组的这种做法,在创建对象的时候明确元素的数据类型。这样就不会在有问题了。而这种技术被称为:泛型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 创建元素对象
    ArrayList<Student> array = new ArrayList<Student>();

    Student s1 = new Student("曹操", 40); // 后知后觉
    Student s2 = new Student("蒋干", 30); // 不知不觉
    Student s3 = new Student("诸葛亮", 26);// 先知先觉

    // 添加元素,如果不是Student类型会编译错误
    array.add(s1);
    array.add(s2);
    array.add(s3);

    // 遍历
    Iterator<Student> it = array.iterator();
    while (it.hasNext()) {
    // 避免强制类型转换:不需要从Object类型转换Student
    Student s = it.next();
    System.out.println(s.getName() + "---" + s.getAge());
    }
    System.out.println("------------------");

    for (int x = 0; x < array.size(); x++) {
    Student s = array.get(x);
    System.out.println(s.getName() + "---" + s.getAge());
    }
  • 泛型解决的问题:

    • 向集合中添加对象元素的时候,没有对元素的类型进行检查,也就是说,我们往集合中添加任意对象,编译器都不会报错。
    • 当我们从集合中获取一个值的时候,我们不能都使用Object类型,需要进行强制类型转换。而这个转换过程由于在添加元素的时候没有作任何的类型的限制跟检查,所以容易出错。例如上面代码中的:String s = (String) it.next();

泛型使用位置

  • 通过看API,如果类,接口,抽象类后面跟的有<E>就说要使用泛型。一般来说就是在集合中使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 用ArrayList存储字符串元素,并遍历。用泛型改进代码
    ArrayList<String> array = new ArrayList<String>();
    //添加
    array.add("hello");
    array.add("world")'
    //遍历
    Iterator<String> it = array.iterator();
    while(it.hasNext()){
    String s = it.next();
    System.out.println(s);
    }
    System.out.println("-----------------");
    for (int x = 0; x < array.size(); x++) {
    String s = array.get(x);
    System.out.println(s);
    }

泛型推断:JDK7的新特性

  • 只要编译器从上下文中能够推断出类型参数,你就可以使用一个空的类型参数集合 (<>)代替调用一个泛型类的构造器所需要的类型参数。 这对尖括号通常叫做 diamond.

  • 在Java SE 7中, 你可以使用一个空的类型参数集合 (<>)代替构造器的参数化类型:

    1
    2
    3
    4
    Map<String, List<String>> myMap = new HashMap<>();
    // 想要在泛型类初始化期间利用自动类型推断,你必须要指定 diamond
    // unchecked conversion warning
    Map<String, List<String>> myMap = new HashMap();
  • Java SE 7对于实例创建的类型推断的支持是有限的; 从上下文来看,只有构造器的参数化类型是明显的才能使用类型推断。 例如, 下面的例子编译不通过:

    1
    2
    3
    4
    5
    6
    7
    List<String> list = new ArrayList<>();
    list.add("A");

    // The following statement should fail since addAll expects
    // Collection<? extends String>

    list.addAll(new ArrayList<>());

    注意: diamond通常在方法调用中起作用;然而, 在变量声明时建议首要使用diamond。

    相比之下, 下面的例子可以编译通过:

    1
    2
    3
    4
    // The following statements compile:

    List<? extends String> list2 = new ArrayList<>();
    list.addAll(list2);

构造器类型推断

  • 注意: 在泛型类和非泛型类中,构造器都可以是泛型的 (换句话说, 声明它们自己的形式参数):

    1
    2
    3
    4
    5
    class MyClass<X> {
    <T> MyClass(T t) {
    // ...
    }
    }
  • 考虑以下 MyClass类的初始化,在Java SE 7以及之前的版本中都有效:

    • 这个语句创建一个参数化类型 MyClass<Integer>的一个实例; 它显式的为泛型类 MyClass<X>指定 Integer 类型作为形式参数X
    • 注意, 这个泛型类的构造器包含一个形式参数。编译器推断这个泛型类的构造器的形式参数T的类型为 String (因为这个构造器的实际参数是一个 String 对象)。
    1
    new MyClass<Integer>("")
  • 在Java SE 7之前,和泛型方法一样,编译器能够推断泛型构造器的实际参数。然而在 Java SE 7中,如果你使用diamond (<>),编译器能够推断被实例化的泛型类的实际参数 。考虑下面的例子,在Java SE 7以及之后的版本中都有效:

    • 在这个例子中,编译器推断泛型类 MyClass<X> 的形式参数 X的类型为 Integer
    • 并且推断这个泛型类的构造器的形式参数T的类型为 String .
    1
    MyClass<Integer> myObject = new MyClass<>("");

小结

class Point<T>{}:这是一个泛型类

  • <T>: 形式类型参数: 参数可以是任意类型

  • Point<具体的类型参数>:参数化类型,应用时,一个类型后边指定一定具体的类型参数Point<Integer>

    • 参数化类型:表面是声明,背后是约定
  • jdk7类型推断,使用菱形语法Point<Integer> point = new Point<>();

  • 原生类型:使用的时候,类型后面没有指定具体类型 Point point = new Point();

  • 类型参数: 可以定义多个类型参数,用逗号分隔 Point<K , V>

  • 规范:

    • 使用一个大写字母

      1
      2
      3
      4
      1. Type T
      2. Element E
      3. Key K
      4. Value v
  • 格式

    1
    2
    3
    4
    5
    class 类名<泛型类型标识>{
    访问修饰符 <泛型类型> 属性;
    访问修饰符 <泛型类型> 方法(){}
    访问修饰符 <泛型类型> 方法(泛型类型 参数){}
    }

泛型的使用

泛型类

  • 把泛型定义在类上

  • 格式:public class 类名<泛型类型1,…>

  • 注意:泛型类型必须是引用类型,基本类型需要使用类型的包装类来解决该问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class GenericTest {
    public static void main(String[] args) {
    swap(new String[]{"111","222"},0,1);//编译通过

    //swap(new int[]{1,2},0,1);
    //编译不通过,因为int不是引用类型

    swap(new Integer[]{1,2},0,1);//编译通过
    }

    /*交换数组a 的第i个和第j个元素*/
    public static <T> void swap(T[]a,int i,int j){
    T temp = a[i];
    a[i] = a[j];
    a[j] = temp;
    }
    }
  • 示例:二个属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class Contacts<K, V> {
    private K id;
    private V Tel;

    public Contacts(K id, V tel) {
    this.id = id;
    Tel = tel;
    }

    public K getId() { return id;}

    public void setId(K id) { this.id = id;}

    public V getTel() { return Tel; }

    public void setTel(V tel) { Tel = tel;}

    }

    public class Demo3 {
    public static void main(String[] args) {
    Contacts<?,String> contacts = new Contacts(1, "123456");
    System.out.println(contacts.getId()+"---"+contacts.getTel());
    }
    }
  • 示例:一个属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //定义泛型类
    public class ObjectTool<T> {
    private T obj;

    public T getObj() {
    return obj;
    }

    public void setObj(T obj) {
    this.obj = obj;
    }
    }
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 创建泛型类对象
    ObjectTool<String> ot = new ObjectTool<String>();

    // ot.setObj(new Integer(27)) //报错。编译期间就过不去

    ot.setObj(new String("上官柳丝"));
    String s = ot.getObj();
    System.out.println("姓名是:" + s); //姓名是:上官柳丝

    -------------------------------------------------------

    ObjectTool<Integer> ot2 = new ObjectTool<Integer>();
    //ot2.setObj(new String("上官柳丝"));//这个时候编译期间就过不去
    ot2.setObj(new Integer(27));
    Integer i = ot2.getObj();
    System.out.println("年龄是:"+ i); //年龄是:27

泛型构造器

  • 使用泛型构造器的时候,不要使用菱形语法

  • 定义:

    1
    2
    3
    <E>  类名(E e){
    //代码块
    }
  • 使用:

    • 参数化类型 引用名 = new 泛型类名<>();
    • 参数化类型 引用名 = new <E>泛型类名<>(); //会报错
    • 参数化类型 引用名 = new <E>泛型类名<具体类型>();
  • 例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //定义泛型构造器
    public <E> Demo(E e){
    System.out.println(e);
    }

    // 类型推断,根据参数推断
    Demo<String> demo = new Demo<>(123);
    //由123 得到<E> 是 Integer

    Demo<String> demo2 = new <Integer> Demo<>(12); //报错,不使用菱形语法
    Demo<String> demo2 = new <Integer> Demo<String>("12"); //报错
    //给定了<E>是Integer ,传入字符串"12"会报错

    Demo<String> demo2 = new <Integer> Demo<String>(12); //编译通过
    Demo<String> demo3 = new <String > Demo<String>("12"); //编译通过

泛型方法

  • 把泛型定义在方法上

  • 格式: public <泛型类型> 返回类型 方法名(泛型类型 .)

  • 我们如果定义为泛型类,想要输出不同格式:要创建不同的对象

    1
    2
    3
    4
    5
    6
    7
    8
    ObjectTool<String> ot = new ObjectTool<String>();
    ot.show("hello");

    ObjectTool<Integer> ot2 = new ObjectTool<Integer>();
    ot2.show(100);

    ObjectTool<Boolean> ot3 = new ObjectTool<Boolean>();
    ot3.show(true);
  • 我们可以将泛型定义在方法上面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class ObjectTool(){
    public <T> void show(T t){
    System.out.println(t);

    }
    }

    // 使用:
    ObjectTool ot = new ObjectTool();
    ot.show("hello");
    ot.show(100);
    ot.show(true);

    结果:
    hello
    100
    true

多个当实参不一致时,T取交集

  • 即第一个共同的父类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class GenericTest {
    public static void main(String[] args) {
    new GenericTest().testType();
    //基本类型有时可以作为实参,因为有自动装箱和拆箱。
    int a = biggerOne(3,5);
    //int 和 double,取交为Number
    Number b = biggerOne(3,5.5);
    //String和int 取交为Object
    Object c = biggerOne("1",2);
    }
    //从x,y中返回y
    public static <T> T biggerOne(T x,T y){
    return y;
    }
    }


    如果用Number b = biggerOne(3,5.5);改为String c = biggerOne(3,5.5);则编译报错:
    Error:(17, 29) java: 不兼容的类型: 推断类型不符合上限
    推断: java.lang.Number&java.lang.Comparable<? extends java.lang.Number&java.lang.Comparable<?>>
    上限: java.lang.String,java.lang.Object

泛型接口

  • 把泛型定义在接口上

  • 格式:public interface 接口名<泛型类型1…>

  • 接口的实现

    • 知道类型的实现:(创建原生类)

      • class 类名 implements 接口名<类型>
    • 不知道类型的实现:(创建泛型类)

      • class 类名<T> implements 接口名<T>
  • 定义泛型接口

    1
    2
    3
    public interface Inter<T> {
    public abstract void show(T t);
    }
  • 实现接口:知道类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //方式一:知道是什么类型
    public class InterImpl implements Inter<String>{

    @Override
    public void show(String t) {
    // TODO Auto-generated method stub
    System.out.println(t);
    }
    }

    // 测试
    Inter<String> i = new InterImpl();
    i.show("hello"); //hello
  • 实现接口:不知道是什么类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //方式二:不知道是什么类型
    public class InterImpl<T> implements Inter<T>{

    @Override
    public void show(T t) {
    System.out.println(t);
    }
    }

    //测试
    Inter<Stirng> i = new InterImpl<String>();
    i.show("hello"); //hello
    i.show(100); //编译期间报错

    Inter<Integer> ii = new InterImpl<Integer>();
    ii.show("hello"); //编译期间报错
    ii.show(100); //100

泛型方法和泛型类的比较

  • 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class B<T>{
    //泛型类的成员方法,该T受A后面的T的限制
    public T memberFunc(T a){
    return a;
    }
    //泛型类的静态方法,这里的T和和类A的T是不同的
    public static <T> T genericFunc(T a){
    return a;
    }
    public static void main(String[] args) {
    //编译不通过
    // java: incompatible types: int cannot be converted to java.lang.String
    // Integer s = new B<String>().memberFunc(1);

    //编译通过
    Integer integer = new B<String>().genericFunc(1);
    }
    }

  • 这里Integer s = new B<String>().memberFunc(Integer.valueOf(1));会编译报错:

    1
    Error:(35, 61) java: 不兼容的类型: java.lang.Integer无法转换为java.lang.String
  • 由这个例子可知,泛型方法的T和和类A的T是不同的。

泛型通配符

  • 通配符的设计存在一定的场景,例如在使用泛型后,首先声明了一个Animal的类,而后声明了一个继承Animal类的Cat类,显然Cat类是Animal类的子类,但是List<Cat>却不是List<Animal>的子类型,而在程序中往往需要表达这样的逻辑关系。为了解决这种类似的场景,在泛型的参数类型的基础上新增了通配符的用法。

  • 无界通配符

    • <?>:任意类型,如果没有明确 ,那么就是Object以及任意的Java类
    • ?代表了任何的一种类型,能代表任何一种类型的只有null(Object本身也算是一种类型,但却不能代表任何一种类型,所以List<Object>List<null>的含义是不同的,前者类型是Object,也就是继承树的最上层,而后者的类型完全是未知的)。
  • <? extends T>上界通配符

    • ? extends E:向下限定, E 及其子类
    • 实例化时,指定类型实参只能是extends后类型的子类或其本身。
  • 下界通配符

    • ? super E:向上限定,E 及其父类
    • 实例化时,指定类型实参只能是super后类型的父类类或其本身。
  • 代码测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    Collection<Object> c1 = new ArrayList<Object>();

    // 泛型如果明确的写的时候,前后必须一致
    Collection<Object> c2 = new ArrayList<Animal>();
    Collection<Object> c3 = new ArrayList<Dog>();
    Collection<Object> c4 = new ArrayList<Cat>();

    // ?表示任意的类型都是可以的
    Collection<?> c5 = new ArrayList<Object>();
    Collection<?> c6 = new ArrayList<Animal>();
    Collection<?> c7 = new ArrayList<Dog>();
    Collection<?> c8 = new ArrayList<Cat>();

    // ? extends E:向下限定,E及其子类
    // Collection<? extends Animal> c9 = new ArrayList<Object>(); //报错,Object不是Animal的子类
    Collection<? extends Animal> c10 = new ArrayList<Animal>();
    Collection<? extends Animal> c11 = new ArrayList<Dog>();
    Collection<? extends Animal> c12 = new ArrayList<Cat>();

    // ? super E:向上限定,E极其父类
    Collection<? super Animal> c13 = new ArrayList<Object>();
    Collection<? super Animal> c14 = new ArrayList<Animal>();
    // Collection<? super Animal> c15 = new ArrayList<Dog>(); //报错,Dog是Animal子类
    // Collection<? super Animal> c16 = new ArrayList<Cat>(); //报错,Animal是Animal子类


    class Animal {
    }

    class Dog extends Animal {
    }

    class Cat extends Animal {
    }

类型参数与通配符的区别

  • 类型参数 可以表示一种类型

  • 通配符不能表示一种类型

  • 类型参数只能指定上限(extend),不能下限

  • 通配符可以指定上限,也可以指定下限

  • 类型参数可以指定多个上限,用&连接

  • 通配符只能指定一个上限

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    interface Info1 {}

    interface Info2 {}

    class Base {}

    class Sub extends Base implements Info1, Info2 {
    }

    //===类型参数===========
    class Point<T extends Base & Info1 & Info2> {}

    //===通配符参数===========
    class Point<T extends Number> {}
    class Point<T super Integer> {}

泛型的擦除

  • 泛型是编译的检查,生成的字节码的时候会擦除

  • 就是指编译器编译带类型说明的集合时会去掉“类型”信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class GenericTest {
    public static void main(String[] args) {
    new GenericTest().testType();
    }

    public void testType(){
    ArrayList<Integer> collection1 = new ArrayList<Integer>();
    ArrayList<String> collection2= new ArrayList<String>();

    System.out.println(collection1.getClass()==collection2.getClass());
    //两者class类型一样,即字节码一致

    System.out.println(collection2.getClass().getName());
    //class均为java.util.ArrayList,并无实际类型参数信息
    }
    }
    // 输出
    true
    java.util.ArrayList
  • 为何会返回true:

    • 这是因为不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一类处理,在内存中也只占用一块内存空间。
    • 从Java泛型这一概念提出的目的来看,其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段
  • 在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。

  • 多个参数化类型,生成的字节码文件是同一个,在生成字节码的时候,会擦除掉形式类型参数

    • String 是 Object 的子类
    • Object类型 = String类型 (可行)
    • 但是Point<String> 不是 Point<Object>的子类,泛型只是检查类型

擦除原则:

  • 对于参数化类型:Point<String>

    • 擦除后 为 原生类型:Point
  • 对于类型参数:

    • 无界类型参数,擦除后为object

      • private T a; -----> private Object a;
      • <T> void f(T t) -----> void f( Object a);
    • 有界:

      • 有一个上限

        • 用上限替换
      • 有多个上限

        • 用第一个上限来替换
      • <T extends Number> void f(T t) -----> void f( Number a);

      • <T extends Base & Info1> void f(T t) -----> void f( Base a);

      • <T extends Info2 & Info1> void f(T t) -----> void f( Info2 a);

泛型的重载

  • 重载:参数名相同,参数列表不同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Point1<T> {}

    class Demo5 {

    public void f(Point1<String> p) {} //Point t
    public void f(Point1<Integer> p) {} //Point t
    -----擦除后一样,不能重载------------------------------

    <T> void f(T t) {}// Object t
    <T extends Number> void f(T t) {}// Number t
    -----擦除后不一样,能重载------------------------------

    <T extends Base & Info1> void f(T t) {}// Base t
    <T extends Info1 & Info2> void f(T t) {}// Info1 t
    -----擦除后不一样,能重载------------------------------
    }
  • 重写: 父类擦除后与子类参数相同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Parent {

    // 父类擦除后与子类参数相同

    public void f(Point<String> p) {}// Point p

    class Child extends Parent {

    @Override
    public void f(Point p) {
    }
    }
    }

泛型的限制

重载出现模糊性错误

  • 对泛型类 User< T, K > 而言,声明了两个泛型类参数:T 和 K。在类中试图根据类型参数的不同重载 set() 方法。这看起来没什么问题,可编译器会报错

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class User<T, K> {

    //重载错误
    public void set(T t) {

    }

    //重载错误
    public void set(K k) {

    }
    }
  • 首先,当声明 User 对象时,T 和 K 实际上不需要一定是不同的类型,以下的两种写法都是正确的

    1
    2
    3
    4
    5
    6
    public class GenericMain {
    public static void main(String[] args) {
    User<String, Integer> stringIntegerUser = new User<>();
    User<String, String> stringStringUser = new User<>();
    }
    }
    • 对于第二种情况,T 和 K 都将被 String 替换,这使得 set() 方法的两个版本完全相同,所以会导致重载失败。
    • 此外,对 set() 方法的类型擦除会使两个版本都变为如下形式:一样会导致重载失败
    1
    2
    3
    public void set(Object o) {

    }

不能实例化类型参数

  • 不能创建类型参数的实例。因为编译器不知道创建哪种类型的对象,T 只是一个占位符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class User<T> {

    private T t;

    public User() {
    //错误
    t = new T();
    }
    }

对静态成员的限制

  • 静态成员不能使用在类中声明的类型参数,但是可以声明静态的泛型方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class User<T> {

    //错误
    private static T t;

    //错误
    public static T getT() {
    return t;
    }

    //正确
    public static <K> void test(K k) {

    }
    }

对泛型数组的限制

  • 不能实例化元素类型为类型参数的数组,但是可以将数组指向类型兼容的数组的引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class User<T> {

    private T[] values;

    public User(T[] values) {
    //错误,不能实例化元素类型为类型参数的数组
    this.values = new T[5];
    //正确,可以将values 指向类型兼容的数组的引用
    this.values = values;
    }
    }
  • 此外,不能创建特定类型的泛型引用数组,但使用通配符的话可以创建指向泛型类型的引用的数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class User<T> {

    private T[] values;

    public User(T[] values) {
    this.values = values;
    }
    }


    public class GenericMain {

    public static void main(String[] args) {
    //错误,不能创建特定类型的泛型引用数组
    User<String>[] stringUsers = new User<>[10];
    //正确,使用通配符的话,可以创建指向泛型类型的引用的数组
    User<?>[] users = new User<?>[10];
    }
    }

对泛型异常的限制

  • 泛型类不能扩展 Throwable,意味着不能创建泛型异常类

反射与泛型

  • 使用反射可跳过编译器,往某个泛型集合加入其它类型数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import java.lang.reflect.Method;
    import java.util.ArrayList;

    public class test1 {
      // 泛型只在编译期有效, 在编译之后通过字节码文件,添加元素 不受泛型限制
    public static void main(String[] args) throws Exception {
    ArrayList<Integer> list = new ArrayList<>();
    list.add(111);
    list.add(222);

    Class clazz = Class.forName("java.util.ArrayList");
    Method m = clazz.getMethod("add", Object.class);
    m.invoke(list, "abc");
    System.out.println(list);
    }

    }

反射获得泛型的实际类型参数

  • 把泛型变量当成方法的参数,利用Method类的getGenericParameterTypes方法来获取泛型的实际类型参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class GenericTest {

    public static void main(String[] args) throws Exception {
    getParamType();
    }

    /*利用反射获取方法参数的实际参数类型*/
    public static void getParamType() throws NoSuchMethodException{
    Method method = GenericTest.class.getMethod("applyMap",Map.class);
    //获取方法的泛型参数的类型
    Type[] types = method.getGenericParameterTypes();
    System.out.println(types[0]);
    //参数化的类型
    ParameterizedType pType = (ParameterizedType)types[0];
    //原始类型
    System.out.println(pType.getRawType());
    //实际类型参数
    System.out.println(pType.getActualTypeArguments()[0]);
    System.out.println(pType.getActualTypeArguments()[1]);
    }

    /*供测试参数类型的方法*/
    public static void applyMap(Map<Integer,String> map){

    }
    }

    // 输出结果:
    java.util.Map<java.lang.Integer, java.lang.String>
    interface java.util.Map
    class java.lang.Integer
    class java.lang.String

比较器

Comparable

  • …..ble 使。。。。。具备。。。。的能力

  • comparable 使。。。。具备比较的能力,一个类实现了这个接口 代表这个类具备了比较的能力

  • 内部比较器 定义在对象类定义的时候 只能用于比较本类对象的

  • 实现了comparable接口的类需要重写compareTo方法, 参数o用于比较的对象和本对象比较(this)

  • 返回值: int类型 this.属性-o.属性的结果

    • > 0 :向后蹿,越大越在后面(升序)
    • =0 :位置不变
    • <0 :向前蹿(降序)
  • 缺点是只能按照一种规则排序,默认使用自然排序 ;升序

Comparator : 外部比较器

  • 定义在类的外部的, 可以随着传入的泛型的类型 ,定义比较规则的。
  • 使用Comparator接口:编写多个排序方式类实现Comparator接口,并重写新Comparator接口中的compare()方法
  • public static <T> void sort(T[] a,Comparator<? super T> c),根据指定比较器产生的顺序对指定对象数组进行排序。数组中的所有元素都必须是通过指定比较器可相互比较的
    • (也就是说,对于数组中的任何 e1 和 e2 元素而言,c.compare(e1, e2)不得抛出 ClassCastException)。
  • 优点是可以按照多种方式排序,你要按照什么方式排序,就创建一个实现Comparator接口的排序方式类,然后将该排序类的对象传入到Arrays.sort(待排序对象,该排序方式类的对象)

Comparable和Comparator区别:

  • 位置。

    • Comparable代码写在要比较的类中;
    • Comparator代码写在要比较的类外;
  • 个数。

    • Comparable就定义一种:
    • Comparator可以定义多个比较方式。

枚举

  • 有的时候一个类的对象是有限且固定的,这种情况下我们使用枚举类就比较方便.
  • 实际上 enum 就是一个 class,只不过 java 编译器帮我们做了语法的解析和编译而已。

枚举类

  1. 语法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    enum 枚举名称{
    对象,第一行处;
    构造; //private
    方法();
    抽象方法();
    所有对象实现了抽象方法,那么此枚举类就是一个抽象枚举类
    重写方法;
    实现接口
    接口的方法要在对象中重写,(使用的是匿名内部类)
    生成了字节码文件
    }
  • 反编译: javap (-private) 包名.枚举名
    1. 隐式的继承了 java.lang.Enum类;
    2. 对于一个非抽象的枚举类,默认是final
    3. 枚举类的对象必须 在枚举类的第一行代码处显示列举出来
    4. 这些枚举对象都是 public static final;
    5. 枚举类的构造器都是私有privatae的
    6. 所有对象实现了抽象方法,那么此枚举类就是一个抽象枚举类

常用方法

  1. int compareTo(E o)
    比较此枚举与指定对象的顺序。
  2. Class<E> getDeclaringClass()
    返回与此枚举常量的枚举类型相对应的 Class 对象。
  3. String name()
    返回此枚举常量的名称,在其枚举声明中对其进行声明。
  4. int ordinal()
    返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
  5. String toString()
    返回枚举常量的名称,它包含在声明中。
  6. static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
    返回带指定名称的指定枚举类型的枚举常量。

对象

必须在枚举类的第一行列举
对象都是 public static final;

1
2
3
4
enum Color {
//枚举类的对象
RED,BLUE,GREEN;
}

成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
enum Color  {
private int no;
private String name;
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//使用: 访问成员变量
Color.RED.setNo(11);
Color.RED.setName("红色");
System.out.println(Color.RED.getNo());
System.out.println(Color.RED.getName());

构造方法

构造方法是private的.
有构造方法,需要修改对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
enum Color implements InfoNew{
//对象--------------------
RED(1,"红");
//构造
private Color(int no, String name) {
this.no = no;
this.name = name;
}

//成员变量
private int no;
private String name;
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

//使用
//------------------构造赋值,访问成员变量-------------
System.out.println(Color.RED.getNo());
System.out.println(Color.RED.getName());

普通方法

1
2
3
4
5
6
7
8
enum Color  {
public void show() {
System.out.println("普通方法");
}
}

//使用:对象都是静态的
Color.RED.show();

抽象方法

如果有抽象方法,对象必须实现抽象方法
实现了抽象方法(匿名内部类),那么此枚举类就是一个抽象枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Color  {
RED(1,"红色") {
@Override
public void af() {
System.out.println("实现了 RED的抽象方法");
}
};
//抽象方法
public abstract void af();
}

//-------------------抽象方法--------------------------------------
Color.RED.af();

实现接口

实现一个接口,枚举对象要重写抽象方法(匿名内部类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface InfoNew{
void ff();
}
enum Color implements InfoNew {
//对象
RED(1,"红色") {
@Override
public void af() {
System.out.println("实现了 RED的抽象方法");
}
@Override
public void ff() {
System.out.println("实现了接口的抽象方法");
}
};
}

重写

重写toString()

1
2
3
4
5
6
7
8
9
10
enum Color{
RED;
@Override
public String toString() {
return no + "," + name;
}
}

//------------------重写--------
System.out.println(Color.RED.toString());

枚举类的遍历

1
2
3
4
for(Color c : Color.values()){
System.out.println(c.ordinal());
System.out.println(c.name());
}

switch使用

switch(表达式)
表达式类型:byte short int char String enum
使用枚举类对象=枚举类.valueOf(String str)
str不是枚举类的对象会抛出异常IllegalArgumentException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Scanner input = new Scanner(System.in);
System.out.println("输入颜色:");
String s = input.next();
Color cr = Color.valueOf(s);
switch(cr) {
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
case BLUE:
System.out.println("蓝色");
break;
}