Funciones de programación polimórficas orientadas a objetos Java

  El polimorfismo es una de las tres características principales de la programación orientada a objetos y una de las últimas manifestaciones del pensamiento orientado a objetos. Antes de comprender el polimorfismo, debe dominar los conceptos de herencia, reescritura y referencias de clase principal que apuntan a objetos de subclase. Los estudiantes que no han entendido completamente la herencia pueden ingresar al portal: las tres características principales de la herencia de programación orientada a objetos Java.
  
  1.
  
  En la herencia de clases abstractas , hemos entendido la relación entre las clases hijo y padre y cómo diseñar las clases hijo y padre. Si ya existen varias clases de entidad, la definición de la clase padre es en realidad un proceso de extracción continua de partes coincidentes comunes. Se generarán múltiples relaciones de herencia si es necesario. En el proceso de extracción y clasificación, además de que los atributos se pueden reutilizar, hay muchos métodos que también se pueden reutilizar, suponiendo un ejemplo gráfico: rectángulo, círculo, pueden tener dos métodos de perímetro y área, pero el método de cálculo es completamente diferente, La relación entre el rectángulo y el círculo no debe constituir una relación padre-hijo, por lo que solo puede heredar una clase padre al mismo tiempo, entonces surge la pregunta, ¿qué tienen en común estas dos clases?
  
  Las imágenes técnicas
  
  parecen no tener nada en común, excepto los gráficos: el rectángulo tiene dos conjuntos de lados y el círculo se describe por el radio, si es necesario conectarse entre sí. . . ¡Espere un momento (en el destello del aura, no lo moleste)! ! ! ¿Es posible calcular la circunferencia y el área? Si lo piensa detenidamente, puede decir la verdad, ¡pero esto es abstracto!
  
  Si este es realmente el caso, solo puede haber una idea vaga. Dado que los atributos que describen los gráficos no se pueden compartir, se deben colocar en dos subclases. ¿Cómo calcular el perímetro y el área? Si el método correspondiente se define en la clase padre, ¿cómo escribir la lista de parámetros? ¿Cómo llenar el cuerpo del método? Este pozo parece ser un poco grande. A continuación, lo llenaremos generosamente.
  
  1. Abstracción y clases abstractas
  
  En el ejemplo anterior, encontramos una situación en la que hay dos clases lógicamente relacionadas, y queremos conectarlas, porque hacerlo puede mejorar la eficiencia, pero en el proceso de implementación Se encuentra que este punto común es un poco vago y es difícil describirlo con código. Es aún más conveniente usarlo por separado. En este momento, es necesario introducir un concepto abstracto. La palabra clave correspondiente es abstracta.
  
  El resumen puede modificar los métodos. Después de la modificación, se llama método abstracto. El
  
  resumen puede modificar la clase. Después de la modificación, se llama clase abstracta. El
  
  resumen no se puede usar junto con el modificador estático.
  
  ¿Y qué si se usa el resumen? Esto significa que los métodos y clases especificados son difíciles de expresar, entonces. . . ¡No hay necesidad de expresarlo! Para la clase principal de la clase de rectángulo (Rectángulo) y la clase de círculo (Círculo): la clase de figura (Figura), solo podemos concluir que tiene un método para calcular el perímetro y el área, y el método de implementación específico que no podemos dar, solo claro La implementación específica solo puede darse después de los gráficos, por lo que usamos abstract para describir estos dos métodos. El método modificado por abstract no necesita tener un cuerpo de método y no puede ser privado. Dado que el método abstract no tiene cuerpo de método, si es invocado por el código ¿Qué debo hacer cuando llegue? Las siguientes dos reglas de restricción pueden eliminar este problema: los
  
  métodos abstractos solo pueden existir en clases abstractas (las interfaces se analizan en otro artículo). Las
  
  clases abstractas no se pueden instanciar directamente (el uso de clases internas anónimas no se discute por ahora)
  
  ya que las clases abstractas no pueden Si se crea una instancia, entonces, naturalmente, esos métodos sin cuerpos de métodos no se llamarán, entonces, ¿cómo deberían llamarse estos métodos? Necesitamos ir paso a paso, al menos por ahora, podemos obtener claramente el siguiente diagrama de relación:
  
  imagen técnica
  
  2. Las características de
  
  la clase abstracta La esencia de la clase abstracta sigue siendo una clase (clase), por lo que tiene una clase común Funciones, incluida la definición de métodos de construcción, etc. Para resumir, las clases abstractas tienen las siguientes características:
  
  Las clases abstractas son modificadas por abstractos
  
  Los métodos abstractos están permitidos en
  
  clases abstractas Las clases abstractas no pueden ser instanciadas directamente a través del constructor.
  
  Los métodos comunes pueden definirse en clases abstractas Para herencia de clase secundaria
  
  Ahora, ya podemos describir la clase primaria abstracta en código:
  
  // Definir clase abstracta: clase de gráficos
  
  public abstract class Figura {
  
  // Definir método abstracto para calcular el perímetro: getC ()
  
  public abstract double getC ();
  
  // Definir método abstracto para calcular el área: getS ()
  
  public abstract double getS ( );
  
  // Defina un método no abstracto para describir gráficos: print ()
  
  public void print () {
  
  System.out.println ("Esto es un gráfico");
  
  }
  
  }
  
  3. Clase padre natural: clase abstracta
  
  ahora ya tenemos Se define una clase abstracta, que también define métodos abstractos. Las clases abstractas no se pueden instanciar directamente para garantizar que los métodos abstractos no se llamen directamente. Recordando nuestro punto de partida, trabajamos duro para crear una clase abstracta para extraer los puntos comunes más abstractos de las dos clases, luego el siguiente paso se hereda naturalmente.
  
  Una clase abstracta no puede ser instanciada directamente. Es una clase abstracta natural.
  
  Si una clase hereda una clase abstracta, debe anular el método abstracto en la clase padre.
  
  Si la clase abstracta define un constructor, puede ser invocada por la subclase o instanciada en el objeto de subclase. cuando se ejecuta
  
  si el niño todavía clase abstracta es una clase abstracta, un método abstracto no puede ser reescrita, la operación de reescritura se deja bajo una subclase
  
  regrabable II
  
  La reescritura se refiere a la relación formada por los métodos entre las clases principal y secundaria. Cuando la clase secundaria hereda la clase principal, algunos métodos ya pueden existir en la clase principal, por lo que se puede llamar directamente a la instancia de clase secundaria. En algunos casos, debido a las diferencias entre las clases padre e hijo, queremos hacer algunas modificaciones a los métodos existentes. En este momento, podemos usar la reescritura para definir un método en la subclase que sea exactamente el mismo que el método en la clase padre, incluido el retorno El tipo de valor y la firma del método (nombre del método + lista de parámetros) constituirán una reescritura en este momento. De esta manera, la instancia de la subclase puede anular el método en la clase padre cuando se llama al método. El proceso específico se explica en la segunda mitad.
  
  1. La diferencia entre reescribir y sobrecargar
  
  Aprendimos un concepto cuando entramos en contacto con los métodos: la sobrecarga, que es algo similar a la reescritura, es fácil de confundir. Si el punto de conocimiento ya es vago, puede ingresar al portal: el diseño del método de los programas Java. En resumen, la reescritura y la sobrecarga tienen las siguientes diferencias: la
  
  sobrecarga es la relación entre métodos y métodos en la misma clase, la
  
  reescritura es la relación entre métodos y métodos entre las clases padre e hijo (entre interfaces y clases de implementación)
  
  constituye una sobrecarga : el mismo nombre del método, una lista de diferentes parámetros, tipo de retorno puede ser diferente
  
  de anulación de configuración: el nombre del mismo método, la misma lista de parámetros, devuelve el mismo valor como un tipo o tipos de sub-clase correspondiente
  
  modificador permiso puede ser diferente entre los métodos constitutivos sobrecargado
  
  peso El modificador de permisos del método de escritura debe ser mayor que el modificador de permisos del método reescrito.Si
  
  la función del modificador de permisos no está clara, puede ingresar al portal: tres características de la encapsulación de programación orientada a objetos Java. Después de aclarar el significado de la reescritura, finalmente podemos volver a levantar el lápiz y completar nuestro ejemplo anterior:
  
  // definir la clase
  
  pública clase pública Rectángulo se extiende Figura {
  
  // definir el constructor
  
  rectángulo público (doble altura, doble ancho) {
  
  this.height = height;
  
  this.width = width;
  
  }
  
  // define la longitud y el ancho
  
  public double height;
  
  public double width;
  
  // reescribe el método de cálculo de la circunferencia
  
  @Override
  
  public double getC () {
  
  return 2 * (this.height + this.width);
  
  }
  
  // Reescribe el método del área de cálculo
  
  @Override
  
  public double getS () {
  
  return this.height + this.width;
  
  }
  
  // Opcional override
  
  @Override
  
  public void print () {
  
  System.out.println ("Rectángulo");
  
  }
  
  }
  
  // define la clase de círculo clase
  
  pública Círculo se extiende Figura {
  
  // define el constructor
  
  círculo público (radio doble) {
  
  this.radius = radio;
  
  }
  
  // define el radio
  
  public double radius;
  
  // Anular método de cálculo del perímetro
  
  @Override
  
  public double getC () {
  
  return 2 * Math.PI * this.radius;
  
  }
  
  // Anular método de área de cálculo
  
  @Override
  
  public double getS () {
  
  return Math. PI * Math.pow (this.radius, 2);
  
  }
  
  //
  
  Anulación opcional @Override
  
  public void print () {
  
  System.out.println ("circle");
  
  }
  
  }
  
  2. Reescritura de reglas de
  
  reescritura de métodos La
  
  reescritura del método @Override se produce en la clase de implementación de la subclase o interfaz.
  
  El método declarado por final no se puede reescribir
  
  . El método declarado por static no se puede reescribir. Solo se pueden declarar métodos estáticos de la misma estructura, pero en este momento No constituye una reescritura
  
  limitada por modificadores de permisos, las subclases solo pueden reescribir algunos de los métodos de la
  
  clase primaria 3. Llamada explícita del método de la clase primaria
  
  Como puede ver en el código anterior, después de que la subclase hereda la clase primaria, si hay un método abstracto, como reescribir, porque el método en la clase primaria es abstracto, no se puede llamar. Para los métodos ordinarios, se puede reescribir selectivamente. Una vez que reescribimos el método de la clase padre, podemos pensar que el método de la clase padre se sobrescribe. De hecho, esta descripción es inexacta y puede considerarse que se sobrescribe en la etapa inicial.
  
  Un argumento más estandarizado es que el método del mismo nombre en la clase principal no se puede invocar directamente a través de la instancia de subclase, pero todavía hay una estructura del método de la clase principal en la memoria, pero no es accesible. Además, también podemos llamar explícitamente al método de clase padre en la subclase, que requiere la palabra clave super.
  
  super se refiere al objeto de clase padre
  
  super puede llamar a la variable miembro de clase padre accesible
  
  super puede llamar al método de miembro de clase padre accesible
  
  super puede llamar al constructor de clase padre accesible
  
  no puede usar super para llamar al método abstracto en la clase padre
  
  puede usar la llamada super Métodos estáticos en la clase principal
  
  Si necesitamos llamar al método o constructor de la clase principal en la clase secundaria, podemos modificar el código de la siguiente manera:
  
  // Definir la clase abstracta: clase de gráficos clase
  
  abstracta pública Figura {
  
  // Definir el constructor en la clase abstracta, Ejecute
  
  public Figure () {
  
  System.out.println ("Figure init");
  
  }
  
  // defina el método abstracto de calcular el perímetro cuando se crea la instancia de subclase : getC ()
  
  public abstract double getC ();
  
  // defina el área calculada Método abstracto: getS ()
  
  public abstract double getS ();
  
  // Definir un método no abstracto para describir gráficos: print ()
  
  public void print () {
  
  System.out.println ("Esto es un gráfico");
  
  }
  
  }
  
  // define la clase
  
  pública clase pública Rectángulo se extiende Figura {
  
  // define la construcción dispositivo
  
  pública del rectángulo (de doble altura, anchura doble) {
  
  super (); // llamará al constructor por defecto sin argumentos, el código se puede omitir
  
  this.height = altura;
  
  this.width = ancho;
  
  }
  
  // anchura y una altura
  
  doble altura pública ;
  
  public double width;
  
  // Anular método de cálculo de perímetro
  
  @Override
  
  public double getC () {
  
  return 2 * (this.height + this.width);
  
  }
  
  // Anular método de área de cálculo
  
  @Override
  
  public double getS () {
  
  devuelve this.height + this.width;
  
  }
  
  // anulación opcional
  
  @Override
  
  public void print () {
  
  super.print (); // llama al método de clase padre
  
  System.out.println ("rectángulo");
  
  }
  
  }
  
  // define la clase de círculo clase
  
  pública clase El círculo se extiende Figura {
  
  // define el constructor
  
  public Circle (double radius) {
  
  super (); // Llamará a la construcción predeterminada sin parámetros, el código se puede omitir
  
  this.radius = radius;
  
  }
  
  // Defina el radio
  
  public double radius;
  
  // Reescribe el método de cálculo del perímetro
  
  @Override
  
  public double getC () {
  
  return 2 * Math.PI * this.radius;
  
  }
  
  // Reescribe el método del área de cálculo
  
  @Override
  
  public double getS () {
  
  return Math.PI * Math.pow (this.radius, 2);
  
  }
  
  / /
  
  Anulación opcional @Override
  
  public void print () {
  
  super.print (); // Llame al método de la clase principal
  
  System.out.println ("circle");
  
  }
  
  }
  
  Tres, los puntos de referencia de la clase principal al objeto de la clase secundaria
  
  Después de digerir el concepto mencionado anteriormente, echemos un vistazo a la clase primaria secundaria La forma de instanciación de objetos y el efecto de ejecución del método.
  
  1. La referencia de la clase principal apunta al objeto de la clase principal.
  
  Si la clase principal es una clase abstracta, no puede usar el nuevo método del constructor más para crear una instancia en el lado derecho del signo igual. Si debe obtener la instancia de la clase primaria, debe usar la clase interna anónima. Uso, no discutido aquí.
  
  Si la clase padre es una clase normal, cuando inicializamos, el lado izquierdo del signo igual es la referencia de tipo padre, y el lado derecho del signo igual es el objeto de tipo padre (instancia). En este momento, no hay diferencia entre nosotros y la creación de un objeto de clase. , No hay necesidad de pensar que él es el padre de una determinada clase, porque en este momento no tendrá una relación con ninguna subclase, solo una clase normal que herede la clase Object por defecto, solo úsela normalmente, y el contenido puede ser llamado Se define en la clase padre.
  
  2. La referencia de la subclase apunta al objeto
  
  de la subclase Cuando se crea una instancia de la subclase, la clase padre se hereda en la definición de la subclase, por lo que al crear el objeto de la subclase, el objeto de la clase padre se creará primero. Al realizar una llamada, de acuerdo con el modificador de permisos, puede llamar los atributos y métodos a los que se puede acceder en la subclase y la clase principal.
  
  Prueba de clase pública {
  
  public static void main (String [] args) {
  
  Rectangle rectangle = new Rectangle (5,10);
  
  // llama al método definido en Rectangle, sujeto a la subclase rewriting
  
  rectangle.print ();
  
  System.out.println (rectangle.getC ()); // Obtener el perímetro rectangular
  
  System.out.println (rectangle.getS ()); // Obtener el área rectangular
  
  Circle circle = new Circle (5);
  
  // Call Circle El método definido en está sujeto a la subclase reescritura
  
  circle.print ();
  
  System.out.println (circle.getC ()); // Obtenga el perímetro circular
  
  System.out.println (circle.getS ()) ; // Obtener el área circular
  
  }
  
  }
  
  3. La relación entre referencias y objetos
  
  Cuando comenzamos a aprender programación, estuvimos expuestos a tipos de datos básicos, que pueden declararse directamente con palabras clave, usarse después de definir asignaciones variables, y no es necesario usar nuevos Palabras clave Para la relación entre referencias y objetos, consulte el artículo anterior para revisar: la unidad operativa básica en clases y objetos Java. Lo que queremos explicar aquí es cómo la parte de referencia en el lado izquierdo del signo igual está relacionada con la parte en el lado derecho del signo igual en el nivel de ejecución del programa.
  
  A diferencia de los tipos de datos básicos, se pueden definir varias propiedades y métodos en la clase, y los objetos deben crearse antes de su uso. La parte en el lado izquierdo del signo igual sigue siendo una declaración de tipo. Aunque es nulo por defecto cuando no está asignado, también se almacenará en la pila cuando el programa se compila y ejecuta, y se registra la información estructural correspondiente. El objeto que señala debe ser Es un tipo compatible con él.
  
  Las referencias de declaración de clase se almacenan en la pila y los objetos instanciados se almacenan en el montón.
  
  En la etapa de escritura de código, el contenido que se puede invocar se basa en el tipo en el lado izquierdo del signo igual.
  
  En la etapa de ejecución del programa, el efecto de ejecución específico se basa en la instancia en el lado derecho del signo igual.
  
  La siguiente figura es un diagrama esquemático de la relación entre referencias e instancias en la memoria. La distribución de los objetos Java en la memoria se explicará en otro artículo:
  
  Imagen técnica
  
  4. La referencia de la clase principal apunta al objeto de la clase secundaria. Después de
  
  comprender la relación entre referencias y objetos, Hay una pregunta, ¿qué pasa si el tipo declarado en el lado izquierdo del signo igual es inconsistente con el tipo de instancia en el lado derecho del signo igual? Si queremos asegurarnos de que el programa se pueda compilar y ejecutar sin problemas, debemos asegurarnos de que los tipos en ambos lados del signo igual sean compatibles. Dos clases completamente no relacionadas no pueden aparecer en los lados izquierdo y derecho del signo igual. Incluso si puede usar la conversión de tipo forzada a través de la compilación, se generará una excepción en el tiempo de ejecución.
  
  ¿Entonces pensamos si es posible ser compatible con las clases de padres e hijos? Habrá dos casos: los puntos de referencia de la subclase al objeto de la clase principal y los puntos de referencia de la clase principal al objeto de la subclase. Analicemos uno por uno a continuación.
  
  ¿Por qué la subclase de referencia apunta al objeto de clase padre? El
  
  punto de referencia de la subclase al objeto de clase padre se refiere a: el lado izquierdo del signo igual es la definición de declaración del subtipo, y el lado derecho del signo igual es la instancia del tipo padre. Primero, la conclusión es que dicho uso no existe, y analizamos las razones de dos aspectos.
  
  ¿Es lógico el primer aspecto? En otras palabras, ¿será necesario que el lenguaje Java proporcione a los desarrolladores tal uso? Obviamente no, el propósito de definir la subclase es extender la funcionalidad de la clase padre. Como resultado, ahora estamos usando una instancia de clase padre antigua y que funciona mal (en el lado derecho del signo igual) para satisfacer a los poderosos y funcionales La necesidad de una declaración de subclase más poderosa (en el lado izquierdo del signo igual) es obviamente irracional.
  
  Por otro lado, ¿se puede hacer mientras se ejecuta el programa? Si realmente escribimos el código relevante, se nos pedirá que agreguemos una declaración de conversión forzada, de lo contrario no se compilará, e incluso si se aprueba, indicará que la conversión de tipo no se puede realizar en tiempo de ejecución. Esto es equivalente a convertir por la fuerza una máquina antigua que solo puede llamar y enviar mensajes de texto a una máquina inteligente que puede instalar varias aplicaciones, lo que obviamente es imposible.
  
  ¿Qué significa que una referencia de clase primaria apunte a un objeto de clase secundaria?
  
  La referencia de la clase principal apunta al objeto de la clase secundaria: el lado izquierdo del signo igual es la definición del tipo principal y el lado derecho del signo igual es la instancia del subtipo. Esta situación se usa a menudo, de manera similar a: la interfaz apunta a la clase de implementación. Entonces, ¿cómo debería explicarse este uso y por qué debería existir?
  
  En primer lugar, comprendamos lo que esto significa, si: la clase principal son gráficos y la clase secundaria es rectángulo y círculo. Esto es como si declarara un objeto gráfico. En este momento, sabemos que los métodos definidos en la clase de gráficos se pueden llamar. Dado que la clase de gráficos es una clase abstracta, no se puede instanciar directamente. Solo podemos usar sus dos subclases para probar Pruébalo
  
  Prueba de clase pública {
  
  public static void main (String [] args) {
  
  // figure1 apunta a la instancia Rectangle
  
  Figure figure1 = new Rectangle (5,10);
  
  System.out.println (figure1.getC ()); // obtiene el perímetro rectangular Long
  
  System.out.println (figure1.getS ()); // Obtenga el área rectangular
  
  // figure2 puntos a la instancia Circle
  
  Figura figure2 = new Circle (5);
  
  System.out.println (figure2.getC ()); // get Perímetro circular
  
  System.out.println (figure2.getS ()); // Obtenga el área circular
  
  }
  
  }
  
  De los resultados anteriores, parece que no hay diferencia entre el efecto de ejecución de la referencia de subclase que apunta al objeto de subclase. Sin embargo, debe tenerse en cuenta que la referencia de la clase padre se utiliza en este momento. La diferencia es que si definimos contenido único en la subclase, no se puede invocar. Se ha explicado anteriormente que el efecto de ejecución se basa en la instancia en el lado derecho del signo igual, por lo que no es difícil entender que el resultado es el mismo que la instancia de subclase creada directamente.
  
  Es importante explicar el significado de la misma: usando la instrucción Figura, significa que ahora solo sé que es una figura y sé qué métodos se pueden ejecutar. Si le digo que es un rectángulo, entonces puedo calcular la circunferencia y el área del rectángulo; si Es un círculo, entonces se puede calcular la circunferencia y el área del círculo. También podemos describirlo de esta manera: esta figura es un rectángulo o esta figura es un círculo.
  
  Si explicamos desde la perspectiva de la operación del programa, ya sabemos que el objeto de la subclase creará una instancia del objeto de la clase principal cuando se instancia, y si la subclase anula el método de la clase principal, el método de la clase principal se ocultará. Si utilizamos una referencia de clase principal para apuntar a un objeto de subclase, esto es equivalente a una instancia de objeto es muy potente, pero solo podemos habilitar parte de la función, pero existe la ventaja de que la misma instrucción, se pueden ejecutar diferentes objetos de subclase, Y habrá diferencias. Esto es equivalente a una máquina de edad avanzada con solo las funciones de hacer llamadas y enviar mensajes de texto. Tanto los teléfonos Xiaomi como los teléfonos Meizu son teléfonos inteligentes actualizados y ampliados. Por supuesto, conservan las funciones de comunicación más básicas del teléfono, por lo que no es un problema usarlo.
  
  Cuarto, polimorfismo Después de
  
  aprender el contenido anterior, de hecho, ha dominado el uso del polimorfismo, ahora resumamos claramente.
  
  1. ¿Qué es el polimorfismo? El
  
  polimorfismo se refiere a la misma clase principal, o la misma interfaz, emitió una misma instrucción (llamada el mismo método), porque la implementación específica de la instancia (objeto de subclase u objeto de clase de implementación) es diferente , Y hay diferentes formas de rendimiento (efecto de ejecución).
  
  Al igual que los gráficos en el ejemplo anterior, es una clase abstracta en sí misma, en la que hay algunos métodos abstractos, y la ejecución específica puede completarse mediante objetos de subclase. Para el método abstracto de la clase abstracta, debido a que la subclase debe reescribirse, la subclase para ejecutar el método abstracto de la clase padre es necesariamente una manifestación de polimorfismo, para otros casos no necesariamente constituye polimorfismo, por lo que los siguientes tres son necesarios Condición
  
  2. Condiciones necesarias para el polimorfismo
  
  Existe una relación de herencia de subclase
  
  Método para que la subclase reescriba la clase principal
  
  Puntos de referencia de la clase principal al objeto de la subclase
  
  Solo cuando se cumplen estas tres condiciones puede ser polimórfico, que son los primeros tres puntos de este artículo. La razón del espacio.
  
  3. Las ventajas del polimorfismo El
  
  uso del polimorfismo tiene muchas ventajas, especialmente cuando una clase abstracta tiene múltiples subclases, o una interfaz tiene múltiples clases abstractas, será muy flexible al pasar parámetros, solo se necesita en el método Defina un tipo primario como una declaración, los parámetros pasados ​​pueden ser el tipo primario en sí mismo, o puede ser cualquier objeto de subtipo correspondiente. Por lo tanto, las ventajas del polimorfismo se pueden resumir de la siguiente manera:
  
  reducir el acoplamiento: solo es necesario asociarlo con la
  
  mantenibilidad del tipo padre (garantía de herencia): solo es necesario agregar o modificar un cierto subtipo, no afectará la
  
  escalabilidad de otras clases (Garantía de polimorfismo): utilizando subclases, puede ampliar rápidamente la   interfaz de
  
  flexibilidad de funciones existentes
  

Supongo que te gusta

Origin www.cnblogs.com/javabk6/p/12717262.html
Recomendado
Clasificación