最近正在复习Java相关内容,顺便做个笔记。
A a = new A();
这段简短的代码,表示一个A类型的引用a,指向一个调用A类的无参构造方法产生的新实例。
假设存在一个父类A和子类B,在使用上述格式代码创建一个引用指向B类实例的时候,调用B的无参构造方法,在子类无参构造方法首行,会使用super去调用父类的构造方法。
public class A {
public A() {
System.out.println("A类的无参构造函数被调用");
}
public A(int s){
System.out.println("A类的有参构造函数被调用"+s);
}
}
public class B extends A{
public B() {
//此处省略了super();
System.out.println("子类的构造函数被调用");
}
}
在此基础上,运行以下main函数。
public class main {
public static void main(String[] args) {
A a = new A();
System.out.println("------------");
B b = new B();
}
}
运行结果如下:
A类的无参构造函数被调用
------------
A类的无参构造函数被调用
子类的构造函数被调用
如果想要子类指定调用父类的某一构造方法,在子类的第一行增加super即可,如下:
public class B extends A{
public B() {
super(0);
System.out.println("子类的构造函数被调用");
}
}
A类的无参构造函数被调用
------------
A类的有参构造函数被调用0
子类的构造函数被调用
了解了以上的执行顺序,我们知道子类在创建一个实例的时候也会创建一个父类实例,假设父类实例占据2M空间,子类实例占据1M空间(在运行时由JVM分配),那么,使用父类引用指向子类对象,即:
A a = new B();
A类的有参构造函数被调用0
子类的构造函数被调用
此时A类型引用a指向实例B中的2M部分(1M部分无引用指向,但也进行了实例化),这就导致a实际上也只能调用父类中的方法。
public class A {
public A() {
System.out.println("A类的无参构造函数被调用");
}
public A(int s){
System.out.println("A类的有参构造函数被调用"+s);
}
public void add(int s){
System.out.println(s);
}
}
public class B extends A{
public B() {
super(0);
System.out.println("子类的构造函数被调用");
}
public void aaa(){
System.out.println("aaa");
}
}
public class main {
public static void main(String[] args) {
A a = new B();
a.aaa();
}
}
编译出错,提示找不到aaa()方法:
无法解析 'A' 中的方法 'aaa'
需要注意,特别的是:由于子类也进行了实例化,如果子类的方法中重写了父类的方法,那么JVM在编译运行的时候就会将父类的方法入口和子类重写的方法的方法体绑定到一起(动态绑定),调用父类的方法实际上就是调用了子类重写的这个方法。下面用一个例子来简单说明:
public class A {
public A() {
System.out.println("A类的无参构造函数被调用");
}
public A(int s){
System.out.println("A类的有参构造函数被调用"+s);
}
public void add(int s){
System.out.println(s);
}
public void add1(int s){
System.out.println(s+2);
}
}
public class B extends A{
public B() {
super(0);
System.out.println("子类的构造函数被调用");
}
public void add(int s){
System.out.println(s+1);
}
}
public class main {
public static void main(String[] args) {
A a = new B();
a.add(0);
a.add1(0);
}
}
结果:
A类的有参构造函数被调用0
子类的构造函数被调用
1
2
可以看到此时调用的是子类重写的的add()方法和父类未被重写的add1()方法。
而子类引用指向父类实例的时候,不难发现,子类的引用要求有3M的内存,但是父类的实例只有2M的内存,显然这是无法指向的。
在main中运行:
B b = (B) new A();
会发生如下的报错:
Exception in thread "main" java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at main.main(main.java:6)
言归正传,那如果使用父类的引用指向子类的实例,那未被指向的1M内存就浪费了吗?有没有办法调用呢?
在这种情况下只需要将父类的引用赋值给子类引用,就可以调用子类和父类中的所有方法了。
public class main {
public static void main(String[] args) {
A a = new B();
B b = (B) a;
b.aaa();
}
}
A类的有参构造函数被调用0
子类的构造函数被调用
aaa
不知道大家有没有想过,在父类引用指向子类实例的时,instanceof运算符会如何比较呢?
首先先验证父类引用指向父类实例,子类引用指向子类实例时的情况:
public class main {
public static void main(String[] args) {
A a = new A();
B b = new B();
if(a instanceof B)
System.out.println("yes");
else
System.out.println("no");
System.out.println("----------------------");
if(b instanceof A)
System.out.println("yes");
else
System.out.println("no");
}
}
A类的无参构造函数被调用
A类的有参构造函数被调用0
子类的构造函数被调用
no
----------------------
yes
因为instance运算符比较的是左边的引用指向的实例是否是右边类(接口)或者子类所创建的的实例,显然左边的引用b指向的实例是右边类A的子类B创建的的实例,故b instanceof A为true。
根据以上的结果不难推测出a instanceof B的结果,由于引用a指向的实例b是B类或其子类创建的实例,故为true。
public class main {
public static void main(String[] args) {
A a = new B();
if(a instanceof B)
System.out.println("yes");
else
System.out.println("no");
}
}
A类的有参构造函数被调用0
子类的构造函数被调用
yes
以上内容,如有错误,烦请指正。