一、摘要
从毕业参加工作到现在也有两年半的时间了,今天在OSC开始自己的博客生涯。这篇博文的名字叫做”始于Java”,是因为两年半的工作期间我的主力编程语言一直是Java。Java是一门面向对象的编程语言,它发展自C++,而C++又是C的面向对象扩展,所以Java也是一门C family的编程语言。在学生时代,最先接触的是C,之后是C++,然后工作主要使用的是Java,由于C/C++先入为主,总是喜欢站在底层的角度去认识去学习Java。这篇博文不会去深入Java的语法细节,将会从底层的内存模型和对于面向对象的实现去简单的介绍一下Java。
二、Java的内存模型
1.堆、栈、常量池
Java有3个地方存储变量,分别为:堆、栈、常量池。
与C++类似,通过new操作符实例化的对象或数组存储于堆中。
局部变量和方法形参存储于栈中,像C/C++在x86平台就是通过栈基址寄存器EBP+偏移地址来访问局部变量的,每进入一个函数,当前的栈基地址会存储于栈顶,外层函数的栈顶会变成被调用的函数的栈底,Java也与此类似。
常量存储于常量池中,像一些字面的数值、字符串等都属于常量。
2.Java的引用
Java中通过引用来访问对象或者数组。引用相当于存储了对象在堆内存中的地址,所以可以通过引用来访问对象。虽然C++中也有引用,但是C++的引用只能绑定一次,不像Java的引用可以多次绑定到不同的对象,所以Java的引用更像C/C++中的指针。
在C/C++中存在左值和右值的概念。对于一块内存,可以对其进行三种操作:读、写、取地址,左值表达式就相当于一块内存,可以对其进行上述的三种操作;而右值表达式相当于一个临时的值,只能对其进行读操作。Java中的左值和右值相比C/C++淡化许多,可以根据表达式的语义来判断它是左值或是右值,一个表达式对应一个对象、局部变量、成员变量、方法形参等就表明该表达式是左值。
3.垃圾回收
堆内存垃圾回收。Java具有自动垃圾回收机制,所以开发者不用去主动释放通过new实例化的对象和数组。jvm记录有所有通过new实例化的对象和数组,jvm在内存不足或者程序主动调用System.gc()时,会从若干根节点起始,通过对象之间的引用关系遍历整棵对象树,遍历完成之后,没有被遍历过的对象会被作为垃圾释放掉。
栈内存的释放。同C/C++一样,执行的代码在退出函数或方法时,函数内定义的局部变量会被自动释放掉。
三、Java的面向对象
封装、继承、多态是面向对象的三大特性,一门面向对象的编程语言都应该具有这三大特性,Java当然也是如此。
1.封装
继续拿C++出来说事,同出一辙的private、protected、public修饰符。private成员只有自己、内部类、外部类是可见的。protected成员只有自己,内部类,外部类,子类,同一package内的类是可见的。
2.继承
不同于C++的多继承,Java是一门单继承的编程语言,但是Java的接口机制允许实现多个接口,在一定程度上弥补了单继承的不足。子类不可继承父类的private和final修饰的成员。
3.多态
多态是用父类/接口的引用来绑定到子类/实现类的实例,通过这种引用调用父类/接口的方法,实际上调用的是子类/实现类override父类的方法。
说到多态就不得不提动态绑定这个概念,Java的protected和public方法都是动态绑定的。在C++中,调用了一个类的非虚方法采用的是静态绑定,编译器在调用方法的地方的处理是:将参数入栈,然后通过类似于JMP的指令直接跳转到该方法对应的机器指令的首地址,也就是说编译器在编译时就知道了方法的明确地址。当调用一个C++对象的虚方法时,方法的首地址存储于对象指向的虚函数表中,也就是说对于动态绑定,编译器在编译时不知道方法的明确地址,但是知道在运行时获取方法地址的手段(即运行时从虚函数表中取得方法的地址)。简而言之,静态绑定就是在编译时获取函数或方法的地址,动态绑定就是在运行时获取函数或方法的地址。
而Java的protected和public方法在调用时都是动态绑定的,都是在运行时来获取方法的地址。