Czy „Parent x = new Child ();” zamiast „Child x = new Child ();” jest złą praktyką, jeśli możemy użyć tego drugiego?


32

Na przykład widziałem niektóre kody, które tworzą taki fragment:

Fragment myFragment=new MyFragment();

który deklaruje zmienną jako Fragment zamiast MyFragment, który MyFragment jest klasą potomną Fragment. Nie jestem usatysfakcjonowany tą linią kodów, ponieważ uważam, że ten kod powinien być:

MyFragment myFragment=new MyFragment();

co jest bardziej szczegółowe, czy to prawda?

Lub w uogólnieniu pytania, czy złą praktyką jest stosowanie:

Parent x=new Child();

zamiast

Child x=new Child();

czy możemy zmienić poprzednią na drugą bez błędu kompilacji?


6
Jeśli zrobimy to drugie, nie ma już sensu abstrakcji / uogólnienia. Oczywiście są wyjątki ...
Walfrat

2
W projekcie, nad którym pracuję, moje funkcje oczekują typu nadrzędnego i uruchamiają metody typu nadrzędnego. Mogę zaakceptować dowolne z kilku dzieci, a kod za tymi (odziedziczonymi i zastąpionymi) metodami jest inny, ale chcę uruchomić ten kod bez względu na to, którego dziecka używam.
Stephen S

4
@Walfrat Rzeczywiście nie ma sensu abstrakcja, gdy już tworzysz konkretny typ, ale nadal możesz zwrócić typ abstrakcyjny, aby kod klienta był błogo ignorantem.
Jacob Raihle

4
Nie używaj rodzica / dziecka. Użyj interfejsu / klasy (dla Java).
Thorbjørn Ravn Andersen

4
„Programowanie do interfejsu” Google zawiera kilka wyjaśnień, dlaczego używanie Parent x = new Child()jest dobrym pomysłem.
Kevin Workman

Odpowiedzi:


69

To zależy od kontekstu, ale argumentowałbym, że powinieneś zadeklarować najbardziej abstrakcyjny typ z możliwych. W ten sposób Twój kod będzie jak najbardziej ogólny i nie będzie zależał od nieistotnych szczegółów.

Przykładem może być posiadanie LinkedListi od ArrayListktórego oba pochodzą List. Jeśli kod działałby równie dobrze z dowolnym rodzajem listy, nie ma powodu, aby arbitralnie ograniczać go do jednej z podklas.


24
Dokładnie. Aby dodać przykład, zastanów się: List list = new ArrayList()Kod korzystający z listy nie dba o to, jaką to jest lista, tylko że spełnia umowę interfejsu List. W przyszłości moglibyśmy z łatwością przejść na inną implementację List, a kod źródłowy nie wymagałby zmiany ani aktualizacji.
Maybe_Factor

19
dobra odpowiedź, ale powinna rozwinąć ten najbardziej abstrakcyjny typ, co oznacza, że ​​kod w zakresie nie powinien być przesyłany z powrotem do elementu potomnego w celu wykonania określonej operacji podrzędnej.
Mike

10
@Mike: Mam nadzieję, że to oczywiste, w przeciwnym razie wszystkie zmienne, parametry i pola zostaną zadeklarowane jako Object!
JacquesB

3
@JacquesB: ponieważ na to pytanie i odpowiedź poświęcono tyle uwagi, myślałem, że jawność będzie przydatna dla osób o różnych poziomach umiejętności.
Mike

1
To zależy od kontekstu nie jest odpowiedzią.
Billal Begueradj

11

Odpowiedź JacquesB jest poprawna, choć trochę abstrakcyjna. Pozwolę sobie wyraźniej podkreślić niektóre aspekty:

W swoim fragmencie kodu wyraźnie używasz newsłowa kluczowego i być może chciałbyś kontynuować dalsze etapy inicjalizacji, które prawdopodobnie są specyficzne dla konkretnego typu: wtedy musisz zadeklarować zmienną z tym konkretnym konkretnym typem.

Sytuacja wygląda inaczej, gdy zdobędziesz ten przedmiot skądinąd, np IFragment fragment = SomeFactory.GiveMeFragments(...);. W tym przypadku fabryka powinna zwykle zwracać najbardziej abstrakcyjny typ, a kod w dół nie powinien zależeć od szczegółów implementacji.


18
„choć trochę abstrakcyjne”. Badumtish.
rosuav

1
czy to nie mogła być edycja?
beppe9000

6

Istnieją potencjalnie różnice w wydajności.

Jeśli klasa pochodna jest zapieczętowana, kompilator może wstawić i zoptymalizować lub wyeliminować to, co w innym przypadku byłoby wirtualnymi wywołaniami. Dlatego może występować znacząca różnica w wydajności.

Przykład: ToStringjest połączeniem wirtualnym, ale Stringjest zapieczętowane. Włącz String, ToStringto brak operacji, więc jeśli deklarujesz jako objectwywołanie wirtualne, jeśli deklarujesz, Stringże kompilator wie, że żadna klasa pochodna nie zastąpiła metody, ponieważ klasa jest zapieczętowana, więc nie ma opcji. Podobne rozważania dotyczą ArrayListvs LinkedList.

Dlatego jeśli znasz konkretny typ obiektu (i nie ma powodu, by go ukryć), powinieneś zadeklarować ten typ. Ponieważ właśnie utworzyłeś obiekt, znasz typ betonu.


4
W większości przypadków różnice w wydajności są niewielkie. I w większości przypadków (słyszałem, że jest to około 90% czasu) powinniśmy zapomnieć o tych małych różnicach. Tylko wtedy, gdy wiemy (najlepiej poprzez pomiar), że starzejymy się jako sekcja kodu o kluczowym znaczeniu dla wydajności, powinniśmy dbać o takie optymalizacje.
Jules

A kompilator wie, że „new Child ()” zwraca Child, nawet gdy jest przypisany do Parent, i tak długo, jak wie, może korzystać z tej wiedzy i wywoływać kod Child ().
gnasher729

+1. Również ukradłem twój przykład w mojej odpowiedzi. = P
Nat

1
Pamiętaj, że istnieją optymalizacje wydajności, które sprawiają, że wszystko jest dyskusyjne. Na przykład HotSpot zauważy, że parametr przekazany do funkcji ma zawsze jedną konkretną klasę i odpowiednio się zoptymalizuje. Jeśli to założenie okaże się kiedykolwiek fałszywe, metoda jest dezoptymalizowana. Ale to silnie zależy od środowiska kompilatora / środowiska wykonawczego. Ostatnim razem, gdy sprawdziłem CLR, nie mogłem nawet zrobić dość trywialnej optymalizacji zauważenia, że ​​p Parent p = new Child()jest zawsze dzieckiem.
Voo

4

Kluczową różnicą jest wymagany poziom dostępu. I każda dobra odpowiedź na temat abstrakcji dotyczy zwierząt, a przynajmniej tak mi powiedziano.

Załóżmy, że masz kilka zwierząt - Cat Birdi Dog. Teraz, te zwierzęta mają kilka wspólnych zachowań - move(), eat(), i speak(). Wszystkie jedzą inaczej i mówią inaczej, ale jeśli potrzebuję, aby moje zwierzę je lub mówiło, nie obchodzi mnie, jak to robią.

Ale czasami tak. Nigdy nie strzegłem kota stróżującego ani ptaka stróżującego, ale mam psa stróżującego. Kiedy więc ktoś włamuje się do mojego domu, nie mogę polegać na mówieniu, aby odstraszyć intruza. Naprawdę potrzebuję mojego psa do szczekania - to coś innego niż jego mowa, która jest po prostu dźwiękiem.

Więc w kodzie, który wymaga intruza, naprawdę muszę to zrobić Dog fido = getDog(); fido.barkMenancingly();

Ale przez większość czasu mogę szczęśliwie nakłonić dowolne zwierzęta do robienia sztuczek, które przywołują, miauczą lub tweetują dla smakołyków. Animal pet = getAnimal(); pet.speak();

Aby być bardziej konkretnym, ArrayList ma kilka metod, które Listtego nie robią. Warto zauważyć, że trimToSize(). Teraz w 99% przypadków nie przejmujemy się tym. To Listprawie nigdy nie jest wystarczająco duże, abyśmy się tym przejmowali. Ale są chwile, kiedy to jest. Od czasu do czasu musimy konkretnie poprosić o ArrayListwykonanie trimToSize()i zmniejszenie jego tablicy pomocniczej.

Pamiętaj, że nie trzeba tego robić podczas budowy - można to zrobić za pomocą metody zwrotu lub nawet obsady, jeśli jesteśmy absolutnie pewni.


Mylić, dlaczego może to mieć negatywne zdanie. Osobiście wydaje się kanoniczny. Z wyjątkiem pomysłu programowalnego psa stróżującego ...
corsiKa

Dlaczego musiałem czytać strony, aby uzyskać najlepszą odpowiedź? Ocena jest do bani.
Basilevs

Dzięki za miłe słowa. System ocen ma wady i zalety, a jedną z nich jest to, że późniejsze odpowiedzi mogą czasem pozostać w pyle. Zdarza się!
corsiKa

2

Ogólnie rzecz biorąc, powinieneś użyć

var x = new Child(); // C#

lub

auto x = new Child(); // C++

dla zmiennych lokalnych, chyba że istnieje uzasadniony powód, aby zmienna miała inny typ niż to, z którym została zainicjowana.

(Tutaj ignoruję fakt, że kod C ++ najprawdopodobniej powinien używać inteligentnego wskaźnika. Są przypadki, w których nie można i nie ma więcej niż jednej linii, której tak naprawdę nie znam).

Ogólna idea polega na tym, że języki obsługują automatyczne wykrywanie zmiennych, użycie go ułatwia odczyt kodu i robi właściwą rzecz (system kompilatora może zoptymalizować jak najwięcej i zmienić inicjalizację na nową typ działa również, jeśli użyto najbardziej abstrakcyjnych).


1
W C ++ zmienne są w większości tworzone bez nowego słowa kluczowego: stackoverflow.com/questions/6500313/...
Marian Spanik

1
Pytanie nie dotyczy C # / C ++, ale ogólnej kwestii obiektowej w języku, który ma przynajmniej pewną formę pisania statycznego (tzn. Byłoby bezsensowne zadawanie pytań w rodzaju Ruby). Poza tym, nawet w tych dwóch językach, tak naprawdę nie widzę dobrego powodu, dla którego powinniśmy to robić tak, jak mówisz.
AnoE

1

Dobrze, ponieważ łatwo jest zrozumieć i łatwo zmodyfikować ten kod:

var x = new Child(); 
x.DoSomething();

Dobrze, ponieważ komunikuje zamiar:

Parent x = new Child(); 
x.DoSomething(); 

Ok, ponieważ jest to powszechne i łatwe do zrozumienia:

Child x = new Child(); 
x.DoSomething(); 

ParentJedyną opcją, która jest naprawdę zła, jest użycie, jeśli tylko Childma metodę DoSomething(). Jest źle, ponieważ źle komunikuje zamiar:

Parent x = new Child(); 
(x as Child).DoSomething(); // DON'T DO THIS! IF YOU WANT x AS CHILD, STORE x AS CHILD

Teraz rozwiążmy trochę więcej na temat sprawy, o którą pyta konkretnie:

Parent x = new Child(); 
x.DoSomething(); 

Sformatujmy to inaczej, tworząc drugą połowę wywołania funkcji:

WhichType x = new Child(); 
FunctionCall(x);

void FunctionCall(WhichType x)
    x.DoSomething(); 

Można to skrócić do:

FunctionCall(new Child());

void FunctionCall(WhichType x)
    x.DoSomething();

Tutaj zakładam, że jest to powszechnie akceptowane WhichType powinien być najbardziej podstawowym / abstrakcyjnym typem, który pozwala na działanie tej funkcji (chyba że występują problemy z wydajnością). W tym przypadku właściwym typem będzie albo Parent, albo coś Parentpochodzi.

Ten tok rozumowania wyjaśnia, dlaczego użycie tego typu Parentjest dobrym wyborem, ale nie zmienia się w pogodę, inne opcje są złe (nie są).


1

tl; dr - Używanie opcjiChildoverParentjest lepsze w zakresie lokalnym. Pomaga to nie tylko w czytelności, ale także zapewnia prawidłowe działanie przeciążonej rozdzielczości metody i umożliwia wydajną kompilację.


W zakresie lokalnym

Parent obj = new Child();  // Works
Child  obj = new Child();  // Better
var    obj = new Child();  // Best

Koncepcyjnie chodzi o zachowanie jak największej ilości informacji o typie. Jeśli obniżymy doParent , zasadniczo usuwamy informacje o typie, które mogłyby być przydatne.

Zachowanie pełnej informacji o typie ma cztery główne zalety:

  1. Dostarcza więcej informacji kompilatorowi.
  2. Zapewnia czytelnikowi więcej informacji.
  3. Czystszy, bardziej znormalizowany kod.
  4. Sprawia, że ​​logika programu jest bardziej zmienna.

Zaleta 1: Więcej informacji dla kompilatora

Typ pozorny jest wykorzystywany przy przeładowaniu rozdzielczości i optymalizacji.

Przykład: Przeciążona rozdzielczość metody

main()
{
    Parent parent = new Child();
    foo(parent);

    Child  child  = new Child();
    foo(child);
}
foo(Parent arg) { /* ... */ }  // More general
foo(Child  arg) { /* ... */ }  // Case-specific optimizations

W powyższym przykładzie oba foo()wywołania działają , ale w jednym przypadku uzyskujemy lepszą rozdzielczość przeciążonej metody.

Przykład: optymalizacja kompilatora

main()
{
    Parent parent = new Child();
    var x = parent.Foo();

    Child  child  = new Child();
    var y = child .Foo();
}
class Parent
{
    virtual         int Foo() { return 1; }
}
class Child : Parent
{
    sealed override int Foo() { return 2; }
}

W powyższym przykładzie oba .Foo()wywołania ostatecznie wywołują tę samą overridemetodę, która zwraca 2. Po prostu w pierwszym przypadku istnieje wirtualne wyszukiwanie metod w celu znalezienia właściwej metody; to wyszukiwanie wirtualne metody nie jest potrzebne w drugim przypadku, ponieważ ta metoda jest sealed.

Podziękowania dla @Ben, który podał podobny przykład w swojej odpowiedzi .

Zaleta 2: Więcej informacji dla czytelnika

Znajomość dokładnego typu, tj. Child, Zapewnia więcej informacji każdemu, kto czyta kod, ułatwiając zobaczenie, co robi program.

Oczywiście, może to nie ma znaczenia, do rzeczywistego kodu ponieważ oba parent.Foo();i child.Foo();sensu, ale dla kogoś, widząc kodu po raz pierwszy, więcej informacji znajduje się po prostu pomocne.

Dodatkowo, w zależności od środowiska programistycznego, IDE może być w stanie dostarczyć bardziej pomocnych podpowiedzi i metadanych na temat Childniż Parent.

Zaleta 3: Czystszy, bardziej znormalizowany kod

Większość przykładów kodu C #, które ostatnio widziałem var, jest w skrócie skrótem Child.

Parent obj = new Child();  // Sub-optimal
Child  obj = new Child();  // Optimal, but anti-pattern syntax
var    obj = new Child();  // Optimal, clean, patterned syntax "everyone" uses now

Widząc varoświadczenie o braku deklaracji po prostu wygląda; jeśli ma to uzasadnienie sytuacyjne, to świetnie, ale poza tym wygląda anty-wzór.

// Clean:
var foo1 = new Person();
var foo2 = new Job();
var foo3 = new Residence();

// Staggered:
Person foo1 = new Person();
Job foo2 = new Job();
Residence foo3 = new Residence();   

Zaleta 4: Bardziej zmienna logika programu do prototypowania

Pierwsze trzy zalety były duże; ten jest o wiele bardziej sytuacyjny.

Jednak dla osób, które używają kodu tak jak inni używają Excela, stale zmieniamy nasz kod. Może nie musimy wywołać metodę unikalnego Childw tej wersji kodu, ale możemy modyfikowanie lub przerobienie kodu później.

Zaletą silnego systemu typów jest to, że zapewnia nam pewne metadane dotyczące logiki naszego programu, dzięki czemu możliwości są bardziej widoczne. Jest to niezwykle przydatne w prototypowaniu, więc najlepiej zachować go tam, gdzie to możliwe.

Podsumowanie

Używanie Parentbałaganu przy przeciążonej rozdzielczości metody, hamuje niektóre optymalizacje kompilatora, usuwa informacje z czytnika i sprawia, że ​​kod jest brzydszy.

Używanie varjest naprawdę właściwą drogą. Jest szybki, czysty, wzorowany i pomaga kompilatorowi i IDE poprawnie wykonywać swoje zadania.


Ważne : Ta odpowiedź dotyczyParentporównaniaChildw lokalnym zasięgu metody. KwestiaParentvs.Childjest bardzo różna dla typów zwracanych, argumentów i pól klas.


2
Dodałbym, że Parent list = new Child()to nie ma większego sensu. Kod, który bezpośrednio tworzy konkretną instancję obiektu (w przeciwieństwie do pośrednictwa przez fabryki, metody fabryczne itp.) Zawiera doskonałe informacje o tworzonym typie, może wymagać interakcji z konkretnym interfejsem, aby skonfigurować nowo utworzony obiekt itp. Zasięg lokalny nie zależy od elastyczności. Elastyczność osiąga się, gdy ujawnisz ten nowo utworzony obiekt klientowi swojej klasy za pomocą bardziej abstrakcyjnego interfejsu (fabryki i metody fabryczne są doskonałym przykładem)
crizzis

„tl; dr Używanie podrzędności nad rodzicem jest preferowane w zakresie lokalnym. Nie tylko pomaga w czytelności”. - niekoniecznie… jeśli nie użyjesz szczegółów podrzędnych, po prostu doda więcej szczegółów niż to konieczne. „ale konieczne jest również zapewnienie, że rozdzielczość przeciążonej metody działa poprawnie i pomaga w wydajnej kompilacji”. - to zależy w dużej mierze od języka programowania. Istnieje wiele, które nie mają z tego powodu żadnego problemu.
AnoE

1
Czy masz źródła, które Parent p = new Child(); p.doSomething();i Child c = new Child; c.doSomething();mają różne zachowanie? Może to dziwactwo w jakimś języku, ale przypuszczalnie obiekt kontroluje zachowanie, a nie odniesienie. To jest piękno dziedzictwa! Tak długo, jak możesz zaufać implementacjom potomnym w celu wypełnienia kontraktu implementacji macierzystej, możesz iść.
corsiKa

@corsiKa Tak, jest to możliwe na kilka sposobów. Dwa szybkie przykłady to przypadki, w których podpisy metod są nadpisywane, np. newSłowem kluczowym w C # i gdy istnieje wiele definicji, np. Z wielokrotnym dziedziczeniem w C ++ .
Nat

@corsiKa Tylko szybki trzeci przykład (ponieważ uważam, że jest to dobry kontrast między C ++ i C #), C # ma problem podobny do C ++, w którym można również uzyskać to zachowanie z metod jawnego interfejsu . To zabawne, ponieważ języki takie jak C # używają interfejsów zamiast częściowego dziedziczenia wielokrotnego w celu uniknięcia tych problemów, ale przyjmując interfejsy, nadal mają część problemu - ale nie jest tak źle, ponieważ w tym scenariuszu dotyczy to tylko do kiedy Parentjest interface.
Nat

0

Biorąc pod uwagę parentType foo = new childType1();, kod będzie ograniczony do używania metod typu nadrzędnego foo, ale będzie mógł przechowywać w fooreferencji do dowolnego obiektu, którego typ pochodzi parent- nie tylko od instancji childType1. Jeśli nowa childType1instancja jest jedyną rzeczą, do której fookiedykolwiek będzie się odwoływać, może lepiej zadeklarować footyp jako childType1. Z drugiej strony, jeśli foozajdzie potrzeba przechowywania odniesień do innych typów, zadeklarowanie go jako parentTypeumożliwi to.


0

Sugeruję użycie

Child x = new Child();

zamiast

Parent x = new Child();

Ten ostatni traci informacje, podczas gdy pierwszy nie.

Możesz zrobić wszystko z tym pierwszym, co możesz zrobić z tym drugim, ale nie odwrotnie.


Aby rozwinąć wskaźnik dalej, pozwól nam mieć wiele poziomów dziedziczenia.

Base-> DerivedL1->DerivedL2

I chcesz zainicjować wynik new DerivedL2()zmiennej. Użycie typu podstawowego pozostawia opcję użycia Baselub DerivedL1.

Możesz użyć

Base x = new DerivedL2();

lub

DerivedL1 x = new DerivedL2();

Nie widzę żadnego logicznego sposobu na preferowanie jednego nad drugim.

Jeśli użyjesz

DerivedL2 x = new DerivedL2();

nie ma się o co kłócić.


3
Być w stanie robić mniej, jeśli nie trzeba robić więcej, to dobra rzecz, nie?
Peter

1
@Peter, zdolność do robienia mniej nie jest ograniczona. Zawsze jest to możliwe. Jeśli zdolność do robienia więcej jest ograniczona, może to być ból.
R Sahu

Ale utrata informacji jest czasem dobrą rzeczą. Jeśli chodzi o twoją hierarchię, większość ludzi prawdopodobnie zagłosowałaby Base(zakładając, że to działa). Wolisz najbardziej szczegółowe, AFAIK większość preferuje najmniej szczegółowe. Powiedziałbym, że w przypadku zmiennych lokalnych nie ma to większego znaczenia.
maaartinus

@maaartinus, jestem zaskoczony, że większość użytkowników woli tracić informacje. Nie widzę korzyści z utraty informacji tak wyraźnie, jak inni.
R Sahu

Kiedy deklarujesz, Base x = new DerivedL2();jesteś najbardziej ograniczony, a to pozwala ci na włączenie innego (wnuczkiego) dziecka przy minimalnym wysiłku. Co więcej, od razu widać, że jest to możliwe. +++ Czy zgadzamy się, że void f(Base)jest to lepsze niż void f(DerivedL2)?
maaartinus

0

Sugerowałbym zawsze zwracanie lub przechowywanie zmiennych jako najbardziej specyficzny typ, ale przyjmowanie parametrów jako najszerszych typów.

Na przykład

<K, V> LinkedHashMap<K, V> keyValuePairsToMap(List<K> keys, List<V> values) {
   //...
}

Parametry są List<T>bardzo ogólne. ArrayList<T>, LinkedList<T>I wszyscy inni mogą zostać zaakceptowane przez tą metodą.

Co ważne, typem zwrotu jest LinkedHashMap<K, V>nie Map<K, V>. Jeśli ktoś chce przypisać wynik tej metody do a Map<K, V>, może to zrobić. Wyraźnie informuje, że ten wynik to więcej niż mapa, ale mapa ze zdefiniowanym porządkiem.

Załóżmy, że ta metoda zwróciła Map<K, V>. Jeśli dzwoniący chciałby LinkedHashMap<K, V>, musiałby sprawdzić typ, obsłużyć i obsługiwać błędy, co jest naprawdę kłopotliwe.


Ta sama logika, która zmusza cię do zaakceptowania szerokiego typu jako parametru, powinna mieć również zastosowanie do typu zwracanego. Jeśli celem funkcji jest odwzorowanie kluczy na wartości, powinna ona zwrócić Mapnie LinkedHashMap- chciałbyś tylko zwrócić funkcję LinkedHashMapdla funkcji podobnej keyValuePairsToFifoMaplub coś w tym rodzaju. Szersze jest lepsze, dopóki nie masz konkretnego powodu, aby tego nie robić.
corsiKa

@corsiKa Hmm Zgadzam się, że to lepsza nazwa, ale to tylko jeden przykład. Nie zgadzam się, że (ogólnie) poszerzenie rodzaju zwrotu jest lepsze. Twoje sztucznie usuwane informacje o typie, bez żadnego uzasadnienia. Jeśli wyobrażasz sobie, że użytkownik będzie musiał rzucić z powrotem szeroki typ, który mu podałeś, to zrobiłeś mu krzywdę.
Alexander - Przywróć Monikę

„bez żadnego uzasadnienia” - ale jest uzasadnienie! Jeśli uda mi się zwrócić szerszy typ, ułatwi to refaktoryzację w dół drogi. Jeśli ktoś POTRZEBUJE określonego typu z powodu pewnych zachowań, jestem za zwrócenie odpowiedniego typu. Ale nie chcę być zamknięty w konkretny typ, jeśli nie muszę. Chciałbym mieć swobodę zmiany specyfiki mojej metody bez szkody dla tych, którzy ją zużywają, a jeśli ją zmienię, powiedzmy LinkedHashMapz TreeMapjakiegokolwiek powodu, teraz mam wiele rzeczy do zmiany.
corsiKa

Właściwie zgadzam się z tym aż do ostatniego zdania. Zmiana z LinkedHashMapna TreeMapnie jest trywialną zmianą, taką jak zmiana z ArrayListna LinkedList. To zmiana o znaczeniu semantycznym. W takim przypadku chcesz, aby kompilator zgłosił błąd, aby zmusić odbiorców wartości zwracanej do zauważenia zmiany i podjęcia odpowiednich działań.
Alexander - Przywróć Monikę

Ale może to być trywialna zmiana, jeśli wszystko, na czym ci zależy, to zachowanie mapy (co, jeśli możesz, powinieneś.) Jeśli kontrakt to „dostaniesz mapę wartości kluczowej”, a kolejność nie ma znaczenia (co jest prawdą dla większość map), nie ma znaczenia, czy jest to mapa drzewa, czy mapa skrótu. Na przykład Thread#getAllStackTraces()- zwraca Map<Thread,StackTraceElement[]>zamiast HashMapspecyficznie, więc w dół drogi mogą zmienić go na dowolny rodzaj, Mapjaki chcą.
corsiKa
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.