Я парень 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 нет концепции "конструктора копирования"?




String являются особенными в Java - они неизменяемы, а строковые константы автоматически превращаются в объекты String.
Ваш пример SomeStringClass cis = "value" не может быть применен к какому-либо другому классу.
Вы также не можете расширить String, потому что он объявлен как final, что означает, что подклассы не разрешены.
Вы не можете. Объекты в двойных кавычках в Java специально распознаются компилятором как строки, и, к сожалению, вы не можете переопределить это (или расширить java.lang.String - он объявлен как final).
final здесь отвлекающий маневр. Даже если String не был окончательным, расширение String ему в этом случае не помогло бы.
Я думаю, вы имеете в виду, что к счастью, вы не можете это изменить. :)
@Motlin: Ха! вы вполне можете быть правы. Думаю, я где-то читал, что Java была разработана для того, чтобы никто не делал глупостей, намеренно исключая что-нибудь подобное ...
String - это специальный встроенный класс языка. Это для класса StringТолько, в котором вы не должны говорить
String s = new String("Polish");
Поскольку буквальный "Polish" уже имеет тип String, и вы создаете лишний ненужный объект. Для любого другого класса, говоря
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
- это правильный (и единственный в данном случае) поступок.
второй момент заключается в том, что компилятор может придумать внутреннюю / распространяющую информацию со строковыми литералами, которые не обязательно возможны с такой странной функцией, как "String (literal)"
Поскольку вы никогда не должны вызывать new String("foo"), вы можете спросить себя, почему существует конструктор new String(String). Ответ в том, что иногда ему есть хорошее применение: stackoverflow.com/a/390854/1442870
К вашему сведению, в приведенном выше комментарии Теты слово «интернирование» написано с ошибкой, как в интернирование струн.
CaseInsensitiveString и String - разные объекты. Вы не можете:
CaseInsensitiveString cis = "Polish";
потому что «Polish» - это строка, а не CaseInsensitiveString. Если String расширяет CaseInsensitiveString String, тогда все будет в порядке, но, очевидно, это не так.
И не беспокойтесь о конструкции здесь, вы не будете делать лишних предметов. Если вы посмотрите на код конструктора, все, что он делает, это сохраняет ссылку на переданную вами строку. Ничего лишнего не создается.
В случае String s = new String ("foobar") он делает что-то другое. Сначала вы создаете буквальную строку «foobar», а затем создаете ее копию, создавая из нее новую строку. Нет необходимости создавать эту копию.
Даже если вы расширите String, это не сработает. Вам понадобится String для расширения CaseInsensitiveString.
в любом случае это невозможно, либо потому, что строка встроена, либо потому, что ее объявлено final
В Java синтаксис «текст» создает экземпляр класса java.lang.String. Назначение:
String foo = "text";
- простое присвоение, не требующее конструктора копирования.
MyString bar = "text";
Все, что вы делаете, является незаконным, потому что класс MyString не является ни java.lang.String, ни суперклассом java.lang.String.
Во-первых, вы не можете создать класс, который расширяется от String, потому что String является последним классом. И Java управляет строками иначе, чем другие классы, поэтому только с String вы можете делать
String s = "Polish";
Но с вашим классом вы должны вызывать конструктор. Итак, этот код в порядке.
Я считаю, что основное преимущество использования буквальной формы (т.е. «foo», а не new String («foo»)) заключается в том, что все строковые литералы «интернированы» виртуальной машиной. Другими словами, он добавляется в пул, так что любой другой код, создающий ту же строку, будет использовать объединенную строку, а не создавать новый экземпляр.
Чтобы проиллюстрировать это, следующий код будет печатать истину для первой строки, но ложь для второй:
System.out.println("foo" == "foo");
System.out.println(new String("bar") == new String("bar"));
Точно так же FindBugs предлагает вам заменить «new Integer (N)» на «Integer.valueOf (N)» - из-за этого интернирования.
Вы также должны добавить "foo" == new String ("foo"). Intern ()
Исправление: строковые литералы указываются компилятором, а не виртуальной машиной на одну и ту же ссылку. VM может вставлять объекты String во время выполнения, поэтому вторая строка может возвращать true или false!
@Motlin: Я не уверен, что это правильно. Документация javadoc для класса String требует, чтобы «все литеральные строки и константные выражения со строковыми значениями были интернированы». Таким образом, мы можем полагаться на интернирование литералов, что означает, что "foo" == "foo" всегда должно возвращать true.
@Leigh Да, литералы интернируются, но компилятором, а не виртуальной машиной. Motlin пытается понять, что виртуальная машина может дополнительно вставлять строки, таким образом, независимо от того, является ли new String ("bar") == new String ("bar") -> false зависимым от реализации.
@Aaron: java.sun.com/docs/books/jls/third_edition/html/… говорит: Значение выражения создания экземпляра класса - это ссылка на вновь созданный объект указанного класса. Каждый раз, когда выражение оценивается, создается новый объект. Так что это всегда будет false.
Да new String ("") создает новый объект String.
В первом примере вы создаете "глупую" строку, а затем передаете ее в качестве параметра конструктору копирования другой строки, который создает вторую строку, идентичную первой. Поскольку строки Java неизменяемы (что часто утомляет людей, привыкших к строкам C), это ненужная трата ресурсов. Вместо этого вам следует использовать второй пример, потому что он пропускает несколько ненужных шагов.
Однако литерал String не является CaseInsensitiveString, поэтому вы не можете делать то, что хотите в последнем примере. Кроме того, нет способа перегрузить оператор приведения, как в C++, поэтому буквально нет способа делать то, что вы хотите. Вместо этого вы должны передать его в качестве параметра конструктору вашего класса. Конечно, я бы, наверное, просто использовал String.toLowerCase () и покончил с этим.
Кроме того, ваш CaseInsensitiveString должен реализовывать интерфейс CharSequence, а также, возможно, интерфейсы Serializable и Comparable. Конечно, если вы реализуете Comparable, вы также должны переопределить equals () и hashCode ().
Строки в java обрабатываются немного особым образом, они неизменяемы, поэтому их можно безопасно обрабатывать с помощью подсчета ссылок.
Если вы напишете
String s = "Polish";
String t = "Polish";
тогда s и t фактически относятся к одному и тому же объекту, а s == t вернет истину, поскольку "==" для прочитанных объектов "является одним и тем же объектом" (или, в любом случае, я не уверен, является ли это частью фактическая спецификация языка или просто детали реализации компилятора - так что, возможно, полагаться на это небезопасно).
Если вы напишете
String s = new String("Polish");
String t = new String("Polish");
тогда s! = t (потому что вы явно создали новую строку), хотя s.equals (t) вернет true (потому что строка добавляет это поведение к equals).
То, что ты хочешь написать,
CaseInsensitiveString cis = "Polish";
не может работать, потому что вы думаете, что цитаты - это своего рода конструктор короткого замыкания для вашего объекта, хотя на самом деле это работает только для простых старых java.lang.Strings.
+1 за упоминание о неизменности, которая для меня является настоящей причиной того, что в java пишут strA = strB вместо strA = new String(strB). это действительно не имеет отношения к интернированию строк.
Они не обрабатываются подсчетом ссылок. JLS требует объединения строк.
В большинстве версий JDK две версии будут одинаковыми:
Строка s = новая Строка («глупо»);
String s = "Больше не глупо";
Поскольку строки неизменяемы, компилятор поддерживает список строковых констант, и если вы попытаетесь создать новый, сначала проверит, определена ли уже строка. Если это так, то возвращается ссылка на существующую неизменяемую строку.
Чтобы уточнить - когда вы говорите «String s =», вы определяете новую переменную, которая занимает место в стеке - тогда независимо от того, говорите ли вы «Больше не глупо» или новую строку («глупо»), происходит то же самое - новый постоянная строка компилируется в ваше приложение, и ссылка на это указывает.
Я не вижу здесь разницы. Однако для вашего собственного класса, который не является неизменным, это поведение не имеет значения, и вы должны вызвать свой конструктор.
ОБНОВЛЕНИЕ: я ошибался! Основываясь на голосовании против и прилагаемом комментарии, я проверил это и понял, что мое понимание неверно - новая строка ("глупо") действительно создает новую строку, а не повторно использует существующую. Я не понимаю, почему это должно быть (в чем польза?), Но код говорит громче, чем слова!
Я бы просто добавил, что в Java есть Копировать конструкторы ...
Ну, это обычный конструктор с объектом того же типа в качестве аргумента.
Это шаблон проектирования, а не языковая конструкция. В Java очень мало применений, в которых конструктор копирования был бы интересен, поскольку все всегда выполняется «по ссылке», и каждый объект имеет только одну копию. На самом деле создание копий действительно вызовет МНОГО проблем.
Строки Java интересны. Похоже, что ответы затронули некоторые интересные моменты. Вот мои два цента.
строки неизменны (вы никогда не можете их изменить)
String x = "x";
x = "Y";
сравнение строк зависит от того, что вы сравниваете
String a1 = new String("A");
String a2 = new String("A");
a1 не равно a2a1 и a2 - объектные ссылкиЯ думаю, вы ошиблись, пытаясь использовать класс, нечувствительный к регистру. Оставьте струны в покое. Что вас действительно волнует, так это то, как вы отображаете или сравниваете значения. Используйте другой класс для форматирования строки или для сравнения.
т.е.
TextUtility.compare(string 1, string 2)
TextUtility.compareIgnoreCase(string 1, string 2)
TextUtility.camelHump(string 1)
Поскольку вы составляете класс, вы можете заставить сравнения делать то, что вы хотите - сравнивать текстовые значения.
Компилятор создает пул строк, а не JVM. Переменные не содержат объектов, они ссылаются на них. Пространство пула строк для строковых литералов никогда не освобождается.
- How do i make CaseInsensitiveString behave like String so the above statement is ok (with and w/out extending String)? What is it about String that makes it ok to just be able to pass it a literal like that? From my understanding there is no "copy constructor" concept in Java right?
С самого начала было сказано достаточно. «Польский» является строковым литералом и не может быть назначен классу 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 и верните оттуда соответствующий экземпляр.
Таким образом, оператор "= = " возвращает правда для двух ссылок на объекты, представляющих такое же значение.
Это полезно, когда похожие объекты используются очень часто и создание затрат обходится дорого.
В документации по строковому классу указано, что класс использует внутренний бассейн
Класс не завершен, возникают некоторые интересные проблемы, когда мы пытаемся пройтись по содержимому объекта при реализации интерфейса CharSequence, но этот код достаточно хорош, чтобы показать, как этот элемент в Книге может быть применен.
Важно отметить, что при использовании объекта внутренний бассейн ссылки не освобождаются и, следовательно, не подлежат сборке мусора, и это может стать проблемой, если создается много объектов.
Он работает для класса String, потому что он интенсивно используется, а пул состоит только из «интернированных» объектов.
Это хорошо работает и для логического класса, потому что есть только два возможных значения.
И, наконец, это также причина, по которой valueOf (число) в классе Integer ограничен значениями от -128 до 127 int.
String - это один из специальных классов, в котором вы можете создавать их без новой части Sring.
это то же самое, что
int x = y;
или же
char c;
Тот факт, что у вас есть слово String в вашем классе, не означает, что вы получаете все специальные возможности встроенного класса String.
CaseInsensitiveString не является String, хотя он содержит String. Литерал String, например "пример", может быть назначен только String.
Лучший способ ответить на ваш вопрос - познакомить вас с «пулом констант строк». В java строковые объекты неизменяемы (т.е. их значения не могут быть изменены после их инициализации), поэтому при редактировании строкового объекта вы в конечном итоге создаете новый отредактированный строковый объект, тогда как старый объект просто плавает в специальных областях памяти, называемых "строкой" постоянный бассейн ». создание нового строкового объекта с помощью
String s = "Hello";
создаст только строковый объект в пуле, и ссылка s будет ссылаться на него, но с использованием
String s = new String("Hello");
вы создаете два строковых объекта: один в пуле, а другой в куче. ссылка будет относиться к объекту в куче.
когда говорят писать
String s = "Silly";
вместо
String s = new String("Silly");
они имеют в виду это при создании объекта String, потому что оба приведенных выше оператора создают объект String, но новая версия String () создает два объекта String: один в куче, а другой в пуле строковых констант. Следовательно, требуется больше памяти.
Но когда ты пишешь
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
вы не создаете String, вместо этого вы создаете объект класса CaseInsensitiveString. Следовательно, вам нужно использовать новый оператор.
Если я правильно понял, ваш вопрос означает, почему мы не можем создать объект, напрямую присвоив ему значение, позволяет не ограничивать его классом Wrapper of String в java.
Чтобы ответить на этот вопрос, я бы просто сказал, что чисто объектно-ориентированные языки программирования имеют некоторые конструкции и говорят, что все литералы, написанные отдельно, могут быть напрямую преобразованы в объект данного типа.
Это точно означает, что если интерпретатор увидит 3, он будет преобразован в объект типа Integer, потому что integer - это тип, определенный для таких литералов.
Если интерпретатор видит что-либо в одинарных кавычках, например 'a', он напрямую создаст объект типа character, вам не нужно указывать его, поскольку язык определяет для него объект по умолчанию типа character.
Точно так же, если интерпретатор видит что-то в "", это будет рассматриваться как объект своего типа по умолчанию, то есть строка. Это некий собственный код, работающий в фоновом режиме.
Благодаря видеокурсу 6.00 MIT, где я получил подсказку для этого ответа.
Это основной закон, согласно которому строки в java неизменяемы и чувствительны к регистру.
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.
String s1 = "foo";
literal войдет в пул, а s1 будет ссылаться.
String s2 = "foo";
на этот раз он проверит, что литерал «foo» уже доступен в StringPool или нет, поскольку теперь он существует, поэтому s2 будет ссылаться на тот же литерал.
String s3=new String("foo");
Литерал "foo" сначала будет создан в StringPool, затем будет создан строковый конструктор arg String Object, то есть "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 создаются в разных местах в куче)
Не используйте форматирование кавычек для текста, который не цитируется, и используйте форматирование кода для кода.
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
Каждый раз, когда используется "", это ссылка на строку такой же в пуле констант.
Строка str1 = "foo"; Строка str2 = "foo"; И str1, и str2 принадлежат одному и тому же объекту String, "foo", b'coz для Java управляет строками в StringPool, поэтому новая переменная относится к той же строке, она не создает другую, а назначает тот же алеради, присутствующий в StringPool. Но когда мы это сделаем: String str1 = new String ("foo"); Строка str2 = новая строка ("foo"); Здесь и str1, и str2 принадлежат разным объектам, b'coz new String () принудительно создает новый объект String.