herencia java inheritance

java - herencia - ¿Realmente tengo un coche en mi garaje?



polymorphism in java (13)

Soy un novato en la programación de Java, tratando de dominar OOP.

Así que construí esta clase abstracta:

public abstract class Vehicle{....}

y 2 subclases:

public class Car extends Vehicle{....} public class Boat extends Vehicle{....}

Car and Boat también tiene algunos campos y métodos únicos que no son comunes (no tienen el mismo nombre, por lo que no puedo definir un método abstracto para ellos en Vehículo).

Ahora en mainClass tengo configurado mi nuevo Garaje:

Vehicle[] myGarage= new Vehicle[10]; myGarage[0]=new Car(2,true); myGarage[1]=new Boat(4,600);

Estaba muy contento con el polimorfismo hasta que intenté acceder a uno de los campos que son exclusivos de Car, como:

boolean carIsAutomatic = myGarage[0].auto;

El compilador no acepta eso. Trabajé alrededor de este problema usando el casting:

boolean carIsAutomatic = ((Car)myGarage[0]).auto;

Eso funciona ... pero no ayuda con métodos, solo campos. Lo que significa que no puedo hacer

(Car)myGarage[0].doSomeCarStuff();

Así que mi pregunta es: ¿qué tengo realmente en mi garaje? Estoy tratando de entender la intuición y de entender qué está pasando "detrás de escena".

Por el bien de los futuros lectores, un breve resumen de las respuestas a continuación:

  1. Sí, hay un Car en myGarage[]
  2. Al ser un lenguaje de tipo estático, el compilador de Java no otorgará acceso a métodos / campos que no sean "Vehículo", si se accede a ellos a través de una estructura de datos basada en la súper clase Vehículo (como Vehicle myGarage[] )
  3. En cuanto a cómo resolver, hay 2 enfoques principales a continuación:
    1. Utilice la conversión de tipos, lo que aliviará las preocupaciones del compilador y dejará cualquier error en el diseño para el tiempo de ejecución
    2. El hecho de que necesito un casting dice que el diseño es defectuoso. Si necesito acceso a capacidades que no sean de vehículos, entonces no debería almacenar los coches y los barcos en una estructura de datos basada en vehículos. O haga que todas esas capacidades pertenezcan al Vehículo, o use estructuras basadas en tipos más específicos (derivados)
  4. En muchos casos, la composición y / o las interfaces serían una mejor alternativa a la herencia. Probablemente el tema de mi próxima pregunta ...
  5. Además de muchos otros buenos conocimientos, si uno tiene tiempo para examinar las respuestas.

Soy un novato en la programación de Java, tratando de dominar OOP.

Solo mis 2 centavos. Intentaré hacerlo corto ya que ya se han dicho muchas cosas interesantes. Pero, de hecho, hay dos preguntas aquí. Uno sobre "OOP" y otro sobre cómo se implementa en Java.

En primer lugar, sí, tienes un coche en tu garaje. Así que tus suposiciones son correctas. Pero, Java es un lenguaje estáticamente tipado . Y el sistema de tipos en el compilador solo puede "saber" el tipo de sus diversos objetos por su declaración correspondiente. No por su uso. Si tienes un conjunto de Vehicle , el compilador solo lo sabe. Por lo tanto, comprobará que solo realice la operación permitida en cualquier Vehicle . (En otras palabras, métodos y atributos visibles en la declaración del Vehicle ).

Puede explicarle al compilador que "de hecho, sabe que este Vehicle es un Car " , utilizando un reparto explícito (Car) . el compilador te creerá, incluso si en Java hay una comprobación en tiempo de ejecución, esto podría llevar a una ClassCastException que evitará más daños si mentiste (otro lenguaje como C ++ no verificará en tiempo de ejecución) tienes que sabes lo que haces)

Finalmente, si realmente lo necesita, puede confiar en la identificación de tipo en tiempo de ejecución (es decir, instanceof ) para verificar el tipo "real" de un objeto antes de intentar lanzarlo. Pero esto se considera principalmente como una mala práctica en Java.

Como dije, esta es la forma Java de implementar OOP. Hay todo diferente clase Familia de idiomas ampliamente conocida como "idiomas dinámicos" , que solo verifica en tiempo de ejecución si una operación está permitida en un objeto o no. Con esos idiomas, no es necesario "subir" todos los métodos comunes a alguna clase base (posiblemente abstracta) para satisfacer el sistema de tipos. Esto se llama tipificación de pato .


Este es un buen lugar para la aplicación del patrón de diseño de Visitor .

La belleza de este patrón es que puede llamar código no relacionado en diferentes subclases de una superclase sin tener que hacer repartos extraños en todas partes o poner toneladas de métodos no relacionados en la superclase.

Esto funciona creando un objeto Visitor y permitiendo que nuestra clase de Vehicle accept() al visitante.

También puede crear muchos tipos de Visitor y llamar a código no relacionado utilizando los mismos métodos, solo una implementación de Visitor diferente, lo que hace que este patrón de diseño sea muy poderoso al crear clases limpias.

Una demo por ejemplo:

public class VisitorDemo { // We''ll use this to mark a class visitable. public static interface Visitable { void accept(Visitor visitor); } // This is the visitor public static interface Visitor { void visit(Boat boat); void visit(Car car); } // Abstract public static abstract class Vehicle implements Visitable { // NO OTHER RANDOM ABSTRACT METHODS! } // Concrete public static class Car extends Vehicle { public void doCarStuff() { System.out.println("Doing car stuff"); } @Override public void accept(Visitor visitor) { visitor.visit(this); } } // Concrete public static class Boat extends Vehicle { public void doBoatStuff() { System.out.println("Doing boat stuff"); } @Override public void accept(Visitor visitor) { visitor.visit(this); } } // Concrete visitor public static class StuffVisitor implements Visitor { @Override public void visit(Boat boat) { boat.doBoatStuff(); } @Override public void visit(Car car) { car.doCarStuff(); } } public static void main(String[] args) { // Create our garage Vehicle[] garage = { new Boat(), new Car(), new Car(), new Boat(), new Car() }; // Create our visitor Visitor visitor = new StuffVisitor(); // Visit each item in our garage in turn for (Vehicle v : garage) { v.accept(visitor); } } }

Como puede ver, StuffVisitor permite llamar a un código diferente en Boat o Car dependiendo de la implementación de la visit . También puede crear otras implementaciones del visitante para llamar a un código diferente con el mismo patrón .visit() .

También tenga en cuenta que al usar este método, no hay uso de instanceof ni de ningún tipo de comprobación de clase. El único código duplicado entre clases es el método void accept(Visitor) .

Si desea admitir 3 tipos de subclases concretas, por ejemplo, también puede agregar esa implementación en la interfaz de Visitor .


Le preguntaste a tu mayordomo:

Jeeves, ¿recuerdas mi garaje en la Isla de Java? Verifique si el primer vehículo estacionado allí es automático.

y el perezoso Jeeves dijo:

pero señor, ¿y si es un vehículo que no puede ser automático o no automático?

Eso es todo.

Ok, eso no es realmente todo, ya que la realidad está más tipificada en pato que estática. Por eso dije que Jeeves es perezoso.


Para responder a su pregunta, puede averiguar qué es exactamente lo que está en su garaje haciendo lo siguiente:

Vehicle v = myGarage[0]; if (v instanceof Car) { // This vehicle is a car ((Car)v).doSomeCarStuff(); } else if(v instanceof Boat){ // This vehicle is a boat ((Boat)v).doSomeBoatStuff(); }

ACTUALIZACIÓN: Como puede leer en los comentarios a continuación, este método está bien para soluciones simples pero no es una buena práctica, especialmente si tiene una gran cantidad de vehículos en su garaje. Así que úsalo solo si sabes que el garaje se mantendrá pequeño. Si ese no es el caso, busque "Evitar instanceof" en el desbordamiento de pila, hay varias formas de hacerlo.


Realmente estoy juntando las ideas de los demás aquí (y no soy un tipo de Java, así que esto es un pseudo en lugar de real) pero, en este ejemplo artificial, abstraería el enfoque de verificación de mi automóvil en una clase dedicada, eso Solo conoce los autos y solo se preocupa por los autos cuando mira garajes:

abstract class Vehicle { public abstract string getDescription() ; } class Transmission { public Transmission(bool isAutomatic) { this.isAutomatic = isAutomatic; } private bool isAutomatic; public bool getIsAutomatic() { return isAutomatic; } } class Car extends Vehicle { @Override public string getDescription() { return "a car"; } private Transmission transmission; public Transmission getTransmission() { return transmission; } } class Boat extends Vehicle { @Override public string getDescription() { return "a boat"; } } public enum InspectionBoolean { FALSE, TRUE, UNSUPPORTED } public class CarInspector { public bool isCar(Vehicle v) { return (v instanceof Car); } public bool isAutomatic(Car car) { Transmission t = car.getTransmission(); return t.getIsAutomatic(); } public bool isAutomatic(Vehicle vehicle) { if (!isCar(vehicle)) throw new UnsupportedVehicleException(); return isAutomatic((Car)vehicle); } public InspectionBoolean isAutomatic(Vehicle[] garage, int bay) { if (!isCar(garage[bay])) return InspectionBoolean.UNSUPPORTED; return isAutomatic(garage[bay]) ? InspectionBoolean.TRUE : InspectionBoolean.FALSE; } }

El punto es que ya has decidido que solo te interesan los autos cuando preguntas por la transmisión del auto. Así que solo pregúntale al CarInspector. Gracias al Enum de tres estados, ahora puede saber si es automático o incluso si no es un automóvil.

Por supuesto, necesitará diferentes Inspectores de Vehículos para cada vehículo que le interese. Y acaba de empujar el problema de qué VehicleInspector para crear una instancia de la cadena.

Así que en lugar de eso, es posible que desee ver las interfaces.

Resumen getTransmission a una interfaz (por ejemplo, HasTransmission ). De esa manera, puede verificar si un vehículo tiene una transmisión, o escribir un TransmissionInspector:

abstract class Vehicle { } class Transmission { public Transmission(bool isAutomatic) { this.isAutomatic = isAutomatic; } private bool isAutomatic; public bool getIsAutomatic() { return isAutomatic; } } interface HasTransmission { Transmission getTransmission(); } class Car extends Vehicle, HasTransmission { private Transmission transmission; @Override public Transmission getTransmission() { return transmission; } } class Bus extends Vehicle, HasTransmission { private Transmission transmission; @Override public Transmission getTransmission() { return transmission; } } class Boat extends Vehicle { } enum InspectionBoolean { FALSE, TRUE, UNSUPPORTED } class TransmissionInspector { public bool hasTransmission(Vehicle v) { return (v instanceof HasTransmission); } public bool isAutomatic(HasTransmission h) { Transmission t = h.getTransmission(); return t.getIsAutomatic(); } public bool isAutomatic(Vehicle v) { if (!hasTranmission(v)) throw new UnsupportedVehicleException(); return isAutomatic((HasTransmission)v); } public InspectionBoolean isAutomatic(Vehicle[] garage, int bay) { if (!hasTranmission(garage[bay])) return InspectionBoolean.UNSUPPORTED; return isAutomatic(garage[bay]) ? InspectionBoolean.TRUE : InspectionBoolean.FALSE; } }

Ahora está diciendo que solo se trata de la transmisión, independientemente del Vehículo, así que puede preguntar al TransmissionInspector. Tanto el autobús como el automóvil pueden ser inspeccionados por el TransmissionInspector, pero solo puede preguntar sobre la transmisión.

Ahora, puedes decidir que los valores booleanos no son lo único que te importa. En ese momento, es posible que prefiera usar un tipo admitido genérico, que exponga tanto el estado admitido como el valor:

class Supported<T> { private bool supported = false; private T value; public Supported() { } public Supported(T value) { this.isSupported = true; this.value = value; } public bool isSupported() { return supported; } public T getValue() { if (!supported) throw new NotSupportedException(); return value; } }

Ahora su inspector podría definirse como:

class TransmissionInspector { public Supported<bool> isAutomatic(Vehicle[] garage, int bay) { if (!hasTranmission(garage[bay])) return new Supported<bool>(); return new Supported<bool>(isAutomatic(garage[bay])); } public Supported<int> getGearCount(Vehicle[] garage, int bay) { if (!hasTranmission(garage[bay])) return new Supported<int>(); return new Supported<int>(getGearCount(garage[bay])); } }

Como he dicho, no soy un tipo de Java, por lo que parte de la sintaxis anterior puede estar equivocada, pero los conceptos deberían mantenerse. Sin embargo, no ejecute lo anterior en ningún lugar importante sin probarlo primero.


Si está en Java, podría usar reflexiones para verificar si una función está disponible y ejecutarla también


Si necesita marcar la diferencia entre el Car y el Boat en su garaje, debe almacenarlos en estructuras distintas.

Por ejemplo:

public class Garage { private List<Car> cars; private List<Boat> boats; }

Luego puede definir métodos que son específicos en barcos o específicos en automóviles.

¿Por qué entonces el polimorfismo tiene?

Digamos que el Vehicle es como:

public abstract class Vehicle { protected int price; public getPrice() { return price; } public abstract int getPriceAfterYears(int years); }

Cada Vehicle tiene un precio, por lo que se puede colocar dentro de la clase abstracta de Vehicle .

Sin embargo, la fórmula que determina el precio después de n años depende del vehículo, por lo que dejó que la clase implementadora lo definiera. Por ejemplo:

public Car extends Vehicle { // car specific private boolean automatic; @Override public getPriceAfterYears(int years) { // losing 1000$ every year return Math.max(0, this.price - (years * 1000)); } }

La clase Boat puede tener otra definición para getPriceAfterYears y atributos y métodos específicos.

Así que ahora de vuelta en la clase de Garage , puedes definir:

// car specific public int numberOfAutomaticCars() { int s = 0; for(Car car : cars) { if(car.isAutomatic()) { s++; } } return s; } public List<Vehicle> getVehicles() { List<Vehicle> v = new ArrayList<>(); // init with sum v.addAll(cars); v.addAll(boats); return v; } // all vehicles method public getAveragePriceAfterYears(int years) { List<Vehicle> vehicules = getVehicles(); int s = 0; for(Vehicle v : vehicules) { // call the implementation of the actual type! s += v.getPriceAfterYears(years); } return s / vehicules.size(); }

El interés del polimorfismo es poder llamar a getPriceAfterYears en un Vehicle sin preocuparse por la implementación.

Por lo general, el abatimiento es un signo de un diseño defectuoso: no almacene sus vehículos todos juntos si necesita diferenciar su tipo real.

Nota: por supuesto el diseño aquí se puede mejorar fácilmente. Es solo un ejemplo para demostrar los puntos.


Si opera en el tipo base, solo puede acceder a los métodos y campos públicos del mismo.

Si desea acceder al tipo extendido, pero tiene un campo del tipo base donde está almacenado (como en su caso), primero debe emitirlo y luego puede acceder a él:

Car car = (Car)myGarage[0]; car.doSomeCarStuff();

O más corto sin campo de temperatura:

((Car)myGarage[0]).doSomeCarStuff();

Dado que está utilizando objetos de Vehicle , solo puede llamar a métodos de la clase base sin lanzarlos. Por lo tanto, para su taller puede ser recomendable distinguir los objetos en diferentes arreglos, o mejores listas, un arreglo a menudo no es una buena idea, ya que es mucho menos flexible en el manejo que una clase basada en la Collection .


Su garaje contiene Vehículos, por lo que la vista de control estático del compilador de que tiene un Vehículo y como .auto es un campo Coche no puede acceder a él, dinámicamente es un Coche para que el elenco no cree ningún problema, si lo será un barco y tú intentas hacer el lanzamiento al auto aumentará una excepción en el tiempo de ejecución.


Su problema aquí se encuentra en un nivel más fundamental: usted construyó el Vehicle de tal manera que Garage necesita saber más sobre sus objetos de lo que la interfaz del Vehicle cede. Debe probar y construir la clase Vehicle desde la perspectiva del Garage (y, en general, desde la perspectiva de todo lo que va a utilizar Vehicle ): ¿qué tipo de cosas deben hacer con sus vehículos? ¿Cómo puedo hacer esas cosas posibles con mis métodos?

Por ejemplo, de su ejemplo:

bool carIsAutomatic = myGarage[0].auto;

¿Tu garaje quiere saber sobre el motor de un vehículo por ... razones? De todos modos, no hay necesidad de que esto solo sea expuesto por Car . Aún puede exponer un isAutomatic() no implementado isAutomatic() en Vehicle , luego implementarlo como return True en Boat y return this.auto en Car .

Sería aún mejor tener una enumeración EngineType tres valores ( HAS_NO_GEARS , HAS_GEARS_AUTO_SHIFT , HAS_GEARS_MANUAL_SHIFT ), que le permitiría a su código razonar sobre las características reales de un Vehicle genérico Vehicle clara y precisa. (De todos modos, necesitarías esta distinción para manejar motos).


Usted definió que su garaje almacenará vehículos, por lo que no le importa qué tipo de vehículos tiene. Los vehículos tienen características comunes como motor, rueda, comportamiento como movimiento. La representación real de estas características puede ser diferente, pero en la capa abstracta son las mismas. Usaste una clase abstracta, lo que significa que algunos atributos y comportamientos son exactamente los mismos para ambos vehículos. Si desea expresar que sus vehículos tienen características abstractas comunes, utilice la interfaz, como moverse, lo que puede significar que sea diferente en automóvil y barco. Ambos pueden ir del punto A al punto B, pero de una manera diferente (en la rueda o en el agua, por lo que la implementación será diferente). Así que tiene vehículos en el garaje que se comportan de la misma manera y no le importan las características específicas. de ellos.

Para responder al comentario:

Interfaz significa un contrato que describe cómo comunicarse con el mundo exterior. En el contrato, usted define que su vehículo puede moverse, puede ser dirigido, pero no describe cómo funcionará realmente, se describe en la implementación. Por clase abstracta, puede tener funciones en las que comparte alguna implementación, pero también tiene Función que no se sabe cómo se implementará.

Un ejemplo del uso de la clase abstracta:

abstract class Vehicle { protected abstract void identifyWhereIAm(); protected abstract void startEngine(); protected abstract void driveUntilIArriveHome(); protected abstract void stopEngine(); public void navigateToHome() { identifyWhereIAm(); startEngine(); driveUntilIArriveHome(); stopEngine(); } }

Utilizará los mismos pasos para cada vehículo, pero la implementación de los pasos diferirá según el tipo de vehículo. El auto podría usar el GPS, el barco podría usar el sonar para identificar dónde está.


Cree campos de nivel de Vehículo que ayudarán a hacer que cada Vehículo individual sea más distinto.

public abstract class Vehicle { public final boolean isCar; public final boolean isBoat; public Vehicle (boolean isCar, boolean isBoat) { this.isCar = isCar; this.isBoat = isBoat; } }

Establezca los campos de Nivel de vehículo en la clase hereditaria al valor apropiado.

public class Car extends Vehicle { public Car (...) { super(true, false); ... } } public class Boat extends Vehicle { public Boat (...) { super(false, true); ... } }

Implementar utilizando los campos de Nivel de vehículo para descifrar correctamente el tipo de vehículo.

boolean carIsAutomatic = false; if (myGarage[0].isCar) { Car car = (Car) myGarage[0]; car.carMethod(); carIsAutomatic = car.auto; } else if (myGarage[0].isBoat) { Boat boat = (Boat) myGarage[0]; boat.boatMethod(); }

Desde que le dijo a su compilador que todo en su garaje es un Vehículo, está atascado con los métodos y campos de nivel de clase de Vehículo. Si desea descifrar correctamente el tipo de vehículo, debe establecer algunos campos de nivel de clase, por ejemplo, isCar y isBoat que le darán al programador una mejor comprensión de qué tipo de vehículo está utilizando.

Java es un lenguaje de tipo seguro, por lo que es mejor teclear siempre la verificación antes de manejar datos que se han emitido como los de sus Boat y Car .


Modelar los objetos que desea presentar en un programa (para resolver algún problema) es una cosa, la codificación es otra historia. En su código, creo que, en esencia, no es apropiado modelar un garaje utilizando una matriz. Las matrices no se deben considerar a menudo como objetos, aunque sí parecen serlo, por lo general, por el tipo de integridad de un lenguaje y de cierta familiaridad, pero la matriz como tipo es en realidad solo una computadora cosa, en mi humilde opinión, especialmente en Java, donde no se pueden extender matrices.

Entiendo que modelar correctamente una clase para representar un garaje no ayudará a responder su pregunta de "autos en un garaje"; Sólo un consejo.

Regresa al código. Además de obtener un bloqueo para la programación orientada a objetos, algunas preguntas serían útiles para crear una escena, por lo tanto, para comprender mejor el problema que desea resolver (suponiendo que haya uno, no solo "algo"):

  1. ¿Quién o qué quiere entender carIsAutomatic?
  2. Dado carIsAutomatic, ¿quién o qué realizaría doSomeCarStuff?

Puede ser algún inspector, o alguien que sepa solo cómo conducir autos de transmisión automática, etc., pero desde la perspectiva del taller, todo lo que sabe es que tiene algún vehículo, por lo tanto (en este modelo) es responsabilidad de este inspector. o conductor para decir si es un coche o un barco; en este momento, es posible que desee comenzar a crear otro grupo de clases para representar tipos similares de * actores * en la escena. Depende del problema a resolver, si realmente tiene que hacerlo, puede modelar el garaje para que sea un sistema súper inteligente para que se comporte como una máquina expendedora, en lugar de un garaje normal, que tenga un botón que diga "Car" y otro que diga "Barco", para que las personas puedan presionar el botón para obtener un auto o un barco como quieran,que a su vez hace que este garaje súper inteligente sea responsable de decir qué (un automóvil o un bote) debe presentarse a sus usuarios; para seguir esta improvisación, el garaje puede requerir cierta contabilidad cuando acepta un vehículo, alguien puede tener que proporcionar la información, etc., todas estas responsabilidades van más allá de una simpleClase principal

Habiendo dicho esto, sin duda entiendo todos los problemas, junto con los marcadores, para codificar un programa OO, especialmente cuando el problema que intenta resolver es muy simple, pero OO es una forma viable de resolver muchos otros problemas. Desde mi experiencia, con algunos comentarios que proporcionan casos de uso, las personas comienzan a diseñar escenas de cómo los objetos interactúan entre sí, los clasifican en clases (así como en interfaces en Java) y luego usan algo como su clase principal para arrancar el mundo .