Sposoby na zwrócenie kilku wartości z funkcji

0

Mam dość duże doświadczenie w C# i w C++, niewielkie w Java. W dwóch pierwszych językach zdarza się, ze tworzę funkcję, która jednocześnie generuje dwa wyniki. Trochę bez sensu pisać dwie takie same funkcje lub dodawać dodatkowy parametr, który określa, którą wartość funkcji zwrócić, co też wiązałoby się z wielokrotnym wywołaniem funkcji.

Taki trywialny przykład, w praktyce bez sensu, ale na potrzeby tematu myślę, że jest OK. Funkcja, która przyjmuje dwie liczby i oddaje inne dwie liczby:

W C# byłoby tak:

void JakasFunkcja(int Dana1, int Data2, out int Wynik1, out int Wynik2)
{
    Wynik1 = Dana1 + Dana2;
    Wynik2 = Dana1 * Dana2;
}

W C++ byłoby tak:

void JakasFunkcja(int Dana1, int Data2, int &Wynik1, int &Wynik2)
{
    Wynik1 = Dana1 + Dana2;
    Wynik2 = Dana1 * Dana2;
}

AFAIK, analogiczny sposób w Java nie jest możliwy. Przykład jest może i "z czapy", ale załóżmy, że nie jest możliwe wykonanie samego dodawania, ani samego mnożenia. W jaki sposób w programiście Java zazwyczaj wyprowadzają z funkcji więcej niż jedną wartość?

Gdzieś czytałem w internecie, że można zrobić tablicę:

object[] JakasFunkcja(int Dana1, int Data2)
{
    object[] Wynik = new object[2];
    Wynik[0] = Dana1 + Dana2;
    Wynik[1] = Dana1 * Dana2;
    return Wynik;
}

Tworzyć nowy obiekt tylko po to, żeby wyciągnąć kilka liczb to chyba trochę bez sensu.

Inny pomysł, to ze zmiennymi globalnymi:

int JakasFunkcja_Wynik1;
int JakasFunkcja_Wynik2;
void JakasFunkcja(int Dana1, int Data2)
{
    JakasFunkcja_Wynik1 = Dana1 + Dana2;
    JakasFunkcja_Wynik2 = Dana1 * Dana2;
}

Teoretycznie ma to sens, o ile w danym programie nie ma możliwości, żeby tą funkcję uruchamiały dwa różne wątki, przy wielokrotnym wywołaniu w różnych wątkach z całą pewnością będzie problem z poprawnością wyników.

Mam przeczucie, że niejeden programista Java skrytykuje taki sposób wyprowadzania wyniku, mimo, że ma on sens, będzie działać w projekcie jednowątkowym (w wielowątkowym również, ale tylko w przypadku, gdy możliwe jest tylko jedno wywołanie funkcji naraz) i nie tworzy dodatkowych obiektów bez potrzeby.

W związku z powyższym jest pytanie ode mnie, czy programiści stosują jeden z dwóch wyżej wymienionych sposobów, czy jeszcze inaczej to robią?

Chodzi o to, że jak pisałem program w C# nie myśląc o Java, a nagle z jakiegoś powodu chcę przerobić program na Java, to jak to najprościej ogarnąć?

10
andrzejlisek napisał(a):

W związku z powyższym jest pytanie ode mnie, czy programiści stosują jeden z dwóch wyżej wymienionych sposobów, czy jeszcze inaczej to robią?

Jak rozwiązanie z tablicą ale tworzy się dedykowany obiekt (teraz jest prościej bo są rekordy pod to), albo używa się jednej z 10 bibliotek do tupli, albo tworzy się własną tuplę

1

Zależy od kontekstu ale zwracanie tablicy z wynikami to śmierdzi strasznie xD.

Zresztą w zakładam, że w C++ czy C# też byś dostał po łapach za te przykłądy jeżeli by tam nie było tematów jakiś mikrooptymalizacji :p

2

W samym języku nie ma mechanizmu krotek (tuple), ale można skorzystać z bibliotek np. https://docs.vavr.io/#_tuples

A sam problem wynika z tego, że w Javie nie masz wskaźników ani możliwości bezpośredniego dostępu do adresu. W swoim kodzie w C++ przekazujesz adres i piszesz bezpośrednio do niego. W Javie tego nie zrobisz (i słusznie).

0
Schadoow napisał(a):

Zresztą w zakładam, że w C++ czy C# też byś dostał po łapach za te przykłądy jeżeli by tam nie było tematów jakiś mikrooptymalizacji :p

Nieważne, co robi ta funkcja, ale załóżmy, że masz jedną funkcję, która ma dość długi kod, wykonuje dużo operacji, w czasie których powstaje kilka liczb, napisów, czy co tam chcesz, a one wszystkie będą potrzebne poza funkcją. Jeżeli krytykujesz wyciąganie wyników przez parametry funkcji, to jakbyś to wykonał w C++ i w C#? Jakie argumenty stoją za niewykorzystywaniem możliwości przekazywania argumentów w obie strony, czyli z zewnątrz do funkcji i z funkcji na zewnątrz? Nie chodzi o jakąś mikrooptymalizację, tylko chyba nie po to jest taka możliwość, żeby z niej nie korzystać. Każdy tutorial do C++ opisuje możliwość przekazywania parametrów przez wskaźnik/referencję.

0
Koziołek napisał(a):

W samym języku nie ma mechanizmu krotek (tuple), ale można skorzystać z bibliotek np. https://docs.vavr.io/#_tuples

Zaciąganie dodatkowej biblioteki tylko po to, żeby w paru miejscach móc wyciągnąć kilka informacji z funkcji to chyba strzał z armaty do muchy. Oczywiście, że tak można i będzie działać, ale czy "tak się robi"?

Koziołek napisał(a):

A sam problem wynika z tego, że w Javie nie masz wskaźników ani możliwości bezpośredniego dostępu do adresu. W swoim kodzie w C++ przekazujesz adres i piszesz bezpośrednio do niego. W Javie tego nie zrobisz (i słusznie).

W C# też nie ma wskaźników ani adresów (ściślej mówiąc są, ale kod ze wskaźnikiem trzeba objąć słowem unsafe, włączyć taką możliwość w ustawieniach kompilacji i do tego uchodzi to za bardzo niezalecany sposób programowania), ale jak widać, jest mozliwe przekazanie przez referencję w obie strony za pomocą słowa ref lub w kierunku z funkcji na zewnątrz za pomocą słowa out. Taki ampersand C++ jest analogiczy do ref, bo umożliwia przekazanie przez wskaźnik w obie strony.

2

W porównaniu do języka C/C++, robiąc int &wynik tak naprawdę przesyłasz referencję struktury o wielkości int, struktura to to samo co obiekt.
Teoretycznie tworzy się niejawnie obiekt, który ma tylko jedno pole int.

Żeby zrobić to samo w javie musisz też stworzyć z liczby obiekt, mutowalny, tyle że do pól struktury bezpośrednio nie można się odnosić więc musisz też settera i gettera zrobić, gdzie w c++ masz overloading operatorów więc nie da się odróżnić od zwykłego syntaxu.

https://godbolt.org/z/bMYYvdf1f

0
andrzejlisek napisał(a):

Mam dość duże doświadczenie w C# i w C++, niewielkie w Java. W dwóch pierwszych językach zdarza się, ze tworzę funkcję, która jednocześnie generuje dwa wyniki. Trochę bez sensu pisać dwie takie same funkcje lub dodawać dodatkowy parametr, który określa, którą wartość funkcji zwrócić, co też wiązałoby się z wielokrotnym wywołaniem funkcji.

Brzmi jakby ta funkcja powinna być klasą.

Argument określający zwracaną wartość to bardzo słabe rozwiązanie. Ale napisanie dwóch takich samych funkcji brzmi sensownie, jeśli dałoby się dużą część z nich wydzielić do mniejszych funkcji - wtedy w sumie nie byłoby nic z tym złego.

1
andrzejlisek napisał(a):

Zaciąganie dodatkowej biblioteki tylko po to, żeby w paru miejscach móc wyciągnąć kilka informacji z funkcji to chyba strzał z armaty do muchy. Oczywiście, że tak można i będzie działać, ale czy "tak się robi"?

Tak, ponieważ Java jako język nie jest jakoś super bogata w funkcjonalności. W dodatku od kilku wersji dąży się do pełnej modularyzacji, tak żeby ograniczyć ilość niepotrzebnych pakietów. Fajnie to widać na liście standardowych modułów Java SE, gdzie masz po prostu krótką listę API. W dodatku niektóre z nich są tylko specyfikacjami, które wymagają biblioteki z implementacją np. SQL.
Przy czym w przeciwieństwie do JS-a jest cały formalny proces związany ze specyfikacjami, więc API jest spójne.

andrzejlisek napisał(a):>

W C# też nie ma wskaźników ani adresów (ściślej mówiąc są, ale kod ze wskaźnikiem trzeba objąć słowem unsafe, włączyć taką możliwość w ustawieniach kompilacji i do tego uchodzi to za bardzo niezalecany sposób programowania), ale jak widać, jest mozliwe przekazanie przez referencję w obie strony za pomocą słowa ref lub w kierunku z funkcji na zewnątrz za pomocą słowa out. Taki ampersand C++ jest analogiczy do ref, bo umożliwia przekazanie przez wskaźnik w obie strony.

A w Javie tego nie masz. Możesz użyć modułów z Panama Project. Dostajesz „niskopoziomowy” dostęp do pamięci i możesz sobie np. wysłać adres obiektu, do funkcji w C, która o ile wie, jak wygląda ten obiekt, to może sobie nim manipulować. Po prostu masz trochę inną filozofię pracy. I tak, czasami chcesz zwrócić dwa wyniki, ale wtedy też zazwyczaj trzy razy zastanowisz się czy to co robisz jest poprawne z punktu widzenia jakości kodu.

2

W jaki sposób w programiście Java zazwyczaj wyprowadzają z funkcji więcej niż jedną wartość?

Wiem że przykład z zmiennymi Wynik1 i Wynik2 jest tylko przykładem "bez sensu" tak jak sam stwierdziłeś, ale wydaje mi się, że w zdecydowanej większości przypadków te zmienne mają jakiś nieabstrakcyjny sens. Wtedy po prostu tworzy się osobną klasę z dwoma polami (mogą być czystą Javą public final, albo rekordem, albo Lombokiem opykane) i nadaje się jej odpowiednią nazwę, nie ma w tym nic dziwnego.

Chcąc zwrócić punkt na płaszczyźnie, nie zwracamy zmiennych x i y tylko obiekt Point. Mając dane osobowe, nie zwracamy String name i int age tylko obiekt PersonDetails.

0
andrzejlisek napisał(a):

Gdzieś czytałem w internecie, że można zrobić tablicę:

object[] JakasFunkcja(int Dana1, int Data2)
{
    object[] Wynik = object[2];
    Wynik[0] = Dana1 + Dana2;
    Wynik[1] = Dana1 * Dana2;
    return Wynik;
}

To Ci się kompiluje? Gdzie jest new?

Bardzo spowalniałoby to Twoją aplikację?

Moim zdaniem nie warto martwić się na zapas.
Ważne, żeby działało i było czytelne.

3

To co pisał @KamilAdam - tworzy się record. I jak kogoś martwi, że to powoduje, że tworzą się na stercie jakieś obiekty i męczy się GC - tak bywa, ale tak nie musi być. Jak to jest problem to bierzesz graala (alternatywne JRE) i przeważnie się nie tworzą.

0

Java nie pozwala na takie cuda jak referencje, Javowe referencje mają inną semantykę do tych z C++ i bliżej im do wskaźników przekazywanych przez wartość. A to dlatego, że ten model daje sobie całkiem dobrze radę i zysk z uproszczenia systemu typów jest dużo większy niż większe możliwości.

Zwracanie rekordu (klasy z kilkoma polami) w dowolnej postaci jest najlepszym rozwiązniem. Alternatywą byłoby przekazanie w argumencie obiektu, który zostanie wypełniony.

2
andrzejlisek napisał(a):

W C# byłoby tak:

void JakasFunkcja(int Dana1, int Data2, out int Wynik1, out int Wynik2)
{
    Wynik1 = Dana1 + Dana2;
    Wynik2 = Dana1 * Dana2;
}

W C# byłoby tak gdybyśmy mieli rok 2016. Teraz byłoby to:

(int wynik1, int wynik2) JakasFunkcja(int Dana1, int Dana2) => (Dana1 + Dana2, Dana1 * Dana2);

out jest rzadko używany zwłaszcza że nie można go mieszać z asynchronicznym kodem.

W javie poleciłbym tez używanie tupli ale jako że nie ma wbudowanej obsługi i podpowiadania nazw składowych tupla to uważam to bez sensu.
Dużo lepiej po prostu stworzyć własną klasę i móc nazwać składowe. Jako że java dorobiła się rekordów jest to banalnie proste i nie wprowadza dużo boilerplate'u:

public record Wynik (int wynik1, int wynik2) {}

Wynik JakasFunkcja(int Dana1, int Dana2) {
  return new Wynik(Dana1 + Dana2, Dana1 * Dana2);
}
0

Chciałbym mieć takie problemy.

Rozwiązania:

  1. Pair z Apache Commons (na pewno już masz, nie wierzę w żaden projekt javowy komercyjny bez apache commons)
  2. Osobna klasa zawierająca zestaw pól zwracanych (może być record, może być @Value, whatever)
0
Spine napisał(a):
andrzejlisek napisał(a):

Gdzieś czytałem w internecie, że można zrobić tablicę:

object[] JakasFunkcja(int Dana1, int Data2)
{
    object[] Wynik = object[2];
    Wynik[0] = Dana1 + Dana2;
    Wynik[1] = Dana1 * Dana2;
    return Wynik;
}

To Ci się kompiluje? Gdzie jest new?

Bardzo spowalniałoby to Twoją aplikację?

Moim zdaniem nie warto martwić się na zapas.
Ważne, żeby działało i było czytelne.

Pisałem z pamięci, bez sprawdzania składni. Teraz poprawiłem, dopisałem new.

0

A jeszcze jedno - out / ref można zawsze zasymulować przekazując obiekt wrapujący Twoją referencję:

void JakasFunkcja(int Dana1, int Dana2, ref_int Wynik1, ref_int Wynik2)
{
    Wynik1.v = Dana1 + Dana2;
    Wynik2.v = Dana1 * Dana2;
}

class ref_int { public int v; }

nie polecam tak robić w javie ale w takim javascript czasem tak nawet rzeczywiście się robi

0
jarekr000000 napisał(a):

To co pisał @KamilAdam - tworzy się record. I jak kogoś martwi, że to powoduje, że tworzą się na stercie jakieś obiekty i męczy się GC - tak bywa, ale tak nie musi być. Jak to jest problem to bierzesz graala (alternatywne JRE) i przeważnie się nie tworzą.

Ja się nie przejmuję tym tworzeniem dodatkowych obiektów, bo i tak na 99% różnica nie będzie odczuwalna. Jednak tworzenie obiektu wydawało się, ze nieco zaciemnia kod. Ale skoro tak się robi, to akceptuję rozwiązanie.

0

Przeszukałem archiwum swoich projekcików i okazało się, że ogólnie rzadko korzystam z out i ref, ale mam kilka przykładów, z czasów, kiedy dopiero co się uczyłem c#. Teraz, to pewnie inaczej bym podszedł, ale wtedy najprościej, jak można, to wyprowadzać wyniki za pomocą out.

Przeliczanie współrzędnych między układem prostokątnym, a biegunowym. To i tak zawsze oblicza się obie współrzędne, bez sensu jest obliczanie tylko jednej, bez obliczania drugiej.

        public void CalcCoordsRectangularToPolar(double X, double Y, out double Angle, out double Radius)
        {
            Angle = Math.Atan2(Y, X);
            Radius = Math.Sqrt((X * X) + (Y * Y));
        }

        public void CalcCoordsPolarToRectangular(double Angle, double Radius, out double X, out double Y)
        {
            X = Math.Cos(Angle) * Radius;
            Y = Math.Sin(Angle) * Radius;
        }

Obliczanie typu indeksu i wartości indeksu na podstawie zapisu typu "(IX+1425)" "(IX-4456)" "(654)" "478", ogólnikowo to wejście może mieć następujące możliwości:

  1. Sama liczba - typ 3
  2. Liczba w nawiasie - typ 0
  3. Liczba poprzedzona "IX+" lub "IX-", całość w nawiasie - typ 1
  4. Liczba poprzedzona "IY+" lub "IY-", całość w nawiasie - typ 2

W każdym przypadku, zamiast liczby moze być znak "$", który znaczy "podany adres to bieżący adres."

Potrzebne jest wychwycenie zarówno wartości liczby, jak i typu zapisu.


// MagicNum - stała, która ma służyć do wyczucia, że na wejściu jest nieprawidłowość uniemożliwiająca odczytanie adresu.
// GetNum() - zamiana liczby hex podanej tekstowo lub "$" na liczbę typu int.

        private void GetIdx(string Arg, out int IdxVal, out int IdxN, int Addr)
        {
            IdxVal = MagicNum;
            IdxN = MagicNum;
            if (Arg.StartsWith("(") && Arg.EndsWith(")"))
            {
                string ArgBuf = Arg.Substring(1, Arg.Length - 2);
                if ((ArgBuf.StartsWith("IX+")) || (ArgBuf.StartsWith("IY+")) || (ArgBuf.StartsWith("IX-")) || (ArgBuf.StartsWith("IY-")))
                {
                    if (ArgBuf.StartsWith("IX-") || ArgBuf.StartsWith("IY-"))
                    {
                        IdxVal = 0 - GetNum(ArgBuf.Substring(3), Addr) + 256;
                    }
                    else
                    {
                        IdxVal = GetNum(ArgBuf.Substring(3), Addr);
                    }
                    if (ArgBuf.StartsWith("IX+") || ArgBuf.StartsWith("IX-"))
                    {
                        IdxN = 1;
                    }
                    else
                    {
                        IdxN = 2;
                    }
                }
                else
                {
                    IdxVal = GetNum(ArgBuf, Addr);
                    IdxN = 0;
                }
            }
            else
            {
                IdxVal = GetNum(Arg, Addr);
                IdxN = 3;
            }
        }

Inny przykład, dla którego kod jest dość skomplikowany (wzbocacony o jeszcze inne elementy), a problem jest bardzo prosty: Na wejściu jest podany plik *.MPO, który jest tak naprawdę skonkatenowanymi dwoma plikami JPEG. Funkcja powinna znaleźć miejsce połączenia plików, a potem zwrócić dwie tablice bajtowe, z których jedna to pierwszy plik JPEG, a druga to drugi plik JPEG. Do tego generuje dwie bitmapy zawierajace miniaturki. A jeżeli nie ma połączenia plików (bo nie jest to MPO, tylko zwykły JPEG), to na wyjście ma zwrócić dwa razy te same dane.

        public void GetImage(string FileName, out Bitmap Img1Disp, out Bitmap Img2Disp, out byte[] Raw10, out byte[] Raw20)
        {
        }
0
obscurity napisał(a):

A jeszcze jedno - out / ref można zawsze zasymulować przekazując obiekt wrapujący Twoją referencję:

void JakasFunkcja(int Dana1, int Dana2, ref_int Wynik1, ref_int Wynik2)
{
    Wynik1.v = Dana1 + Dana2;
    Wynik2.v = Dana1 * Dana2;
}

class ref_int { public int v; }

nie polecam tak robić w javie ale w takim javascript czasem tak nawet rzeczywiście się robi

Z tym sposobem też się chyba spotkałem. Jeżeli nie robię żadnej mikrooptymalizacji i ewentualne tworzenie dodatkowego obiektu nie jest problemem, to dlaczego tego sposobu nie polecasz?

1
andrzejlisek napisał(a):

Z tym sposobem też się chyba spotkałem. Jeżeli nie robię żadnej mikrooptymalizacji i ewentualne tworzenie dodatkowego obiektu nie jest problemem, to dlaczego tego sposobu nie polecasz?

Bo jest brzydko i spowalnia czytanie kodu - mamy tu nagle funkcję z nieoczekiwanym efektem ubocznym co może nie być jasne w pierwszym momencie przy czytaniu. Funkcje powinny być perfekcyjnie pure a stany niemutowalne. Poza tym nie ma żadnej zalety względem utworzenia specjalnego typu pod typ wyjściowy. Ten kod raczej wywoła wtf na twarzy przeciętnego czytacza kodu

0
obscurity napisał(a):

nie polecam tak robić w javie ale w takim javascript czasem tak nawet rzeczywiście się robi

Gdzie niby? Szczególnie, że w JS możesz zrobić takie coś:

const [result, error, somethingElse, whatever] = func();
0
andrzejlisek napisał(a):
Schadoow napisał(a):

Zresztą w zakładam, że w C++ czy C# też byś dostał po łapach za te przykłądy jeżeli by tam nie było tematów jakiś mikrooptymalizacji :p

Nieważne, co robi ta funkcja, ale załóżmy, że masz jedną funkcję, która ma dość długi kod, wykonuje dużo operacji, w czasie których powstaje kilka liczb, napisów, czy co tam chcesz, a one wszystkie będą potrzebne poza funkcją. Jeżeli krytykujesz wyciąganie wyników przez parametry funkcji, to jakbyś to wykonał w C++ i w C#? Jakie argumenty stoją za niewykorzystywaniem możliwości przekazywania argumentów w obie strony, czyli z zewnątrz do funkcji i z funkcji na zewnątrz? Nie chodzi o jakąś mikrooptymalizację, tylko chyba nie po to jest taka możliwość, żeby z niej nie korzystać. Każdy tutorial do C++ opisuje możliwość przekazywania parametrów przez wskaźnik/referencję.

Utworzyłbym obiekt, strukturę i ją zwrócił.
Co do argumentów:

  • brak side-effectów
  • wieksza czytelność - nie trzeba czytać implementacji funkcji aby jej użyć.
  • bardziej debiloodporne zrównoleglanie funkcji - bo "pure".

Jestem leniwym programistą i nie lubię jak ktoś każe mi czytać i myśleć w miejscach w których nie ma to uzasadanienia.

0

W sumie to wszystko co trzeba było powiedzieć to już tutaj powiedziano. Ogólnie się zgadzam z tym, że takie przypadki należy zazwyczaj rozwiązywać przez stworzenie dodatkowej struktury typu Pair czy w ogóle jakiegoś dedykowanego rekordu.

Wyjątkiem od tej reguły są niektóre ciężkie frameworki. Np. w Flinku funkcja może wyglądać tak: ProcessFunction.processElement(I value, KeyedProcessFunction.Context ctx, Collector<O> out), gdzie zarówno ctx, jak i out mogą być wyjściem. Tyle tylko, że tam ma to sens (ctx to taki globalny kontekst, out to jest dedykowane wyjście), natomiast w prostych przypadkach sensu to nie ma żadnego.

0
wartek01 napisał(a):

W sumie to wszystko co trzeba było powiedzieć to już tutaj powiedziano. Ogólnie się zgadzam z tym, że takie przypadki należy zazwyczaj rozwiązywać przez stworzenie dodatkowej struktury typu Pair czy w ogóle jakiegoś dedykowanego rekordu.

Również moim zdaniem jest już wszystko powiedziane i rozumiem z tego, co następuje:

  1. Najbardziej pożądany sposób, to pozbyć się out/ref/& przed przerabianiem na Java poprzez przerobienie architektury, np. poprzez dodatkowe klasy i obiekty, zamianę jednej funkcji na kilka mniejszych, potem można 1:1 przerobić.
  2. Jeżeli nie jest to program, w którym każda milisekunda pracy ma znaczenie (czyli 99% przypadków), to tworzenie dodatkowych obiektów i struktur w celu wyprowadzania wyników jest w porządku.
  3. Klasa z jednym polem typu prymitywnego, której obiekt może być nośnikiem jednej wartości prymitywnej. Możliwe, spełni swoje zadanie, ale niezalecane, bo zmniejsza czytelność kodu.
  4. Funkcja tworząca i zwracająca "krotkę" (tuple) przechowującą wszystkie wyciągane wartości - ten sposób jest uznany za dobry i praktykowany, mimo, że Java sama w sobie nie ma możliwości tworzenia krotek.
  5. Funkcja tworząca i zwracająca strukturę record. W zasadzie prawie to samo, co krotka, też rozwiązanie uznane za dobre.
  6. Tworzenie zmiennych globalnych do chwilowego przechowania wartości wygenerowanych przez funkcję to bardzo zły pomysł.
  7. Torzenie tablicy object[] raczej jest złym pomysłem.
0

Nigdy nie używałem krotek (tuple), ale widzę, że tuple to jest tak naprawdę tablica elementów różnych typów.

Patrząc tutaj: https://docs.vavr.io/#_tuples

Taki kod na przykład:

// (Java, 8)
Tuple2<String, Integer> java8 = Tuple.of("Java", 8); 

// "Java"
String s = java8._1; 

// 8
Integer i = java8._2;

Czym różni się od tego:

record TupleStrInt
{
  String _1;
  int _2;
}

TupleStrInt java8 = new TupleStrInt();
java8._1 = "Java";
java8._2 = 8;


// "Java"
String s = java8._1; 

// 8
Integer i = java8._2;

Można uogólnić:

record Tuple
{
  object _1;
  object _2;
}

TupleStrInt java8 = new TupleStrInt();
java8._1 = "Java";
java8._2 = 8;


// "Java"
String s = (String)java8._1; 

// 8
Integer i = (int)java8._2;

Albo od tego:

object[] java8 = new object[2];
java8[0] = "Java";
java8[1] = 8;


// "Java"
String s = (String)java8[0]; 

// 8
Integer i = (int)java8[1];

Jak dla mnie, wszystkie trzy to jedno i to samo, czyli coś, co przechowuje kilka wartości różnych typów i ma formę uporządkowaną (ponumerowane pola).

2

@andrzejlisek: wersja 3 i 4, używa typu Object i potem robisz rzutowania. Nie po to mamy typy, żeby rzutować. W przypadku 1 i 2 masz konkretne typy w konkretnych polach. Vavr powstał, zanim w Javie pojawiły się rekordy i dlatego ten kod wygląda podobnie. Przynajmniej na tym poziomie, bo Vavr dostarcza też całkiem zgrabne API, które pozwala na operowanie na elementach krotek.

0

Nie prościej napisać funkcję która przyjmuje funkcję zwracając funkcję?

0
Koziołek napisał(a):

@andrzejlisek: wersja 3 i 4, używa typu Object i potem robisz rzutowania. Nie po to mamy typy, żeby rzutować. W przypadku 1 i 2 masz konkretne typy w konkretnych polach. Vavr powstał, zanim w Javie pojawiły się rekordy i dlatego ten kod wygląda podobnie. Przynajmniej na tym poziomie, bo Vavr dostarcza też całkiem zgrabne API, które pozwala na operowanie na elementach krotek.

Każdy typ i tak dziedziczy po typie object, co pozwala w zmiennej object przechować wartość każdego typu? Rzutowanie trochę zaciemnia kod i zwiększa podatność na błędy. Czy dobrze rozumiem, że record to jest to samo, co struct w c++ i c#; lub record w Pascal? Jak nie było rekordów, to równie dobrze mogła być klasa, ale to troszeczkę komplikuję sprawę, bo każda klasa wymaga osobnego pliku nazwanego nazwą klasy, nie można w Javie zaimplementować klasy wewnątrz innej klasy, ani nie może być kilku klas w jednym pliku.

0
andrzejlisek napisał(a):

nie można w Javie zaimplementować klasy wewnątrz innej klasy,

Można, https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
Ale jest tricky, dobrze domyślnei dopisywać static, Eh te reużywanie słów w Jave :(

ani nie może być kilku klas w jednym pliku.

Chyba można ale tylko jedna może być publiczna? w zasadzie nie sprawdzałem
UPDATE https://stackoverflow.com/questions/2336692/java-multiple-class-declarations-in-one-file

BTW co do Record to też dziedziczy w Javie

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