Исходя из С++ в Java, очевидный неотвеченный вопрос заключается в том, почему Java не включала перегрузку оператора?
Есть ли известная причина для этого, допустимые аргументы для недопущения перегрузки оператора? Является ли причина произвольной или потеряна во времени?
Ответ 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..
Так много языков, с очень многими разными (и иногда противоположными) философиями, и все же они все согласны в этом вопросе.
Пища для размышлений...