常见的java笔试题—类与继承(一)

  1. 下面这段代码的输出结果是什么?
public class Test {
    public static void main(String[] args)  {
        new Circle();
    }
}
 
class Draw {
     
    public Draw(String type) {
        System.out.println(type+" draw constructor");
    }
}
 
class Shape {
    private Draw draw = new Draw("shape");
     
    public Shape(){
        System.out.println("shape constructor");
    }
}
 
class Circle extends Shape {
    private Draw draw = new Draw("circle");
    public Circle() {
        System.out.println("circle constructor");
    }
}

运行结果
shape draw constructor
shape constructor
circle draw constructor
circle constructor
父类的构造器调用以及初始化过程一定在子类的前面。
由于Circle类的父类是Shape类,所以Shape类先进行初始化,然后再执行Shape类的构造器。接着才是对子类Circle进行初始化,最后执行Circle的构造器。

2.下面这段代码的输出结果是什么?

public class Test {
    public static void main(String[] args)  {
        Shape shape = new Circle();
        System.out.println(shape.name);
        shape.printType();
        shape.printName();
    }
}
 
class Shape {
    public String name = "shape";
     
    public Shape(){
        System.out.println("shape constructor");
    }
     
    public void printType() {
        System.out.println("this is shape");
    }
     
    public static void printName() {
        System.out.println("shape");
    }
}
 
class Circle extends Shape {
    public String name = "circle";
     
    public Circle() {
        System.out.println("circle constructor");
    }
     
    public void printType() {
        System.out.println("this is circle");
    }
     
    public static void printName() {
        System.out.println("circle");
    }
}

运行结果
shape constructor
circle constructor
shape
this is circle
shape
1.对于这个例子,静态绑定的过程是:java文件编译时,编译器检查出引用shape的静态类型是Shape类,由于将printName()方法和父类Father关联起来。也就是说,程序运行前编译器是无法检查出引用shape的动态类型的,所以会直接调用静态类型中对应的方法。
2.覆盖只针对非静态方法,而隐藏是针对成员变量和静态方法的。
3.覆盖和隐藏之间的区别是:覆盖受RTTI约束的,而隐藏却不受该约束。也就是说只有覆盖方法才会进行动态绑定,而隐藏是不会发生动态绑定的。

静态类型和动态类型:

任何一个引用变量都有两个类型:一个叫静态类型,也就是定义该引用变量的类型;另一个叫动态类型,也就是该引用实际指向的对象类型。

比如对于两个类A和类B,有:A a=new B();

那么,引用a的静态类型就是A,动态类型就是B。

静态绑定

:所有依赖于静态类型来将某方法和该方法所在的类关联起来的动作都是静态绑定。因为静态绑定在程序运行前发生,所有又叫前期绑定。

private:不能被继承,则不能通过子类对象调用,而只能通过类本身的对象进行调用,所以可以说private方法和方法所属的类绑定;

final:final方法虽然可以被继承,但是不能被重写(覆盖),虽然子类对象可以调用,但是调用的都是父类中的final方法(因此可以看出当类中的方法声明为final的时候,一是为了防止方法被覆盖,而是为了有效关闭java的动态绑定);

static:static方法可以被子类继承,但是不能被子类重写(覆盖),但是可以被子类隐藏。(这里意思是说如果父类里有一个static方法,它的子类里如果没有对应的方法,那么当子类对象调用这个方法时就会使用父类中的方法。而如果子类中定义了相同的方法,则会调用子类的中定义的方法。唯一的不同就是,当子类对象上转型为父类对象时,不论子类中有没有定义这个静态方法,该对象都会使用父类中的静态方法。因此这里说静态方法可以被隐藏而不能被覆盖。这与子类隐藏父类中的成员变量是一样的。隐藏和覆盖的区别在于,子类对象转换成父类对象后,能够访问父类被隐藏的变量和方法,而不能访问父类被覆盖的方法)。

动态绑定

:所有依赖于动态类型来将某方法和该方法所在的类关联起来的动作都是动态绑定。因为动态绑定是在程序运行时,通过RTTI实现,所以又叫后期绑定。

调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定。动态绑定的过程分为以下几个环节:

(1)编译器查看对象的声明类型和方法名;

(2)编译器查看调用方法时提供的参数类型。例如x.f(“hello”),编译器将会挑选f(String),而不是f(int),由于存在类型转换(int转换为double),所以可能会更复杂。如果编译器没找到参数类型匹配的方法,或者发现有多个方法与之匹配,就会报告一个错误。

至此,编译器获得了需要调用的方法名字和参数类型。

(3)采用动态绑定调用方法的时候,一定调用与所引用对象的实际类型最合适的类的方法。如果x的实际类型是D,它是C类的子类,如果D定义了一个方法f(String),就直接调用它,否则将在D类的超类中寻找f(String),以此类推。

  • 例1:
public class Test {
    public static void main(String[] args) {
        Dog woofer = new Dog();
        Dog nipper = new Basenji();
        Basenji child = new Basenji();
        woofer.bark();
        woofer.aa();
        System.out.println();
        nipper.bark();
        nipper.aa();
        System.out.println();
        child.bark();
        child.aa();
    }
}

class Dog {
    public static void bark() {
        System.out.println("woof");
    }

    public void aa() {
        System.out.println("woofaa");
    }
}

class Basenji extends Dog {
    public static void bark() {
        System.out.println("Basenji");
    }

    public void aa() {
        System.out.println("Basenjiaa");
    }
}

运行结果
woof
woofaa
woof
Basenjiaa
Basenji
Basenjiaa

  • 例2:
public class Test { 
	public static void main(String[] args) {
		 Parent child=new Son(); 
		 child.staticMethod();
		 Son s=new Son(); 
		 s.staticMethod();
		 }
class Parent {
	 public static void staticMethod(){
		 System.out.println("Parent staticMethod run");
	 }
} 
class Son extends Parent {
  	 public static void staticMethod(){
		 System.out.println("son");
 }

运行结果
Parent staticMethod run
Parent staticMethod run
子类不会继承父类的static方法,但是可以访问。
子类不会继承父类修饰的static变量,但是可以访问。
子类和父类中同名的static变量和方法都是相互独立的,并不存在任何的重写的关系。

3.下面这段代码的输出结果是什么?

class Fu {
    public int num = 10;
    public Fu() {
        System.out.println("fu");
    }
}

class Zi extends Fu {
    public int num = 20;
    public Zi() {
        System.out.println("zi");
    }
    public void show() {
        int num = 30;
        System.out.println(num); //30
        System.out.println(this.num); //20
        System.out.println(super.num); //10
    }
}
class ExtendsTest {
    public static void main(String[] args) {
        Zi z = new Zi();
        z.show();
    }
}

运行结果:
fu
zi
30
20
10
A:访问成员变量的原则:就近原则。
B:this和super的问题:
this 访问本类的成员
super 访问父类的成员
C:子类的所有构造方法执行前默认先执行父类的无参构造方法。
D:一个类的初始化过程:
成员变量进行初始化过程如下:
默认初始化
显示初始化
构造方法初始化

4.下面这段代码的输出结果是什么?

class Fu {
    static {
        System.out.println("静态代码块Fu");
    }

    {
        System.out.println("构造代码块Fu");
    }

    public Fu() {
        System.out.println("构造方法Fu");
    }
}

class Zi extends Fu {
    static {
        System.out.println("静态代码块Zi");
    }

    {
        System.out.println("构造代码块Zi");
    }

    public Zi() {
        System.out.println("构造方法Zi");
    }
}

class ExtendsTest2 {
    public static void main(String[] args) {
        Zi z = new Zi();
    }
}

输出结果是:
  静态代码块Fu
  静态代码块Zi
  构造代码块Fu
  构造方法Fu
  构造代码块Zi
  构造方法Zi
A:一个类的静态代码块,构造代码块,构造方法的执行流程:
静态代码块 > 构造代码块 > 构造方法
B:静态的内容是随着类的加载而加载,
即:静态代码块的内容会优先执行。
C:构造代码块
在类中方法外出现(即在类中的成员位置),可以把多个构造方法方法中相同的代码存放到一起,用于对对象进行初始化,
每次调用构造方法都执行,并且在构造方法前执行。
D:子类的所有的构造方法默认都会去访问父类的无参构造方法。

5.下面这段代码的输出结果是什么?

class X {
    //成员变量(引用类型)
    Y b = new Y();
    //无参构造方法
    X() {
        System.out.print("X");
    }
}

class Y {
    //无参构造方法
    Y() {
        System.out.print("Y");
    }
}

public class Z extends X {
    //成员变量(引用类型)
    Y y = new Y();
    //无参构造方法
    Z() {
        //super(); //它仅仅表示要先初始化父类数据,再初始化子类数据。
        System.out.print("Z");
    }
    public static void main(String[] args) {
        new Z(); 
    }
}

结果:
YXYZ
A:成员变量的问题
int x = 10; //成员变量x是基本类型
Student s = new Student(); //成员变量s是引用类型
B:一个类的初始化过程
先进行成员变量的初始化:
默认初始化
显示初始化
构造方法初始化
C:子父类的初始化(分层初始化)
先进行父类初始化,然后进行子类初始化。

你可能感兴趣的:(java)