Строки Java: "String s = new String (" глупо ");" - PullRequest
84 голосов
/ 02 декабря 2008

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

Ответы [ 23 ]

108 голосов
/ 02 декабря 2008

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

String s = new String("Polish");

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

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

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

56 голосов
/ 02 декабря 2008

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

Для иллюстрации следующий код будет печатать true для первой строки, но false для второй:

System.out.println("foo" == "foo");
System.out.println(new String("bar") == new String("bar"));
30 голосов
/ 02 декабря 2008

Строки обрабатываются немного специально в 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.

19 голосов
/ 18 января 2014
String s1="foo";

литерал войдет в пул и s1 будет ссылаться.

String s2="foo";

на этот раз он проверит, что литерал "foo" уже доступен в StringPool или нет, как сейчас, поэтому s2 будет ссылаться на тот же литерал.

String s3=new String("foo");

Сначала в StringPool будет создан литерал "foo", затем через строковый аргумент 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 создаются в разных местах в куче)

12 голосов
/ 02 декабря 2008

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

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

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

7 голосов
/ 02 декабря 2008

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

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

6 голосов
/ 02 декабря 2008

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

6 голосов
/ 10 мая 2012

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

String s = "Hello";

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

String s = new String("Hello");

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

4 голосов
/ 03 декабря 2008

- Как мне заставить 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.

3 голосов
/ 14 декабря 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...