Java java지도 시간 Java는 불필요한 객체 생성을 방지합니다.

Java는 불필요한 객체 생성을 방지합니다.

Dec 01, 2016 pm 04:27 PM
java

리틀 앨런은 최근에 "Effective Java"라는 책을 읽었습니다. 이 책은 매우 풍부한 내용을 담고 있습니다. 저는 이 책을 자세히 소개하지 않겠습니다. Java 개발자로서 이 책은 필연적으로 그리울 것입니다. 작은 후회가 되기 때문에 그래도 시간이 남는 친구들에게는 이 책을 읽어보라고 권한다. 나는 아직 이 책에 소개된 내용 중 많은 부분을 이해하지 못하고 일상적인 개발에서 반드시 많은 부분을 사용하지 않을 수도 있으므로 모든 것을 자세히 설명하지는 않고 이 책에서 최대한 추출할 수 있습니다. 일상적인 개발에서 실용적인 부분과 초보자인 Little Alan이 이해할 수 있는 부분에 대해서는 Little Alan의 작업 경험과 깊은 이해를 바탕으로 천천히 정리할 수 있습니다. 그것이 유용하다고 생각하는 몇몇 친구들을 위한 생각.

"효과적인 자바" 5조: 불필요한 객체 생성을 피하세요

원문을 여러 부분으로 나누어 이해하고, 작은 목표를 하나씩 달성하고, 최종적으로 이 부분의 내용을 완전히 이해하게 됩니다. .

1부: 일반적으로 말해서, 필요할 때마다 동일한 기능을 가진 새 객체를 만드는 것보다 객체를 재사용하는 것이 더 좋습니다. 재사용은 빠르고 대중적입니다. 객체가 변경 불가능한 경우 언제든지 재사용할 수 있습니다.

부정적 예:

String s = new String("Papapa"); //이 작업을 수행하지 마세요!

이 문이 실행될 때마다 둘 다 새 항목을 생성합니다. 문자열 인스턴스이지만 객체를 생성하는 이러한 작업은 모두 불필요합니다. String 생성자에 전달된 매개변수("papapa")는 그 자체가 String 인스턴스이며, 이는 생성자에 의해 생성된 모든 객체와 기능적으로 동일합니다. 이 사용법이 루프에 있거나 자주 호출되는 메서드에 있으면 수천 개의 불필요한 String 인스턴스가 생성됩니다.

개선된 버전:

String s = "Papapa";

이 버전은 String 인스턴스가 실행될 때마다 새 String 인스턴스를 생성하는 대신 하나의 String 인스턴스만 사용합니다. 또한 동일한 문자열 리터럴을 포함하는 한 동일한 가상 머신에서 실행되는 모든 코드에 대해 객체가 재사용된다는 것을 보장합니다.

확장된 아이디어: ① Java 1.7에서 실행될 때 Java는 메소드 영역에서 실행될 때 상수 풀에 첫 번째 인스턴스를 기록합니다. 즉, "pah pah pah"가 상수 풀에 저장된다는 의미입니다. 다음에 String s = "Papapa";를 호출하면 Java는 새 객체를 다시 생성하는 대신 이 객체에 대한 참조를 직접 반환합니다. 이렇게 하면 메모리 오버헤드가 절약되고 메서드에서 안전하게 루프를 사용할 수 있습니다. 메소드에서 자주 호출되는 것을 두려워하지 않습니다. String s = new String("PaPaPaPa"); 실제로 두 개의 객체를 생성합니다. 하나는 힙에 저장되고 다른 하나는 상수 풀에 저장됩니다. s는 스택에 저장된 객체에 대한 참조일 뿐입니다. 및 String s = "Papapa"; 객체를 생성하여 상수 풀에 저장한 다음 스택에 객체에 대한 참조를 저장합니다. (저는 Java 가상 머신에 대해 깊이 이해하지 못합니다. 제가 잘못 이해했다면 지적해 주세요.)

2부: 정적 팩토리 메서드와 생성자를 모두 제공하는 불변 클래스의 경우 일반적으로 생성자 대신 정적 팩터리 메서드를 사용하여 불필요한 객체 생성을 방지할 수 있습니다. 예를 들어 정적 팩터리 메서드 Boolean.valueOf(String)는 거의 항상 Boolean(String) 생성자보다 우선합니다. 생성자는 호출될 때마다 새 객체를 생성하지만 정적 팩토리 메서드는 이 작업을 수행할 필요가 없으며 실제로도 그렇게 하지 않습니다.

확장 아이디어:

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

}
로그인 후 복사

정적 팩토리 메소드 valueOf를 사용하면 새 객체가 생성되지 않아 불필요한 객체가 많이 생성되는 것을 방지할 수 있습니다. 많은 클래스의 기본 valueOf 메소드는 Java에서 제공하는 유형뿐만 아니라 원본 기사에서 언급한 부울 유형과 같은 새 인스턴스를 반환하지 않습니다. 일상적인 개발에서 비슷한 요구 사항이 있는 경우 정적 인스턴스를 모방하는 것이 좋습니다. 우리 자신의 클래스를 제공하기 위해 Java에서 제공하는 팩토리 메소드는 객체 획득을 달성하고 객체의 반복 생성을 피하기 위해 정적 팩토리 메소드를 정의하지만 정적 팩토리 메소드 사용에 대해 지나치게 미신적이지는 않습니다. 정적 팩토리 메소드에 대해서는 "Effective Java"의 1조)를 읽을 수 있습니다. 저는 이 메소드를 거의 사용하지 않습니다. 일반적인 클래스에서 더 많은 객체를 생성해도 사용법에 약간만 주의를 기울이는 한 큰 영향을 미치지 않습니다. 괜찮아.

3부: 불변 객체를 재사용하는 것 외에도 수정되지 않는 것으로 알려진 가변 객체를 재사용할 수도 있습니다. 책에 쓰여진 예는 이해하기가 매우 어려우므로 시간을 내어 모두에게 비슷한 예를 생각해 냈습니다. 이것이 의미하는 바가 무엇인지 모르겠습니다. 조언!

부정적 예:

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 Type<引用类型>)混用,按需要自动装箱和拆箱。自动装箱使得基本类型和引用类型之间的差别变得模糊起来,但是并没有完全消除。它们在语义上还有着微妙的差别,在性能上也有着比较明显的差别。考虑下面的程序,它计算所有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虚拟机)具有高度优化的垃圾回收器,如果是轻量的对象池可能还不如垃圾回收器的性能。

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


본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

자바의 완전수 자바의 완전수 Aug 30, 2024 pm 04:28 PM

Java의 완전수 가이드. 여기서는 정의, Java에서 완전 숫자를 확인하는 방법, 코드 구현 예제에 대해 논의합니다.

Java의 난수 생성기 Java의 난수 생성기 Aug 30, 2024 pm 04:27 PM

Java의 난수 생성기 안내. 여기서는 예제를 통해 Java의 함수와 예제를 통해 두 가지 다른 생성기에 대해 설명합니다.

자바의 웨카 자바의 웨카 Aug 30, 2024 pm 04:28 PM

Java의 Weka 가이드. 여기에서는 소개, weka java 사용 방법, 플랫폼 유형 및 장점을 예제와 함께 설명합니다.

Java의 스미스 번호 Java의 스미스 번호 Aug 30, 2024 pm 04:28 PM

Java의 Smith Number 가이드. 여기서는 정의, Java에서 스미스 번호를 확인하는 방법에 대해 논의합니다. 코드 구현의 예.

Java Spring 인터뷰 질문 Java Spring 인터뷰 질문 Aug 30, 2024 pm 04:29 PM

이 기사에서는 가장 많이 묻는 Java Spring 면접 질문과 자세한 답변을 보관했습니다. 그래야 면접에 합격할 수 있습니다.

Java 8 Stream foreach에서 나누거나 돌아 오시겠습니까? Java 8 Stream foreach에서 나누거나 돌아 오시겠습니까? Feb 07, 2025 pm 12:09 PM

Java 8은 스트림 API를 소개하여 데이터 컬렉션을 처리하는 강력하고 표현적인 방법을 제공합니다. 그러나 스트림을 사용할 때 일반적인 질문은 다음과 같은 것입니다. 기존 루프는 조기 중단 또는 반환을 허용하지만 스트림의 Foreach 메소드는이 방법을 직접 지원하지 않습니다. 이 기사는 이유를 설명하고 스트림 처리 시스템에서 조기 종료를 구현하기위한 대체 방법을 탐색합니다. 추가 읽기 : Java Stream API 개선 스트림 foreach를 이해하십시오 Foreach 메소드는 스트림의 각 요소에서 하나의 작업을 수행하는 터미널 작동입니다. 디자인 의도입니다

Java의 날짜까지의 타임스탬프 Java의 날짜까지의 타임스탬프 Aug 30, 2024 pm 04:28 PM

Java의 TimeStamp to Date 안내. 여기서는 소개와 예제와 함께 Java에서 타임스탬프를 날짜로 변환하는 방법에 대해서도 설명합니다.

미래를 창조하세요: 완전 초보자를 위한 Java 프로그래밍 미래를 창조하세요: 완전 초보자를 위한 Java 프로그래밍 Oct 13, 2024 pm 01:32 PM

Java는 초보자와 숙련된 개발자 모두가 배울 수 있는 인기 있는 프로그래밍 언어입니다. 이 튜토리얼은 기본 개념부터 시작하여 고급 주제를 통해 진행됩니다. Java Development Kit를 설치한 후 간단한 "Hello, World!" 프로그램을 작성하여 프로그래밍을 연습할 수 있습니다. 코드를 이해한 후 명령 프롬프트를 사용하여 프로그램을 컴파일하고 실행하면 "Hello, World!"가 콘솔에 출력됩니다. Java를 배우면 프로그래밍 여정이 시작되고, 숙달이 깊어짐에 따라 더 복잡한 애플리케이션을 만들 수 있습니다.

See all articles