对象构造过程
- 所有属性,如果在声明中没有明确赋值,则初始化为缺省值(
0
,false
,null
); - 按照声明次序,依次执行
static
语句(块);(仅在类首次被加载的时候,执行一次,并且,如果该类继承自某个类,则先执行父类的static
语句) - 按照声明次序,依次执行对象语句块(类似的,如果该类继承自某个类,则先执行父类的语句块);
- 执行构造方法(如果在一个构造方法中,有调用另外一个构造方法,则先执行被调用的方法主体)。
方法调用过程
方法调用并不等同于方法执行,而是确定被执行的方法版本的过程。
方法调用有解析和分派(Dispatch)两种类型。
如果被调用的方法,在真正运行前就已经确定,并且在运行期不可变,这类方法的调用就称为解析。
而分派又可以分为静态分派和动态分派。
静态分派会根据传入参数的静态类型选择被调用方法的执行版本。对于同一个类或接口中的重载(Overload)方法,会采用静态分派。
动态分派则会根据调用者的实际类型选择被调用方法的执行版本。子类对父类的覆盖(Override)方法,会采用动态分派。
- 如果是
private
、static
、final
方法或者是构造方法,那么在编译期就能够确定方法的执行版本,等到类加载阶段就可以解析成直接引用; - 静态分派也是在编译期完成的,它和解析并不是排它关系(想想静态方法也是可以重载的),它们分别在不同层次上去确定方法的执行版本;
- 而动态分派则是在运行时完成的,执行引擎首先会根据类名、方法名,获取所有的候选方法。然后,再根据方法的参数列表,挑选出最合适的方法(如果同一个方法签名能够匹配到 0 个或者是多个方法,都会报错)。挑选的时候,首先会在实际类型中寻找匹配的方法,否则在父类中继续查找。因为每次调用方法都需要进行搜索,因此,虚拟机会预先为每个类创建一个方法表(一般发生在类加载的链接阶段),来提升性能。
其它
- 使用
this
关键字,显式地区别对象的属性和局部变量。 - 对象的属性会初始化为缺省值(如果不嫌麻烦,明确初始化值更好),而局部变量则不会。
- 使用
getter
和setter
方法来操纵对象的属性的主要好处是:增强了类的封装性,它提供了修改类内部实现的可行性。 static
变量,属于类变量,为该类所有对象所共享,每个对象都有一份自己的拷贝。- 使用
static
变量和static
方法时,都应使用类名来调用。 - Java 的方法都是传值调用(call by value),如果方法的参数是一个对象的引用,那么传递的也只是这个引用的一份拷贝。(不妨考虑一下:在一个方法内交换2个对象引用的例子:
swap(User a, User b)
)。 - 直接返回一个对象的引用时,一定要慎重,因为这可能会破环封装性,不妨考虑一下,能否只是返回该对象的一个克隆作为替代。
- Java 通过包(
package
)机制来组织类,相当于命名空间的作用,可以避免类名的冲突。但是这由产生了另外一个问题,就是使用一个类时,需要书写完整的类名,非常繁琐。因此,又有了import
。通过import
事先告诉编译器应该去哪里查找某个类,从而达到了简写的目的。精确导入一个类,不会减少代码的大小,但是可以减少编译的时间,也有利于阅读(特别是打印出来的时候)。 finalize
方法会在垃圾回收器清除对象之前调用,但是由于很难确定它真正的调用时机,所以不要使用它。- 封装性是提升类的重用性和可靠性的保证。应尽量私有化属性和方法,因为一旦公开,也就意味着不受控制,不可修改。
- 应尽可能地减少类和类之间的依赖,也就是所谓的“低耦合”。
- 继承,体现了“is a”的关系。主要的好处是:代码复用。
- 抽象类是不完整的(接口也是如此,就好像一张未完成的蓝图),因此,不要尝试使用它来构造对象(在使用匿名类的时候可能会产生错觉)。但是,通过声明抽象类或接口类型的变量,来实现多态是可取的。
- 抽象方法,没有方法体,就好比先圈好地,然后等着子类来盖楼。
- 关于访问控制:
private
- 默认(也就是不写修饰符) -protected
-public
,封闭性递减。
暂时先这么多吧,后面想到再补充。