Notas de estudio de Java: polimorfismo y métodos virtuales, clases internas y clases anónimas

1. Polimorfismo y método virtual

1.1 ¿Qué es el polimorfismo?

Polimorfismo significa que en el programa, un nombre representa muchos significados diferentes. El
polimorfismo se divide en dos situaciones: (¿Por qué están divididos en diferentes períodos? ¡Piense primero!)

Primero: polimorfismo en tiempo de compilación

Forma típica de lograr polimorfismo en tiempo de compilación:
¿Por qué hay polimorfismo en tiempo de compilación en Overload ? ¿Por qué la sobrecarga es una implementación típica del polimorfismo en tiempo de compilación?
En el programa, es decir, al escribir el programa, tal vez usemos el mismo nombre para cierto método, pero queremos lograr polimorfismo, inevitablemente haremos que algunos métodos del mismo nombre sean diferentes, y es imposible lograr más. ¡Estado! ¿Cuándo es la diferencia? La ejecución del programa tiene un período de compilación y un período de ejecución, y cada una de estas dos etapas tiene un medio para distinguir el método del mismo nombre al codificar. Durante el período de compilación, nuestras funciones sobrecargadas se compilan, ensamblan, vinculan y finalmente se ejecutan (Java debería generar archivos de código de bytes). Y esta sobrecarga, está en la etapa de compilación para completar la distinción del método del mismo nombre ¿Cómo distinguirlo?
Por ejemplo: programa de muestra y código de desmontaje (comando de desmontaje: javap -c nombre de archivo)
Inserte la descripción de la imagen aquí
Inserte la descripción de la imagen aquí
se puede ver desde aquí, después de la compilación, nuestras tres funciones sobrecargadas son completamente diferentes, por lo que podemos saber que esto está en ¡El compilador ha completado la distinción!

Segundo: polimorfismo en tiempo de ejecución

Una forma típica de lograr el polimorfismo en tiempo de ejecución: Override Override
debe existir en la relación de herencia. ¡Sin herencia, no hay override! Debido a que está en la relación de herencia, nuestra subclase reescribe el método de la clase principal. En este momento, cuando el objeto de la subclase llama a este método, no llamará al método de la clase principal, sino que llamará a la reescritura de la subclase. ¡Método posterior! ¿No es esto que el método del mismo nombre tiene diferentes efectos en diferentes escenarios de aplicación? Así que reescribir es un polimorfismo típico. Entonces, ¿por qué reescribir un polimorfismo en tiempo de ejecución? ¿Cómo se distingue cuando el método con el mismo nombre llama a la subclase y cuando se llama a la clase principal?

Esto lo determina el sistema, que tiene un mecanismo: ¡enlace dinámico! También se llama llamada de método virtual, ¿por qué se llama método virtual? ¿Es imaginario? ? ? De hecho, no lo es, pero cuando está codificando, reescribe el método de la clase principal, y luego de la compilación, el sistema no sabe cuál es el método que usa, quién lo posee y cuál es el cuerpo del método. En este momento es una relación dinámica, solo sepa que cada método pertenece a cada clase y luego compílelo en un archivo de código de bytes de acuerdo con la clase.
De acuerdo con nuestro ejemplo anterior, puede ver el código desensamblado, cada método tiene ese logotipo:
invokevirtual ¡ Esto se maneja de acuerdo con el método virtual! Pero hay un método diferente, ¡se invoca especialmente ! Ese es el método de construcción, ¡es real! ¿Y por qué estos métodos son virtuales? ¡Porque estos métodos están a nivel de objeto, y el objeto puede o no ser llamado! ¡Así que todavía no hay ningún vínculo con la instancia! ¡Necesita enlace dinámico! (El método estático a nivel de clase no es un método virtual, ¡y es comprensible desde aquí!)

Este tipo de polimorfismo en tiempo de ejecución es para completar el enlace dinámico cuando se llama al método. En este momento, el sistema lo ejecuta, ¡que es el polimorfismo en tiempo de ejecución!

1.2. Modelado ascendente (mayor comprensión del tiempo de compilación y el tiempo de ejecución)

Supongamos que hay una clase principal: Persona
Persona tiene una subclase: Estudiante
tal línea de código: Person p = new Student();
o llámala en los parámetros de función:

Person p = new Student();

void fun(Person p){
    
    }

En este punto, ¡interpretemos los dos códigos anteriores!
Llamamos a este método de declarar una variable de un tipo de clase base y hacer referencia a ella con un objeto de subclase, que se llama modelado de seguimiento. Durante la compilación, es una variable de Person (clase base), pero durante el tiempo de ejecución, esta variable de referencia apuntará a Objeto de subclase. ¡Así que en realidad el objeto de p es la subclase Student! Luego, en la función, si el parámetro pasado es un objeto de Student, también se puede pasar a la función que requiere la aprobación de la clase Person, porque el alumno también es una persona, lo cual no es contradictorio (la subclase es una encarnación especial de la clase principal). Pero cuando se usa en el cuerpo del método, es el tiempo de ejecución. En este momento, se vinculará dinámicamente de acuerdo con la situación real, ¡y se usarán los métodos y propiedades del objeto específico!
Inserte la descripción de la imagen aquí

1.3. Demostración de método virtual y polimorfismo


public class VirtualMethod {
    
    
	
	static void GetDarw(Shape s) {
    
    
		s.darw();
	}
	
	static void GetSay(Shape s) {
    
    
		Shape.Say();
		// 写成 s.Say();会报警告!
	}
	
	public static void main(String[] args) {
    
    
		
		// 运行时多态:用重写来实现,调用的是虚方法!
		// 虚方法是除了static、private、final的方法,因为父类的这些修饰符的方法都是不可Override的!
		Circle c = new Circle();
		GetDarw(c);
		
		Triangle t = new Triangle();
		GetDarw(t);
		
		Line l = new Line();
		GetDarw(l);
		
//		Shape shape = new Circle();
//		shape.darw();  result: Circle is darwed! 因为实际shape是Circle的对象,这个是运行期决定的!
		
		
		// static修饰的方法,不算虚方法,是类级别的,是取决于类的声明的(在编译器)
		
		Circle StaticC = new Circle();
		GetSay(StaticC);// 无论传入的对象是什么,static的方法,只取决于类的声明,而与实例无关!
		
		
	}
}

class Shape {
    
    
	void darw() {
    
    
		System.out.println("Shape is darwed!");
	}
	
	static void Say() {
    
    
		System.out.println("Hello, i am Shape!");
	}
}

class Circle extends Shape {
    
    
	void darw() {
    
    
		System.out.println("Circle is darwed!");
	}
	
	static void Say() {
    
    
		System.out.println("Hello, i am Circle");
	}// 注意:Override不能重写父类的静态方法,这里不算重写!
}

class Triangle extends Shape {
    
    
	void darw() {
    
    
		System.out.println("Triangle is darwed!");
	}
	
	static void Say() {
    
    
		System.out.println("Hello, i am Triangle!");
	}
}

class Line extends Shape {
    
    
	void darw() {
    
    
		System.out.println("Line is darwed!");
	}
	
	static void Say() {
    
    
		System.out.println("Hello, i am Line!");
	}
}

Mira el efecto de correr:
Inserte la descripción de la imagen aquí

Análisis y resumen de la demostración de muestra:

El primer punto: primero, veamos el método Darw:

static void GetDarw(Shape s) {
    
    
		s.darw();
	}

La variable pasada en este método es del tipo Shape, pero debido a que se permite el modelado de seguimiento ascendente, podemos pasar el objeto de su tipo (es decir, el tipo Shape) al pasar el parámetro, o pasar la subclase del tipo Shape Objetos (es decir, los objetos Círculo, Triángulo y Línea de esta demostración), y en estos objetos, hemos reescrito los métodos de la clase padre Shape, por lo que es imposible determinar a quién se llama el método en tiempo de compilación y cómo implementarlo , Estos no se conocen, por lo que es un método virtual en este momento. Luego, en tiempo de ejecución, aclararemos a quién se refiere el parámetro, y luego seremos específicos de la instancia y llamaremos al método que pertenece a la instancia específica. Entonces, en el tiempo de ejecución, seremos específicos de los objetos de instancia entrantes a llamar, es decir, ¡anulan la implementación del método de la clase principal!

El segundo punto: echemos un vistazo al método GetSay:

static void GetSay(Shape s) {
    
    
		Shape.Say();
		// 写成 s.Say();会报警告!
	}

Este método se remonta al método Say de la clase principal:

static void Say() {
    
    
		System.out.println("Hello, i am Shape!");
	}

Este es un método que no admite el enlace dinámico (los métodos estáticos, privados y finales no pueden enlazarse dinámicamente porque no se pueden anular. No es necesario el enlace dinámico, pero se producirá un error si está enlazado. !!!)
Para este tipo de método que no admite enlace dinámico, solo pertenecerá a la clase declarada. Declaramos la clase Shape, por lo que este método estático llamará al método Shape. Es decir: para los métodos que no se pueden vincular dinámicamente, solo nos importa la declaración. ¡Qué tipo de declaración es inmediatamente qué tipo de clase, y no se vinculará dinámicamente!

Resumen final:

Inserte la descripción de la imagen aquí

Dos, clase interna y clase anónima

2.1, clase interna

¿Qué es una clase interna?
La clase interna es poner la definición de una clase en otra clase. Esta clase interna y otros métodos de la clase externa están en el mismo nivel, pero el compilador seguirá generando archivos de clase relacionados, llamados: nombre de clase externa $ nombre de clase interno El formato de .class ¿Uso de
clases internas?
Inserte la descripción de la imagen aquí
Lo más especial de las clases internas es que se usan en otros lugares: la primera es la instanciación: el
formato es: 外部类名.内部类名 in(对象名) = 外部对象.new 内部类名(构造函数的参数列表);
y luego el uso: el usado es bastante normal y no es necesario nombrar la clase externa.

2.2. Ejemplos de clases internas:


public class InnerClass {
    
    
	public static void main(String[] args) {
    
    
		Parcel p = new Parcel();
		
		p.getInfo();
		
		Parcel.Content c = p.new Content(20);
		Parcel.Destination d = p.new Destination("QingHai");
		p.getInfo(c, d);
		
		System.out.println(c.Val() + " " + d.getDes());
	}
	
}


class Parcel {
    
    
	
	public Parcel() {
    
    
		
	}
	
	public void getInfo() {
    
    
		Content cc = new Content(10);
		Destination dd = new Destination("Hunan");
		System.out.println("包裹送到 " + dd.getDes() + " ,里面装的内容是: " + cc.Val());
	}
	
	public void getInfo(Content c, Destination d) {
    
    
		System.out.println("包裹送到 " + d.getDes() + " ,里面装的内容是: " + c.Val());
	}
	
	protected class Content {
    
    
		private int c;
		public Content(int c){
    
    
			this.c = c;
		}
		public int Val() {
    
    
			return this.c;
		}
	}
	
	protected class Destination {
    
    
		private String des;
		public Destination(String des) {
    
    
			this.des = des;
		}
		public String getDes() {
    
    
			return this.des;
		}
	}
	
}

El resultado de la operación es:
Inserte la descripción de la imagen aquí

Interpretación de ejemplo:

¡Creo que este ejemplo es bastante claro!
Inserte la descripción de la imagen aquí
Cuando la clase interna está definida, escrita y utilizada en la clase, no hay una gran diferencia con otras clases ordinarias. La única diferencia es que puede agregar modificadores. Solo se pueden agregar public, protected, private, abstract y final, porque las clases internas también son clases. Los miembros de, ¡así que no hay gran diferencia con el método!

Nota: No puede usar estática para decorar clases internas, porque esta no es una clase interna, está a nivel de clase, ¡no a nivel de objeto!

Inserte la descripción de la imagen aquí
¿Por qué el segundo punto es así?
Debido a que la clase interna es un miembro de la instancia de la clase externa, al llamar al campo o método de la clase externa con el mismo nombre, debe usar el nombre de la clase externa. ¡Esta función es para decirle al sistema que estamos llamando al método de la clase externa! La razón es que el método (método general) de la clase externa está en el nivel del objeto. Primero tiramos del objeto relacionado (es decir, el puntero this), y luego usamos la instancia del objeto para acceder a él.

¿Cómo entender la instanciación de clases internas?
Debido a que el sistema no genera clases internas, también hemos visto que solo generará archivos de código de bytes de nombre de clase externa $ nombre de clase interno. Entonces, cuando queremos declarar la instancia de la clase interna, debemos obtenerla a través de la clase externa y luego instanciarla, porque la clase interna es generada por el objeto de instancia de la clase externa, por lo que necesitamos que el objeto de instancia sea nuevo, que es la instancia de clase externa. Nuevo ( Esto es solo una suposición personal, fácil de recordar, si es correcta, ¡bienvenido a discutir!)

2.2, clase anónima

La clase anónima es una clase interna especial. No tiene nombre y es de un solo uso. A menudo se usa en la situación real de parámetros de función para implementar interfaces.
Una clase anónima genera una instancia de objeto al mismo tiempo que se define, ¡lista para usar!
En cuanto al uso de
Inserte la descripción de la imagen aquí
clases anónimas : las clases anónimas no tienen nombre, ¡así que no hay método de construcción! Entonces, el formato comúnmente utilizado es:

new 接口名(参数){
    
    实现、Overwrite父类/接口方法}

A menudo se utiliza como parámetro de método.

Supongo que te gusta

Origin blog.csdn.net/qq_44274276/article/details/105396301
Recomendado
Clasificación