[java] Java가 정적 메소드 대체를 허용하지 않는 이유는 무엇입니까?

정적 메소드를 대체 할 수없는 이유는 무엇입니까?

가능하면 예를 사용하십시오.



답변

재정의는 클래스의 인스턴스가 있어야합니다. 다형성의 요점은 클래스를 서브 클래스 할 수 있고 해당 서브 클래스를 구현하는 객체는 수퍼 클래스에 정의 된 (및 서브 클래스에서 재정의 된) 동일한 메소드에 대해 다른 동작을 갖습니다. 정적 메소드는 클래스의 인스턴스와 연관되지 않으므로 개념을 적용 할 수 없습니다.

이에 영향을주는 Java 설계를 추진할 때 고려해야 할 두 가지 사항이 있습니다. 하나는 성능에 대한 우려였습니다. 스몰 토크가 너무 느려서 (쓰레기 수거 및 다형성 호출이 그 일부 임) 많은 비판이 있었으며 Java 제작자는이를 피하기로 결정했습니다. 또 다른 하나는 Java의 대상이 C ++ 개발자라는 결정이었습니다. 정적 메소드가 작동하는 방식으로 작동하게 만드는 것은 C ++ 프로그래머에게 친숙한 이점이 있었으며 호출 할 메소드를 파악하기 위해 런타임까지 기다릴 필요가 없기 때문에 매우 빠릅니다.


답변

개인적으로 나는 이것이 Java 디자인의 결함이라고 생각합니다. 예, 예, 정적이 아닌 메소드는 인스턴스에 첨부되는 반면 정적 메소드는 클래스 등에 첨부된다는 것을 이해합니다. 여전히 다음 코드를 고려하십시오.

public class RegularEmployee {
    private BigDecimal salary;

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }

    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".02");
    }

    public BigDecimal calculateBonus() {
        return salary.multiply(getBonusMultiplier());
    }

    /* ... presumably lots of other code ... */
}

public class SpecialEmployee extends RegularEmployee {
    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".03");
    }
}

이 코드는 예상대로 작동하지 않습니다. 즉, SpecialEmployee는 일반 직원과 마찬가지로 2 %의 보너스를받습니다. 그러나 “정적”을 제거하면 SpecialEmployee는 3 %의 보너스를받습니다.

(실제로이 예제는 실제 코딩 방식이 아니라면 보너스 멀티 플라이어가 하드 코딩 된 것이 아닌 데이터베이스에 있기를 원할 것입니다. 그러나 그것은 예제를 많이 사용하고 싶지 않기 때문입니다. 요점과 관련이없는 코드).

getBonusMultiplier를 정적으로 만들고 싶을 수도 있습니다. 각 범주에 직원의 인스턴스가 없어도 모든 범주의 직원에 대해 보너스 승수를 표시 할 수 있습니다. 그러한 예제 인스턴스를 찾는 시점은 무엇입니까? 새 범주의 직원을 만들면서 아직 할당 된 직원이없는 경우 어떻게합니까? 이것은 논리적으로 정적 함수입니다.

그러나 작동하지 않습니다.

그리고 예, 그렇습니다. 위 코드를 다시 작성하여 작동하게하는 방법에는 여러 가지가 있습니다. 내 요점은 해결할 수없는 문제를 일으키는 것이 아니라, 합리적인 사람이 기대하는 것처럼 언어가 작동하지 않기 때문에 경고하지 않는 프로그래머를위한 함정을 만드는 것입니다.

아마도 OOP 언어 용 컴파일러를 작성하려고했을 때 정적 함수를 재정의 할 수 없도록 구현하는 이유를 빨리 알 수있을 것입니다.

또는 Java가 이런 식으로 동작하는 이유가있을 수 있습니다. 누구나이 행동으로 장점을 지적 할 수 있습니까? 이로 인해 더 쉬운 문제 범주가 있습니까? Java 언어 사양을 가리켜 서 “이것이 어떻게 작동하는지 문서화되어 있습니다”라고 말하지 마십시오. 나도 알아 그러나 왜 이런 식으로 행동해야 하는가? ( “제대로 작동하는 것이 너무 어려웠습니다”라는 것 외에도 …)

최신 정보

@VicKirk : Java가 정적을 처리하는 방식에 맞지 않기 때문에 이것이 “나쁜 디자인”이라는 것을 의미한다면 제 대답은 “물론, 물론입니다.”입니다. 원래 게시물에서 말했듯이 작동하지 않습니다. 그러나 이것이 작동하는 언어, 근본적으로 가상 함수처럼 정적 변수를 재정의 할 수있는 언어에 근본적으로 잘못된 것이 있다는 의미에서 나쁜 디자인을 의미하는 경우 어떻게 든 모호성을 유발하거나 불가능할 수 있습니다 효율적으로 또는 그와 같은 일부를 구현하면 “왜? 개념에 어떤 문제가 있습니까?”라고 대답합니다.

내가 제공하는 예는하고 싶은 매우 자연스러운 일이라고 생각합니다. 인스턴스 데이터에 의존하지 않는 함수가 있고 인스턴스와 독립적으로 호출하고 인스턴스 메소드 내에서 호출하려는 클래스가 있습니다. 왜 이것이 작동하지 않습니까? 나는이 상황에 수년에 걸쳐 상당한 횟수를 겪었습니다. 실제로 함수를 가상으로 만든 다음 더미 인스턴스로 가상 메소드에 대한 호출을 전달하는 정적 메소드가 인생의 유일한 목적인 정적 메소드를 작성하여 문제를 해결했습니다. 그것은 거기에 도착하는 매우 원형 교차로처럼 보인다.


답변

짧은 대답은 : 그것은 가능하지만 Java는 그렇지 않습니다.

다음은 Java 의 현재 상태 를 보여주는 코드입니다 .

파일 Base.java:

package sp.trial;
public class Base {
  static void printValue() {
    System.out.println("  Called static Base method.");
  }
  void nonStatPrintValue() {
    System.out.println("  Called non-static Base method.");
  }
  void nonLocalIndirectStatMethod() {
    System.out.println("  Non-static calls overridden(?) static:");
    System.out.print("  ");
    this.printValue();
  }
}

파일 Child.java:

package sp.trial;
public class Child extends Base {
  static void printValue() {
    System.out.println("  Called static Child method.");
  }
  void nonStatPrintValue() {
    System.out.println("  Called non-static Child method.");
  }
  void localIndirectStatMethod() {
    System.out.println("  Non-static calls own static:");
    System.out.print("  ");
    printValue();
  }
  public static void main(String[] args) {
    System.out.println("Object: static type Base; runtime type Child:");
    Base base = new Child();
    base.printValue();
    base.nonStatPrintValue();
    System.out.println("Object: static type Child; runtime type Child:");
    Child child = new Child();
    child.printValue();
    child.nonStatPrintValue();
    System.out.println("Class: Child static call:");
    Child.printValue();
    System.out.println("Class: Base static call:");
    Base.printValue();
    System.out.println("Object: static/runtime type Child -- call static from non-static method of Child:");
    child.localIndirectStatMethod();
    System.out.println("Object: static/runtime type Child -- call static from non-static method of Base:");
    child.nonLocalIndirectStatMethod();
  }
}

이것을 실행하면 (Java 1.6을 사용하여 Eclipse에서 Mac에서 수행) 다음을 얻습니다.

Object: static type Base; runtime type Child.
  Called static Base method.
  Called non-static Child method.
Object: static type Child; runtime type Child.
  Called static Child method.
  Called non-static Child method.
Class: Child static call.
  Called static Child method.
Class: Base static call.
  Called static Base method.
Object: static/runtime type Child -- call static from non-static method of Child.
  Non-static calls own static.
    Called static Child method.
Object: static/runtime type Child -- call static from non-static method of Base.
  Non-static calls overridden(?) static.
    Called static Base method.

여기에서 유일한 놀람이 될 수 있습니다 (그리고 어떤 질문에 대한됩니다) 표시의 경우는 수하는 첫 번째 사례 :

“런타임 유형은 객체 인스턴스 ( obj.staticMethod())를 사용하여 호출 할 때 호출되는 정적 메소드를 결정하는 데 사용되지 않습니다 .”

그리고 마지막 경우 :

“클래스의 객체 메소드 내에서 정적 메소드를 호출 할 때 선택한 정적 메소드 는 객체의 런타임 유형을 정의하는 클래스가 아니라 클래스 자체에서 액세스 할 수 있는 메소드입니다.”

객체 인스턴스로 호출

정적 호출은 컴파일 타임에 해결되지만 비 정적 메소드 호출은 런타임에 해결됩니다. 정적 메소드는 (부모로부터) 상속 되지만 (자식에 의해) 재정의 되지는 않습니다 . 당신이 달리 예상했다면 이것은 놀랍습니다.

객체 메소드 내에서 호출

객체 메소드 호출은 런타임 유형을 사용하여 해결되지만 정적 ( class ) 메소드 호출은 컴파일 시간 (선언) 유형을 사용하여 해결됩니다.

규칙 변경

이러한 규칙을 변경하여라는 예제의 마지막 호출에서 Child.printValue()컴파일러가 선언 된 객체 클래스로 컴파일 타임에 호출을 해결하는 대신 런타임에 정적 호출에 유형을 제공해야합니다. 문맥). 정적 호출은 오늘날 객체 메소드 호출과 마찬가지로 (동적) 유형 계층 구조를 사용하여 호출을 해결할 수 있습니다.

이것은 쉽게 할 수 있으며 (Java를 변경 한 경우 : -O) 전혀 부당한 것은 아니지만 몇 가지 흥미로운 고려 사항이 있습니다.

주요 고려 사항은 어떤 정적 메서드 호출을 수행 해야하는지 결정 해야한다는 것입니다.

현재 Java에는 obj.staticMethod()호출이 호출로 대체되는 언어 ObjectClass.staticMethod()(일반적으로 경고가 표시됨) 에이 “질투”가 있습니다 . [ 참고 : ObjectClass 의 컴파일 타임 유형입니다 obj.] 런타임 유형 의을 사용하여 이러한 방식으로 재정의 할 수 있습니다 obj.

그렇게하면 메소드 본문을 읽기가 더 어려워 질 것이다. 부모 클래스의 정적 호출은 동적으로 “재 라우팅” 될 수있다 . 이를 피하려면 클래스 이름으로 정적 메소드를 호출해야합니다. 이렇게하면 컴파일 타임 유형 계층 구조 (지금과 같이)로 호출이보다 명확하게 해결됩니다.

정적 메소드를 호출하는 다른 방법은 더 까다 롭습니다. 의 런타임 유형을 사용 this.staticMethod()하는 것과 같은 의미 여야합니다 . 그러나 이로 인해 기존 프로그램에서 두통이 발생할 수 있습니다 .obj.staticMethod()thisthis.method()

그래서 무의미한 통화는 staticMethod()어떻습니까? 나는 그들이 오늘날과 똑같이하고 지역 수업 맥락을 사용하여 무엇을 해야할지 결정하는 것이 좋습니다. 그렇지 않으면 큰 혼란이 생길 ​​것입니다. 물론 그것은 그것이 의미하는 method()것을 의미 this.method()하는 경우 method가 아닌 정적 메서드, 그리고 ThisClass.method()경우는 method정적 방법이었다. 이것은 또 다른 혼란의 근원입니다.

다른 고려 사항

우리가이 동작을 변경 (정적 호출 가능성이 동적으로 로컬이 아닌했다), 우리는 아마의 의미를 다시 방문 할 것 final, private그리고 protected예선과 (와) 같은 static클래스의 방법. 우리는 모두 메소드 private staticpublic final메소드가 재정의되지 않았으므로 컴파일 타임에 안전하게 해결 될 수 있으며 로컬 참조로 읽을 수 있도록 “안전” 하다는 사실에 익숙해 져야합니다.


답변

실제로 우리는 틀렸다.
Java에서는 기본적으로 정적 메소드를 대체 할 수 없지만 Java에서 클래스 및 메소드 클래스의 문서를 자세히 살펴보면 다음 해결 방법으로 정적 메소드 대체를 에뮬레이트하는 방법을 여전히 찾을 수 있습니다.

import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;

class RegularEmployee {

    private BigDecimal salary = BigDecimal.ONE;

    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }
    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".02");
    }
    public BigDecimal calculateBonus() {
        return salary.multiply(this.getBonusMultiplier());
    }
    public BigDecimal calculateOverridenBonus() {
        try {
            // System.out.println(this.getClass().getDeclaredMethod(
            // "getBonusMultiplier").toString());
            try {
                return salary.multiply((BigDecimal) this.getClass()
                    .getDeclaredMethod("getBonusMultiplier").invoke(this));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }
    // ... presumably lots of other code ...
}

final class SpecialEmployee extends RegularEmployee {

    public static BigDecimal getBonusMultiplier() {
        return new BigDecimal(".03");
    }
}

public class StaticTestCoolMain {

    static public void main(String[] args) {
        RegularEmployee Alan = new RegularEmployee();
        System.out.println(Alan.calculateBonus());
        System.out.println(Alan.calculateOverridenBonus());
        SpecialEmployee Bob = new SpecialEmployee();
        System.out.println(Bob.calculateBonus());
        System.out.println(Bob.calculateOverridenBonus());
    }
}

결과 출력 :

0.02
0.02
0.02
0.03

우리가 달성하려고했던 것 🙂

세 번째 변수 Carl을 RegularEmployee로 선언하고 SpecialEmployee의 인스턴스에 할당하더라도 첫 번째 경우 RegularEmployee 메서드를 호출하고 두 번째 경우에는 SpecialEmployee 메서드를 호출합니다

RegularEmployee Carl = new SpecialEmployee();

System.out.println(Carl.calculateBonus());
System.out.println(Carl.calculateOverridenBonus());

출력 콘솔을 살펴보십시오.

0.02
0.03

😉


답변

정적 메소드는 JVM에 의해 전역으로 처리되며 오브젝트 인스턴스에는 전혀 바인딩되지 않습니다.

스몰 토크와 같은 언어에서와 같이 클래스 객체에서 정적 메소드를 호출 할 수 있다면 개념적으로 가능하지만 Java에서는 그렇지 않습니다.

편집하다

정적 메소드 를 오버로드 할 수 있습니다 . 그러나 클래스는 일류 객체가 아니기 때문에 정적 메서드를 재정 의 할 수 없습니다 . 리플렉션을 사용하여 런타임에 객체의 클래스를 가져올 수 있지만 얻은 객체는 클래스 계층 구조와 평행하지 않습니다.

class MyClass { ... }
class MySubClass extends MyClass { ... }

MyClass obj1 = new MyClass();
MySubClass obj2 = new MySubClass();

ob2 instanceof MyClass --> true

Class clazz1 = obj1.getClass();
Class clazz2 = obj2.getClass();

clazz2 instanceof clazz1 --> false

수업에 대해 생각해 볼 수는 있지만 거기서 멈 춥니 다. 당신은 사용하여 정적 메소드를 호출하지 않는 clazz1.staticMethod(),하지만 사용 MyClass.staticMethod(). 정적 메소드는 오브젝트에 바인드되지 않으므로 정적 메소드에 대한 개념 this이나 개념이 없습니다 super. 정적 메소드는 전역 함수입니다. 결과적으로 다형성에 대한 개념도 없으므로 메서드 재정의는 의미가 없습니다.

그러나 MyClassSmalltalk (또는 한 의견에서 제안한 것처럼 JRuby 일 수도 있지만 JRuby에 대해서는 아는 바 없음)와 같이 메소드를 호출하는 런타임에 객체 인 경우 가능할 수 있습니다 .

아 그래 … 한가지 더 객체를 통해 정적 메소드를 호출 할 수 obj1.staticMethod()있지만 실제로 구문 설탕은 MyClass.staticMethod()피해야합니다. 일반적으로 최신 IDE에서는 경고가 발생합니다. 그들이 왜이 지름길을 허용했는지 모르겠습니다.


답변

메소드의 재정의는 동적 디스패치에 의해 가능 합니다. 즉, 선언 된 객체 유형이 동작을 결정하지 않고 런타임 유형을 결정합니다.

Animal lassie = new Dog();
lassie.speak(); // outputs "woof!"
Animal kermit = new Frog();
kermit.speak(); // outputs "ribbit!"

모두에도 불구 lassie하고이 kermit유형의 객체로 선언 Animal, 자신의 행동 (방법 .speak())에 따라 다릅니다 동적 파견 때문에만이 바인드 메소드 호출 .speak()런타임에 구현 -하지 컴파일시.

이제 static키워드가 이해되기 시작합니다. “정적”이라는 단어는 “동적”의 반의어입니다. 따라서 정적 메서드를 재정의 할 수없는 이유는 정적 멤버에 동적 디스패치가 없기 때문입니다. 정적은 문자 적으로 “동적 아님”을 의미하기 때문입니다. static키워드 가 동적으로 전달되어 재정의 될 수있는 경우 키워드는 더 이상 의미가 없습니다.


답변

예. 실제로 Java는 정적 메소드를 대체 할 수 있으며 이론적으로 Java에서 정적 메소드를 대체하면 컴파일 및 실행이 원활하지만 Java의 기본 특성 인 다형성이 손실됩니다. 컴파일하고 실행하는 것은 불가능합니다. 당신은 당신의 대답을 얻을 것입니다. 예를 들어 클래스 동물과 정적 메소드 eat ()를 가지고 있고 서브 클래스의 정적 메소드를 재정의하면 Dog라고합니다. 그런 다음 어디서나 Dog 객체를 Animal Reference에 할당하고 Java Dog의 eat ()에 따라 eat ()를 호출하면 정적 동물의 eat ()가 호출됩니다.

class Animal {
    public static void eat() {
        System.out.println("Animal Eating");
    }
}

class Dog extends Animal{
    public static void eat() {
        System.out.println("Dog Eating");
    }
}

class Test {
    public static void main(String args[]) {
       Animal obj= new Dog();//Dog object in animal
       obj.eat(); //should call dog's eat but it didn't
    }
}


Output Animal Eating

Java의 다형성 원칙에 따르면 출력은이어야 Dog Eating합니다.
그러나 다형성을 지원하기 위해 Java는 후기 바인딩을 사용하기 때문에 결과가 달랐습니다. 즉, 메소드는 런타임시에만 호출되지만 정적 메소드의 경우에는 호출되지 않습니다. 정적 메소드 컴파일러는 런타임이 아닌 컴파일 타임에 메소드를 호출하므로 참조에 따라 메소드를 얻습니다. 객체에 따라 참조를 포함하지 않으므로 정적 오버 링을 지원하지만 이론적으로는 그렇지 않습니다. ‘티.