热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

设计从哪些角度出发,什么是设计观点

文章目录场景还原查找找原因从设计角度分析`T`和`?extendsT`同理扩展分析`T`和`?superT`结论场景还原一个java开发者在其开发的生涯中,难免会写

文章目录 场景还原查找找原因从设计角度分析`T`和`? extends T`同理扩展分析`T`和`? super T`结论

场景还原

一个java开发者在其开发的生涯中, 难免会写这样的代码

List ss = new ArrayList<>();// 父类对象的集合引用子类对象的集合List fs = ss;

也许你更多的遇到的是一个方法参数是List, 但是你传入了一个List进去, 但是无所谓了, 上面只是一个示例

然后编辑器就会爆红, 编译的时候也会抛出异常.

但是如果我们将代码写成下面的样子

List ss = new ArrayList<>();// 父类对象的集合引用子类对象的集合List fs = ss;

然后就发现, 编译正常通过, 运行也正常, 但是在我们在调用add() 方法, 往里面加对象时却发现, 编译器再次爆红

List ss = new ArrayList<>();// 父类对象的集合引用子类对象的集合List fs = ss;// 编译不通过, 爆红fs.add(new Father())

然后我们就特别不理解为什么会这样!

查找找原因

当我们上网查找原因时, 无非就是搜到

向上转型是安全的,向下转型是不安全的,除非你知道List中的真实类型,否则向下转型就会报错T 和 ? extends T 和 ? super T 的区别.上界, 下界之类的概念或是PECS之类的概念

Remember PECS: “Producer Extends, Consumer Super”.
“Producer Extends” - If you need a List to produce T values (you want to read Ts from the list), you need to declare it with ? extends T, e.g. List. But you cannot add to this list.
“Consumer Super” - If you need a List to consume T values (you want to write Ts into the list), you need to declare it with ? super T, e.g. List. But there are no guarantees what type of object you may read from this list.
If you need to both read from and write to a list, you need to declare it exactly with no wildcards, e.g. List.

老实说, 看了上面的乱起八糟的东西, 我是一脸懵逼, 最终捣鼓研究之后才明白, 哦! 原来是这种情况.

虽然这个设计理念很合理, 但是对于开发者学习来讲, 特别不友好, 我觉得很多人初学这些概念的时候也和我一样都是一脸懵逼的状态.

为了方便大家的理解, 我们站在设计者的角度上去考虑这样设计的缘由.

从设计角度分析T和? extends T

先来一个接口Fruit, 两个类 Apple 和 Pair 都是 Fruit 的实现类.

interface Fruit { }class Apple implements Fruit { }class Pair implements Fruit { }

我们都知道苹果是一个水果, 那么一箱苹果也是一箱水果

那么我们可以很自然的在java中这样设计;

因为苹果是一个水果

Apple apple = new Apple();Fruit fruit = apple;

所以一箱苹果也是一箱水果

List apples = new ArrayList();List fruits = (List) apples; // 假设这个是对的

可是这里就出现了一个问题

List apples = new ArrayList();我们需要把它看成两部分.
等号后面new ArrayList() 这是一个苹果篮子的实例对象, 它只能够放苹果, 不能够往里面加梨.
等号前面是它的引用, 当这个苹果篮子的实例对象被List类型引用时, 我们可以正常使用它, 我们可以通过add()方法往里面加苹果, 不能够往里面加梨.
但是当这个苹果篮子的实例对象被List类型引用时, 我们就可以往里面放Fruit, 那么我们既可以往里面放苹果, 也可以放梨.
然后就造成了一个设计上的bug, 我们往一个只能够放苹果的篮子里面放入了梨.

public void test1() { // 水果篮子, 不仅可以放苹果, 还可以放梨 List fruits = new ArrayList<>(); // 一篮水果, 只能放苹果 List apples = new ArrayList<>(); // 苹果是一个水果, 但是一箱苹果不是一箱水果 // xndcc把一篮苹果看成一篮水果, 那么这篮子水果里面岂不是可以放梨 List fruitList = (List) apples; // 编译错误 }

那么这样设计肯定是不行的, 它有一个冲突

苹果是一个水果, 那么一箱苹果也是一箱水果, 这个是我们的生活常识, 但是java的语言特性让我们不能够将List看成是List.但是在很多情况下, 我们的确需要将一箱苹果看作是一箱水果.

为了解决上面的冲突, 单凭一个List 肯定是不够的, 那么我们引入一个List.

我们可以这样
我们不能直接将List看成是List,
但是我们可以将List看作是List,
然后再将List看成是List.

List apples = new ArrayList();List fruits = apples; // errorList appleTs = apples;List fruitTs = appleTs;

我们将 List 看作是一个引用, 它引用的就是一箱水果(new ArrayList()), 没有其他的可能, 因此它里面可以放苹果, 可以放梨, 放桃子.
我们将 List 看作是一个引用, 它引用的可能是一箱只能放苹果的篮子, 也可能是一个只能放水果的篮子,

我们不能够确定它里面能够放什么东西, 因此我们干脆在设计的时候就禁止往它里面放东西.但是这里面却可以取东西, 因为不管这里面是一箱只能放苹果的篮子或是一个只能放水果的篮子, 从它里面取出来的必定是水果. 同理扩展分析T和? super T interface Fruit { default void fun1();}class Vegetable { void fun2(){}}class Tomato extends Vegetable implements Fruit {}

对于一个Fruit实例对象来讲, 其中只有 fun1() 方法.
但是如果 List 中存入的可以看作是 Tomato, Tomato 既有 fun1() 方法, 也有fun2().
但是如果List 实际引用的对象是一个List, 那么List通过get()获取到的"Tomato"对象实际上是一个Vegetable的实体类, 那么调用"Tomato"的fun1(), 实际上调用的是Vegetable中的 fun1(), 但是 Vegetable中没有fun1(), 因此会出现冲突.
List 中的实例我们不知道是什么, 但是只要是实例, 那么就是一个Object

因此,set()方法正常,但get()只能存放Object对象里

List fruits = new ArrayList<>();List list = fruits;list.get(0).fun2(); // 获取到的是Fruit实例, 但是Fruit里面没有fun2()函数 结论

List 之间是不能相互引用转换的(List 和 List 不能相互转换).
为了使集合模板相互转换, 我们可以先将 List 变成 List 或 List, 然后进行引用转换.

实际上无论是方法传参还是引用赋值, 你会发现无非就是引用之间的关系转换.

我们抛开对象, 只看引用关系, 用代码表示那么就是下面的样子

interface Fruit { } static class Apple implements Fruit { } public static void extendsTest(String[] args) { List apples = null; List fruits = null; List appleExtends = null; List fruitExtends = null; // List 可以转化为 List appleExtends = apples; // List 可以转化为 List fruitExtends = apples; // List 可以转化为 List fruitExtends = appleExtends; // 可以正常 get final Apple apple = appleExtends.get(0); // 添加对象特爆红, 编译不通过 appleExtends.add(new Apple()); // error } public static void superTest(String[] args) { List apples = null; List fruits = null; List fruitSupers = null; List appleSupers = null; // List 可以转化为 List appleSupers = apples; // List 可以转化为 List fruitSupers = fruits; // List 可以转化为 List appleSupers = fruitSupers; // 添加对象正常 appleSupers.add(new Apple()); // 获取对象必须使用 Object 引用 final Object object = appleSupers.get(0); }

转换为图片就是下面的关系


接下来看PECS是不是就很清晰了呢?

Remember PECS: “Producer Extends, Consumer Super”.
“Producer Extends” - If you need a List to produce T values (you want to read Ts from the list), you need to declare it with ? extends T, e.g. List. But you cannot add to this list.
“Consumer Super” - If you need a List to consume T values (you want to write Ts into the list), you need to declare it with ? super T, e.g. List. But there are no guarantees what type of object you may read from this list.
If you need to both read from and write to a list, you need to declare it exactly with no wildcards, e.g. List.


推荐阅读
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • 应用链时代,详解 Avalanche 与 Cosmos 的差异 ... [详细]
  • 深入解析 Lifecycle 的实现原理
    本文将详细介绍 Android Jetpack 中 Lifecycle 组件的实现原理,帮助开发者更好地理解和使用 Lifecycle,避免常见的内存泄漏问题。 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 多线程基础概览
    本文探讨了多线程的起源及其在现代编程中的重要性。线程的引入是为了增强进程的稳定性,确保一个进程的崩溃不会影响其他进程。而进程的存在则是为了保障操作系统的稳定运行,防止单一应用程序的错误导致整个系统的崩溃。线程作为进程的逻辑单元,多个线程共享同一CPU,需要合理调度以避免资源竞争。 ... [详细]
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 本文详细介绍了MySQL数据库的基础语法与核心操作,涵盖从基础概念到具体应用的多个方面。首先,文章从基础知识入手,逐步深入到创建和修改数据表的操作。接着,详细讲解了如何进行数据的插入、更新与删除。在查询部分,不仅介绍了DISTINCT和LIMIT的使用方法,还探讨了排序、过滤和通配符的应用。此外,文章还涵盖了计算字段以及多种函数的使用,包括文本处理、日期和时间处理及数值处理等。通过这些内容,读者可以全面掌握MySQL数据库的核心操作技巧。 ... [详细]
  • 如何使用 `org.opencb.opencga.core.results.VariantQueryResult.getSource()` 方法及其代码示例详解 ... [详细]
  • NFT市场热度持续攀升,波卡能否抓住机遇迎来NFT夏季热潮?
    NFT市场热度持续攀升,波卡能否抓住机遇迎来NFT夏季热潮? ... [详细]
  • Spring框架中枚举参数的正确使用方法与技巧
    本文详细阐述了在Spring Boot框架中正确使用枚举参数的方法与技巧,旨在帮助开发者更高效地掌握和应用枚举类型的数据传递,适合对Spring Boot感兴趣的读者深入学习。 ... [详细]
  • 深入解析Android 4.4中的Fence机制及其应用
    在Android 4.4中,Fence机制是处理缓冲区交换和同步问题的关键技术。该机制广泛应用于生产者-消费者模式中,确保了不同组件之间高效、安全的数据传输。通过深入解析Fence机制的工作原理和应用场景,本文探讨了其在系统性能优化和资源管理中的重要作用。 ... [详细]
  • 深入解析CAS机制:全面替代传统锁的底层原理与应用
    本文深入探讨了CAS(Compare-and-Swap)机制,分析了其作为传统锁的替代方案在并发控制中的优势与原理。CAS通过原子操作确保数据的一致性,避免了传统锁带来的性能瓶颈和死锁问题。文章详细解析了CAS的工作机制,并结合实际应用场景,展示了其在高并发环境下的高效性和可靠性。 ... [详细]
author-avatar
V陈冬梅_717
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有