12条通用编程原则✨全面提升Java编码规范性、可读性及性能表现

简介: 12条通用编程原则✨全面提升Java编码规范性、可读性及性能表现

12条通用编程原则✨全面提升Java编码规范性、可读性及性能表现

基于Effective Java 通用编程章节总结12条通用编程原则

从最基础的局部变量使用、控制结构编写,到集成类库、明智选择数据类型,慎用反射和本地方法,以及如何对待优化和遵循命名惯例等方面,全面提升编码的规范性、可读性以及性能表现

image.png

将局部变量作用域最小化

将局部变量作用域最小化可以增强可读性、可维护性,降低出错可能

要将局部变量作用域最小化,最好在第一次使用它时进行声明

几乎每个局部变量声明时都需要初始化

如果局部变量作用域只在循环中,那么for循环优于while循环

        //for循环局部变量作用域只在循环中
        for (Iterator iterator = list.iterator(); iterator.hasNext(); ) {
            System.out.println(iterator.next());
        }

        //while循环作用域更大
        Iterator<Integer> i1 = list.iterator();
        while (i1.hasNext()) {
            System.out.println(i1.next());
        }

并且使用for循环能够降低CV大法出错的可能性

        Iterator<Integer> i1 = list.iterator();
        while (i1.hasNext()) {
            System.out.println(i1.next());
        }

        Iterator<Integer> i2 = list.iterator();
        //CV大法忘记修改 i1
        while (i1.hasNext()) {
            System.out.println(i1.next());
        }

遇到方法实现代码较多导致局部变量作用域变大可以拆分方法

foreach循环优于for循环

foreach循环又叫增强for循环是一种语法糖,实际上使用迭代器和for循环实现

foreach循环能够隐藏迭代器和for循环中的索引(只展示元素)

如果只需要元素而不需要迭代器和索引,那么foreach会更加简洁,并且不会产生性能消耗

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
        //for循环
        for (Iterator iterator = list.iterator(); iterator.hasNext(); ) {
            System.out.println(iterator.next());
        }
        for (int index = 0; index < list.size(); index++) {
            System.out.println(list.get(index));
        }

        //foreach
        for (Integer integer : list) {
            System.out.println(integer);
        }

如果需要使用迭代器或索引那么就无法使用foreach

了解和使用类库

使用类库的好处是不必自己造轮子性能会提高(由专业的算法工程师实现)、会维护(随着版本升级,开发者提出的缺点会被解决)、让代码更易维护

优先使用JDK中的类库,无法满足需求时使用第三方类库如guava,三方类库也无法满足时再自己实现

精确计算避免使用浮点型

浮点型flout、double在计算时可能出现精度溢出

        //拥有的钱 1.00
        double funds = 1.00;
        int itemsBought = 0;
        //每次都买商品递增 0.1
        //每次价格 0.1、0.2、0.3、0.4 期望买四个商品 剩下的钱为0
        for (double price = 0.10; funds >= price; price += 0.10) {
            funds -= price;
            itemsBought++;
        }
        //3 items bought.
        System.out.println(itemsBought + " items bought.");
        //Change: $0.3999999999999999
        System.out.println("Change: $" + funds);

如果需要精确计算不要使用浮点型,可以转换为最小单位使用整形int、long计算(注意值不要超过范围)

        //去除小数、使用整形
        int funds = 100;
        int itemsBought = 0;
        for (int price = 10; funds >= price; price += 10) {
            funds -= price;
            itemsBought++;
        }
        //4 items bought.
        System.out.println(itemsBought + " items bought.");
        //Cash left over: 0 cents
        System.out.println("Cash left over: " + funds + " cents");

如果必须有小数计算或超过整形的范围上限可以使用BigDecimal计算

(BigDecimal不熟悉API就使用容易踩坑)

基本数据类型优于包装类

Java提供8种基本数据类型:boolean、byte、char、short、int、long、float、double

每种基本数据类型都有其对应的包装类(基本数据类型的对象版本)

基本数据类与包装类的区别:

  1. 包装类可以值相同但对象引用不同,比较要用equals (如果使用 == 可能会导致结果错误)
    Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
    int result1 = naturalOrder.compare(new Integer(42), new Integer(42));
    // 1
    System.out.println(result1);
  1. 包装类还可以为null
  2. 基本数据类型更省时间、空间

基本数据类型与包装类混用时,会将包装类自动拆箱为基本数据类型

        Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> {
            //自动拆箱
            int i = iBoxed, j = jBoxed;
            return i < j ? -1 : (i == j ? 0 : 1);
        };
        int result2 = naturalOrder.compare(new Integer(42), new Integer(42));
        // 0
        System.out.println(result2);

如果基本数据类型需要转换为包装类使用,则会使用自动装箱

注意自动拆箱、装箱,循环操作会有大量性能开销

        Long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
            System.out.println(sum);
        }

当使用集合、反射时需要使用包装类,其他情况考虑使用基本数据类型

如果其他类型更适合,尽量避免使用字符串

字符串一般只用于文本存储

如果存储类型为整形数值、确定是否的布尔类型或是其他更适合该场景的类型,都避免使用字符串代替

字符串可能带来更大的开销,如存储数值99999

注意字符串拼接性能问题

使用字符串时注意拼接性能问题

需要频繁拼接使用StringBuilder,需要线程安全使用StringBuffer

通过接口引用对象

对于字段、局部变量、方法入参、方法返回值的声明,如果有合适的接口类型就用接口类型进行声明

接口类型能够更灵活,后续如果想增强功能、替换实现不需要太大改动 (注意使用的方法为实现类都共享的方法)

        Map<String, String> map = new HashMap<>();
        Map<String, String> map = new LinkedHashMap<>();

如果没有合适的接口类型,则尽量使用类结构上层的类,如抽象类、公共实现父类等

谨慎使用反射

使用反射会带来很多缺点:

  1. 失去编译期间的类型检查(运行时可能抛出异常)
  2. 反射实现的代码可读性不好且繁多
  3. 性能更差

使用反射能够在编译期间引用对应的接口,而不需要其实现类已经实现

比如编译时引用Set,运行时再去加载其实现类HashSet或TreeSet

        Class<? extends Set<String>> cl = (Class<? extends Set<String>>) Class.forName("java.util.HashSet");
//        Class<? extends Set<String>> cl = (Class<? extends Set<String>>) Class.forName("java.util.TreeSet");
        Constructor<? extends Set<String>> cons = cl.getDeclaredConstructor();
        Set<String> s = cons.newInstance();
        s.addAll(Arrays.asList("a", "aa", "bbb", "qqq", "asd"));
        //使用TreeSet:[a, aa, asd, bbb, qqq]
        //使用HashSet:[aa, qqq, a, bbb, asd]
        System.out.println(s);

使用JDBC获取MySQL的连接,在编译期间不需要MySQL实现JDBC连接的实现类

通过引用连接的接口Connection,运行时使用Class.forName加载实现类

类似这种服务提供者框架都可以使用反射来实现

谨慎使用本地方法

本地方法就是非Java(其他语言)实现的方法/函数

通过JNI/JNA使用本地方法时,无法得到JVM分配内存、垃圾回收等好处,稍有不小心就会导致出现错误,并且不好进行调试

如果非要使用本地方法一定要做好测试

谨慎进行优化

设计系统时考虑性能因素,不要为了性能而牺牲设计的合理结构

构建完系统后测试性能,如果未满足要求再去通过工具(如arthas)分析性能瓶颈,解决后再进行压测,直到满足要求

遵守普遍接受的命名规范

遵守常规的命名规范:包名小写、驼峰命名、常量大写...

可实例化的类通用单数名词,不可实例的工具类用复数 (Collection、Collections)

类型转换通用toXX(toString、toArray)

返回包装类对应基本类型通用xxValue (intValue、doubleValue)

命名规范尽量使用大家公认的方法,见名知意

总结

最好让局部变量作用域最小化,在第一次使用时声明,作用域只在循环时优先使用for循环

foreach是迭代器与for循环实现的语法糖,只展示元素屏蔽迭代器与索引,优先使用foreach

优先使用JDK、第三方类库,不要自己造轮子

精确计算不使用浮点型,可以转换为最小单位使用整形,如果必须要小数部分或计算量超出整形范围使用BigDecimal

只有集合、反射时才用包装类,其他情况使用基本数据类型,使用包装类注意空指针和用equals比较

字符串适合文本,其他类型合适避免使用字符串

字符串大量拼接时使用StringBuilder,线程安全使用StringBuffer

声明入参、变量、返回时使用接口声明更加灵活,如果没合适接口就选择抽象类/公共父类

反射会导致丢失编译类型检测、性能损耗、代码不好阅读,可以实现服务提供者框架,在编译期间引用接口,而不需要其实现类已经实现

使用本地方法会丢失JVM分配内存、垃圾回收等益处,尽量不用,如果使用要做好测试

设计系统时考虑性能,不要为了性能破坏合理的设计结构,性能不达标分析性能瓶颈再优化

遵守常规、公认的命名规范,见名知意

最后(不要白嫖,一键三连求求拉~)

本篇文章被收入专栏 Effective Java,感兴趣的同学可以持续关注喔

本篇文章笔记以及案例被收入 Gitee-CaiCaiJavaGithub-CaiCaiJava 感兴趣的同学可以stat下持续关注喔~

有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~

关注菜菜,分享更多干货,公众号:菜菜的后端私房菜

相关文章
|
7天前
|
设计模式 安全 Java
Java编程中的单例模式深入剖析
【10月更文挑战第21天】在Java的世界里,单例模式是设计模式中一个常见而又强大的存在。它确保了一个类只有一个实例,并提供一个全局访问点。本文将深入探讨如何正确实现单例模式,包括常见的实现方式、优缺点分析以及最佳实践,同时也会通过实际代码示例来加深理解。无论你是Java新手还是资深开发者,这篇文章都将为你提供宝贵的见解和技巧。
90 65
|
3天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
11 3
|
4天前
|
Java 数据库连接 数据库
优化之路:Java连接池技术助力数据库性能飞跃
在Java应用开发中,数据库操作常成为性能瓶颈。频繁的数据库连接建立和断开增加了系统开销,导致性能下降。本文通过问题解答形式,深入探讨Java连接池技术如何通过复用数据库连接,显著减少连接开销,提升系统性能。文章详细介绍了连接池的优势、选择标准、使用方法及优化策略,帮助开发者实现数据库性能的飞跃。
14 4
|
5天前
|
监控 安全 Java
Java多线程编程的艺术与实践
【10月更文挑战第22天】 在现代软件开发中,多线程编程是一项不可或缺的技能。本文将深入探讨Java多线程编程的核心概念、常见问题以及最佳实践,帮助开发者掌握这一强大的工具。我们将从基础概念入手,逐步深入到高级主题,包括线程的创建与管理、同步机制、线程池的使用等。通过实际案例分析,本文旨在提供一种系统化的学习方法,使读者能够在实际项目中灵活运用多线程技术。
|
6天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
2天前
|
Java 数据库连接 数据库
深入探讨Java连接池技术如何通过复用数据库连接、减少连接建立和断开的开销,从而显著提升系统性能
在Java应用开发中,数据库操作常成为性能瓶颈。本文通过问题解答形式,深入探讨Java连接池技术如何通过复用数据库连接、减少连接建立和断开的开销,从而显著提升系统性能。文章介绍了连接池的优势、选择和使用方法,以及优化配置的技巧。
7 1
|
3天前
|
缓存 安全 Java
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文将深入探讨Java中的多线程编程,包括其基本原理、实现方式以及常见问题。我们将从简单的线程创建开始,逐步深入了解线程的生命周期、同步机制、并发工具类等高级主题。通过实际案例和代码示例,帮助读者掌握多线程编程的核心概念和技术,提高程序的性能和可靠性。
8 2
|
4天前
|
Java
Java中的多线程编程:从基础到实践
本文深入探讨Java多线程编程,首先介绍多线程的基本概念和重要性,接着详细讲解如何在Java中创建和管理线程,最后通过实例演示多线程的实际应用。文章旨在帮助读者理解多线程的核心原理,掌握基本的多线程操作,并能够在实际项目中灵活运用多线程技术。
|
4天前
|
Java 程序员 开发者
Java编程中的异常处理艺术
【10月更文挑战第24天】在Java的世界里,代码就像一场精心编排的舞蹈,每一个动作都要精准无误。但就像最完美的舞者也可能踩错一个步伐一样,我们的程序偶尔也会遇到意外——这就是所谓的异常。本文将带你走进Java的异常处理机制,从基本的try-catch语句到高级的异常链追踪,让你学会如何优雅地处理这些不请自来的“客人”。
|
6天前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
22 3