Home  >  Article  >  Java  >  Java avoids creating unnecessary objects

Java avoids creating unnecessary objects

高洛峰
高洛峰Original
2016-12-01 16:27:151160browse

Little Alan recently read the book "Effective Java". This book contains very rich content. I will not introduce this book in detail. I can only say silently that as a Java developer, I missed this book. It will inevitably become a small regret, so I still recommend that friends who have time can read this book. There is always time to squeeze. I still don’t understand many of the things introduced in this book, and we may not necessarily use many of them in our daily development, so I won’t explain everything in detail, but will only extract what we can from it. The more practical parts in daily development, and the parts that little Alan, a novice, can understand. As for some impractical and advanced parts, we can only sort them out slowly based on little Alan's work experience and in-depth understanding. I also clarify my thoughts for some friends who find it useful.

Article 5 of "Effective Java": Avoid creating unnecessary objects

We split the original text into several parts to understand, achieve small goals one by one, and finally fully understand the content of this piece.

Part 1: Generally speaking, it is better to reuse objects rather than create a new object with the same functionality every time you need it. Reuse is fast and popular. If an object is immutable, it can always be reused.

Negative example:

String s = new String("Papapa"); //Don't do this!

This statement creates a new String instance every time it is executed, but these create objects All actions are unnecessary. The parameter ("papapa") passed to the String constructor is itself a String instance, which is functionally equivalent to all objects created by the constructor. If this usage is in a loop, or in a method that is called frequently, thousands of unnecessary String instances will be created.

Improved version:

String s = "Papapa";

This version only uses one String instance instead of creating a new String instance every time it is executed. Furthermore, it guarantees that the object will be reused for all code running in the same virtual machine as long as they contain the same string literal.

Expanded ideas: ① When running in Java 1.7, Java will record the first instance in the constant pool when running in the method area, which means that "pah pah pah" will be saved in the constant pool, then when you call it next time When String s = "Papapa";, Java will directly return a reference to this object instead of re-creating a new object. This saves memory overhead and can be used in a loop with confidence. You are not afraid of being called frequently in methods. String s = new String("PaPaPaPa"); actually creates two objects, one is stored in the heap, and the other is "PaPaPaPa" saved in the constant pool. s is just a reference to the object stored in the stack. , and String s = "Papapa"; it will only create an object and save it in the constant pool, and then save a reference to the object on the stack. (I don’t have a deep understanding of the Java virtual machine. Please point out if I understand wrongly. Many thanks).

Part 2: For immutable classes that provide both static factory methods and constructors, you can usually use static factory methods instead of constructors to avoid creating unnecessary objects. For example, the static factory method Boolean.valueOf(String) will almost always take precedence over the constructor Boolean(String). The constructor creates a new object every time it is called, while static factory methods are never required to do this, and in fact will not do so.

Extended ideas:

package com.czgo.effective;

/**
 * 用valueOf()静态工厂方法代替构造器
 * @author AlanLee
 * @version 2016/12/01
 *
 */
public class Test {

    public static void main(String[] args) {
        // 使用带参构造器
        Integer a1 = new Integer("1");
        Integer a2 = new Integer("1");
        
        //使用valueOf()静态工厂方法
        Integer a3 = Integer.valueOf("1");
        Integer a4 = Integer.valueOf("1");
        
        //结果为false,因为创建了不同的对象
        System.out.println(a1 == a2);
        
        //结果为true,因为不会新建对象
        System.out.println(a3 == a4);
    }

}

It can be seen that using the static factory method valueOf will not create a new object, avoiding the creation of a large number of unnecessary objects. In fact, the default valueOf method of many classes will not return a new instance, such as the original article mentioned The Boolean types mentioned are not only the types provided by Java. If we have similar needs in daily development, we might as well imitate the static factory methods provided by Java and define such static factory methods for our own classes to implement. Obtain objects and avoid repeated creation of objects, but don’t be overly superstitious about using static factory methods. This method also has its drawbacks (for knowledge about static factory methods, you can read the first article of "Effective Java"). Personally, If you use this method sparingly, creating more objects for your usual class will not have much impact. As long as you pay a little attention to the usage, it will be ok.

Part 3: In addition to reusing immutable objects, you can also reuse mutable objects that are known not to be modified. The examples written in the book are very difficult to understand, and I didn’t take the time to read them. I came up with a similar example for everyone. I don’t know if this is what it means. Please give me some advice!

Negative example:

package com.czgo.effective;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBUtilBad {
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/imooc";
    private static final String UNAME = "root";
    private static final String PWD = "root";

    public static Connection getConnection() {
        Connection conn = null;
        try {
            // 1.加载驱动程序
            Class.forName("com.mysql.jdbc.Driver");
            // 2.获得数据库的连接
            conn = DriverManager.getConnection(URL, UNAME, PWD);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
}

该类提供的getConnection方法获取JDBC数据库连接对象,每次调用该方法都会新建一个conn实例,而我们知道在平时的开发中数据库连接对象往往只需要一个,也不会总是去修改它,没必要每次都去新创建一个连接对象,每次都去创建一个实例不知道程序会不会出现什么意外情况,这个我不知道,但有一点是肯定的,这种方式影响程序的运行性能,增加了Java虚拟机垃圾回收器的负担。我们可以对它进行改进。

改进版本:

package com.czgo.effective;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBUtil {
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/imooc";
    private static final String UNAME = "root";
    private static final String PWD = "root";

    private static Connection conn = null;

    static {
        try {
            // 1.加载驱动程序
            Class.forName("com.mysql.jdbc.Driver");
            // 2.获得数据库的连接
            conn = DriverManager.getConnection(URL, UNAME, PWD);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() {
        return conn;
    }
}

我们使用了静态代码块来创建conn实例,改进后只有在类加载初始化的时候创建了conn实例一次,而不是在每次调用getConnection方法的时候都去创建conn实例。如果getConnection方法被频繁的调用和使用,这种方式将会显著的提高我们程序的性能。除了提高性能之外,代码的含义也更加的清晰了,使得代码更易于理解。

第四部分:Map接口的keySet方法返回该Map对象的Set视图,其中包含该Map中所有的键(key)。粗看起来,好像每次调用keySet都应该创建一个新的Set实例,但是,对于一个给定的Map对象,实际上每次调用keySet都返回同样的Set实例。虽然被返回的Set实例一般是可改变的,但是所有返回的对象在功能上是等同的:当其中一个返回对象发生变化的时候,所有其他返回对象也要发生变化,因为它们是由同一个Map实例支撑的。虽然创建keySet视图对象的多个实例并无害处,却也是没有必要的。

package com.czgo.effective;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class TestKeySet {

    public static void main(String[] args) {
        
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("A", "A");
        map.put("B", "B");
        map.put("C", "C");
        
        Set<String> set = map.keySet();
        Iterator<String> it = set.iterator();
        while(it.hasNext()){
            System.out.println(it.next()+"①");
        }
        
        System.out.println("---------------");
        
        map.put("D", "D");
        set = map.keySet();
        it = set.iterator();
        while(it.hasNext()){
            System.out.println(it.next()+"②");
        }
        
    }

}

第五部分:有一种创建多余对象的新方法,称作自动装箱(autoboxing),它允许程序员将基本类型和装箱基本类型(Boxed Primitive Type4f157ac1ec92f600c22109a8c35fc045)混用,按需要自动装箱和拆箱。自动装箱使得基本类型和引用类型之间的差别变得模糊起来,但是并没有完全消除。它们在语义上还有着微妙的差别,在性能上也有着比较明显的差别。考虑下面的程序,它计算所有int正值的总和。为此,程序必须使用long变量,因为int不够大,无法容纳所有int正值的总和:

package com.czgo.effective;

public class TestLonglong {

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

段程序算出的结果是正确的,但是比实际情况要慢的多,只因为打错了一个字符。变量sum被声明成Long而不是long,意味着程序构造了大约2的31次方个多余的Long实例(大约每次往Long sum中增加long时构造一个实例)。将sum的声明从Long改成long,速度快了不是一点半点。结论很明显:要优先使用基本类型而不是引用类型,要当心无意识的自动装箱。

最后,不要错误地认为"创建对象的代价非常昂贵,我们应该尽可能地避免创建对象"。相反,由于小对象的构造器只做很少量的显示工作,所以小对象的创建和回收动作是非常廉价的,特别是在现代的JVM实现上更是如此。通过创建附加的对象,提升程序的清晰性、简洁性和功能性,这通常是件好事。

反之,通过维护自己的对象池(Object pool)来避免创建对象并不是一种好的做法,除非池中的对象是非常重量级的。真正正确使用对象池的典型对象示例就是数据库连接池。建立数据库连接的代价是非常昂贵的,因此重用这些对象非常有意义。而如今的JVM(Java虚拟机)具有高度优化的垃圾回收器,如果是轻量的对象池可能还不如垃圾回收器的性能。

这里我们说到“当你应该重用现有对象的时候,请不要创建新的对象”,反之我们也应该考虑一个问题“当你应该创建新对象的时候,请不要重用现有的对象”。有时候重用对象要付出的代价要远远大于因创建重复对象而付出的代价。必要时,如果没能创建新的对象实例将会导致潜在的错误和安全漏洞;而不必要地创建对象则只会影响程序的风格和性能。


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn