java.lang.String이 최종이 아닌 경우 실제로 어떻게됩니까?


16

저는 오랜 자바 개발자이며 마지막으로 전공 후 인증 시험을보기 위해 괜찮은 수준으로 공부할 시간이 있습니다 ... 항상 저를 괴롭힌 한 가지는 문자열이 "최종"입니다. 보안 문제와 관련 내용을 읽을 때 이해합니다 ... 그러나 진지하게 누구든지 그에 대한 진정한 예가 있습니까?

예를 들어 String이 최종이 아닌 경우 어떻게됩니까? 루비에없는 것처럼. Ruby 커뮤니티에서 불만을 듣지 못했습니다 ... 그리고 StringUtils 및 관련 클래스를 알고 있습니다. 직접 구현하거나 웹을 검색하여 해당 동작 (4 줄의 코드)을 구현해야합니다. 기꺼이.


9
Stack Overflow 에서이 질문에 관심이있을 수 있습니다 : stackoverflow.com/questions/2068804/why-is-string-final-in-java
Thomas Owens

마찬가지로 그렇지 java.io.File않은 경우 어떻게됩니까 final? 아뇨. 도청 전문가.
Tom Hawtin-tackline

1
우리가 알고있는 서구 문명의 끝.
Tulains Córdova

답변:


22

주된 이유는 속도입니다. final클래스를 확장 할 수 없으므로 문자열을 처리 할 때 JIT가 모든 종류의 최적화를 수행 할 수 있습니다. 재정의 된 메서드를 확인할 필요는 없습니다.

또 다른 이유는 스레드 안전성입니다. 불변은 스레드가 다른 사람에게 전달되기 전에 스레드를 완전히 빌드해야하기 때문에 항상 스레드 안전합니다. 빌드 후에는 더 이상 변경할 수 없습니다.

또한 Java 런타임 개발자는 항상 안전 측면에서 실수하기를 원했습니다. String을 확장 할 수 있으면 (Govy에서 자주하는 일이 매우 편리하기 때문에) 수행중인 작업을 모르는 경우 전체 웜 캔을 열 수 있습니다.


2
정답이 아닙니다. JVM 스펙에서 요구하는 검증 외에 HotSpot final은 코드를 컴파일 할 때 메소드가 대체되지 않는 빠른 점검으로 만 사용 합니다.
Tom Hawtin-tackline

1
@ TomHawtin-tackline : 참조?
Aaron Digulla

1
당신은 불변성과 최종적인 것이 어떻게 든 관련되어 있음을 암시하는 것처럼 보입니다. "또 다른 이유는 스레드 안전입니다. 불변은 항상 스레드 안전 b ..."입니다. 객체가 불변인지 아닌지는 이유가 최종적이지 않기 때문입니다.
Tulains Córdova

1
또한 클래스 의 키워드에 관계없이 String 클래스 변경할 수 없습니다 final. 포함 된 문자 배열 최종적이므로 변경할 수 없습니다. 변경 가능한 서브 클래스를 도입 할 수 없기 때문에 @abl에서 언급했듯이 클래스 자체를 final로 만들면 루프가 닫힙니다.

1
@Snowman 정확하게 말하면, 문자 배열은 final이므로 배열의 내용이 아니라 배열 참조를 변경할 수 없습니다.
Michał Kosmulski

10

Java String클래스가 최종 클래스가되어야하는 또 다른 이유 가 있습니다. 일부 시나리오에서는 보안에 중요합니다. 경우 String클래스가 최종되지 않은, 다음 코드는 악의적 인 발신자에 의해 미묘한 공격에 취약 할 것입니다 :

 public String makeSafeLink(String url, String text) {
     if (!url.startsWith("http:") && !url.startsWith("https:"))
         throw SecurityException("only http/https URLs are allowed");
     return "<a href=\"" + escape(url) + "\">" + escape(text) + "</a>";
 }

공격 : 악의적 인 호출자가 String의 서브 클래스를 생성 할 수 있습니다. EvilString여기서 EvilString.startsWith()항상 true를 반환하지만 값이 EvilString악한 경우 (예 :) javascript:alert('xss')입니다. 서브 클래 싱으로 인해 보안 검사가 회피됩니다. 이 공격은 TOCTTOU (time-of-check-to-to-use-time) 사용 취약점으로 알려져 있습니다. 점검이 완료된 시간 (URL이 http / https로 시작하는 시간)과 값이 사용되는 시간 사이 (html 스 니펫을 구성하기 위해) 유효 값이 변경 될 수 있습니다. String최종적이지 않은 경우 TOCTTOU 위험이 만연합니다.

따라서 String최종적이지 않으면 발신자를 신뢰할 수 없으면 보안 코드를 작성하는 것이 까다로워집니다. 물론, 그것은 Java 라이브러리가있는 위치입니다. 신뢰할 수없는 애플릿에 의해 호출 될 수 있으므로 호출자를 신뢰할 수 없습니다. 즉, String최종적인 것이 아니라면 안전한 라이브러리 코드를 작성하는 것은 부당하게 까다로울 것 입니다.


물론 리플렉션을 사용 char[]하여 문자열 내부 를 수정하여 TOCTTOU에서 제거 할 수는 있지만 타이밍에 약간의 운이 필요합니다.
피터 테일러

2
@ 피터 테일러, 그보다 더 복잡합니다. SecurityManager가 권한을 부여한 경우 리플렉션을 사용하여 전용 변수에 액세스 할 수 있습니다. 애플릿 및 기타 신뢰할 수없는 코드에는이 권한이 부여되지 않으므로 리플렉션을 사용 char[]하여 문자열 내부의 개인을 수정할 수 없습니다 . 따라서 TOCTTOU 취약점을 악용 할 수 없습니다.
DW

나는 알고 있지만 여전히 응용 프로그램과 서명 된 애플릿을 남겨두고 자체 서명 된 애플릿에 대한 경고 대화 상자로 실제로 얼마나 많은 사람들이 두려워합니까?
피터 테일러

2
@ 피터, 그게 요점이 아닙니다. 요점은 신뢰할 수있는 Java 라이브러리를 작성하는 것이 훨씬 어려울 것이며, String최종적이지 않은 경우 Java 라이브러리에보고 된 취약점이 훨씬 많을 것 입니다. 이 위협 모델이 관련성이있는 한 방어하는 것이 중요해집니다. 애플릿 및 기타 신뢰할 수없는 코드에 중요 String하다면 신뢰할 수있는 응용 프로그램에 필요하지 않더라도 최종 결정 을 정당화하기에 충분 합니다. (어쨌든 신뢰할 수있는 응용 프로그램은 최종 보안 여부에 관계없이 모든 보안 조치를 이미 무시할 수 있습니다.)
DW

"공격 : 악의적 인 호출자가 String, EvilString의 하위 클래스를 만들 수 있습니다. 여기서 EvilString.startsWith ()는 항상 true를 반환합니다."-startsWith ()가 최종이 아닌 경우.
Erik

4

그렇지 않다면, 멀티 스레드 자바 앱은 엉망이 될 것입니다 (실제보다 더 나쁩니다).

IMHO 최종 문자열 (불변)의 주요 장점은 본질적으로 스레드로부터 안전하다는 것입니다. 동기화가 필요하지 않습니다 (코드 작성이 때로는 사소하지만 그보다 훨씬 먼 경우). 변경 가능하다면 멀티 스레드 창 툴킷과 같은 것들이 매우 어려울 것이므로 그 것들이 반드시 필요합니다.


3
final클래스는 클래스의 가변성과 아무 관련이 없습니다.

이 답변은 질문을 다루지 않습니다. -1
FUZxxl

3
Jarrod Roberson : 클래스가 마지막이 아닌 경우 상속을 사용하여 가변 문자열을 만들 수 있습니다.
ysdx

@JarrodRoberson은 마침내 누군가 오류를 발견했습니다. 허용되는 답변은 또한 불변의 final equals를 의미합니다.
Tulains Córdova

1

String최종적인 또 다른 장점 은 불변성과 마찬가지로 예측 가능한 결과를 보장한다는 것입니다. String클래스이지만 값 유형과 같은 몇 가지 시나리오에서 처리되어야합니다 (컴파일러는을 통해 클래스를 지원합니다 String blah = "test"). 따라서 특정 값으로 문자열을 만들면 정수에 대한 기대치와 유사하게 문자열에 상속되는 특정 동작이있을 것으로 예상됩니다.

이것에 대해 생각해보십시오 : 또는 메소드 를 서브 클래스 화 java.lang.String하고 오버로드하면 어떻게됩니까? 갑자기 두 문자열 "test"가 더 이상 서로 같을 수 없습니다.equalshashCode

내가 할 수 있다면 혼란을 상상해보십시오.

public class Password extends String {
    public Password(String text) {
        super(text);
    }

    public boolean equals(Object o) {
        return true;
    }
}

다른 수업 :

public boolean isRightPassword(String suppliedString) {
    return suppliedString != null && suppliedString.equals("secret");
}

public void login(String username, String password) {
    return isRightUsername(username) && isRightPassword(password);
}

public void loginTest() {
    boolean success = login("testuser", new Password("wrongpassword"));
    // success is true, despite the password being wrong
}

1

배경

Java에서 final이 나타날 수있는 위치는 세 곳입니다.

클래스를 마지막으로 만들면 클래스의 모든 서브 클래스가 방지됩니다. 메소드를 final로 설정하면 메소드의 서브 클래스가 메소드를 대체하지 못합니다. 필드를 최종적으로 만들면 나중에 변경되지 않습니다.

오해

최종 방법 및 필드와 관련하여 최적화가 있습니다.

최종 방법을 사용하면 인라인을 통해 HotSpot을보다 쉽게 ​​최적화 할 수 있습니다. 그러나 HotSpot은 메서드가 다른 방법으로 입증 될 때까지 재정의되지 않았다는 가정에 따라 최종 방법이 아닌 경우에도이를 수행합니다. SO에 대한 자세한 내용

최종 변수는 적극적으로 최적화 될 수 있으며 이에 대한 자세한 내용은 JLS 섹션 17.5.3 에서 읽을 수 있습니다 .

그러나 이러한 이해 를 통해 이러한 최적화 중 어느 것도 수업을 마무리 하는 것에 관한 것이 아님 을 알아야합니다 . 수업을 최종적으로 완료하면 성능이 향상되지 않습니다.

클래스의 마지막 측면은 불변성과 관련이 없습니다. final이 아닌 변경 불가능한 클래스 (예 : BigInteger ) 또는 변경 가능 하고 최종적인 클래스 (예 : StringBuilder )를 가질 수 있습니다 . 수업 마지막 이어야 하는지에 대한 결정은 디자인의 문제입니다.

최종 디자인

문자열은 가장 많이 사용되는 데이터 형식 중 하나입니다. 그것들은 맵의 키로 발견되며, 사용자 이름과 암호를 저장하며, 키보드 나 웹 페이지의 필드에서 읽은 것입니다. 문자열은 어디에나 있습니다 .

지도

String을 서브 클래 싱 할 수있을 때 어떤 일이 발생하는지 고려해야 할 첫 번째 사항은 누군가가 String 인 것처럼 보이는 가변 가능한 String 클래스를 구성 할 수 있다는 것을 인식하는 것입니다. 이것은 모든 곳의지도를 망칠 것입니다.

이 가상 코드를 고려하십시오.

Map t = new TreeMap<String, Integer>();
Map h = new HashMap<String, Integer>();
MyString one = new MyString("one");
MyString two = new MyString("two");

t.put(one, 1); h.put(one, 1);
t.put(two, 2); h.put(two, 2);

one.prepend("z");

이것은 일반적으로 맵에서 변경 가능한 키를 사용하는 데 문제가 있지만 내가 거기에서 얻으려고하는 것은 갑자기 맵 나누기에 관한 많은 것들입니다. 엔트리가 더 이상 맵의 올바른 지점에 없습니다. HashMap에서 해시 값이 변경되었으므로 더 이상 올바른 항목이 아닙니다. TreeMap에서 노드 중 하나가 잘못된쪽에 있기 때문에 트리가 손상되었습니다.

이러한 키에 문자열을 사용하는 것이 일반적이므로이 동작은 문자열을 최종적으로 만들어 방지해야합니다.

당신은 독서에 관심이있을 수 있습니다 왜 문자열 불변의 자바에? String의 불변 속성에 대한 자세한 내용은.

사악한 줄

문자열에 대한 여러 가지 사악한 옵션이 있습니다. equal이 호출 될 때 항상 true를 반환하는 String을 만들고 암호 확인에 전달했는지 고려하십시오. 또는 MyString에 할당하면 문자열 복사본을 일부 전자 메일 주소로 보내도록 만들었습니까?

이것들은 String을 서브 클래스 화 할 수있는 능력이있을 때 실제로 가능한 가능성입니다.

Java.lang 문자열 최적화

앞서 언급했지만 final은 String을 더 빨리 만들지 않습니다. 그러나 String 클래스 (및의 다른 클래스 java.lang)는 필드 및 메소드의 패키지 레벨 보호를 자주 사용하여 다른 java.lang클래스가 String에 대한 공용 API를 항상 거치지 않고 내부와 땜질 할 수있게합니다. 범위 검사가없는 getChars 또는 StringBuffer에서 사용하는 lastIndexOf 또는 기본 배열을 공유하는 생성자 (메모리 문제로 인해 변경된 Java 6 항목) 가 없는 함수 입니다.

누군가가 String의 하위 클래스를 만든 경우 해당 최적화를 공유 할 수 없습니다 (일부 패키지java.lang 가 아닌 한 봉인 된 패키지입니다 ).

확장을 위해 무언가를 설계하기가 더 어렵습니다.

확장 가능한 것을 설계하는 것은 어렵다 . 그것은 당신이 다른 것을 수정할 수 있도록 내부의 일부를 노출시켜야 함을 의미합니다.

확장 가능한 문자열은 메모리 누수를 수정 하지 못했습니다 . 그 부분은 서브 클래스에 노출되어 있어야하며 해당 코드를 변경하면 서브 클래스가 중단됩니다.

Java는 이전 버전과의 호환성에 자부심을 갖고 핵심 클래스를 확장하여 개방함으로써 타사 서브 클래스와의 계산 성을 유지하면서 문제를 해결할 수있는 능력을 잃습니다.

Checkstyle 에는 "DesignForExtension"이라는 규칙이 적용되어 (내부 코드를 작성할 때 실제로 실망합니다) 모든 클래스가 다음 중 하나가되도록 강제합니다.

  • 요약
  • 결정적인
  • 빈 구현

합리적인 이유는 다음과 같습니다.

이 API 디자인 스타일은 서브 클래스에 의해 수퍼 클래스가 손상되지 않도록 보호합니다. 단점은 서브 클래스의 유연성이 제한적이며, 특히 수퍼 클래스에서 코드 실행을 막을 수는 없지만 서브 클래스가 수퍼 메소드 호출을 잊어서 수퍼 클래스의 상태를 손상시킬 수 없다는 것입니다.

구현 클래스를 확장 할 수 있다는 것은 서브 클래스가 기반이되는 클래스의 상태를 손상시키고 수퍼 클래스가 제공하는 다양한 보장이 유효하지 않게 만들 수 있음을 의미합니다. String과 같이 복잡한 무언가의 경우, 그것의 일부를 바꾸면 무언가 깨질 것이라는 것은 거의 확실합니다 .

개발자 hurbis

개발자의 일부입니다. 각 개발자가 자체 유틸리티 모음을 사용하여 자체 String 서브 클래스를 작성할 가능성을 고려하십시오 . 그러나 이제이 서브 클래스는 서로 자유롭게 할당 될 수 없습니다.

WleaoString foo = new WleaoString("foo");
MichaelTString bar = foo; // This doesn't work.

이 방법은 광기로 이어집니다. 사방에 String으로 캐스팅하고 문자열의 인스턴스 있는지 확인 하여 아니, 그냥 ... 그 하나를 기반으로 새로운 문자열을 만들고, String 클래스 아닌지. 하지마

나는 확실히 당신이 좋은 문자열 클래스를 쓸 수 있어요 ...하지만 C 쓰기 ++ 및 처리해야하는 미친 사람들에 여러 문자열 구현을 작성 남겨 std::stringchar*및 부스트에서 뭔가 SString 하고, 나머지는 모두 .

자바 문자열 마법

Java가 Strings와 함께하는 몇 가지 마술이 있습니다. 이를 통해 프로그래머는 언어를 다루기 쉽지만 언어의 일부 불일치를 소개합니다. String에서 서브 클래스를 허용하면 다음과 같은 마법적인 일을 처리하는 방법에 대해 매우 중요한 생각이 필요합니다.

  • 문자열 리터럴 (JLS 3.10.5 )

    할 수있는 코드가 있습니다.

    String foo = "foo";

    이것은 정수와 같은 숫자 유형의 권투와 혼동되어서는 안됩니다. 당신은 할 수 없어 1.toString(),하지만 당신은 할 수 있습니다 "foo".concat(bar).

  • +연산자 (JLS 15.18.1 )

    Java의 다른 참조 유형은 연산자를 사용할 수 없습니다. 끈은 특별하다. 그 때문에 문자열 연결 연산자는 컴파일러 수준에서 작동 "foo" + "bar"되고 "foobar"이 실행시보다는 컴파일 할 때.

  • 문자열 변환 (JLS 5.1.11 )

    문자열 컨텍스트에서 모든 개체를 사용하여 문자열로 변환 할 수 있습니다.

  • 문자열 인터 닝 ( JavaDoc )

    String 클래스는 String 풀에 액세스하여 컴파일 유형으로 채워진 객체를 표준 리터럴로 표시 할 수 있도록합니다.

String의 서브 클래스를 허용한다는 것은 다른 String 유형이 가능할 때 프로그래밍하기 쉬운 String을 가진 비트가 매우 어려워 지거나 불가능 해짐을 의미합니다.


0

String이 최종이 아닌 경우 모든 프로그래머 도구 상자에는 고유 한 "몇 가지 훌륭한 도우미 메서드가 포함 된 String"이 포함됩니다. 이들 각각은 다른 모든 공구 상자와 호환되지 않습니다.

나는 그것이 어리석은 제한이라고 생각했다. 오늘 저는 이것이 매우 건전한 결정이라고 확신합니다. 문자열을 문자열로 유지합니다.


finalJava final에서 C # 과 동일하지 않습니다 . 이와 관련하여 C #과 동일 readonly합니다. 참조 stackoverflow.com/questions/1327544/...
Arseni Mourzenko

9
@MainMa : 사실,이 맥락에서와 같이 C #의 봉인과 동일한 것 같습니다 public final class String.
구성자
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.