首先简要说明下程序运行时,内存的结构。堆区栈区,常量区,静态方法区和非静态方法区。
1.栈:存放基本类型的变量数据和对象的引用(也就是在new对象时左边那一块),但是对象本身不放在栈中,而是存在堆(new出来的对象)。栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。
2.堆:存放new出来的对象。堆中的对象由垃圾回收器负责回收,因此大小和生命周期不需要确定。
3.常量区:存放字符串常量和基本类型常量。
代码示例:
class Fu
{
void method1()
{
System.out.println("fu method_1");
}
void method2()
{
System.out.println("fu method_2");
}
static void method3()
{
System.out.println("fu method_3");
}
}
class Zi extends Fu
{
void method1()
{
System.out.println("zi method_1");
}
void method4()
{
System.out.println("zi method_4");
}
static void method3()
{
System.out.println("fu method_3");
}
}
class DemoDuotai
{
public static void main(String args[])
{
Fu aa=new Zi();//创建一个父类引用,指向子类。
aa.method1();//发生动态绑定,打印子的
aa.method3();//打印父的
}
}
内存图简要示例
分析
aa.method1()-->this.method1()-->(new Zi()).method1()
- 我们知道类的非静态成员函数,需要类对象来调用。aa是指向Zi类的对象,method1()在运行时一定是被对象调用执行,因为他打印的时候或者访问的时候是访问对象中的那些数据。
- 而静态方法就不同了,他本身不访问对象特有数据。当Fu类和Zi类被加载到内存的时候,静态方法区就已经在里面啦,并且绑定在类上,所以可以直接采用类名调用,也就是所谓的静态绑定。他找的是静态区的方法,不参考右边的对象。所以aa.method3();只参考这个引用型变量aa是哪个类的。
总结
- 编译时期:参考应用型变量所属的类中是否有调用的方法。如果有,编译通过,如果没有,编译失败。
- 运行时期:参考对象(new出来的对象)所属的类中是否有调用的方法。(动态绑定)。
- 多态中,成员变量的特点:无聊编译运行,都参考左边(引用型变量所属的类)。
- 多态中,静态函数的特点:无聊编译运行,都参考左边。(静态绑定)
java编译和运行时的区别
举个例子来说明编译时(javac)和运行时(java)的区别,代码如下:
class Demo
{
private int num;
Demo(int num)
{
this.num=num;
}
public boolean equals(Object d)
{
Demo a=(Demo)d;
return this.num==a.num;
}
}
class Person
{
}
public class object_equals
{
public static void main(String[] args)
{
Demo a=new Demo(3);
Demo b=new Demo(4);
Person c=new Person();
System.out.println(a.equals(c));
}
}
结果:编译通过,运行挂掉
分析:equals方法是超类Object中的方法,我们把他复写,调用equals时,参数 Object d=new Person()这里发生了向上转型,接着Demo a=(Demo)d 编译的时候是不会出错的,因为此处的d引用变量是绑定的Object这个类;而运行时,发生动态绑定也就绑定到了Person类上,把Person类的对象转化成Demo类的对象,出现类型转换错误。
改正:加上以下判断即可.
if(!d instanceof Demo) return ;