Строки Java: «String s = new String (« глупо »);»

85

Я парень 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";
  1. Почему первое утверждение подходит? Не должно быть

    CaseInsensitiveString cis = "Polish";

  2. Как заставить CaseInsensitiveStringсебя вести себя так, Stringчтобы приведенное выше утверждение было в порядке (с расширением и без него String)? Что такого в String, что позволяет просто передавать ему такой литерал? Насколько я понимаю, в Java нет концепции "конструктора копирования"?

JavaNewbie
источник
2
Строка 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.
Akash5288

Ответы:

110

Stringэто специальный встроенный класс языка. Это для Stringкласса только , в которой вы должны избегать говоря

String s = new String("Polish");

Потому что литерал "Polish"уже имеет тип String, а вы создаете лишний ненужный объект. Для любого другого класса, говоря

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

- это правильный (и единственный в данном случае) поступок.

Адам Розенфилд
источник
8
второй момент заключается в том, что компилятор может придумать создание / распространение материалов со строковыми литералами, что не обязательно возможно с таким странным вызовом функции, как «String (literal)»
Тета,
4
Поскольку вы никогда не должны звонить new String("foo"), вы можете спросить себя, почему new String(String)существует конструктор . Ответ заключается в том, что иногда для этого есть хорошее применение: stackoverflow.com/a/390854/1442870
Enwired
К вашему сведению, комментарий Теты выше содержит ошибку в слове «интернирование», как в случае интернирования строк .
Basil Bourque
56

Я считаю, что главное преимущество использования буквальной формы (т.е. «foo», а не new String («foo»)) заключается в том, что все строковые литералы «интернированы» виртуальной машиной. Другими словами, он добавляется в пул, так что любой другой код, создающий ту же строку, будет использовать объединенную строку, а не создавать новый экземпляр.

Чтобы проиллюстрировать, следующий код будет печатать истину для первой строки, но ложь для второй:

System.out.println("foo" == "foo");
System.out.println(new String("bar") == new String("bar"));
Ли
источник
14
Точно так же FindBugs говорит вам заменить «new Integer (N)» на «Integer.valueOf (N)» - из-за этого интернирования.
Пол Томблин,
6
Вам также следует добавить "foo" == new String ("foo"). Intern ()
Джеймс Шек
4
Исправление: строковые литералы указываются компилятором, а не виртуальной машиной на одну и ту же ссылку. VM может вставлять объекты String во время выполнения, поэтому вторая строка может возвращать true или false!
Крейг П. Мотлин, 02
1
@Motlin: Я не уверен, что это правильно. Документация javadoc для класса String требует, чтобы «все литеральные строки и константные выражения со строковыми значениями были интернированы». Таким образом, мы можем полагаться на интернирование литералов, что означает, что "foo" == "foo" всегда должно возвращать true.
Ли
3
@Leigh Да, литералы интернируются, но компилятором, а не виртуальной машиной. Motlin пытается понять, что виртуальная машина может дополнительно вставлять строки, таким образом, независимо от того, является ли new String ("bar") == new String ("bar") -> false зависимым от реализации.
Аарон Маенпаа,
30

Строки в 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). это действительно не имеет отношения к интернированию строк.
kritzikratzi
Они не обрабатываются подсчетом ссылок. JLS требует объединения строк.
Маркиз Лорн,
20
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 создаются в разных местах в куче)

Викас
источник
1
Не используйте форматирование кавычек для текста, который не цитируется, и используйте форматирование кода для кода.
Маркиз Лорн
12

Strings особенные в Java - они неизменяемы, а строковые константы автоматически превращаются в Stringобъекты.

Ваш SomeStringClass cis = "value"пример нельзя применить к любому другому классу.

Вы также не можете расширить String, потому что он объявлен как final, что означает, что подклассы не разрешены.

Альнитак
источник
7

Строки 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)

Поскольку вы составляете класс, вы можете заставить сравнения делать то, что вы хотите - сравнивать текстовые значения.

мсон
источник
Компилятор создает пул строк, а не JVM. Переменные не содержат объектов, они ссылаются на них. Пространство пула строк для строковых литералов никогда не освобождается.
Маркиз Лорн
6

Вы не можете. Объекты в двойных кавычках в Java специально распознаются компилятором как строки, и, к сожалению, вы не можете переопределить это (или расширить java.lang.String- это объявлено final).

Дэн Винтон
источник
final здесь отвлекающий маневр. Даже если String не был окончательным, расширение String ему в этом случае не помогло бы.
Даррон
1
Я думаю, вы имеете в виду, что к счастью, вы не можете это изменить. :)
Крейг П. Мотлин 02
@Motlin: Ха! Вы вполне можете быть правы. Думаю, я где-то читал, что Java была разработана для того, чтобы никто не делал глупостей, намеренно исключая что-нибудь подобное ...
Дэн Винтон
6

Лучший способ ответить на ваш вопрос - познакомить вас с «пулом констант строк». В java строковые объекты неизменяемы (т.е. их значения не могут быть изменены после инициализации), поэтому при редактировании строкового объекта вы в конечном итоге создаете новый отредактированный строковый объект, тогда как старый объект просто плавает в специальных областях памяти, называемых "строкой" постоянный бассейн ». создание нового строкового объекта с помощью

String s = "Hello";

создаст только строковый объект в пуле, и ссылка s будет ссылаться на него, но с использованием

String s = new String("Hello");

вы создаете два строковых объекта: один в пуле, а другой в куче. ссылка будет относиться к объекту в куче.

Сурендер Такран
источник
4

- Как заставить 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 и верните оттуда соответствующий экземпляр.

Таким образом, оператор "==" возвращает истину для двух ссылок на объекты, представляющих одно и то же значение. .

Это полезно, когда похожие объекты используются очень часто и создание затрат обходится дорого.

В документации по строковому классу указано, что класс использует внутренний пул.

Класс не завершен, некоторые интересные проблемы возникают, когда мы пытаемся пройтись по содержимому объекта при реализации интерфейса CharSequence, но этот код достаточно хорош, чтобы показать, как этот элемент в Книге может быть применен.

Важно отметить, что при использовании объекта internalPool ссылки не освобождаются и, следовательно, не подлежат сборке мусора, и это может стать проблемой, если создается много объектов.

Он работает для класса String, потому что он интенсивно используется, а пул состоит только из «интернированных» объектов.

Это хорошо работает и для логического класса, потому что есть только два возможных значения.

И, наконец, это также причина, по которой valueOf (int) в классе Integer ограничено от -128 до 127 значений int.

OscarRyz
источник
3

В вашем первом примере вы создаете строку "глупо", а затем передаете ее в качестве параметра конструктору копирования другой строки, который создает вторую строку, идентичную первой. Поскольку строки Java неизменяемы (что часто вызывает у людей, привыкших к строкам C), это ненужная трата ресурсов. Вместо этого вам следует использовать второй пример, потому что он пропускает несколько ненужных шагов.

Однако литерал String не является CaseInsensitiveString, поэтому в последнем примере вы не можете делать то, что хотите. Кроме того, нет способа перегрузить оператор приведения, как в C ++, поэтому буквально нет способа делать то, что вы хотите. Вместо этого вы должны передать его в качестве параметра конструктору вашего класса. Конечно, я бы просто использовал String.toLowerCase () и покончил с этим.

Кроме того, ваш CaseInsensitiveString должен реализовывать интерфейс CharSequence, а также, возможно, интерфейсы Serializable и Comparable. Конечно, если вы реализуете Comparable, вы также должны переопределить equals () и hashCode ().

Джеймс
источник
3

Тот факт, что у вас есть слово Stringв вашем классе, не означает, что вы получаете все специальные возможности встроенного Stringкласса.

Javaguy
источник
3

CaseInsensitiveStringне является, Stringхотя и содержит String. StringБуквальный , например , «пример» может быть назначен только String.

fastcodejava
источник
2

CaseInsensitiveString и String - разные объекты. Вы не можете:

CaseInsensitiveString cis = "Polish";

потому что «Polish» - это строка, а не CaseInsensitiveString. Если String расширяет CaseInsensitiveString String, тогда все будет в порядке, но, очевидно, это не так.

И не беспокойтесь о конструкции здесь, вы не будете делать лишние предметы. Если вы посмотрите на код конструктора, все, что он делает, это сохраняет ссылку на переданную вами строку. Ничего лишнего не создается.

В случае String s = new String ("foobar") он делает что-то другое. Сначала вы создаете буквальную строку «foobar», а затем создаете ее копию, создавая из нее новую строку. Нет необходимости создавать эту копию.

Herms
источник
Даже если вы расширите String, это не сработает. Вам понадобится String для расширения CaseInsensitiveString.
Даррон
в любом случае это невозможно, либо потому, что строка встроена, либо потому, что она объявлена ​​окончательной
люк
2

когда говорят писать

String s = "Silly";

вместо того

String s = new String("Silly");

они имеют в виду это при создании объекта String, потому что оба приведенных выше оператора создают объект String, но новая версия String () создает два объекта String: один в куче, а другой в пуле строковых констант. Следовательно, требуется больше памяти.

Но когда ты пишешь

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

вы не создаете String, вместо этого вы создаете объект класса CaseInsensitiveString. Следовательно, вам нужно использовать новый оператор.


источник
1

Если я правильно понял, ваш вопрос означает, почему мы не можем создать объект, напрямую присвоив ему значение, не ограничивая его классом Wrapper of String в java.

Чтобы ответить на этот вопрос, я бы просто сказал, что чисто объектно-ориентированные языки программирования имеют некоторые конструкции, и в нем говорится, что все литералы, написанные отдельно, могут быть напрямую преобразованы в объект данного типа.

Это точно означает, что если интерпретатор увидит 3, он будет преобразован в объект типа Integer, потому что integer - это тип, определенный для таких литералов.

Если интерпретатор видит что-либо в одинарных кавычках, например 'a', он непосредственно создает объект типа character, вам не нужно указывать его, поскольку язык определяет для него объект по умолчанию типа character.

Точно так же, если интерпретатор видит что-то в "", это будет рассматриваться как объект его типа по умолчанию, т.е. строка. Это некий собственный код, работающий в фоновом режиме.

Благодаря видеокурсу 6.00 MIT, где я получил подсказку для этого ответа.

дхарам
источник
0

В Java синтаксис «текст» создает экземпляр класса java.lang.String. Назначение:

String foo = "text";

- простое присвоение, не требующее конструктора копирования.

MyString bar = "text";

Все, что вы делаете, является незаконным, потому что класс MyString не является ни java.lang.String, ни суперклассом java.lang.String.

Даррон
источник
0

Во-первых, вы не можете создать класс, который расширяется от String, потому что String является последним классом. И Java управляет строками иначе, чем другие классы, поэтому только с String вы можете делать

String s = "Polish";

Но с вашим классом вы должны вызывать конструктор. Итак, этот код в порядке.

Лукас Габриэль Санчес
источник
0

Я бы просто добавил, что в Java есть конструкторы копирования ...

Ну, это обычный конструктор с объектом того же типа в качестве аргумента.

PhiLho
источник
2
Это шаблон проектирования, а не языковая конструкция. В Java очень мало применений, в которых конструктор копирования был бы интересен, поскольку все всегда выполняется «по ссылке», и каждый объект имеет только одну копию. Фактически, создание копий действительно вызовет МНОГО проблем.
Bill K
0

В большинстве версий JDK две версии будут одинаковыми:

Строка s = новая Строка («глупо»);

String s = «Больше не глупо»;

Поскольку строки неизменяемы, компилятор поддерживает список строковых констант, и если вы попытаетесь создать новый, сначала проверит, определена ли уже строка. Если это так, то возвращается ссылка на существующую неизменяемую строку.

Чтобы уточнить - когда вы говорите «String s =», вы определяете новую переменную, которая занимает место в стеке - тогда независимо от того, говорите ли вы «Больше не глупо» или новую строку («глупо»), происходит точно то же самое - новый постоянная строка компилируется в ваше приложение, и ссылка на это указывает.

Я не вижу здесь разницы. Однако для вашего собственного класса, который не является неизменным, это поведение не имеет значения, и вы должны вызвать свой конструктор.

ОБНОВЛЕНИЕ: я ошибался! Основываясь на голосовании против и прилагаемом комментарии, я проверил это и понял, что мое понимание неверно - новая строка ("глупо") действительно создает новую строку, а не повторно использует существующую. Я не понимаю, почему это может быть (в чем польза?), Но код говорит громче, чем слова!

Эван Мейкпис
источник
0

String - это один из специальных классов, в котором вы можете создавать их без новой части Sring.

это то же самое, что

int x = y;

или

char c;

Толга Э
источник
0

Это основной закон, согласно которому строки в java неизменяемы и чувствительны к регистру.

user1945649
источник
0
 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.

Акаш5288
источник
-1

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

Патрик Майклсен
источник
Каждый раз, когда используется "", это ссылка на ту же строку в пуле констант.
Маркиз Лорн,