添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
  • 早期Java是使用Object来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全。
  • 对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常。
  • 泛型的好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。
  • 有了泛型以后:
  • 代码更加简洁【不用强制转换】。
  • 程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】。
  • 可读性和稳定性【在编写集合的时候,就限定了类型】。
  • 泛型中的 <Object> 并不是像以前那样有继承关系的,也就是说 List<Object> List<String> 是毫无关系的。
  • Java泛型提供了类型通配符 ? ? 通配符表示可以匹配任意类型,任意的Java类都可以匹配。
  • 使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。
  • 简单泛型为什么需要左右一致:

  • 如果允许 List<Number> 指向 new ArrayList<Integer>() ,由于简单泛型允许存入子类元素,最终List里会混入Integer、Long、Double等多种类型元素。然后等到 new ArrayList<Integer>() 重新被赋值给 List<Integer> 时就出事了:
  • 编译器看到 List<Integer> ,只会按它的边界类型转,也就是自动转型为Integer,此时会抛ClassCastException。
  • 设置通配符上下限:

  • 设置上限:
  • 如果想接收一个List集合,它只能操作数字类型的元素【Float、Integer、Double、Byte等数字类型都行】。
  • 设定通配符上限 List<? extends Number> ,表示List集合装载的元素只能是Number的子类或自身。
  • 泛型中可以使用 Number 的方法。
  • 设置下限:
  • 传递进来的只能是Type或Type的父类: <? super Type>
  • 无论是设定通配符?的上限还是下限,都是不能操作与对象有关的方法。
  • 泛型中的参数不考虑其继承关系,如果要考虑其继承关系应该使用设置上下限的方法。
  • 带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。
  • 如果使用extends指明类型,List不允许通过 add(E e) 方法添加上限的的对象,唯一例外的是 null。
  • 如果使用super指明类型,List通过 get() 方法取出数据只能往object自动转型。
  • “存入”指的是形参带泛型的方法,而“取出”则是返回值带泛型的方法。
  • List<? extends Human>指向:只能指向子类型List,比如 List<Chinese> ,Human是最上边的类。
  • List<? extends Human>取出:接上一条限制,不论指向什么List元素必然是Human及其子类型,按Human转。
  • List<? extends Human>存入:简单泛型要是能解决早解决了,还轮得到我?直接禁止存入。
  • 频繁往外读取内容的,适合用<? extends T>:extends返回值稍微精确些。
  • super小结

  • List<? super Human>接收:只能指向父类型List,比如 ArrayList<Creature> ArrayList<Primate>
  • List<? super Human>取出:只能转Object。
  • 通过List<? super Human>取值时,由于它可能指向 ArrayList<Primate> 也可能指向 ArrayList<Creature> ,都是Human的父类型, 而且Human的父类型未来还可能增加。
  • Java所有类型都继承自Object,所以用Object接收万无一失。
  • List<? super Human>存入:只能存Human及其子类型元素。
  • 经常往里插入的,适合用<? super T>:super允许存入子类型元素。
  • 存:禁止存入
  • 取:只能是Object
  • T和?的区别:

  • T是一个确定的类型(类型参数),通常用于泛型类和泛型方法的定义,?是一个 不确定的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
  • 通过T可以确保泛型参数的一致性,而?不能。
  • 类型参数T可以多重限定(用&设置多个上限)而通配符?不行。
  • 通配符?可以使用超类限定(设置下界)而类型参数T不行。
  • ?可作为实例化对象时的类型参数。
  • 如何选择是用通配符还是泛型方法:

    大多时候,我们都可以使用泛型方法来代替通配符的:

    //使用通配符
    public static void test(List<?> list) {
    //使用泛型方法
    public <T> void  test2(List<T> t) {
    

    如果参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法。

    如果没有依赖关系的,就使用通配符,通配符会灵活一些。

  • 泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。
  • 但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。
  • 因为有类型擦除的存在,不能通过泛型的不同来重载方法。
  • 泛型的兼容性:

  • JDK5提出了泛型这个概念,但是JDK5以前是没有泛型的。也就是泛型是需要兼容JDK5以下的集合的。
  • 当把带有泛型特性的集合赋值给老版本的集合时候,会把泛型给擦除了。它保留的就类型参数的上限Object。
  • 如果把没有类型参数的集合赋值给带有类型参数的集合赋值,不会报错,仅仅是提示“未经检查的转换”。
  • 2、泛型实现机制

    运行Java代码的两个阶段:编译期、运行期。

    Java代码运行有4个要素:源代码、编译器、字节码、虚拟机。

    泛型的实现:

  • 为了兼容各种类型,泛型底层还是采用Object接收所有类型。ArrayList也一样,它其实底层还是Object[]存储元素。
  • Java的泛型存在“编译后泛型擦除”。
  • 而编译器自动类型转换也和“泛型擦除”有关:因为底层还是Object接收,要想返回实际类型,只能强转。
  • 泛型是JDK专门为编译器创造的语法糖,只在编译期,由编译器负责解析,虚拟机不知情。
  • 存入:普通类继承泛型类并给变量类型T赋值后,就能强制让编译器帮忙进行类型校验。
  • 取出:代码编译时,编译器底层会根据实际类型参数自动进行类型转换,无需程序员在外部手动强转。
  • 通过反射,是可以绕过编译器对泛型的检查,存入其他类型的对象。
  • 本身泛型的引入就是为了解决引用类型强转易出错的问题,也就自然不会去考虑基本类型
  • JDK1.5不仅引入泛型,还同时发布自动拆装箱特性,所以int完全可以用Integer代替,也就无需支持int。
  • Java已经尽自己最大的努力让泛型支持基本类型了。只不过它不是从语法上支持,而是从功能上支持。
  • 但归根结底,Java泛型之所以无法支持基本类型,还是因为存在泛型擦除,底层仍是Object,而基本类型无法直接赋值给Object类型,导致JDK只能用自动拆装箱特性来弥补,而自动拆装箱会带来性能损耗。
  • 枚举是一种特殊的类,和class、interface等是一个级别的(其实就是一个类),一般用于表示多种固定的状态。

    自定义类山寨枚举:

  • 静态代码块初始化final常量。
  • 构造器私有,防止外部创建。
  • 正版枚举:

    反编译后:

    与山寨"枚举"的区别在于:

  • enum关键字修饰的WeekDayEnum,在编译后会自动继承Enum类,且声明为final
  • 比山寨版枚举多了valueOf()方法,传入名称返回对应的枚举实例
  • values()并不是直接返回WeekDay[],而是$VALUES.clone()
  • 防止外部调用者随意增删枚举对象。
  • 正版枚举重写了toString()方法。
  • 然当初编写WeekDayEnum时,构造器只传入了两个参数,但编译器却给我们额外加了两个,用来为父类Enum中的name和ordinal赋值。name来自MONDAY、TUESDAY等枚举名称,而ordinal则是编写序号。
  • 枚举和常量类有什么区别呢?

  • 作为常量使用时,区别不大。
  • 枚举优点一:限制重复定义常量。枚举作为常量使用时,编译器会自动限制取值不能相同,而常量类做不到,有可能会重复。
  • 枚举优点二:包含更多维度的信息。枚举毕竟是对象,可以包含更多的信息。
  • 枚举优点三:枚举可以直接用于Switch判断,代码通常会比常量+if/else简洁一些。
  • 枚举做统一返回结果,可以有效规范错误码及错误信息。将错误码及错误信息实现绑定。
  • 枚举策略:

    根据会员返回打折力度。黄金会员:6折白银会员:7折;青铜会员:8折。

    用枚举实现(在枚举类中定义抽象方法,强制子类实现):

  • 这是基于,枚举中的每个具体元素,都是该枚举类引用指向的对象。
  • public class MemberDemo {
        public static void main(String[] args) {
            User user = new User(1L, "bravo", MemberEnum.GOLD_MEMBER.getType());
            BigDecimal productPrice = new BigDecimal("1000");
            BigDecimal discountedPrice = calculateFinalPrice(productPrice, user.getMemberType());
            System.out.println(discountedPrice);
         * 根据会员身份返回折扣后的商品价格
         * @param originPrice
         * @param user
         * @return
        public static BigDecimal calculateFinalPrice(BigDecimal originPrice, Integer type) {
            return MemberEnum.getEnumByType(type).calculateFinalPrice(originPrice);
    @Data
    @AllArgsConstructor
    class User {
        private Long id;
        private String name;
         * 会员身份
         * 1:黄金会员,6折优惠
         * 2:白银会员,7折优惠
         * 3:青铜会员,8折优惠
        private Integer memberType;
    enum MemberEnum {
        // ---------- 把这几个枚举当做本应该在外面实现的MemberEnum之类,不要看成MemberEnum内部的 ----------
        GOLD_MEMBER(1, "黄金会员") {
            @Override
            public BigDecimal calculateFinalPrice(BigDecimal originPrice) {
                return originPrice.multiply(new BigDecimal(("0.6")));
        SILVER_MEMBER(2, "白银会员") {
            @Override
            public BigDecimal calculateFinalPrice(BigDecimal originPrice) {
                return originPrice.multiply(new BigDecimal(("0.7")));
        BRONZE_MEMBER(3, "青铜会员") {
            @Override
            public BigDecimal calculateFinalPrice(BigDecimal originPrice) {
                return originPrice.multiply(new BigDecimal(("0.8")));
        // ---------- 下面才是MemberEnum类的定义 ---------
        private final Integer type;
        private final String desc;
        MemberEnum(Integer type, String desc) {
            this.type = type;
            this.desc = desc;
         * 定义抽象方法,留个子类实现
         * @param originPrice
         * @return
        protected abstract BigDecimal calculateFinalPrice(BigDecimal originPrice);
        public Integer getType() {
            return type;
        public String getDesc() {
            return desc;
        public static MemberEnum getEnumByType(Integer type) {
            MemberEnum[] values = MemberEnum.values();
            for (MemberEnum memberEnum : values) {
                if (memberEnum.getType().equals(type)) {
                    return memberEnum;
            throw new IllegalArgumentException("Invalid Enum type:" + type);