方法调用和方法的绑定机制
方法调用:虚方法与非虚方法
在jvm中将符号引用为调用方法的直接引用与方法的绑定机制有关,如果方法在在编译期就确定了调用的具体版本,这个版本在运行的时候是不可变的,这样的方法被称之为非虚方法。静态方法,私有方法,final方法,实例构造器,父类方法都是非虚方法。其它的方法被称之为虚方法。
虚拟机中提供了以下几条方法调用指令:
普通调用指令:
1、invokestatic:调用静态方法,解析阶段确定唯一方法版本
2、 invokespecial:调用方法、私有及父类方法,解析阶段确定唯一方法版本
3、invokevirtual:调用所有虚方法
4、 invokeinterface:调用接口方法
动态调用指令:
invokedynamic:动态解析出需要调用的方法,然后执行
前四条指令固化在虚拟机内部,方法的调用执行不可人为千预,而invokedynamic指令则支持由用户确定方法版本。
其中invokestatic指令和invokespecial指令调用的方法称为非虚方法,其余的(final修饰的除外)称为虚方法。
@FunctionalInterface
interface Func {
public boolean func(String str);
}
public class Lambda {
public void lambda(Func func) {
return;
}
public static void main(String[] args) {
Lambda lambda = new Lambda();
Func func = s -> {
return true;
};
lambda.lambda(func);
lambda.lambda(s -> {
return true;
});
}
}
动态类型的语言与静态类型的语言
两者的区别是在对于类型的检查是在编译期还是运行期,满足在编译期对类型的检查的是静态类型语言,反之是动态类型的语言。
private String str= “abcd”// str = abcd
// 判断str的是静态类型语言(强语言,如:java),判断abcd的是动态类型的语言(弱语言,如:js,python)。
静态类型的语言是判断变量自身的类型信息,动态类型的语言
是判断变量值的类型信息,变量没有类型信息,变量值有类型信息。
class Father implements MethodInterface{
public Father() {
System.out.println("father的构造器");
}
public static void showStatic(String str) {
System.out.println("father " + str);
}
public final void showFinal() {
System.out.println("father show final");
}
public void showCommon() {
System.out.println("father 普通方法");
}
@Override
public void methodA() {
System.out.println("我是接口方法!");
}
}
public class Son extends Father{
public Son() {
//invokespecial:调用<init>方法,私有及父类方法,解析阶段唯一确定调用方法
super();
}
public Son(int age) {
//invokespecial:调用<init>方法,私有及父类方法,解析阶段唯一确定调用方法
this();
}
//不是重写的父类的静态方法,因为静态方法不能被重写!
public static void showStatic(String str) {
System.out.println("son " + str);
}
private void showPrivate(String str) {
System.out.println("son private" + str);
}
public void show() {
//invokestatic:调用静态方法,解析阶段唯一确定调用方法
showStatic("www.baidu.com");
//invokestatic
super.showStatic("good!");
//invokespecial
showPrivate("hello!");
//invokespecial
super.showCommon();
//invokevirtual:调用所有虚方法
showFinal();//因为此方法声明有final,不能被子类重写,所以也认为此方法是非虚方法。
//虚方法如下:
//invokevirtual
showCommon();
info();
MethodInterface in = new Father();
//invokeinterface:调用接口方法
in.methodA();
}
public void info(){
}
public void display(Father f){
f.showCommon();
}
public static void main(String[] args) {
Son so = new Son();
so.show();
}
}
interface MethodInterface{
void methodA();
}
静态链接
当一个字节码文件被装载进jvm内部时,如果被调用的目标方法在编译期内便可知,且运行时不变,这种情况下将调用方法的符号引用转换为直接引用的过程被称为静态链接。
动态链接
如果被调用的方法在编译期内无法被确认下来,只能够在程序运行期将调用方法的符号引用转换为直接引用,这种的引用过程具备动态性,因此被称为动态链接。
class Lesson{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 静态方法:非虚方法
public static void returnName(){
System.out.println("我的名字是课程!");
}
}
class Teacher extends Lesson implements Method{
private String name;
private int age;
// 私有方法:非虚方法
private void TeacherName(){
System.out.println("我比较害羞,我是私有方法!");
}
// final方法:非虚方法
public final void returnData(){
System.out.println("我要返回一个常量!Hello Word!");
}
// 构造器
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public void teachLesson(String name) {
super.setName(name);// 静态链接,其他类似
System.out.println("调用父类构造器方法:"+super.getName());
this.age = 24;
this.name = "小红";
// 调用父类静态方法
Lesson.returnName();
returnData();
}
@Override
public String toString() {
this.TeacherName();
return "Teacher{" +
"我是:" + name +
",年龄是:" + age +
",教授的课程是:"+ super.getName()+
'}';
}
}
interface Method{
void teachLesson(String name);
}
public class VirtualMethodTest01 {
public static void main(String[] args) {
Teacher teacher = new Teacher();
// 运行期才确定的:虚方法。
teacher.teachLesson("高等数学");// 动态链接,其他类似
System.out.println(teacher.toString());
}
}
结果:
方法绑定
对应的方法的绑定可分为早期绑定和晚期绑定,绑定是一个字段、方法、或者类在符号引用被替换成直接引用,这个过程只发生一次。
早期绑定
早期绑定是指被调用的目标方法如果在编译期内可知,且运行期保持不变的时候,即可将这个方法和所属的类型进行绑定,由于明确了被调用的目标方法是哪一个,因此可以将静态连接的方式将符号引用转换为直接引用。
晚期绑定
如果被调用的方法在编译期无法被确定下来,只能够在程序运行期根据实际的类型绑定的相关方法,这样被称之为晚期绑定。
class Animal{
public void eat(){
System.out.println("动物进食");
}
}
interface Huntable{
void hunt();
}
class Dog extends Animal implements Huntable{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
@Override
public void hunt() {
System.out.println("捕食耗子,多管闲事");
}
}
class Cat extends Animal implements Huntable{
public Cat(){
super();//表现为:早期绑定
}
public Cat(String name){
this();//表现为:早期绑定
}
@Override
public void eat() {
super.eat();//表现为:早期绑定
System.out.println("猫吃鱼");
}
@Override
public void hunt() {
System.out.println("捕食耗子,天经地义");
}
}
public class AnimalTest {
public void showAnimal(Animal animal){
animal.eat();//表现为:晚期绑定
}
public void showHunt(Huntable h){
h.hunt();//表现为:晚期绑定
}
}
线程安全问题
如果只有一个线程可以操作这个数据,那必然是线程安全的。
如果有多个线程可以操作这个数据,这个数据是处于内存共享区域,如果不考虑同步机制,那这个数据是存在线程安全问题。
public class StringBuilderTest {
int num = 10;
//s1的声明方式是线程安全的
public static void method1(){
//StringBuilder:线程不安全,没有线程同步机制
// StringBUffer: 线程安全,有线程同步机制,内部方法有synchronized修饰
StringBuilder s1 = new StringBuilder();
StringBuffer s2 = new StringBuffer();
s1.append("a");
s1.append("b");
s2.append('c');
s2.append('d');
//...
}
//sBuilder的操作过程:是线程不安全的
public static void method2(StringBuilder sBuilder){
sBuilder.append("a");
sBuilder.append("b");
//...
}
//s1的操作:是线程不安全的
public static StringBuilder method3(){
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
return s1;
}
//s1的操作:是线程安全的
public static String method4(){
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
return s1.toString();
}
public static void main(String[] args) {
StringBuilder s = new StringBuilder();
new Thread(() -> {
s.append("a");
s.append("b");
}).start();
method2(s);
}
}