Строки Java: "String s = new String(" глупо ");"
Я парень C++, изучающий Java. Я читаю Эффективную Java и что-то меня смутило. Он говорит, что никогда не писать код, как это:
String s = new String("silly");
Потому что это создает ненужные String
объекты. Но вместо этого это должно быть написано так:
String s = "No longer silly";
Хорошо, пока хорошо... Однако, учитывая этот класс:
public final class CaseInsensitiveString {
private String s;
public CaseInsensitiveString(String s) {
if (s == null) {
throw new NullPointerException();
}
this.s = s;
}
:
:
}
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
Почему первое утверждение в порядке? Не должно ли это быть
CaseInsensitiveString cis = "Polish";
Как мне сделать
CaseInsensitiveString
вести себя какString
так что приведенное выше утверждение в порядке (с расширением и безString
)? Что такого в String, которое позволяет просто передавать такой литерал? Насколько я понимаю, в Java нет понятия "конструктор копирования"?
23 ответа
String
это специальный встроенный класс языка. Это для String
класс только в котором вы должны избегать говорить
String s = new String("Polish");
Потому что буквальный "Polish"
уже типа String
и вы создаете лишний ненужный объект. Для любого другого класса, говоря
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
это правильная (и единственная в данном случае) вещь, которую нужно сделать.
Я считаю, что основным преимуществом использования литеральной формы (то есть "foo", а не новой String("foo")) является то, что все литералы String "интернированы" ВМ. Другими словами, он добавляется в пул так, что любой другой код, который создает ту же строку, будет использовать объединенную строку вместо создания нового экземпляра.
Чтобы проиллюстрировать это, следующий код напечатает true для первой строки, но false для второй:
System.out.println("foo" == "foo");
System.out.println(new String("bar") == new String("bar"));
Строки обрабатываются немного специально в Java, они неизменны, поэтому для них безопасно обрабатывать подсчет ссылок.
Если ты пишешь
String s = "Polish";
String t = "Polish";
тогда s и t на самом деле ссылаются на один и тот же объект, а s==t вернет true, поскольку "==" для объектов, которые читаются, "тот же объект" (или, во всяком случае, я не уверен, является ли это частью реальная спецификация языка или просто детали реализации компилятора, так что, возможно, на это нельзя опасаться) .
Если ты пишешь
String s = new String("Polish");
String t = new String("Polish");
тогда s!=t (потому что вы явно создали новую строку), хотя s.equals(t) вернет true (потому что строка добавляет это поведение в equals).
То, что вы хотите написать,
CaseInsensitiveString cis = "Polish";
не может работать, потому что вы думаете, что цитаты - это своего рода конструктор короткого замыкания для вашего объекта, хотя на самом деле это работает только для простых старых java.lang.Strings.
String s1="foo";
литерал пойдет в пул, а s1 будет ссылаться.
String s2="foo";
на этот раз он проверит, что литерал "foo" уже доступен в StringPool или нет, так как теперь он существует, поэтому s2 будет ссылаться на тот же литерал.
String s3=new String("foo");
Сначала в StringPool будет создан литерал "foo", затем будет создан строковый аргумент arg. Будет создан объект String, т. е. в куче будет "foo" из-за создания объекта через оператор new, после чего s3 будет ссылаться на него.
String s4=new String("foo");
такой же как s3
так System.out.println(s1==s2);// **true** due to literal comparison.
а также System.out.println(s3==s4);// **false** due to object
сравнение (s3 и s4 создаются в разных местах в куче)
String
s являются особенными в Java - они неизменны, а строковые константы автоматически превращаются в String
объекты.
Там нет никакого способа для вашего SomeStringClass cis = "value"
Пример для применения к любому другому классу.
Вы не можете продлить String
потому что он объявлен как final
Это означает, что подклассификация не допускается.
Лучший способ ответить на ваш вопрос - познакомить вас с "пулом констант строки". В Java строковые объекты являются неизменяемыми (то есть их значения не могут быть изменены после инициализации), поэтому при редактировании строкового объекта вы в конечном итоге создаете новый отредактированный строковый объект, в котором старый объект просто плавает в специальной области памяти, называемой "строка". постоянный пул ". создание нового строкового объекта
String s = "Hello";
будет создавать только строковый объект в пуле, и ссылка s будет ссылаться на него, но с помощью
String s = new String("Hello");
вы создаете два строковых объекта: один в пуле, а другой в куче. ссылка будет ссылаться на объект в куче.
Строки Java интересны. Похоже, что ответы охватили некоторые интересные моменты. Вот мои два цента.
строки являются неизменяемыми (вы никогда не сможете их изменить)
String x = "x";
x = "Y";
- Первая строка создаст переменную x, которая будет содержать строковое значение "x". JVM будет искать в своем пуле строковых значений и видеть, существует ли "x", если он существует, он укажет на него переменную x, если она не существует, она создаст ее и затем выполнит присваивание
- Вторая строка удалит ссылку на "x" и увидит, существует ли "Y" в пуле строковых значений. Если он существует, он назначит его, если нет, то сначала создаст его, а затем назначит. Поскольку строковые значения используются или нет, пространство памяти в пуле строковых значений будет восстановлено.
сравнения строк зависят от того, что вы сравниваете
String a1 = new String("A");
String a2 = new String("A");
a1
не равноa2
a1
а такжеa2
ссылки на объекты- Когда строка явно объявлена, создаются новые экземпляры, и их ссылки не будут одинаковыми.
Я думаю, что вы на неверном пути, пытаясь использовать регистронезависимый класс. Оставь струны в покое. Что вас действительно волнует, так это то, как вы отображаете или сравниваете значения. Используйте другой класс для форматирования строки или сравнения.
т.е.
TextUtility.compare(string 1, string 2)
TextUtility.compareIgnoreCase(string 1, string 2)
TextUtility.camelHump(string 1)
Поскольку вы составляете класс, вы можете заставить сравнения делать то, что вы хотите - сравнивать текстовые значения.
Ты не можешь Вещи в двойных кавычках в Java специально распознаются компилятором как строки, и, к сожалению, вы не можете переопределить это (или расширить java.lang.String
- объявлено final
).
Я могу сделать CaseInsensitiveString вести себя как String, так что вышеприведенный оператор в порядке (с и без расширения String)? Что такого в String, которое позволяет просто передавать его в таком виде? Насколько я понимаю, в Java нет понятия "конструктор копирования", верно?
С первого момента было сказано достаточно. "Польский" является строковым литералом и не может быть назначен классу CaseInsentiviveString.
Теперь о втором пункте
Хотя вы не можете создавать новые литералы, вы можете следовать первому пункту этой книги для "похожего" подхода, поэтому следующие утверждения верны:
// Lets test the insensitiveness
CaseInsensitiveString cis5 = CaseInsensitiveString.valueOf("sOmEtHiNg");
CaseInsensitiveString cis6 = CaseInsensitiveString.valueOf("SoMeThInG");
assert cis5 == cis6;
assert cis5.equals(cis6);
Вот код
C:\oreyes\samples\java\insensitive>type CaseInsensitiveString.java
import java.util.Map;
import java.util.HashMap;
public final class CaseInsensitiveString {
private static final Map<String,CaseInsensitiveString> innerPool
= new HashMap<String,CaseInsensitiveString>();
private final String s;
// Effective Java Item 1: Consider providing static factory methods instead of constructors
public static CaseInsensitiveString valueOf( String s ) {
if ( s == null ) {
return null;
}
String value = s.toLowerCase();
if ( !CaseInsensitiveString.innerPool.containsKey( value ) ) {
CaseInsensitiveString.innerPool.put( value , new CaseInsensitiveString( value ) );
}
return CaseInsensitiveString.innerPool.get( value );
}
// Class constructor: This creates a new instance each time it is invoked.
public CaseInsensitiveString(String s){
if (s == null) {
throw new NullPointerException();
}
this.s = s.toLowerCase();
}
public boolean equals( Object other ) {
if ( other instanceof CaseInsensitiveString ) {
CaseInsensitiveString otherInstance = ( CaseInsensitiveString ) other;
return this.s.equals( otherInstance.s );
}
return false;
}
public int hashCode(){
return this.s.hashCode();
}
// Проверка класса с использованием ключевого слова assert
public static void main( String [] args ) {
// Creating two different objects as in new String("Polish") == new String("Polish") is false
CaseInsensitiveString cis1 = new CaseInsensitiveString("Polish");
CaseInsensitiveString cis2 = new CaseInsensitiveString("Polish");
// references cis1 and cis2 points to differents objects.
// so the following is true
assert cis1 != cis2; // Yes they're different
assert cis1.equals(cis2); // Yes they're equals thanks to the equals method
// Now let's try the valueOf idiom
CaseInsensitiveString cis3 = CaseInsensitiveString.valueOf("Polish");
CaseInsensitiveString cis4 = CaseInsensitiveString.valueOf("Polish");
// References cis3 and cis4 points to same object.
// so the following is true
assert cis3 == cis4; // Yes they point to the same object
assert cis3.equals(cis4); // and still equals.
// Lets test the insensitiveness
CaseInsensitiveString cis5 = CaseInsensitiveString.valueOf("sOmEtHiNg");
CaseInsensitiveString cis6 = CaseInsensitiveString.valueOf("SoMeThInG");
assert cis5 == cis6;
assert cis5.equals(cis6);
// Futhermore
CaseInsensitiveString cis7 = CaseInsensitiveString.valueOf("SomethinG");
CaseInsensitiveString cis8 = CaseInsensitiveString.valueOf("someThing");
assert cis8 == cis5 && cis7 == cis6;
assert cis7.equals(cis5) && cis6.equals(cis8);
}
}
C:\oreyes\samples\java\insensitive>javac CaseInsensitiveString.java
C:\oreyes\samples\java\insensitive>java -ea CaseInsensitiveString
C:\oreyes\samples\java\insensitive>
То есть создайте внутренний пул объектов CaseInsensitiveString и верните оттуда соответствующий экземпляр.
Таким образом, оператор "==" возвращает true для двух ссылок на объекты, представляющих одно и то же значение.
Это полезно, когда подобные объекты используются очень часто, а создание затрат обходится дорого.
В документации по строковому классу говорится, что класс использует внутренний пул
Класс не завершен, некоторые интересные проблемы возникают, когда мы пытаемся обойти содержимое объекта при реализации интерфейса CharSequence, но этот код достаточно хорош, чтобы показать, как этот элемент в Книге может быть применен.
Важно отметить, что при использовании объекта internalPool ссылки не освобождаются и, следовательно, не подлежат сборке мусора, и это может стать проблемой при создании большого количества объектов.
Он работает для класса String, потому что он интенсивно используется, и пул состоит только из "интернированного" объекта.
Это хорошо работает и для логического класса, потому что есть только два возможных значения.
И, наконец, это также причина, по которой значение valueOf(int) в классе Integer ограничено значениями от -128 до 127 int.
Просто потому, что у тебя есть слово String
в вашем классе не означает, что вы получаете все особенности встроенного String
учебный класс.
CaseInsensitiveString
это не String
хотя он содержит String
, String
например, "пример" может быть назначен только String
,
В первом примере вы создаете "глупую" строку, а затем передаете ее в качестве параметра в конструктор копирования другой строки, который создает вторую строку, идентичную первой. Поскольку Java Strings являются неизменяемыми (что часто вызывает у людей, привыкших к C-строкам), это бесполезная трата ресурсов. Вместо этого вы должны использовать второй пример, потому что он пропускает несколько ненужных шагов.
Однако литерал String не является CaseInsensitiveString, поэтому в последнем примере вы не можете делать то, что хотите. Кроме того, в C++ нет способа перегрузить оператор приведения, как в C++, поэтому буквально нет способа сделать то, что вы хотите. Вместо этого вы должны передать его в качестве параметра конструктору вашего класса. Конечно, я бы, вероятно, просто использовал String.toLowerCase() и покончил с этим.
Кроме того, ваша CaseInsensitiveString должна реализовывать интерфейс CharSequence, а также интерфейсы Serializable и Comparable. Конечно, если вы реализуете Comparable, вы должны также переопределить equals() и hashCode().
Когда они говорят, чтобы написать
String s = "Silly";
вместо
String s = new String("Silly");
они имеют это в виду при создании объекта String, поскольку оба приведенных выше оператора создают объект String, но новая версия String() создает два объекта String: один в куче, а другой в пуле строковых констант. Следовательно, используя больше памяти.
Но когда ты пишешь
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
вы не создаете строку, вместо этого вы создаете объект класса CaseInsensitiveString. Следовательно, вам нужно использовать новый оператор.
CaseInsensitiveString и String - это разные объекты. Вы не можете сделать:
CaseInsensitiveString cis = "Polish";
потому что "Polish" - это String, а не CaseInsensitiveString. Если бы String расширил CaseInsensitiveString String, то все было бы в порядке, но, очевидно, это не так.
И не беспокойтесь о строительстве здесь, вы не будете делать ненужные объекты. Если вы посмотрите на код конструктора, все, что он делает, - хранит ссылку на строку, которую вы передали. Ничего лишнего не создается.
В случае String s = new String("foobar") он делает что-то другое. Сначала вы создаете буквальную строку "foobar", а затем создаете ее копию, создавая из нее новую строку. Там нет необходимости создавать эту копию.
Если я правильно понял, ваш вопрос означает, почему мы не можем создать объект, непосредственно присвоив ему значение, давайте не будем ограничивать его классом Wrapper of String в java.
Чтобы ответить на это, я бы просто сказал, что чисто объектно-ориентированные языки программирования имеют некоторые конструкции, и в них говорится, что все литералы, если они написаны отдельно, могут быть непосредственно преобразованы в объект данного типа.
Это точно означает, что если интерпретатор видит 3, он будет преобразован в объект Integer, потому что integer - это тип, определенный для таких литералов.
Если интерпретатор видит какую-либо вещь в одинарных кавычках, например, 'a', он непосредственно создаст объект типа символ, вам не нужно указывать его, поскольку язык определяет для него объект по умолчанию типа символ.
Точно так же, если интерпретатор видит что-то в "", он будет рассматриваться как объект его типа по умолчанию, то есть строки. Это некоторый нативный код, работающий в фоновом режиме.
Благодаря видеокурсу MIT 6.00, где я получил подсказку для этого ответа.
String - это один из специальных классов, в котором вы можете создавать их без новой части Sring.
это так же, как
int x = y;
или же
символ с;
Во-первых, вы не можете создать класс, который выходит из String, потому что String - последний класс. И Java управляет строками в отличие от других классов, так что только с помощью String вы можете делать
String s = "Polish";
Но с вашим классом вы должны вызвать конструктор. Итак, этот код в порядке.
Я бы просто добавил, что в Java есть конструкторы Copy...
Ну, это обычный конструктор с объектом того же типа, что и аргумент.
String str1 = "foo";
String str2 = "foo";
И str1, и str2 принадлежат к одному и тому же объекту String, "foo", b'coz Java управляет строками в StringPool, поэтому, если новая переменная ссылается на ту же строку, она не создает другую, а скорее присваивает тот же алерадий, присутствующий в StringPool,
String str1 = new String("foo");
String str2 = new String("foo");
Здесь и str1, и str2 принадлежат разным объектам, b'coz new String() принудительно создает новый объект String.
В Java синтаксис "текст" создает экземпляр класса java.lang.String. Назначение:
String foo = "text";
это простое назначение, без конструктора копирования, необходимого.
MyString bar = "text";
Недопустимо, что бы вы ни делали, потому что класс MyString не является ни java.lang.String, ни суперклассом java.lang.String.
Это основной закон, что строки в Java неизменны и чувствительны к регистру.
Java создает объект String для каждого строкового литерала, который вы используете в своем коде. В любой момент ""
используется, это то же самое, что вызов new String()
,
Строки - это сложные данные, которые просто "действуют" как примитивные данные. Строковые литералы на самом деле являются объектами, хотя мы притворяемся, что они примитивные литералы, как 6, 6.0, 'c',
и т.д. Так что Строка "буквальная" "text"
возвращает новый объект String со значением char[] value = {'t','e','x','t}
, Поэтому звоню
new String("text");
на самом деле сродни призыву
new String(new String(new char[]{'t','e','x','t'}));
Надеюсь, отсюда вы можете понять, почему ваш учебник считает это излишним.
Для справки, вот реализация String: http://www.docjar.com/html/api/java/lang/String.java.html
Это весело читать и может вдохновить на некоторое понимание. Это также здорово для начинающих, чтобы читать и пытаться понять, так как код демонстрирует очень профессиональный и соответствующий стандартам код.
Другой хороший справочник - учебник по Java для строк: http://docs.oracle.com/javase/tutorial/java/data/strings.html
В большинстве версий JDK две версии будут одинаковыми:
String s = новая строка ("глупая");
Строка s = "Больше не глупо";
Поскольку строки являются неизменяемыми, компилятор поддерживает список строковых констант, и, если вы попытаетесь создать новую, она сначала проверит, определена ли строка. Если это так, то возвращается ссылка на существующую неизменяемую строку.
Чтобы уточнить - когда вы говорите "String s =", вы определяете новую переменную, которая занимает место в стеке, - тогда, говорите ли вы "больше не глупо" или новая строка ("глупо"), происходит точно то же самое - новая константная строка компилируется в ваше приложение, и ссылка указывает на это.
Я не вижу здесь различия. Однако для вашего собственного класса, который не является неизменным, это поведение не имеет значения, и вы должны вызвать ваш конструктор.
ОБНОВЛЕНИЕ: я был неправ! Основываясь на отрицательном голосовании и прилагаемых комментариях, я проверил это и понял, что мое понимание неверно - новая строка ("глупая") действительно создает новую строку, а не повторно использует существующую. Мне непонятно, почему это будет (в чем выгода?), Но код говорит громче, чем слова!