清朝云资源网

作者: 清朝云

  • 11月29日,星期五, 每天60秒读懂全世界!

    百度热搜新闻

    新闻来源:百度热搜榜

    1. 俄罗斯卢布大贬值 11月27日,俄罗斯卢布出现恐慌性抛售,卢布兑美元汇率盘中一度跌超8.5%,达到1美元兑114.75卢布,创出一年来新低。

    2. 董宇辉撞脸山东博物馆镇馆之宝

    3. 物流成本降低体现在哪? 11月27日,《有效降低全社会物流成本行动方案》对外发布。行动方案从多方面对物流成本“怎么降、降什么”进行了系统部署。

    4. 卖不动的HPV疫苗 瞄向了男人 据悉,已经跌成白菜价的HPV疫苗,将箭头瞄准了男性。记者发现,在一些健康平台上,已经出现了男性九价HPV疫苗预约订阅页面。

    5. 台学生穿“中国”字样服饰找马龙签名 11月28日,中国台湾,马龙以大学生身份赴台交流,身穿中国球衣的青年拿到龙队全场第一个签名。

    6. 业内:A股即将进入新周期

    7. 载有500多条狗的货车被截停 25日,河南驻马店正阳县一辆载有500多条狗的货车被交警截停。货车上的两人被警方带走,狗被转移到当地农业农村局暂时安置。

    8. 王境泽帮父亲承担200万生意亏损 近日,王境泽在采访中提到,爸爸曾经做生意赔了200万,自己义不容辞担了起来,最后回来的机票钱都是他掏的。

    9. 胖东来回应1件羽绒服利润仅3毛

    10. 8城人均存款超15万 2024统计年鉴显示:北京、上海人均存款余额超过了20万元,还有8座城市的人均存款余额超过了15万元。

    —- 百度热搜新闻 End —-

    知乎新闻

    新闻来源:知乎日榜

    标题: 《西游记》里的唐僧、孙悟空都成佛了,为什么猪八戒成了净坛使者,沙和尚成了金身罗汉?
    链接: https://daily.zhihu.com/story/9777006
    ———————-
    标题: 是否地球大多数动物的嘴是上下开合的?为什么鲜有左右开合?
    链接: https://daily.zhihu.com/story/9777145
    ———————-
    标题: 如果在距离地球六千五百万光年的地方看向地球,能看得见恐龙吗?
    链接: https://daily.zhihu.com/story/9777148
    ———————-
    标题: 瞎扯 · 如何正确地吐槽
    链接: https://daily.zhihu.com/story/9777222
    ———————-

    —- 知乎新闻 End —-

    IT之家新闻

    新闻来源:ITHome之家科技新闻

    标题: 华为 Mate 60 / Pura 70 等设备将适配 Mate70 同款“手势隔空传送文件”功能,店内展示机已支持
    发布时间: 2024-11-28T15:45:37.103
    新闻简介: 华为官方在此前预热了一项新功能“手势隔空传送文件”,IT之家实测,搭载最新版本 HarmonyOS NEXT 系统的 Mate 60、Pura 70 等设备也支持该功能。
    ———————-
    标题: 售价超百万,鸿蒙智行尊界 S800 开启预订 48 小时订单达 2108 台
    发布时间: 2024-11-28T19:33:36.043
    新闻简介: 今日鸿蒙智行宣布,开启预订 48 小时内,预计售价 100-150 万元的尊界 S800 预订量已达 2108 台。
    ———————-
    标题: 华为 Mate 60 / X5 等系列手机升级 HarmonyOS 5.0 及以上版本支持 L2HC 4.0,传输码率可达 2.3Mbps
    发布时间: 2024-11-28T17:44:40.6
    新闻简介: IT之家注意到,Mate XT、Mate 60 系列、Mate X5 系列、Pura 70 系列(标准版不支持)、Pocket 2 等设备在更新 HarmonyOS 5.0 及以上版本后也将支持 L2HC 4.0(可达 2.3Mbps 码率)。
    ———————-
    标题: 全新纯电动品牌“烨 P”命名遭吐槽,本田发起舆情问卷调查
    发布时间: 2024-11-28T18:18:37.86
    新闻简介: 今日汽车博主 @SugarDesign 放出一张图片,暗示本田似乎已注意到其新能源品牌命名问题,并展开了相关调查。图片是一份名为“本田中国媒体反馈调查问卷”的截图,本田中国询问受访者:“您认为‘烨’品牌发布后的舆情是否影响了您对本田品牌的整体印象,以及出现这些舆情的可能原因。”这一表述表明,本田官方已经意识到新品牌名称未能获得广泛认可。
    ———————-
    标题: vivo S20 / Pro 手机发布,2299 元 / 3399 元起
    发布时间: 2024-11-28T19:53:34.96
    新闻简介: 配色上,标准版包括凤羽金、玉露白、松烟墨;Pro 版包括凤羽金、紫气东来、松烟墨。其中,vivo S20 Pro搭载了全等深微四曲屏,vivo S20标准版则是“超薄直屏”。
    ———————-
    标题: 研究:约 10% 的程序员几乎什么都不做,却正常拿报酬
    发布时间: 2024-11-28T17:35:45.907
    新闻简介: 斯坦福大学对数百家公司的 50,000 多名软件工程师进行的一项研究发现,约 9.5% 的软件工程师属于“幽灵员工”。他们虽然在公司占着职位,但工作效率极低,不到平均水平的十分之一,几乎对团队没有任何实质性贡献,却依然领着全额薪水。
    ———————-
    标题: 某厂天玑 9400 小迭代 S 工程机参数曝光,预计为 vivo X200s 系列
    发布时间: 2024-11-28T11:42:14.973
    新闻简介: 博主 @数码闲聊站 今日曝光一款天玑 9400 小迭代 S 工程机参数,评论区网友推测了为 vivo X200s 系列。该机搭载天玑 9400 处理器,6.67 英寸 1.5K+120Hz LTPS 四窄边纯直屏,金属中框 + 玻璃机身。
    ———————-
    标题: 马斯克母亲不满外界对儿子的评价:富豪这词很贬低人,我认为埃隆是天才
    发布时间: 2024-11-28T08:36:12.47
    新闻简介: 她对主持人说,“我认为他是世界的天才,人们爱他就是因为他所做的事情,也因此尊重他。而我为他感到无比自豪。”
    ———————-
    标题: 生态伙伴集体呼吁升级原生鸿蒙,华为推近千元先锋权益
    发布时间: 2024-11-28T10:27:01.26
    新闻简介: 11 月 26 日,HUAWEI Mate 70 系列与 HUAWEI Mate X6 正式发布,到手即可升级 HarmonyOS NEXT。同时,余承东在华为 Mate 品牌盛典上宣布,2025 年华为推出的所有新手机和平板将全部搭载原生鸿蒙
    ———————-
    标题: 快手电商废止“退款不退货服务规则”
    发布时间: 2024-11-28T13:51:58.383
    新闻简介: 快手电商 11 月 25 日发布公告称,因业务内容调整,经平台综合考虑决策,快手电商现针对“退款不退货服务”进行废止,后续将推出更好的服务产品供大家使用。
    ———————-
    标题: 2299 元起,魅族 21 手机全版本限时优惠 600 元
    发布时间: 2024-11-28T15:26:14.58
    新闻简介: 星纪魅族科技宣布,今日(11 月 28 日)起,魅族 21 手机全版本限时优惠 600 元,魅族 21 Note 手机全版本限时优惠 200 元,活动 11 月 30 日截止。
    ———————-
    标题: 比亚迪仰望 U9 纯电超跑将全量推送“飞跃”功能
    发布时间: 2024-11-28T10:40:52.963
    新闻简介: 注意事项显示,该功能仅做娱乐需求使用,在车辆行驶过程中,可使车辆向前飞跃一段距离,提升驾驶的乐趣,禁止用于飞跃障碍物、坑洼、斜坡等实用场景,以防出现人身或财产损失。
    ———————-

    —- IT之家新闻 End —-

  • 11月28日,星期四, 每天60秒读懂全世界!

    百度热搜新闻

    新闻来源:百度热搜榜

    1. 董浩叔叔全网寻人!8090后集体破防 近日,董浩叔叔发布视频,寻找当年写过信的小朋友,其中一封信来自湖南郴州实验小学,如今写信的同学已成为郴州苏仙中学的副校长。

    2. 碎片化睡眠危害等同于熬夜 近日,有专家表示,“碎片化睡眠”会造成机体代谢紊乱,其危害程度等同于熬夜。

    3. 本轮寒潮要结束了?专家分析 本轮冷空气持续时间长、降温降雨雪的强度较强,多个地区日降水量都突破了历史同期极值,是什么原因导致冷空气影响时间长?后续全国天气情况又是怎样的趋势?看专家分析。

    4. 黄圣依被麦琳气翻白眼了 近日,《再见爱人4》中,麦琳说赡养父母取决于自己的经济状况,黄圣依气到直接翻了个白眼。

    5. 报告称我国居民退休准备不足 11月27日,有报告显示,中国居民退休准备指数为5.53,表明我国居民退休准备不足,需要引起关注并采取相应措施。

    6. 伊能静庾澄庆之子谈父母离婚 日前,伊能静庾澄庆之子庾恩利在一期节目中聊到了自己的原生家庭。他认为,父母离婚是一件不错的事情。

    7. 邱淑贞携夫带女现身尼泊尔

    8. 花2万买保时捷小卡宴车标竟是驴 近日,据媒体报道,河南李大叔花2万买了辆“保时捷小卡宴”,厂家给他发的车标竟然是“保时捷驴”。经协调,厂家已给李大叔退款。

    9. 乌克兰开除2.3万大学生

    10. 全球最年长男子去世历经24任英国首相

    —- 百度热搜新闻 End —-

    知乎新闻

    新闻来源:知乎日榜

    标题: 现在的恐龙复原图会不会跟实际比相差甚远?
    链接: https://daily.zhihu.com/story/9776996
    ———————-
    标题: 为什么外国人爬的山相对原始,我们中国人爬的山多数都有台阶?
    链接: https://daily.zhihu.com/story/9777135
    ———————-
    标题: 如果把牛顿关在一个有电脑的房间里,他多久能发现如何上网冲浪打游戏?
    链接: https://daily.zhihu.com/story/9777142
    ———————-
    标题: 瞎扯 · 如何正确地吐槽
    链接: https://daily.zhihu.com/story/9777216
    ———————-

    —- 知乎新闻 End —-

    IT之家新闻

    新闻来源:ITHome之家科技新闻

    标题: 小米 REDMI K80 Pro 手机发布:搭载骁龙 8 至尊版、全焦段旗舰三摄,售价 3699 元起
    发布时间: 2024-11-27T19:56:18.567
    新闻简介: 在今晚的 REDMI K80 大满贯双旗舰新品发布会上,REDMI 正式发布了 REDMI K80 Pro 手机,号称“REDMI 新十年诚意之作”,售价3699元起。#REDMI##K80##樊振东同款K80今晚见#
    ———————-
    标题: REDMI K80 手机发布:6550mAh“小米最大”金沙江电池,2499 元起
    发布时间: 2024-11-27T20:22:42.917
    新闻简介: 小米REDMI K80手机今晚正式发布,提供5款不同配置,售价区间为2499-3599元。#REDMI##K80##樊振东同款K80今晚见#
    ———————-
    标题: 消息称支持升级 HarmonyOS NEXT 系统的机型不会获鸿蒙 4.3 版本更新
    发布时间: 2024-11-27T06:50:23.1
    新闻简介: 博主 @Adak封狼居胥 昨日发文透露,鸿蒙 HarmonyOS 4.3 版本主要为新机和 NEXT 设备互联特性做适配。已在 NEXT 升级计划序列的机型不会推送 4.3 版本。
    ———————-
    标题: 2499 元起正式开抢:REDMI K80 / Pro 手机首发赠碎屏保 + 五年电池保
    发布时间: 2024-11-27T20:27:16.977
    新闻简介: REDMI K80、REDMI K80 Pro 手机京东自营现已上架,今晚 20:40 开启预售
    ———————-
    标题: 江苏省消费补贴宣布扩容:新增手机等 27 类立减 15%,每件不超过 1500~2000 元
    发布时间: 2024-11-27T12:32:36.803
    新闻简介: 自通告发布之日起,新增 20 类家电商品按成交价格(商家优惠折扣后)的 15% 给予直接补贴,每件补贴不超过 2000 元(不往前追溯)。
    ———————-
    标题: 华为 Mate 70 系列 4 款机型通过开源鸿蒙兼容性测评,操作系统版本号 OpenHarmony 5.0.0 Release
    发布时间: 2024-11-27T15:47:39.047
    新闻简介: 华为 Mate 70、Mate 70 Pro、Mate 70 Pro+ 和 Mate 70 RS 非凡大师 4 款机型于今年 11 月 23 日通过开源鸿蒙兼容性测评,评测结果显示上述机型的操作系统版本号为 OpenHarmony 5.0.0 Release。
    ———————-
    标题: 华为 Mate 70 系列 / Mate X6 手机支持智感扫码功能,适配微信、支付宝、钱包 3 种付款方式
    发布时间: 2024-11-27T08:32:10.397
    新闻简介: IT之家注意到,华为官网现对手机智感扫码功能适配情况作出更新:Mate 70 系列 / Mate X6 手机均适配智感扫码功能,支持微信、支付宝、钱包 3 种付款方式。
    ———————-
    标题: 赵明:荣耀独立出来了,所有的技术创新都是自己的
    发布时间: 2024-11-27T16:23:33.217
    新闻简介: 荣耀 CEO 赵明今日在直播中对于外界调侃的荣耀与华为共用图纸进行了回应,他称荣耀独立出来了,所有的技术创新都是自己的。
    ———————-
    标题: 华为 Mate 70 系列手机通过 UFCS 融合快充协议认证
    发布时间: 2024-11-27T07:43:39.483
    新闻简介: 型号为“PLR-AL00”“PLA-AL10”“PLU-AL10”以及“CLS-AL00”的华为手机设备已通过 UFCS 融合快充协议认证,其型号归属华为 Mate 70 系列手机。
    ———————-
    标题: 小米 REDMI K80 Pro 冠军版手机发布:赛道勇者灵感设计,4999 元
    发布时间: 2024-11-27T20:01:22.023
    新闻简介: 在今晚举行的 REDMI K80 大满贯双旗舰新品发布会上,REDMI K80 Pro 冠军版正式发布,有深灰色、绿色两个版本,16GB+1TB 版本售价 4999 元。#REDMI##K80##樊振东同款K80今晚见#
    ———————-
    标题: 【IT之家评测室】REDMI K80 Pro 手机体验:新十年的起点,不一样的旗舰
    发布时间: 2024-11-27T20:56:20.493
    新闻简介: 11 月 27 日晚,REDMI 举办新品发布会,推出了备受期待的全新 K80 系列新机。目前,IT之家已经拿到了 16GB 内存 + 1TB 存储雪岩白版本的 REDMI K80 Pro 手机 #K80大满贯首发评测#
    ———————-
    标题: 小米 REDMI K80 Pro 冠军版手机亮相,再度联名兰博基尼汽车
    发布时间: 2024-11-27T09:12:59.16
    新闻简介: 该机再度联名兰博基尼汽车 SQUADRA CORSE(IT之家注:兰博基尼的赛事运动部门),手机后盖部分印有联名 Logo,以及 Y 字形线条。
    ———————-

    —- IT之家新闻 End —-

  • Docker 搭建中继服务器 derp – 纯 IP 实现


    共计 1908 个字符,预计需要花费 5 分钟才能阅读完成。

    1. 纯IP搭建关键

    接之前 Docker 搭建中继服务器 derp,上次的文章需要有域名,并且申请好 ssl 证书。

    原因在于 derp 服务器,在 tailscale/cmd/derper/cert.go 文件中需要验证域名,如果将验证的代码去掉,那么 derp 将不会出现证书不匹配的错误,也就是说此时可以使用 IP或者域名都不会报错。

    func (m *manualCertManager) getCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
            // 需要将下面的判断注释
            //if hi.ServerName != m.hostname {
            //      return nil, fmt.Errorf("cert mismatch with hostname: %q", hi.ServerName)
            //}
    
            // Return a shallow copy of the cert so the caller can append to its
            // Certificate field.
            certCopy := new(tls.Certificate)
            *certCopy = *m.cert
            certCopy.Certificate = certCopy.Certificate[:len(certCopy.Certificate):len(certCopy.Certificate)]
            return certCopy, nil
    }

    另外需要注意的是,tailscale 客户端也会进行证书验证,由于 derp 服务器生成的自签名证书肯定不可能与我们所使用的 IP或者域名匹配,所以客户端直接连接 derp 肯定也会报错,所以需要客户端跳过域名验证。

    总结来说,需要满足以下两点:

    • derp 服务器不验证域名
    • tailscale 不验证域名

    2. derp 搭建

    使用如下命令即可搭建一个不验证域名的 derp 服务器。

    docker run -d \
    --name derper \
    --restart always  \
    -p 12345:443 \
    -p 3478:3478/udp  \
    -d hausen1012/ip_derper

    此时浏览器访问 https://IP:12345/ 将提示不安全的链接,因为域名不匹配。

    Docker 搭建中继服务器 derp - 纯 IP 实现

    />

    正因为如此,才需要配置 tailscale 跳过域名验证,否则还是不能用 derp。

    3. 配置headscale

    除了 derper 之外,Tailscale 客户端还需要跳过域名验证,这个需要在 Headscale 中配置。

    而 Headscale 的本地 YAML 文件即 derp.yaml 目前还不支持这个配置项,所以只能使用在线 URL 了,就和官方提供的 derp 服务器一样,也是给了一个 url 地址。

    新建 derp.json

    {
      "Regions": {
        "901": {
          "RegionID": 901,
          "RegionCode": "hh",
          "RegionName": "Hotiis Hongkong",
          "Nodes": [
            {
              "Name": "901",
              "RegionID": 901,
              "DERPPort": 12345,
              "StunPort": 3478,
              "StunOnly": false,
              "HostName": "xx",
              "InsecureForTests": true
            }
          ]
        }
      }
    }

    HostName 填写 IP 或域名,最主要的就是配置 InsecureForTests 选项,目前 derp.yaml 中不支持,需要使用在线文件,所以需要将这个文件放在 nginx 或者其他web服务器下。

    接下来修改 headscale 的配置文件:

    # /etc/headscale/config.yaml
    derp:
      # xxxx
      urls:
      #  - https://controlplane.tailscale.com/derpmap/default
        - http://xxx/derp.json
      paths:
      #  - /etc/headscale/derp.yaml
      # xxxx
      auto_update_enabled: true
      # xxxx
      update_frequency: 24h

    http://xxx/derp.json 修改为可访问的在线链接,不能仅局域网可访问。

    接下来重启 headscale 即可。

    docker restart headscale

    4. 验证

    使用 netcheck 命令仅仅只是验证 3478 端口能通。

    Docker 搭建中继服务器 derp - 纯 IP 实现

    />

    当使用 web 页面访问 derp 时,会有 http: TLS handshake error 相关的错误,这个不影响使用。

    如果看见 client [xxx]: readFrameHeader 相关的信息时,说明客户端可以连接上 derp 服务器了。

    Docker 搭建中继服务器 derp - 纯 IP 实现

    />

    通过 status 命令可以查看使用的中转服务器,使用 tailscale ping 命令也可以查看使用哪个中转服务进行转发。

    Docker 搭建中继服务器 derp - 纯 IP 实现

    提醒:本文发布于513天前,文中所关联的信息可能已发生改变,请知悉!

    />

    Tips:清朝云网络工作室

  • Docker 自建超实用的导航网址 onenav


    共计 894 个字符,预计需要花费 3 分钟才能阅读完成。

    为什么选择onenav

    在之前,清朝云也搭建过导航网址,不过当时使用的 wordpress 搭建,并使用的一为导航主题。

    但是后来发现,这个导航网站的作用并不大,因为搭建以后我并没有去管理过它,毕竟因为添加那么多链接是一件很心累的事。

    此外,wordpress 搭建静态导航网址,实在是大材小用,占用内存也比较高,还需要用到 mysql。

    直到发现了 onenav 这款导航,配合 baisu 主题,我觉得它真正做到了我需要的东西。

    onenav优势

    首先,清朝云觉得首先最有用的,就是前端添加和编辑分类和链接,这一点 wordpress 导航主题我没有遇到过的,可能我解除得不多。

    Docker 自建超实用的导航网址 onenav

    />

    添加分类时可以选择分类图标,设置权重以及是否私有等。

    Docker 自建超实用的导航网址 onenav

    />

    添加书签链接也和分类类似,可以设置权重以及是否私有,这样可以让自己常用的链接排在前面,更容易找到。

    第二,onenav 还能在浏览器任意页面快速添加收藏,只需要安装 onenav 插件即可。

    Docker 自建超实用的导航网址 onenav

    />

    第三,就是上面提到的,可以设置分类或者链接为私有。

    Docker 自建超实用的导航网址 onenav

    />

    如果未登录就看不见上面设置为私有的分类了。

    其他的话,onenav 后台设置相对比较简单。

    Docker 自建超实用的导航网址 onenav

    />

    搭建

    搭建命令如下:

    docker run -d \
    --name=onenav \
    -p 80:80 \
    -v /home/docker/onenav:/data/wwwroot/default/data \
    helloz/onenav:0.9.35

    data 目录是 sqlite 数据库和配置文件存放的地址,如果有新的主题可以在其文件夹下新建 templates 文件夹,并解压后放入。

    清朝云多映射了一个文件夹,这个是用来放静态文件的,比如网站图标、logo等。

    最后

    并且由于 onenav 和 baisu 主题不是一个作者,所以多少还是会有点问题,这是一个比较麻烦的点,所以如果你自己动手能力比较强可以对主题进行一定的修改。

    清朝云对主题做了一定的修改和功能增强,基本是修改了 index.php 文件和相关 js 文件,比如修复主题设置的 header 和 footer 不生效,增加图标懒加载功能,固定手机端的 header 和修复一些其他问题。

    但是由于没有系统的整理相关修改,并且比较零散不好分享,如果有需要可以在评论区评论。

    提醒:本文发布于506天前,文中所关联的信息可能已发生改变,请知悉!

    Tips:清朝云网络工作室

  • Java 彻底理解两种动态代理


    共计 2523 个字符,预计需要花费 7 分钟才能阅读完成。

    java 中,有两种常见的动态代理实现方式,即基于接口的动态代理(JDK 动态代理)和基于类的动态代理(CGLIB 动态代理),下面就对这两种代理进行解释和举例。

    1. JDK动态代理

    这种动态代理实现方式是基于接口的,使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来创建代理对象。代理对象实现了目标接口,并将方法调用委托给提供的 InvocationHandler 实现类来处理。这种方式要求目标类实现接口,因此只能代理接口定义的方法

    这里最重要的就是需要实现 InvocationHandler 接口,在 invoke 方法中添加增强代码,而目标代码的执行就一行代码 res = method.invoke(target,args);

    代码如下:

    动态代理类:MyInvocationHandler.java

    public class MyInvocationHandler implements InvocationHandler {
    
        private Object target;
    
        public MyInvocationHandler() {
        }
    
        public MyInvocationHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            // 通过代理对象执行方法时候,会调用执行这个invoke方法
    
            Object res = null;
    
            System.out.println("目标方法执行时间:" + new Date());
            // 执行目标类方法,通过method类实现
            res = method.invoke(target,args); //实现SomeServiceImpl.doOthers() doSome()方法
            System.out.println("提交事务....");
    
            //目标方法执行结果
            return res;
        }
    }

    接口类:SomeService.java

    public interface SomeService {
    
        public void doSome();
    }

    接口实现类:SomeServiceImpl.java

    public class SomeServiceImpl implements SomeService {
    
        @Override
        public void doSome() {
            System.out.println("执行doSome方法");
        }
    }

    测试类:Test.java

    public class Test {
    
        /**
         * 测试动态代理
         */
        public static void main(String[] args) {
            // 创建目标类
            SomeService target = new SomeServiceImpl();
    
            // 创建InvocationHandler对象
            InvocationHandler handler = new MyInvocationHandler(target);
    
            // 使用Proxy创建代理
            SomeService proxy = (SomeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),handler);
    
            proxy.doSome();
        }
    }

    2. CGLIB动态代理

    这种动态代理实现方式使用第三方库(如 CGLIB)来生成代理类。CGLIB(Code Generation Library)通过继承目标类创建代理对象,代理对象可以直接调用目标类的方法,并在方法调用前后执行自定义的逻辑。相比于 JDK 动态代理,CGLIB 动态代理可以代理具体类而不仅限于接口。

    具体类:SomeService.java

    public class SomeService {
    
        public void doSome() {
            System.out.println("执行doSome方法");
        }
    }

    拦截器类:SomeServiceInterceptor.java

    public class SomeServiceInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("目标方法执行时间:" + new Date());
            Object result = proxy.invokeSuper(obj, args);
            System.out.println("提交事务....");
            return result;
        }
    }

    测试类:Test.java

    public class Test {
    
        /**
         * 测试动态代理
         */
        public static void main(String[] args) {
            // CGLIB 动态代理的核心类
            Enhancer enhancer = new Enhancer();
            // 设置需要动态代理的类,代理类将继承此类
            enhancer.setSuperclass(SomeService.class);
            // 设置拦截器
            enhancer.setCallback(new SomeServiceInterceptor());
            // 创建代理类
            SomeService proxy = (SomeService) enhancer.create();
            proxy.doSome();
        }
    }

    如果需要判断类是由哪种动态代理生成,打印类名即可判断。

    在 JDK 动态代理中,生成的代理类名通常遵循以下模式:$ProxyN,其中 N 是一个递增的数字。例如,$Proxy0、$Proxy1 等。

    在 CGLIB 动态代理中,生成的代理类名通常遵循以下模式:SomeService$$EnhancerByCGLIB$$RandomNumber,其中 SomeService 是原始类的名字,RandomNumber 是一个随机生成的数字。

    提醒:本文发布于498天前,文中所关联的信息可能已发生改变,请知悉!

    Tips:清朝云网络工作室

  • 11月27日,星期三, 每天60秒读懂全世界!

    百度热搜新闻

    新闻来源:百度热搜榜

    1. 地铁疑因钉子户改线 花5亿拆迁一半 武汉多家自媒体发文称:武汉地铁11号线原计划从小区地下穿过,因少部分业主拒绝签约,导致部分房屋无法拆除,地铁线路因此改线。

    2. 东北局地特大暴雪已破纪录

    3. 寒潮≠冷空气?“冷知识”了解一下 今年下半年的首场寒潮给全国多地带来雨雪降温和大风天气,那么究竟什么是寒潮天气?寒潮是如何形成的?我们又该如何防范寒潮影响?一起来了解一下寒潮“冷知识”。

    4. 菲前总统炮轰现总统吸毒成瘾

    5. 日本近海发生6.3级地震 当地时间11月26日22时47分,据中国地震台网正式测定,在日本本州西岸近海发生6.3级地震,震源深度60公里。

    6. #北京飘雪了#

    7. 余承东说Mate70对得起那四个字

    8. 殡仪馆被曝用炖盅当骨灰瓮 近日,广东一先生反映在肇庆德庆县殡仪馆办丧事时,骨灰瓮是炖汤盅。当地民政局回应:馆内剩余的同款式“骨灰瓮”已全部废弃不用。

    9. 失联5个多月后,马天宇回来了

    10. 微信回应14天自动清理原图

    —- 百度热搜新闻 End —-

    知乎新闻

    新闻来源:知乎日榜

    标题: 孟德尔做实验的时候吃豌豆吗?
    链接: https://daily.zhihu.com/story/9776992
    ———————-
    标题: 自己的哥哥是皇帝,称之为「皇兄」;如果自己的弟弟是皇帝,应该如何称呼?
    链接: https://daily.zhihu.com/story/9777115
    ———————-
    标题: 哈利波特决斗厉害吗?
    链接: https://daily.zhihu.com/story/9777125
    ———————-
    标题: 瞎扯 · 如何正确地吐槽
    链接: https://daily.zhihu.com/story/9777212
    ———————-

    —- 知乎新闻 End —-

    IT之家新闻

    新闻来源:ITHome之家科技新闻

    标题: 华为 Mate70 / Pro / Pro+ 系列旗舰手机发布:业界首发高亮钛玄武机身、红枫原色影像,5499 元起
    发布时间: 2024-11-26T15:47:36.567
    新闻简介: 华为 Mate70 / Pro / Pro+ 系列业界首发高亮钛玄武机身,后盖采用锦纤材质,相比上一代更耐磨、耐摔等,支持 IP68、IP69 防尘防水。#华为发布会##华为mate70价格##华为mate70#
    ———————-
    标题: 华为 Mate70 / Pro / Pro+ 系列旗舰手机亮相,余承东豪言“靠抄袭是没有未来的”
    发布时间: 2024-11-26T14:55:28.8
    新闻简介: 华为 Mate70 Pro+、Mate70 Pro、Mate70 均有四个配色版本,如下图所示。余承东还在演讲中豪言“靠抄袭是没有未来的”。#华为Mate70# #华为Mate品牌盛典# #华为发布会#
    ———————-
    标题: 华为 Mate 70 / X6 提供鸿蒙 4.3 / 原生鸿蒙 5.0 选择,明年华为推出的所有新手机平板将全部搭载原生鸿蒙
    发布时间: 2024-11-26T15:51:12.933
    新闻简介: 在目前正在进行的华为 Mate 品牌盛典上,华为称两三个月后鸿蒙生态将会更加完善,将会为用户带来更流畅、更安全更智能的原生体验。
    ———————-
    标题: 18:08 开抢:华为 Mate 70 / X6 旗舰手机即将预售,半年内限购 1 单
    发布时间: 2024-11-26T16:46:29.957
    新闻简介: 华为 Mate 70 /X6 期间手机将在今晚 18:08 预售,每人每 180 天内至多可以下 1 单。
    ———————-
    标题: Mate70 系列全球首发卫星寻呼,还是华为首款 Wi-Fi 7 手机
    发布时间: 2024-11-26T15:03:03.993
    新闻简介: 该技术支持 120° 超广对星角度、9 秒超快连星、0.7 秒超低时延。#华为发布会##华为mate70##华为mate70价格#
    ———————-
    标题: 华为 Mate X6 折叠屏手机正式发布,售价 12999 元起
    发布时间: 2024-11-26T15:52:23.167
    新闻简介: 在今天下午举行的华为 Mate 品牌盛典上,华为新款可折叠智能手机华为 Mate X6 正式发布,售价12999元起。#华为Mate70# #华为Mate品牌盛典# #华为发布会#
    ———————-
    标题: 11999/12999 元华为 Mate70 RS 非凡大师发布:双层 OLED 显示屏,首发瑞声科技高低频双振膜立体声单元
    发布时间: 2024-11-26T15:47:54.667
    新闻简介: 华为 Mate70 系列正式发布,其中 Mate70 Pro RS 提供两个版本#华为Mate70# #华为Mate品牌盛典# #华为发布会#
    ———————-
    标题: 华为余承东:发布会刚结束有人给我转了 6 万元,要订 3 台尊界 S800
    发布时间: 2024-11-26T18:26:07.63
    新闻简介: 在今日的华为 Mate 品牌盛典上,鸿蒙智行尊界 S800 开启预订,意向金 2 万元,预计售价 100-150 万元。刚刚华为常务董事、终端 BG 董事长、智能汽车解决方案 BU 董事长@余承东 在微博发文称:“发布会刚结束,一看手机,有朋友直接微信转账了 6 万给我,要订 3 台尊界!”#华为Mate70# #华为Mate品牌盛典# #华为发布会#
    ———————-
    标题: 华为 Mate70 / Pro 系列首发红枫原色影像:150 万多光谱通道,还原真实色彩
    发布时间: 2024-11-26T14:59:10.587
    新闻简介: 亮点包括高亮钛玄武架构、第二代灵犀通信、业界首发卫星寻呼、100W 有线 + 80W 无线快充、红枫原色影像等等。#华为Mate70# #华为Mate品牌盛典# #华为发布会#
    ———————-
    标题: 消息称苹果 iPhone 17 Pro 系列将迎来“重大设计变更”,高端产品回归铝制机身
    发布时间: 2024-11-26T06:34:52.393
    新闻简介: 苹果自 2017 年开始从 iPhone 8 和 iPhone X 引入玻璃背板,而在此之前几乎所有 iPhone 都采用的是铝制背板。
    ———————-
    标题: 华为 Mate 70 Pro 机型支持选购“鸿蒙 NEXT 先锋版”,出厂系统为 HarmonyOS 5.0
    发布时间: 2024-11-26T19:14:18.713
    新闻简介: IT之家注意到,华为官网显示 Mate 70 Pro 机型支持购买“鸿蒙 NEXT 先锋版”,设备出厂搭载的操作系统为 HarmonyOS 5.0。
    ———————-
    标题: 华为 Mate70 RS 非凡大师亮相
    发布时间: 2024-11-26T15:14:34.443
    新闻简介: 华为 Mate70 RS非凡大师亮相#华为Mate70# #华为Mate品牌盛典# #华为发布会#
    ———————-

    —- IT之家新闻 End —-

  • 11月27日,星期三, 每天60秒读懂全世界!

    百度热搜新闻

    新闻来源:百度热搜榜

    1. 地铁疑因钉子户改线 花5亿拆迁一半 武汉多家自媒体发文称:武汉地铁11号线原计划从小区地下穿过,因少部分业主拒绝签约,导致部分房屋无法拆除,地铁线路因此改线。

    2. 东北局地特大暴雪已破纪录

    3. 寒潮≠冷空气?“冷知识”了解一下 今年下半年的首场寒潮给全国多地带来雨雪降温和大风天气,那么究竟什么是寒潮天气?寒潮是如何形成的?我们又该如何防范寒潮影响?一起来了解一下寒潮“冷知识”。

    4. 菲前总统炮轰现总统吸毒成瘾

    5. 日本近海发生6.3级地震 当地时间11月26日22时47分,据中国地震台网正式测定,在日本本州西岸近海发生6.3级地震,震源深度60公里。

    6. #北京飘雪了#

    7. 余承东说Mate70对得起那四个字

    8. 殡仪馆被曝用炖盅当骨灰瓮 近日,广东一先生反映在肇庆德庆县殡仪馆办丧事时,骨灰瓮是炖汤盅。当地民政局回应:馆内剩余的同款式“骨灰瓮”已全部废弃不用。

    9. 失联5个多月后,马天宇回来了

    10. 微信回应14天自动清理原图

    —- 百度热搜新闻 End —-

    知乎新闻

    新闻来源:知乎日榜

    标题: 孟德尔做实验的时候吃豌豆吗?
    链接: https://daily.zhihu.com/story/9776992
    ———————-
    标题: 自己的哥哥是皇帝,称之为「皇兄」;如果自己的弟弟是皇帝,应该如何称呼?
    链接: https://daily.zhihu.com/story/9777115
    ———————-
    标题: 哈利波特决斗厉害吗?
    链接: https://daily.zhihu.com/story/9777125
    ———————-
    标题: 瞎扯 · 如何正确地吐槽
    链接: https://daily.zhihu.com/story/9777212
    ———————-

    —- 知乎新闻 End —-

    IT之家新闻

    新闻来源:ITHome之家科技新闻

    标题: 华为 Mate70 / Pro / Pro+ 系列旗舰手机发布:业界首发高亮钛玄武机身、红枫原色影像,5499 元起
    发布时间: 2024-11-26T15:47:36.567
    新闻简介: 华为 Mate70 / Pro / Pro+ 系列业界首发高亮钛玄武机身,后盖采用锦纤材质,相比上一代更耐磨、耐摔等,支持 IP68、IP69 防尘防水。#华为发布会##华为mate70价格##华为mate70#
    ———————-
    标题: 华为 Mate70 / Pro / Pro+ 系列旗舰手机亮相,余承东豪言“靠抄袭是没有未来的”
    发布时间: 2024-11-26T14:55:28.8
    新闻简介: 华为 Mate70 Pro+、Mate70 Pro、Mate70 均有四个配色版本,如下图所示。余承东还在演讲中豪言“靠抄袭是没有未来的”。#华为Mate70# #华为Mate品牌盛典# #华为发布会#
    ———————-
    标题: 华为 Mate 70 / X6 提供鸿蒙 4.3 / 原生鸿蒙 5.0 选择,明年华为推出的所有新手机平板将全部搭载原生鸿蒙
    发布时间: 2024-11-26T15:51:12.933
    新闻简介: 在目前正在进行的华为 Mate 品牌盛典上,华为称两三个月后鸿蒙生态将会更加完善,将会为用户带来更流畅、更安全更智能的原生体验。
    ———————-
    标题: 18:08 开抢:华为 Mate 70 / X6 旗舰手机即将预售,半年内限购 1 单
    发布时间: 2024-11-26T16:46:29.957
    新闻简介: 华为 Mate 70 /X6 期间手机将在今晚 18:08 预售,每人每 180 天内至多可以下 1 单。
    ———————-
    标题: Mate70 系列全球首发卫星寻呼,还是华为首款 Wi-Fi 7 手机
    发布时间: 2024-11-26T15:03:03.993
    新闻简介: 该技术支持 120° 超广对星角度、9 秒超快连星、0.7 秒超低时延。#华为发布会##华为mate70##华为mate70价格#
    ———————-
    标题: 华为 Mate X6 折叠屏手机正式发布,售价 12999 元起
    发布时间: 2024-11-26T15:52:23.167
    新闻简介: 在今天下午举行的华为 Mate 品牌盛典上,华为新款可折叠智能手机华为 Mate X6 正式发布,售价12999元起。#华为Mate70# #华为Mate品牌盛典# #华为发布会#
    ———————-
    标题: 11999/12999 元华为 Mate70 RS 非凡大师发布:双层 OLED 显示屏,首发瑞声科技高低频双振膜立体声单元
    发布时间: 2024-11-26T15:47:54.667
    新闻简介: 华为 Mate70 系列正式发布,其中 Mate70 Pro RS 提供两个版本#华为Mate70# #华为Mate品牌盛典# #华为发布会#
    ———————-
    标题: 华为余承东:发布会刚结束有人给我转了 6 万元,要订 3 台尊界 S800
    发布时间: 2024-11-26T18:26:07.63
    新闻简介: 在今日的华为 Mate 品牌盛典上,鸿蒙智行尊界 S800 开启预订,意向金 2 万元,预计售价 100-150 万元。刚刚华为常务董事、终端 BG 董事长、智能汽车解决方案 BU 董事长@余承东 在微博发文称:“发布会刚结束,一看手机,有朋友直接微信转账了 6 万给我,要订 3 台尊界!”#华为Mate70# #华为Mate品牌盛典# #华为发布会#
    ———————-
    标题: 华为 Mate70 / Pro 系列首发红枫原色影像:150 万多光谱通道,还原真实色彩
    发布时间: 2024-11-26T14:59:10.587
    新闻简介: 亮点包括高亮钛玄武架构、第二代灵犀通信、业界首发卫星寻呼、100W 有线 + 80W 无线快充、红枫原色影像等等。#华为Mate70# #华为Mate品牌盛典# #华为发布会#
    ———————-
    标题: 消息称苹果 iPhone 17 Pro 系列将迎来“重大设计变更”,高端产品回归铝制机身
    发布时间: 2024-11-26T06:34:52.393
    新闻简介: 苹果自 2017 年开始从 iPhone 8 和 iPhone X 引入玻璃背板,而在此之前几乎所有 iPhone 都采用的是铝制背板。
    ———————-
    标题: 华为 Mate 70 Pro 机型支持选购“鸿蒙 NEXT 先锋版”,出厂系统为 HarmonyOS 5.0
    发布时间: 2024-11-26T19:14:18.713
    新闻简介: IT之家注意到,华为官网显示 Mate 70 Pro 机型支持购买“鸿蒙 NEXT 先锋版”,设备出厂搭载的操作系统为 HarmonyOS 5.0。
    ———————-
    标题: 华为 Mate70 RS 非凡大师亮相
    发布时间: 2024-11-26T15:14:34.443
    新闻简介: 华为 Mate70 RS非凡大师亮相#华为Mate70# #华为Mate品牌盛典# #华为发布会#
    ———————-

    —- IT之家新闻 End —-

  • 50 个 Java 编程技巧


    共计 28176 个字符,预计需要花费 71 分钟才能阅读完成。

    一、语法类技巧

    1.1. 使用三元表达式

    普通:

    String title;
    if (isMember(phone)) {
        title = "会员";
    } else {
        title = "游客";
    }

    精简:

    String title = isMember(phone) ? "会员" : "游客";

    1.2.利用 for-each 语句

    从 Java 5 起,提供了 for-each 循环,简化了数组和集合的循环遍历。 for-each 循环允许你无需保持传统 for 循环中的索引就可以遍历数组,或在使用迭代器时无需在 while 循环中调用 hasNext 方法和 next 方法就可以遍历集合。

    普通:

    double[] values = ...;
    for(int i = 0; i < values.length; i++) {
        double value = values[i];    
        // TODO: 处理value
    }
    
    List<Double> valueList = ...;
    Iterator<Double> iterator = valueList.iterator();
    
    while (iterator.hasNext()) {
        Double value = iterator.next();    
        // TODO: 处理value
    }

    精简:

    double[] values = ...;
    for(double value : values) {    
        // TODO: 处理value
    }
    List<Double> valueList = ...;
    for(Double value : valueList) {
        // TODO: 处理value
    }

    1.3.利用 try-with-resource 语句

    所有实现 Closeable 接口的“资源”,均可采用 try-with-resource 进行简化。

    普通:

    BufferedReader reader = null;
    try {
        reader = new BufferedReader(new FileReader("cities.csv"));
        String line;
        while ((line = reader.readLine()) != null) {
            // TODO: 处理line
        }
    } catch (IOException e) {
        log.error("读取文件异常", e);
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                log.error("关闭文件异常", e);
            }
        }
    }

    精简:

    try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) {
        String line;
        while ((line = reader.readLine()) != null) {
            // TODO: 处理line
        }
    } catch (IOException e) {
        log.error("读取文件异常", e);
    }

    1.4.利用 return 关键字

    利用 return 关键字,可以提前函数返回,避免定义中间变量。

    普通:

    public static boolean hasSuper(@NonNull List<UserDO> userList) {
        boolean hasSuper = false;
        for (UserDO user : userList) {
            if (Boolean.TRUE.equals(user.getIsSuper())) {
                hasSuper = true;
                break;
            }
        }
        return hasSuper;
    }

    精简:

    public static boolean hasSuper(@NonNull List<UserDO> userList) {
        for (UserDO user : userList) {
            if (Boolean.TRUE.equals(user.getIsSuper())) {
                return true;
            }
        }
        return false;
    }

    1.5.利用 static 关键字

    利用 static 关键字,可以把字段变成静态字段,也可以把函数变为静态函数,调用时就无需初始化类对象。

    普通:

    public final class GisHelper {
        public double distance(double lng1, double lat1, double lng2, double lat2) {
            // 方法实现代码
        }
    }
    
    GisHelper gisHelper = new GisHelper();
    double distance = gisHelper.distance(116.178692D, 39.967115D, 116.410778D, 39.899721D);

    精简:

    public final class GisHelper {
        public static double distance(double lng1, double lat1, double lng2, double lat2) {
            // 方法实现代码
        }
    }
    double distance = GisHelper.distance(116.178692D, 39.967115D, 116.410778D, 39.899721D);

    1.6.利用 lambda 表达式

    Java 8 发布以后,lambda 表达式大量替代匿名内部类的使用,在简化了代码的同时,更突出了原有匿名内部类中真正有用的那部分代码。

    普通:

    new Thread(new Runnable() {
        public void run() {
            // 线程处理代码
        }
    }).start();

    精简:

    new Thread(() -> {
        // 线程处理代码
    }).start();

    1.7.利用方法引用

    方法引用(::),可以简化 lambda 表达式,省略变量声明和函数调用。

    普通:

    Arrays.sort(nameArray, (a, b) -> a.compareToIgnoreCase(b));
    
    List<Long> userIdList = userList.stream()
        .map(user -> user.getId())
        .collect(Collectors.toList());

    精简:

    Arrays.sort(nameArray, String::compareToIgnoreCase);
    
    List<Long> userIdList = userList.stream()
        .map(UserDO::getId)
        .collect(Collectors.toList());

    1.8.利用静态导入

    静态导入(import static),当程序中大量使用同一静态常量和函数时,可以简化静态常量和函数的引用。

    普通:

    List<Double> areaList = radiusList.stream().map(r -> Math.PI * Math.pow(r, 2)).collect(Collectors.toList());...

    精简:

    import static java.lang.Math.PI;
    import static java.lang.Math.pow;
    import static java.util.stream.Collectors.toList;
    
    List<Double> areaList = radiusList.stream().map(r -> PI * pow(r, 2)).collect(toList());...

    注意:静态引入容易造成代码阅读困难,所以在实际项目中应该警慎使用。

    1.9.利用 unchecked 异常

    Java 的异常分为两类:Checked 异常和 Unchecked 异常。Unchecked 异常继承了RuntimeException ,特点是代码不需要处理它们也能通过编译,所以它们称作 Unchecked 异常。利用 Unchecked 异常,可以避免不必要的 try-catch 和 throws 异常处理。

    普通:

    @Service
    public class UserService {
        public void createUser(UserCreateVO create, OpUserVO user) throws BusinessException {
            checkOperatorUser(user);
            ...
        }
        private void checkOperatorUser(OpUserVO user) throws BusinessException {
            if (!hasPermission(user)) {
                throw new BusinessException("用户无操作权限");
            }
            ...
        }
        ...
    }
    
    @RestController
    @RequestMapping("/user")
    public class UserController {
        @Autowired
        private UserService userService;
    
        @PostMapping("/createUser")
        public Result<Void> createUser(@RequestBody @Valid UserCreateVO create,OpUserVO user) throws BusinessException {
            userService.createUser(create, user);
            return Result.success();
        }
        ...
    }

    精简:

    @Service
    public class UserService {
        public void createUser(UserCreateVO create, OpUserVO user) {
            checkOperatorUser(user);
            ...
        }
        private void checkOperatorUser(OpUserVO user) {
            if (!hasPermission(user)) {
                throw new BusinessRuntimeException("用户无操作权限");
            }
            ...
        }
        ...
    }
    
    @RestController
    @RequestMapping("/user")
    public class UserController {
        @Autowired
        private UserService userService;
    
        @PostMapping("/createUser")
        public Result<Void> createUser(@RequestBody @Valid UserCreateVO create, OpUserVO user) {
            userService.createUser(create, user);
            return Result.success();
        }
        ...
    }

    二、利用注解

    2.1.利用 Lombok 注解

    Lombok 提供了一组有用的注解,可以用来消除Java类中的大量样板代码。

    普通:

    public class UserVO {
        private Long id;
        private String name;
        public Long getId() {
            return this.id;
        }
        public void setId(Long id) {
            this.id = id;
        }
        public String getName() {
            return this.name;
        }
        public void setName(String name) {
            this.name = name;
        }
        ...
    }

    精简:

    @Getter
    @Setter
    @ToString
    public class UserVO {
        private Long id;
        private String name;
        ...
    }

    2.2.利用 Validation 注解

    普通:

    @Getter
    @Setter
    @ToString
    public class UserCreateVO {
        @NotBlank(message = "用户名称不能为空")
        private String name;
        @NotNull(message = "公司标识不能为空")
        private Long companyId;
        ...
    }
    @Service
    @Validated
    public class UserService {
        public Long createUser(@Valid UserCreateVO create) {
            // TODO: 创建用户
            return null;
        }
    }

    精简:

    @Getter
    @Setter
    @ToString
    public class UserCreateVO {
    
        @NotBlank(message = "用户名称不能为空")
        private String name;
    
        @NotNull(message = "公司标识不能为空")
        private Long companyId;
    
        ...
    
    }
    
    @Service
    @Validated
    public class UserService {
        public Long createUser(@Valid UserCreateVO create) {
    
            // TODO: 创建用户
    
            return null;
    
        }
    }

    2.3.利用 @NonNull 注解

    Spring 的 @NonNull 注解,用于标注参数或返回值非空,适用于项目内部团队协作。只要实现方和调用方遵循规范,可以避免不必要的空值判断,这充分体现了阿里的“新六脉神剑”提倡的“因为信任,所以简单”。

    普通:

    public List<UserVO> queryCompanyUser(Long companyId) {
        // 检查公司标识
        if (companyId == null) {
            return null;
        }
    
        // 查询返回用户
        List<UserDO> userList = userDAO.queryByCompanyId(companyId);
        return userList.stream().map(this::transUser).collect(Collectors.toList());
    }
    
    Long companyId = 1L;List<UserVO> userList = queryCompanyUser(companyId);
    if (CollectionUtils.isNotEmpty(userList)) {
        for (UserVO user : userList) {
            // TODO: 处理公司用户
        }
    }

    精简:

    public @NonNull List<UserVO> queryCompanyUser(@NonNull Long companyId) {
        List<UserDO> userList = userDAO.queryByCompanyId(companyId);
        return userList.stream().map(this::transUser).collect(Collectors.toList());
    }
    
    Long companyId = 1L;List<UserVO> userList = queryCompanyUser(companyId);
    for (UserVO user : userList) {
        // TODO: 处理公司用户
    }

    2.4.利用注解特性

    注解有以下特性可用于精简注解声明:

    1、当注解属性值跟默认值一致时,可以删除该属性赋值;

    2、当注解只有value属性时,可以去掉value进行简写;

    3、当注解属性组合等于另一个特定注解时,直接采用该特定注解。

    普通:

    @Lazy(true);
    
    @Service(value = "userService")
    
    @RequestMapping(path = "/getUser", method = RequestMethod.GET)

    精简:

    @Lazy
    
    @Service("userService")
    
    @GetMapping("/getUser")

    三、利用泛型

    3.1.泛型接口

    在 Java 没有引入泛型前,都是采用 Object 表示通用对象,最大的问题就是类型无法强校验并且需要强制类型转换。

    普通:

    public interface Comparable {
        public int compareTo(Object other);
    }
    
    @Getter
    @Setter
    @ToString
    public class UserVO implements Comparable {
        private Long id;
    
        @Override
        public int compareTo(Object other) {
            UserVO user = (UserVO)other;
            return Long.compare(this.id, user.id);
        }
    }

    精简:

    public interface Comparable<T> {
        public int compareTo(T other);
    }
    
    @Getter
    @Setter
    @ToString
    public class UserVO implements Comparable<UserVO> {
        private Long id;
    
        @Override
        public int compareTo(UserVO other) {
            return Long.compare(this.id, other.id);
        }
    }

    3.2.泛型类

    普通:

    @Getter
    @Setter
    @ToString
    public class IntPoint {
        private Integer x;
        private Integer y;
    }
    
    @Getter
    @Setter
    @ToString
    public class DoublePoint {
        private Double x;
        private Double y;
    }

    精简:

    @Getter
    @Setter
    @ToString
    public class Point<T extends Number> {
        private T x;
        private T y;
    }

    3.3.泛型方法

    普通:

    public static Map<String, Integer> newHashMap(String[] keys, Integer[] values) {
        // 检查参数非空
        if (ArrayUtils.isEmpty(keys) || ArrayUtils.isEmpty(values)) {
            return Collections.emptyMap();
        }
    
        // 转化哈希映射
        Map<String, Integer> map = new HashMap<>();
        int length = Math.min(keys.length, values.length);
        for (int i = 0; i < length; i++) {
            map.put(keys[i], values[i]);
        }
        return map;
    }

    精简:

    public static <K, V> Map<K, V> newHashMap(K[] keys, V[] values) {
        // 检查参数非空
        if (ArrayUtils.isEmpty(keys) || ArrayUtils.isEmpty(values)) {
            return Collections.emptyMap();
        }
    
        // 转化哈希映射
        Map<K, V> map = new HashMap<>();
        int length = Math.min(keys.length, values.length);
        for (int i = 0; i < length; i++) {
            map.put(keys[i], values[i]);
        }
        return map;
    }

    四、利用自身方法

    4.1.利用构造方法

    构造方法,可以简化对象的初始化和设置属性操作。对于属性字段较少的类,可以自定义构造方法。

    普通:

    @Getter
    @Setter
    @ToString
    public class PageDataVO<T> {
        private Long totalCount;
        private List<T> dataList;
    }
    
    PageDataVO<UserVO> pageData = new PageDataVO<>();
    pageData.setTotalCount(totalCount);
    pageData.setDataList(userList);
    return pageData;

    精简:

    @Getter
    @Setter
    @ToString
    @NoArgsConstructor
    @AllArgsConstructor
    public class PageDataVO<T> {
        private Long totalCount;
        private List<T> dataList;
    }
    
    return new PageDataVO<>(totalCount, userList);

    注意:如果属性字段被替换时,存在构造函数初始化赋值问题。比如把属性字段title替换为 nickname ,由于构造函数的参数个数和类型不变,原有构造函数初始化语句不会报错,导致把原title值赋值给 nickname 。如果采用 Setter 方法赋值,编译器会提示错误并要求修复。

    4.2.利用 Set 的 add 方法

    利用 Set 的 add 方法的返回值,可以直接知道该值是否已经存在,可以避免调用 contains 方法判断存在。

    普通:

    以下案例是进行用户去重转化操作,需要先调用 contains 方法判断存在,后调用add方法进行添加。

    Set<Long> userIdSet = new HashSet<>();
    List<UserVO> userVOList = new ArrayList<>();
    for (UserDO userDO : userDOList) {
        if (!userIdSet.contains(userDO.getId())) {
            userIdSet.add(userDO.getId());
            userVOList.add(transUser(userDO));
        }
    }

    精简:

    SSet<Long> userIdSet = new HashSet<>();
    List<UserVO> userVOList = new ArrayList<>();
    for (UserDO userDO : userDOList) {
        if (userIdSet.add(userDO.getId())) {
            userVOList.add(transUser(userDO));
        }
    }

    4.3.利用 Map 的 computeIfAbsent 方法

    利用 Map 的 computeIfAbsent 方法,可以保证获取到的对象非空,从而避免了不必要的空判断和重新设置值。

    普通:

    Map<Long, List<UserDO>> roleUserMap = new HashMap<>();
    for (UserDO userDO : userDOList) {
        Long roleId = userDO.getRoleId();
        List<UserDO> userList = roleUserMap.get(roleId);
        if (Objects.isNull(userList)) {
            userList = new ArrayList<>();
            roleUserMap.put(roleId, userList);
        }
        userList.add(userDO);
    }

    精简:

    Map<Long, List<UserDO>> roleUserMap = new HashMap<>();
    for (UserDO userDO : userDOList) {
        roleUserMap.computeIfAbsent(userDO.getRoleId(),key -> new ArrayList<>())
        .add(userDO);
    }

    4.4.利用链式编程

    链式编程,也叫级联式编程,调用对象的函数时返回一个this对象指向对象本身,达到链式效果,可以级联调用。链式编程的优点是:编程性强、可读性强、代码简洁。

    普通:

    StringBuilder builder = new StringBuilder(96);
    builder.append("select id, name from ");
    builder.append(T_USER);builder.append(" where id = ");builder.append(userId);
    builder.append(";");

    精简:

    StringBuilder builder = new StringBuilder(96);
    builder.append("select id, name from ").append(T_USER).append(" where id = ").append(userId).append(";");

    五、利用工具方法

    5.1.避免空值判断

    普通:

    if (userList != null && !userList.isEmpty()) {
        // TODO: 处理代码
    }

    精简:

    if (CollectionUtils.isNotEmpty(userList)) {
        // TODO: 处理代码
    }

    5.2.避免条件判断

    普通:

    double result;
    if (value <= MIN_LIMIT) {
        result = MIN_LIMIT;}
    else {
        result = value;
    }

    精简:

    double result = Math.max(MIN_LIMIT, value);

    5.3.简化赋值语句

    普通:

    public static final List<String> ANIMAL_LIST;
    static {
        List<String> animalList = new ArrayList<>();
        animalList.add("dog");
        animalList.add("cat");
        animalList.add("tiger");
        ANIMAL_LIST = Collections.unmodifiableList(animalList);
    }

    精简:

    // JDK流派
    
    public static final List<String> ANIMAL_LIST = Arrays.asList("dog", "cat", "tiger");
    
    // Guava流派
    
    public static final List<String> ANIMAL_LIST = ImmutableList.of("dog", "cat", "tiger");

    注意:Arrays.asList 返回的 List 并不是 ArrayList ,不支持 add 等变更操作。

    5.4.简化数据拷贝

    普通:

    UserVO userVO = new UserVO();
    userVO.setId(userDO.getId());
    userVO.setName(userDO.getName());
    ...
    userVO.setDescription(userDO.getDescription());
    userVOList.add(userVO);

    精简:

    UserVO userVO = new UserVO();
    BeanUtils.copyProperties(userDO, userVO);
    userVOList.add(userVO);

    反例:

    List<UserVO> userVOList = JSON.parseArray(JSON.toJSONString(userDOList), UserVO.class);

    精简代码,但不能以过大的性能损失为代价。例子是浅层拷贝,用不着 JSON 这样重量级的武器。

    5.5.简化异常断言

    普通:

    if (Objects.isNull(userId)) {
        throw new IllegalArgumentException("用户标识不能为空");
    }

    精简:

    Assert.notNull(userId, "用户标识不能为空");

    注意:可能有些插件不认同这种判断,导致使用该对象时会有空指针警告。

    5.6.简化测试用例

    把测试用例数据以 JSON 格式存入文件中,通过 JSON 的 parseObject 和 parseArray 方法解析成对象。虽然执行效率上有所下降,但可以减少大量的赋值语句,从而精简了测试代码。

    普通:

    @Test
    public void testCreateUser() {
        UserCreateVO userCreate = new UserCreateVO();
        userCreate.setName("Changyi");
        userCreate.setTitle("Developer");
        userCreate.setCompany("AMAP");
        ...
        Long userId  = userService.createUser(OPERATOR, userCreate);
        Assert.assertNotNull(userId, "创建用户失败");
    }

    精简:

    @Test
    public void testCreateUser() {
        String jsonText = ResourceHelper.getResourceAsString(getClass(), "createUser.json");
        UserCreateVO userCreate = JSON.parseObject(jsonText, UserCreateVO.class);
        Long userId  = userService.createUser(OPERATOR, userCreate);
        Assert.assertNotNull(userId, "创建用户失败");
    }

    建议:JSON 文件名最好以被测试的方法命名,如果有多个版本可以用数字后缀表示。

    5.7.简化算法实现

    一些常规算法,已有现成的工具方法,我们就没有必要自己实现了。

    普通:

    int totalSize = valueList.size();
    List<List<Integer>> partitionList = new ArrayList<>();
    for (int i = 0; i < totalSize; i += PARTITION_SIZE) {
        partitionList.add(valueList.subList(i, Math.min(i + PARTITION_SIZE, totalSize)));
    }

    精简:

    List<List<Integer>> partitionList = ListUtils.partition(valueList, PARTITION_SIZE);

    5.8.封装工具方法

    一些特殊算法,没有现成的工具方法,我们就只好自己亲自实现了。

    普通:

    比如,SQL 设置参数值的方法就比较难用,setLong 方法不能设置参数值为 null 。

    // 设置参数值
    if (Objects.nonNull(user.getId())) {
    
        statement.setLong(1, user.getId());
    
    } else {
    
        statement.setNull(1, Types.BIGINT);
    
    }

    精简:

    我们可以封装为一个工具类 SqlHelper ,简化设置参数值的代码。

    /** SQL辅助类 */
    public final class SqlHelper {
        /** 设置长整数值 */
        public static void setLong(PreparedStatement statement, int index, Long value) throws SQLException {
            if (Objects.nonNull(value)) {
                statement.setLong(index, value.longValue());
            } else {
                statement.setNull(index, Types.BIGINT);
            }
        }
        ...
    }
    
     // 设置参数值SqlHelper.setLong(statement, 1, user.getId());

    六、利用数据结构

    6.1.利用数组简化

    对于固定上下限范围的 if-else 语句,可以用数组+循环来简化。

    普通:

    public static int getGrade(double score) {
        if (score >= 90.0D) {
            return 1;
        }
        if (score >= 80.0D) {
            return 2;
        }
        if (score >= 60.0D) {
            return 3;
        }
        if (score >= 30.0D) {
            return 4;
        }
        return 5;
    }

    精简:

    private static final double[] SCORE_RANGES = new double[] {90.0D, 80.0D, 60.0D, 30.0D};
    public static int getGrade(double score) {
        for (int i = 0; i < SCORE_RANGES.length; i++) {
            if (score >= SCORE_RANGES[i]) {
                return i + 1;
            }
        }
        return SCORE_RANGES.length + 1;
    }

    思考:上面的案例返回值是递增的,所以用数组简化是没有问题的。但是,如果返回值不是递增的,能否用数组进行简化呢?答案是可以的,请自行思考解决。

    6.2.利用 Map 简化

    对于映射关系的 if-else 语句,可以用Map来简化。此外,此规则同样适用于简化映射关系的 switch 语句。

    普通:

    public static String getBiologyClass(String name) {
        switch (name) {
            case "dog" :
            return "animal";
            case "cat" :
            return "animal";
            case "lavender" :
            return "plant";
            ...
            default :
            return null;
        }
    }

    精简

    private static final Map<String, String> BIOLOGY_CLASS_MAP
        = ImmutableMap.<String, String>builder()
        .put("dog", "animal")
        .put("cat", "animal")
        .put("lavender", "plant") 
        ...
        .build();
    public static String getBiologyClass(String name) {
        return BIOLOGY_CLASS_MAP.get(name);
    }

    已经把方法简化为一行代码,其实都没有封装方法的必要了。

    6.3.利用容器类简化

    Java 不像 Python 和 Go ,方法不支持返回多个对象。如果需要返回多个对象,就必须自定义类,或者利用容器类。常见的容器类有 Apache 的 Pair 类和 Triple 类, Pair 类支持返回 2 个对象, Triple 类支持返回 3 个对象。

    普通:

    @Setter
    @Getter
    @ToString
    @AllArgsConstructor
    public static class PointAndDistance {
        private Point point;
        private Double distance;
    }
    
    public static PointAndDistance getNearest(Point point, Point[] points) {
        // 计算最近点和距离
        ...
    
        // 返回最近点和距离
            return new PointAndDistance(nearestPoint, nearestDistance);
    }

    精简:

    public static Pair<Point, Double> getNearest(Point point, Point[] points) {
        // 计算最近点和距离
        ...
    
        // 返回最近点和距离
        return ImmutablePair.of(nearestPoint, nearestDistance);
    }

    6.4.利用 ThreadLocal 简化

    ThreadLocal 提供了线程专有对象,可以在整个线程生命周期中随时取用,极大地方便了一些逻辑的实现。用 ThreadLocal 保存线程上下文对象,可以避免不必要的参数传递。

    普通:

    由于 DateFormat 的 format 方法线程非安全(建议使用替代方法),在线程中频繁初始化 DateFormat 性能太低,如果考虑重用只能用参数传入 DateFormat 。例子如下:

    public static String formatDate(Date date, DateFormat format) {
        return format.format(date);
    }
    
    public static List<String> getDateList(Date minDate, Date maxDate, DateFormat format) {
        List<String> dateList = new ArrayList<>();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(minDate);
        String currDate = formatDate(calendar.getTime(), format);
        String maxsDate = formatDate(maxDate, format);
        while (currDate.compareTo(maxsDate) <= 0) {
            dateList.add(currDate);
            calendar.add(Calendar.DATE, 1);
            currDate = formatDate(calendar.getTime(), format);
        }
        return dateList;
    }

    精简

    可能你会觉得以下的代码量反而多了,如果调用工具方法的地方比较多,就可以省下一大堆 DateFormat 初始化和传入参数的代码。

    private static final ThreadLocal<DateFormat> LOCAL_DATE_FORMAT = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyyMMdd");
        }
    };
    
    public static String formatDate(Date date) {
        return LOCAL_DATE_FORMAT.get().format(date);
    }
    
    public static List<String> getDateList(Date minDate, Date maxDate) {
        List<String> dateList = new ArrayList<>();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(minDate);
        String currDate = formatDate(calendar.getTime());
        String maxsDate = formatDate(maxDate);
        while (currDate.compareTo(maxsDate) <= 0) {
            dateList.add(currDate);
            calendar.add(Calendar.DATE, 1);
            currDate = formatDate(calendar.getTime());
        }
        return dateList;
    }

    注意:ThreadLocal 有一定的内存泄露的风险,尽量在业务代码结束前调用 remove 方法进行数据清除。

    七、利用 Optional

    在 Java 8 里,引入了一个 Optional 类,该类是一个可以为 null 的容器对象。

    7.1.保证值存在

    普通:

    Integer thisValue;
    if (Objects.nonNull(value)) {
        thisValue = value;
    }else {
        thisValue = DEFAULT_VALUE;
    }

    精简:

    Integer thisValue = Optional.ofNullable(value).orElse(DEFAULT_VALUE);

    7.2.保证值合法

    普通:

    Integer thisValue;
    if (Objects.nonNull(value) && value.compareTo(MAX_VALUE) <= 0) {
        thisValue = value;} else {
        thisValue = MAX_VALUE;
    }

    精简

    Integer thisValue = Optional.ofNullable(value)
        .filter(tempValue -> tempValue.compareTo(MAX_VALUE) <= 0).orElse(MAX_VALUE);

    7.3.避免空判断

    普通:

    String zipcode = null;if (Objects.nonNull(user)) {
        Address address = user.getAddress();
        if (Objects.nonNull(address)) { 
            Country country = address.getCountry(); 
            if (Objects.nonNull(country)) {   
                zipcode = country.getZipcode();    
            }
        }
    }

    精简:

    String zipcode = Optional.ofNullable(user).map(User::getAddress)
        .map(Address::getCountry).map(Country::getZipcode).orElse(null);

    八.利用 Stream

    流(Stream)是Java 8的新成员,允许你以声明式处理数据集合,可以看成为一个遍历数据集的高级迭代器。流主要有三部分构成:获取一个数据源→数据转换→执行操作获取想要的结果。每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象,这就允许对其操作可以像链条一样排列,形成了一个管道。流(Stream)提供的功能非常有用,主要包括匹配、过滤、汇总、转化、分组、分组汇总等功能。

    8.1.匹配集合数据

    普通:

    boolean isFound = false;for (UserDO user : userList) {   
        if (Objects.equals(user.getId(), userId)) {  
            isFound = true;    
            break;  
        }
    }

    精简:

    boolean isFound = userList.stream() 
        .anyMatch(user -> Objects.equals(user.getId(), userId));

    8.2.过滤集合数据

    普通:

    List<UserDO> resultList = new ArrayList<>();
    for (UserDO user : userList) { 
        if (Boolean.TRUE.equals(user.getIsSuper())) {   
            resultList.add(user);
        }
    }

    精简

    List<UserDO> resultList = userList.stream()  
        .filter(user -> Boolean.TRUE.equals(user.getIsSuper()))   
        .collect(Collectors.toList());

    8.3.汇总集合数据

    普通:

    double total = 0.0D;for (Account account : accountList) { 
        total += account.getBalance();
    }

    精简:

    double total = accountList.stream().mapToDouble(Account::getBalance).sum();

    8.4.转化集合数据

    普通:

    List<UserVO> userVOList = new ArrayList<>();for (UserDO userDO : userDOList) { 
        userVOList.add(transUser(userDO));
    }

    精简

    List<UserVO> userVOList = userDOList.stream() 
        .map(this::transUser).collect(Collectors.toList());

    8.5.分组集合数据

    普通:

    Map<Long, List<UserDO>> roleUserMap = new HashMap<>();for (UserDO userDO : userDOList) {   
        roleUserMap.computeIfAbsent(userDO.getRoleId(), key -> new ArrayList<>())
            .add(userDO);
    }

    精简:

    Map<Long, List<UserDO>> roleUserMap = userDOList.stream() 
        .collect(Collectors.groupingBy(UserDO::getRoleId));

    8.6.分组汇总集合

    普通:

    Map<Long, Double> roleTotalMap = new HashMap<>();
    for (Account account : accountList) {
        Long roleId = account.getRoleId();  
        Double total = Optional.ofNullable(roleTotalMap.get(roleId)).orElse(0.0D);  
        roleTotalMap.put(roleId, total + account.getBalance());
    }

    精简:

    roleTotalMap = accountList.stream().collect(Collectors.groupingBy(Account::getRoleId, Collectors.summingDouble(Account::getBalance)));

    8.7.生成范围集合

    Python 的 range 非常方便,Stream 也提供了类似的方法。

    普通:

    int[] array1 = new int[N];
    for (int i = 0; i < N; i++) {
        array1[i] = i + 1;
    }
    
    int[] array2 = new int[N];array2[0] = 1;
    for (int i = 1; i < N; i++) {
        array2[i] = array2[i - 1] * 2;
    }

    精简:

    int[] array1 = IntStream.rangeClosed(1, N).toArray();
    int[] array2 = IntStream.iterate(1, n -> n * 2).limit(N).toArray();

    九、利用程序结构

    9.1.返回条件表达式

    条件表达式判断返回布尔值,条件表达式本身就是结果。

    普通:

    public boolean isSuper(Long userId) {
        UserDO user = userDAO.get(userId);   
        if (Objects.nonNull(user) && Boolean.TRUE.equals(user.getIsSuper())) {  
            return true;  
        }  
        return false;
    }

    精简:

    public boolean isSuper(Long userId)  {
        UserDO user = userDAO.get(userId); 
        return Objects.nonNull(user) && Boolean.TRUE.equals(user.getIsSuper());
    }

    9.2.最小化条件作用域

    最小化条件作用域,尽量提出公共处理代码。

    普通:

    Result result = summaryService.reportWorkDaily(workDaily);
    if (result.isSuccess()) {
        String message = "上报工作日报成功";
        dingtalkService.sendMessage(user.getPhone(), message);
    } else {    String message = "上报工作日报失败:" + result.getMessage(); 
          log.warn(message); 
          dingtalkService.sendMessage(user.getPhone(), message);
    }

    精简:

    String message;
    Result result = summaryService.reportWorkDaily(workDaily);
    if (result.isSuccess()) { 
        message = "上报工作日报成功";
    } else {  
        message = "上报工作日报失败:" + result.getMessage();  
        log.warn(message);
    }
    dingtalkService.sendMessage(user.getPhone(), message);

    9.3.调整表达式位置

    调整表达式位置,在逻辑不变的前提下,让代码变得更简洁。

    普通1:

    String line = readLine();while (Objects.nonNull(line)) {  
    
        ...  // 处理逻辑代码  
    
        line = readLine();
    }

    普通2:

    for (String line = readLine(); Objects.nonNull(line); line = readLine()) {
        ... // 处理逻辑代码
    }

    精简

    String line;
    while (Objects.nonNull(line = readLine())) {
        ... // 处理逻辑代码
    }

    注意:有些规范可能不建议这种精简写法。

    9.4.利用非空对象

    在比较对象时,交换对象位置,利用非空对象,可以避免空指针判断。

    普通:

    private static final int MAX_VALUE = 1000;boolean isMax = (value != null && value.equals(MAX_VALUE));
    boolean isTrue = (result != null && result.equals(Boolean.TRUE));

    精简:

    private static final Integer MAX_VALUE = 1000;boolean isMax = MAX_VALUE.equals(value);
    boolean isTrue = Boolean.TRUE.equals(result);

    十、利用设计模式

    10.1.模板方法模式

    模板方法模式(Template Method Pattern)定义一个固定的算法框架,而将算法的一些步骤放到子类中实现,使得子类可以在不改变算法框架的情况下重定义该算法的某些步骤。

    普通:

    @Repository
    public class UserValue {
        /** 值操作 */ 
        @Resource(name = "stringRedisTemplate")  
        private ValueOperations<String, String> valueOperations;   
        /** 值模式 */ 
        private static final String KEY_FORMAT = "Value:User:%s";
    
        /** 设置值 */  
        public void set(Long id, UserDO value) {  
            String key = String.format(KEY_FORMAT, id);    
            valueOperations.set(key, JSON.toJSONString(value));  
        }
    
        /** 获取值 */  
        public UserDO get(Long id) {  
            String key = String.format(KEY_FORMAT, id);    
            String value = valueOperations.get(key);   
            return JSON.parseObject(value, UserDO.class);
        }
    
        ...
    }
    
    @Repository
    public class RoleValue {  
        /** 值操作 */
        @Resource(name = "stringRedisTemplate")  
        private ValueOperations<String, String> valueOperations;  
        /** 值模式 */ 
        private static final String KEY_FORMAT = "Value:Role:%s";
    
        /** 设置值 */    public void set(Long id, RoleDO value) {   
            String key = String.format(KEY_FORMAT, id);   
            valueOperations.set(key, JSON.toJSONString(value));  
        }
    
        /** 获取值 */    public RoleDO get(Long id) {  
            String key = String.format(KEY_FORMAT, id);  
            String value = valueOperations.get(key);    
            return JSON.parseObject(value, RoleDO.class); 
        }
    
        ...
    }

    精简:

    public abstract class AbstractDynamicValue<I, V> {  
        /** 值操作 */  
        @Resource(name = "stringRedisTemplate")  
        private ValueOperations<String, String> valueOperations;
        /** 设置值 */    public void set(I id, V value) { 
            valueOperations.set(getKey(id), JSON.toJSONString(value)); 
        }
    
        /** 获取值 */  
        public V get(I id) { 
            return JSON.parseObject(valueOperations.get(getKey(id)), getValueClass());  
        }
        ...
        /** 获取主键 */
        protected abstract String getKey(I id);
    
        /** 获取值类 */ 
        protected abstract Class<V> getValueClass();
    }
    
    @Repository
    public class UserValue extends AbstractValue<Long, UserDO> {   
        /** 获取主键 */  
        @Override   
        protected String getKey(Long id) {     
        return String.format("Value:User:%s", id);  
    }
    
        /** 获取值类 */ 
        @Override 
        protected Class<UserDO> getValueClass() {    
            return UserDO.class;   
        }
    }
    
    @Repository
    public class RoleValue extends AbstractValue<Long, RoleDO> {  
        /** 获取主键 */  
        @Override   
        protected String getKey(Long id) {     
            return String.format("Value:Role:%s", id); 
        }
    
        /** 获取值类 */   
        @Override    protected Class<RoleDO> getValueClass() {  
            return RoleDO.class;   
        }
    }

    10.2.建造者模式

    建造者模式(Builder Pattern)将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。

    普通:

    public interface DataHandler<T> { 
        /** 解析数据 */
        public T parseData(Record record);
    
        /** 存储数据 */
        public boolean storeData(List<T> dataList);}
    
    public <T> long executeFetch(String tableName, int batchSize, DataHandler<T> dataHandler) throws Exception {  
        // 构建下载会话  
        DownloadSession session = buildSession(tableName);
    
        // 获取数据数量   
        long recordCount = session.getRecordCount();  
        if (recordCount == 0) {   
            return 0;   
        }
    
        // 进行数据读取  
        long fetchCount = 0L; 
        try (RecordReader reader = session.openRecordReader(0L, recordCount, true)) {    
            // 依次读取数据   
            Record record;     
            List<T> dataList = new ArrayList<>(batchSize);   
            while ((record = reader.read()) != null) {   
                // 解析添加数据      
                T data = dataHandler.parseData(record);   
                if (Objects.nonNull(data)) {        
                    dataList.add(data);         
                }
    
                // 批量存储数据     
                if (dataList.size() == batchSize) {  
                    boolean isContinue = dataHandler.storeData(dataList);      
                    fetchCount += batchSize;  
                    dataList.clear();     
                    if (!isContinue) {          
                        break;          
                    }
                }   
            }
    
            // 存储剩余数据    
            if (CollectionUtils.isNotEmpty(dataList)) {   
                dataHandler.storeData(dataList);   
                fetchCount += dataList.size();    
                dataList.clear();   
            }
        }
    
        // 返回获取数量  
        return fetchCount;
    }
    
     // 使用案例
    long fetchCount = odpsService.executeFetch("user", 5000, new DataHandler() { 
        /** 解析数据 */ 
        @Override
        public T parseData(Record record) {  
            UserDO user = new UserDO();   
            user.setId(record.getBigint("id"));  
            user.setName(record.getString("name"));   
            return user;   
        }
    
        /** 存储数据 */  
        @Overridepublic boolean storeData(List<T> dataList) {  
            userDAO.batchInsert(dataList);   
            return true;  
        }
    });

    精简:

    public <T> long executeFetch(String tableName, int batchSize, Function<Record, T> dataParser, Function<List<T>, Boolean> dataStorage) throws Exception { 
        // 构建下载会话  
        DownloadSession session = buildSession(tableName);
    
        // 获取数据数量  
        long recordCount = session.getRecordCount();  
        if (recordCount == 0) {    
            return 0;    
        }
        // 进行数据读取  
        long fetchCount = 0L; 
        try (RecordReader reader = session.openRecordReader(0L, recordCount, true)) {  
            // 依次读取数据      
            Record record;     
            List<T> dataList = new ArrayList<>(batchSize);      
            while ((record = reader.read()) != null) {        
                // 解析添加数据    
                T data = dataParser.apply(record);    
                if (Objects.nonNull(data)) {           
                    dataList.add(data);        
                }
    
                // 批量存储数据 
                if (dataList.size() == batchSize) {    
                    Boolean isContinue = dataStorage.apply(dataList);   
                    fetchCount += batchSize;          
                    dataList.clear();         
                    if (!Boolean.TRUE.equals(isContinue)) {   
                        break;             
                    }
                }
            }
    
            // 存储剩余数据 
            if (CollectionUtils.isNotEmpty(dataList)) {      
                dataStorage.apply(dataList);         
                fetchCount += dataList.size();     
                dataList.clear();     
            }
        }
    
        // 返回获取数量  
        return fetchCount;
    }
    
     // 使用案例
    long fetchCount = odpsService.executeFetch("user", 5000, record -> {  
        UserDO user = new UserDO();  
        user.setId(record.getBigint("id"));   
        user.setName(record.getString("name"));  
        return user;  
    }, dataList -> {   
        userDAO.batchInsert(dataList);
        return true;   
    });

    普通的建造者模式,实现时需要定义 DataHandler 接口,调用时需要实现 DataHandler 匿名内部类,代码较多较繁琐。而精简后的建造者模式,充分利用了函数式编程,实现时无需定义接口,直接使用 Function 接口;调用时无需实现匿名内部类,直接采用 lambda 表达式,代码较少较简洁。

    10.3.代理模式

    Spring 中最重要的代理模式就是 AOP (Aspect-Oriented Programming,面向切面的编程),是使用 JDK 动态代理和 CGLIB 动态代理技术来实现的。

    普通:

    @Slf4j
    @RestController
    @RequestMapping("/user")
    public class UserController {
        /** 用户服务 */  
        @Autowired  
        private UserService userService;
    
        /** 查询用户 */  
        @PostMapping("/queryUser") 
        public Result<?> queryUser(@RequestBody @Valid UserQueryVO query) {  
            try {     
                PageDataVO<UserVO> pageData = userService.queryUser(query);  
                return Result.success(pageData);    
            } catch (Exception e) {        
                log.error(e.getMessage(), e); 
                return Result.failure(e.getMessage());   
            }
        }
        ...
    }

    精简1:

    基于 @ControllerAdvice 的异常处理:

    @RestController
    @RequestMapping("/user")
    public class UserController {  
        /** 用户服务 */  
        @Autowired   
        private UserService userService;
    
        /** 查询用户 */ 
        @PostMapping("/queryUser")  
        public Result<PageDataVO<UserVO>> queryUser(@RequestBody @Valid UserQueryVO query) {     
            PageDataVO<UserVO> pageData = userService.queryUser(query);   
            return Result.success(pageData);  
        }
        ...
    }
    
    @Slf4j
    @ControllerAdvice
    public class GlobalControllerAdvice {  
        /** 处理异常 */ 
        @ResponseBody  
        @ExceptionHandler(Exception.class)   
        public Result<Void> handleException(Exception e) {   
            log.error(e.getMessage(), e);   
            return Result.failure(e.getMessage()); 
        }
    }

    精简2:

    基于 AOP 的异常处理:

    // UserController代码同"精简1"
    @Slf4j
    @Aspect
    public class WebExceptionAspect {  
        /** 点切面 */ 
        @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)") 
        private void webPointcut() {}
    
        /** 处理异常 */   
        @AfterThrowing(pointcut = "webPointcut()", throwing = "e") 
        public void handleException(Exception e) {  
            Result<Void> result = Result.failure(e.getMessage());    
            writeContent(JSON.toJSONString(result));   
        }
        ...
    }

    十一、利用删除代码

    “少即是多”,“少”不是空白而是精简,“多”不是拥挤而是完美。删除多余的代码,才能使代码更精简更完美。

    11.1.删除已废弃的代码

    删除项目中的已废弃的包、类、字段、方法、变量、常量、导入、注解、注释、已注释代码、Maven包导入、MyBatis的SQL语句、属性配置字段等,可以精简项目代码便于维护。

    普通:

    import lombok.extern.slf4j.Slf4j;
    @Slf4j
    @Service
    public class ProductService { 
        @Value("discardRate")  
        private double discardRate;  
        ...  
        private ProductVO transProductDO(ProductDO productDO) {  
            ProductVO productVO = new ProductVO();  
            BeanUtils.copyProperties(productDO, productVO);  
            // productVO.setPrice(getDiscardPrice(productDO.getPrice())); 
            return productVO;  
        }
        private BigDecimal getDiscardPrice(BigDecimal originalPrice) {     
            ...  
        }
    }

    精简:

    @Service
    public class ProductService {
    
        ...
    
        private ProductVO transProductDO(ProductDO productDO) {
    
            ProductVO productVO = new ProductVO();
    
            BeanUtils.copyProperties(productDO, productVO);
    
            return productVO;
    
        }
    }

    11.2.删除接口方法的public

    对于接口(interface),所有的字段和方法都是 public 的,可以不用显式声明为 public 。

    普通:

    public interface UserDAO {    
        public Long countUser(@Param("query") UserQuery query); 
        public List<UserDO> queryUser(@Param("query") UserQuery query);
    }

    精简:

    public interface UserDAO { 
        Long countUser(@Param("query") UserQuery query);
        List<UserDO> queryUser(@Param("query") UserQuery query);
    }

    11.3.删除枚举构造方法的 private

    对于枚举(menu),构造方法都是 private 的,可以不用显式声明为 private 。

    普通:

    public enum UserStatus {  
        DISABLED(0, "禁用"),  
        ENABLED(1, "启用"); 
        private final Integer value;  
        private final String desc;
        private UserStatus(Integer value, String desc) {  
            this.value = value;    
            this.desc = desc;   
        }  
        ...
    }

    精简:

    public enum UserStatus {  
        DISABLED(0, "禁用"),
        ENABLED(1, "启用");  
        private final Integer value; 
        private final String desc; 
        UserStatus(Integer value, String desc) {  
            this.value = value;       
            this.desc = desc; 
        }
        ...
    }

    11.4.删除 final 类方法的 final

    对于 final 类,不能被子类继承,所以其方法不会被覆盖,没有必要添加 final 修饰。

    普通:

    public final Rectangle implements Shape {  
    
        ...
    
        @Override
        public final double getArea() {  
            return width * height; 
        }
    }

    精简:

    public final Rectangle implements Shape {  
    
        ...    
    
        @Override
        public double getArea() {
            return width * height; 
        }
    }

    11.5.删除基类 implements 的接口

    如果基类已 implements 某接口,子类没有必要再 implements 该接口,只需要直接实现接口方法即可。

    普通:

    public interface Shape {  
        ... 
        double getArea();
    }
    
    public abstract AbstractShape implements Shape {   
        ...
    }
    
    public final Rectangle extends AbstractShape implements Shape { 
    
        ... 
    
        @Override
        public double getArea() {    
            return width * height; 
        }
    }

    精简:

    public final Rectangle extends AbstractShape {  
    
        ...    
    
        @Override   
        public double getArea() { 
            return width * height; 
        }
    }

    11.6.删除不必要的变量

    不必要的变量,只会让代码看起来更繁琐。

    普通:

    public Boolean existsUser(Long userId) {  
        Boolean exists = userDAO.exists(userId); 
        return exists;
    }

    精简:

    public Boolean existsUser(Long userId) { 
        return userDAO.exists(userId);
    }

    结语:

    关于java编程其实还有很多的小技巧,都需要我们在工作或学习中慢慢探索,在我学java的过程中,百战程序员的线上编程课对我帮助很大,不管是教学方式还是教学水平我都很认同,而且从中我也get了许多的编程规范和技巧,深受其益!

    提醒:本文发布于496天前,文中所关联的信息可能已发生改变,请知悉!

    Tips:清朝云网络工作室

  • 设计模式之UML类图


    共计 2051 个字符,预计需要花费 6 分钟才能阅读完成。

    1. 类图概述

    统一建模语言(Unified Modeling Language,UML)是用来设计软件的可视化建模语言。它的特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。

    UML 从目标系统的不同角度出发,定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等 9 种图。

    类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。

    2 类图的作用

    • 在软件工程中,类图是一种静态的结构图,描述了系统的类的集合,类的属性和类之间的关系,可以简化了人们对系统的理解;
    • 类图是系统分析和设计阶段的重要产物,是系统编码和测试的重要模型。

    3 类图表示法

    3.1 类的表示方式

    UML类图中,类使用包含类名、属性(field) 和方法(method) 且带有分割线的矩形来表示,比如下图表示一个Employee类,它包含name,age和address这3个属性,以及work()方法。

    设计模式之UML类图

    />

    属性/方法名称前加的加号和减号表示了这个属性/方法的可见性,UML类图中表示可见性的符号有三种:

    • +:表示public

    • -:表示private

    • :表示protected

    属性的完整表示方式是: 可见性 名称 :类型 [ = 缺省值]

    方法的完整表示方式是: 可见性 名称(参数列表) [ : 返回类型]

    注意:

    ​ 1,中括号中的内容表示是可选的

    ​ 2,也有将类型放在变量名前面,返回值类型放在方法名前面

    举个栗子:

    设计模式之UML类图

    />

    上图Demo类定义了三个方法:

    • method()方法:修饰符为public,没有参数,没有返回值。
    • method1()方法:修饰符为private,没有参数,返回值类型为String。
    • method2()方法:修饰符为protected,接收两个参数,第一个参数类型为int,第二个参数类型为String,返回值类型是int。

    3.2 类与类之间关系的表示方式

    3.2.1 关联关系

    关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系。我们先介绍一般关联。

    关联又可以分为单向关联,双向关联,自关联。

    1,单向关联

    设计模式之UML类图

    />

    在UML类图中单向关联用一个带箭头的实线表示。上图表示每个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现。

    2,双向关联

    设计模式之UML类图

    />

    从上图中我们很容易看出,所谓的双向关联就是双方各自持有对方类型的成员变量。

    在UML类图中,双向关联用一个不带箭头的直线表示。上图中在Customer类中维护一个List\,表示一个顾客可以购买多个商品;在Product类中维护一个Customer类型的成员变量表示这个产品被哪个顾客所购买。

    3,自关联

    设计模式之UML类图

    />

    自关联在UML类图中用一个带有箭头且指向自身的线表示。上图的意思就是Node类包含类型为Node的成员变量,也就是“自己包含自己”。

    3.2.2 聚合关系

    聚合关系是关联关系的一种,是强关联关系,是整体和部分之间的关系。

    聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。

    在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。下图所示是大学和教师的关系图:

    设计模式之UML类图

    />

    3.2.3 组合关系

    组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。

    在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,没有了头,嘴也就不存在了。

    在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。下图所示是头和嘴的关系图:

    设计模式之UML类图

    />

    3.2.4 依赖关系

    依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。

    在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示是司机和汽车的关系图,司机驾驶汽车:

    设计模式之UML类图

    />

    3.2.5 继承关系

    继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。

    在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。例如,Student 类和 Teacher 类都是 Person 类的子类,其类图如下图所示:

    设计模式之UML类图

    />

    3.2.6 实现关系

    实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。

    在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如,汽车和船实现了交通工具,其类图如图 9 所示。

    设计模式之UML类图

    提醒:本文发布于495天前,文中所关联的信息可能已发生改变,请知悉!

    />

    Tips:清朝云网络工作室

  • 设计模式之软件设计原则


    共计 7182 个字符,预计需要花费 18 分钟才能阅读完成。

    在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据 6 条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本。

    1 开闭原则

    对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。

    想要达到这样的效果,我们需要使用接口和抽象类。

    因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。

    下面以 搜狗输入法 的皮肤为例介绍开闭原则的应用。

    【例】搜狗输入法 的皮肤设计。

    分析:搜狗输入法 的皮肤是输入法背景图片、窗口颜色和声音等元素的组合。用户可以根据自己的喜爱更换自己的输入法的皮肤,也可以从网上下载新的皮肤。这些皮肤有共同的特点,可以为其定义一个抽象类(AbstractSkin),而每个具体的皮肤(DefaultSpecificSkin和HeimaSpecificSkin)是其子类。用户窗体可以根据需要选择或者增加新的主题,而不需要修改原代码,所以它是满足开闭原则的。

    设计模式之软件设计原则

    />

    2 里氏代换原则

    里氏代换原则是面向对象设计的基本原则之一。

    里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

    如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。

    下面看一个里氏替换原则中经典的一个例子

    【例】正方形不是长方形。

    在数学领域里,正方形毫无疑问是长方形,它是一个长宽相等的长方形。所以,我们开发的一个与几何图形相关的软件系统,就可以顺理成章的让正方形继承自长方形。

    设计模式之软件设计原则

    />

    代码如下:

    长方形类(Rectangle):

    public class Rectangle {
        private double length;
        private double width;
    
        public double getLength() {
            return length;
        }
    
        public void setLength(double length) {
            this.length = length;
        }
    
        public double getWidth() {
            return width;
        }
    
        public void setWidth(double width) {
            this.width = width;
        }
    }

    正方形(Square):

    由于正方形的长和宽相同,所以在方法setLength和setWidth中,对长度和宽度都需要赋相同值。

    public class Square extends Rectangle {
    
        public void setWidth(double width) {
            super.setLength(width);
            super.setWidth(width);
        }
    
        public void setLength(double length) {
            super.setLength(length);
            super.setWidth(length);
        }
    }

    类RectangleDemo是我们的软件系统中的一个组件,它有一个resize方法依赖基类Rectangle,resize方法是RectandleDemo类中的一个方法,用来实现宽度逐渐增长的效果。

    public class RectangleDemo {
    
        public static void resize(Rectangle rectangle) {
            while (rectangle.getWidth() <= rectangle.getLength()) {
                rectangle.setWidth(rectangle.getWidth() + 1);
            }
        }
    
        //打印长方形的长和宽
        public static void printLengthAndWidth(Rectangle rectangle) {
            System.out.println(rectangle.getLength());
            System.out.println(rectangle.getWidth());
        }
    
        public static void main(String[] args) {
            Rectangle rectangle = new Rectangle();
            rectangle.setLength(20);
            rectangle.setWidth(10);
            resize(rectangle);
            printLengthAndWidth(rectangle);
    
            System.out.println("============");
    
            Rectangle rectangle1 = new Square();
            rectangle1.setLength(10);
            resize(rectangle1);
            printLengthAndWidth(rectangle1);
        }
    }

    我们运行一下这段代码就会发现,假如我们把一个普通长方形作为参数传入resize方法,就会看到长方形宽度逐渐增长的效果,当宽度大于长度,代码就会停止,这种行为的结果符合我们的预期;假如我们再把一个正方形作为参数传入resize方法后,就会看到正方形的宽度和长度都在不断增长,代码会一直运行下去,直至系统产生溢出错误。所以,普通的长方形是适合这段代码的,正方形不适合。
    我们得出结论:在resize方法中,Rectangle类型的参数是不能被Square类型的参数所代替,如果进行了替换就得不到预期结果。因此,Square类和Rectangle类之间的继承关系违反了里氏代换原则,它们之间的继承关系不成立,正方形不是长方形。

    如何改进呢?此时我们需要重新设计他们之间的关系。抽象出来一个四边形接口(Quadrilateral),让Rectangle类和Square类实现Quadrilateral接口

    设计模式之软件设计原则

    />

    3 依赖倒转原则

    高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

    下面看一个例子来理解依赖倒转原则

    【例】组装电脑

    现要组装一台电脑,需要配件cpu,硬盘,内存条。只有这些配置都有了,计算机才能正常的运行。选择cpu有很多选择,如Intel,AMD等,硬盘可以选择希捷,西数等,内存条可以选择金士顿,海盗船等。

    类图如下:

    设计模式之软件设计原则

    />

    代码如下:

    希捷硬盘类(XiJieHardDisk):

    public class XiJieHardDisk implements HardDisk {
    
        public void save(String data) {
            System.out.println("使用希捷硬盘存储数据" + data);
        }
    
        public String get() {
            System.out.println("使用希捷希捷硬盘取数据");
            return "数据";
        }
    }

    Intel处理器(IntelCpu):

    public class IntelCpu implements Cpu {
    
        public void run() {
            System.out.println("使用Intel处理器");
        }
    }

    金士顿内存条(KingstonMemory):

    public class KingstonMemory implements Memory {
    
        public void save() {
            System.out.println("使用金士顿作为内存条");
        }
    }

    电脑(Computer):

    public class Computer {
    
        private XiJieHardDisk hardDisk;
        private IntelCpu cpu;
        private KingstonMemory memory;
    
        public IntelCpu getCpu() {
            return cpu;
        }
    
        public void setCpu(IntelCpu cpu) {
            this.cpu = cpu;
        }
    
        public KingstonMemory getMemory() {
            return memory;
        }
    
        public void setMemory(KingstonMemory memory) {
            this.memory = memory;
        }
    
        public XiJieHardDisk getHardDisk() {
            return hardDisk;
        }
    
        public void setHardDisk(XiJieHardDisk hardDisk) {
            this.hardDisk = hardDisk;
        }
    
        public void run() {
            System.out.println("计算机工作");
            cpu.run();
            memory.save();
            String data = hardDisk.get();
            System.out.println("从硬盘中获取的数据为:" + data);
        }
    }

    测试类(TestComputer):

    测试类用来组装电脑。

    public class TestComputer {
        public static void main(String[] args) {
            Computer computer = new Computer();
            computer.setHardDisk(new XiJieHardDisk());
            computer.setCpu(new IntelCpu());
            computer.setMemory(new KingstonMemory());
    
            computer.run();
        }
    }

    上面代码可以看到已经组装了一台电脑,但是似乎组装的电脑的cpu只能是Intel的,内存条只能是金士顿的,硬盘只能是希捷的,这对用户肯定是不友好的,用户有了机箱肯定是想按照自己的喜好,选择自己喜欢的配件。

    根据依赖倒转原则进行改进:

    代码我们只需要修改Computer类,让Computer类依赖抽象(各个配件的接口),而不是依赖于各个组件具体的实现类。

    类图如下:

    设计模式之软件设计原则

    />

    电脑(Computer):

    public class Computer {
    
        private HardDisk hardDisk;
        private Cpu cpu;
        private Memory memory;
    
        public HardDisk getHardDisk() {
            return hardDisk;
        }
    
        public void setHardDisk(HardDisk hardDisk) {
            this.hardDisk = hardDisk;
        }
    
        public Cpu getCpu() {
            return cpu;
        }
    
        public void setCpu(Cpu cpu) {
            this.cpu = cpu;
        }
    
        public Memory getMemory() {
            return memory;
        }
    
        public void setMemory(Memory memory) {
            this.memory = memory;
        }
    
        public void run() {
            System.out.println("计算机工作");
        }
    }

    面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度。

    4 接口隔离原则

    客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上。

    下面看一个例子来理解接口隔离原则

    【例】安全门案例

    我们需要创建一个黑马品牌的安全门,该安全门具有防火、防水、防盗的功能。可以将防火,防水,防盗功能提取成一个接口,形成一套规范。类图如下:

    设计模式之软件设计原则

    />

    上面的设计我们发现了它存在的问题,黑马品牌的安全门具有防盗,防水,防火的功能。现在如果我们还需要再创建一个传智品牌的安全门,而该安全门只具有防盗、防水功能呢?很显然如果实现SafetyDoor接口就违背了接口隔离原则,那么我们如何进行修改呢?看如下类图:

    设计模式之软件设计原则

    />

    代码如下:

    AntiTheft(接口):

    public interface AntiTheft {
        void antiTheft();
    }

    Fireproof(接口):

    public interface Fireproof {
        void fireproof();
    }

    Waterproof(接口):

    public interface Waterproof {
        void waterproof();
    }

    HeiMaSafetyDoor(类):

    public class HeiMaSafetyDoor implements AntiTheft,Fireproof,Waterproof {
        public void antiTheft() {
            System.out.println("防盗");
        }
    
        public void fireproof() {
            System.out.println("防火");
        }
    
        public void waterproof() {
            System.out.println("防水");
        }
    }

    ItcastSafetyDoor(类):

    public class ItcastSafetyDoor implements AntiTheft,Fireproof {
        public void antiTheft() {
            System.out.println("防盗");
        }
    
        public void fireproof() {
            System.out.println("防火");
        }
    }

    5 迪米特法则

    迪米特法则又叫最少知识原则。

    只和你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。

    其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

    迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。

    下面看一个例子来理解迪米特法则

    【例】明星与经纪人的关系实例

    明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如和粉丝的见面会,和媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则。

    类图如下:

    设计模式之软件设计原则

    />

    代码如下:

    明星类(Star)

    public class Star {
        private String name;
    
        public Star(String name) {
            this.name=name;
        }
    
        public String getName() {
            return name;
        }
    }

    粉丝类(Fans)

    public class Fans {
        private String name;
    
        public Fans(String name) {
            this.name=name;
        }
    
        public String getName() {
            return name;
        }
    }

    媒体公司类(Company)

    public class Company {
        private String name;
    
        public Company(String name) {
            this.name=name;
        }
    
        public String getName() {
            return name;
        }
    }

    经纪人类(Agent)

    public class Agent {
        private Star star;
        private Fans fans;
        private Company company;
    
        public void setStar(Star star) {
            this.star = star;
        }
    
        public void setFans(Fans fans) {
            this.fans = fans;
        }
    
        public void setCompany(Company company) {
            this.company = company;
        }
    
        public void meeting() {
            System.out.println(fans.getName() + "与明星" + star.getName() + "见面了。");
        }
    
        public void business() {
            System.out.println(company.getName() + "与明星" + star.getName() + "洽淡业务。");
        }
    }

    6 合成复用原则

    合成复用原则是指:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

    通常类的复用分为继承复用和合成复用两种。

    继承复用虽然有简单和易实现的优点,但它也存在以下缺点:

    1. 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
    2. 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
    3. 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。

    采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:

    1. 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
    2. 对象间的耦合度低。可以在类的成员位置声明抽象。
    3. 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。

    下面看一个例子来理解合成复用原则

    【例】汽车分类管理程序

    汽车按“动力源”划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这两种分类,其组合就很多。类图如下:

    设计模式之软件设计原则

    />

    从上面类图我们可以看到使用继承复用产生了很多子类,如果现在又有新的动力源或者新的颜色的话,就需要再定义新的类。我们试着将继承复用改为聚合复用看一下。

    设计模式之软件设计原则

    提醒:本文发布于495天前,文中所关联的信息可能已发生改变,请知悉!

    />

    Tips:清朝云网络工作室