Effective-Java-Chapter5-泛型

news2024/11/24 5:28:07

https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/dev/Chapter-5/Chapter-5-Introduction.md
在这里插入图片描述

准则一 不要使用原始类型

首先来看一下什么是原始类型呢?
List 对应的原始类型是 List,那其实就是说不带参数化类型。
直接使用原始类型会有什么危害呢?上例子

List list = new ArrayList();
// 添加一个点对象
list.add(new Point(1, 2));
// 添加一个字符串
list.add("add String");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    Point next = (Point) iterator.next();
    System.out.println(next.x + " : " + next.y);
}

上面这个代码可以编译通过,但是运行的时候会报错,当然平时我们可能不会这样去写。之所以能够编译通过,是因为Java是伪泛型,通过擦写为Object来实现的,所以相当于往List《Object》里放数据。
在这里插入图片描述

public static void main(String[] args) {
      List<String> strings = new ArrayList<>();
      unsafeAdd1(strings, Integer.valueOf(42));
      unsafeAdd2(strings, Integer.valueOf(42));
      String s = strings.get(0); 
  }
  private static void unsafeAdd1(List list, Object o) {
      list.add(o);
  }
  private static void unsafeAdd2(List<Object> list, Object o) {
      list.add(o);
  }

在这里插入图片描述
为什么unsafeAdd2通不过编译呢?那是因为如果参数化类型写成Objet,List《String》不是List《Object》的子类,这一点千万要记住,译器会抛出错误,因为它不允许你将一个List《String》当作List《Object》使用。这是因为List《String》是一个更具体的类型,而《Object》是一个更通用的类型。
所以最佳实践是我们不使原始类型,也不是用List《Object》而是使用采用下面的方式:

// 这里为什么要Collection 呢?因为这样的话,我们的方法会更通用
public  static <E> void unsafeAdd4(Collection<E> target, E element) {
   target.add(element);
}

或者使用通配符:
这样做确实可以达到目的,但是我们一般不这么使用,我们一般来说要通用一点,但是这样写就是检查了一个类型,并不是一个通用的方法。

private static void unsafeAdd3(List<? super String> list, String o) {
    list.add(o);
}

也许你会这样写?

private static void safeAdd3(List<? super Object> list, Object o) {
    list.add(o);
}

这样是不是更通用了,答案是如果你还是像上面那样使用,编译通不过,问题就在于你传入的是List《String》,但是往里面添加的元素却是Object类型,类型是不匹配的。

补充知识,关于参数中使用通配符 ? extends 和 ? super 是有区别的:
如果说你是消费集合里面的元素要用extends,如果你要修改集合往里面添加或者删除则要用 super,什么意思呢?还是举个例子:

public static void lower(List<? extends Number> producer, List<? super Number> consumer) {
    // 消费数据用 extend 比如我们遍历数据就是消费,也就是读取数据 这里不在写代码进行演示
    
    // 报错 不允许添加因为不确定与实际类型是否兼容
    producer.add(Integer.valueOf(1));
    
    // 操作数据用super
    // 可以正常添加
    consumer.add(Integer.valueOf(1));
}

List<? extends Number> producer:表示这是一个列表,其中的元素类型是Number或其子类。我们不能确定具体是什么类型,但可以确定它一定是Number的一个子类。这就可能出现 实际类型为 javaList<Integer>、List<Double> 或 List<Byte> 会导致类型不兼容,比如我们的类型是Integer,如果允许操作的话,这个时候来一个Double怎么办呢?所以extends不允许我们进行操作。
List<? super Number> consumer:表示这是一个列表,其中的元素类型是Number或其父类。这可以包括Number类型或其超类(比如Object)。这就意味着Integer,Double都可以添加,因为Number是一个更通用的类型,类型是兼容的。

友情连接:
https://blog.csdn.net/qq_43259860/article/details/137127886
https://blog.csdn.net/qq_43259860/article/details/137032842

准则二 消除 unchecked 警告

如果不能消除警告,但是可以证明引发警告的代码是类型安全的,那么(并且只有在那时)使用 SuppressWarnings(“unchecked”) 注解来抑制警告。

准则三 list 优于数组

  • 理由一
    如果 Sub 是 Super 的一个子类型,那么数组类型 Sub[] 就是数组类型 Super[] 的一个子类型。
    但是List<sub> 和List<super>不存在这样的关系为什么呢?
    里氏替换原则(Liskov Substitution Principle,LSP)面向对象设计的基本原则之一。里氏替换原则指出:任何父类可以出现的地方,子类一定可以出现。
    你想想看比如java List<String> 如果是List<Object>的子类根据里氏替换原则是不是说不通啊。Sting的List只能添加Sting的类型也很明显不符合里氏替换原则。
// 数组类型 Sub[] 就是数组类型 Super[] 的一个子类型 所以下面这样写编译时可以通过的
Object object[] = new Long[1];
// 这样就导致了运行时异常
object[0] = "java";
// 编译无法通过
List<Object> list = new ArrayList<String>();

数组是具体化的 [JLS, 4.7]。这意味着数组在运行时知道并强制执行他们的元素类型。相比之下,泛型是通过擦除来实现的 [JLS, 4.6]。这意味着它们只在编译时执行类型约束,并在运行时丢弃(或擦除)元素类型信息。
由于这些基本差异创建泛型、参数化类型或类型参数的数组是非法的。因此,这些数组创建表达式都不是合法的:new List[]、new List[]、new E[]。

// (1) 不会通过编译
List<String>[] stringLists = new List<String>[1];
// (2)
List<Integer> intList = Arrays.asList(1);
// (3)
Object[] objects = stringLists;
// (4)
objects[0] = intList;
// (5)
String s = stringLists[0].get(0);

为什么呢?因为这样存在类型安全,假如第一步中的创建是合法的,那么由于前面提到的sub[] 和 super[]存在父子关系,所以第三步是合法的,那么在第四步我们这个操作完全没毛病吧,反正是一个对象类型的数组都可以存,但是使用的时候这就报错了ClassCastException。

  • 理由二
// Chooser - a class badly in need of generics!
public class Chooser {
  private final Object[] choiceArray;

  public Chooser(Collection choices) {
    choiceArray = choices.toArray();
}

  public Object choose() {
    Random rnd = ThreadLocalRandom.current();
    return choiceArray[rnd.nextInt(choiceArray.length)];
  }
}

// A first cut at making Chooser generic - won't compile
public class Chooser<T> {
  private final T[] choiceArray;

  public Chooser(Collection<T> choices) {
    choiceArray = (T[]) choices.toArray();
  }

  // choose method unchanged
}


// List-based Chooser - typesafe
public class Chooser<T> {
    private final List<T> choiceList;

    public Chooser(Collection<T> choices) {
        choiceList = new ArrayList<>(choices);
    }

    public T choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceList.get(rnd.nextInt(choiceList.size()));
    }
}

我们依次来分析这三段代码,首先第一段我们可以实现随机选取一个对象,但是我们必须知道对象是什么类型,每次使用的时候都必须强转。所以我们有了第二段代码,进行了优化,更加的通用,但是第二段代码在(T[]) 这个步骤的时候对得到一个unchecked,但是其实这个警告我们可以忽略,因为我们明确的知道此处不会存在类型转换安全。但是如果要进一步消除这个警告,我们就可以按照第三段代码的方式来写。

准则四 优先使用泛型

但是我们在泛型的使用过程中存在一些问题,让我来看一个例子。

public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new E[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public E pop() {
        if (size == 0)
            throw new EmptyStackException();
        E result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    } ... // no changes in isEmpty or ensureCapacity
}

问题在哪里?
elements = new E[DEFAULT_INITIAL_CAPACITY]; 泛型是不允许实例化的,为了解决这个问题我们有两种方案。

  • 方案一
    尽管这里会得到unchecked异常,但是我们自己清楚这里这样做是安全的,所以我们可以忽略这个警告。
@SuppressWarnings("unchecked")
public Stack() {
    elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}
  • 方案二:
    尽然是栈,那我们参考一下Java源码的实现,我们可以看到源码中的Stack继承了Vector
    在这里插入图片描述
    我们可以清晰的看到,它定义的是对象类型的数组。那么类型转换是在那里完成的呢?我们可以看到添加的时候是直接进行了添加:
    在这里插入图片描述

在这里插入图片描述
我们可以看看pop方法,可以看到最终取元素的逻辑如下:
在这里插入图片描述

那就是在每一个元素使用的时候进行转换。这里依然可能存在类型安全的问题,同样的我们清晰的知道是允许的,所以方法上申明了一个@SuppressWarnings(“unchecked”),这也是符合准则二。

准则五 优先使用泛型方法

使用泛型方法是为了更通用,这里可以参考源码,比如UnaryOperator:

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    /**
     * Returns a unary operator that always returns its input argument.
     *
     * @param <T> the type of the input and output of the operator
     * @return a unary operator that always returns its input argument
     */
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

又比如经典的Comparable:

public interface Comparable<T> {
    public int compareTo(T o);
}

准则六 使用有界通配符增加 API 的灵活性

那为什么通配符能够增加灵活性呢?

   public static void main(String[] args) {
        DemoStack<Number> stack = new DemoStack<>();
        stack.push(new Integer(1));
        Iterable<Integer> integers = Arrays.asList(1, 2, 3);
        stack.pushAll(integers);
    }

    public static class DemoStack<E> {
        private Object[] data;
        private int idx = 0;

        public DemoStack() {
            data = new Object[10];
        }

        public void push(E e) {
            this.data[idx++] = e;
        }
        public void pushAll(Iterable<E> src) {
            for (E e : src)
                push(e);
        }
    }

我们可以看到这样的代码有个缺陷:
在这里插入图片描述
那如何解决呢?

public void pushAll(Iterable<? extends E> src) {
   for (E e : src)
       push(e);
}

那如果是进行,我们把元素取出来添加到另一个集合,因为这里是要进行操作,所以只有当元素是E类或者是E的父类才允许操作,关于为什么可以看前面准则一,举的例子:

public void popAll(Collection<? super E> dst) {
  while (!isEmpty())
    dst.add(pop());
}

生产者使用 extends,消费者使用 super(PECS)。
在我们的 Stack 示例中,pushAll 的 src 参数生成 E 的实例供 Stack 使用,因此 src 的适当类型是 Iterable<? extends E>;popAll 的 dst 参数使用 Stack 中的 E 实例,因此适合 dst 的类型是 Collection<? super E>。

还要记住,所有的 comparable 和 comparator 都是消费者。Comparables 始终是消费者,所以一般应优先使用 Comparable<? super T> 而不是 Comparable,比较器也是如此;因此,通常应该优先使用 Comparator<? super T> 而不是 Comparator。

如果它是一个无界类型参数,用一个无界通配符替换它;无解参数会存在问题,下面的代码不会通过编译:

public static void swap(List<?> list, int i, int j) {
  list.set(i, list.set(j, list.get(i)));
}

问题是 list 的类型是 List<?>,你不能在 List<?> 中放入除 null 以外的任何值。但是你可以liyong下面这种写法来实现这个功能。

public static void swap(List<?> list, int i, int j) {
  swapHelper(list, i, j);
}
// Private helper method for wildcard capture
private static <E> void swapHelper(List<E> list, int i, int j) {
  list.set(i, list.set(j, list.get(i)));
}

准则七 考虑类型安全的异构容器

public class Favorites {
  private Map<Class<?>, Object> favorites = new HashMap<>();

  public <T> void putFavorite(Class<T> type, T instance) {
    favorites.put(Objects.requireNonNull(type), instance);
  }
  public <T> T getFavorite(Class<T> type) {
    return type.cast(favorites.get(type));
  }
}

如果一个容器能够同时存储Integer、String和其他类型的对象,那么它就是一个异构容器。
比如我们来看Favorites 这个类,与普通 Map 不同,所有键都是不同类型的,也就是可以存储不同的类型。
上面这样做还是有一定的缺陷,因为类型的一致是通过键和值类确定的,那么如果值的类型可能会和键不一样,如果使用人员想要这样做的话。所以我们为了保障类型安全:

// 这样就能保证类型的安全 所以推荐实践过程中可以选用异构安全的容器
public <T> void putFavorite(Class<T> type, T instance) {
    favorites.put(type, type.cast(instance));
}

同时这里还有一个缺陷:
你可以存储的 Favorites 实例类型为 String 类型或 String[],但不能存储 List。原因是你不能为 List 获取 Class 对象,List.class 是一个语法错误,这也是一件好事。List 和 List 共享一个 Class 对象,即 List.class。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2036337.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Selenium + Python 自动化测试13(HTML报告)

我们的目标是&#xff1a;按照这一套资料学习下来&#xff0c;大家可以独立完成自动化测试的任务。 上一篇我们讨论了unittest中discover 的构建&#xff0c;可以组织测试更多测试用例。 本篇文章我们接着讲。如何生成HTML报告&#xff0c;提高我们测试报告的可读性。 1、引入…

IOS 01 CocoaPods 安装与使用

什么是CocoaPods CocoaPods是一个依赖管理工具&#xff0c;类似Java语言的Maven&#xff0c;Gradle这样的工具&#xff1b;只是他是管理iOS&#xff0c;macOS等平台项目的依赖而已。 同类软件 Carthage&#xff0c;Swift官方提供的Swift Package Manager。 流行程度 Cocoa…

若依框架将Mybatis改成MybatisPlus

1.引入MybatisPlus的maven依赖 <mybatis-plus.version>3.2.0</mybatis-plus.version> <dependencies><!-- mybatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifac…

国联证券:蛇吞象

券商蛇吞象&#xff0c;难解业绩荒 今天我们来聊——国联证券 重大资产重组预案发布两个半月后&#xff0c;国联证券最新重组方案出炉&#xff0c;作价294.92亿收购民生证券99.26%的股份。 炒了这么久的券商重组&#xff0c;终于迎来实质性落地。 也因为这两家的营收差异&…

PHP多商家营销活动平台系统小程序源码

解锁营销新境界&#xff01;「多商家营销活动平台」让你的品牌火出圈✨ &#x1f680;【聚合力量&#xff0c;共创辉煌】&#x1f680; 在这个竞争激烈的市场中&#xff0c;单打独斗早已不是最佳选择&#xff01;「多商家营销活动平台」横空出世&#xff0c;它像一座桥梁&…

Redis操作--RedisTemplate(一)介绍

一、介绍 1、简介 RedisTemplate 是 Spring Data Redis 提供的一个高级抽象&#xff0c;由 Spring 官方提供的方便操作 Redis 数据库的一个工具类&#xff0c;支持模板设计模式&#xff0c;使得操作 Redis 更加符合 Spring 的编程模型。还支持序列化机制&#xff0c;可以处理…

基于SpringBoot的航空公司管理系统设计与实现,源码、部署+讲解

摘要 随着互联网时代的日益发展&#xff0c;互联网技术得到了广泛的应用。互联网正在经历一场由传统线下管理模式转变为以互联网主导的线上管理模式的深刻变革。在线管理技术犹如雨后春笋一般冒芽而出&#xff0c;这为我们的生活带来了许多变动。 在近些年国内航空业的蓬勃发…

LVS理论知识

目录 1.描述以及工作原理 1.什么是LVS 2.LVS调度算法 1.静态调度算法 1.轮询RR 2.加权轮询WRR 3.目标地址hash---DH 4.源地址hash---SH 2.动态调度算法 1.LC最少连接 2.wlc加权最少连接 3.sed最少期望延迟 4.nq不排队调度算法 5.lblc基于本地最少连接 6.lnlcr带…

BioMistral 7B: 生物医学领域的开源多语言AI模型

人工智能咨询培训老师叶梓 转载标明出处 尽管目前有许多开源的针对健康领域的大模型可供使用&#xff0c;但现有模型在数据隐私风险、模型性能以及多语言支持方面的局限性&#xff0c;限制了它们在医疗领域的应用。为了克服这些限制&#xff0c;研究者们提出了BioMistral&#…

吴恩达机器学习 笔记四十二 基于内容过滤的深度学习

在电影评分的案例中&#xff0c;基于内容过滤的方法需要用到两个向量&#xff0c;一个是来自用户的特征向量Vu,另一个是电影特征的向量 Vm。以用户的特征为例&#xff0c;原始的向量Xu作为一个神经网络的输入&#xff0c;经过几层之后输出一个有32个单元的向量Vu&#xff0c;电…

中芯国际:回暖

业绩稳重向好&#xff0c;半导体周期逆转&#xff1f;或许可以去掉问号。 今天我们来聊国产晶圆代工龙头——中芯国际 半导体复苏的风已经吹了几个月&#xff0c;各家业绩预告亮眼&#xff0c;但只有数据真正落地才能下定结论。而半导体看晶圆代工&#xff0c;晶圆代工看中芯国…

14.1 NumPy基础

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; 工&#x1f497;重&#x1f497;hao&#x1f497;&#xff1a;野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题.…

uni-app 吸顶方案总结

效果 页面级 uni.pageScrollTo 官方文档&#xff1a;https://uniapp.dcloud.net.cn/api/ui/scroll.html#pagescrollto 原生头部导航 uni.pageScrollTo({selector: #tabs,duration: 300 });(推荐)需要兼容自定义头部导航 <template><view id"demo1" :styl…

Sakana.ai 迈向完全自动化的开放式科学发现

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

设计模式-标识域(Identity Field)

目的 为了在内存对象和数据库行之间维护标识而在对象内保存的一个数据库标识域。 关系数据库和内存对象的区别 区分行&#xff1a;关系数据库使用键来区分数据行&#xff0c;而内存对象不需要这样一个键 引用方法&#xff1a;对象系统中通过原始内存位置直接区分对象&#x…

【3】字节码文件的组成

一.应用场景与学习路线 二.以正确的姿势打开文件 使用记事本和NotePad打开字节码文件很不方便 三.字节码文件的组成

Spring MVC源码详解

什么是Spring MVC &#xff1f; Spring MVC就是SpringMVC。 Spring就不介绍了&#xff0c;什么是MVC&#xff1f; M&#xff1a;模型&#xff0c;javabeanV&#xff1a;视图&#xff0c;如jspC&#xff1a;控制层&#xff0c;如Controller、Servlet SpringMVC请求处理流程 用…

图像处理(空域变换(上))

数字图像处理 参考视频 Task 1 亮度变换 1. 幂律变换 伽马变换本质上是对图像矩阵中的每个值进行幂运算。 幂函数 s c r γ ( r ∈ [ 0 , 1 ] ) \text{幂函数}scr^γ(r\in[0,1]) 幂函数scrγ(r∈[0,1]) 其中&#xff0c;r为灰度图像的输入值&#xff08;原来的灰度值&…

<数据集>集装箱缺陷识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;3793张 标注数量(xml文件个数)&#xff1a;3793 标注数量(txt文件个数)&#xff1a;3793 标注类别数&#xff1a;4 标注类别名称&#xff1a;[DAMAGE - DEFRAME, DENT, DAMAGE - RUST, DAMAGE - HOLE] 序号类别名…

高分六号卫星助力农业监测_卫星介绍_论文分享_数据获取

卫星遥感已经成为农业发展的重要支持工具。《“数据要素X”三年行动计划(2024-2026年)》指出,在现代农业交通运输、应急管理等领域鼓励探索利用遥感数据。为什么高分六号会经常应用于农业检测呢&#xff1f;本文将介绍高分六号卫星的农业检测特性、在农业应用中的优势、具体农业…