[java] Java가 왜 연산자 오버로드를 제공하지 않습니까?

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 ++ 에서처럼 함수 / 메소드를 통해 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인터페이스 를 보자 :

이 인터페이스를 구현하는 객체를 복제해야합니다. 그러나 당신은 거짓말을 할 수 있습니다. 그리고 다른 객체를 만듭니다. 실제로이 인터페이스는 너무 약해서 재미를 위해 다른 유형의 객체를 모두 반환 할 수 있습니다.

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

그리고 이것은 행렬에만 국한되지 않습니다. BigIntegerBigDecimal자바의 클래스는 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" ;에서도 사용할 수 있습니다 … 그러나 잠시만 기다리십시오. 이것은 연산자 오버로드입니다. 그렇지 않습니까? 부정 행위 아닌가요 ???

:-디

일반 코드?

내장 / 프리미티브 (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와 달리 사용자 유형은 내장 유형과 비교할 때 언어의 2 급 시민으로 간주되지 않습니다. 따라서 내장 유형에 연산자가 있으면 사용자 유형도 연산자를 가질 수 있어야합니다.

진실은 저를 인처럼 toString(), clone(), equals()방법은 자바위한 것입니다 ( 즉, 준 표준 등 ), C ++ 연산자 오버로딩이 언급 Java 메소드 전에 원래 C 사업자 등의 자연, 또는으로 될 것으로 ++ C의 너무 많은 부분입니다.

템플릿 프로그래밍과 결합 된 연산자 오버로딩은 잘 알려진 디자인 패턴이됩니다. 실제로 오버로드 된 연산자를 사용하지 않고 자신의 클래스에 대한 오버로드 연산자를 사용하지 않으면 STL에서 멀리 갈 수 없습니다.

…하지만 남용해서는 안됩니다

연산자 과부하는 연산자의 의미를 존중하기 위해 노력해야합니다. +연산자 에서 빼지 마십시오 ( ” add함수 에서 빼지 마십시오 “또는 ” clone메소드 에서 크랩 반환 “).

캐스트 과부하는 모호성을 유발할 수 있으므로 매우 위험 할 수 있습니다. 따라서 실제로 정의 된 사례를 위해 예약해야합니다. 에 관해서 &&그리고 ||당신이 정말로 당신이 무슨 일을하는지 알지 못한다면 당신은 기본 운영자 그 단락 회로 평가를 잃게됩니다로서, 지금까지 그들을 과부하가 걸리지 않도록 &&하고 ||즐길 수 있습니다.

그렇다면 … Ok … 그렇다면 왜 Java에서는 불가능합니까?

제임스 고슬링이 그렇게 말했기 때문에 :

C ++에서 너무 많은 사람들이 그것을 남용하는 것을 보았 기 때문에 연산자 오버로드를 상당히 개인적인 선택으로 제외 했습니다.

제임스 고슬링 출처 : http://www.gotw.ca/publications/c_family_interview.htm

위의 Gosling의 텍스트를 Stroustrup의 아래 텍스트와 비교하십시오.

많은 C ++ 디자인 결정은 사람들이 특정한 방식으로 일을하도록 강요하는 것에 대해 싫어하는 데 뿌리를두고있다. […] 종종, 나는 개인적으로 싫어하는 기능을 불법화하려는 유혹을 받았다. 다른 사람에 대한 나의 견해를 강요 할 권리 .

Bjarne Stroustrup. 출처 : C ++의 디자인과 진화 (1.3 일반 배경)

연산자 과부하가 Java에 도움이됩니까?

일부 객체는 연산자 오버로드 (BigDecimal, 복소수, 행렬, 컨테이너, 반복자, 비교기, 파서 등과 같은 콘크리트 또는 숫자 유형)의 이점을 크게 누릴 수 있습니다.

C ++에서는 Stroustrup의 겸손으로 인해이 혜택을 누릴 수 있습니다. Java에서는 Gosling이 개인적으로 선택 했기 때문에 문제가 발생했습니다 .

Java에 추가 할 수 있습니까?

현재 Java에 운영자 과부하를 추가하지 않는 이유는 내부 정치, 기능에 대한 알레르기, 개발자에 대한 불신 (Java 팀을 괴롭히는 것처럼 보이는 파괴자), 이전 JVM과의 호환성, 정확한 사양 작성 시간 등

따라서이 기능을 기다리는 숨을 참지 마십시오 …

그러나 그들은 C #으로 그것을합니다!

네…

이것이 두 언어의 유일한 차이점은 아니지만이 언어는 결코 나를 즐겁게하지 않습니다.

분명히 C # 사람들은 “모든 프리미티브는 a struct이고 structObject에서 파생됩니다”라는 첫 번째 시도에서 올바르게 얻었습니다.

그리고 그들은 다른 언어 로도 합니다 !!!

모든에도 불구하고 FUD 사용 정의 연산자 오버로딩에 대해, 다음과 같은 언어를 지원 : 스칼라 , 다트 , 파이썬 , F 번호 , C #을 , D , 알골 (68) , 스몰 토크 , 그루비 , 펄 6 , C ++, 루비 , 하스켈 , MATLAB , 에펠 , 루아 , Clojure , Fortran 90 , Swift , Ada , Delphi 2005

너무나 많은 다른 (때때로 반대되는) 철학을 가진 많은 언어들이 있지만, 그들은 모두 그 시점에 동의합니다.

생각할 거리…


답변

James Gosling은 Java 디자인을 다음과 같이 비유했습니다.

“한 아파트에서 다른 아파트로 이사 할 때 이사하는 것에 대한이 원칙이 있습니다. 흥미로운 실험은 아파트를 포장하고 모든 것을 상자에 넣은 다음 다음 아파트로 옮기고 필요할 때까지 포장을 풀지 않는 것입니다. 첫 식사를 다시하고 상자에서 무언가를 꺼내는 것입니다. 그리고 한 달 정도 지나면 인생에서 실제로 필요한 것들을 거의 파악하고 나머지는 물건-좋아하는 것과 잊어 버리는 것을 잊어 버리고 그냥 버려라 인생을 단순화시키는 방법이 놀랍고 모든 종류의 디자인 문제에서 그 원칙을 사용할 수 있습니다. ‘멋지거나 재미 있기 때문에.’

여기에서 인용문문맥을 읽을 수 있습니다

기본적으로 연산자 과부하는 일종의 포인트, 통화 또는 복소수를 모델링하는 클래스에 적합합니다. 그러나 그 후에는 예제가 빨리 부족하기 시작합니다.

또 다른 요인은 개발자가 ‘&&’, ‘||’, 캐스트 연산자 및 물론 ‘new’와 같은 연산자를 오버로드하는 개발자가 C ++의 기능을 남용하는 것입니다. 이를 통과 가치 및 예외와 결합하여 발생하는 복잡성에 대해서는 예외적 인 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;

실제로 “에너지 = 4J”를 출력합니다.


답변

자바 디자이너들은 연산자 오버로딩이 가치가있는 것보다 더 많은 문제라고 판단했다. 그렇게 간단합니다.

모든 객체 변수가 실제로 참조 인 언어에서 연산자 오버로딩은 적어도 C ++ 프로그래머에게는 비논리적 인 추가 위험을 초래합니다. 상황을 C #의 == 연산자 오버로드 및 Object.EqualsObject.ReferenceEquals(또는 호출 된 모든 것과) 비교하십시오.


답변

Groovy 는 연산자 오버로딩을 가지고 있으며 JVM에서 실행됩니다. 성능 저하를 신경 쓰지 않으면 (매일 더 작아집니다). 메소드 이름에 따라 자동으로 수행됩니다. 예를 들어, ‘+’는 ‘plus (argument)’메소드를 호출합니다.


답변

필자는 이것이 의도적으로 의도를 전달하는 이름을 가진 함수를 개발자가 만들도록 의식적인 디자인 선택 일 수 있다고 생각합니다. C ++에서 개발자는 종종 주어진 연산자의 일반적으로 받아 들여지는 특성과 관련이없는 기능을 가진 연산자를 오버로드하여 연산자의 정의를 보지 않고 코드 조각이 무엇인지 판단하는 것이 거의 불가능합니다.