Почему Java не предлагает перегрузку операторов?

StackOverflow https://stackoverflow.com/questions/77718

  •  09-06-2019
  •  | 
  •  

Вопрос

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

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

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

Это было полезно?

Решение

Предполагая, что вы хотите перезаписать предыдущее значение объекта, на который ссылается 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++ это копирует значение, поэтому результат сравнения не равен.На Яве, operator= выполняет эталонное копирование, поэтому a и b теперь имеют в виду одно и то же значение.В результате сравнение выдаст «равно», поскольку объект будет сравниваться равным самому себе.

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

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

Другие советы

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

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

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

Этот аргумент является заблуждением.

Обфускация возможна на всех языках...

Запутать код на 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 интерфейс на Яве:

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

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

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

Мы могли бы перегрузить toString() метод MyComplexNumber class, чтобы он возвращал строковый час дня.Должен ли 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-матрицы" и "объект матрицы С++":

// 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 есть (жестко запрограммированная) перегрузка операторов.

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

В 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 происходит от объекта", получилось с первой попытки.

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

Несмотря на все препятствия против использования перегрузки определенных операторов, ее поддерживают следующие языки: Скала, Дарт, Питон, Ф#, С#, Д, Алголь 68, Болтовня, классный, Перл 6, С++, Рубин, Хаскелл, МАТЛАБ, Эйфелева, Луа, Кложур, Фортран 90, Быстрый, Ада, Дельфи 2005...

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

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

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

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

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

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

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

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

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

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

на самом деле выведет «Энергия = 4 Дж», и это правильно.

Разработчики Java решили, что перегрузка операторов приносит больше проблем, чем пользы.Просто как тот.

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

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

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

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

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

Сказать, что перегрузка операторов приводит к логическим ошибкам типа того, что оператор не соответствует логике работы, это все равно, что ничего не сказать.Ошибка того же типа возникнет, если имя функции не соответствует логике работы. Так какое решение:отказаться от возможности использования функций!?Это комичный ответ - «Не подходит для логики работы», каждое имя параметра, каждый класс, функция или что-то еще может быть логически неподходящим.Я думаю, что эта опция должна быть доступна на приличном языке программирования, и те, кто думает, что это небезопасно - эй, оба говорят, что вы должны ее использовать.Возьмем C#.Они опустили указатели, но эй - есть заявление о "небезопасном коде" - программируйте как хотите на свой страх и риск.

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

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

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

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

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

Если предположить, что Java является языком реализации, то a, b и c будут ссылками на тип Complex с начальными значениями null.Также предполагая, что Complex неизменяем, как упоминалось выше. БигИнтегер и подобные неизменяемые BigDecimal, Я бы подумал, что вы имеете в виду следующее, поскольку вы присваиваете ссылку на комплекс, полученный в результате сложения b и c, а не сравниваете эту ссылку с a.

Не так ли:

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

много проще, чем:

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

Иногда было бы неплохо иметь перегрузку операторов, дружественные классы и множественное наследование.

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

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

Это не веская причина для запрета, а практичная:

Люди не всегда используют его ответственно.Посмотрите на этот пример из библиотеки Python scapy:

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

Вот объяснение:

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

Альтернативы встроенной поддержке перегрузки операторов Java

Поскольку в Java нет перегрузки операторов, вы можете рассмотреть следующие альтернативы:

  1. Используйте другой язык.Оба классный и Скала имеют перегрузку операторов и основаны на Java.
  2. Использовать Java-оо, плагин, который позволяет перегружать операторы в Java.Обратите внимание, что он НЕ зависит от платформы.Кроме того, у него много проблем, и он несовместим с последними выпусками Java (т.Ява 10).(Исходный источник StackOverflow)
  3. Использовать JNI, собственный интерфейс Java или альтернативы.Это позволяет вам писать методы C или C++ (может быть, и другие?) для использования в Java.Конечно, это также НЕ зависит от платформы.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top