Java学习笔记:多态 & 虚方法、内部类 & 匿名类

一、多态性 & 虚方法

1.1、什么是多态?

多态即是在程序中,一个名字代表着多种不同的含义
多态分为两种情形:(为何分为不同时期?先可思考!)

第一:编译时多态

典型实现编译时多态的方式:重载(Overload)
为何有编译时多态?为何重载是典型的实现编译时多态?
在程序中,也就是在编写程序的时候,也许某个方法我们用了相同的名字,但是我们想实现多态,必然还是会让一些同名的方法有所区别,完全无法区别是不可能实现多态的!那何时区别呢?程序执行有编译期和运行期,这两个阶段各自有区别编码时同名方法的手段。在编译期,我们的重载函数,经过编译、汇编、连接,最终执行的阶段(Java应该是生成字节码文件)。而这个重载,正是在编译的阶段完成了同名方法的区分 如何区分的呢?
比如:示例程序 & 反汇编代码(反汇编命令:javap -c 文件名)
在这里插入图片描述
在这里插入图片描述
从这里可以看出来,经过编译之后,我们的三个重载函数是完全不一样的,如此便可得知这是在编译器完成了区分!

第二:运行时多态

典型实现运行时多态的方式:重写(Override)
重写必须在继承关系中才能存在,没有继承就没有重写!由于是在继承关系中,我们子类对父类的方法进行重写,此时在子类的对象调用该方法的时候,就不会调用父类的方法了,而是调用子类的重写过后的方法!这不就是对同名称的方法在不同的运用情境下有不同的功效吗?所以重写是典型的多态表现。那么重写为何是运行期多态呢?又是如何来区分同名方法何时去调用子类的,何时调用父类的呢?

这是靠系统来决定的,系统有一种机制:动态绑定!也叫虚方法的调用,为什么叫虚方法呢?它很虚???其实不是,而是在你编码的时候,重写父类的方法,然后编译之后,系统都不晓得你用的方法到底是个什么方法,归谁所有,方法体如何,都不知道!此时是一个动态关系,只晓得每个方法属于每个类,然后按类完成编译成为字节码文件!
就按照我们上面的例子,可以看到反汇编的代码,每个方法都有这么一个标识:
invokevirtual 这就是按照虚方法处理的!但是有一个方法不同,它是invokespecial 的!那个是构造方法,它是实实在在的!而为什么这些方法是虚方法呢?因为这些方法都是对象级别的,且是对象可能调用也可能不调用的!所以还没有与实例进行绑定!需要动态绑定!(类级别的static方法不是虚方法,从此处也是可以理解的!)

这种运行时多态是在调用方法的时候,才会完成动态绑定,此时是系统来运行的,就是运行时多态!

1.2、上溯造型(进一步理解编译期和运行期)

假设有一个父类:Person
Person有一个子类:Student
这么一行代码:Person p = new Student();
或者是在函数参数里这样调用:

Person p = new Student();

void fun(Person p){
    
    }

此时,上述两份代码,我们来解读一下!
我们把这种声明一个基类类型的变量,用子类对象来引用的方法,叫做上溯造型,在编译期间,它是Person(基类)的变量,但是在运行的期间,这个引用变量会指向子类的对象。所以实际上p的对象是子类Student的!然后在函数中,如果传参是一个Student的对象,也能传给要求传Person类的函数,因为学生也是人,这不矛盾(子类是父类的特殊具体化)。但是在方法体中使用的时候,就是运行期了,这时候就会根据实际情况,动态绑定,就会使用具体对象的方法、属性了!
在这里插入图片描述

1.3、虚方法 & 多态的示例Demo


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!");
	}
}

看看运行效果:
在这里插入图片描述

示例Demo的分析与总结:

第一点:首先我们来看Darw这个方法:

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

这个方法传入的变量是Shape类型的,但是由于上溯造型是允许的,所以我们传参的时候是可以传入它的类型的对象(也就是Shape类型),也可以传入Shape类型的子类对象(也就是此Demo的Circle、Triangle、Line的对象),而在这些对象中,我们都重写了父类Shape的方法,所以在编译期是无法确定调用的是谁的方法,具体实现如何,这些都不知道,所以此时是虚方法!然后在运行期,我们会明确参数的引用对象是谁,此时才会具体到实例,去调用属于具体实例的方法!所以在运行的时候,我们会具体到传入的实例对象,去调用,也就是它们重写父类的方法实现!

第二点:再来看看GetSay这个方法:

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

这个方法我们追溯到父类的Say方法:

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

这是一个不支持动态绑定的方法(static、private、final方法都是不允许动态绑定的,因为它们都不可以重写!没有动态绑定的必要,绑定了反而出错!千万注意!!)
对于这种不支持动态绑定的方法,只会属于声明的类,我们声明的是Shape类,于是乎这个static方法就会调用Shape的方法!也就是说:对于不可以动态绑定的方法,我们只在乎声明,声明的是什么类就立即是什么类,不会动态绑定!

最后总结:

在这里插入图片描述

二、内部类 & 匿名类

2.1、内部类

什么是内部类?
内部类就是把一个类的定义放在另一个类里面,这个内部类和外部类的其他方法是同级别的,但是编译器还是会生成相关的class文件,命名为:外部类名$内部类名.class的格式
内部类的使用?
在这里插入图片描述
内部类最特殊的地方就在于在其他地方使用内部类,首先是实例化:
格式为:外部类名.内部类名 in(对象名) = 外部对象.new 内部类名(构造函数的参数列表);
然后是使用:使用的化其实挺正常的,不需要冠以外部类的名字

2.2、内部类的实例:


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;
		}
	}
	
}

运行结果是:
在这里插入图片描述

实例解读:

我相信这个实例已经是相当明了了!
在这里插入图片描述
内部类在类里面定义、书写、使用的时候与其他的普通类没啥大区别,唯一的区别就是它可以加修饰符,只能加public、protected、private、abstract、final,因为内部类也是类的成员,所以和方法没啥大区别!

注意:不能用static修饰内部类,因为这样就不是内部类了,那是类级别,不再是对象级别!

在这里插入图片描述
为何第二点是这样的呢?
因为内部类是外部类的实例的成员,所以调用外部类的同名字段或者方法的时候,需要用外部类名.this,这样的作用是告诉系统,我们调用的是外部类的方法!原因是外部类的方法(一般方法)是对象级别的,我们先牵引到相关对象(即是this指针),然后用对象实例去访问!

如何理解内部类的实例化呢?
因为系统并没有生成内部类,我们也看到了,只会生成外部类名$内部类名.class的字节码文件!于是我们要声明内部类实例,就必须通过外部类来得到,然后实例化的时候,因为内部类是依托外部类的实例对象产生的,于是需要实例对象去new,也就是外部类实例.new(这只是个人猜想,方便记忆,是否正确,欢迎讨论!)

2.2、匿名类

匿名类是一种特殊的内部类,它没有名字,属于一次性使用的东西,经常用在函数参数实现接口的实际情景!
匿名类在定义的同时就生成了对象实例,即来即用!
有关匿名类的使用:
在这里插入图片描述
匿名类没有名字,于是也没有构造方法!然后常用的格式是:

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

常常用来作为方法的参数!

猜你喜欢

转载自blog.csdn.net/qq_44274276/article/details/105396301