泛型(Generics)

78 阅读3分钟

泛型简介

泛型是jdk1.5之后加入的,它可以帮我建立类型安全的集合。提供了一种参数化类型机制,允许在定义类、接口、方法时使用类型参数,在创建对象或调用方法时再制定具体的类型。

可以把它理解为一个数据类型的占位符。即告诉编译器,在调用泛型时必须传入实际类型。

参数化类型:

  • 把类型当做参数一样传递
  • <数据类型>只能是引用类型(Integer、Double、Character),不可以是基本数据类型

image.png

泛型的优点

  • 类型安全,编译时检查类型
  • 消除强制转换:代码更简洁
  • 代码复用:同一套逻辑可用于多种类型

类型擦除

编译器在编译阶段会移除泛型的类型信息,使得泛型在运行时不保留具体的类型参数。

泛型主要用于编译阶段,编译后生成的字节码class文件不包含泛型中的类型信息,涉及的类型转换仍然是普通的强制类型转换。类型参数在编译后会被替换成Object,运行虚拟机并不知道泛型。

表现:

image.png

定义泛型

泛型字符可以是任何标识符,一般采用几个标记:E、T、K、V、N

泛型标记对应单词说明
EElement在容器中使用,表示容器中的元素
TType表示普通的java类
KKey表示键,比如Map中的key
VValue表示值
NNumber表示数值类型
?表示不确定的java类型

泛型类

在类名后声明类型参数,使得类中成员变量、方法参数或者返回值可以使用该类型参数;使用方法就是在类后面声明一个或者多个类型参数声明,例如:<T、K、V>

class 类名<类型参数1,类型参数2> {
  // 类体中可以使用该类型参数
}
package com.generic.test.it;

public class Generic<T> {
    private T flag;

    public void setFlag(T flag) {
        this.flag = flag;
    }

    public T getFlag() {
        return this.flag;
    }
}
package com.generic.test.it;

public class Test {
    public static void main(String[] args) {
        Generic<String> generic = new Generic<>();
        generic.setFlag("admin");
        String s = generic.getFlag();
        System.out.println(s);

        Generic<Integer> num = new Generic<>();
        num.setFlag(12);
        Integer n = num.getFlag();
        System.out.println(n);
    }
}

泛型接口(Generic interface)

与泛型类相似,在接口名后声明参数类型,接口中方法可以使用该类型

interface 接口名<类型参数1,类型参数2> {
}

定义接口:

public interface Igeneric <T>{
    T getName(T name);
}

定义接口实现类:

package com.generic.test.it;

public class Igenericmpl implements Igeneric<String> {
    @Override
    public String getName(String name) {
        return name;
    }
}

测试类:

package com.generic.test.it;

public class TestIgeneric {
    public static void main(String[] args) {
        // 使用接口实现类来修饰,则已经确定T是String
        Igenericmpl generic = new Igenericmpl();
        String name = generic.getName("name");
        System.out.println("name"); // name

        // 使用接口来修饰,T目前还是Object,要指定String
        Igeneric<String> generic1 = new Igenericmpl();
        String name1 = generic1.getName("long");
        System.out.println(name1); // long
    }
}

泛型方法(Generic Methods)

在方法返回类型之前声明类型参数,使方法可以独立于类或接口的泛型而使用自己的类型参数

语法:

修饰符 <类型参数1, 类型参数2,...>返回值类型 方法名(参数列表) {
}

public <泛型表示参数> void getName(泛型表示参数 name) {}
1. 非静态方法

定义泛型方法:

package com.generic.test.it;

public class MethodGeneric {
    public <T> void setName(T name) {
        System.out.println(name);
    }

    public <T> T getName(T name) {
        return name;
    }
}

测试泛型方法:

package com.generic.test.it;

public class TestMethodGeneric {
    public static void main(String[] args) {
        MethodGeneric methodGeneric = new MethodGeneric();
        methodGeneric.setName("我在测试泛型方法");
        methodGeneric.setName(123456);

        System.out.println(methodGeneric.getName("测试getName方法"));
        System.out.println(methodGeneric.getName(123456));

    }
}
2. 静态方法

静态方法中使用泛型时,有一种情况需要注意:静态方法无法使用类上定义的泛型;所以如果静态类型操作的引用数据类型不确定,也必须要将泛型定义在方法上。

package com.generic.test.it;

public class Generic<T> {
    private T flag;
    public static T getName() {} // 会报错,static不可以使用类上的泛型
}

语法结构:

public static <泛型表示符号> void getName(泛型表示符号 name) {}

静态方法泛型的定义:

package com.generic.test.it;

public class MethodGeneric {
    // 静态方法的调用
    public static<T> void setFlag(T flag) {
        System.out.println(flag);
    }

    public static<T> T getFlag(T flag) {
        return flag;
    }
}

验证静态方法泛型

package com.generic.test.it;

public class TestMethodGeneric {
    public static void main(String[] args) {
        // 静态方法的调用验证
        MethodGeneric.setFlag("time");
        MethodGeneric.setFlag(123457);
        System.out.println(MethodGeneric.getFlag(true));
        System.out.println(MethodGeneric.getFlag(false));

    }
}