首页 > 代码库 > Core Java 经典笔试题总结(关键字,特性问题)

Core Java 经典笔试题总结(关键字,特性问题)

2016-10-19 


说说&和&&的区别

技术分享
初级问题,但是还是加入了笔记,因为得满分不容易。

&和&&都可以用作逻辑与的运算(两边是boolean类型),全真则真,一假则假。

&&还具有短路的功能,即第一个表达式为false,则不再计算第二个表达式。例如,

if(str != null&& !str.equals(““))

当str为null时,后面的表达式不会执行,不会出现NullPointerException,如果将&&改为&,则会抛出NullPointerException异常。

&还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。备注:这道题先说两者的共同点,再说出&&和&的特殊之处,并列举一些经典的例子来表明自己理解透彻深入。
解析

final有什么用法?它的内存层面的语义你了解么?

前半部分是老生常谈的。大家都知道:final 用于声明类的属性,方法和类本身,分别表示属性本身不可变,方法不可覆盖,类不可继承。且内部类要访问局部变量,局部变量必须定义成final类型,因为常量的作用范围变大,内部类才可以访问到。

本题问的主要是后者,涉及到了Java内存模型和重排序的含义。更加详细参考下面文章

JVM学习(3)——总结Java内存模型

深入理解Java内存模型(六)——final

简单说就是构造器对一个final域的写,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序,即如果final域在构造器内初始化,那么一定是先初始化完毕,才能使用对象。不会逃逸。

初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序,读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读包含这个final域的对象的引用。

在构造器内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

下面代码的输出结果是?

技术分享

技术分享
不能执行,编译有错, final修饰的变量是常量,使用之前必须进行初始化,可以显示初始化,也可以通过构造方法或者初始化块进行初始化,如果不初始化编译会报错。
解析

请说说finalize方法的用法,为什么一些书上不推荐程序员手动调用它?

技术分享
finalize是Object类的一个保护方法

protected void finalize() throws Throwable { } 

当垃圾回收器确定不存在对该对象的更多引用时,对象的垃圾回收器自动调用此方法,程序员可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。而记得《Java编程思想》说:“如果需要进行清理,最好是编写你自己的清理方法,但不要用finalize”,其实你写了或者你用了“自己的清理方法”,只是没感觉到。比如I/O的close方法就是,现在的问题是为什么释放资源的代码不直接写在finalize里面。因为手动调用它,JVM不保证此方法总被及时调用或者根本不保证会被调用,这都不能保证,要你何用。而且它的调用对性能影响很大。具体来说就是:

java的GC只负责内存的清理,所有其它资源的清理必须由程序员手工完成。要不然会引起资源泄露,有可能导致程序崩溃。 而finalize()是Object的protected方法,那么子类理论上可以覆盖该方法,且GC会在回收对象之前调用对象的该方法。但是finalize()与C++中的析构函数不是对应的。C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性,不建议用finalize方法完成“非内存资源”的清理工作。Java语言规范并不保证finalize方法会被及时地执行、而且根本不会保证它们会被执行,finalize方法可能会带来性能问题。因为JVM通常在单独的低优先级线程中完成finalize的执行。

在Java虚拟机的垃圾回收器看来,堆区中的每个对象都可能处于以下三个状态之一。

可触及状态:当一个对象(假定为Sample对象)被创建后,只要程序中还有引用变量引用它,那么它就始终处于可触及状态。
可复活状态:当程序不再有任何引用变量引用Sample对象时,它就进入可复活状态。在这个状态中,垃圾回收器会准备释放它占用的内存,在释放之前,会调用它及其他处于可复活状态的对象的finalize()方法,这些finalize()方法有可能使Sample 对象重新转到可触及状态。
不可触及状态:当Java虚拟机执行完所有可复活对象的finalize()方法后,假如这些方法都没有使Sample对象转到可触及状态,那么Sample对象就进入不可触及状态。只有当对象处于不可触及状态时,垃圾回收器才会真正回收它占用的内存。
一个对象开始是可触及态的,当对象到GC Root没有任何引用连接相连。那么就证明这个对象目前是不可用的,这时这个对象就被判死缓了(从可触及到可复活),但是想要真正的对这个对象判死刑,这个对象还至少经历两次判决。第一次是判断是否有必要对这个对象执行finalize()方法,如果当前对象没有覆盖finalize()方法,或finalize()方法已经被调用过了(最多调用一次),那么当前对象就不会调用finalize()方法了,即对象成功的逃脱了一审判决。如果对象有必要执行finalize()方法,那么这个对象会被放入一个叫做F-Queue的队列中,这也就意味着在一审中这个对象已经被判死刑了,但是还没执行。而在JVM中,会有一个叫做Finalizer的低优先级线程去触发可复活对象的finalize()方法,finalize()方法是对象逃脱死刑的最后一次机会,如果在finalize()的过程中成功的与GC Root相连,则对象成功的逃脱死刑(进入可触及态)。否则对象就会被第二次标记(进入不可触及态),被第二次标记的对象就会被送上刑场枪毙。又想起一个问题,即使在程序里,不显式指定对象指向null,JVM也会找机会解决它。

分析说明:finalize方法至多由GC执行一次, 尽管finalize()方法是保证在回收内存空间之前执行的,但是对具体的执行时间和执行顺序是没有任何保证的。多个实例之间的finalize()执行顺序是不能提前预知的,甚至有可能它们是并行执行的。程序不应该预先假设实例执行finalize()的方法,也不应该使用finalize()方法来回收资源。
解析
可以参考文章

JVM学习(4)——全面总结Java的GC算法和回收机制

下面代码的输出结果是?

技术分享
技术分享
这个题很重要,纠正我一个误区:Java只有值传递。java中没有显式的指针。没有类似c和c++的那种地址,引用传递。对于java值传递,形参str指向了实参str指向的对象good字符串,但是change方法内部,形参str指向重新开辟一个空间存储的test ok,change执行完毕,实参str的指向对象并没有被修改。故不起作用。而ch是数组,值传递的是对象的引用,值发生了改变,打印good and gbc
重要:别混淆:Java传参方式只有一种:按值传递

Java一直是只有值传递。不幸的是,制定规则的人决定把Java里的指针的东西叫做引用,因此初学者总是被搞晕。其实这些引用也是通过值传递的。即Java中传递任何东西都是传值。如果传入方法的是基本类型的东西,你就得到此基本类型的一份拷贝。如果是传递引用,就得到引用的拷贝。 一般来说,对于基本类型的传递,我们很容易理解,而对于对象,总让人感觉是按引用传递。
解析

下面代码的输出结果是?

技术分享

1true 

技术分享

请看下面这个简单的例子,其中"c.getDeclaredMethods"的作用是?

技术分享
技术分享
取得类的所有方法的对象。
public Method[] getDeclaredMethods() //返回类或接口声明的所有方法,包括public, protected, default (package) 访问和private方法的Method对象,但不包括继承的方法。当然也包括它所实现接口的方法。

public Method[] getMethods() // 返回某个类的所有public方法,包括其继承类的公用方法,当然也包括它所实现接口的方法

Java反射

可以理解为在运行时期获取对象类型信息的操作,传统的编程方法要求程序员在编译阶段决定使用的类型,但是在反射的帮助下,编程人员可以动态获取这些信息,从而编写更加具有可移植性的代码。严格地说,反射并非java语言的特性,因为任何一种语言都可以实现反射机制,但是如果编程语言本身支持反射,那么反射的实现就会方便很多。 

获得类型类;Java中一切都是对象,我们一般所使用的对象都直接或间接继承自Object类。Object类中包含一个方法名叫getClass(final修饰),利用这个方法就可以获得一个实例的类型类。类型类指的是代表一个类型的类,因为一切皆是对象,类型也不例外,在Java使用类型类来表示一个类型。所有的类型类都是Class类的实例。例:

A a = new A();

if(a.getClass()==A.class)  

  System.out.println("equal");

else

  System.out.println("unequal");

结果就是打印出 “equal”。对象a是A的一个实例,if中使用a.getClass()返回的结果正是A的类型类。在Java中表示一个特定类型的类型类可以用“类型.class”的方式获得,因为a.getClass()获得是A的类型类,也就是A.class,因此上面的代码执行的结果就是打印出 “equal”,特别注意的是,类型类是一一对应的,父类的类型类和子类的类型类是不同的,因此,假设A是B的子类,那么如下的代码将得到 “unequal”的输出:

A a = new A();

if(a.getClass()==B.class)  

  System.out.println("equal");

else

  System.out.println("unequal");

因此,如果你知道一个实例,那么你可以通过实例的“getClass()”方法获得该对象的类型类,如果你知道一个类型,那么你可以使用“.class”的方法获得该类型的类型类。

小结:

Class cl=A.class;  // JVM将使用类A的类装载器, 将类A装入内存(前提是:类A还没有装入内存),不对类A做类的初始化工作.返回类A的Class的对象(类型类,一切都是对象)。

Class cl=对象引用.getClass(); // 返回引用运行时真正所指的对象(因为:子对象的引用可能会赋给父对象的引用变量)所属的类的Class的对象。 

Class.forName(); //  返回的是一个类,   .newInstance() 后才创建一个对象,  Class.forName()的作用是要求JVM查找并加载指定的类

 

获得类型的信息

在获得类型类之后可以调用其中的一些方法获得类型的信息,主要的方法有:

getName():String:获得该类型的全称名称。

getSuperClass():Class:获得该类型的直接父类,如果该类型没有直接父类,那么返回null。

getInterfaces():Class[]:获得该类型实现的所有接口。

isArray():boolean:判断该类型是否是数组。

isEnum():boolean:判断该类型是否是枚举类型。

isInterface():boolean:判断该类型是否是接口。

isPrimitive():boolean:判断该类型是否是基本类型,即是否是int,boolean,double等等。

isAssignableFrom(Class cls):boolean:判断这个类型是否是类型cls的父(祖先)类或父(祖先)接口。

getComponentType():Class:如果该类型是一个数组,那么返回该数组的组件类型。
解析

下面代码的输出结果是?

技术分享

技术分享
Object的getClass作用是返回运行时的类的名字,但是SuperTest和Date的getClass都没有被重写,他们都是调用Object的getClass,而Object的getClass作用是返回运行时的类的名字。那么此时这个运行时的类就是当前类,所以返回的是test.SuperTest,与Date类无关,要返回Date类的名字需要写super.getClass().getSuperclass()。还有别忘了包名!
解析

以下声明合法的是?

技术分享

技术分享
java的访问权限有public、protected、private和default的,default不能修饰变量。native是方法修饰符。定义navtive方法时,并不提供实现体,因为其实现体是用非Java语言在外面实现的。native可以和任何修饰符连用,abstract除外。因为native暗示这个方法有实现体,而abstract却显式指明了这个方法没有实现体。
解析

JAVA语言的下面几种数组复制方法中,哪个效率最高?

技术分享

技术分享
从c++/c的角度来看,数组的复制就是对某块内存的复制,直接和内存打交道的是最快的,for循环的话,for是最慢的。虽然很灵活。

看System.arraycopy()源码:

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

可以看到是native方法:native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。 可以将native方法比作Java程序同C程序的接口。效率最高。

C项写法就错了,copyOf不是System的方法,而是Arrays的静态方法,下面是源码,可以看到本质上是调用的本地方法arraycopy,那么其效率必然是比不上 arraycopy的。

       public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {

           @SuppressWarnings("unchecked")

           T[] copy = ((Object)newType == (Object)Object[].class)

               ? (T[]) new Object[newLength]

               : (T[]) Array.newInstance(newType.getComponentType(), newLength);

           System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));

           return copy;

       }

clone的话,属于Object类的保护本地方法,返回的是Object,需要强制转换。 一般用clone效率是比较差的。

protected native Object clone() throws CloneNotSupportedException;    
解析

Java的入口方法:main为什么必须是静态的?它可以被重载么?

技术分享
main()方法一定是静态的。如果main()允许是非静态的,那么在调用main方法时,JVM就得实例化它的类。在实例化时,得调用类的构造函数。必然要找该类的入口,而入口是非静态的方法,矛盾了。main()方法必须声明为静态的,这样JVM才可以调用main()方法而无需实例化它的类。如果从main()方法去掉“static”这个声明,虽然编译依然可以成功,但在运行时会导致程序失败。

对于后个问题,当然可以重载main()方法。一个Java类可以有任意数量的main()方法。为了运行java类,类的main()方法应该有例如“public static void main(String[] args)”的声明。如果对此声明做任何修改,编译也是可以成功的。但是,运行不了Java程序。会得到运行时错误,因为找不到main方法。
解析

能否声明main方法为private或protected,或者不用访问修饰符?

技术分享
不能,main方法必须public。不能定义main()方法为private和protected,也不能不用访问修饰符。这是为了能让JVM访问main()方法。如果你不定义main()方法为public,虽然编译也会成功,但你会得到运行时错误,因为找不到main方法。
解析

能否在Java中覆盖main方法?

技术分享
不能,因为main方法是静态方法,而在Java中静态方法在编译时会结合在一起,所以你在Java中不能覆盖静态方法。
解析

能否在Java中同步main方法?

技术分享
main方法可以在Java中同步,synchronized修饰符允许用于main方法的声明中,这样就可以在Java中同步main方法了。
解析

说说Object类java.lang.Object,它里面都有什么方法?

技术分享
java.lang包在使用的时候无需显示导入,编译时由编译器自动导入。提供java编成语言的程序设计的基础类.Object类是类层次结构的根,Java中所有的类从根本上都继承自这个类。Object类是Java中唯一没有父类的类。其他所有的类,包括标准容器类,比如数组,都继承Object类中的方法

补充;Object类源代码分析

package java.lang;   
public class Object {   
   /* 一个私有静态的native方法,具体是用C(C++)在DLL Dynamic Link Library 中实现的,然后通过JNI调用。*/    
    private static native void registerNatives();   
  /* 对象初始化时自动调用此方法*/  
    static {   
        registerNatives();   
    }   
   /* 返回此 Object 的运行时类。*/  
    public final native Class<?>  getClass();   
/*  hashCode 的常规协定是:  1.在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。   2.如果根据 equals() 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。3.如果根据 equals() 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。*/  
public native int hashCode();   
//Object类的equals()方法判断调用equals()方法的引用于传进来的引用是否一致,即两个引用是否指向同一个对象。Object类中的equals()方法等价于==。
//只有当继承Object的类覆写(override)equals()方法之后,继承类实现equals(),才可以说equals()方法与==的不同。
//equals()方法需要具有如下特点:
//自反性(reflexive):任何非空引用x.equals(x)返回为true。
//对称性(symmetric)任何非空引用x和y,x.equals(y)返回true当且仅当y.equals(x)返回true。
//传递性(transitive):任何非空引用x和y,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)返回true。
//一致性(consistent):两个非空引用x和y,x.equals(y)的多次调用应该保持一致的结果,(前提条件是在多次比较之间没有修改x和y用于比较的相关信息)。
//约定:对于任何非空引用x,x.equals(null)应该返回为false。并且覆写equals()方法时,应该同时覆写hashCode()方法,反之亦然。
    public boolean equals(Object obj) {   
        return (this == obj);   
    }   
/*本地clone方法,用于对象的复制。(想到了一个面试题:Java生成对象有几种方式?)任何类在实例上调用clone(),他将实现cloneable接口重写clone方法创建副本。有这个方法的类必须实现java.lang.Cloneable接口,否则会抛出CloneNotSupportedException异常。Cloneable接口中不包含任何方法,所以实现它时只要在类声明中加上implements语句即可。
第二个比较特殊的地方在于这个方法是protected修饰的,覆写clone()方法的时候需要写成public,才能让类外部的代码调用。
    protected native Object clone() throws CloneNotSupportedException;   
/*返回该对象的字符串表示。打印引用会自动调用对象的toString()方法,打印出引用所指的对象的toString()方法的返回值,因为每个类都直接或间接地继承自Object,因此每个类都有toString()方法。
    public String toString() {   
        return getClass().getName() + "@" + Integer.toHexString(hashCode());   
    }     
    public final native void notify();   
public final native void notifyAll();   
/*在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。*/  
    public final void wait() throws InterruptedException {   
        wait(0);   
    }   
/*在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。*/  
    public final native void wait(long timeout) throws InterruptedException;   
/* 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。*/  
    public final void wait(long timeout, int nanos) throws InterruptedException {   
        if (timeout < 0) {   
            throw new IllegalArgumentException("timeout value is negative");   
        }     
        if (nanos < 0 || nanos > 999999) {   
            throw new IllegalArgumentException(   
                "nanosecond timeout value out of range");   
        }   
    if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {   
        timeout++;   
    }     
    wait(timeout);   
    }   
    /*当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
    protected void finalize() throws Throwable { } 
}
解析

下面代码的输出结果是?

技术分享

技术分享
枚举类所有的枚举值都是类静态常量,在初始化时会对所有的枚举值对象进行第一次初始化。枚举类在后台实现时,实际上是转化为一个继承了java.lang.Enum类的实体类,原先的枚举类型变成对应的实体类型,上例中AccountType变成了个class AccountType,并且会生成一个新的构造函数,若原来有构造函数,则在此基础上添加两个参数,生成新的构造函数,新构造器会包含已有的构造器的代码,而且在这个类中,还会添加若干(本例子是3个字段)静态final字段来代表具体的枚举类型。最后而且还会添加一段static代码段初始化他们。在初始化过程中new AccountType构造函数被调用了三次,所以Enum中定义的构造函数中的打印代码被执行了3遍。或者简单的说private类内可见,类外不可见,枚举类有三个实例,故调用三次构造方法,打印三次It is a account type,然后main方法里打印一次FIXED。
解析

Java的instanceof运算符是干嘛的?实现原理是怎样的? 

技术分享
java 中的instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例。instanceof通过返回一个布尔值来指出这个对象是否是这个特定类或者是它的子类的一个实例。 用法:
result = object instanceof class
参数:

Result:布尔类型。

Object:必选项。任意对象表达式。

Class:必选项。任意已定义的对象类。

说明:如果 object 是 class 的一个实例,则 instanceof 运算符返回 true。如果 object 不是指定类的一个实例,或者 object 是 null,则返回 false。 

至于具体实现原理,简单说说:JVM有一条名为 instanceof 的指令,而Java源码编译到Class文件时会把Java语言中的 instanceof 运算符映射到JVM的 instanceof 指令上。
解析 

参考:Java instanceof 关键字是如何实现的? 

在使用super 和this关键字时,以下描述正确的是

技术分享 

super(), this() 都得放在构造函数第一行,肯定不能放在一起。

关键字super的作用是?

技术分享

super代表父类对应的对象,所以用super访问在子类中无法直接使用的父类成员和方法,如果在子类中对从父类继承来的成员变量进行重新定义,即出现了子类变量对父类变量的隐藏,不是说的private成员。

除去扩展名.java的部分,包含在java文件里的Java里的公共类必须与文件的名字相同么?

技术分享
public class Outter{
  public class Inner{
  }
}

内部类就不需要与文件名相同
解析

下列语句正确的是( )

 技术分享

技术分享
形式参数就是函数定义时设定的参数。例如函数头 int min(int x,int y,int z) 中 x,y,z 就是形参。

实际参数是调用函数时所使用的实际的参数。

形参可以是对象,是对象的时候传递引用.形式参数只能用final修饰符,其它任何修饰符都会引起编译器错误 。但是用这个修饰符也有一定的限制,就是在方法中不能对参数做任何修改。 不过一般情况下,一个方法的形参不用final修饰。只有在特殊情况下,那就是:方法内部类。

一个方法内的内部类如果使用了这个方法的参数或者局部变量的话,这个参数或局部变量应该是final。

形式参数可被视为local variable。形参和局部变量一样都不能离开方法。都只有在方法内才会发生作用,也只有在方法中使用,不会在方法外可见。
解析

下列说法正确的有( )

 技术分享

技术分享
a选项 
-d即可设置系统属性

c选项 
一次编译多个java文件用javac  *.java. 即可编译当前目录下的所有java文件

d选项
-s指定存放生成的源文件的位置
解析

 

Core Java 经典笔试题总结(关键字,特性问题)