Рядки Java: "Рядок 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)? Що саме в рядку робить нормальним можливість просто передавати його таким літералом? Наскільки я розумію, у Java немає концепції "конструктора копій"?


2
Рядок str1 = "foo"; Рядок str2 = "foo"; І str1, і str2 належать одному об'єкту String, "foo", b'coz для Java керує рядками в StringPool, тому нова змінна відноситься до тієї самої String, вона не створює іншої, а присвоює ту саму алерадію, присутню в StringPool. Але коли ми робимо це: String str1 = new String ("foo"); Рядок str2 = новий рядок ("foo"); Тут і str1, і str2 належать різним Об'єктам, b'coz new String () примусово створює новий String Object.
Akash5288

Відповіді:


110

Stringце спеціальний вбудований клас мови. Це для Stringкласу тільки , в якій ви повинні уникати кажучи

String s = new String("Polish");

Оскільки літерал "Polish"уже має тип String, і ви створюєте зайвий непотрібний об’єкт. Для будь-якого іншого класу, кажучи

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

це правильне (і єдине, в даному випадку), що потрібно зробити.


8
другий момент полягає в тому, що компілятор може вигадувати об'єднання / розповсюдження з рядковими літералами, що не обов'язково можливо за допомогою такого дивного
виклику

4
Оскільки вам ніколи не слід телефонувати new String("foo"), ви можете запитати себе, чому new String(String)існує конструктор . Відповідь в тому , що іноді є використання добре для нього: stackoverflow.com/a/390854/1442870
Enwired

FYI, вищезазначений коментар Тетхи неправильно написав слово "стажування", як у рядку інтернування .
Василь Бурк,

56

Я вважаю, що головна перевага використання літеральної форми (тобто "foo", а не нового String ("foo")) полягає в тому, що всі літерали String "інтерновані" VM. Іншими словами, він додається до пулу таким чином, що будь-який інший код, який створює той самий рядок, буде використовувати об'єднаний рядок, а не створювати новий екземпляр.

Для ілюстрації, наступний код буде друкувати true для першого рядка, але false для другого:

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
Виправлення: рядкові літерали змушені вказувати на одне посилання компілятором, а не віртуальною машиною. Віртуальна машина може інтернувати String-об'єкти під час виконання, тому другий рядок може повертати true або false!
Крейг П. Мотлін,

1
@Motlin: Я не впевнений, що це правильно. Javadoc для класу String вимагає, щоб "усі літеральні рядки та рядкові значення константних виразів інтерновані". Тож ми можемо покладатися на інтерновані літерали, що означає, що "foo" == "foo" завжди має повертати true.
Лі

3
@Leigh Так, літерали інтернуються, але компілятором, а не віртуальною машиною. Що Motlin досягає, це те, що віртуальна машина може додатково інтернірувати рядки, таким чином, незалежно від того, чи є новий String ("bar") == new String ("bar") -> false.
Аарон Маенпаа,

30

Рядки обробляються в 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.


+1 за згадку про незмінність, яка для мене є справжньою причиною в Java strA = strB, а не замість strA = new String(strB). це насправді не має багато робити з інтернуванням рядків.
kritzikratzi

Вони не обробляються шляхом підрахунку посилань. Об’єднання рядків вимагає JLS.
Маркіз Лорнський

20
String s1="foo";

літерал буде входити до пулу, а s1 посилатиметься.

String s2="foo";

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

String s3=new String("foo");

Літерал "foo" буде створений спочатку в StringPool, потім через конструктор рядкових аргументів String Object буде створений, тобто "foo" у купі через створення об'єкта за допомогою нового оператора, після чого 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 спеціально розпізнаються компілятором як Strings, і, на жаль, ви не можете це перевизначити (або розширити java.lang.String- це оголошено final).


фінал тут - червоний оселедець. Навіть якби String не був остаточним, розширення String не допомогло б йому в цьому випадку.
Даррон,

1
Я думаю, ви маєте на увазі, на щастя, ви не можете це перевизначити. :)
Крейг П. Мотлін,

@Motlin: Ха! Ви цілком можете мати рацію. Здається, я десь читав, що Java створена для того, щоб заважати будь-кому робити щось дурне, навмисно виключаючи щось подібне ...
Ден Вінтон,

6

Найкращим способом відповісти на ваше запитання було б ознайомити вас із "постійним пулом рядків". У Java-рядкові об'єкти незмінні (тобто їх значення не можуть бути змінені після їх ініціалізації), тому при редагуванні рядкового об'єкта ви в кінцевому підсумку створюєте новий відредагований об'єкт-рядок, тоді як старий об'єкт просто плаває в спеціальній області пам'яті, яка називається "рядок постійний пул ". створення нового рядкового об'єкта за допомогою

String s = "Hello";

створить лише об'єкт рядка в пулі, і посилання будуть посилатися на нього, але за допомогою

String s = new String("Hello");

ви створюєте два рядкові об'єкти: один у пулі, а другий у купі. посилання буде посилатися на об'єкт у купі.


4

- Як я можу змусити CaseInsensitiveString поводитися як 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

У вашому першому прикладі ви створюєте рядок, "дурний", а потім передаєте його як параметр іншому конструктору копіювання рядка, який робить другий рядок, ідентичний першому. Оскільки рядки Java незмінні (те, що часто жалить людей, які звикли до рядків C), це марне витрачання ресурсів. Натомість слід використати другий приклад, оскільки він пропускає кілька непотрібних кроків.

Однак літерал String не є CaseInsensitiveString, тому ви не можете робити те, що хочете у своєму останньому прикладі. Крім того, немає можливості перевантажити оператор кастингу, як ви можете в C ++, тому буквально немає можливості робити те, що ви хочете. Натомість ви повинні передати його як параметр конструктору вашого класу. Звичайно, я б, мабуть, просто використовував String.toLowerCase () і закінчував з цим.

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


3

Те, що у вас є слово Stringу вашому класі, не означає, що ви отримаєте всі особливі можливості вбудованого Stringкласу.


3

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


2

CaseInsensitiveString і String - це різні об'єкти. Ви не можете зробити:

CaseInsensitiveString cis = "Polish";

оскільки "Polish" - це рядок, а не CaseInsensitiveString. Якщо String розширив CaseInsensitiveString String, то з вами все буде в порядку, але, очевидно, ні.

І не турбуйтеся про будівництво тут, ви не будете робити непотрібні предмети. Якщо ви подивитесь на код конструктора, все, що він робить, - це зберігання посилання на рядок, який ви ввели. Нічого зайвого не створюється.

У випадку String s = new String ("foobar") він робить щось інше. Спочатку ви створюєте буквальний рядок "foobar", а потім створюєте його копію, створюючи з нього новий рядок. Немає необхідності створювати цю копію.


Навіть якщо ви розширили String, це не спрацює. Вам знадобиться String для розширення CaseInsensitiveString.
Даррон,

в будь-якому випадку це неможливо, або тому, що вбудований рядок, або тому, що його оголошено остаточним
luke

2

коли кажуть писати

String s = "Silly";

замість

String s = new String("Silly");

вони мають на увазі це під час створення об'єкта String, оскільки обидва вищезазначені оператори створюють об'єкт String, але нова версія String () створює два об'єкти String: один у купі, а інший у пулі констант. Отже, використання більше пам'яті.

Але коли ти пишеш

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

ви не створюєте рядок, натомість ви створюєте об'єкт класу CaseInsensitiveString. Отже, вам потрібно використовувати новий оператор.


1

Якщо я правильно зрозумів, ваше запитання означає, чому ми не можемо створити об’єкт, безпосередньо присвоївши йому значення, не дозволяємо обмежувати його класом обгортки рядка в java.

Щоб відповісти, що я б просто сказав, суто об’єктно-орієнтовані мови програмування мають деякі конструкції, і в них сказано, що всі літерали, написані самостійно, можуть бути безпосередньо перетворені в об’єкт даного типу.

Це точно означає, що якщо інтерпретатор побачить 3, він буде перетворений в об’єкт Integer, оскільки ціле число є типом, визначеним для таких літералів.

Якщо інтерпретатор бачить будь-яку річ в одинарних лапках на зразок 'a', він безпосередньо створить об'єкт символу типу, вам не потрібно його вказувати, оскільки мова визначає для нього об'єкт типу за замовчуванням.

Подібним чином, якщо "" інтерпретатор бачить щось у "", це буде розглядатися як об'єкт за замовчуванням, тобто "рядок". Це якийсь власний код, який працює у фоновому режимі.

Завдяки відео-лекційному курсу MIT 6.00, де я отримав підказку щодо цієї відповіді.


0

У Java синтаксис "text" створює екземпляр класу 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 має конструктори Copy ...

Ну, це звичайний конструктор з об'єктом того ж типу, що і аргумент.


2
Це шаблон дизайну, а не мовна конструкція. У Java дуже мало застосувань, де конструктор копій був би цікавим, оскільки все завжди «за посиланням», і кожен об’єкт має лише одну копію. Насправді, виготовлення копій справді спричинить БАГАТО проблем.
Білл К

0

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

Рядок s = новий Рядок ("безглуздо");

Рядок s = "Більше не дурно";

Оскільки рядки незмінні, компілятор підтримує список констант рядків, і якщо ви спробуєте створити новий, спочатку перевірить, чи рядок вже визначений. Якщо це тоді, повертається посилання на існуючий незмінний рядок.

Щоб уточнити - коли ви говорите "String s =", ви визначаєте нову змінну, яка займає місце в стеку, - тоді, чи ви говорите "Більше не дурно" або new String ("дурно"), відбувається те саме - нова константний рядок компілюється у вашій програмі і посилання на це.

Я не бачу тут відмінності. Однак для вашого власного класу, який не є незмінним, така поведінка не має значення, і ви повинні викликати свій конструктор.

ОНОВЛЕННЯ: Я помилився! На підставі доданого голосування та коментаря я перевірив це і усвідомлюю, що моє розуміння хибне - новий рядок ("Дурний") справді створює новий рядок, а не повторно використовує існуючий. Мені незрозуміло, чому це було б (яка користь?), Але код говорить голосніше, ніж слова!


0

String - це один із спеціальних класів, в якому ви можете створювати їх без нової частини Sring

це те саме, що

int x = y;

або

char c;



0
 String str1 = "foo"; 
 String str2 = "foo"; 

І str1, і str2 належать одному об'єкту String, "foo", b'coz Java керує рядками в StringPool, тому, якщо нова змінна посилається на ту саму String, вона не створює іншої, а присвоює ту саму алерадію, присутню в StringPool .

 String str1 = new String("foo"); 
 String str2 = new String("foo");

Тут і str1, і str2 належать різним Об'єктам, b'coz new String () з силою створює новий String Object.


-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'}));

Сподіваємось, звідси ви зрозумієте, чому ваш підручник вважає це зайвим.

Для довідки, ось реалізація рядка: http://www.docjar.com/html/api/java/lang/String.java.html

Це веселе читання і може надихнути на деяке розуміння. Початківцям також чудово читати та намагатися зрозуміти, оскільки код демонструє дуже професійний та сумісний з умовами код.

Ще одним хорошим посиланням є підручник з Java щодо рядків: http://docs.oracle.com/javase/tutorial/java/data/strings.html


Будь-коли "" використовується посилання на той самий рядок у пулі констант.
Маркіз Лорнський
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.