本文是我将网上有关类继承方面的笔试题加以消化整理,希望通过此种方式来巩固自己的基础知识,同时也希望这些笔试题对其它朋友有所帮助,文中内容如若涉及到他人的版权或是隐私请通知我,我会在第一时间对文章内容进行处理操作,最后也希望看到此文的朋友对文中的内容给予批评指正,风尘在此先行谢过!Ok,现在进入正题:
/类继承初始化方法调用相关笔试题/
笔试题1:
class X
{
Y y = new Y();
X()
{
System.out.print("X");
}
}
class Y
{
Y()
{
System.out.print("Y");
}
}
public class Z extends X
{
Z()
{
System.out.print("Z");
}
Y y=new Y();
public static void main(String[] args)
{
new Z();
}
}
请指出输出结果?
结果:YXYZ
分析:要想准确的知道打印结果,首先要弄清java类继承初始化顺序
父类--静态变量
父类--静态初始化块
子类--静态变量
子类--静态初始化块
父类--变量
父类--初始化块
父类--构造器
子类--变量
子类--初始化块
子类--构造器
现在,结果已经不言自明了,在上题中new Z()时先会去初始化父类中相关变量 Y y = new Y(),此时会打印Y,变量初始化完后初始化父类的构造函数,此时打印X,父类构造函数初始化完后,初始化自己的变量Y y= new Y(),此时会输出Y,然后初始化自己的构造构造输出Z。于是上题的最终打印结果为:YXYZ.
上面是对继承时初始化过程,下面再看一个没有继承时(这里说的是显示继承,和超类Object无关 呵呵)类初始化过程例子
笔试题2:
public class InitialOrderTest {
// 静态变量
public static String staticField = "静态变量";
// 变量
public String field = "变量";
// 静态初始化块
static {
System.out.println(staticField);
System.out.println("静态初始化块");
}
// 初始化块
{
System.out.println(field);
System.out.println("初始化块");
}
// 构造器
public InitialOrderTest() {
System.out.println("构造器");
}
public static void main(String[] args) {
new InitialOrderTest();
}
}
输出结果:
静态变量
静态初始化块
变量
初始化块
构造器
可以发现和上面的说的初始化顺序一致:对于静态变量、静态初始化块、变量、初始化块、构造器,它们的初始化顺序以此是(静态变量、静态初始化块)>(变量、初始化块)>构造器。
那么对于静态变量和静态初始化块之间、变量和初始化块之间的先后顺序又是怎样呢?是否静态变量总是先于静态初始化块,变量总是先于初始化块就被初始化了呢?实际上这取决于它们在类中出现的先后顺序。我们以静态变量和静态初始化块为例来进行说明
笔试题3:
public class TestOrder {
// 静态变量
public static TestA a = new TestA();
// 静态初始化块
static {
System.out.println("静态初始化块");
}
// 静态变量
public static TestB b = new TestB();
public static void main(String[] args) {
new TestOrder();
}
}
class TestA {
public TestA() {
System.out.println("Test--A");
}
}
class TestB {
public TestB() {
System.out.println("Test--B");
}
}
输出结果为:
Test--A
静态初始化块
Test--B
大家可以随意改变变量a、变量b以及静态初始化块的前后位置,就会发现输出结果随着它们在类中出现的前后顺序而改变,这就说明静态变量和静态初始化块是依照他们在类中的定义顺序进行初始化的。同样,变量和初始化块也遵循这个规律。了解了继承情况下类的初始化顺序之后,如何判断最终输出结果就迎刃而解了。
我们写一个简单的例子打印来看一下:
public class TestStatic {
public TestStatic() {
System.out.println("构造函数没有改变前 age = "+age);
age = 0;
System.out.println("构造方法中改变后 age = " + age);
}
private static int age = 10;
{
age = 12;
System.out.println("代码块 age =" +age);
}
static {
System.out.println("第一个静态代码块");
System.out.println("age = " + age);
}
static{
age = 11;
System.out.println("第二个静态代码块");
System.out.println("age = " + age);
}
public static void main(String[] args) {
new TestStatic();
}
}
按上面的规律很简单的得到打印结果如下:
第一个静态代码块
age = 10
第二个静态代码块
age = 11
代码块 age =12
构造函数没有改变前 age = 12
构造方法中改变后 age = 0
笔试题4:
public class A
{
public static void print()
{
System.out.println(">>>>>>>>>>>>A");
}
public void f()
{
System.out.println(">>>>>>>>>>>>f");
}
}
public class B extends A
{
public static void print()
{
System.out.println(">>>>>>>>>>>>B");
}
public void f()
{
System.out.println(">>>>>>>>>>>>ff");
}
public static void main(String[] args) {
B b = new B();
b.print();
A a = new B();
a.print();
}
}
输出结果:
>>>>>>>>>>>>B
>>>>>>>>>>>>A
分析:
1.首先声明一点: Static修饰的变量/函数优行于该类对象存在; 随着类的加载而加载,而这时还不需要对象; 这也是静态代码可用类名. 出来的原来;
2.理解以上就可推断出很多;eg:
2.1 假设有B b=new B();
则这时因为继承的关系,会一直调用到A的构造方法。这样就会把A,B中两个print()都加入进方法区, 但A的print()方法在A对应的区域,B的print方法在B中对应的区域;此时我们用b,print()调用的是B中的print();
2.2 假设有A a=new B();
此时因为编译器在编译时认为a的类型是A,所以调用的是A中的print();
//这也是父类对象不能调用子类对象的特有方法的原因.。
而在运行时, jvm会发现a的真实类型是B,所以a可向下转型为B类型;这时就可以调用子类特有方法
//这也是为什么可以向下转型,
但对于静态成员,不管在编译时还是运行时,父类对象都不能调用子类特有的静态方法;
而子类可以继承父类非private的static成员,子类也有自己特有静态方法,但这个名字和父类一样.也就是说其实子类同时有两个静态方法;但这两个方法不在同一区域内,所以不存在同名的冲突。只是子类的静态方法屏蔽了父类的静态方法;你可以把静态方法看成子类对象特有的方法。
其实B对象中也可访问A类中的print(),修改代码即可;
//类继承访问权限笔试题///
笔试题1:
public class Main{
protected String toString(){ // 1
return super.toSring(); //2
}
}
问:哪一行可能出错?编译或运行时。
分析:回答这个问题前先要知道java的访问权限修饰符
Java语言中的访问权限修饰符有4种,但是仅有3个关键字,因为不写访问权限,在Java中被称为默认权限,或同包权限,本文中以(default)代替。下面按照权限从小到大的顺序对4中访问权限分别介绍。
1.私有权限(private)
private可以修饰数据成员,构造方法,方法成员,不能修饰类(此处指外部类,不考虑内部类)。被private修饰的成员,只能在定义它们的类中使用,在其他类中不能调用。
2.默认权限(default)
类,数据成员,构造方法,方法成员,都能够使用默认权限,即不写任何关键字。默认权限即同包权限,同包权限的元素只能在定义它们的类中,以及同包的类中被调用。
3.受保护权限(protected)
protected可以修饰数据成员,构造方法,方法成员,不能修饰类(此处指外部类,不考虑内部类)。被protected修饰的成员,能在定义它们的类中,同包的类中被调用。如果有不同包的类想调用它们,那么这个类必须是定义它们的类的子类。
4.公共权限(public)
public可以修饰类,数据成员,构造方法,方法成员。被public修饰的成员,可以在任何一个类中被调用,不管同包或不同包,是权限最大的一个修饰符。
在类继承时方法重写时,并且重写方法的访问权限不能比被重写方法的权限更严格。
因此在本题中标号所在的行会出错,因为重写了超类Object的toString()方法,而在Object中toString方法是public修饰的,在子类重写时用了protected缩小了访问权限因此会报错。
笔试题2:
以下代码运行输出是(C)
public class Person{
private String name=”Person”;
int age=0;
}
public class Child extends Person{
public String grade;
public static void main(String[] args){
Person p = new Child();
System.out.println(p.name);
}
}
A) 输出:Person
B) 没有输出
C) 编译出错
D) 运行出错
选c.
分析:name属性是父类私有的,所以在子类不能继承父类的私有变量。因些会编译时出错。
笔试题3:
public class Father {
protected int value = 3;
public String string = "father";
public int getValue(){
return this.value;
}
public String getString() {
return string;
}
}
public class Son extends Father {
protected int value = 1233;
protected String string = "son";
protected String getString() { //这里会出错
return string;
}
public static void main(String[] args) {
System.out.println(new Son().getValue());
System.out.println(new Son().getString());
}
}
该题重写父类的getString()方法时将访问权限减低了,所以会出错,子类继承父类方法时访问权限必须大于等于父类的访问权限。若将Son中的getString()方法的访问权限设为public那么输出结果是多少?
打印结果如下:
3
son
getValue()方法没有重写父类,所以会调用父类的getValue()方法,因此不管父类的value是private还是public都将调用父类的getValue()方法返回父类中定义的变量值
在理解了继承时访问权限的问题后下面的题很容易得出正确选项D
以下(D)添加到ComputerBook中不会出错
class Book{
protected int getPrice(){
return 30;
}
}
public class ComputerBook extends Book{
}
A) protected float getPrice(){}
B) protected int getPrice(int page){}
C) int getPrice(){}
D) public int getPrice(){return 10;}
由上面的几个实例我们引出java中绑定的概念:绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。对java来说,绑定分为静态绑定和动态绑定;或者叫做前期绑定和后期绑定
一、动态绑定的过程:
仍用前面的Son继承Father例子,在其main方法中:
public static void main(String[] args) { Fathor son = new Son(); //向上转型 System.out.println(son.getValue()); System.out.println(son.getString()); }
1. 首先,编译器根据对象的声明类型和方法名,搜索相应类(Son)及其父类(Father)的“方法表”,找出所有访问属性为public的getValue方法。可能存在多个方法名为getValue的方法,只是参数类型或数量不同。
2. 然后,根据方法的“签名”找出完全匹配的方法。
方法的名称和参数列表称为方法的签名。
在Java SE 5.0 以前的版本中,覆盖父类的方法时,要求返回类型必须是一样的。现在子类覆盖父类的方法时,允许其返回类型定义为原始类型的子类型,如
public Father getFather(){...} //父类中的方法 public Son getFather(){...} //子类覆盖父类中的getFather()方法
3. 如果是private static final 的方法或者是构造器,则编译器明确地知道要调用哪儿个方法,这种调用方式成为”静态调用"。
4. 调用方法。
如果子类Son中定义了 getValue() 的方法,则直接调用子类中的相应方法;如果子类Son中没有定义相应的方法,则到其父类中寻找getValue()方法。
动态绑定只是针对对象的方法,对于属性无效。因为属性不能被重写,在处理java类中的成员变量时,并不是采用运行时绑定,而是一般意义上的静态绑定。所以在向上转型的情况下,对象的方法可以找到子类,而对象的属性还是父类的属性。因此上面的调用getValue()方法后返回值是父类中定义的变量而不是子类中定义的同名变量。
声明的是父类的引用,但是执行的过程中调用的是子类的对象,程序首先寻找子类对象的getValue方法,但是没有找到,于是向上转型去父类寻找.由于子类重写了父类的method方法,根据上面的理论知道会去调用子类getString方法去执行,因为子类对象有getString方法而没有向上转型去寻找。
在java中,几乎所有的方法都是后期绑定的,在运行时动态绑定方法属于子类还是基类。但是也有特殊,针对static方法和final方法由于不能被继承,因此在编译时就可以确定他们的值,他们是属于前期绑定的。特别说明的一点是,private声明的方法和成员变量不能被子类继承,所有的private方法都被隐式的指定为final的(由此我们也可以知道:将方法声明为final类型的一是为了防止方法被覆盖,二是为了有效的关闭java中的动态绑定)。
分析到这里,我想再把前面的父类和子类中同义的static类型的同名方法与这里子类重写父类的方法做下对比。请看下面的实例:
public class Father {
private int value = 3;
public String string = "father";
public int getValue(){
return this.value;
}
public String getString() {
return string;
}
public static void print(){
System.out.println("i am father!");
}
}
public class Son extends Father {
protected int value = 1233;
protected String string = "son";
public String getString() {
return string;
}
public static void print(){
System.out.println("i am son!");
}
public static void main(String[] args) {
Father son = new Son();
System.out.println(son.getValue());
System.out.println(son.getString());
//print();
System.out.print("父类引用调用print方法 ") ;
son.print();
Son son2 = new Son();
System.out.print("子类引用调用print方法");
son2.print();
}
}
打印结果:
3son
父类引用调用print方法 i am father!
子类引用调用print方法i am son!
从结果中看到对于父类与子类的静态同名方法,调用时与方法所属的引用有关。从上面分析我们也知道静态方法是不能被重写(因为静态方法是类在加载时就被加载到内存中的方法,在整个运行过程中保持不变,因而不能重写。但非静态方法是在对象实例化时才单独申请内存空间,为每一个实例分配独立的运行内存,因而可以重写),换言之即使父类与子类名字相同也不叫重写,从结果中我们也可以看到子类与父类有static的同名方法时引用若是父类的话仍调用的仍是父类方法。
笔试题4:关于接口继承
interface Playable {
void play();
}
interface Bounceable {
void play();
}
interface Rollableextends Playable, Bounceable {
Ball ball = new Ball("PingPang");
}
class Ballimplements Rollable {
private String name;
public String getName() {
return name;
}
public Ball(String name) {
this.name = name;
}
publicvoid play() {
ball = new Ball("Football");
System.out.println(ball.getName());
}
}
指出上面的程序错误的地方,说明原因。
答案:
会ball =new Ball("Football");这行出错,问题出在interface Rollable里的"Ball ball = new Ball("PingPang");"。任何在interface里声明的interface variable (接口变量,也可称成员变量),默认为public static final。也就是说"Ball ball = new Ball("PingPang")";实际上是"public static final Ball ball = new Ball("PingPang");"。在Ball类的Play()方法中,"ball = new Ball("Football");"
改变了ball的reference,而这里的ball来自Rollable interface,Rollable interface里的ball是public static final的,final的object是不能被改变reference的。因此编译器将在"ball = new Ball("Football");"这里显示有错。关于接口必须知道以下几点:
接口没有提供构造方法
A) 接口没有提供构造方法,抽象类中可以有构造方法
B) 接口中的方法默认使用public、abstract修饰,因此不要企图使用proteced或是private修饰符去修饰方法,同时由于接口中的方法都是abstract的所以方法不能有具体实现,即方法后不能有{},抽象类中可以有实现过的方法。
C) 接口中的属性默认使用public、static、final修饰,因此不要企图使用proteced或是private修饰符去修饰属性,同时由于接口中的属性是public static final的那么定义属性时必须初始化,否则会报错。
D) 接口可以多继承
类继承相关概念笔试题 ///
笔试题1:如果一个类继承了一个父类,父类可以有无参构造函数对吗?
答案:Java 中如果有继承存在,父类一般有无参构造方法 ,若父类没有无参构造方法则子类的所有构造方法中使用super( 参数列表 );显示的调用父类的有参构造方法 而且super必须是第一条可执行语句,否则子类在继承父类时就会出错。
如下面的定义继承关系:
public class Parent {
}
public class Sub extends Parent {
}
上面定义的父类默认有无参构造方法,子类继承时无需显示指定构造方法。而如果父类定义如下(没有无参构造方法)
public class Parent {
public Parent(String name) {
// TODO Auto-generated constructor stub
}
}
此时子类若仍安上面的定义会报错,eclipse提示以下信息:
Implicit super constructor Parent() is undefined for default constructor. Must define an explicit constructo
public class Sub extends Parent {
}
上面的子类实现会报错,需要更改成如下代码:
public class Sub extends Parent {
public Sub(String name) {
super(name); //显示调用父类的有参构造方法
// TODO Auto-generated constructor stub
}
}
构造方法不能继承:子类继承父类所有的非私有的成员变量和非私有的成员方法,但不继承父类的构造方法。 可以在构造方法的第一行使用this关键字调用本类的其它(重载的)构造方法
调用父类构造方法:子类的构造方法必须调用父类的构造方法!这也就解释了上面类继承程序题中类继承初始化顺序。子类初始时同级别的(静态变量,静态代码块,变量,代码块,构造方法)先要调用父类的,如果子类的构造方法中没有显示地调用父类构造方法,也没有使用this关键字调用重载的其它构造方法,则系统默认调用父类无参数的构造方法。如下:
public class Parent {
private int a = 0;
int b = 0;
public Parent() {
System.out.println("我是父类的无参构造方法");
}
public Parent(String name){
System.out.println("我是父类的有参构造方法");
}
}
class Sub extends Parent {
public Sub(){//没有显示调用父类的构造方法,则会调用父类的无参构造方法
}
public Sub(String name){//没有显示调用父类的构造方法,则会调用父类的无参构造方法
}
public Sub(String name,int age){//调用了父类的有参构造方法
super(name);
}
public static void main(String[] args) {
Sub sub = new Sub();
sub = new Sub("");
}
}
运行结果:
我是父类的无参构造方法
我是父类的无参构造方法
我是父类的有参构造方法
曾看到一道面试题是this,和super能否出现在同一个构造函数当中,这个题我认为要分开来讲,若this是去调用本类的构造方法,super是调用父类的构造方法的话刚二者不可以在同一个构造方法中出现,因为当this和super调用构造方法时都必须放在构造方法代码第一行,倘若不是调用构造方法刚可以出现在同一个构造方法当中。
笔试题2:
class Person{
public Person(){
System.out.println(“this is a Person”);
}
}
public class Teacher extends Person{
private String name=”tom”;
public Teacher(){
System.out.println(“this is a teacher”);
super();
}
public static void main(String[] args){
Teacher teacher = new Teacher();
System.out.println(this.name);
}
}
A) this is a Person
this is a teacher
tom
B) this is a teacher
this is a Person
tom
C) 运行出错
D) 编译有两处错误
选择D项
分析:一处是子类用super调用父类的构造方法时必须将super放在方法代码第一行,this.name行出错,因为在this是指当前对象,而static修饰的变量或方法优先于对象而存在。