Heim  >  Artikel  >  Java  >  Java vermeidet die Erstellung unnötiger Objekte

Java vermeidet die Erstellung unnötiger Objekte

高洛峰
高洛峰Original
2016-12-01 16:27:151199Durchsuche

Der kleine Alan hat kürzlich das Buch „Effective Java“ gelesen. Ich werde dieses Buch nicht im Detail vorstellen. Als Java-Entwickler werde ich dieses Buch zwangsläufig verpassen Es ist ein kleines Bedauern geworden, daher empfehle ich Freunden, die Zeit haben, dieses Buch zu lesen. Es gibt immer Zeit, sich Zeit zu nehmen. Ich verstehe viele der in diesem Buch vorgestellten Dinge immer noch nicht und wir werden viele davon möglicherweise nicht unbedingt in der täglichen Entwicklung verwenden. Daher werde ich nicht alles im Detail erklären, sondern nur das herausholen, was wir daraus machen können Praktische Teile in der täglichen Entwicklung und die Teile, die der kleine Alan, ein Anfänger, verstehen kann. Was einige unpraktische und fortgeschrittene Teile betrifft, können wir sie nur langsam auf der Grundlage der Arbeitserfahrung und des tiefgreifenden Verständnisses des kleinen Alan klären Gedanken für einige Freunde, die es nützlich finden.

Artikel 5 von „Effektives Java“: Vermeiden Sie die Erstellung unnötiger Objekte

Wir werden den Originaltext in mehrere Teile aufteilen, um ihn zu verstehen, nacheinander kleine Ziele zu erreichen und schließlich den Inhalt dieses Teils vollständig zu verstehen .

Teil 1: Im Allgemeinen ist es besser, Objekte wiederzuverwenden, als jedes Mal, wenn Sie sie benötigen, ein neues Objekt mit derselben Funktionalität zu erstellen. Die Wiederverwendung ist schnell und beliebt. Wenn ein Objekt unveränderlich ist, kann es jederzeit wiederverwendet werden.

Negativbeispiel:

String s = new String("Papapa"); //Tu das nicht!

Jedes Mal, wenn diese Anweisung ausgeführt wird, erstellen beide eine neue String-Instanz, aber diese Aktionen zum Erstellen von Objekten sind alle unnötig. Der an den String-Konstruktor übergebene Parameter („papapa“) ist selbst eine String-Instanz, die funktional allen vom Konstruktor erstellten Objekten entspricht. Wenn diese Verwendung in einer Schleife oder in einer Methode erfolgt, die häufig aufgerufen wird, werden Tausende unnötiger String-Instanzen erstellt.

Verbesserte Version:

String s = "Papapa";

Diese Version verwendet nur eine String-Instanz, anstatt bei jeder Ausführung eine neue String-Instanz zu erstellen. Darüber hinaus garantiert es, dass das Objekt für den gesamten Code, der in derselben virtuellen Maschine ausgeführt wird, wiederverwendet wird, solange sie dasselbe Zeichenfolgenliteral enthalten.

Erweiterte Ideen: ① Bei der Ausführung in Java 1.7 zeichnet Java beim Ausführen im Methodenbereich die erste Instanz im Konstantenpool auf, was bedeutet, dass „pah pah pah“ dann im Konstantenpool gespeichert wird Wenn Sie String s = „Papapa“ das nächste Mal aufrufen, gibt Java direkt einen Verweis auf dieses Objekt zurück, anstatt ein neues Objekt zu erstellen. Dies spart Speicheraufwand und ermöglicht die sichere Verwendung in der Methode Sie haben keine Angst davor, in der Methode häufig angerufen zu werden. String s = new String("PaPaPaPa"); erstellt tatsächlich zwei Objekte, eines wird im Heap gespeichert und das andere wird im Konstantenpool gespeichert. s ist nur eine Referenz auf das im Stapel gespeicherte Objekt. und String s = „Papapa“; es wird nur ein Objekt erstellt und im Konstantenpool gespeichert und dann ein Verweis auf das Objekt auf dem Stapel gespeichert (ich habe keine tiefen Kenntnisse der Java Virtual Machine. Bitte weisen Sie darauf hin, wenn ich das falsch verstehe. Vielen Dank.

Teil 2: Für unveränderliche Klassen, die sowohl statische Factory-Methoden als auch Konstruktoren bereitstellen, können Sie normalerweise statische Factory-Methoden anstelle von Konstruktoren verwenden, um die Erstellung unnötiger Objekte zu vermeiden. Beispielsweise hat die statische Factory-Methode Boolean.valueOf(String) fast immer Vorrang vor dem Konstruktor Boolean(String). Der Konstruktor erstellt bei jedem Aufruf ein neues Objekt, während statische Factory-Methoden dazu niemals erforderlich sind und dies auch nicht tun werden.

Erweiterte Ideen:

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);
    }

}

Es ist ersichtlich, dass durch die Verwendung der statischen Factory-Methode valueOf kein neues Objekt erstellt wird, wodurch die Erstellung einer großen Anzahl unnötiger Objekte vermieden wird. Die Standardmethode valueOf vieler Klassen gibt keine neue Instanz zurück, z. B. den im Originalartikel erwähnten booleschen Typ, nicht nur diese von Java bereitgestellten Typen. Wenn wir in der täglichen Entwicklung ähnliche Anforderungen haben, können wir die Statik genauso gut nachahmen Die von Java bereitgestellte Factory-Methode gibt uns unsere eigenen Klassen. Definieren Sie auch solche statischen Factory-Methoden, um die Objekterfassung zu erreichen und die wiederholte Erstellung von Objekten zu vermeiden. Seien Sie jedoch nicht zu abergläubisch bei der Verwendung statischer Factory-Methoden. Diese Methode hat auch ihre Nachteile (für das Wissen darüber). Statische Factory-Methoden finden Sie in „Artikel 1 von „Effektives Java“). Ich verwende diese Methode selten. Das Erstellen weiterer Objekte in einer normalen Klasse hat keine großen Auswirkungen, solange Sie der Verwendung ein wenig Aufmerksamkeit schenken sei ok.

Teil 3: Zusätzlich zur Wiederverwendung unveränderlicher Objekte können Sie auch veränderliche Objekte wiederverwenden, von denen bekannt ist, dass sie nicht geändert werden. Die im Buch geschriebenen Beispiele sind sehr schwer zu verstehen, und ich habe mir nicht die Zeit genommen, sie zu lesen. Ich weiß nicht, ob es das ist, was es bedeutet Beratung!

Negativbeispiel:

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虚拟机)具有高度优化的垃圾回收器,如果是轻量的对象池可能还不如垃圾回收器的性能。

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


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn