CharSequence VS String w Javie?

421

Programując w Androidzie, większość wartości tekstowych jest oczekiwana w CharSequence.

Dlaczego? Jakie są korzyści i jakie są główne skutki CharSequencenadmiernego używania String?

Jakie są główne różnice i jakich problemów się spodziewamy, korzystając z nich i przechodząc od jednego do drugiego?

e-satis
źródło
1
Lepsze odpowiedzi można znaleźć w Dokładna różnica między CharSequence i String w java
Suragch

Odpowiedzi:

343

Ciągi to CharSequences , więc możesz po prostu używać Ciągów i nie martwić się. Android stara się tylko pomóc, pozwalając ci również określić inne obiekty CharSequence, takie jak StringBuffers.

Zarkonnen
źródło
94
Z wyjątkiem sytuacji, gdy Android przekazuje mi CharSequence w wywołaniu zwrotnym i potrzebuję String - call charSeq.toString ().
Martin Konicek,
100
Ale pamiętaj o tym zastrzeżeniu z CharSequencejavadoc: ten interfejs nie poprawia ogólnych umów equalsi hashCodemetod. Rezultat porównania dwóch obiektów, które implementują, CharSequencejest zatem ogólnie nieokreślony . Każdy obiekt może być zaimplementowany przez inną klasę i nie ma gwarancji, że każda klasa będzie w stanie przetestować swoje instancje pod kątem równości z instancjami drugiej. Dlatego niewłaściwe jest używanie dowolnych CharSequenceinstancji jako elementów zestawu lub kluczy na mapie.
Trevor Robinson
3
@Pacerier: Myślę, że to bardziej praktyczne ograniczenie. CharSequencebył modernizacją w JDK 1.4, aby wprowadzić wspólny interfejs o ograniczonym przeznaczeniu do obiektów zawierających sekwencje znaków. Niektóre z tych obiektów zawierają inny stan, więc zdefiniowanie go Object.equalsjako „zawiera tę samą sekwencję znaków” może nie mieć sensu . CharBufferNa przykład NIO odsłania znaki tylko między nim positiona limitnim CharSequence, mimo że może zawierać wiele innych znaków.
Trevor Robinson
6
@TrevorRobinson, więc błąd projektowy ma przede wszystkim equals/ hashCodewłączony Object....
Pacerier,
4
@Pacerier: IMHO Nie ma błędu projektowego ani, Objectani CharSequenceżaden interfejs nie jest wymagany, aby zapewnić równość rozsądku między implementacjami. Żadne dwa nie Collectionsą wymagane do zapewnienia równości Collectioninterfejsu, ale mogą to zrobić, jeśli chcą. IMHO CharSequencepowinno być ograniczone do danych wejściowych i wykorzystywane rzadziej w przypadku rodzajów zwrotów.
Brett Ryan
58

CharSequence= interfejs
String= konkretna implementacja

Powiedziałeś:

konwersja z jednego na drugi

Nie ma konwersji z String.

  • Każdy Stringobiekt jestCharSequence .
  • Każdy CharSequencemoże wyprodukować String. Zadzwoń CharSequence::toString. Jeśli CharSequencezdarza się, że a String, to metoda zwraca odwołanie do własnego obiektu.

Innymi słowy, każdy Stringjest CharSequence, ale nie każdy CharSequencejest String.

Programowanie do interfejsu

Programując w Androidzie, większość wartości tekstowych jest oczekiwana w CharSequence.

Dlaczego? Jakie są korzyści i jakie są główne skutki używania CharSequence w stosunku do String?

Zasadniczo programowanie w interfejsie jest lepsze niż programowanie w konkretnych klasach. Daje to elastyczność, dzięki czemu możemy przełączać się między konkretnymi implementacjami konkretnego interfejsu bez łamania innego kodu.

Tworząc interfejs API, który ma być używany przez różnych programistów w różnych sytuacjach, napisz swój kod, aby udostępnić i wziąć jak najbardziej ogólne interfejsy. Daje to programistom wywołującym swobodę korzystania z różnych implementacji tego interfejsu, w zależności od tego, która implementacja jest najlepsza dla danego kontekstu.

Na przykład spójrz na środowisko Java Collections Framework . Jeśli API daje lub bierze uporządkowanego zbioru obiektów, deklarują swoje metody, jak przy użyciu Listzamiast ArrayList, LinkedListalbo jakakolwiek inna realizacja 3rd partia List.

Pisząc szybką i brudną małą metodę, która ma być używana tylko przez Twój kod w jednym określonym miejscu, w przeciwieństwie do pisania interfejsu API do użycia w wielu miejscach, nie musisz zawracać sobie głowy bardziej ogólnym interfejsem niż konkretnym klasa. Ale nawet wtedy korzystanie z najbardziej ogólnego interfejsu, jaki możesz, może zaszkodzić.

Jakie są główne różnice i jakich problemów można się spodziewać podczas ich używania,

  • Z Stringwiesz, że masz jeden fragment tekstu, w całości w pamięci i jest niezmienna.
  • Dzięki CharSequencenie wiesz, jakie mogą być szczególne cechy konkretnej implementacji.

CharSequencePrzedmiot może stanowić ogromny fragment tekstu, a zatem ma wpływ na pamięć. Lub może być wiele fragmentów tekstu śledzonych osobno, które będą musiały zostać połączone podczas połączenia toString, a zatem występują problemy z wydajnością. Implementacja może nawet pobierać tekst ze zdalnej usługi, a zatem ma wpływ na opóźnienia.

i przechodząc od jednego do drugiego?

Zasadniczo nie będziesz nawracał się w tę iz powrotem. A String jest a CharSequence. Jeśli twoja metoda deklaruje, że wymaga a CharSequence, programista wywołujący może przekazać Stringobiekt lub przekazać coś innego, na przykład a StringBufferlub StringBuilder. Kod twojej metody będzie po prostu używał tego, co zostało przekazane, wywołując dowolną z CharSequencemetod.

Konwersja jest najbliższa, jeśli Twój kod otrzyma CharSequencei wiesz, że potrzebujesz String. Być może łączysz się ze starym kodem zapisanym w Stringklasie, a nie w CharSequenceinterfejsie. A może Twój kod będzie intensywnie pracował z tekstem, na przykład powtarzając pętle lub analizując w inny sposób. W takim przypadku chcesz wykonać jeden możliwy hit wydajności tylko raz, więc zadzwoń z toStringgóry. Następnie kontynuuj pracę, używając tego, co wiesz, że jest pojedynczym fragmentem tekstu całkowicie w pamięci.

Zakręcona historia

Zanotuj komentarze do zaakceptowanej odpowiedzi . CharSequenceInterfejs został zamontować na istniejących struktur klasowych, więc istnieją pewne ważne subtelności ( equals()& hashCode()). Zwróć uwagę na różne wersje Javy (1, 2, 4 i 5) oznaczone na klasach / interfejsach - z biegiem lat sporo rezygnacji. Idealnie CharSequencebyłoby na miejscu od samego początku, ale takie jest życie.

Mój diagram klas poniżej może pomóc zobaczyć duży obraz typów ciągów w Javie 7/8. Nie jestem pewien, czy wszystkie są obecne w Androidzie, ale ogólny kontekst może nadal być dla Ciebie przydatny.

schemat różnych klas i interfejsów związanych z łańcuchem

Basil Bourque
źródło
2
Zrobiłeś sam ten schemat? zastanawiam się, czy istnieje katalog tych diagramów dla różnych struktur danych.
user171943,
4
@ user171943 Autor: ja, ręcznie wykonany przy użyciu aplikacji OmniGraffle z OmniGroup.
Basil Bourque,
37

Uważam, że najlepiej jest używać CharSequence. Powodem jest to, że String implementuje CharSequence, dlatego możesz przekazać String do CharSequence, JEDNAK nie możesz przekazać CharSequence do String, ponieważ CharSequence nie implementuje String. TAKŻE, w Androidzie EditText.getText()metoda zwraca wartość Edytowalną, która również implementuje CharSequence i może być łatwo przekazana do jednego, a nie łatwo do Łańcucha. CharSequence obsługuje wszystko!

Pozorny
źródło
7
Możesz zrobićcharSequence.toString()
Jorge Fuentes González
1
@jorge: Tyle że będzie to względnie nieefektywne, jeśli sekwencja jest zmienna (lub z jakiegokolwiek powodu wymaga kopii znaków, aby utworzyć ciąg niezmienny).
Lawrence Dol
bardzo ładne wyjaśnienie ..!
majurageerthan
23

Zasadniczo użycie interfejsu pozwala na różnicowanie implementacji przy minimalnym uszkodzeniu dodatkowym. Mimo że java.lang.String są bardzo popularne, może się zdarzyć, że w pewnych kontekstach ktoś może chcieć użyć innej implementacji. Budując interfejs API wokół CharSequences zamiast Strings, kod daje taką możliwość.

Itay Maman
źródło
8

Jest to prawie na pewno powód wydajności. Na przykład, wyobraź sobie parser, który przechodzi przez 500k ByteBuffer zawierający łańcuchy.

Istnieją 3 podejścia do zwracania treści ciągu:

  1. Zbuduj String [] w czasie analizy, po jednym znaku na raz. Zajmie to zauważalnie dużo czasu. Możemy użyć == zamiast .equals do porównania referencji w pamięci podręcznej.

  2. Zbuduj int [] z przesunięciami w czasie analizy, a następnie dynamicznie buduj String, gdy nastąpi get (). Każdy ciąg będzie nowym obiektem, więc żadne buforowanie nie zwróci wartości i użycie ==

  3. Zbuduj CharSequence [] w czasie analizy. Ponieważ żadne nowe dane nie są przechowywane (poza przesunięciami do bufora bajtów), parsowanie jest znacznie niższe niż # 1. W czasie nie musimy budować Ciągu, więc uzyskana wydajność jest równa # 1 (znacznie lepsza niż # 2), ponieważ zwracamy tylko odwołanie do istniejącego obiektu.

Oprócz korzyści przetwarzania uzyskanych za pomocą CharSequence, zmniejszasz także zużycie pamięci, nie powielając danych. Na przykład, jeśli masz bufor zawierający 3 akapity tekstu i chcesz zwrócić wszystkie 3 lub pojedynczy akapit, potrzebujesz 4 ciągów znaków, które to reprezentują. Używając CharSequence potrzebujesz tylko 1 bufora z danymi i 4 instancji implementacji CharSequence, która śledzi początek i długość.

Phil Lello
źródło
6
referencje plz. brzmi jak losowe zgadywanie, co się dzieje. również nie uważam twojego argumentu za ważny. można po prostu zapisać bajt 500k jako ciąg znaków i po prostu zwrócić podciągi, co jest szalone szybkie i znacznie bardziej powszechne.
kritzikratzi
6
@kritzikratzi - od JDK7 podłańcuch na String nie dzieli już podstawowej tablicy i nie jest „szalony szybki”. Zajmuje czas O (N) długości podłańcucha i generuje kopię podstawowych znaków za każdym razem, gdy go wywołujesz (więc dużo śmieci).
BeeOnRope,
@kritzikratzi Uważam, że powodem zmiany jest to, że gdyby kopia nie została wykonana, oryginalny ciąg znaków zostałby zachowany do końca życia wszystkich podciągów. Biorąc pod uwagę, że podciągi są zwykle tylko małymi częściami oryginału i mogą trwać w nieskończoność w zależności od tego, w jaki sposób są używane, często skutkowałoby to jeszcze większą ilością śmieci, gdyby podciągi były używane znacznie dłużej niż oryginalny ciąg. Ciekawym rozwiązaniem alternatywnym może być ustalenie, czy kopiować na podstawie stosunku podłańcucha do rozmiaru ciągu nadrzędnego, ale w tym celu trzeba by rzucić własną CharSequenceimplementację.
JAB
1
Być może nie trafiłem w sedno, ale ta odpowiedź jest bzdurą. A CharSequencejest interfejsem - z definicji nie ma żadnych szczegółów implementacji, które omawiasz, ponieważ nie ma własnej implementacji . A Stringjest jedną z kilku konkretnych klas, które implementują CharSequenceinterfejs. Więc String jestCharSequence . Możesz porównać szczegóły dotyczące wydajności Stringvs StringBuffervs StringBuilder, ale nie CharSequence. Pisanie „korzyści z przetwarzania uzyskiwanych za pomocą CharSequence” nie ma znaczenia.
Basil Bourque,
7

Problem, który pojawia się w praktycznym kodzie Androida, polega na tym, że porównywanie ich z CharSequence.equals jest prawidłowe, ale niekoniecznie działa zgodnie z przeznaczeniem.

EditText t = (EditText )getView(R.id.myEditText); // Contains "OK"
Boolean isFalse = t.getText().equals("OK"); // will always return false.

Porównanie powinno zostać wykonane przez

("OK").contentEquals(t.GetText()); 
Crypth
źródło
5

CharSequence

A CharSequencejest interfejsem, a nie rzeczywistą klasą. Interfejs to tylko zbiór reguł (metod), które klasa musi zawierać, jeśli implementuje interfejs. W Androidzie a CharSequencejest parasolem na różnego rodzaju ciągi tekstowe. Oto niektóre z typowych:

(Możesz przeczytać więcej o różnicach między nimi tutaj .)

Jeśli masz CharSequenceobiekt, to w rzeczywistości jest to obiekt jednej z implementujących klas CharSequence. Na przykład:

CharSequence myString = "hello";
CharSequence mySpannableStringBuilder = new SpannableStringBuilder();

Zaletą posiadania ogólnego typu parasola CharSequencejest to, że można obsługiwać wiele typów za pomocą jednej metody. Na przykład, jeśli mam metodę, która przyjmuje CharSequencejako parametr, mogłabym przekazać a Stringlub a SpannableStringBuilderi obsłużyłaby jedną z nich.

public int getLength(CharSequence text) {
    return text.length();
}

Strunowy

Można powiedzieć, że a Stringjest tylko jednym rodzajem CharSequence. Jednak w przeciwieństwie do CharSequence, jest to klasa rzeczywista, więc możesz tworzyć z niej obiekty. Więc możesz to zrobić:

String myString = new String();

ale nie możesz tego zrobić:

CharSequence myCharSequence = new CharSequence(); // error: 'CharSequence is abstract; cannot be instantiated

Ponieważ CharSequencejest to tylko lista reguł, które są Stringzgodne, możesz to zrobić:

CharSequence myString = new String();

Oznacza to, że za każdym razem sposób prosi o CharSequence, to jest w porządku, aby nadać mu String.

String myString = "hello";
getLength(myString); // OK

// ...

public int getLength(CharSequence text) {
    return text.length();
}

Jednak przeciwieństwo nie jest prawdą. Jeśli metoda przyjmuje Stringparametr, nie można przekazać mu czegoś, co jest ogólnie znane jako a CharSequence, ponieważ może to być faktycznie SpannableStringinny rodzaj CharSequence.

CharSequence myString = "hello";
getLength(myString); // error

// ...

public int getLength(String text) {
    return text.length();
}
Suragch
źródło
0

CharSequencejest interfejsem i Stringimplementuje go. Możesz utworzyć instancję, Stringale nie możesz tego zrobić, CharSequenceponieważ jest to interfejs. Inne implementacje można znaleźć CharSequencena oficjalnej stronie Java.

Xiaogang
źródło
-4

CharSequence to czytelna sekwencja wartości char, która implementuje String. ma 4 metody

  1. charAt (int index)
  2. długość()
  3. subSequence (int start, int end)
  4. toString ()

Proszę zapoznać się z dokumentacją Dokumentacja CharSequence

Srinivas Thammanaboina
źródło
6
CharSequencenie implementuje String. Ale odwrotność jest prawdziwa.
seh
1
Usuń tę nonsensowną odpowiedź
J. Doe