Java에서 String 클래스가 final로 선언 된 이유는 무엇입니까?


141

java.lang.StringJava에서 클래스 가 final로 선언 되었다는 것을 알았을 때 왜 그런지 궁금했습니다. 그때 대답을 찾지 못했지만이 게시물 : Java에서 String 클래스의 복제본을 만드는 방법은 무엇입니까? 내 질문을 상기시켰다.

물론 String은 내가 필요한 모든 기능을 제공하며 String 클래스의 확장이 필요한 작업은 생각하지 못했지만 여전히 누군가가 필요한 것을 알 수는 없습니다!

그렇다면 디자이너가 최종 결정을 내릴 때 디자이너의 의도가 무엇인지 아는 사람이 있습니까?


답변, 특히 TrueWill, Bruno Reis 및 Thilo에 감사드립니다. 나는 하나 이상의 답변을 최고의 답변으로 선택할 수 있기를 원하지만 불행히도 ...!
Alex Ntousias

1
또한 "Om String에 대한 몇 가지 utilty 메소드가 더 필요하다"라는 프로젝트의 확산을 고려하십시오. 프로젝트는 서로 다른 클래스이기 때문에 서로 String을 사용할 수 없습니다.
Thorbjørn Ravn Andersen 님이

이 회신에 매우 감사합니다. 지금 두 가지 사실이 있습니다. String은 Final 클래스이며 변경할 수 없지만 다른 객체를 참조 할 수 있기 때문에 변경할 수 없습니다. 그러나 어떨까요 :-String a = new String ( "test1"); 그런 다음 s = "test2"; String이 Final 클래스 객체이면 어떻게 수정할 수 있습니까? 수정 된 최종 객체를 어떻게 사용할 수 있습니까? 내가 잘못 물어 본다면 제게 알려주세요.
Suresh Sharma

이 좋은 기사를 볼 수 있습니다 .
Aniket Thakur

4
우리가 Java에서 고맙게도 피한 한 가지는 "모두가 많은 추가 메소드를 가진 자체의 String 서브 클래스를 가지고 있으며 이들 중 어느 것도 서로 호환되지 않는다"는 것입니다.
Thorbjørn Ravn Andersen

답변:


88

변경 불가능한 객체 로 문자열을 구현하는 것이 매우 유용 합니다 . 당신은에 대해 읽어야 불변성 그것에 대해 더 이해하기.

불변 개체의 장점 중 하나 는

중복을 단일 인스턴스로 지정하여 중복을 공유 할 수 있습니다.

( 여기에서 ).

문자열이 최종이 아닌 경우 서브 클래스를 작성하고 "문자열로 볼 때"와 유사하지만 실제로는 다른 두 개의 문자열을 가질 수 있습니다.


70
최종 클래스와 내가 볼 수없는 불변의 객체 사이에 연결이 없다면, 귀하의 답변이 질문과 어떻게 관련되어 있는지 알 수 없습니다.
sepp2k

9
마지막이 아닌 경우 StringChild를 String 매개 변수로 일부 메소드에 전달할 수 있으며 (하위 클래스 상태 변경으로 인해) 변경할 수 있기 때문입니다.
helios

4
와! 공감 비? 서브 클래 싱이 불변성과 어떤 관련이 있는지 이해하지 못하십니까? 문제가 무엇인지 설명해 주셔서 감사합니다.
Bruno Reis

7
@Bruno, re : downvotes : 난 당신을 downvote하지 않았지만 서브 클래스를 막는 것이 불변성을 강제하는 방법에 대한 문장을 추가 할 수 있습니다. 지금은 반 답변입니다.
Thilo

12
@ BrunoReis- 링크 할 수있는 멋진 기사를 찾았습니다. James Gosling (Java 개발자)과의 인터뷰에서 여기서이 주제에 대해 간단히 이야기 합니다 . 흥미로운 스 니펫은 다음과 같습니다. "문자열을 변경 불가능하게 만든 것 중 하나는 보안이었습니다. 파일 열기 방법이 있습니다. 문자열을 전달합니다. 그런 다음 OS를 실행하기 전에 모든 종류의 인증 검사를 수행합니다. 보안 점검 후와 OS 호출 전에 문자열을 효과적으로 변경 한 작업을 수행하는 경우 붐이 발생합니다. "
Anurag

60

위의 답변에서 이미 언급 한 두 가지 이유를 간략히 설명 하는 좋은 기사 입니다.

  1. 보안 : 시스템은 변경 될 걱정없이 민감한 읽기 전용 정보를 전달할 수 있습니다.
  2. 성능 : 불변의 데이터는 스레드 안전을 만드는 데 매우 유용합니다.

그리고 이것은 아마도 그 기사에서 가장 자세한 주석 일 것입니다. Java 및 보안 문제의 문자열 풀과 관련이 있습니다. 문자열 풀에 들어갈 내용을 결정하는 방법에 대해 설명합니다. 문자의 순서가 동일하면 두 문자열이 동일하다고 가정하면 누가 먼저 보안 문제에 도달하는지에 대한 경쟁 조건이 있습니다. 그렇지 않은 경우, 문자열 풀에는 중복 문자열이 포함되므로 우선 문자열을 사용하는 이점을 잃게됩니다. 그냥 읽어 보시겠습니까?


문자열을 확장하면 같고 인턴으로 혼란 스러울 수 있습니다. JavaDoc은 다음과 같습니다.

이 문자열을 지정된 객체와 비교합니다. 인수가 널이 아니고이 오브젝트와 동일한 문자 시퀀스를 나타내는 String 오브젝트 인 경우에만 결과가 참입니다.

java.lang.String최종이 아니라고 가정하면 SafeStringa String와 같고 그 반대도 가능합니다. 그것들은 동일한 문자 시퀀스를 나타 내기 때문입니다.

당신이 적용하면 어떻게 될까 internA를 SafeString- 것 SafeStringJVM의 문자열 풀로 이동? ClassLoader모두는 개체를 SafeString다음 JVM의 수명 동안 제자리에 고정 얻을 것이다으로 보류 참조. 캐릭터 시퀀스를 처음으로 인턴 할 수있는 사람에 대한 경쟁 조건이 생길 수 있습니다. 어쩌면 당신 SafeString이 이길 수도 있고 String, 또는 SafeString다른 클래스 로더에 의해로드 될 수도 있습니다 (따라서 다른 클래스).

당신이 수영장에서 레이스에서 우승했다면, 이것은 진정한 싱글 톤 일 것 secretKey.intern().getClass().getClassLoader()입니다.

또는 JVM은 구체적인 String 객체 (하위 클래스는 없음) 만 풀에 추가되도록하여이 구멍을 차단할 수 있습니다.

등호가 같은 구현 된 경우 있음 SafeString! = String다음 SafeString.intern! = String.internSafeString풀에 추가되어야 할 것이다. 그러면 수영장이 <Class, String>대신 수영장이되며 수영장에 <String>들어가기 위해 필요한 것은 새로운 클래스 로더입니다.


2
물론 성능 이유는 잘못된 것입니다. String이 인터페이스 였다면 내 응용 프로그램에서 더 나은 성능을 구현하는 구현을 제공 할 수 있었을 것입니다.
Stephan Eggermont

28

String이 불변이거나 최종적인 가장 중요한 이유는 클래스 로딩 메커니즘에 의해 사용되므로 심오하고 근본적인 보안 측면을 가지고 있기 때문입니다.

문자열이 변경 가능하거나 최종적이지 않은 경우 "java.io.Writer"로드 요청이 "mil.vogoon.DiskErasingWriter"로드로 변경되었을 수 있습니다.

참조 : Java에서 String을 변경할 수없는 이유


15

String Java의 핵심 클래스이며, 많은 것들이 특정 방식으로 작동합니다 (예 : 불변).

클래스를 만들면 final이러한 가정을 어길 수있는 서브 클래스가 방지됩니다.

지금도 리플렉션을 사용하는 경우 문자열을 끊을 수 있습니다 (값 또는 해시 코드 변경). 보안 관리자를 사용하여 리플렉션을 중지 할 수 있습니다. 경우 String아니었다 final, 모든 사람이 그것을 할 수 있습니다.

선언되지 않은 다른 클래스를 final사용하면 다소 깨진 서브 클래스를 정의 할 수 List있지만 (예를 들어 잘못된 위치에 추가 할 수 있음 ) 적어도 JVM은 핵심 작업의 클래스에 의존하지 않습니다.


6
final클래스에서 불변성을 보장하지는 않습니다. 그것은 단지 클래스의 불변 (하나는 불변성 일 수 있음)이 서브 클래스에 의해 변경 될 수 없음을 보증합니다.
케빈 브록

1
@ 케빈 : 예. 클래스의 결승은 하위 클래스가 없음을 보장합니다. 불변성과 관련이 없습니다.
Thilo

4
최종 수업을한다고해서 그 자체를 실현할 수있는 것은 아닙니다. 그러나 불변 클래스를 final로 만들면 아무도 불변성을 깨는 서브 클래스를 만들 수 없습니다. 아마도 불변성에 대해 논하는 사람들은 정확히 그들이 의미하는 바가 불분명했지만, 그들의 진술은 맥락에서 이해 될 때 정확합니다.
Jay

때때로 나는이 답변을 읽었으며 그것이 답이라고 생각한 다음 'The Effective Java'에서 해시 코드와 동등한 것을 읽고 매우 좋은 대답임을 깨달았습니다. 누구나 설명이 필요합니다. 같은 책의 ieam 8 & iteam 9를 권장합니다.
Abhishek Singh

6

브루노가 말했듯이 그것은 불변성에 관한 것입니다. Strings뿐만 아니라 Double, Integer, Character 등과 같은 래퍼도 중요합니다. 여기에는 여러 가지 이유가 있습니다.

  • 스레드 안전
  • 보안
  • Java 자체에서 관리되는 힙 (다른 방식으로 가비지 수집되는 일반 힙과는 다름)
  • 메모리 관리

기본적으로 프로그래머는 문자열이 변경되지 않도록 할 수 있습니다. 또한 작동 방식을 알고 있으면 메모리 관리 기능을 향상시킬 수 있습니다. "hello"와 같이 두 개의 동일한 문자열을 하나씩 만들어보십시오. 디버깅하는 경우 동일한 ID를 가지고 있음을 알 수 있습니다. 즉, 동일한 SAME 개체임을 의미합니다. 이것은 Java가 당신을 그렇게 하자는 사실 때문입니다. 문자열이 변경 가능하면 가능하지 않습니다. 그들은 결코 변하지 않을 것이기 때문에 같은 것을 가질 수 있습니다. 따라서 1,000,000 개의 문자열 "hello"를 작성하기로 결정한 경우 실제로 수행하는 작업은 "hello"에 대한 1,000,000 개의 포인터를 작성하는 것입니다. 문자열이나 함수의 래퍼에 대한 모든 기능을 사용하면 다른 개체가 생성됩니다 (개체 ID를 보면 다시 변경됩니다).

Aditionally 자바 마지막이되지 않습니다 반드시 그 객체는 변경할 수 없습니다 의미 (그것은 ++ 예 : C 다릅니다). 즉, 주소가 가리키는 주소는 변경할 수 없지만 속성 및 / 또는 속성은 변경할 수 있습니다. 따라서 어떤 경우에는 불변성과 최종의 차이를 이해하는 것이 실제로 중요 할 수 있습니다.

HTH

참고 문헌 :


1
문자열이 다른 힙으로 이동하거나 다른 메모리 관리를 사용한다고 생각하지 않습니다. 그들은 확실히 가비지 수집 가능합니다.
Thilo

2
또한 클래스의 최종 키워드는 필드의 최종 키워드와 완전히 다릅니다.
Thilo

1
좋아, Sun의 JVM에서 intern () 된 문자열은 힙의 일부가 아닌 perm-gen에 들어갈 수 있습니다. 그러나 모든 문자열 또는 모든 JVM에서 반드시 그런 것은 아닙니다.
Thilo

2
모든 문자열이 해당 영역으로 이동하는 것이 아니라 인터 닝 된 문자열 만 해당합니다. 인터 닝은 리터럴 문자열에 대해 자동입니다. (@Thilo, 의견을 제출할 때 입력).
케빈 브록

이 회신에 매우 감사합니다. 지금 두 가지 사실이 있습니다. String은 Final 클래스이며 변경할 수 없지만 다른 객체를 참조 할 수 있기 때문에 변경할 수 없습니다. 그러나 어떨까요 :-String a = new String ( "test1"); 그런 다음 s = "test2"; String이 Final 클래스 객체이면 어떻게 수정할 수 있습니까? 수정 된 최종 객체를 어떻게 사용할 수 있습니까? 내가 잘못 물어 본다면 제게 알려주세요.
Suresh Sharma

2

구현을 단순화했을 수도 있습니다. 클래스 사용자가 상속 할 수있는 클래스를 디자인하는 경우 디자인을 고려할 완전히 새로운 사용 사례가 있습니다. X 보호 필드를 사용하여이 작업을 수행하면 어떻게됩니까? 최종적으로 공개 인터페이스를 올바르게 작동시키는 데 집중하고 제대로 작동하는지 확인할 수 있습니다.


3
"상속 설계가 어렵다"는 +1 BTW, 그것은 Bloch의 "Effective Java"에 아주 잘 설명되어 있습니다.
sleske

2

많은 좋은 점이 이미 언급되었으므로 Java에서 String을 변경할 수없는 이유 중 하나는 String이 해시 코드를 캐시 하도록 허용 하고 Java의 불변 문자열은 해시 코드를 캐시하고 모든 것을 계산하지는 않는 것입니다 우리는 String의 해시 코드 메소드를 호출 하여 Java의 해시 맵에서 사용되는 해시 맵 키처럼 매우 빠릅니다.

간단히 말해서 String은 변경할 수 없기 때문에 아무도 한 번 작성된 내용을 변경할 수 없으므로 여러 호출에서 String의 hashCode가 동일하게 보장됩니다.

당신이 볼 경우 String클래스로 선언했다

/** Cache the hash code for the string */
private int hash; // Default to 0

hashcode()다음과 같은 기능은 -

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

이미 컴퓨터 인 경우 값을 반환하십시오.


2

더 나은 구현을 얻지 못하도록합니다. 물론 인터페이스 였어야합니다.

아, 더 단서없는 투표를 받고있다. 대답은 완벽하게 심각합니다. 어리석은 문자열 구현을 여러 번 프로그래밍하여 심각한 성능 및 생산성 손실을 초래했습니다.


2

다른 답변에서 제안 된 명백한 이유 외에도 String 클래스를 final로 만드는 한 가지 생각은 가상 메서드 성능 오버 헤드와 관련이있을 수 있습니다. String은 무거운 클래스 이므로이 최종 결과는 확실하게 하위 구현이 없음을 의미하며 간접 호출을 간접적으로 호출하지 않습니다. 물론 이제는 가상 호출 및 기타와 같은 것들이 있으며 항상 이러한 종류의 최적화를 수행합니다.


2

다른 답변 (보안, 불변성, 성능)에 언급 된 이유 외에도 String특별한 언어 지원이 있음을 알아야합니다 . String리터럴 을 작성할 수 있으며 +연산자를 지원합니다 . 프로그래머가 서브 클래스를 허용하면 String다음과 같은 해킹이 권장됩니다.

class MyComplex extends String { ... }

MyComplex a = new MyComplex("5+3i");
MyComplex b = new MyComplex("7+4i");
MyComplex c = new MyComplex(a + b);   // would work since a and b are strings,
                                      // and a string + a string is a string.

1

글쎄, 나는 내가 틀린지 아닌지 확실하지 않지만 Java String에서는 기본 데이터 유형으로 취급 될 수있는 유일한 객체이며 String name = "java 와 같은 String 객체를 만들 수 있음을 의미한다. " . 이제 다른 것과 마찬가지로 참조 문자열로 복사 하지 않고 으로 복사되는 기본 데이터 유형 은 동일한 동작을 가지므로 String이 최종적인 이유입니다. 그게 내 생각이야 완전히 비논리적 인 경우 무시하십시오.


1
제 생각에는 문자열은 기본 유형처럼 동작하지 않습니다. "java"와 같은 문자열 리터럴은 실제로 String 클래스의 객체입니다 (마침표 바로 뒤에 점 연산자를 사용할 수 있음). 따라서 문자열 변수에 리터럴을 할당하는 것은 평소와 같이 객체 참조를 할당하는 것입니다. 차이점은 String 클래스에는 컴파일러에 언어 수준의 지원 기능이 내장되어 있습니다. 큰 따옴표로 묶은 것을 String 객체로 바꾸고 위에서 언급 한 + 연산자를 사용하는 것입니다.
Georgie

1

문자열의 최종성은 또한 표준으로 문자열을 방어합니다. C ++에서는 문자열의 서브 클래스를 작성할 수 있으므로 모든 프로그래밍 상점이 자체 버전의 문자열을 가질 수 있습니다. 이것은 강력한 표준의 부족으로 이어질 것입니다.


1

Employee메소드 가있는 클래스가 있다고 가정 해 봅시다 greet. greet메소드가 호출 되면 단순히 인쇄합니다 Hello everyone!. 이것이 방법 의 예상되는 동작 입니다greet

public class Employee {

    void greet() {
        System.out.println("Hello everyone!");
    }
}

이제 아래와 같이 GrumpyEmployee서브 클래스 Employeegreet메소드를 오버라이드 하자 .

public class GrumpyEmployee extends Employee {

    @Override
    void greet() {
        System.out.println("Get lost!");
    }
}

이제 아래 코드에서 sayHello메소드를 살펴보십시오 . 그것은 소요 Employee매개 변수로 인스턴스를하고 말 것이라고 기대하는 인사 메소드를 호출 Hello everyone!하지만 우리가 얻을 것은 Get lost!. 이 행동의 변화는Employee grumpyEmployee = new GrumpyEmployee();

public class TestFinal {
    static Employee grumpyEmployee = new GrumpyEmployee();

    public static void main(String[] args) {
        TestFinal testFinal = new TestFinal();
        testFinal.sayHello(grumpyEmployee);
    }

    private void sayHello(Employee employee) {
        employee.greet(); //Here you would expect a warm greeting, but what you get is "Get lost!"
    }
}

이 상황은 수업이 이루어진 경우 피할 수 있습니다 . 이제 클래스가로 선언되지 않은 경우 건방진 프로그래머가 발생할 수있는 혼란의 정도는 상상력에 달려 있습니다 .EmployeefinalStringfinal


0

JVM이 변경 불가능한 것을 알고 있습니까? 정답은 아니오입니다. 상수 풀에는 모든 불변 필드가 포함되지만 모든 불변 필드 / 개체는 상수 풀에만 저장되지 않습니다. 불변성과 그 기능을 달성하는 방식으로 만 구현합니다. 풀링을위한 Java 특수 동작을 제공하는 MarkerInterface를 사용하여 최종적으로 만들지 않고도 CustomString을 구현할 수 있습니다.이 기능은 계속 기다리고 있습니다!


0

대부분의 답변은 불변성과 관련이 있습니다. 왜 String 유형의 객체를 업데이트 할 수 없습니까? 여기에는 좋은 토론이 많이 있으며 Java 커뮤니티는 변경 불가능 성을 원칙으로 채택하는 것이 좋습니다. (내 숨을 참지 마라.)

그러나 OP의 질문은 왜 최종적인지에 대한 것입니다. 왜 확장 할 수 없는지에 대한 것입니다. 여기에 일부는이 작업을 수행했지만 여기에 실제 격차가 있다는 OP에 동의합니다. 다른 언어에서는 개발자가 유형에 대한 새로운 명목 유형을 만들 수 있습니다. 예를 들어 Haskell에서 런타임에 텍스트와 동일하지만 컴파일 타임에 바인딩 안전을 제공하는 다음과 같은 새로운 유형을 만들 수 있습니다.

newtype AccountCode = AccountCode Text
newtype FundCode = FundCode Text

따라서 Java 언어를 향상시키기 위해 다음 제안을 제시합니다.

newtype AccountCode of String;
newtype FundCode of String;

AccountCode acctCode = "099876";
FundCode fundCode = "099876";

acctCode.equals(fundCode);  // evaluates to false;
acctCode.toString().equals(fundCode.toString());  // evaluates to true;

acctCode=fundCode;  // compile error
getAccount(fundCode);  // compile error

(또는 아마도 우리는 자바에서 벗어나기 시작할 수 있습니다)


-1

문자열을 한 번만 만들면 수정하려는 경우 개체이므로 새 개체를 만들 수 없습니다.


답을 명확히하십시오.
Marko Popovic
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.