多线程进阶之并发工具类:CountDownLatch、CyclicBarrier

时间:2024-04-08 23:07:44

在Java中,类不能多继承。啥意思呢?就是说一个类不能既继承A,又继承B,它只能继承一个类,否则在编译时会报错 Class cannot extend multiple classes。

子类继承父类时,会继承父类的成员变量和方法,但是不是所有的成员变量和方法都会继承,这里也常常是笔试题考点。

会继承哪些成员变量呢?

首先这个成员变量必须是非静态的,其次再看访问修饰符,public、protected的会继承,缺省的包访问修饰符修饰的话,子类如果和父类在同一个包下,也会继承。其他都不继承。

如果在子类中没有定义同名的非静态变量,则在子类中可以用this.变量名或者super.变量名获取变量的值,this可以省略。如果在子类中重新定义了同名的非静态变量,则在子类中想获取父类的变量值只能用super.变量名,想获取子类的变量值可以用this.变量名,this也可以省略。注意,子类重新定义同名非静态变量时,变量类型可以为任意类型。

另外一种情况,如果把子类实例赋值给父类变量,则父类变量的成员变量值是父类定义的那个,还是子类定义的那个?如下示例:

    public static void main(String[] args) {
MySuper m = new MySub();
System.out.println(m.a);
System.out.println(m.b);
} static class MySub extends MySuper {
private boolean a = true;
private boolean d = true;
} static class MySuper {
public int a = 0;
protected int b = 1;
public static int d = 3;
}

直接打点获取变量值,是看编译类型的,不看运行时类型。这是和方法调用很大区别的一个地方。

上面会打印

0

1

会继承哪些方法呢?

首先,构造器不会继承,静态方法不会继承,其次再看访问修饰符,public、protected的会继承,缺省的包访问修饰符修饰的话,子类如果和父类在同一个包下,也会继承。其他都不继承。

继承来的方法我们要是自定义实现的话,这就是所谓的重写了。

非静态方法的调用是看调用者的运行时类型的,如果是静态方法的话,则看调用者的编译时类型,因为静态方法不会被继承。

如下示例:

    public static void main(String[] args) {
MySuper m = new MySub();
m.hh();
m.sp();
} static class MySub extends MySuper {
public MySub() {
} @Override
public void hh() {
System.out.println("sub" + 1);
} public static void sp() {
System.out.println(1);
} } static class MySuper { public MySuper() {
} public void hh() {
System.out.println(1);
} public static void sp() {
System.out.println(2);
}
}

以上,hh()是非静态方法,且是public的,MySub可从MySuper继承。MySub又重写了hh()方法。在调用时看调用者的运行时类型,发现其实是MySub类型,就会去执行MySub的hh()方法。

sp()是静态方法,不可被继承。在调用时,看调用者的编译时类型,发现是MySuper类型,就执行MySuper的sp()方法。

下面再讲一下构造器。

构造器是不可被继承的,构造器是不可被继承的,构造器是不可被继承的。重要的事情说三遍。

调用子类构造器时,会先调用父类构造器。什么意思呢?调用父类的哪个构造器呢?

如果子类构造器的实现代码中,第一行用super(参数列表)指定了父类的构造器,则会先调用这个指定的构造器,然后再执行子类构造器中后面的代码。如果没有在第一行用super显式指定构造器,则会调用父类的无参构造器,之后再执行子类构造器中的代码。

    public static void main(String[] args) {
MySuper m1 = new MySub();
MySuper m2 = new MySub(2);
} static class MySub extends MySuper { public MySub() {
System.out.println("MySub 无参");
} public MySub(int a) {
super(a);
System.out.println("MySub 有参");
} } static class MySuper { public MySuper() {
System.out.println("MySuper 无参");
} public MySuper(int a) {
System.out.println("MySuper 有参, 参数值是= " + a);
}
}

再多说一点,类的静态成员变量初始化、静态代码块都是在类加载时执行的,且只会执行一次。类只有在用到的时候才会加载。如果有父类,则会先初始化父类的静态成员变量、执行静态代码块,再初始化子类的静态成员变量、执行静态代码块。注意,这时候,子类、父类的非静态成员变量还没有初始化、非静态代码块还没有执行。类的静态成员变量初始化和静态代码块的执行顺序与代码中的顺序一样。

调用子类的构造器时,会先初始化父类的非静态成员变量、执行父类的非静态代码块,再执行父类的构造器,之后才开始初始化子类的非静态成员变量、执行子类的非静态代码块,最后再执行子类的构造器。类的非静态成员变量初始化和非静态代码块的执行顺序与代码中的顺序一样。

面试的时候碰见过一种极端情况,就是在父类的构造器中调用一个非静态方法,打印一个成员变量的值,这个非静态方法又被子类重写了。子类覆盖了父类的这个成员变量。看打印出什么。

public class Test {
public static void main(String[] args) {
Base obj = new Sub();
System.out.println(obj.x);
System.out.println(obj.getX());
} public static class Base {
int x = 1; public Base() {
this.echo();
this.x = 2;
} public void echo() {
System.out.println("Base.x=" + this.x);
} public int getX() {
return this.x;
}
} public static class Sub extends Base {
int x = 3; public Sub() {this.x = 4;
this.echo();
this.x = 4;
} @Override
public void echo() {
System.out.println("Sub.x=" + this.x);
} @Override
public int getX() {
return this.x;
}
}
}

案例解析:

执行new Sub(),由于第一行不是用super来显式调用父类构造器,所以是隐式地调用父类的无参构造器。Base的无参构造器中调用了this.echo()方法,这个方法又被Sub重写了,所以最终调用的是Sub的echo()方法。