it-swarm-eu.dev

Proč Java nabízí operátorovi přetížení?

Z C++ na Javu je zřejmé, že nezodpovězenou otázkou je, proč Java nezahrnuje přetížení operátora?

Není Complex a, b, c; a = b + c; mnohem jednodušší než Complex a, b, c; a = b.add(c);?

Existuje pro to známý důvod, platné argumenty pro ne umožňující přetížení operátora? Je důvod libovolný nebo ztracený?

375
rengolin

Za předpokladu, že byste chtěli přepsat předchozí hodnotu objektu, na který odkazuje a, pak by musela být vyvolána funkce člena.

Complex a, b, c;
// ...
a = b.add(c);

V C++ tento výraz říká kompilátoru, aby vytvořil tři (3) objekty v zásobníku, provedl sčítání a copy výslednou hodnotu z dočasného objektu do existujícího objektu a.

V jazyce Java však operator= neprovádí kopírování hodnot pro referenční typy a uživatelé mohou vytvářet pouze nové typy referencí, nikoli typy hodnot. Takže u uživatelsky definovaného typu s názvem Complex znamená přiřazení kopírovat odkaz na existující hodnotu.

Zvažte místo toho:

b.set(1, 0); // initialize to real number '1'
a = b; 
b.set(2, 0);
assert( !a.equals(b) ); // this assertion will fail

V jazyce C++ tato hodnota zkopíruje hodnotu, takže porovnání bude výsledkem ne-rovno. V jazyce Java provádí operator= referenční kopii, takže a a b nyní odkazují na stejnou hodnotu. Výsledkem je, že porovnání bude produkovat „rovno“, protože objekt se bude porovnávat stejně jako samotný objekt.

Rozdíl mezi kopiemi a odkazy pouze přispívá k záměně přetížení operátora. Jak zmínil @Sebastian, Java a C # se musí zabývat hodnotou a referenční rovností zvlášť - operator+ by se pravděpodobně zabývalo hodnotami a objekty, ale operator= je již implementováno, aby se zabývalo odkazy.

V jazyce C++ byste se měli zabývat pouze jedním druhem srovnání, takže to může být méně matoucí. Například na Complex, operator= a operator== pracují na hodnotách - kopírování hodnot a porovnávání hodnot.

17
Aaron

Existuje mnoho příspěvků, které si stěžují na přetížení operátora.

Cítil jsem, že musím objasnit koncepty „přetížení operátora“, které nabízejí alternativní pohled na tento koncept.

Kód obfuscating?

Tento argument je klam.

Zmatení je možné ve všech jazycích ...

Je to tak snadné obfuscate kód v C nebo Java prostřednictvím funkcí/metod, jak je to v C++ přes operátora přetížení:

// C++
T operator + (const T & a, const T & b) // add ?
{
   T c ;
   c.value = a.value - b.value ; // subtract !!!
   return c ;
}

// Java
static T add (T a, T b) // add ?
{
   T c = new T() ;
   c.value = a.value - b.value ; // subtract !!!
   return c ;
}

/* C */
T add (T a, T b) /* add ? */
{
   T c ;
   c.value = a.value - b.value ; /* subtract !!! */
   return c ;
}

... Dokonce i ve standardních rozhraních Java

Pro další příklad se podívejme na Cloneable interface v jazyce Java:

Měli byste klonovat objekt implementující toto rozhraní. Ale mohl jsi lhát. A vytvořit jiný objekt. Ve skutečnosti je toto rozhraní tak slabé, že byste mohli úplně vrátit jiný typ objektu, jen pro zábavu:

class MySincereHandShake implements Cloneable
{
    public Object clone()
    {
       return new MyVengefulKickInYourHead() ;
    }
}

Vzhledem k tomu, že rozhraní Cloneable může být zneužito/obfuscated, mělo by to být zakázáno na stejném základě C++ operátor přetížení má být?

Můžeme přetížit metodu toString() třídy MyComplexNumber, aby vrátila přísnou hodinu dne. Má být také toString() přetížení zakázáno? Mohli bychom Sabotage MyComplexNumber.equals vrátit náhodnou hodnotu, upravit operandy ... atd. Atd. Atd.

V jazyce Java, stejně jako v jazyce C++, musí programátor při psaní kódu respektovat minimum sémantiky. To znamená implementaci funkce add, která přidává, a Cloneable implementační metodu, která klonuje, a operátora ++ než přírůstky.

Co to vlastně znamená?

Teď, když víme, že kód může být sabotován i přes nedotčené metody Java, můžeme se ptát na skutečné využití přetížení operátora v C++?

Jasný a přirozený zápis: metody vs. přetížení obsluhy?

Pro různé případy porovnáme "stejný" kód v jazyce Java a C++, abychom měli představu o tom, který typ kódování je jasnější.

Přírodní srovnání:

// C++ comparison for built-ins and user-defined types
bool    isEqual          = A == B ;
bool    isNotEqual       = A != B ;
bool    isLesser         = A <  B ;
bool    isLesserOrEqual  = A <= B ;

// Java comparison for user-defined types
boolean isEqual          = A.equals(B) ;
boolean isNotEqual       = ! A.equals(B) ;
boolean isLesser         = A.comparesTo(B) < 0 ;
boolean isLesserOrEqual  = A.comparesTo(B) <= 0 ;

Mějte prosím na paměti, že A a B mohou být jakéhokoliv typu v C++, pokud jsou k dispozici přetížení obsluhy. V jazyce Java, když A a B nejsou primitivy, se kód může stát velmi matoucím, dokonce i pro objekty typu primitivní (BigInteger, atd.) ...

Přirozené přístupy pole/kontejneru a subskripce:

// C++ container accessors, more natural
value        = myArray[25] ;         // subscript operator
value        = myVector[25] ;        // subscript operator
value        = myString[25] ;        // subscript operator
value        = myMap["25"] ;         // subscript operator
myArray[25]  = value ;               // subscript operator
myVector[25] = value ;               // subscript operator
myString[25] = value ;               // subscript operator
myMap["25"]  = value ;               // subscript operator

// Java container accessors, each one has its special notation
value        = myArray[25] ;         // subscript operator
value        = myVector.get(25) ;    // method get
value        = myString.charAt(25) ; // method charAt
value        = myMap.get("25") ;     // method get
myArray[25]  = value ;               // subscript operator
myVector.set(25, value) ;            // method set
myMap.put("25", value) ;             // method put

V jazyce Java vidíme, že pro každý kontejner, který dělá totéž (přístup k obsahu prostřednictvím indexu nebo identifikátoru), máme jiný způsob, jak to udělat, což je matoucí.

V C++ používá každý kontejner stejný přístup ke svému obsahu díky přetížení operátora.

Přirozené pokročilé typy manipulace

Níže uvedené příklady používají objekt Matrix, který byl nalezen pomocí prvních odkazů nalezených na Googlu pro objekt " objekt Java Matrix " a " objekt c ++ Matrix ":

// C++ YMatrix matrix implementation on CodeProject
// http://www.codeproject.com/KB/architecture/ymatrix.aspx
// A, B, C, D, E, F are Matrix objects;
E =  A * (B / 2) ;
E += (A - B) * (C + D) ;
F =  E ;                  // deep copy of the matrix

// Java JAMA matrix implementation (seriously...)
// http://math.nist.gov/javanumerics/jama/doc/
// A, B, C, D, E, F are Matrix objects;
E = A.times(B.times(0.5)) ;
E.plusEquals(A.minus(B).times(C.plus(D))) ;
F = E.copy() ;            // deep copy of the matrix

A to není omezeno na matice. Třídy BigInteger a BigDecimal třídy Java trpí stejnou matoucí výřečností, zatímco jejich ekvivalenty v C++ jsou stejně jasné jako vestavěné typy.

Přírodní iterátory:

// C++ Random Access iterators
++it ;                  // move to the next item
--it ;                  // move to the previous item
it += 5 ;               // move to the next 5th item (random access)
value = *it ;           // gets the value of the current item
*it = 3.1415 ;          // sets the value 3.1415 to the current item
(*it).foo() ;           // call method foo() of the current item

// Java ListIterator<E> "bi-directional" iterators
value = it.next() ;     // move to the next item & return the value
value = it.previous() ; // move to the previous item & return the value
it.set(3.1415) ;        // sets the value 3.1415 to the current item

Přírodní funktory:

// C++ Functors
myFunctorObject("Hello World", 42) ;

// Java Functors ???
myFunctorObject.execute("Hello World", 42) ;

Zřetězení textu:

// C++ stream handling (with the << operator)
                    stringStream   << "Hello " << 25 << " World" ;
                    fileStream     << "Hello " << 25 << " World" ;
                    outputStream   << "Hello " << 25 << " World" ;
                    networkStream  << "Hello " << 25 << " World" ;
anythingThatOverloadsShiftOperator << "Hello " << 25 << " World" ;

// Java concatenation
myStringBuffer.append("Hello ").append(25).append(" World") ;

Ok, v Javě můžete použít MyString = "Hello " + 25 + " World" ; taky ... Ale počkejte chvíli: To je přetížení operátora, že? Není to podvádění ???

:-D

Obecný kód?

Stejné operandy modifikující obecný kód by měly být použitelné jak pro vestavby/primitivy (které nemají rozhraní v jazyce Java), tak pro standardní objekty (které nemohly mít správné rozhraní) a objekty definované uživatelem.

Například výpočet průměrné hodnoty dvou hodnot libovolných typů:

// C++ primitive/advanced types
template<typename T>
T getAverage(const T & p_lhs, const T & p_rhs)
{
   return (p_lhs + p_rhs) / 2 ;
}

int     intValue     = getAverage(25, 42) ;
double  doubleValue  = getAverage(25.25, 42.42) ;
complex complexValue = getAverage(cA, cB) ; // cA, cB are complex
Matrix  matrixValue  = getAverage(mA, mB) ; // mA, mB are Matrix

// Java primitive/advanced types
// It won't really work in Java, even with generics. Sorry.

Diskuse o přetížení operátora

Nyní, když jsme viděli spravedlivé srovnání mezi kódem C++ pomocí přetížení operátora a stejným kódem v jazyce Java, můžeme nyní diskutovat o konceptu „přetížení operátora“.

Přetížení operátorů existovalo již před počátkem počítačů

I mimo počítačovou vědu dochází k přetížení operátora: Například v matematice jsou operátoři jako +, -, * atd. Přetíženi.

Význam +, -, * atd. Se totiž mění v závislosti na typech operandů (numerické, vektory, funkce kvantových vln, matice atd.).

Většina z nás, v rámci našich přírodovědných kurzů, se v závislosti na typech operandů naučila pro operátory více významů. Zjistili jsme, že jsou matoucí?

Přetížení operátora závisí na jeho operandech

Toto je nejdůležitější část přetížení operátora: Stejně jako v matematice, tak i ve fyzice, operace závisí na typech operandů.

Znáte tedy typ operandu a budete znát účinek operace.

Dokonce i C a Java mají (pevně) přetížení operátora

V C se skutečné chování operátora změní podle jeho operandů. Například přidání dvou celých čísel je jiné než přidání dvou dvojitých, nebo dokonce jednoho celého čísla a jednoho dvojitého. Existuje dokonce celá aritmetická doména ukazatele (bez obsazení, můžete přidat do ukazatele celé číslo, ale nemůžete přidat dva ukazatele ...).

V Javě neexistuje aritmetika ukazatele, ale někdo, kdo ještě našel řetězec zřetězení bez operátora +, by byl dost směšný na to, aby ospravedlnil výjimku v „přetížení operátora je zlo“.

Je to jen, že jako C (z historických důvodů) nebo Java (pro osobní důvody, viz níže) kodér, nemůžete poskytnout vlastní.

V C++ není přetížení operátora volitelné ...

V C++ není možné přetížení obsluhy pro vestavěné typy (a to je dobrá věc), ale uživatelsky definovaný typy mohou mít uživatelsky definovaný přetížení obsluhy.

Jak již bylo řečeno dříve, v jazyce C++ a naopak v porovnání s jazykem Java nejsou typy uživatelů považovány za občany druhé třídy ve srovnání s vestavěnými typy. Pokud tedy mají vestavěné typy operátory, měly by mít i ty typy uživatelů.

Pravdou je, že stejně jako metody toString(), clone(), equals() jsou pro Javu (tj. kvazi-standard), Přetížení operátora C++ je tolik součástí C++, že se stává tak přirozenou jako původní operátoři C, nebo dříve zmíněné metody Java.

V kombinaci s programováním šablon se přetížení obsluhy stává známým vzorem návrhu. Ve skutečnosti nemůžete jít daleko do STL bez použití přetížených operátorů a přetížení operátorů pro vaši vlastní třídu.

... ale neměla by být zneužívána

Přetížení operátora by mělo usilovat o respektování sémantiky operátora. Neodčítejte v operátoru + (jako v "neodpočítávejte v add funkci" nebo "return crap v metodě clone").

Přetížení odlitků může být velmi nebezpečné, protože může vést k nejasnostem. Měly by tedy být skutečně vyhrazeny pro dobře definované případy. Co se týče && a ||, nikdy je nepřetěžujte, pokud opravdu nevíte, co děláte, protože ztratíte vyhodnocení zkratu, které mají nativní operátoři && a ||.

Takže ... Ok ... Tak proč to není možné v Javě?

Jak řekl James Gosling:

Nechal jsem operátora přetížit jako poměrně osobní volba protože jsem viděl příliš mnoho lidí, kteří ho zneužívají v C++.

James Gosling. Zdroj: http://www.gotw.ca/publications/c_family_interview.htm

Porovnejte Goslingův text s textem Stroustrup níže:

Mnoho C + + design rozhodnutí mají své kořeny v mé nelíbí, že nutí lidi, aby dělali věci nějakým zvláštním způsobem [...] Často jsem byl v pokušení postavit funkci, kterou osobně nemám rád, jsem se zdržel dělat tak, protože Nemyslel jsem si, že mám právo vynucovat své názory na ostatní.

Bjarne Stroustrup. Zdroj: Desing a evoluce C++ (1.3 Obecné souvislosti)

Přispěl by operátor přetížení Java?

Některé objekty by značně těžit z přetížení operátora (konkrétní nebo numerické typy, jako je BigDecimal, komplexní čísla, matice, kontejnery, iterátory, komparátory, analyzátory atd.).

V C + + můžete z této výhody profitovat díky pokorě Stroustrupa. V Javě jste prostě kvůli Goslingovi osobní volba.

Mohl by být přidán do jazyka Java?

Důvody pro nepřidání operátora přetížení nyní v Javě by mohla být směs interní politiky, alergie na funkci, nedůvěra k vývojářům (víte, sabotéři, kteří se zdají strašit Java týmům ...), kompatibilita s předchozími JVM, čas napsat správnou specifikaci atd.

Takže nedržte dech čekající na tuto funkci ...

Ale dělají to v C # !!!

To jo...

I když to není zdaleka jediným rozdílem mezi těmito dvěma jazyky, nikdy mě to nezajímá.

Zdá se, že C # lidé, s jejich "každý primitiv je struct a struct pochází z objektu", to hned při prvním pokusu.

A dělají to v jiných jazycích !!!

Navzdory veškerému FUDu oproti použitému definovanému přetížení operátora ho podporují následující jazyky: Scala , Dart , Python , F # , C # , D , ALGOL 68 , Smalltalk , Groovy , Perl 6 , C++, Ruby , Haskell , MATLAB , Eiffel , Lua , Clojure , Fortran 90 , Swift , Ada , Delphi 2005 ...

Tolik jazyků, s tolika různými (a někdy protichůdnými) filosofiemi, a přesto se na tom shodují.

K zamyšlení...

738
paercebal

James Gosling přirovnal navrhování Javy k následujícím:

"Je tu tento princip pohybu, když se pohybujete z jednoho bytu do druhého. Zajímavým experimentem je zabalit si byt a dát všechno do krabic, pak se přesunout do dalšího bytu a nic nevybalit, dokud ho nebudete potřebovat. Takže vy" Po prvním jídle si něco vytáhnete z krabice a po měsíci, nebo tak jste použili, aby jste zjistili, jaké věci ve vašem životě skutečně potřebujete, a pak si vezmete zbytek věci - zapomeňte, jak moc se vám to líbí, nebo jak je to cool - a prostě to odhodíte, je to úžasné, jak to zjednodušuje váš život, a tento princip můžete použít ve všech typech problémů designu: neudělat věci jen proto, že "Buď v pohodě, nebo jen proto, že jsou zajímavé."

Můžete si přečíst kontext citace zde

Přetížení operátora je v podstatě skvělé pro třídu, která modeluje určitý počet bodů, měn nebo složitých čísel. Ale poté, co začnete rychle docházet příklady.

Dalším faktorem bylo zneužití funkce v C++ vývojáři, kteří přetížili operátory jako „&&“, „||“, operátory obsazení a samozřejmě „nové“. Komplexnost vyplývající z kombinování s průchodem hodnotou a výjimkami je dobře popsána v Exceptional C++ book.

39
Garth Gilmour

Odhlásit Boost.Units: text odkazu

Poskytuje nulovou režijní analýzu pomocí přetížení obsluhy. Kolik jasnější to může být?

quantity<force>     F = 2.0*newton;
quantity<length>    dx = 2.0*meter;
quantity<energy>    E = F * dx;
std::cout << "Energy = " << E << endl;

by skutečně vydával "Energy = 4 J", což je správné.

20
user15793

Návrháři v Javě rozhodli, že přetížení operátorů je více problémů, než stojí za to. Jednoduché.

V jazyce, kde je každá objektová proměnná vlastně odkazem, přetížení operátora dostane další nebezpečí, že je zcela nelogické - přinejmenším programátorovi C++. Porovnejte situaci s přetížením operátoru C # == a Object.Equals a Object.ReferenceEquals (nebo s jakýmkoliv názvem).

11
Sebastian Redl

Groovy má operátora přetížení a běží v JVM. Pokud vám nevadí výkon hit (který se zmenší každý den). Je to automatické na základě názvů metod. např. '+' volá metodu 'plus (argument)'.

8
noah

Myslím, že to mohlo být vědomá volba designu, která nutí vývojáře vytvářet funkce, jejichž jména jasně sdělují jejich záměry. V C++ vývojáři by přetíželi operátory funkcemi, které by často neměly žádný vztah k běžně přijímané povaze daného operátora, což znemožnilo určit, co kus kódu dělá, aniž by se podíval na definici operátora.

6
user14128

Dobře se můžete natáčet v nohou s přetížením obsluhy. Je to jako u lidí, kteří s nimi dělají hloupé chyby, a tak bylo rozhodnuto, že se nůžky odejdou.

Přinejmenším si myslím, že to je ten důvod. :)

5
Sarien

Technicky dochází k přetížení operátora v každém programovacím jazyce, který může pracovat s různými typy čísel, např. celé číslo a reálná čísla. Vysvětlení: Termín přetížení znamená, že pro jednu funkci existuje pouze několik implementací. Ve většině programovacích jazyků jsou poskytovány různé implementace pro operátora +, jedno pro celá čísla, jedno pro reals, toto se nazývá přetížení operátora.

Mnozí lidé nyní považují za podivné, že Java má pro operátora přetížení operátora + pro přidávání řetězců dohromady a z matematického hlediska by to bylo divné, ale z pohledu vývojáře programovacího jazyka není nic špatného s přidáváním přetížení vestavěného operátora pro operátora + pro jiné třídy např Řetězec. Nicméně, většina lidí souhlasí, že jakmile přidáte vestavěné přetížení pro + pro String, pak je obecně dobré poskytnout tuto funkcionalitu také pro vývojáře.

Úplně nesouhlasím s klamem, který operátor přetížení kóduje, protože toto je ponecháno na rozhodnutí vývojáře. To je naivní myslet a být upřímný, stárne.

+1 pro přidání přetížení operátora v jazyce Java 8.

4
Olai

Říci, že přetížení operátora vede k logickým chybám typu, které operátor neodpovídá logice provozu, je to jako říci nic. Stejný typ chyby nastane, pokud je název funkce nevhodný pro provozní logiku - takže co je řešení: zrušte možnost použití funkce !? Toto je komická odpověď - "Nevhodné pro operační logiku", každý název parametru, každá třída, funkce nebo cokoliv může být logicky nevhodné. Myslím si, že tato možnost by měla být k dispozici ve slušném programovacím jazyce. , a ti, kteří si myslí, že je to nebezpečné - hej žádné bothy říká, že to musíte použít. Umožňuje vzít C #. Ukazovali ukazatele, ale hej - je zde prohlášení o „nebezpečných kódech“ - program, jak se vám líbí na vlastní nebezpečí.

4
Kvant

Někteří lidé říkají, že přetížení operátora v Javě by vedlo k záhadě. Zastavili se ti lidé, aby se podívali na nějaký kód Java, který dělá některé základní matematiky, jako je zvýšení finanční hodnoty o procento pomocí BigDecimal? .... výřečnost takového cvičení se stává jeho vlastním projevem zmatku. Je ironií, že přidání operátora přetížením do Javy by nám umožnilo vytvořit vlastní třídu měn, která by učinila takový matematický kód elegantním a jednoduchým (méně obscénním).

3
Volksman

Za předpokladu, že Java je implementační jazyk, pak a, b a c by byly všechny odkazy na typ Complex s počátečními hodnotami null. Také za předpokladu, že Complex je neměnný jako zmíněný BigInteger a podobný neměnný BigDecimal , já bych si myslel, že máte na mysli následující, jak jste přiřazení odkaz na Complex vrátil z přidávání b a c, a neporovnávání tohoto odkazu na.

Není:

Complex a, b, c; a = b + c;

mnohem jednodušší než:

Complex a, b, c; a = b.add(c);
2

Někdy by bylo hezké mít přetížení operátora, třídy přátel a vícenásobné dědictví.

Stále si však myslím, že to bylo dobré rozhodnutí. Pokud by Java měla operátora přetížení, pak bychom si nikdy nemohli být jisti významem operátora, aniž bychom se dívali na zdrojový kód. V současné době to není nutné. A myslím, že váš příklad použití metod namísto přetížení operátora je také docela čitelný. Pokud chcete, aby věci byly jasnější, můžete vždy přidat komentář nad chlupatými výroky.

// a = b + c
Complex a, b, c; a = b.add(c);
1
user14070

Alternativy k nativní podpoře přetížení operátora Java

Vzhledem k tomu, že Java nemá přetížení operátora, zde je několik alternativ, na které se můžete podívat:

  1. Použijte jiný jazyk. Obě Groovy a Scala mají operátora přetížení a jsou založeny na jazyce Java.
  2. Použijte Java-oo , plugin, který umožňuje operátorovi přetížení v Javě. Všimněte si, že není nezávislý na platformě. Také má mnoho problémů a není kompatibilní s nejnovějšími verzemi Java (tj. Java 10). ( Original StackOverflow Source )
  3. Použijte JNI , Java Native Interface nebo alternativy. To vám umožní psát metody C nebo C++ (možná i jiné?) Pro použití v Javě. To samozřejmě také není nezávislé na platformě.

Pokud se někdo dozví o jiných, prosím komentujte a přidám ho do tohoto seznamu.

0
gagarwa

To není dobrý důvod, proč je zakázat, ale praktický:

Lidé to ne vždy používají zodpovědně. Podívejte se na tento příklad z knihovny Python:

>>> IP()
<IP |>
>>> IP()/TCP()
<IP frag=0 proto=TCP |<TCP |>>
>>> Ether()/IP()/TCP()
<Ether type=0x800 |<IP frag=0 proto=TCP |<TCP |>>>
>>> IP()/TCP()/"GET / HTTP/1.0\r\n\r\n"
<IP frag=0 proto=TCP |<TCP |<Raw load='GET / HTTP/1.0\r\n\r\n' |>>>
>>> Ether()/IP()/IP()/UDP()
<Ether type=0x800 |<IP frag=0 proto=IP |<IP frag=0 proto=UDP |<UDP |>>>>
>>> IP(proto=55)/TCP()
<IP frag=0 proto=55 |<TCP |>>

Zde je vysvětlení:

Operátor byl použit jako operátor kompozice mezi dvěma vrstvami. Spodní vrstva tak může mít jednu nebo více svých výchozích polí Přetíženou podle horní vrstvy. (Stále Můžete zadat požadovanou hodnotu). Jako základní vrstvu lze použít řetězec.

0
Sarien