1、关键字 this
this关键字有两个作用:
- 一是引用隐式参数:在类的实例方法调用时,会自动传递一个this的参数给方法(这个参数是隐式传递的)。所以在方法里可以使用this这个参数。this在方法中表示当前对象,其this关键字本身可以看成是一个引用,必要时可直接返回。
- 二是在构造器中调用该类其他的构造器:this(….)方法必须出现在构造器中的第一行,用来调用其他重载构造器。调用时参数必须严格匹配。(注:这种调用方式的优点在于一个构造器可以不必重复编写其他构造器中已有的代码,而是通过调用其他构造函数以实现复用,从而提供良好和类代码结构。)
public Class Foo {
private int id;
public Foo() {
this(1); //this使用场景2,引用其他构造器
}
public Foo(int i) {
this.id = i; //this使用场景1,作为隐式参数传递,表示当前对象。
}
public Foo getCurrent() {
return this; //要获取当前对象,用this直接返回。
}
}
需要注意的是,this在第一个作用场景下代表的是某个对象实例的引用,所以他 绝对不可以存在于静态方法、静态代码块中,否则会出现编译错误(如下图)。
究其原因,是因为静态方法是在类初次加载时就被加载到jvm方法区,它是属于这个加载类的(而不是对象);而当使用new创建对象时,才会在jvm堆上开辟内存存放一个实例,也就是this引用指向的位置。简单讲,就是你在使用静态方法时可以没有任何实例创建,如常用的 String.valueOf(int i); 所以作为一个后来才被创建的实例,其引用关键字this是无法在静态方法/静态块中存在的。
2、关键字super
引子:super关键字与this在使用上非常相似,但却有本质区别。在java类中使用super来引用父类的成分,但与this可作为引用存在不同,super只是一个指示编译器调用超类方法的特殊关键字,他本身不能被赋予另一个对象变量。
super关键字也有两个用途:
程序执行输出结果:
super关键字也有两个用途:
- 一是作为超类的关键字(非超类对象的引用),调用超类的方法或属性。
- 二是调用超类的构造器,与this使用方式类似,使用时super()只能在子构造器的第一条语句位置出现。
//定义一个父类Father
public class Father {
private int age; //年龄
private String name; //姓名
Father(int age, String name) { //定义构造器,使用this关键字初始化属性
this.age = age;
this.name = name;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
//定义一个子类Child
public class Child extends Father {
private int age; //年龄
private String name; //姓名
Child (int age) { //定义子类构造器,以年龄age作为入参
super(age + 25, "father"); //super关键字使用场景2,调用父类构造器,并传入父类的初始化参数。
this.age = age;
this.name = "child";
}
public String getName() {
return this.name;
}
public String getFatherName() { //super关键字使用场景1,访问父类方法
return super.getName();
}
public int getAge() {
return this.age;
}
public int getFatherAge() { //super关键字使用场景1,访问父类方法
return super.getAge();
}
}
public class Main {
public static void main(String[] args) {
Child child = new Child(5);
System.out.println(child.getAge());
System.out.println(child.getFatherAge());
System.out.println(child.getName());
System.out.println(child.getFatherName());
}
}
程序执行输出结果:
注意几点:
- super关键字用于父类构造器调用时,必须出现在子类构造函数的第一行,如果没有显示写明,则默认调用父类的默认无参构造器super()。
- super不是引用,不能直接赋值或返回super关键字。
- 如果我们利用继承关系,把父类的属性设为public访问权限,并删除子类的所有属性。则在new Child(5)时,子类中持有的是父类定义的属性。根据先初始化父类、后初始化子类属性的顺序,此时输出会变成【5 5 child child】。
- 如果我们把父类中访问权限改为public的同时,还在子类中也定义了public的相同属性age与name,则在new Child(5)时,子类中既持有父类属性,也持有子类同名属性。此时输出会变成【5 30 child father】。
3、类的初始化顺序
引子:在继承体系中,在new Xxx()的过程中需要初始化对象。对于每个类中定义的静态块、静态变量、非静态块、非静态变量以及构造器,它的初始化顺序如何?
public class Parent {
// 静态变量
public static String p_StaticField = "父类--静态变量";
// 非静态变量
public String p_Field = "父类--变量";
// 静态初始化块
static {
System.out.println(p_StaticField);
System.out.println("父类--静态初始化块");
}
// 非静态初始化块
{
System.out.println(p_Field);
System.out.println("父类--初始化块");
}
// 构造器
public Parent() {
System.out.println("父类--构造器");
}
}
public class Child extends Parent {
public static String s_StaticField = "子类--静态变量";
public String s_Field = "子类--变量";
static {
System.out.println(s_StaticField);
System.out.println("子类--静态初始化块");
}
{
System.out.println(s_Field);
System.out.println("子类--初始化块");
}
public Child() {
System.out.println("子类--构造器");
}
public static void main(String[] args) {
System.out.println("*************in main***************");
new Child();
}
}
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
*************in main***************
父类--变量
父类--初始化块
父类--构造器
子类--变量
子类--初始化块
子类--构造器
结论:
- 静态变量/代码块是在类加载的过程中初始化的,而不是在new Child()之后才初始化,他们依附于类的Class对象中,而非Child实例对象中。
- 父类的初始化都是在子类的初始化之前完成的。
- 构造器初始化在变量域/代码块初始化之后完成。
- 对于 静态变量 <=> 静态代码块、非静态变量 <=> 非静态代码块 来说,初始化的顺序取决于代码中的位置,也就是说如果Parent类中的 p_StaticField 写在 static{} 后面,那么程序会先进行static{} 的初始化,后定义 p_StaticField 域。那么static使用p_StaticField 域就会报错: