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

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

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

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

将局部变量作用域最小化

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

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

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

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

java 复制代码
        //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大法出错的可能性

java 复制代码
        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会更加简洁,并且不会产生性能消耗

java 复制代码
        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在计算时可能出现精度溢出

java 复制代码
        //拥有的钱 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计算(注意值不要超过范围)

java 复制代码
        //去除小数、使用整形
        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 (如果使用 == 可能会导致结果错误)

    java 复制代码
        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);
  2. 包装类还可以为null

  3. 基本数据类型更省时间、空间

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

java 复制代码
        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);

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

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

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

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

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

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

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

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

注意字符串拼接性能问题

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

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

通过接口引用对象

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

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

java 复制代码
        Map<String, String> map = new HashMap<>();
        Map<String, String> map = new LinkedHashMap<>();

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

谨慎使用反射

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

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

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

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

java 复制代码
        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下持续关注喔~

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

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

相关推荐
utmhikari43 分钟前
【架构艺术】Go语言微服务monorepo的代码架构设计
后端·微服务·架构·golang·monorepo
蜡笔小新星1 小时前
Flask项目框架
开发语言·前端·经验分享·后端·python·学习·flask
计算机学姐1 小时前
基于Asp.net的驾校管理系统
vue.js·后端·mysql·sqlserver·c#·asp.net·.netcore
欢乐少年19043 小时前
SpringBoot集成Sentry日志收集-3 (Spring Boot集成)
spring boot·后端·sentry
夏天的味道٥4 小时前
使用 Java 执行 SQL 语句和存储过程
java·开发语言·sql
冰糖码奇朵5 小时前
大数据表高效导入导出解决方案,mysql数据库LOAD DATA命令和INTO OUTFILE命令详解
java·数据库·sql·mysql
好教员好5 小时前
【Spring】整合【SpringMVC】
java·spring
浪九天6 小时前
Java直通车系列13【Spring MVC】(Spring MVC常用注解)
java·后端·spring
堕落年代7 小时前
Maven匹配机制和仓库库设置
java·maven
功德+n7 小时前
Maven 使用指南:基础 + 进阶 + 高级用法
java·开发语言·maven