对于初学java的同学,应该都有个疑惑,我们在定义一个数据类的时候,为什么不把字段直接写成public的,硬是要把属性定义成private的,然后给属性加上getset方法,比如下面这两种写法
class Data{
public String name="";
public int age=1;
}
class Data{
private String name="";
private int age=1;
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;
}
}
之前一直以为是为了做方法封装,我们可以在属性的getset方法中加入一些逻辑,扩展性更好,所以没有深究
直到后来学习了类的加载过程,看到了一道这样的题
public void main(){
Father fa=new Son();
System.out.println(fa.age);
System.out.println(fa.name);
System.out.println(fa.getName());
}
class Father{
public String name="张三";
public int age=1;
public String getName(){
return name;
}
}
class Son extends Father{
public String name="李四";
public int age=2;
public String getName(){
return name;
}
}
这是一道典型的面向对象的多态的例子,我们当初接触java的面向对象的时候,最开始接触的就是这种写法。
大家猜测一下输出结果是什么
1
张三
李四
不知道大家猜对了没有。
下面来解释一下原因:
我们都知道每个类里面都有一个this关键字指向本类对象,用来告诉我们当前使用变量的属于哪个实例对象,也就是所谓的上下文。
类初始化的时候,每个非静态方法内部都会有一个变量this,这个可以通过查看自字节码看出来,包括匿名内部类,也会持有当前类的this对象(这个就是android handler内存泄露的原因之一)。
所以在在方法内调用属性,其实相当于这么写
public String getName(){
return this.name;
}
但是属性是没有this指针的,也就是说属性是没有多态的,我们调用的对象是什么,输出之后就是哪个对象的属性。
//大家可以试试这个例子,输出结果都是=号前面的对象的属性。
public void main(){
Son son=new Son();
System.out.println(son.age);
System.out.println(son.name);
System.out.println(son.getName());
Father fa=new Father();
System.out.println(fa.age);
System.out.println(fa.name);
System.out.println(fa.getName());
}
而这种多态的写法是面向对象的核心所在,在一些大的工具框架内随处可见,比如安卓的源码中的Context类,Application,Activity,Service等一众关键类都继承自Context。
那为啥同样编译成字节码的kotlin却可以直接调用属性呢,那是因为kotlin默认帮你实现了属性的getset方法
//这时候编译器会提示我们Redundant getter ,多余的getset方法
class Data{
var name:String=""
get() {return field}
set(value) {
field=value
}
}
其实我们在刷面试题的时候有很多对应的面试题:比如下面这个,属于经典面试题
public void main() {
// Father father = new Father();
Father fa = new Son();
// Son son = new Son();
}
class Father {
public String name = "张三";
public Father() {
System.out.println(name);
System.out.println(this.name);
getName();
}
public void getName() {
System.out.println("调用的Father");
System.out.println(this.name);
}
}
class Son extends Father {
public String name = "李四";
public Son() {
}
public void getName() {
System.out.println("调用的son");
System.out.println(this.name);
}
}
大家可以想想输出结果是啥
张三
张三
调用的son
null
李四
这道题其实和类的加载顺序有关系,先加载父类的属性(属性加载会先把属性初始化为基本值,比如int为0,String为null),再加载父类的构造方法,然后再是子类的属性和子类的构造方法。这里最难理解的其实是这个null,所以我特意在getName中多加了一个打印。
子类初始化的时候,先调用父类的构造方法,父类调用构造方法的时候,去调用getName方法,被调用的被重写的子类的getName方法,而这时候子类的属性赋值操作还没执行,只做了初始化操作,所以打印出来的name值就是null。
至于static的静态变量和方法,都是属于class对象本身,谁调用就是用谁的,相对来说简单一点。