Почему Java не предлагает перегрузку операторов? - PullRequest
385 голосов
/ 17 сентября 2008

Переходя от C ++ к Java, очевидный вопрос без ответа состоит в том, почему Java не включает перегрузку операторов?

Разве Complex a, b, c; a = b + c; не намного проще, чем Complex a, b, c; a = b.add(c);?

Существует ли известная причина для этого, допустимые аргументы для , не допускающие перегрузку операторов? Причина произвольна или потеряна для времени?

Ответы [ 15 ]

751 голосов
/ 12 октября 2008

Есть много постов с жалобами на перегрузку операторов.

Я чувствовал, что должен прояснить концепции «перегрузки операторов», предлагая альтернативную точку зрения на эту концепцию.

Код запутывает?

Этот аргумент ошибочен.

Запутывание возможно на всех языках ...

Обфусцировать код в C или Java с помощью функций / методов так же легко, как и в C ++ с помощью перегрузок операторов:

// 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 ;
}

... Даже в стандартных интерфейсах Java

В качестве другого примера давайте рассмотрим интерфейс Cloneable в Java:

Вы должны клонировать объект, реализующий этот интерфейс. Но ты мог бы лгать. И создать другой объект. На самом деле, этот интерфейс настолько слаб, что вы можете вернуть другой тип объекта, просто для удовольствия:

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

Так как интерфейс Cloneable может быть злоупотреблен / запутан, должен ли он быть запрещен по тем же причинам, что и перегрузка оператора C ++?

Мы могли бы перегрузить метод toString() класса MyComplexNumber, чтобы он возвращал строковый час дня. Следует ли запретить перегрузку toString()? Мы можем саботировать MyComplexNumber.equals, чтобы он возвращал случайное значение, изменял операнды ... и т. Д. И т. Д.

В Java, как в C ++ или на любом другом языке, программист должен соблюдать минимум семантики при написании кода. Это означает реализацию функции add, которая добавляет, и метода реализации Cloneable, который клонирует, и оператора ++, который увеличивает значение.

Что в любом случае запутывает?

Теперь, когда мы знаем, что код можно саботировать даже с помощью нетронутых методов Java, мы можем задаться вопросом о реальном использовании перегрузки операторов в C ++?

Четкая и естественная запись: методы против перегрузки операторов?

Ниже мы сравним, для разных случаев, «один и тот же» код в Java и C ++, чтобы понять, какой стиль кодирования более понятен.

Естественные сравнения:

// 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 ;

Обратите внимание, что A и B могут быть любого типа в C ++, если предусмотрены перегрузки операторов. В Java, когда A и B не являются примитивами, код может стать очень запутанным, даже для примитивных объектов (BigInteger и т. Д.) ...

Природные массивы / контейнерные методы доступа и подписка:

// 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

В Java мы видим, что для каждого контейнера, выполняющего одно и то же (доступ к его содержимому через индекс или идентификатор), у нас есть другой способ сделать это, что сбивает с толку.

В C ++ каждый контейнер использует один и тот же способ доступа к своему контенту благодаря перегрузке операторов.

Естественные сложные типы манипуляций

В приведенных ниже примерах используется объект Matrix, найденный с помощью первых ссылок, найденных в Google для " Java Matrix object " и " c ++ Matrix object ":

// 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

И это не ограничивается матрицами. Классы BigInteger и BigDecimal Java страдают от той же запутанной многословности, в то время как их эквиваленты в C ++ столь же ясны, как и встроенные типы.

Естественные итераторы:

// 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

Природные функторы:

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

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

Объединение текста:

// 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") ;

Хорошо, в Java вы тоже можете использовать MyString = "Hello " + 25 + " World" ; ... Но, подождите секунду: это перегрузка операторов, не так ли? Разве это не мошенничество ???

: - D

Общий код?

Одни и те же операнды, модифицирующие общий код, должны использоваться как для встроенных модулей / примитивов (которые не имеют интерфейсов в Java), так и для стандартных объектов (которые не могут иметь правильный интерфейс), и пользовательских объектов.

Например, вычисление среднего значения двух значений произвольных типов:

// 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.

Обсуждение перегрузки оператора

Теперь, когда мы увидели справедливые сравнения между кодом C ++, использующим перегрузку операторов, и тем же кодом в Java, мы можем теперь обсудить «перегрузку операторов» как концепцию.

Перегрузка оператора существовала еще до появления компьютеров

Даже за пределами компьютерной науки существует перегрузка операторов: например, в математике такие операторы, как +, -, * и т. Д. Перегружены.

Действительно, значение +, -, * и т. Д. Изменяется в зависимости от типов операндов (чисел, векторов, квантовых волновых функций, матриц и т. Д.).

Большинство из нас, как часть наших научных курсов, изучили множество значений для операторов, в зависимости от типов операндов. Мы нашли их смущающими, их?

Перегрузка оператора зависит от его операндов

Это самая важная часть перегрузки операторов: как и в математике или физике, операция зависит от типов ее операндов.

Итак, знайте тип операнда, и вы узнаете эффект операции.

Даже C и Java имеют (жестко запрограммированную) перегрузку операторов

В С реальное поведение оператора будет меняться в зависимости от его операндов. Например, добавление двух целых чисел отличается от добавления двух двойных или даже одного целого и одного двойного. Существует даже целая арифметическая область указателя (без приведения можно добавить к указателю целое число, но нельзя добавить два указателя ...).

В Java нет арифметики указателей, но кто-то все же нашел, что конкатенация строк без оператора + была бы достаточно нелепой, чтобы оправдать исключение в кредо "перегрузка оператора - зло".

Просто вы, как кодер C (по историческим причинам) или Java (по личным причинам , см. Ниже), не можете предоставить свой собственный.

В C ++ перегрузка операторов не является обязательной ...

В C ++ перегрузка операторов для встроенных типов невозможна (и это хорошо), но определяемые пользователем типы могут иметь определяемые пользователем перегрузки операторов .

Как уже говорилось ранее, в C ++ и, в отличие от Java, пользовательские типы не считаются гражданами второго сорта по сравнению со встроенными типами. Поэтому, если встроенные типы имеют операторы, пользовательские типы также должны иметь их.

Правда состоит в том, что, подобно методам toString(), clone(), equals() для Java (, то есть квазистационарно ), перегрузка операторов C ++ является такой большой частью C ++, что оно становится таким же естественным, как исходные операторы C или ранее упомянутые методы Java.

В сочетании с шаблонным программированием перегрузка оператора становится широко известным шаблоном проектирования. На самом деле, вы не можете пойти очень далеко в STL, не используя перегруженные операторы и перегружая операторы для своего собственного класса.

... но этим не следует злоупотреблять

Перегрузка оператора должна стремиться соблюдать семантику оператора. Не вычитать в операторе + (как в «не вычитать в add функции» или «возвращать дерьмо в методе clone»).

Перегрузка приведения может быть очень опасной, поскольку может привести к неясностям. Таким образом, они действительно должны быть зарезервированы для четко определенных случаев. Что касается && и ||, никогда не перегружайте их, если вы действительно не знаете, что делаете, так как вы потеряете оценку короткого замыкания, которой обладают нативные операторы && и ||.

Итак ... Хорошо ... Тогда почему это невозможно в Java?

Потому что Джеймс Гослинг сказал так:

Я исключил перегрузку операторов как довольно личный выбор , потому что я видел слишком много людей, злоупотребляющих им в C ++.

Джеймс Гослинг. Источник: http://www.gotw.ca/publications/c_family_interview.htm

Пожалуйста, сравните текст Гослинга выше с текстом Страуструпа:

Многие дизайнерские решения C ++ коренятся в моей неприязни к тому, чтобы заставлять людей делать что-то определенным образом [...] Часто у меня возникало желание запретить функцию, которая мне лично не нравилась, я воздерживался от этого, потому что Я не думал, что имею право навязывать свои взгляды другим .

Бьярне Страуструп. Источник: Разработка и развитие C ++ (1.3 Общая информация)

Будет ли перегрузка операторов полезной для Java?

Некоторые объекты могут значительно выиграть от перегрузки операторов (конкретные или числовые типы, такие как BigDecimal, комплексные числа, матрицы, контейнеры, итераторы, компараторы, парсеры и т. Д.).

В C ++ вы можете воспользоваться этим преимуществом благодаря скромности Страуструпа. В Java вы просто облажались из-за личного выбора Гослинга .

Можно ли добавить его в Java?

Причинами, по которым теперь не добавляется перегрузка операторов в Java, может быть сочетание внутренней политики, аллергии на эту функцию, недоверия разработчиков (вы знаете, диверсантов, которые, похоже, преследуют команды Java ...), совместимости с предыдущие JVM, время для написания правильной спецификации и т.д ..

Так что не задерживайте дыхание в ожидании этой функции ...

Но они делают это на C # !!!

Да ...

Хотя это далеко не единственное различие между двумя языками, этот не может не удивить меня.

Очевидно, что люди C # со своими "каждым примитивом являются struct, а struct происходит от объекта" , поняли это правильно с первой попытки.

И они делают это на других языках !!!

Несмотря на все FUD против используемой определенной перегрузки операторов, следующие языки поддерживают ее: Scala , Dart , Python , F #, C # , D , Algol 68 , Smalltalk , Groovy , Perl 6, C ++, Рубин , Haskell , MATLAB , Eiffel , Lua , Clojure, Фортран 90 , Свифт , Ада , Delphi 2005 ...

Так много языков, с таким количеством разных (и иногда противоположных) философий, и все же они все согласны с этим.

Пища для размышлений ...

41 голосов
/ 17 сентября 2008

Джеймс Гослинг сравнил проектирование Java со следующим:

"Есть такой принцип, когда вы переезжаете, когда вы переезжаете из одной квартиры в другую. Интересный эксперимент состоит в том, чтобы упаковать свою квартиру и положить все в коробки, затем переехать в следующую квартиру и ничего не распаковывать, пока она вам не понадобится. Итак, вы делаете свой первый прием пищи и вытаскиваете что-то из коробки. Затем примерно через месяц вы использовали это, чтобы в значительной степени выяснить, какие вещи в вашей жизни вам действительно нужны, и затем вы берете остальные вещи - забудьте, насколько вам это нравится или насколько это круто - и вы просто выбрасываете это. Удивительно, как это упрощает вашу жизнь, и вы можете использовать этот принцип во всех вопросах дизайна: не делать вещи только потому, что они крутые или просто интересные. "

Вы можете прочитать контекст цитаты здесь

В основном перегрузка операторов отлично подходит для класса, который моделирует некоторую точку, валюту или комплексное число. Но после этого у вас быстро заканчиваются примеры.

Другим фактором было злоупотребление функцией в C ++ разработчиками, перегружающими такие операторы, как '&&', '||', операторы приведения и, конечно, 'new'. Сложность, возникающая в результате объединения этого с передачей по значению и исключениями, хорошо освещена в книге Exceptional C ++ .

22 голосов
/ 17 сентября 2008

Проверьте Boost.Units: текст ссылки

Обеспечивает анализ размеров без накладных расходов за счет перегрузки оператора. Насколько яснее это можно получить?

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

фактически выдаст «Energy = 4 Дж», что правильно.

19 голосов
/ 17 сентября 2008

Если вы хотите перезаписать предыдущее значение объекта, на который указывает a, тогда должна быть вызвана функция-член.

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

В C ++ это выражение указывает компилятору создать три (3) объекта в стеке, выполнить сложение и скопировать результирующее значение из временного объекта в существующий объект a.

Однако в Java operator= не выполняет копирование значений для ссылочных типов, и пользователи могут создавать только новые ссылочные типы, а не типы значений. Таким образом, для пользовательского типа с именем Complex присваивание означает копирование ссылки на существующее значение.

Рассмотрим вместо этого:

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

В C ++ это копирует значение, поэтому сравнение будет неравным. В Java operator= выполняет эталонное копирование, поэтому a и b теперь ссылаются на одно и то же значение. В результате сравнения будет получено значение «равно», поскольку объект будет сравниваться с равным самому себе.

Разница между копиями и ссылками только добавляет путаницы в перегрузку операторов. Как упоминал @Sebastian, Java и C # оба должны иметь дело со значением и равенством ссылок по отдельности - operator+, вероятно, будет иметь дело со значениями и объектами, но operator= уже реализован для работы со ссылками.

В C ++ вы должны иметь дело только с одним видом сравнения за раз, так что это может быть менее запутанным. Например, Complex, operator= и operator== оба работают со значениями - копируют значения и сравнивают значения соответственно.

11 голосов
/ 17 сентября 2008

Разработчики Java решили, что перегрузка операторов доставляет больше хлопот, чем того стоит. Все просто.

В языке, где каждая переменная объекта на самом деле является ссылкой, перегрузка оператора получает дополнительную опасность быть довольно нелогичной - по крайней мере для программиста C ++. Сравните ситуацию с перегрузкой оператора C # == и Object.Equals и Object.ReferenceEquals (или как там это называется).

8 голосов
/ 17 сентября 2008

Groovy имеет перегрузку оператора и работает в JVM. Если вы не возражаете против падения производительности (которое уменьшается с каждым днем). Это автоматически на основе имен методов. например, «+» вызывает метод «плюс (аргумент)».

6 голосов
/ 17 сентября 2008

Я думаю, что это был осознанный выбор дизайна, чтобы заставить разработчиков создавать функции, чьи имена четко отражают их намерения. В C ++ разработчики перегружали бы операторы функциональностью, которая часто не имела бы отношения к общепринятой природе данного оператора, что делало практически невозможным определение того, что делает часть кода, не глядя на определение оператора.

5 голосов
/ 17 сентября 2008

Ну, вы действительно можете выстрелить себе в ногу с перегрузкой оператора. Как с указателями люди делают с ними глупые ошибки, и поэтому было решено убрать ножницы.

По крайней мере, я думаю, что в этом причина. В любом случае, я на вашей стороне. :)

4 голосов
/ 24 ноября 2011

Технически существует перегрузка операторов в каждом языке программирования, который может работать с различными типами чисел, например, целые и действительные числа. Пояснение: термин перегрузка означает, что для одной функции существует просто несколько реализаций. В большинстве языков программирования предусмотрены различные реализации для оператора +, одна для целых чисел, одна для вещественных чисел, это называется перегрузкой оператора.

Теперь многим кажется странным, что в Java есть перегрузка оператора для оператора + для добавления строк вместе, и с математической точки зрения это было бы действительно странно, но с точки зрения разработчика языка программирования нет ничего плохого в добавлении встроенная перегрузка оператора для оператора + для других классов, например Строка. Однако большинство людей сходятся во мнении, что, как только вы добавите встроенную перегрузку для + для String, то, как правило, хорошей идеей будет предоставить эту функциональность и разработчику.

Совершенно не согласен с ошибкой, что перегрузка оператора запутывает код, так как это оставлено на усмотрение разработчика. Это наивно думать, и, честно говоря, оно стареет.

+ 1 для добавления перегрузки операторов в Java 8.

4 голосов
/ 24 марта 2010

Некоторые люди говорят, что перегрузка операторов в Java может привести к запутыванию. Неужели эти люди когда-нибудь останавливались, чтобы посмотреть на какой-нибудь Java-код, выполняющий некоторые базовые математические операции, такие как увеличение финансовой стоимости на процент с помощью BigDecimal? .... многословие такого упражнения становится его собственной демонстрацией запутывания. По иронии судьбы, добавление перегрузки операторов в Java позволило бы нам создать собственный класс Currency, который сделал бы такой математический код элегантным и простым (менее запутанным).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...