文章目录
1. 概念补充
1.1. 访问修饰符
header 1 | 同一个类 | 同一个包 | 不同包的子类 | 不同包的非子类 |
---|---|---|---|---|
private | √ | |||
default | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
1.2. 异常
java中的异常都是Throwable的子类,主要有两个方向 Error和Exception两种主要子类
未检查异常
也称为运行时异常 RuntimeException及其子类
检查异常
1.3. 重载
定义
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同
规则
- 方法名一定要相同
- 方法的参数表必须不同,包括参数的类型或个数,以此区分不同的方法体
- 如果参数个数不同,就不管它的参数类型了
- 如果参数个数相同,那么参数的类型必须不同
- 方法的返回类型、修饰符、抛出异常可以相同,也可不同
- main 方法也可以被重载
- final修饰的方法可以重载。
1.4. 重写
定义
重写简单来说就是子类按照相同的方法名方法返回值,覆写父类的方法来实现子类的逻辑
规则
- 只能重写可以被继承的方法 不能重写
静态方法
private方法
final方法
构造方法
- 访问级别 可以比父类的更宽泛
- 返回值 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类
- 参数 必须相同
- 异常部分
- 可以随意抛出运行时异常
- 重写方法不能抛出新的异常
- 可以抛出父方法异常的子异
- 可以抛出部分异常
- 可以不抛出异常
Class Animal{
public void sayHello(){
}
}
1.5. 多态
- 多态是同一个行为具有多个不同表现形式或形态的能力。
- 多态就是同一个接口,使用不同的实例而执行不同操作。
- 多态性是对象多种表现形式的体现。
1.6. 分派
分派是什么,我看了很多资料都没有给一个定义。从我从现在接触的资料来看,分派的定义为:
在需要调用一个方法时需要确定需要掉用的是哪个类的哪个方法
之所以要确定这个是因为重写和重载机制的存在
java多态特性决定了执行方法前必须进行分派。
1.7. 静态类型和实际类型
Animal animal = new Cat();
等式左边叫静态类型,等式右边是实际类型
比如 animal 这个引用,Animal,实际类型是Cat
静态类型在编译时就已经确定,但是实际类型只有在运行时才能确定。
有人说 不是已经赋值Cat类型了吗 那么我们换个写法
public static void sayHello(Animal animal){
animal.syaHello();
}
public static void main(String args[]){
sayHello(new Cat());
sayHello(new Dog());
}
由于java的继承特性和多态特性,对于sayHello方法本身来说,在没有被真正调用的时候是无法知道实际类型的,只知道静态类型为Animal
1.8. 虚方法
可以被覆写的方法都可以称作虚方法
因此虚方法并不需要做特殊的声明,也可以理解为除了实力构造器、父类方法及用static、final、private修饰之外的所有方法都是虚方法
1.9. 宗量
方法的接收者与方法的参数统称为方法的宗量。
2. 方法调用
java虚拟机有五种调用方法的字节码指令
- invokestatic 调用静态方法
- invokespecial 调用实例构造器方法、私有方法和父类方法
- invokevirtual 调用虚方法
- invokeinterface 调用接口方法
- invokedynamic 现在运行时动态解析出调用点限定符引用的方法,然后执行
2.1. 解析
在解析阶段 静态方法
、私有方法
、实例构造器<init>方法
和父类方法
的调用会被编译成为 invokestatic
invokespecial
指令
这两个指令都可以直接明确到执行方法的是哪个类,哪个方法,没有疑问,所以在类加载的时候就会把符号引用解析为该方法的直接应用。
2.2. 分派
2.2.1. 静态分派
public class StaticDispatch {
static abstract class Human{
}
static class Man extends Human{
}
static class Woman extends Human{
}
public static void sayHello(Human guy){
System.out.println("hello,guy!");
}
public static void sayHello(Man guy){
System.out.println("hello,gentlemen!");
}
public static void sayHello(Woman guy){
System.out.println("hello,lady!");
}
public static void main(String[] args) {
Human man=new Man();
Human woman=new Woman();
sayHello(man);
sayHello(woman);
}
}
运行结果
hello,guy!
hello,guy!
我们把“Human”称为变量的静态类型,后面的“Man”称为变量的实际类型
编译器在编译期并不知道一个对象的实际类型是什么
编译器在重载时是通过参数的静态类型而不是实际类型作为判定的依据
并且静态类型在编译期可知,因此,编译阶段,Javac编译器会根据参数的静态类型决定使用哪个重载版本
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,其典型应用是方法重载(根据参数的静态类型来定位目标方法)
静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机执行的。而很多情况下重载版本并不是唯一的,往往只能确定一个更加合适的版本。
自动类型转换
public class Overload {
public static void sayHello(Object arg) {
System.out.println("hello Object");
}
public static void sayHello(int arg) {
System.out.println("hello int");
}
public static void sayHello(long arg) {
System.out.println("hello long");
}
public static void sayHello(Character arg) {
System.out.println("hello Character");
}
public static void sayHello(char arg) {
System.out.println("hello char");
}
public static void sayHello(char... arg) {
System.out.println("hello char……");
}
public static void sayHello(Serializable arg) {
System.out.println("hello Serializable");
}
public static void main(String[] args) {
sayHello('a');
}
}
直接执行 输出
hello char
注释掉sayHello(char arg)
不会出错 输出hello in
注释掉sayHello(int arg)
不会出错 输出hello long
注释掉sayHello(long arg)
不会出错 输出hello Character
注释掉sayHello(Character arg)
不会出错 输出hello Serializable
注释掉sayHello(Serializable arg)
不会出错 输出hello Object
注释掉sayHello(Object arg)
不会出错 输出hello char……
这儿之所以不会报错是因为有个自动的类型转换,当前类型找不到之后会自动类型转换继续查找到最合适的版本。转换的顺序为
char -> int -> long -> 装箱类型 -> 装箱类型的接口 -> 父类 -> 可变参数
2.2.2. 动态分派
我们把在运行期根据实际类型确定方法执行版本的分派过程为动态分派。
public class DynamicDispatch{
static abstract class Human{
protected void sayHello(){
System.out.println("human sya hello");
}
}
static class Man extends Human{
protected void sayHello(){
System.out.println("man sya hello");
}
}
static class Woman extends Human{
protected void sayHello(){
System.out.println("woman sya hello");
}
}
public static void main(String[]args){
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
}
}
运行结果
man sya hello
woman sya hello
woman sya hello
这个可能大家都不会对结果有任何疑问。而且很明显这儿调用哪个方法不再是使用静态类型来来决定的而是通过实际类型决定的。
通过javap命令我们可以知道 调用方法是使用的 invokevirtual
字节码指令来实现的。
他的执行流程如下:
- 找到对象引用的对象实际类型。
- 在对象实际类型中找方法,如果找到且允许访问,返回方法的直接引用。
- 如果2查找不到那么按照继承关系从下往上依次查找,查找方式按照2的方式执行。
- 如果查找到最后一个类也查找不到那么抛出异常AbstractMethodError;
2.2.3. 单分派和多分派
方法的接收者与方法的参数统称为方法的宗量。
根据分派基于多少种宗量,将分派划分为单分派和多分派两种。
方法的接收者,在运行时可以理解为方法的实际执行者。
Java是一种静态多分派,动态单分派的语言,至少现阶段是。