¿Por qué asignamos una referencia principal al objeto secundario en Java?


84

Estoy haciendo una pregunta bastante simple, pero estoy un poco confundido en esto.

Supongamos que tengo una clase Parent:

public class Parent {

    int name;
}

Y tener otra clase Child:

public class Child extends Parent{

    int salary;
}

Y finalmente mi clase Main.java

public class Main {

    public static void main(String[] args)
    {
        Parent parent = new Child();
        parent.name= "abcd";
    }
}

Si hago un objeto infantil como

Child child = new Child():

Entonces el childobjeto puede acceder a ambas name and salaryvariables.

Mi pregunta es:

Parent parent = new Child();

da acceso a la única namevariable de la clase Parent. Entonces, ¿cuál es el uso exacto de esta línea?

 Parent parent = new Child();

Y también cuando se usa polimorfismo dinámico, ¿por qué la variable de la clase secundaria no es accesible después de hacer esto?

Parent parent = new Child();

1
Lea el ejemplo del animal aquí: en.wikipedia.org/wiki/…
mrswadge

En su ejemplo, realmente no ayuda. Pero piense que tiene un método que hace algo con una referencia de padre. En algún lugar de la clase Main, por ejemplo. Toma una referencia de objeto principal y hace cosas con ella. Tendría sentido aceptar también subclases de Parent, como un objeto Child, a pesar de que solo le hace cosas parentales. Además, busque en el enlace dinámico cosas más interesantes.
Andrei Bârsan

¿Es este el nametipo correcto?
Roman C

Respuestas:


53

Primero, una aclaración de la terminología: estamos asignando un Childobjeto a una variable de tipo Parent. Parentes una referencia a un objeto que resulta ser un subtipo de Parent, a Child.

Solo es útil en un ejemplo más complicado. Imagina que agregas getEmployeeDetailsa la clase Parent:

public String getEmployeeDetails() {
    return "Name: " + name;
}

Podríamos anular ese método Childpara proporcionar más detalles:

@Override
public String getEmployeeDetails() {
    return "Name: " + name + " Salary: " + salary;
}

Ahora puede escribir una línea de código que obtenga los detalles disponibles, ya sea que el objeto sea un Parento Child:

parent.getEmployeeDetails();

El siguiente código:

Parent parent = new Parent();
parent.name = 1;
Child child = new Child();
child.name = 2;
child.salary = 2000;
Parent[] employees = new Parent[] { parent, child };
for (Parent employee : employees) {
    employee.getEmployeeDetails();
}

Dará como resultado la salida:

Name: 1
Name: 2 Salary: 2000

Usamos un Childcomo un Parent. Tenía un comportamiento especializado exclusivo de la Childclase, pero cuando getEmployeeDetails()llamamos podíamos ignorar la diferencia y centrarnos en cómoParent y Childson similares. Esto se llama polimorfismo de subtipo .

Su pregunta actualizada pregunta por qué Child.salaryno es accesible cuando el Childobjeto está almacenado en una Parentreferencia. La respuesta es la intersección de "polimorfismo" y "tipificación estática". Debido a que Java se escribe estáticamente en el momento de la compilación, obtiene ciertas garantías del compilador, pero se ve obligado a seguir reglas a cambio o el código no se compilará. Aquí, la garantía relevante es que cada instancia de un subtipo (p Child. Ej. ) Puede usarse como una instancia de su supertipo (p Parent. Ej .). Por ejemplo, se le garantiza que cuando accede employee.getEmployeeDetailso employee.nameel método o campo se define en cualquier objeto no nulo que podría asignarse a una variable employeede tipoParent . ., Para hacer esta garantía, el compilador considera solo ese tipo estático (básicamente, el tipo de referencia de variable Parent) al decidir a qué puede acceder. Por lo tanto, no puede acceder a ningún miembro definido en el tipo de tiempo de ejecución del objeto,Child .

Cuando realmente desee usar un Childcomo, Parentesta es una restricción fácil de usar y su código se podrá usar para Parenttodos sus subtipos. Cuando eso no sea aceptable, escriba el tipo de referencia Child.


22

Cuando compila su programa, la variable de referencia de la clase base obtiene memoria y el compilador verifica todos los métodos en esa clase. Por lo tanto, verifica todos los métodos de la clase base pero no los métodos de la clase secundaria. Ahora, en tiempo de ejecución, cuando se crea el objeto, solo se pueden ejecutar los métodos marcados. En caso de que se anule un método en la clase secundaria, esa función se ejecuta. Las otras funciones de la clase secundaria no se ejecutan porque el compilador no las ha reconocido en el momento de la compilación.


1
¡Esta respuesta tiene sentido! Gracias
user7098526

13

Le permite acceder a todas las subclases a través de una interfaz principal común. Esto es beneficioso para ejecutar operaciones comunes disponibles en todas las subclases. Se necesita un mejor ejemplo:

public class Shape
{
  private int x, y;
  public void draw();
}

public class Rectangle extends Shape
{ 
  public void draw();
  public void doRectangleAction();
}

Ahora si tienes:

List<Shape> myShapes = new ArrayList<Shape>();

Puede hacer referencia a cada elemento de la lista como una forma, no tiene que preocuparse si es un rectángulo o algún otro tipo como, por ejemplo, círculo. Puedes tratarlos de todos modos; puedes dibujarlos todos. No puede llamar a doRectangleAction porque no sabe si la forma es realmente un rectángulo.

Este es un intercambio que haces entre tratar los objetos de una manera genérica y tratar los objetos específicamente.

Realmente creo que necesitas leer más sobre OOP. Un buen libro debería ayudar: http://www.amazon.com/Design-Patterns-Explained-Perspective-Object-Oriented/dp/0201715945


2
En su ejemplo citado, ¿está utilizando "clase abstracta" o "interfaz"? Porque una clase simple no puede tener métodos sin implementar.
HQuser

1
@HQuser: bien identificado, parece que quiere explicar más sobre el polimorfismo en el contexto de la pregunta, pero seguramente esto necesita una enmienda.
Vivek

11

Si asigna un tipo principal a una subclase, significa que está de acuerdo con utilizar las características comunes de la clase principal.

Le da la libertad de abstraerse de diferentes implementaciones de subclase. Como resultado, lo limita con las funciones principales.

Sin embargo, este tipo de asignación se denomina upcasting.

Parent parent = new Child();  

Lo contrario es abatimiento.

Child child = (Child)parent;

Por lo tanto, si crea una instancia de Childy la rebaja, Parentpuede usar ese atributo de tipo name. Si crea una instancia de Parent, puede hacer lo mismo que con el caso anterior, pero no puede usarlo salaryporque no existe ese atributo en el Parent. Regrese al caso anterior que puede usar salarypero solo si se baja a Child.

Hay una explicación más detallada


Entonces, para eso puedo hacer Parent parent = new Parent (). ¿Por qué hacer eso? Por favor ayúdame.
Narendra Pal

Es cierto, pero todavía no explica por qué querrías hacerlo.
John Watts

Por qué hacerlo depende de lo que quieras hacer.
Roman C

Si asigna Parentpuede usar Parentsi asigna (si puede) Childpuede usar Child.
Roman C

1
Hijo hijo = padre (hijo); esto resultará en ClassCastException. No puede rebajar un objeto principal a una variable de referencia secundaria.
Muhammad Salman Farooq

7

Es simple.

Parent parent = new Child();

En este caso, el tipo de objeto es Parent. Ant Parenttiene solo una propiedad. Es name.

Child child = new Child();

Y en este caso el tipo de objeto es Child. Ant Childtiene dos propiedades. Son namey salary .

El hecho es que no es necesario inicializar el campo no final inmediatamente en la declaración. Por lo general, esto se hace en tiempo de ejecución porque a menudo no se puede saber exactamente qué implementación necesitará. Por ejemplo, imagina que tienes una jerarquía de clases con la clase Transporta la cabeza. Y tres subclases: Car, Helicoptery Boat. Y hay otra clase Tourque tiene campo Transport. Es decir:

class Tour {
   Transport transport;
}  

Mientras un usuario no haya reservado un viaje y no haya elegido un tipo de transporte en particular, no puede inicializar este campo. Es primero.

En segundo lugar, suponga que todas estas clases deben tener un método go()pero con una implementación diferente. Puede definir una implementación básica por defecto en la superclase Transporty poseer implementaciones únicas en cada subclase. Con esta inicialización Transport tran; tran = new Car();puedes llamar al métodotran.go() y obtener el resultado sin preocuparse por la implementación específica. Llamará al método anulado de una subclase particular.

Además, puede usar una instancia de subclase en cualquier lugar donde se use una instancia de superclase. Por ejemplo, desea brindar la oportunidad de alquilar su transporte. Si no se utiliza el polimorfismo, usted tiene que escribir una gran cantidad de métodos para cada caso: rentCar(Car car), rentBoat(Boat boat)y así sucesivamente. Al mismo tiempo, el polimorfismo le permite crear un método universal rent(Transport transport). Puede pasar en él objeto de cualquier subclase de Transport. Además, si con el tiempo su lógica aumentará y necesitará crear otra clase en la jerarquía. Cuando se usa polimorfismo, no es necesario cambiar nada. Simplemente extienda la clase Transporty pase su nueva clase al método:

public class Airplane extends Transport {
    //implementation
}

y rent(new Airplane()). Y new Airplane().go()en el segundo caso.


que lo sé, pero mi pregunta es ¿por qué hacerlo? Si podemos hacer Parent p = new Parent () para obtener la referencia principal.
Narendra Pal

Este es el polimorfismo . Es muy útil. El polimorfismo hace que su programa sea más flexible y extensible. ¿Cómo puede ser útil esto exactamente? Vea mis ejemplos en la respuesta actualizada .
Kapelchik

1
Gran ejemplo con el transporte ... se entiende fácilmente. ¡Gracias!
ChanwOo Park

2

Esta situación ocurre cuando tienes varias implementaciones. Dejame explicar. Suponga que tiene varios algoritmos de clasificación y desea elegir en tiempo de ejecución el que desea implementar, o desea darle a otra persona la capacidad de agregar su implementación. Para resolver este problema, generalmente crea una clase abstracta (Padre) y tiene una implementación diferente (Niño). Si tú escribes:

Child c = new Child();

enlaza su implementación a la clase Child y ya no puede cambiarla. De lo contrario, si usa:

Parent p = new Child();

Siempre que Child amplíe Parent, puede cambiarlo en el futuro sin modificar el código.

Se puede hacer lo mismo usando interfaces: Parent ya no es una clase, sino una interfaz java.

En general, puede usar este enfoque en el patrón DAO donde desea tener varias implementaciones dependientes de DB. Puede echar un vistazo a FactoryPatter o AbstractFactory Pattern. Espero que esto le pueda ayudar.


1

Digamos que le gustaría tener una matriz de instancias de la clase Parent y un conjunto de clases secundarias Child1, Child2, Child3 extendiendo Parent. Hay situaciones en las que solo está interesado en la implementación de la clase principal, que es más general, y no le importan las cosas más específicas introducidas por las clases secundarias.


1

Creo que todas las explicaciones anteriores son un poco demasiado técnicas para las personas que son nuevas en la programación orientada a objetos (OOP). Hace años, me tomó un tiempo entender esto (como Jr Java Developer) y realmente no entendía por qué usamos una clase principal o una interfaz para ocultar la clase real a la que realmente estamos llamando bajo las sábanas.

  1. La razón inmediata por la que es para ocultar la complejidad, para que la persona que llama no necesite cambiar con frecuencia (ser pirateado y secuestrado en términos sencillos). Esto tiene mucho sentido, especialmente si su objetivo es evitar la creación de errores. Y cuanto más modifiques el código, más probable será que algunos de ellos se te acerquen. Por otro lado, si solo extiende el código, es menos probable que tenga errores porque se concentra en una cosa a la vez y su código anterior no cambia o cambia solo un poco. Imagina que tienes una aplicación sencilla que permite a los empleados de la profesión médica crear perfiles. Para simplificar, supongamos que solo tenemos médicos generales, cirujanos y enfermeras (en realidad, hay muchas profesiones más específicas, por supuesto). Para cada profesión, desea almacenar información general y alguna específica solo para ese profesional. Por ejemplo, un cirujano puede tener campos generales como firstName, lastName, yearsOfExperience como campos generales, pero también campos específicos, por ejemplo, especializaciones almacenadas en una variable de instancia de lista, como Lista con contenido similar a "Cirugía ósea", "Cirugía ocular", etc. Una enfermera no tendría nada de eso, pero puede tener una lista de procedimientos con los que esté familiarizada, GeneralPractioners tendría sus propios detalles. Como resultado, cómo se guarda un perfil de un específico. Sin embargo, no desea que su clase de ProfileManager conozca estas diferencias, ya que inevitablemente cambiarán y aumentarán con el tiempo a medida que su aplicación amplíe su funcionalidad para cubrir más profesiones médicas, por ejemplo, fisioterapeuta, cardiólogo, oncólogo, etc. Todo lo que quiere que haga su ProfileManger es decir guardar (), sin importar de quién sea el perfil que esté guardando. Por lo tanto, es una práctica común ocultar esto detrás y la Interfaz y la Clase abstracta o una Clase principal (si planea permitir la creación de un empleado médico general). En este caso, elijamos una clase principal y la llamamos MedicalEmployee. Debajo de las cubiertas, puede hacer referencia a cualquiera de las clases específicas anteriores que lo amplían. Cuando ProfileManager llama a myMedicalEmployee.save (), el método save () se resolverá polimórficamente (muchas estructuras) en el tipo de clase correcto que se usó para crear el perfil originalmente, por ejemplo Nurse y llamará al método save () en ese clase. o una Clase para padres (si planea permitir la creación de un empleado médico general). En este caso, elijamos una clase principal y la llamamos MedicalEmployee. Debajo de las cubiertas, puede hacer referencia a cualquiera de las clases específicas anteriores que lo amplían. Cuando ProfileManager llama a myMedicalEmployee.save (), el método save () se resolverá polimórficamente (muchas estructuras) en el tipo de clase correcto que se usó para crear el perfil originalmente, por ejemplo Nurse y llamará al método save () en ese clase. o una Clase para padres (si planea permitir la creación de un empleado médico general). En este caso, elijamos una clase principal y la llamamos MedicalEmployee. Debajo de las cubiertas, puede hacer referencia a cualquiera de las clases específicas anteriores que lo amplían. Cuando ProfileManager llama a myMedicalEmployee.save (), el método save () se resolverá polimórficamente (muchas estructuras) en el tipo de clase correcto que se usó para crear el perfil originalmente, por ejemplo Nurse y llamará al método save () en ese clase.

  2. En muchos casos, no sabe realmente qué implementación necesitará en tiempo de ejecución. En el ejemplo anterior, no tiene idea de si un médico general, un cirujano o una enfermera crearían un perfil. Sin embargo, sabe que debe guardar ese perfil una vez completado, pase lo que pase. MedicalEmployee.profile () hace exactamente eso. Es replicado (anulado) por cada tipo específico de Empleado Médico: Médico General, Cirujano, Enfermera,

  3. El resultado de (1) y (2) anteriores es que ahora puede agregar nuevas profesiones médicas, implementar save () en cada nueva clase, anulando así el método save () en MedicalEmployee, y no tiene que modificar ProfileManager en todas.


1

Sé que este es un hilo muy antiguo, pero una vez me encontré con la misma duda.

Entonces, el concepto de Parent parent = new Child();tiene algo que ver con el enlace temprano y tardío en java.

La vinculación de métodos privados, estáticos y finales ocurre en la compilación, ya que no se pueden anular y las llamadas a métodos normales y los métodos sobrecargados son ejemplos de vinculación temprana.

Considere el ejemplo:

class Vehicle
{
    int value = 100;
    void start() {
        System.out.println("Vehicle Started");
    }

    static void stop() {
        System.out.println("Vehicle Stopped");
    }
}

class Car extends Vehicle {

    int value = 1000;

    @Override
    void start() {
        System.out.println("Car Started");
    }

    static void stop() {
        System.out.println("Car Stopped");
    }

    public static void main(String args[]) {

        // Car extends Vehicle
        Vehicle vehicle = new Car();
        System.out.println(vehicle.value);
        vehicle.start();
        vehicle.stop();
    }
}

Salida: 100

Coche arrancado

Vehículo detenido

Esto sucede porque stop()es un método estático y no se puede anular. Por lo tanto, la vinculación stop()ocurre en tiempo de compilación y start()no es estática y se anula en la clase secundaria. Por lo tanto, la información sobre el tipo de objeto está disponible solo en el tiempo de ejecución (enlace tardío) y, por lo tanto, elstart() , se llama método de la clase Car.

También en este código vehicle.valuenos da 100como salida porque la inicialización de la variable no viene bajo enlace tardío. La invalidación de métodos es una de las formas en que Java admite el polimorfismo en tiempo de ejecución .

  • Cuando se llama a un método anulado a través de una referencia de superclase, Java determina qué versión (superclase / subclases) de ese método se ejecutará en función del tipo de objeto al que se hace referencia en el momento en que se produce la llamada. Por tanto, esta determinación se realiza en tiempo de ejecución.
  • En tiempo de ejecución, depende del tipo de objeto al que se hace referencia (no del tipo de la variable de referencia) que determina qué versión de un método anulado se ejecutará

Espero que esto responda dónde Parent parent = new Child();es importante y también por qué no pudo acceder a la variable de clase secundaria usando la referencia anterior.


-1

por ejemplo tenemos un

class Employee

{

int getsalary()

{return 0;}

String getDesignation()

{

returndefault”;

}

}

class Manager extends Employee

{

int getsalary()

{

return 20000;

}

String getDesignation()

{

return “Manager”

}

}

class SoftwareEngineer extends Employee

{

int getsalary()

{

return 20000;

}

String getDesignation()

{

return “Manager”

}

}

ahora, si desea establecer u obtener el salario y la designación de todos los empleados (es decir, ingeniero de software, gerente, etc.)

tomaremos una matriz de Empleado y llamaremos a ambos métodos getsalary (), getDesignation

Employee arr[]=new Employee[10];

arr[1]=new SoftwareEngieneer();

arr[2]=new Manager();

arr[n]=…….

for(int i;i>arr.length;i++)

{

System.out.println(arr[i].getDesignation+””+arr[i].getSalary())

}

ahora es una especie de acoplamiento suelto porque puede tener diferentes tipos de empleados, por ejemplo: ingeniero de software, gerente, recursos humanos, empleado de despensa, etc.

para que pueda dar un objeto a la referencia principal independientemente de los diferentes objetos de empleado


-3

Declara parent como Parent, por lo que java proporcionará solo métodos y atributos de la clase Parent.

Child child = new Child();

Deberia trabajar. O

Parent child = new Child();
((Child)child).salary = 1;

1
Por supuesto que esto funciona. OP estaba pidiendo un contexto, donde Parent parent = new Child();sea ​​útil / significativo.
Baz

2
Es la misma explicación que en las otras respuestas, entonces, ¿por qué rechazar esto? No digo que no trabajo, digo que si dice Child c = new Parant (), entonces tiene una instancia de Parent y no de Child, por lo que no tiene salario de atributo. Si desea el salario de Atributo, debe crear una instancia de Niño o
transmitir de
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.