Co zwrócić zamiast null?

0

witam,

mam taki mały problem, mianowicie nie wiem co powinienem zwrócić z funkcji jeśli nie wykona się poprawnie. Mam jakąś swoją klasę Entry i do tego funkcję get(), która zwraca Entry. Co powinienem zwrócić, gdy nie ma takiego Entry, które get mogłaby zwrócić (na przykład nie ma żadnego)? Próbowałem null ale i tak wyskakuje błąd nullPointerExeption. Nie doszedłem jeszcze do wyjątków ale chodziłoby mi o to, aby problem był rozwiązany już na poziomie funkcji czyli przy zwracaniu, a nie żeby za każdym razem wywołując funkcje trzeba było sprawdzać wyjątek. Jest to możliwe?

0

moze dodaj do tej klasy pole ktore bedzie odpowiadalo za stan Klasy, czy zostala poprawnie wykonana, i np jak niepoprawnie sie wykona to zwrocisz Entry z tym polem jako false albo -1 i pozniej sobie juz w programie sprawdz stan tego pola, ale tu az sie prosi o obsluge wyjatkow

0

Wg mnie dobrze jest gdy wylatuje null z geta, gdy nie ma tam nic. Twoim zadaniem jest za to później sprawdzić czy nie mamy nulla, a jeśli tak to trzeba to obsłużyć (ale nie generując i łapiąc wyjątek).

Ostatecznie możesz zwrócić nowy defaultowy obiekt, ale to zły pomysł.

3

Uwaga, następujący post będzie o podejściu funkcjonalnym :]

Dla przykładu w Haskellu w ogóle nie ma czegoś takiego jak null i zamiast tego używa się monady Maybe[Typ], która ma dwa warianty Just(cośtam) i Nothing. Klasa (w sumie też monada) Option w Scali jest w zasadzie tym samym. Option jest też w bibliotece Functional Java: http://functionaljava.googlecode.com/svn/artifacts/3.0/javadoc/fj/data/Option.html

Na stronie http://learnyouahaskell.com/a-fistful-of-monads#do-notation jest pokazane jak monada Maybe wraz z konstrukcją do może znacznie uprościć kod, w którym używa się wiele razy funkcji mogących zwrócić Nothing.

  1. Bez wykorzystywania właściwości monady:
routine :: Maybe Pole  
routine =   
    case Just (0,0) of   
        Nothing -> Nothing  
        Just start -> case landLeft 2 start of  
            Nothing -> Nothing  
            Just first -> case landRight 2 first of  
                Nothing -> Nothing  
                Just second -> landLeft 1 second  
  1. Wykorzystując właściwości monady i notacji do:
routine :: Maybe Pole  
routine = do  
    start <- return (0,0)  
    first <- landLeft 2 start  
    Nothing  
    second <- landRight 2 first  
    landLeft 1 second  

Dopiszę jeszcze, że w Scali for jest mniej więcej odpowiednikiem notacji do z Haskella i też można takie cuda robić :)

A wracając do podejścia niefunkcjonalnego - jest coś takiego jak wzorzec Pusty Obiekt/ Null Object Pattern. Chodzi tu o to, że robimy specjalną podklasę klasy bazowej z tylko jednym obiektem (tzn singleton). Załóżmy, że mamy klasę Krzesło. Aby wprowadzić wzorzec Pusty Obiekty, tworzymy klasę PusteKrzesło jako singleton, dziedziczącą po Krzesło i mającą jakieś tam domyślne implementacje metod. Gdybyś wprowadził wzorzec Pusty Obiekt w całej aplikacji, to metody w pustych obiektach mogłyby zwracać kolejne puste obiekty i dzięki temu, przy ostrożnym projektowaniu aplikacji, miałbyś podobny efekt jak z monadą Maybe - zmalałaby ilość sprawdzeń czy coś jest Pustym Obiektem/ nullem.

0
Wibowit napisał(a):

Gdybyś wprowadził wzorzec Pusty Obiekt w całej aplikacji, to metody w pustych obiektach mogłyby zwracać kolejne puste obiekty i dzięki temu, przy ostrożnym projektowaniu aplikacji, miałbyś podobny efekt jak z monadą Maybe - zmalałaby ilość sprawdzeń czy coś jest Pustym Obiektem/ nullem.

Uwaga, poważne pytanie: napisałeś kiedyś całą bardziej skomplikowaną aplikację w ten sposób? Da się tak napisać całą aplikacje? Ja osobiście trochę powątpiewam, ponieważ mój kod to tylko cząstka, jest pełno innego kodu ktory w ten sposób pisany nie jest (frameworki, liby), i trzebaby tworzyć całe mnóstwo abstrakcji żeby cała aplikacja używała Null Object. Przynajmniej ja to tak widzę, jako zaawansowany noob programowania funkcyjnego, i myślę że to dość trudne zadanie? Hm?

0

Nie widziałem całych aplikacji opartych o wzorzec Pusty Obiekt, ale z drugiej strony czasem da się spokojnie użyć tego wzorca lokalnie, np w strukturze danych typu drzewo - zamiast ustawiać nulle w liściach, można dawać referencje do pustego obiektu. W sumie to przesadziłem z tym przepisywaniem całej aplikacji - trzeba by każdy przypadek przemyśleć oddzielnie.

Natomiast jeśli chodzi o monady typu Option, to są one używane dość często w Scali. Na pewno łatwiej się korzysta z Option niż wymyśla pusty obiekt. Pusty obiekt narzuca też wspólną logikę dla pustych obiektów na całą aplikację, a przy monadzie Option można sobie dowolnie tworzyć logikę w dowolnym miejscu gdzie używamy tej Option. Tylko, że obsługa Option nie byłaby raczej jakoś dużo lepsza w Javie niż zabawa z nullami - nie ma ani specjalnej notacji, ani zwięzłych lambd (przynajmniej jak na razie).

1

ale i tak wyskakuje błąd nullPointerExeption

Wyskakuje zza krzaka i strzela z karabinu?

0

Null Object (pusty obiekt), to taka cwańsza wersja nulla. Na przykład dla tablicy dowolnych obiektów może to być new Object[0], który jest prawidłowym obiektem o długości zero. Podobnie może działać pusta lista czy choćby iterator do takiego obiektu (hasNext zawsze zwraca false, a next zawsze generuje NoSuchElementException). Praktycznie wszystkie operacje poprawne na dowolnym obiekcie są również poprawne na null obiekcie - mimo iż taki niczego nie zwraca lub niczego nie zmienia. Na przykład iteracja przy pomocy foreach po null liście (lub null tablicy) jest zawsze poprawna, a że pętla nie zakręci się ani razu z powodu zerowej długości, to inna sprawa. Nie trzeba pisać stosów ifów tylko po to aby odseparować jakieś dziwne przypadki (bo w końcu można się w nich pogubić). Mi bardzo się to przydaje do pisania kodu szablonowego.
Robienie null obiektów jest trochę podobne do pisania stuba - w takim wypadku też robi się prosty obiekt, który prawie nic nie może lub nie potrafi - i o tym informuje.

0

Jeżeli uważasz, że brak danego elementu jest rzeczą normalną to powinieneś obsłużyć jego brak sprawdzając czy jest null czy nie. Bo tak samo musiałbyś sprawdzać jakąś tam zmienną, która by Ci określała czy obiekt został pobrany. Po co sobie komplikować?
Jeżeli taka sytuacja wynika z poważnego błędu programu (jak na przykład niespójność w bazie danych), to tutaj wyjątek jest jak najbardziej na miejscu.

0

Nie trzeba pisać stosów ifów tylko po to aby odseparować jakieś dziwne przypadki (bo w końcu można się w nich pogubić). Mi bardzo się to przydaje do pisania kodu szablonowego.

To ze referencja do listy jest nullem nie oznacza, ze lista jest pusta. Tylko czasami chcesz osiagnac takie samo zachowanie w tych dwoch roznych przypadkach.
Teraz jak widze kod, ktory odwoluje sie do obiektu i nie sprawdza czy ten jest nullem to wiem, ze pewnie ktos nie pomyslal i to jest najprawdopodobniej blad do poprawy.
Jak ktos odwoluje sie do null obiektu to nie mam zielonego pojecia czy to celowe zachowanie czy moze kod sie wywali kilka warstw nizej ze wzgledu na blad w logice (bo ktos nie pomyslal rozroznieniu dwoch przypadkow - lista niezainicjowana i lista pusta). Taki blad bedzie trudniej zdebugowac niz prosty NullPointerException.
IMHO null obiekty maja zastosowanie w ograniczonej liczbie przypadkow (np. struktury danych, ktore dosc latwo ogarnac w przeciwienstwie do kodu z duza iloscia zaleznosci).

0

No właśnie. Jeden programista będzie zwracał nulla, tam gdzie inny zwraca pustą kolekcję i wtedy, żeby być pewnym trzeba robić rzeczy w stylu:

if (kolekcja != null & !kolekcja.isEmpty())

W Haskellu nie ma nulla i nikt z tego powodu nie płacze. Albo się używa pustych obiektów, albo opakowuje wyniki w monadę Maybe. Jest niby wartość undefined ale nie da się jej wprost używać, nawet porównanie czegoś z undefined skutkuje wywaleniem programu.

0

Rozumiem, że Haskell etc. jest cool, ale co w wypadku kiedy korzystamy z języka imperatywnego?

W języku imperatywnym też można zaimplementować wzorzec Pusty Obiekt i monadę Maybe. Podawałem już link do libki Functional Java, w Javie 8 będą zwięzłe lambdy w końcu i kod funkcyjny w Javie wreszcie będzie wyglądał przyzwoicie.

Poza tym, może średni argument, no ale jakiś - twórca null referencji (a także QuickSorta) twierdzi, że wynalezienie jej było jego największym błędem: http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare

0

Dzięki za wszystkie odpowiedzi, w rezultacie póki co skorzystam z wyjątków. Mam takie pytanie już co do kodu. Oto i on:

 
package crossword.board;

public class BoardCell {
    public boolean position[] = new boolean[3];
    public boolean direction[] = new boolean[2];
    public boolean enabled;
    private String letter;
    public void enable() {
        enabled = true;
    }
    public void disable() {
        enabled = false;
    }
    public void horiz() {
        direction[0] = true;
    }
    public void vert() {
        direction[1] = true;
    }
    public void start() {
        position[0] = true;
    }
    public void inner() {
        position[1] = true;
    }
    public void end() {
        position[2] = true;
    }
    public void setContent(String content) {
        if (content.length() > 0 && content.length() < 2)
            letter = content;
    }
    public String getContent() {
        return letter;
    }
}

I druga klasa:

 
package crossword.board;

import java.util.*;

public class Board {
    private BoardCell[][] board;
    public Board(int x, int y) {
        board = new BoardCell[x][y];
    }
    public int getWidth() {
        return board.length;
    }
    public int getHeight() {
        if (getWidth() > 0)
            return board[0].length;
        return 0;
    }
    public BoardCell getCell(int x, int y) {
        if (x < getWidth() && y < getHeight())
            return board[x][y];
        return null;
    }
    public void setCell(int x, int y, BoardCell c) {
        if (x <= getWidth() && y <= getHeight()) {
            board[x][y] = c;
        }
    }
    public LinkedList<BoardCell> getStartCells() {
        LinkedList<BoardCell> temp = new LinkedList<BoardCell>();
        for (int i = 0; i < getWidth(); ++i) {
            for (int j = 0; j < getHeight(); ++j) {
                if (board[i][j].position[0] == true)
                    temp.add(board[i][j]);
            }
        }
        return temp;
    }
    public String createPattern(int fromx, int fromy, int tox, int toy) {
        String temp = new String();
        if (fromx > -1 && fromy > -1 && tox < getWidth() && toy < getHeight()) {
            if (fromx == tox) {
                for (int i = fromy; i <= toy; ++i) {
                    if (board[fromx][i].enabled == true)
                        temp += getCell(fromx, i);
                    else
                        temp += ".";
                }
                return temp;
            }
            if (fromy == toy) {
                for (int i = fromx; i <= tox; ++i) {
                    if (board[i][fromy].enabled == true)
```java

``` temp += getCell(i, fromy); else temp += "."; } return temp; } } return null; } } ```

Nie mogę dojść dlaczego, ale w funkcjach getStartCells i createPattern, kiedy sprawdzam atrybut komórki tablicy board (w funkcji getStartCells sprawdzam position[0] a w createPattern enabled) i jest ona fałszywa to również otrzymuję błąd NullPointerExeption. Faktycznie mam funkcję, która może zwrócić null i jest uzywana (getCell()) a nie obsługuję jej wyjątkiem, ale alalizująć kod linijka po linijce wyszło że kompilator nigty tam nie dochodzi. Ma ktoś jakiś pomysł? Tutaj wrzucę przykładowego maina:

 
package crossword.dictionary;

import crossword.board.*;
import java.util.*;

public class Test {
    public static void main(String[] args) {
        Board board = new Board(3,2);
        System.err.println(board.getWidth() + " " + board.getHeight());
        BoardCell cell = new BoardCell();
        cell.enable();
        cell.start();
        cell.vert();
        cell.setContent("e");
        BoardCell cell1 = new BoardCell();
        cell1.enable();
        cell1.start();
        cell1.horiz(); 
        cell1.setContent("a");
        board.setCell(0, 0, cell);
        board.setCell(1, 1, cell1);
        System.out.println(board.getCell(0, 0).getContent());
        LinkedList<BoardCell> list = new LinkedList<BoardCell>();
        list = board.getStartCells();
        Iterator<BoardCell> itr = list.iterator();
        while (itr.hasNext())
            System.err.print(itr.next().getContent());
        System.out.print(board.createPattern(0, 0, 2, 0));
    }
}

Dzieki z góry za wszystkie odpowiedzi :)

1

Weź pod uwagę, że board[x][y] może być null nawet jeżeli x i y mieszczą się w zakresie. Metoda getCell jest źle napisana bo zamiast zwracać null, który nie wiadomo gdzie rąbnie powinna wywalać wyjątek NullPointerException dla poprawnych x, y i board[x][y] == null oraz wyjątek np. InvalidArgumentException gdy x i y wyjadą poza zakres getWidth i getHeight.
Dlatego dostępie do tablicy board powinieneś używać tylko getCell i setCell, a nie dobierać się do komórek bezpośrednio. Ja bym natomiast zrobił prywatną getCell do użytku wewnętrznego oraz publiczną, która zwyczajnie wywołuje tę prywatną. Dzięki temu można by używać tej prywatnej nawet w konstruktorze.

Krótko mówiąc wypełniasz tylko dwie komórki board (pozostałe są null), a przeszukujesz wszystkie w poszukiwaniu właściwości obiektu, który istnieje tylko w dwóch miejscach.

0

Myślałem że tworząc tablicę board[][] dla każdej komórki wywoła się konstruktor BoardCell(), chodziło mi o to, żeby już od początku tablica była zapełniona, z tym że z wszystkimi wartościami false. Dzięki temu to, że String będzie pusty nic nie zmieni bo jeśli wszystkie będą na false to żadna funkcja tego Stringa nie użyje. Co powinienem zrobić inaczej żeby tak zrobic?

0

W Javie tablica obiektów to w rzeczywistości tablica referencji i dlatego domyślnie referencje są ustawiane na null. W C++ jak alokujesz tablicę wskaźników to też raczej nie są tworzone obiekty.

Skrótowo:
Wszystko co dziedziczy po Object (można to sprawdzić przez operator instanceof) jest dostępne przez referencję, cała reszta (czyli prymitywy, takie jak int, byte, short, char, long, float, double) przez wartość.

0

Chyba troszkę nie rozumiem. Coś takiego : BoardCell cell = new BoardCell(); referencja już wskazuje na jakiś obiekt a cos takiego: board = new BoardCell[x][y]; referencje wskazują na nulle? To znaczy, że w wypadku tablicy nie są wywoływane konstruktory?

0

Taki kod w Javie:

Object[] tablica = new Object[N];

Jest odpowiednikiem mniej więcej takiego z C++:

typ **tablica = new typ*[N];
0
kidziman napisał(a):

Chyba troszkę nie rozumiem. Coś takiego : BoardCell cell = new BoardCell(); referencja już wskazuje na jakiś obiekt a cos takiego: board = new BoardCell[x][y]; referencje wskazują na nulle? To znaczy, że w wypadku tablicy nie są wywoływane konstruktory?

W Javie podobnie jak w C/C++ nie istnieją de facto tablice wielowymiarowe. W wypadku kodu, który podałeś cell to zmienna przechowująca referencje do obiektów klasy BoardCell. Natomiast BoardCell[], to tablica referencji do obiektów tej klasy, a BoardCell[][], to tablica referencji do tablic referencji obiektów klasy BoarCell. Operator new X(args) powoduje przydzielenie pamięci na obiekt typu X oraz wywołanie konstruktora X pasującego do parametrów args, ale zapis new X[size] powoduje przydzielenie pamięci na obiekt tablicy referencji do obiektów X i wywołanie jej niejawnego konstruktora (tablicowego), którego parametrem jest rozmiar tej tablicy podany między nawiasami kwadratowymi. Jeżeli typem tablicy jest też tablica, to masz taką postać: new X[size][]. Tablica jest normalnym obiektem , dlatego ilości elementów drugiego i kolejnych wymiarów nie mają żadnego znaczenia ponieważ każdy kolejny wymiar to referencje do obiektów jakiejś tablicy. A te mogą mieć różne długości nawet w ramach tego samego wymiaru (tablice nierówne). Dlatego poprawne będzie Object[][][] x = new Object[10][][];. A nawet int[][][] y = new int[10][][];, która jest tablicą 10 elementów będących tablicami tablic liczb int.
Nie jest to od razu jasne, ale da się ogarnąć. W C/C++ tablice są jeszcze bardziej zagmatwane bo tam tablica to po prostu czarna dziura w ramie. ;)

1 użytkowników online, w tym zalogowanych: 0, gości: 1