Java 返回子类类型的超类

Java 返回子类类型的超类,java,generics,inheritance,polymorphism,Java,Generics,Inheritance,Polymorphism,考虑以下在OO文献中非常常见的场景: public class Vehicle { } public class Car extends Vehicle { } public class Bike extends Vehicle { } 现在,假设我想创建一个函数get(),该函数将始终返回子类类型,以便获得子类实例: public static void main(String[] args) { Car car = Car.get(); Bike bike = Bik

考虑以下在OO文献中非常常见的场景:

public class Vehicle {
}

public class Car extends Vehicle {
}

public class Bike extends Vehicle {
}
现在,假设我想创建一个函数
get()
,该函数将始终返回子类类型,以便获得子类实例:

public static void main(String[] args) {

    Car car = Car.get();
    Bike bike = Bike.get();
    car.Start();
    bike.Start();
}
可以在知道其返回类型的超类中实现
公共静态T get()
,而无需将其作为参数传递或从
对象进行强制转换

更新:


在第一个版本中,我谈论的是抽象类。现在我删除了这个抽象,并且使用了一个常规类。

在这个场景中,如果调用超类上的静态方法,Java不知道要返回哪种类型的子类。您提到希望在不传入参数的情况下执行此操作,但您不能。如果可以传入一个参数,您可以这样做:

package com.stackexchange.stackoverflow;

public class Question19281170 {
  public static void main(String[] args) throws InstantiationException, IllegalAccessException {
    Car car = Vehicle.get(Car.class);
    Bike bike = Bike.get(Bike.class);
    car.start();
    bike.start();
  }

  static abstract class Vehicle {
    public void start() {
      System.out.println(String.format("Start %s", toString()));
    }
    public static <T extends Vehicle> T get(Class<T> clazz) throws IllegalAccessException, InstantiationException {
      return clazz.newInstance();
    }
    @Override
    public String toString() {
      return "Vehicle";
    }
  }

  static class Car extends Vehicle {
    @Override
    public String toString() {
      return "Car";
    }
  }

  static class Bike extends Vehicle {
    @Override
    public String toString() {
      return "Bike";
    }
  }
}

在这个场景中,如果调用超类上的静态方法,Java不知道要返回哪种类型的子类。您提到希望在不传入参数的情况下执行此操作,但您不能。如果可以传入一个参数,您可以这样做:

package com.stackexchange.stackoverflow;

public class Question19281170 {
  public static void main(String[] args) throws InstantiationException, IllegalAccessException {
    Car car = Vehicle.get(Car.class);
    Bike bike = Bike.get(Bike.class);
    car.start();
    bike.start();
  }

  static abstract class Vehicle {
    public void start() {
      System.out.println(String.format("Start %s", toString()));
    }
    public static <T extends Vehicle> T get(Class<T> clazz) throws IllegalAccessException, InstantiationException {
      return clazz.newInstance();
    }
    @Override
    public String toString() {
      return "Vehicle";
    }
  }

  static class Car extends Vehicle {
    @Override
    public String toString() {
      return "Car";
    }
  }

  static class Bike extends Vehicle {
    @Override
    public String toString() {
      return "Bike";
    }
  }
}

继承是一个强大的特性,非常适合构建基于类的层次结构,如JavaFX中的层次结构

但是为继承设计类是一件痛苦的事情,并且充满了潜在的问题。这里有一个常见的问题:如果您决定使用HashMap来表示车辆集合,该怎么办?问题是,为子类编写equals()和hashcode()方法的方式不会破坏equals()和hashcode()的契约,这比听起来要困难。此外,继承破坏了封装,使子类依赖于超类中的实现细节,这会使代码变得脆弱或更难维护

继承的另一种选择是使用基于接口的类型系统,类似于Java Collections API所使用的类型系统

public interface Vehicle {
    public void start();
    :
}

public class Car implements Vehicle {
}

public class Bike implements Vehicle {
}
接口作为基本类型发挥作用,这有几个优点。最重要的是,实现Vehicle接口的任何类都可以传递给请求Vehicle的任何方法,无论该类是什么。尽管这看起来类似于继承,但并没有实际的基于类的层次结构。实现细节、封装和equals()契约的所有问题都消失了

基于接口的类型系统的缺点有几个:

  • 您必须能够通过接口定义可靠地指定基类型的行为。如果没有这一点,作为基类型的接口将变得不方便

  • 接口必须由使用它的每个类完全实现(除非为接口创建抽象框架实现类,例如集合API中的AbstractList)

  • 很难更改接口。。。但是编辑超类通常是有问题的。记住,对超类的更改可能会在其子类中产生副作用,这些副作用取决于超类中的实现细节

使用基于接口的类型会使您的情况看起来像这样(使用静态工厂):


TL;DR:基于接口的类型系统对于解决创建基于类的类型系统(继承破坏封装)的问题非常有用,并且是解决某些问题的一个很好的替代方案。

继承是一个强大的功能,非常适合构建基于类的层次结构,如JavaFX中的层次结构

但是为继承设计类是一件痛苦的事情,并且充满了潜在的问题。这里有一个常见的问题:如果您决定使用HashMap来表示车辆集合,该怎么办?问题是,为子类编写equals()和hashcode()方法的方式不会破坏equals()和hashcode()的契约,这比听起来要困难。此外,继承破坏了封装,使子类依赖于超类中的实现细节,这会使代码变得脆弱或更难维护

继承的另一种选择是使用基于接口的类型系统,类似于Java Collections API所使用的类型系统

public interface Vehicle {
    public void start();
    :
}

public class Car implements Vehicle {
}

public class Bike implements Vehicle {
}
接口作为基本类型发挥作用,这有几个优点。最重要的是,实现Vehicle接口的任何类都可以传递给请求Vehicle的任何方法,无论该类是什么。尽管这看起来类似于继承,但并没有实际的基于类的层次结构。实现细节、封装和equals()契约的所有问题都消失了

基于接口的类型系统的缺点有几个:

  • 您必须能够通过接口定义可靠地指定基类型的行为。如果没有这一点,作为基类型的接口将变得不方便

  • 接口必须由使用它的每个类完全实现(除非为接口创建抽象框架实现类,例如集合API中的AbstractList)

  • 很难更改接口。。。但是编辑超类通常是有问题的。记住,对超类的更改可能会在其子类中产生副作用,这些副作用取决于超类中的实现细节

使用基于接口的类型会使您的情况看起来像这样(使用静态工厂):


TL;DR:基于接口的类型系统有助于解决创建基于类的类型系统(继承破坏封装)的问题对于某些问题,Java是一个很好的替代解决方案。

我想知道Java是否支持为什么要这样做抽象类的全部目的就是隐藏具体的细节。为什么不直接说:
Vehicle car=new car()…等等?不,不是。Java有