关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯

简介: 关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯

关于创建、销毁对象⭐Java程序员需要掌握的8个编程好习惯

本文基于Effective Java中创建和销毁对象的章节汇总出8个相关的好习惯(文末附案例地址)

思维导图如下:

image.png

1.静态工厂代替构造器

1.可以自定义名称,见名知意

见名知意的方法更容易理解,比如 getInstanceByCode 根据Code获取实例

2.获取对象时,可以使用单例、享元等思想进行复用

Boolean value = Boolean.valueOf(true);
public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

3.可以返回原类型的子类

Collections.singletonList(null);
public static <T> List<T> singletonList(T o) {
    return new SingletonList<>(o);
}

4.返回对象可以随着入参发生改变

根据入参key的不同可以获取不同的策略

public static <T extends XXStrategy> T getStrategyByKey(String key) {
    return (T) strategyFactory.get(key);
}

5.返回对象的类可以在编写静态工厂时不存在

在编写静态工厂中获取对象时,对象可以不存在(未编译),通过反射的手段在运行时加载进来

典型的例子就是JDBC,在使用前通过反射加载驱动

Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("url", "username", "password");

小结

业务中使用场景挺多的,比如:spring的getBean、实现业务的策略工厂(基本上能覆盖1-5特点)

策略工厂可以使用Map维护code以及对应的策略,这样就已经实现1、2、4

实现3时还需要使用泛型进行转换(上面案例给出),使用反射实现5

2.多个构造器参数考虑建造者模式

构造器的入参太多时容易弄混,还好现在的IDE开发工具可以提示参数名称,如果参数名称取的一团糟也会容易混淆

在这种场景下可以选择建造者模式 Builder

        //构造器
        Student student = new Student("张三", 18, "男", "北京", "13812345678", "", "12345678");
        
        //Builder
        Student.builder()
                .name("菜菜的后端私房菜")
                .age(18)
                .sex("男")
                .address("北京")
                .phone("13812345678")
                .email("")
                .qq("12345678")
                .build();

提高阅读性、多建一个类的开销、可以防止对象在构造期间逃逸

3.私有构造或枚举强化单例

私有构造防止调用构造创建

可以通过字段直接获取单例,也可以通过静态工厂方法获取单例 (饿汉式)

//通过字段
SingletonField singletonField = SingletonField.INSTANCE;
//通过静态工厂方法
SingletonStaticFactory singletonStaticFactory = SingletonStaticFactory.getInstance();
//通过枚举
SingletonEnum singletonEnum = SingletonEnum.INSTANCE;

通过反射、JDK序列化依旧可以调用构造,破坏单例

使用枚举避免反射调用单例

反射在实例化前会判断是否枚举,枚举则抛出异常

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enum objects");

4.考虑依赖注入

构造器注入,能够灵活使用多种依赖

    static class DependencyInjectionNotUse {
        private Util util = new Util();
        public DependencyInjectionNotUse() {
        }
    }
    static class DependencyInjectionUse {
        private Util util;
        public DependencyInjectionUse(Util util) {
            this.util = util;
        }
    }

依赖太多在大项目中会很乱,可以使用DI框架如 spring的IOC

5.避免创建不必要的对象

//创建了不必要对象
String a = new String("菜菜的后端私房菜");
String b = "菜菜的后端私房菜";

自动拆装箱,也会创建不必要对象(先转成long相加,再转Long)

        Long sum = 0L;
        for (long i = 0; i <= Integer.MAX_VALUE; i++) {
            sum += i;
//            Long.valueOf(sum = sum.longValue() + i);
        }

多使用单例、享元模式,复用资源,避免创建不必要对象:字符串、自动拆装箱等

6.消除过期的对象引用

Java采用引用计数法,如果不再使用对象时需要置空,否则会认为还要使用,从而导致内存泄漏

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        //出栈时需要将元素清除,不再引用
        //elements[size-1] = null
        return elements[--size];
    }

缓存、监听器也可能导致内存泄漏,注意使用弱引用或注意关闭

7.不使用finalize方法

finalize 由守护线程执行,无法预估执行的时机

finalize 为不可达对象才会执行的方法,如果要清楚资源使用finally关闭资源

感兴趣的同学可以查看这篇文章:如何判断对象“已死”

8.try-with-resources优于try-finally

twr会自动关闭资源,优先使用

        //try-finally
        BufferedReader br = new BufferedReader(new FileReader(path));
        try {
            return br.readLine();
        } finally {
            br.close();
        }
        //twr try-with-resources
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            return br.readLine();
        }

总结

考虑使用静态工厂方法代替构造器,静态工厂方法能够见名知意、可以使用单例或享元模式返回对象、搭配泛型返回对象可以是原类型子类、返回的对象可以随着入参改变、返回的对象所在的类可以在编译期不存在

在参数较多时可以考虑使用建造者模式,可以代码可读性更高、防止构造对象期间发生逃逸

可以通过私有构造强化单例,但能够被反射、序列号破坏单例;使用枚举单元素强化单例则可以避免破坏(在反射实例化前判断为枚举则抛出异常)

对象依赖的“工具”不是固定的时,可以采用依赖注入DI的方式进行改变,而不是直接写死;频繁使用DI在大项目中会比较混乱,使用DI框架可以解决,比如 Spring 的IOC

避免创建不必要的对象,如String的字符串常量、基本类型与包装类型的自动拆装箱

消除过期的引用对象:不再使用对象时,需要消除引用关系,否则基于引用计数法的Java则无法给对象进行回收,从而导致内存泄漏

不使用finalize方法:finalize方法是只有对象成为不可达对象才会调用,而且由守护线程执行,无法预估执行时机,不要使用其做清理工作

try-with-resources优于try-finally:twr自动关闭资源,避免忘记,在多层嵌套时阅读性也较好

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

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

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

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

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

相关文章
|
11天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
17天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
10天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
9天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
12天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
14天前
|
SQL 存储 Java
面向 Java 程序员的 SQLite 替代品
SQLite 是轻量级数据库,适用于小微型应用,但其对外部数据源支持较弱、无存储过程等问题影响了开发效率。esProc SPL 是一个纯 Java 开发的免费开源工具,支持标准 JDBC 接口,提供丰富的数据源访问、强大的流程控制和高效的数据处理能力,尤其适合 Java 和安卓开发。SPL 代码简洁易懂,支持热切换,可大幅提高开发效率。
|
18天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
15天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
17天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
41 2
|
3天前
|
Java API 数据库
Java 反射机制:动态编程的 “魔法钥匙”
Java反射机制是允许程序在运行时访问类、方法和字段信息的强大工具,被誉为动态编程的“魔法钥匙”。通过反射,开发者可以创建更加灵活、可扩展的应用程序。