С тех пор, как я начал писать Java в течение года, я всегда сталкивался с проблемами, а затем решал их, я не занимался активным изучением особенностей языка Java и углубленным чтением исходного кода JDK. Так как я решил полагаться на Java в будущем Чтобы поесть, вам все равно придется потратить на это какое-то время, уделить немного времени играм и систематическим занятиям.
Java String — один из наиболее часто используемых классов в программировании на Java и самый простой класс, предоставляемый JDK. Поэтому я решил начать с класса String и копнуть немного глубже, чтобы получить хорошее начало.
Определение класса и члены класса
При открытии исходного кода String в JDK следует в первую очередь обратить внимание на определение класса String.
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
ненаследуемый и неизменяемый
Любой, кто писал на Java, знает, что когдаfinal
Когда ключевое слово украшает класс, это означает, что этот класс не может быть унаследован. Таким образом, класс String не может быть унаследован извне. В это время нам может быть любопытно, дизайнер String
Зачем проектировать его так, чтобы он не наследовался. Я нашел это на ZhihuВопросы и обсуждения,
Я думаю, что первый ответ довольно ясен. Строка как самый основной ссылочный тип данных в Java, наиболее важным моментом является неизменность, поэтому используйтеfinal
Просто дляНаследование запрещено
ломает неизменную природу String.
Реализуйте неизменность класса, а не только с помощьюfinal
Класс модификации настолько прост.Как вы можете видеть из исходного кода, String на самом деле является инкапсуляцией массива символов, а массив символов является закрытым и не предоставляется.
Любой метод, который изменяет массив символов, поэтому после инициализации объект String не может быть изменен.
Сериализация
Из приведенного выше определения класса мы видим, что String реализует интерфейс сериализации.Serializable
, поэтому String поддерживает сериализацию и десериализацию.
Что такое сериализация объектов Java? Я полагаю, что у многих новичков в Java, таких как я, есть этот вопрос.Углубленный анализ сериализации и десериализации JavaЭтот отрывок из статьи
Хорошо объяснил.
Платформа Java позволяет нам создавать повторно используемые объекты Java в памяти, но в целом Эти объекты могут существовать только во время работы JVM. То есть время жизни этих объектов не будет больше времени жизни JVM. Но в реальных приложениях Может потребоваться возможность сохранения (сохранения) указанного объекта после остановки JVM и повторного чтения сохраненного объекта в будущем. Сериализация объектов Java может помочь нам реализовать эту функцию. При сериализации объектов Java при сохранении объекта его состояние сохраняется в виде набора байтов, и в дальнейшем эти байты собираются в объект. Следует отметить, что сериализация объекта сохраняет «состояние» объекта, то есть его переменные-члены. Отсюда видно, что сериализация объектов не обращает внимания на статические переменные в классе. В дополнение к сериализации объектов при сохранении объектов сериализация объектов также используется при использовании RMI (удаленный вызов метода) или при передаче объектов по сети. Java Serialization 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 для определения согласованности потока байтов во время десериализации и локального класса. Если они совпадают, они считаются согласованными.
Может быть десериализован и выдает исключение, если отличается.
serialPersistentFields
Это определение гораздо реже предыдущего, и, вероятно, предполагается, что оно связано с членами класса при сериализации. Для того, чтобы понять смысл этого поля, я гуглю и Baidu, а также
Только что нашел документацию JDK для классаObjectStreamField
небольшое описание,A description of a Serializable field from a Serializable class. An array of ObjectStreamFields is used to declare the Serializable fields of a class.
Общая идея заключается в том, что этот класс используется для описания сериализованного поля сериализованного класса,
Если вы определяете массив этого класса, вы можете объявить поля класса, которые необходимо сериализовать. Но я до сих пор не нашел конкретного использования и функции этого класса. Позже я более подробно рассмотрел определение этого поля,
а такжеserialVersionUID
Он также должен определять различные правила через определенные имена полей, а затем я напрямую искал ключевые слова.serialPersistentFields
, и, наконец, нашел свою конкретную роль.
который,Настройка сериализации по умолчанию включает ключевые словаtransient
и статические имена полейserialPersistentFields
,transient
Используется для указания того, какое поле не должно сериализоваться по умолчанию,serialPersistentFields
Используется для указания того, какие поля должны быть сериализованы по умолчанию. Если также определеноserialPersistentFields
а такжеtransient
,transient
будет игнорироваться.Я проверил это на себе, и это действительно работает.
понялserialPersistentFields
После роли снова возникает проблема.Поскольку это статическое поле используется для определения членов класса, участвующих в сериализации, почему длина этого массива определена как 0 в String?
После недолгих поисков и нахождения информации, я так и не нашел внятного объяснения, надеюсь, что если кто увидит, то сможет ответить.
Сортируемый
Класс String также реализуетComparable
интерфейс,Comparable<T>
Интерфейс имеет только один методpublic int compareTo(T o)
, реализация этого интерфейса означает, что класс поддерживает сортировку,
готовые к использованию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 для StringComparator
использовать, см.Разница между Comparable и ComparatorЭта статья.
Строка реализует цель двух методов сравнения, фактически с первого взгляда. выполнитьComparable
Интерфейс предоставляет стандартную схему сортировки для классов, и чтобы удовлетворить большинство требований сортировки для сортировки без учета регистра,
Строка предоставляет другойComparator
в общедоступный статический член класса. Если есть другие потребности, то это можем реализовать только мы сами.
метод класса
Строковые методы можно условно разделить на следующие категории.
- Метод строительства
- Функциональный подход
- заводской метод
- стажер метод
Что касается разбора методов 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
Все результаты сравнения верны, потому что Stringequals
значение сравнения (по умолчанию для объектов Objectequals
Реализация заключается в сравнении ссылок,
Строка переопределяет этот метод).==
Сравнивает ссылки двух объектов и возвращает результат, если ссылки совпадают.true
, иначе возвратfalse
.s1==s2: false
а такжеs2==s4: false
объяснилnew
Объект должен создать новую ссылку для возврата.s1==s3: true
Это доказывает, что объекты, созданные с использованием литералов с одними и теми же литералами, получат одинаковые ссылки.
s5 == s6
на самом деле иs1 == s3
То же самое и с JVM, потому что простые операции над такими константами выполняются уже на этапе компиляции. мы можем использоватьjavap
Декомпилируйте файл класса, чтобы увидеть
После компиляции.
➜ ~ 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 не нужно беспокоиться о нескольких переменных, ссылающихся на один и тот же объект и изменяющих состояние объекта. Глобальные объекты, созданные экземпляром при одновременном запуске В пуле строковых констант есть таблица, которая всегда содержит ссылку на каждый строковый объект в пуле, поэтому эти объекты не будут подвергаться сборке мусора.
Что делает метод стажера
Многое из того, что я сказал выше, не касается темы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
На самом деле обычные строковые объекты также связаны с пулом констант.
Конечноintern
Принципы реализации и лучшие практики также необходимо понять и изучить.Эта статья технической команды MeituanУглубленный анализ String#internОчень подробно и подробно, рекомендую к прочтению.