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

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

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

相关文章
|
27天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
28天前
|
安全 Java UED
深入浅出Java多线程编程
【10月更文挑战第40天】在Java的世界中,多线程是提升应用性能和响应能力的关键。本文将通过浅显易懂的方式介绍Java中的多线程编程,从基础概念到高级特性,再到实际应用案例,带你一步步深入了解如何在Java中高效地使用多线程。文章不仅涵盖了理论知识,还提供了实用的代码示例,帮助你在实际开发中更好地应用多线程技术。
43 5
|
1月前
|
XML Java 数据库连接
性能提升秘籍:如何高效使用Java连接池管理数据库连接
在Java应用中,数据库连接管理至关重要。随着访问量增加,频繁创建和关闭连接会影响性能。为此,Java连接池技术应运而生,如HikariCP。本文通过代码示例介绍如何引入HikariCP依赖、配置连接池参数及使用连接池高效管理数据库连接,提升系统性能。
57 5
|
1月前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
122 3
|
14天前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
18天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
18天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
11天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
11天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
32 3
|
16天前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
34 2