Dlaczego java.util.ArrayList pozwala na dodanie wartości null?

41

Zastanawiam się, dlaczego java.util.ArrayListpozwala to dodać null. Czy jest jakiś przypadek, w którym chciałbym dodać nulldo ArrayList?

Zadaję to pytanie, ponieważ w projekcie mieliśmy błąd, gdzie niektóre kod został dodając nulldo ArrayListi trudno było dostrzec, gdzie był błąd. Oczywiście NullPointerExceptionwyrzucono a, ale dopóki inny kod nie próbował uzyskać dostępu do elementu. Problem polegał na tym, jak zlokalizować kod, który dodał nullobiekt. Byłoby łatwiej, gdyby ArrayListwrzucił wyjątek w kodzie, w którym dodawano elementy.

Alfredo Osorio
źródło
12
Projekt Guava ma całkiem interesującą stronę na ten temat (nie pozwalają nullna większość swoich kolekcji).
Joachim Sauer
6
Uważam, że podane tutaj odpowiedzi dobrze obejmują to pytanie. Może jedną rzeczą, o której należy wspomnieć, jest: nie bierz wszystkiego, co jest w JDK, za uświęcone i idealne, a następnie walnij w głowę, próbując zrozumieć, dlaczego jest tak „idealny”. Niektóre rzeczy to (szczere, IMHO) błędy, które pozostały tam ze względu na kompatybilność wsteczną i to wszystko. Nawet twórcy Javy przyznają to, wystarczy przeczytać książki Joshua Blocha, aby zobaczyć jego krytykę niektórych interfejsów API Java. W każdym razie twoje pytanie sprowadza się do pogody, nie ma bardziej eleganckiego sposobu na złapanie NPE w Javie. Odpowiedź brzmi: nie, ale powinna być.
Shivan smok
2
Czy możesz podać więcej informacji o tym, dlaczego nie powinno to być dozwolone? Jeśli jest to tylko kwestia gustu, należy preferować mniej restrykcyjne.
Mare Infinitus
Prosta odpowiedź jest taka, że nulljest to domyślny sposób reprezentowania brakujących danych w Javie, czy ci się to podoba, czy nie. W rzeczywistości wielu ludziom się to nie podoba i argumentuje to za funkcjonalnymi rzeczami w stylu programowania. Najbardziej pozytywna odpowiedź nie ma sensu / nie oddaje istoty problemu.
Xji 12.04.16
@JIXiang Upraszczasz, co nulljest. „Brak” to tylko jedna z kilku potencjalnych interpretacji null. Inne prawidłowe interpretacje mogą być „Nieznane”, „Nie dotyczy” lub „Niezainicjowane”. To, co nullreprezentuje, zależy od aplikacji. Jak powiedzieliby społeczność Python: „W obliczu dwuznaczności odmów pokusy zgadywania”. Odmowa trzymania wartości null w pojemniku, który jest w stanie to zrobić, byłaby tylko - zgadnij.
riwalk

Odpowiedzi:

34

Ta decyzja projektowa wydaje się wynikać głównie z nazewnictwa.

Name ArrayList sugeruje czytelnikowi funkcjonalność podobną do tablic - i jest rzeczą naturalną, że projektanci Java Collections Framework oczekują, że zdecydowana większość użytkowników API będzie polegać na tym, że działa podobnie do tablic.

W szczególności dotyczy to traktowania zerowych elementów. Użytkownik interfejsu API, wiedząc, że poniżej działa OK:

array[0] = null; // NPE won't happen here

byłby bardzo zaskoczony , aby dowiedzieć się, czy podobny kod ArrayList rzucał NPE:

arrayList.set(0, null); // NPE => WTF?

Rozumowanie jak wyżej przedstawiono w samouczku JCF, podkreślając punkty, które sugerują ścisłe podobieństwo między ArrayList i zwykłymi tablicami:

ArrayList ... oferuje stały dostęp do pozycji i jest po prostu szybki ...

Jeśli chcesz, aby implementacja listy nie zezwalała na wartości null, lepiej byłoby ją wywołać tak NonNullableArrayListlub coś takiego, aby uniknąć mylących użytkowników interfejsu API.


Na marginesie znajduje się pomocnicza dyskusja w komentarzach poniżej, wraz z dodatkowymi uwagami na poparcie przedstawionego tutaj rozumowania.

komar
źródło
12
Wyjaśnienie to nie jest przekonujące, ponieważ obsługuje LinkedList równieżnull wpisy listy.
Stephen C
12
Tak ... ale prostszym i (IMO) bardziej prawdopodobnym wyjaśnieniem jest to, że dopuszczanie nullwpisów jest przydatne w wielu przypadkach.
Stephen C
7
ArrayList nie jest tak nazywany, ponieważ naśladuje tablicę. Nazywa się tak, ponieważ jest to lista zaimplementowana jako tablica. Podobnie jak TreeMap nie zachowuje się jak Drzewo.
Florian F
11
Ta odpowiedź, IMO, jest po prostu błędna. Nulls są dozwolone, ponieważ w Javie, na lepsze lub gorsze, (IMO, gorzej) null jest często używany do reprezentowania niezainicjowanych lub brakujących wartości. Jak uzyskał najwięcej głosów i czek „akceptuj”? Poważnie, naprawdę tracę obecnie wiarę w StackOverflow.
user949300,
8
Ta odpowiedź jest po prostu błędna. To tylko nieobsługiwane zgadywanie, czy wartości zerowe są dozwolone w implementacji listy. Oto Roundup kolekcji Java pozwalających / null zabronienie; zauważ, że nie ma to nic wspólnego z podobieństwem do tablic.
Andres F.,
32

Wartość null może być prawidłową wartością dla elementu listy. Powiedz, że twoja lista zawiera elementy, które reprezentują niektóre opcjonalne dane o liście użytkowników i jest przechowywana w tej samej kolejności, co użytkownicy. Jeśli dodatkowe dane zostaną wypełnione, twoja lista będzie zawierać dodatkowe dane, w przeciwnym razie miejsce odpowiadające użytkownikowi będzie puste. (Jestem pewien, że są lepsze przykłady, ale masz pomysł)

jeśli nie chcesz zezwalać na dodawanie wartości null, możesz owinąć listę tablic własnym opakowaniem, które powstało po dodaniu wartości null.

Sam Holder
źródło
2
Wydaje się to złym przykładem tego, dlaczego wartości null powinny być dozwolone - istnieją lepsze sposoby reprezentowania opcjonalnych danych niż stosowanie wartości null na liście.
casablanca
@casablanca tak, całkowicie się zgadzam, to nie jest świetny przykład.
Sam Holder
Nie mogę znaleźć dobrego powodu, aby ArrayList zawierała wartości null, inne niż złe niespodzianki dla następnego programisty, który „odkrył” go: /
AndreasScheinert
7
@casablanca Chociaż zgadzam się z tobą, że należy unikać wartości zerowych dla reprezentowania opcjonalnych danych, w języku Java, dla lepszego lub gorszego, to jest „tradycyjne”.
user949300,
2
Solidny przypadek użycia: chcesz przekazać parametry do jakiejś funkcji dynamicznej jako listę. Masz kilka funkcji, każda z innym zestawem parametrów, więc potrzebujesz dynamicznie dopasowanej tablicy i zawsze możesz przekazać null jako prawidłowy argument funkcji!
Falco,
19

ArrayListzezwala na wartość zerową. To jest celowe. Z javadoc :

„[ArrayList jest] implementacją tablicy List o zmiennym rozmiarze. Implementuje wszystkie opcjonalne operacje na liście i zezwala na wszystkie elementy, w tym null .”

Odpowiedź na „dlaczego” jest taka, że ​​jeśli nie, ArrayList nie byłby użyteczny w przypadkach, w których konieczne jest umieszczenie nulllisty na liście. W przeciwieństwie do tego, możesz uniemożliwić ArrayListowi zawieranie wartości null, albo testując wartości przed ich dodaniem, albo używając opakowania, które temu zapobiega.

Czy jest jakiś przypadek, w którym chciałbym dodać wartość null do ArrayList?

Oczywiście każdy przypadek nullma wyraźne znaczenie. Na przykład może to oznaczać, że wartość na danym miejscu na liście nie została zainicjowana ani dostarczona.

Byłoby łatwiej, gdyby ArrayList zgłosił wyjątek w kodzie, w którym dodawane były elementy.

Możesz łatwo zaimplementować to zachowanie, tworząc klasę opakowania. Jednak nie jest to zachowanie, którego potrzebuje większość programistów / aplikacji.

Stephen C.
źródło
8

Czy jest jakiś przypadek, w którym chciałbym dodać wartość null do ArrayList?

Jasne, a co z wstępną alokacją? Chcesz ArrayListczegoś, czego nie możesz jeszcze utworzyć, ponieważ nie masz wystarczającej ilości informacji. Tylko dlatego, że nie możesz znaleźć powodu, dla którego ktoś może chcieć coś zrobić, nie czyni tego złym pomysłem. Cokolwiek. (Teraz jestem pewien, że ktoś przyjdzie i powie, że zamiast tego powinieneś mieć puste obiekty, które spełniają pewien wzorzec, o którym czytają na jakimś niejasnym blogu i że programiści powinni naprawdę móc pisać programy bez użycia ifinstrukcji, bla bla.)

Jeśli macie umowę, która nigdy nie powinna znajdować się nullw kontenerze, to od was zależy, czy kontrakt zostanie dotrzymany, prawdopodobnie najlepiej przez zapewnienie. Prawdopodobnie zajęłoby Ci to maksymalnie 10 linii kodu. Java sprawia, że ​​jest to niezwykle łatwe. JDK nie może czytać w twoich myślach.

James
źródło
1
Twój argument przedalokacji jest nieprawidłowy, ponieważ jest on już wbudowany w ArrayList za pomocą ensureCapacity(int minCapacity)metody i ArrayList(int initialCapacity)konstruktora.
Philipp
7
@Philipp Jakie wartości umieści w tej przestrzeni?
James
Oczywistym wyborem jest wstawienie nullwstępnie przydzielonych (ale jak dotąd nieużywanych ) pozycji ArrayList; ale możemy na to pozwolić ensureCapacity, a jednocześnie nie pozwolić na inne funkcje, takie jak setto. Powody podane gdzie indziej wydają się silniejsze.
David K
@DavidK: Sensowne jest stwierdzenie, że setelement powinien mieć możliwość dowolnej wartości, która może zostać zwrócona get. Nawet jeśli normalna próba getelementu, dla którego zostało przydzielone miejsce, ale nigdy nie zostało napisane, powinna rzucić wyjątek, a nie powrót null, nadal przydatne byłoby posiadanie pary metod, które pozwoliłyby list1.setOrEraseIfNull(index, list2.getOrReturnNull(index))zamiast wymagaćif (list2.valueSet(index)) list1.set(index, list2.get(index)); else list1.unset(index);
supercat
1
@DavidK: Próba odczytania wpisu, który został przydzielony, ale jeszcze nie został napisany, musi dać wartość null lub zgłosić wyjątek; łatwiej jest zwrócić wartość zero.
supercat
4

Wydaje się, że jest to bardziej filozoficzne pytanie (programowe).

ArrayList ponieważ klasa użyteczności ma być pomocna w szerokim kontekście możliwych przypadków użycia.

W przeciwieństwie do ukrytego twierdzenia, że ​​należy odradzać przyjmowanie wartości null jako wartości prawidłowej, istnieje wiele przykładów, w których wartość null jest całkowicie legalna.

Najważniejszym powodem jest to, że nulljest to do not knowodpowiednik dowolnego typu odniesienia, w tym typów ram. Oznacza to, że w żadnym wypadku nie można zastąpić wartości null bardziej płynną wersją nothingwartości.

Załóżmy więc, że przechowujesz liczby całkowite 1, 3, 7 na liście arraylist w odpowiednim indeksie: Z powodu pewnych obliczeń chcesz uzyskać element o indeksie 5, więc tablica powinna zwrócić: „brak przechowywanej wartości”. Można to osiągnąć przez zwrócenie wartości null lub NullObject . W większości przypadków zwracanie wbudowanej wartości null jest wystarczająco wyraziste. Po wywołaniu metody, która może zwrócić wartość null i użyciu jej wartości zwracanej, sprawdzanie wartości zwracanej względem wartości null jest dość powszechne we współczesnym kodzie.

Mare Infinitus
źródło
4

Wyrok

File f = null;

jest ważny i użyteczny (nie sądzę, że muszę wyjaśniać dlaczego). Przydatne jest również posiadanie zbioru plików, z których niektóre mogą mieć wartość NULL.

List<File> files = new ArrayList<File>();
// use my collection of files
// ....
// not using this one anymore:
files.set(3, null);

Przydatność kolekcji, które mogą zawierać obiekty zerowe, wynika bezpośrednio z użyteczności obiektów, które mogą być zerowe. To jest naprawdę tak proste.

kod x
źródło
2

Łatwiej jest zaakceptować wszystko, niż być restrykcyjnym, a następnie spróbować otworzyć swój projekt po fakcie.

Na przykład, co jeśli jeśli oracle / sun podają tylko NonNullableArrayList, ale chcesz mieć możliwość dodania wartości Null do swojej listy. Jak byś to zrobił? Prawdopodobnie będziesz musiał utworzyć zupełnie inny obiekt, nie możesz użyć rozszerzenia NonNullableArrayList. Zamiast tego, jeśli masz ArrayList, która bierze wszystko, możesz łatwo ją rozszerzyć i zastąpić dodawanie, w którym nie akceptuje wartości zerowych.

NiteRain
źródło
2
Nie zgadzam się Dopuszczenie zbyt dużej ilości jest trudniejsze do skorygowania, gdy błąd zostanie powszechnie użyty. Jeśli projektanci języka dopuściliby wartości zerowe na późniejszym etapie, nie spowodowałoby to uszkodzenia istniejących programów, ale nie jest odwrotnie.
Andres F.,
2
Ale zgadzam się. Chodzi o maksymalizację funkcjonalności. Możesz użyć listy, która przyjmuje wartości zerowe, nawet jeśli ich nie potrzebujesz. Nie możesz użyć listy, która odrzuca wartości null, jeśli potrzebujesz wartości null.
Florian F
-1

Przydatność null?

Bardzo, jeśli idziesz z listą list, aby modelować płytkę 2D z różnymi pozycjami. Połowa z tych pozycji może być pusta (w losowych współrzędnych), podczas gdy wypełnione nie reprezentują prostej int lub String, ale są reprezentowane przez złożony obiekt. Jeśli chcesz zachować informacje o położeniu, musisz wypełnić puste miejsca wartością null, aby twoja lista.get (x) .get (y)nie prowadzi do przykrych niespodzianek. Aby przeciwdziałać zerowym wyjątkom, możesz sprawdzić wartość null (bardzo popularna) lub możesz użyć Opcjonalnego (co wydaje mi się przydatne w tym przypadku). Alternatywą byłoby wypełnienie pustych miejsc „śmieciowymi” obiektami, które mogą prowadzić do wszelkiego rodzaju bałaganu na drodze. Jeśli Twój test zerowy zawiedzie lub zostanie gdzieś zapomniany, Java poinformuje Cię o tym. Z drugiej strony obiekt zastępczy „śmieci”, który nie jest odpowiednio sprawdzony, może przejść niezauważony.

Nanobody
źródło