Приведение экземпляра суперкласса, возвращенного методом суперкласса, в экземпляр подкласса - PullRequest
0 голосов
/ 13 октября 2019

Рассмотрим:

public class Super
{
    Integer state = 0;

    Super f()
    {
        Super x = new Super();
        x.state = this.state + 1;
        return x;
    }
}

public class Sub extends Super
{
    Sub f()
    {
        return (Sub) super.f();
    }
}

public class Main
{
    public static void main (String[] args)
    {
        Sub s = new Sub();
        Sub t = s.f();
    }
}

Это приводит к сбою:

Exception in thread "main" java.lang.ClassCastException: Super cannot be cast to Sub
    at Sub.f(Sub.java:5)
    at Main.main(Main.java:6)

И, тем не менее, экземпляр Sub не имеет ни малейшего признака, отличного от Super.

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

Можно ли заставить этот код работать, не дублируя определение f?

Ответы [ 3 ]

1 голос
/ 13 октября 2019

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

Когда ваш пример запускает return (Sub) super.f();, он потерпит неудачу с java.lang.ClassCastException, так как в этом случае фактический объект не является Sub, это Super. Вы можете увидеть это, немного изменив свой метод следующим образом:

Sub f() {
    Super sup = super.f();
    if(sup instanceof  Sub) {
        System.out.println("Instance of Sub");
    } else {
        System.out.println("Not an instance of Sub, it is a : " +  sup.getClass());
    }
    return (Sub) sup;
}

Причина здесь в том, что в Super f() вы создаете чистый объект Super и возвращаете его, поэтому фактический тип объекта равен Super и поскольку Super не является Sub, поэтому мы не можем привести его к Sub.

Вот пример:

public class Animal {

  private String type;
  private int age;

  public Animal(String type, int age) {
    this.type = type;
    this.age = age;
  }

  public int getAge() {
    return age;
  }

}

public class Cat extends Animal {
  public Cat(String type, int age) {
    super(type, age);
  }
}
public class Dog extends Animal {
  public Dog(String type, int age) {
    super(type, age);
  }
}

public class Vet {

  public static void printHumanYears(Animal animal) {
    if(animal instanceof Dog) {
        Dog dog = (Dog) animal;
        System.out.println("Dogs age : " + dog.getAge() * 7);
    } else if(animal instanceof Cat) {
        Cat cat = (Cat) animal;
        System.out.println("Cat age : " + cat.getAge() * 5);
    } else {
        System.out.println("Not sure how to calculate age for a " + animal.getClass());
    }
  }

  public static void main (String [] args) {
    Dog dog = new Dog("Collie", 10);
    Cat cat = new Cat("Tabby", 20);
    Animal animal = new Animal("Animal", 20);
    printHumanYears(dog);
    printHumanYears(cat);
    printHumanYears(animal);
  }

}

Когда вызывается метод printHumanYears(Animal animal), ему передается объект Animal, затем мы проверяем, к какому экземпляру относится этот объект Animal, и затем соответствующим образом понижаем. Причина, по которой мы не получили бы ClassCastException, заключается в том, что целевой объект (Dog или Cat) был создан во время выполнения. Когда мы передаем Animal, он не может быть понижен до Dog или Cat, потому что это ни то, ни другое, когда он был изначально создан.

Хорошо помнить:

  • Приведение не меняет фактический тип объекта.
  • Приведение приводит только к изменению типа ссылки.
  • Обновление всегда безопасно и никогда не приводит к ошибкам.
  • Понижение не является безопасным и может вызвать ClassCastException, поэтому всегда хорошая идеяиспользовать instanceof, чтобы проверить, что тип цели - это то, что вы пытаетесь разыграть.
1 голос
/ 13 октября 2019

Давайте рассмотрим первую часть вопроса:

Можно ли заставить этот код работать

В текущем состоянии код не может работать, потому что этов соответствии со спецификацией языка, определенной в Разделе 5.5.3 Спецификации языка Java , которая гласит:

Если Tтип класса, тогда R должен быть того же класса (§4.3.4), что и T, или подклассом T, либо генерируется исключение времени выполнения

В примере, представленном в вопросе, (return (Sub) super.f();), T - это Sub, а R - это Super.

Теперь рассмотрим вторую часть вопроса:

без дублированияопределение f

Одним из способов достижения этого является перемещение кода, который является общим для super и Sub, в отдельный метод:

public class Super {
    Integer state = 0;

    Super f() {
        Super x = new Super();
        incrementState(x);
        return x;
    }

    protected void incrementState(Super x) {
        x.state = this.state + 1;
    }
}

public class Sub extends Super {
    Sub f() {
        Sub s = new Sub();
        incrementState(s);
        return s;
    }
}
1 голос
/ 13 октября 2019

Да, вы можете, но ваша f операция должна быть защищена внутри иерархии классов.

static class Super implements Cloneable {
    int state = 0;

    // inspection
    public int getState() {
        return state;
    }

    // default behavior
    public void doSomething() {
        state += 1;
    }

    // unsafe, should be protected
    public <S extends Super> S f() throws CloneNotSupportedException {
        // unchecked cast !!!
        S s = (S) this.clone();
        s.doSomething();
        return s;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

static class Sub extends Super {
    // override behavior
    @Override
    public void doSomething() {
        super.doSomething();
        super.doSomething();
    }
}

public static void main(String[] args) throws Exception {
    Sub s = new Sub();
    System.out.printf("%d => %d%n", s.getState(), s.f().getState());
}

С выводом:

0 => 2

Здесь,f возвращает не Super, а любое S, расширяющее Super, теперь f будет закрыто под групповыми операциями (каждая операция суперкласса). Но должен быть защищен , так как это приведение не проверяется во время компиляции. Например,

Sub s = new Sub();
Sub t = s.f();  // ok
Sub2 u = s.f(); // java.lang.ClassCastException !!!!

Именно поэтому операция f должна быть защищена и не должна быть публично раскрыта.

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