学习总结录 学习总结录
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
  • 设计模式

  • 算法思想

  • 编码规范

    • 创建和销毁对象
      • 静态工厂方法代替构造器
      • 多个构造器参数时要考虑使用构造器
      • 通过私有构造器强化不可实例化的能力
      • 避免创建不必要的对象
        • 静态工厂方法避免创建不必要的对象
        • 自动装箱造成多余对象创建
      • try-with-resources 优于 try-finally
      • 参考
    • 类和接口
  • 技术思想
  • 编码规范
旭日
2023-04-05
目录

创建和销毁对象

# 静态工厂方法代替构造器

对于一个类而言,如果想要获得它的实例,有两种方法:

  • 构造方法
  • 静态工厂方法

其中构造方法是大家熟悉的,但是构造方法存在一些问题:

  • 构造方法的名称必须和类名相同
  • 构造方法的参数复杂,一个类可能有多个构造函数,如果要具体参数的内容就需要去代码里面查看

静态工厂方法如下(Boolean为例):

    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
  • 静态工厂方法能够拥有自己的名字,便于开发者更好调用。

  • 静态工厂方法不需要每次去返回一个新的对象。只需要把返回的对象进行静态实例化,避免了对象的多次创建。例如在Boolean中就设置了两个静态实例。

        /**
         * The {@code Boolean} object corresponding to the primitive
         * value {@code true}.
         */
        public static final Boolean TRUE = new Boolean(true);
    
        /**
         * The {@code Boolean} object corresponding to the primitive
         * value {@code false}.
         */
        public static final Boolean FALSE = new Boolean(false);
    

如果程序经常请求创建相同的对象,并且创建的对象代价很高,通过这样的技术极大地提升了性能。

# 多个构造器参数时要考虑使用构造器

现在假设存在一个Person类,其中这个类中name和sex属性是必须的,其他属性age、hight、weight不是必须的,甚至随着业务的发展,这个类以后可能还有其他的属性。

public class Person {

    private final String name;

    private final Integer sex;

    private final Integer age;

    private final Integer height;

    private final Integer weight;

    public Person(String name, Integer sex) {
        this(name, sex, 0);
    }
    
    public Person(String name, Integer sex, Integer age) {
        this(name, sex, age, 0);
    }
    
    public Person(String name, Integer sex, Integer age, Integer height) {
        this(name, sex, age, height, 0);
    }
    
    public Person(String name, Integer sex, Integer age, Integer height, Integer weight) {
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.height = height;
        this.weight = weight;
    }
}

这样的构造器通常需要许多我本来不想设置,但是又不得不设置的值,同时参数会随着业务的增长变得越来越多,整个代码会特别复杂。这时候会去采用一个建造者的方法,通过链式来创建对象。

public class Person {

    private final String name;

    private final Integer sex;

    private final Integer age;

    private final Integer height;

    private final Integer weight;

    public static class Builder {
        private final String name;

        private final Integer sex;

        private Integer age = 0;

        private Integer height = 0;

        private Integer weight = 0;

        public Builder(String name, Integer sex) {
            this.name = name;
            this.sex = sex;
        }

        public Builder setAge(Integer val) {
            age = val;
            return this;
        }

        public Builder setHeight(Integer val) {
            height = val;
            return this;
        }

        public Builder setWeight(Integer val) {
            weight = val;
            return this;
        }

        public Person build() {
            return new Person(this);
        }
    }

    private Person(Builder builder) {
        name = builder.name;
        age = builder.age;
        sex = builder.sex;
        height = builder.height;
        weight = builder.weight;
    }
}

具体使用:

public class Main {
    public static void main(String[] args) {
        Person person = new Person.Builder("wxx", 0)
                .setWeight(100)
                .setAge(24)
                .setHeight(180).build();
    }
}

通过这样构建的代码,有如下好处:

  • 阅读性强
  • 必须参数和非必须参数能够进行分离

# 通过私有构造器强化不可实例化的能力

现在我有一个工具类,它只需要编写静态方法和静态域的类,但是我并不像让其实例化:

public class TestUtils {

    public static void test1() {
        System.out.println("test1");
    }

    public static void test2() {
        System.out.println("test2");
    }

    public static void test3() {
        System.out.println("test3");
    }
}

上面的代码在缺少显示构造器的情况下,编译器会自动提供一个公有的、无参的缺省构造器。这样客户端是可以通过new的方式来创建对象。为了让这个工具类无法实例化,只需要让这个类包含一个私有构造器。

public class TestUtils {

    private TestUtils() {
        
    }
    public static void test1() {
        System.out.println("test1");
    }

    public static void test2() {
        System.out.println("test2");
    }

    public static void test3() {
        System.out.println("test3");
    }
}

这类思想多处用于很多经典工具源码中:

public class Arrays {

    // Suppresses default constructor, ensuring non-instantiability.
    private Arrays() {}
}

# 避免创建不必要的对象

有如下简单的代码:

String test = new String("123456");

该语句在每次运行的时候都会创建一个新的String实例,如果这个用法是在一个循环中,或者是在一个频繁调用的方法中,那么就会创建许多个不必要的实例。

改进后的语句如下:

String test = "123456";

针对这种不必要的对象创建,需要在两个地方注意:

  • 静态工厂方法
  • 自动装箱

# 静态工厂方法避免创建不必要的对象

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

如果这段代码需要经常执行,我们需要根据传入的参数,返回True和FALSE,可以发现这两个返回结果每当执行这个方法一次,就需要进行返回。

如果每次返回我们都去创建一个新的对象,那么性能就很低下,所以对这种需要经常创建的对象,采用静态实例来避免创建不必要的对象。

    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code true}.
     */
    public static final Boolean TRUE = new Boolean(true);

    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code false}.
     */
    public static final Boolean FALSE = new Boolean(false);

# 自动装箱造成多余对象创建

    public static void main(String[] args) {
        Long sum = 0L;
        for (long i = 0; i <= Integer.MAX_VALUE ; i++) {
            sum += i;
        }
        System.out.println(sum);
    }

上述代码只是一个简单的累加,但是因为i是基本类型long,但是sum是包装类型Long。累加的过程就会涉及到自动装箱,每次往sum中增加long时就会去构造一个实例。

改进后的代码如下(只需要改进sum为基本类型)

    public static void main(String[] args) {
        long sum = 0L;
        for (long i = 0; i <= Integer.MAX_VALUE ; i++) {
            sum += i;
        }
        System.out.println(sum);
    }

记住:要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。

# try-with-resources 优于 try-finally

对于一些I/O相关的代码操作,为了确保资源最终关闭,我们会采用try-finally方法:

    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new FileReader("123456");
        try {
            bufferedReader.readLine();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            bufferedReader.close();
        }
    }

这样的代码会存在两个问题:

  • 如果当调用的IO资源变多的时候,finally代码块就会有许多关闭资源的代码
  • try和finally都可能因为底层物理设置异常,抛出异常,如果第二个异常覆盖了第一个异常,就不便于异常的排查。

使用try-with-resources进行代码改进,把需要关闭的资源放入try中即可:

    public static void main(String[] args) throws FileNotFoundException {
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader("123456"))) {
            bufferedReader.readLine();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

在处理必须关闭的资源时,始终要优先考虑用try-with-resources

# 参考

Effective Java (opens new window)

上次更新: 2024/06/29, 15:13:44
滑动窗口
类和接口

← 滑动窗口 类和接口→

最近更新
01
基础概念
10-31
02
Pytorch
10-30
03
Numpy
10-30
更多文章>
Theme by Vdoing | Copyright © 2021-2024 旭日 | 蜀ICP备2021000788号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式