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

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

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

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

Ответ 1

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

Обфускация кода?

Этот аргумент является ошибкой.

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

Так же легко обфускать код на C или Java через функции/методы, как это происходит на С++ при перегрузках операторов:

// 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 interface в Java:

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

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

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

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

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

Что все-таки запутывает?

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

Ясное и естественное обозначение: методы против перегрузки оператора?

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

Натуральные сравнения:

// 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 могут быть любого типа в С++, если предусмотрены перегрузки оператора. В 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 мы видим, что для каждого контейнера делать то же самое (доступ к его контенту через индекс или идентификатор), у нас есть другой способ сделать это, что путает.

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

Мануалы с естественными расширенными типами

В приведенных ниже примерах используется объект Matrix, найденный с использованием первых ссылок, найденных в Google, для " объект Java 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

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

Натуральные итераторы:

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

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

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

Перегрузка оператора существовала с тех пор, как до компьютеров

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

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

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

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

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

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

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

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

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

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

В С++ перегрузка оператора необязательна...

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

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

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

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

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

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

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

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

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

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

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

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

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

Бьярне Страуструп. Источник: Desing and Evolution of С++ (1.3 Общие сведения)

Может ли оператор перегружать преимущества Java?

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

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

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

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

Поэтому не задерживайте дыхание, ожидая этой функции...

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

Да...

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

По-видимому, люди С# с их "каждым примитивом являются struct, а a struct выводится из Object" , с первого раза его правильно.

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

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

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

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

Ответ 3

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

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

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

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

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

Ответ 4

Проверьте 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 J", что является правильным.

Ответ 5

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

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

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

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

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

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

Ответ 11

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

Ответ 12

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

Нет:

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

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

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

Ответ 13

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

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

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

Ответ 14

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

Люди не всегда используют это ответственно. Посмотрите на этот пример из библиотеки 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 |>>

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

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