Java String 클래스의 내용에 대한 종합 분석(코드 포함)
이 기사의 내용은 Java String 클래스(코드 포함)에 대한 종합적인 분석입니다. 특정 참조 가치가 있으므로 도움이 필요한 친구에게 도움이 되기를 바랍니다.
Java를 쓰기 시작한 지난 1년 동안 저는 직면한 문제를 해결하려고 노력해왔지만, Java 언어의 기능을 깊이 있게 배우거나 JDK의 소스 코드를 깊이 읽는 데 앞장서지 않았습니다. 이제 미래를 Java
에 의존하기로 결정했지만 여전히 생각을 좀 하고 게임을 하는 시간을 포기하고 시스템을 깊이 연구해야 합니다.
Java String은 Java 프로그래밍에서 가장 일반적으로 사용되는 클래스 중 하나이며 JDK에서 제공하는 가장 기본적인 클래스입니다. 그래서 저는 좋은 시작을 위해 String 수업부터 시작해서 깊이 있게 공부하기로 결정했습니다.
클래스 정의 및 클래스 멤버
JDK에서 String 소스 코드를 열면 먼저 String 클래스의 정의에 주목해야 합니다.
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
상속 불가능 및 불변
Java를 작성한 모든 사람은 최종 키워드가 클래스를 수정하면 이 클래스가 상속 불가능하다는 것을 의미한다는 것을 알고 있습니다. 따라서 String 클래스는 외부에서 상속될 수 없습니다. 이때 String의 설계자는 왜 이를 상속 불가능하게 설계했는지 궁금할 것입니다. Zhihu에서 관련 질문과 토론을 찾았는데,
첫 번째 답변을 보면 매우 명확해진 것 같습니다. Java의 가장 기본적인 참조 데이터 유형인 String의 가장 중요한 점은 불변성이므로 final을 사용하는 것은 **상속을 금지하고 String의 불변성을 파괴**하는 것입니다.
클래스의 불변성을 달성하려면 final로 클래스를 장식하는 것만큼 간단하지 않습니다. 소스 코드에서 String은 실제로 문자 배열을 캡슐화한 것이며 문자 배열은 비공개임을 알 수 있습니다. 배열의 메소드에는 수정할 수 있는 문자가 제공되지 않으므로 초기화가 완료되면 String 객체를 수정할 수 없습니다.
Serialization
Java 객체 직렬화란 무엇인가요? 저와 같은 많은 Java 초보자가 이 질문을 가지고 있다고 생각합니다. Java 직렬화 및 역직렬화에 대한 심층 분석에 대한 이 문서의 이 단락은 이에 대해 매우 잘 설명합니다.
Java 플랫폼을 사용하면 메모리에 재사용 가능한 Java 객체를 생성할 수 있지만 일반적으로이러한 객체는 JVM이 실행 중일 때만 존재할 수 있습니다.
즉, 이러한 객체의 수명 주기는 JVM의 수명 주기보다 길지 않습니다. JVM이 더 길어졌습니다. 그러나 실제 애플리케이션에서는 JVM 실행이 중지된 후 지정된 객체를 저장(지속)하고 나중에 저장된 객체를 다시 읽어야 할 수도 있습니다.
Java 객체 직렬화는 이 기능을 달성하는 데 도움이 될 수 있습니다.
객체 직렬화는 객체의 "상태", 즉 멤버 변수를 저장한다는 점에 유의해야 합니다. 객체 직렬화는 클래스의 정적 변수에 주의를 기울이지 않는다는 것을 알 수 있습니다.
객체를 유지할 때 사용되는 객체 직렬화 외에도 RMI(원격 메서드 호출)를 사용하거나 네트워크를 통해 객체를 전송할 때도 객체 직렬화가 사용됩니다.
Java 직렬화 API는 객체 직렬화를 처리하기 위한 표준 메커니즘을 제공합니다. API는 간단하고 사용하기 쉽습니다.
String 소스 코드에서 직렬화를 지원하는 클래스 멤버 정의도 볼 수 있습니다.
/** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; /** * Class String is special cased within the Serialization Stream Protocol. * * A String instance is written into an ObjectOutputStream according to * <a href="{@docRoot}/../platform/serialization/spec/output.html"> * Object Serialization Specification, Section 6.2, "Stream Elements"</a> */ private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
serialVersionUID는 직렬화 버전 번호입니다. Java는 이 UID를 사용하여 역직렬화 및 로컬 클래스의 일관성을 확인합니다.
역직렬화될 수 있습니다. 예외가 발생했습니다.
serialPertantFields 이 정의는 이전 정의보다 훨씬 드물지만 직렬화 중 클래스 멤버와 관련이 있을 수 있습니다. 이 필드의 의미를 이해하기 위해 Baidu를 검색한 결과 JDK 문서에서 ObjectStreamField 클래스에 대한 약간의 설명인 '직렬화 가능 클래스의 직렬화 가능 필드에 대한 설명'만 찾았습니다.
ObjectStreamFields 배열은 클래스의 직렬화 가능 필드.' 일반적인 개념은 이 클래스가 직렬화된 클래스의 직렬화된 필드를 설명하는 데 사용된다는 것입니다. 이 유형의 배열을 정의하면 클래스가 직렬화되어야 하는 필드를 선언할 수 있습니다. 하지만 아직 이 클래스의 구체적인 사용법과 기능을 찾지 못했습니다. 나중에 이 필드의 정의를 자세히 살펴보았는데 serialVersionUID도 특정 필드 이름을 통해 다양한 규칙을 정의해야 합니다. 그러다가 serialPertantFields라는 키워드를 직접 검색하여 마침내 해당 역할을 찾았습니다.
즉, **기본 직렬화 사용자 정의에는 임시 키워드가 포함되며 정적 필드 이름 serialPertantFields는 기본적으로 직렬화되지 않는 필드를 지정하는 데 사용되고
저도 직접 테스트해봤는데 이런 효과가 있더라구요.
知道了 serialPersistentFields 的作用以后,问题又来了,既然这个静态字段是用来定义参与序列化的类成员的,那为什么在 String 中这个数组的长度定义为0?
经过一番搜索查找资料以后,还是没有找到一个明确的解释,期待如果有大佬看到能解答一下。
可排序
String 类还实现了 Comparable 接口,Comparable
即可用 Collections.sort 或 Arrays.sort 等方法对该类的对象列表或数组进行排序。
在 String 中我们还可以看到这样一个静态变量,
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); private static class CaseInsensitiveComparator implements Comparator<String>, java.io.Serializable { // use serialVersionUID from JDK 1.2.2 for interoperability private static final long serialVersionUID = 8575799808933029326L; public int compare(String s1, String s2) { int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { // No overflow because of numeric promotion return c1 - c2; } } } } return n1 - n2; } /** Replaces the de-serialized object. */ private Object readResolve() { return CASE_INSENSITIVE_ORDER; } }
从上面的源码中可以看出,这个静态成员是一个实现了 Comparator 接口的类的实例,而实现这个类的作用是比较两个忽略大小写的 String 的大小。
那么 Comparable 和 Comparator 有什么区别和联系呢?同时 String 又为什么要两个都实现一遍呢?
第一个问题这里就不展开了,总结一下就是,Comparable 是类的内部实现,一个类能且只能实现一次,而 Comparator 则是外部实现,可以通过不改变
类本身的情况下,为类增加更多的排序功能。
所以我们也可以为 String 实现一个 Comparator使用。
String 实现了两种比较方法的意图,实际上是一目了然的。实现 Comparable 接口为类提供了标准的排序方案,同时为了满足大多数排序需求的忽略大小写排序的情况,
String 再提供一个 Comparator 到公共静态类成员中。如果还有其他的需求,那就只能我们自己实现了。
类方法
String 的方法大致可以分为以下几类。
构造方法
功能方法
工厂方法
intern方法
关于 String 的方法的解析,这篇文章已经解析的够好了,所以我这里也不再重复的说一遍了。不过
最后的 intern 方法值得我们去研究。
intern方法
字符串常量池
String 做为 Java 的基础类型之一,可以使用字面量的形式去创建对象,例如 String s = "hello"。当然也可以使用 new 去创建 String 的对象,
但是几乎很少看到这样的写法,久而久之我便习惯了第一种写法,但是却不知道背后大有学问。下面一段代码可以看出他们的区别。
public class StringConstPool { public static void main(String[] args) { String s1 = "hello world"; String s2 = new String("hello world"); String s3 = "hello world"; String s4 = new String("hello world"); String s5 = "hello " + "world"; String s6 = "hel" + "lo world"; String s7 = "hello"; String s8 = s7 + " world"; System.out.println("s1 == s2: " + String.valueOf(s1 == s2) ); System.out.println("s1.equals(s2): " + String.valueOf(s1.equals(s2))); System.out.println("s1 == s3: " + String.valueOf(s1 == s3)); System.out.println("s1.equals(s3): " + String.valueOf(s1.equals(s3))); System.out.println("s2 == s4: " + String.valueOf(s2 == s4)); System.out.println("s2.equals(s4): " + String.valueOf(s2.equals(s4))); System.out.println("s5 == s6: " + String.valueOf(s5 == s6)); System.out.println("s1 == s8: " + String.valueOf(s1 == s8)); } } /* output s1 == s2: false s1.equals(s2): true s1 == s3: true s1.equals(s3): true s2 == s4: false s2.equls(s4): true s5 == s6: true s1 == s8: false */
从这段代码的输出可以看到,equals 比较的结果都是 true,这是因为 String 的 equals 比较的值( Object 对象的默认 equals 实现是比较引用,
String 对此方法进行了重写)。== 比较的是两个对象的引用,如果引用相同则返回 true,否则返回 false。s1==s2: false和 s2==s4: false
说明了 new 一个对象一定会生成一个新的引用返回。s1==s3: true 则证明了使用字面量创建对象同样的字面量会得到同样的引用。
s5 == s6 实际上和 s1 == s3 在 JVM 眼里是一样的情况,因为早在编译阶段,这种常量的简单运算就已经完成了。我们可以使用 javap 反编译一下 class 文件去查看
编译后的情况。
➜ ~ javap -c StringConstPool.class Compiled from "StringConstPool.java" public class io.github.jshanet.thinkinginjava.constpool.StringConstPool { public io.github.jshanet.thinkinginjava.constpool.StringConstPool(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #2 // String hello world 2: astore_1 3: return }
看不懂汇编也没关系,因为注释已经很清楚了......
s1 == s8 的情况就略复杂,s8 是通过变量的运算而得,所以无法在编译时直接算出其值。而 Java 又不能重载运算符,所以我们在 JDK 的源码里也
找不到相关的线索。万事不绝反编译,我们再通过反编译看看实际上编译器对此是否有影响。
public class io.github.jshanet.thinkinginjava.constpool.StringConstPool { public io.github.jshanet.thinkinginjava.constpool.StringConstPool(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #2 // String hello 2: astore_1 3: new #3 // class java/lang/StringBuilder 6: dup 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 10: aload_1 11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 14: ldc #6 // String world 16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 22: astore_2 23: return }
通过反编译的结果可以发现,String 的变量运算实际上在编译后是由 StringBuilder 实现的,s8 = s7 + " world" 的代码等价于
(new StringBuilder(s7)).append(" world").toString()。Stringbuilder 是可变的类,通过 append 方法 和 toString 将两个 String 对象聚合
成一个新的 String 对象,所以到这里就不难理解为什么 s1 == s8 : false 了。
之所以会有以上的效果,是因为有字符串常量池的存在。字符串对象的分配和其他对象一样是要付出时间和空间代价,而字符串又是程序中最常用的对象,JVM
为了提高性能和减少内存占用,引入了字符串的常量池,在使用字面量创建对象时, JVM 首先会去检查常量池,如果池中有现成的对象就直接返回它的引用,如果
没有就创建一个对象,并放到池里。因为字符串不可变的特性,所以 JVM 不用担心多个变量引用同一个对象会改变对象的状态。同时运行时实例创建的全局
字符串常量池中有一个表,总是为池中的每个字符串对象维护一个引用,所以这些对象不会被 GC 。
intern 方法的作用
上面说了很多都没有涉及到主题 intern
方法,那么 intern
方法到作用到底是什么呢?首先查看一下源码。
/** * Returns a canonical representation for the string object. * <p> * A pool of strings, initially empty, is maintained privately by the * class {@code String}. * <p> * When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned. * <p> * It follows that for any two strings {@code s} and {@code t}, * {@code s.intern() == t.intern()} is {@code true} * if and only if {@code s.equals(t)} is {@code true}. * <p> * All literal strings and string-valued constant expressions are * interned. String literals are defined in section 3.10.5 of the * <cite>The Java™ Language Specification</cite>. * * @return a string that has the same contents as this string, but is * guaranteed to be from a pool of unique strings. */ public native String intern();
Oracle JDK 中,intern
方法被 native
关键字修饰并且没有实现,这意味着这部分到实现是隐藏起来了。从注释中看到,这个方法的作用是如果常量池
中存在当前字符串,就会直接返回当前字符串,如果常量池中没有此字符串,会将此字符串放入常量池中后再返回。通过注释的介绍已经可以明白这个方法的作用了,
再用几个例子证明一下。
public class StringConstPool { public static void main(String[] args) { String s1 = "hello"; String s2 = new String("hello"); String s3 = s2.intern(); System.out.println("s1 == s2: " + String.valueOf(s1 == s2)); System.out.println("s1 == s3: " + String.valueOf(s1 == s3)); } } /* output s1 == s2: false s1 == s3: true */
这里就很容易的了解 intern
实际上就是把普通的字符串对象也关联到常量池中。
위 내용은 Java String 클래스의 내용에 대한 종합 분석(코드 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

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

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

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

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

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

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

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

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

뜨거운 주제











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

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

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

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

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

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

캡슐은 3 차원 기하학적 그림이며, 양쪽 끝에 실린더와 반구로 구성됩니다. 캡슐의 부피는 실린더의 부피와 양쪽 끝에 반구의 부피를 첨가하여 계산할 수 있습니다. 이 튜토리얼은 다른 방법을 사용하여 Java에서 주어진 캡슐의 부피를 계산하는 방법에 대해 논의합니다. 캡슐 볼륨 공식 캡슐 볼륨에 대한 공식은 다음과 같습니다. 캡슐 부피 = 원통형 볼륨 2 반구 볼륨 안에, R : 반구의 반경. H : 실린더의 높이 (반구 제외). 예 1 입력하다 반경 = 5 단위 높이 = 10 단위 산출 볼륨 = 1570.8 입방 단위 설명하다 공식을 사용하여 볼륨 계산 : 부피 = π × r2 × h (4
