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下持续关注喔~

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

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

相关文章
|
5天前
|
数据采集 安全 Java
Java并发编程学习12-任务取消(上)
【5月更文挑战第6天】本篇介绍了取消策略、线程中断、中断策略 和 响应中断的内容
30 4
Java并发编程学习12-任务取消(上)
|
1天前
|
Java 测试技术
如何提高Java代码的可读性
Java是一种常用的编程语言,但是写出易懂且可读性高的代码却是一项挑战。本文将分享一些技巧和建议,帮助您提高Java代码的可读性和可维护性。
|
1天前
|
Java 程序员 调度
Java中的多线程编程:基础知识与实践
【5月更文挑战第19天】多线程编程是Java中的一个重要概念,它允许程序员在同一时间执行多个任务。本文将介绍Java多线程的基础知识,包括线程的创建、启动和管理,以及如何通过多线程提高程序的性能和响应性。
|
2天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【5月更文挑战第18天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将了解线程池的基本概念,应用场景,以及如何优化线程池的性能。通过实例分析,我们将看到线程池如何提高系统性能,减少资源消耗,并提高系统的响应速度。
12 5
|
2天前
|
消息中间件 安全 Java
理解Java中的多线程编程
【5月更文挑战第18天】本文介绍了Java中的多线程编程,包括线程和多线程的基本概念。Java通过继承Thread类或实现Runnable接口来创建线程,此外还支持使用线程池(如ExecutorService和Executors)进行更高效的管理。多线程编程需要注意线程安全、性能优化和线程间通信,以避免数据竞争、死锁等问题,并确保程序高效运行。
|
2天前
|
安全 Java 容器
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第18天】随着多核处理器的普及,并发编程变得越来越重要。Java提供了丰富的并发编程工具,如synchronized关键字、显式锁Lock、原子类、并发容器等。本文将深入探讨Java并发编程的核心概念,包括线程安全、死锁、资源竞争等,并分享一些性能优化的技巧。
|
2天前
|
安全 Java 开发者
Java中的多线程编程:理解与实践
【5月更文挑战第18天】在现代软件开发中,多线程编程是提高程序性能和响应速度的重要手段。Java作为一种广泛使用的编程语言,其内置的多线程支持使得开发者能够轻松地实现并行处理。本文将深入探讨Java多线程的基本概念、实现方式以及常见的并发问题,并通过实例代码演示如何高效地使用多线程技术。通过阅读本文,读者将对Java多线程编程有一个全面的认识,并能够在实际开发中灵活运用。
|
2天前
|
Java 编译器
Java并发编程中的锁优化策略
【5月更文挑战第18天】在Java并发编程中,锁是一种常用的同步机制,用于保护共享资源的访问。然而,不当的锁使用可能导致性能问题和死锁风险。本文将探讨Java中锁的优化策略,包括锁粗化、锁消除、锁分离和读写锁等技术,以提高并发程序的性能和可靠性。
|
3天前
|
Java 编译器
Java 并发编程中的锁优化策略
【5月更文挑战第17天】在 Java 并发编程中,锁是一种常见的同步机制,用于保护共享资源的访问。然而,不当使用锁可能导致性能问题和死锁风险。本文将探讨 Java 中的锁优化策略,包括锁粗化、锁消除、锁降级以及读写锁等技术,以提高并发程序的性能和可靠性。
|
3天前
|
Java 编译器
Java并发编程中的锁优化策略
【5月更文挑战第17天】在Java并发编程中,锁是一种常见的同步机制,用于保护共享资源。然而,使用不当的锁可能导致性能下降和死锁等问题。本文将探讨Java中锁的优化策略,包括锁粗化、锁消除、锁排序等方法,以提高程序的性能和可靠性。