两个概念:
一,编译单元:一个.java文件就是一个编译单元。一个.java文件会被编译成一个或多个.class文件。在eclipse等IDE中,默认自动编译,即,一旦.java文件被改动则重新编译并产生.class文件。
二,类的加载:一旦已运行的程序第一次访问一个类的静态数据或者创建一个类的对象时,jvm就会根据包名及类名到classpath下的对应文件夹中找到相应的.class文件将其加载到内存中,此时,程序进入运行时。
所以,编译期和运行时的物理边界是类的加载时。
如果一段程序在运行时每次执行都产生固定的结果,那么这段程序完全可以在编译时直接输出该结果,从而减轻运行时的压力。这是编译期和运行时的另一种边界。
三、绑定:将一个方法调用(既指向方法所在内存的引用)同一个方法主体(内存中的代码块)关联起来的过程。
//RandomTest.java文件中 public Class RandomTest { private Random rand = new Random(47); public int getRandomNum() { return rand.nextInt(3); } }
//RandomInt.java文件中public Class RandomInt { public static void main(String[] args) { RandomTest rdt1 = new RandomTest(); RandomTest rdt2 = new RandomTest(); int randomNum1 = rdt1.getRandomNum(); int randomNum2 = rdt2.getRandomNum(); System.out.println(randomNum1); System.out.println(randomNum2); }} 输出:2 1
当main方法中的getRanomNum方法执行时,getRandomNum方法已经被编译完成了。但最终却产生了不同的结果,说明nextInt方法是在运行时才产生结果,而非编译时就产生结果。
一次编译、两次运行、两个结果。说明了在运行时才产生了最终的结果而非编译时。
//A.java中public class A { public void m() { System.out.print("A"); }}public class B extends A { public void m() { System.out.print("B"); }}public class C extends A { public void m() { System.out.print("C"); }}//Test.java中public class TestA { public static void testA(A a) { a.m(); }}public class Test { public static void main(String[] args) { TestA testA = new TestA(); B b = new B(); C c = new C(); testA(b); testA(c); }}输出:B C
同理,对于TestA()方法来说,一次编译,两次执行,产生了两个结果。
说明,在编译TestA时,a.m()并没有指向具体的方法体(内存地址)。而是在运行时,jvm根据入参的类型,才将a.m()指向不同的方法体(内存地址)。