关于创建、销毁对象⭐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下持续关注喔~

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

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

相关文章
|
4天前
|
Java 程序员
JAVA程序员的进阶之路:掌握URL与URLConnection,轻松玩转网络资源!
在Java编程中,网络资源的获取与处理至关重要。本文介绍了如何使用URL与URLConnection高效、准确地获取网络资源。首先,通过`java.net.URL`类定位网络资源;其次,利用`URLConnection`类实现资源的读取与写入。文章还提供了最佳实践,包括异常处理、连接池、超时设置和请求头与响应头的合理配置,帮助Java程序员提升技能,应对复杂网络编程场景。
25 9
|
3天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
1天前
|
SQL Java 程序员
倍增 Java 程序员的开发效率
应用计算困境:Java 作为主流开发语言,在数据处理方面存在复杂度高的问题,而 SQL 虽然简洁但受限于数据库架构。SPL(Structured Process Language)是一种纯 Java 开发的数据处理语言,结合了 Java 的架构灵活性和 SQL 的简洁性。SPL 提供简洁的语法、完善的计算能力、高效的 IDE、大数据支持、与 Java 应用无缝集成以及开放性和热切换特性,能够大幅提升开发效率和性能。
|
3天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
11 3
|
5天前
|
监控 安全 Java
Java多线程编程的艺术与实践
【10月更文挑战第22天】 在现代软件开发中,多线程编程是一项不可或缺的技能。本文将深入探讨Java多线程编程的核心概念、常见问题以及最佳实践,帮助开发者掌握这一强大的工具。我们将从基础概念入手,逐步深入到高级主题,包括线程的创建与管理、同步机制、线程池的使用等。通过实际案例分析,本文旨在提供一种系统化的学习方法,使读者能够在实际项目中灵活运用多线程技术。
|
3天前
|
缓存 安全 Java
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文将深入探讨Java中的多线程编程,包括其基本原理、实现方式以及常见问题。我们将从简单的线程创建开始,逐步深入了解线程的生命周期、同步机制、并发工具类等高级主题。通过实际案例和代码示例,帮助读者掌握多线程编程的核心概念和技术,提高程序的性能和可靠性。
8 2
|
4天前
|
Java
Java中的多线程编程:从基础到实践
本文深入探讨Java多线程编程,首先介绍多线程的基本概念和重要性,接着详细讲解如何在Java中创建和管理线程,最后通过实例演示多线程的实际应用。文章旨在帮助读者理解多线程的核心原理,掌握基本的多线程操作,并能够在实际项目中灵活运用多线程技术。
|
4天前
|
Java 程序员 开发者
Java编程中的异常处理艺术
【10月更文挑战第24天】在Java的世界里,代码就像一场精心编排的舞蹈,每一个动作都要精准无误。但就像最完美的舞者也可能踩错一个步伐一样,我们的程序偶尔也会遇到意外——这就是所谓的异常。本文将带你走进Java的异常处理机制,从基本的try-catch语句到高级的异常链追踪,让你学会如何优雅地处理这些不请自来的“客人”。
|
4天前
|
设计模式 SQL 安全
Java编程中的单例模式深入解析
【10月更文挑战第24天】在软件工程中,单例模式是设计模式的一种,它确保一个类只有一个实例,并提供一个全局访问点。本文将探讨如何在Java中使用单例模式,并分析其优缺点以及适用场景。
8 0
|
5天前
|
存储 Java
在Java编程的世界里,标识符命名是一项基础且至关重要的技能
在Java编程的世界里,标识符命名是一项基础且至关重要的技能
8 0