最有价值的50道java面试题(二)

时间:2022-04-26
本文章向大家介绍最有价值的50道java面试题(二),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

来自骆昊的技术专栏

26、抽象的(abstract)方法是否可同时是静态的(static),是否可同时是本地方法(native),是否可同时被synchronized修饰?

答:都不能。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。

27、静态变量和实例变量的区别?

答:静态变量是被static修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝;实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。在Java开发中,上下文类和工具类中通常会有大量的静态成员。

28、是否可以从一个静态(static)方法内部发出对非静态(non-static)方法的调用?

答:不可以,静态方法只能访问静态成员,因为非静态方法的调用要先创建对象,因此在调用静态方法时可能对象并没有被初始化。

29、如何实现对象克隆?

答:有两种方式:

1.实现Cloneable接口并重写Object类中的clone()方法;

2.实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,代码如下。

package com.lovo;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class MyUtil {
 private MyUtil() {
 throw new AssertionError();
 }
 public static <T> T clone(T obj) throws Exception {
 ByteArrayOutputStream bout = new ByteArrayOutputStream();
 ObjectOutputStream oos = new ObjectOutputStream(bout);
 oos.writeObject(obj);
 ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
 ObjectInputStream ois = new ObjectInputStream(bin);
 return (T) ois.readObject();
 // 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
 // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源
 }
}

下面是测试代码:

package com.lovo;
import java.io.Serializable;
/**
 * 人类
 * @author 骆昊
 *
 */
class Person implements Serializable {
 private static final long serialVersionUID = -9102017020286042305L;
 private String name; // 姓名
 private int age; // 年龄
 private Car car; // 座驾
 public Person(String name, int age, Car car) {
 this.name = name;
 this.age = age;
 this.car = car;
 }
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
 public int getAge() {
 return age;
 }
 public void setAge(int age) {
 this.age = age;
 }
 public Car getCar() {
 return car;
 }
 public void setCar(Car car) {
 this.car = car;
 }
 @Override
 public String toString() {
 return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
 }
}
/**
 * 小汽车类
 * @author 骆昊
 *
 */
class Car implements Serializable {
 private static final long serialVersionUID = -5713945027627603702L;
 private String brand; // 品牌
 private int maxSpeed; // 最高时速
 public Car(String brand, int maxSpeed) {
 this.brand = brand;
 this.maxSpeed = maxSpeed;
 }
 public String getBrand() {
 return brand;
 }
 public void setBrand(String brand) {
 this.brand = brand;
 }
 public int getMaxSpeed() {
 return maxSpeed;
 }
 public void setMaxSpeed(int maxSpeed) {
 this.maxSpeed = maxSpeed;
 }
 @Override
 public String toString() {
 return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
 }
}
class CloneTest {
 public static void main(String[] args) {
 try {
 Person p1 = new Person("Hao LUO", 33, new Car("Benz", 300));
 Person p2 = MyUtil.clone(p1); // 深度克隆
 p2.getCar().setBrand("BYD");
 // 修改克隆的Person对象p2关联的汽车对象的品牌属性
 // 原来的Person对象p1关联的汽车不会受到任何影响
 // 因为在克隆Person对象时其关联的汽车对象也被克隆了
 System.out.println(p1);
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
}

注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。

30、GC 是什么?为什么要有GC?

答:GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显示的垃圾回收调用。

垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得iOS的系统比Android系统有更好的用户体验,其中一个深层次的原因就在于Android系统中垃圾回收的不可预知性。

补充:垃圾回收机制有很多种,包括:分代复制垃圾回收、标记垃圾回收、增量垃圾回收等方式。标准的Java进程既有栈又有堆。栈保存了原始型局部变量,堆保存了要创建的对象。Java平台对堆内存回收和再利用的基本算法被称为标记和清除,但是Java对其进行了改进,采用“分代式垃圾收集”。这种方法会跟Java对象的生命周期将堆内存划分为不同的区域,在垃圾收集过程中,可能会将对象移动到不同区域:

  • 伊甸园(Eden):这是对象最初诞生的区域,并且对大多数对象来说,这里是它们唯一存在过的区域。
  • 幸存者乐园(Survivor):从伊甸园幸存下来的对象会被挪到这里。
  • 终身颐养园(Tenured):这是足够老的幸存对象的归宿。年轻代收集(Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进终身颐养园时,就会触发一次完全收集(Major-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。

与垃圾回收相关的JVM参数:

  • -Xms / -Xmx --- 堆的初始大小 / 堆的最大大小
  • -Xmn --- 堆中年轻代的大小
  • -XX:-DisableExplicitGC --- 让System.gc()不产生任何作用
  • -XX:+PrintGCDetail --- 打印GC的细节
  • -XX:+PrintGCDateStamps --- 打印GC操作的时间戳

31、String s=new String(“xyz”);创建了几个字符串对象?

答:两个对象,一个是静态存储区的"xyz",一个是用new创建在堆上的对象。

32、接口是否可继承(extends)接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承具体类(concrete class)?

答:接口可以继承接口。抽象类可以实现(implements)接口,抽象类可继承具体类,但前提是具体类必须有明确的构造函数。

33、一个“.java”源文件中是否可以包含多个类(不是内部类)?有什么限制?

答:可以,但一个源文件中最多只能有一个公开类(public class)而且文件名必须和公开类的类名完全保持一致。

34、Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口?

答:可以继承其他类或实现其他接口,在Swing编程中常用此方式来实现事件监听和回调。

35、内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?

答:一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。

36、Java 中的final关键字有哪些用法?

答:(1)修饰类:表示该类不能被继承;(2)修饰方法:表示方法不能被重写;(3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。

37、指出下面程序的运行结果:

class A{
    static{
        System.out.print("1");
    }
    public A(){
        System.out.print("2");
    }
}
class B extends A{
    static{
        System.out.print("a");
    }
    public B(){
        System.out.print("b");
    }
}
public class Hello{
    public static void main(String[] args){
        A ab = new B();
        ab = new B();
    }
}

答:执行结果:1a2b2b。创建对象时构造器的调用顺序是:先初始化静态成员,然后调用父类构造器,再初始化非静态成员,最后调用自身构造器。

38、数据类型之间的转换:

1)如何将字符串转换为基本数据类型?

2)如何将基本数据类型转换为字符串?

答:

1)调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)即可返回相应基本类型;

2)一种方法是将基本数据类型与空字符串(””)连接(+)即可获得其所对应的字符串;另一种方法是调用String 类中的valueOf(…)方法返回相应字符串

39、如何实现字符串的反转及替换?

答:方法很多,可以自己写实现也可以使用String或StringBuffer / StringBuilder中的方法。有一道很常见的面试题是用递归实现字符串反转,代码如下所示:

public static String reverse(String originStr) {
 if(originStr == null || originStr.length() <= 1) 
 return originStr;
 return reverse(originStr.substring(1)) + originStr.charAt(0);
 }

40、怎样将GB2312编码的字符串转换为ISO-8859-1编码的字符串?

答:代码如下所示:

String s1 = "你好";

String s2 = newString(s1.getBytes("GB2312"), "ISO-8859-1");

41、日期和时间:

1)如何取得年月日、小时分钟秒?

2)如何取得从1970年1月1日0时0分0秒到现在的毫秒数?

3)如何取得某月的最后一天?

4)如何格式化日期?

答:操作方法如下所示:

1)创建java.util.Calendar 实例,调用其get()方法传入不同的参数即可获得参数所对应的值

2)以下方法均可获得该毫秒数:

Calendar.getInstance().getTimeInMillis();

System.currentTimeMillis();

3)示例代码如下:

Calendar time = Calendar.getInstance();

time.getActualMaximum(Calendar.DAY_OF_MONTH);

4)利用java.text.DataFormat 的子类(如SimpleDateFormat类)中的format(Date)方法可将日期格式化。

42、打印昨天的当前时刻。

答:

public class YesterdayCurrent {
    public static void main(String[] args){
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DATE, -1);
        System.out.println(cal.getTime());
    }
}

43、比较一下Java 和JavaSciprt。

答:JavaScript 与Java是两个公司开发的不同的两个产品。Java 是原Sun 公司推出的面向对象的程序设计语言,特别适合于互联网应用程序开发;而JavaScript是Netscape公司的产品,为了扩展Netscape浏览器的功能而开发的一种可以嵌入Web页面中运行的基于对象和事件驱动的解释性语言,它的前身是LiveScript;而Java 的前身是Oak语言。

下面对两种语言间的异同作如下比较:

1)基于对象和面向对象:Java是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言。因而它本身提供了非常丰富的内部对象供设计人员使用;

2)解释和编译:Java 的源代码在执行之前,必须经过编译;JavaScript 是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行;

3)强类型变量和类型弱变量:Java采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript中变量声明,采用其弱类型。即变量在使用前不需作声明,而是解释器在运行时检查其数据类型;

4)代码格式不一样。

补充:上面列出的四点是原来所谓的标准答案中给出的。其实Java和JavaScript最重要的区别是一个是静态语言,一个是动态语言。目前的编程语言的发展趋势是函数式语言和动态语言。在Java中类(class)是一等公民,而JavaScript中函数(function)是一等公民。对于这种问题,在面试时还是用自己的语言回答会更加靠谱。

44、什么时候用assert?

答:assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。一般来说,assertion用于保证程序最基本、关键的正确性。assertion检查通常在开发和测试时开启。为了提高性能,在软件发布后, assertion检查通常是关闭的。在实现中,断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为true;如果表达式计算为false,那么系统会报告一个AssertionError。

断言用于调试目的:

assert(a > 0); // throws an AssertionError if a <= 0

断言可以有两种形式:

assert Expression1;

assert Expression1 : Expression2 ;

Expression1 应该总是产生一个布尔值。

Expression2 可以是得出一个值的任意表达式;这个值用于生成显示更多调试信息的字符串消息。

断言在默认情况下是禁用的,要在编译时启用断言,需使用source 1.4 标记:

javac -source 1.4 Test.java

要在运行时启用断言,可使用-enableassertions 或者-ea 标记。

要在运行时选择禁用断言,可使用-da 或者-disableassertions 标记。

要在系统类中启用断言,可使用-esa 或者-dsa 标记。还可以在包的基础上启用或者禁用断言。可以在预计正常情况下不会到达的任何位置上放置断言。断言可以用于验证传递给私有方法的参数。不过,断言不应该用于验证传递给公有方法的参数,因为不管是否启用了断言,公有方法都必须检查其参数。不过,既可以在公有方法中,也可以在非公有方法中利用断言测试后置条件。另外,断言不应该以任何方式改变程序的状态。

45、Error 和Exception 有什么区别?

答:Error 表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的情况;Exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。

补充:2005年摩托罗拉的面试中曾经问过这么一个问题“If a process reports a stack overflow run-time error, what’s the most possible cause?”,给了四个选项a. lack of memory; b. write on an invalid memory space; c. recursive function calling; d. array index out of boundary. Java程序在运行时也可能会遭遇StackOverflowError,这是一个错误无法恢复,只能重新修改代码了,这个面试题的答案是c。如果写了不能迅速收敛的递归,则很有可能引发栈溢出的错误,如下所示:

package com.lovo;
public class StackOverflowErrorTest {
 public static void main(String[] args) {
 main(null);
 }
}

因此,用递归编写程序时一定要牢记两点:1. 递归公式;2. 收敛条件(什么时候就不再递归而是回溯了)。

46、try{}里有一个return语句,那么紧跟在这个try后的finally{}里的code会不会被执行,什么时候被执行,在return前还是后?

答:会执行,在方法返回调用者前执行。Java允许在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,这会对程序造成很大的困扰,C#中就从语法上规定不能做这样的事。

47、Java 语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用?

答:Java 通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java 中,每个异常都是一个对象,它是Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并进行处理。Java 的异常处理是通过5 个关键词来实现的:try、catch、throw、throws和finally。一般情况下是用try来执行一段程序,如果出现异常,系统会抛出(throw)一个异常,这时候你可以通过它的类型来捕捉(catch)它,或最后(finally)由缺省处理器来处理;try用来指定一块预防所有“异常”的程序;catch 子句紧跟在try块后面,用来指定你想要捕捉的“异常”的类型;throw 语句用来明确地抛出一个“异常”;throws用来标明一个成员函数可能抛出的各种“异常”;finally 为确保一段代码不管发生什么“异常”都被执行一段代码;可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部写另一个try语句保护其他代码。每当遇到一个try 语句,“异常”的框架就放到栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种“异常”进行处理,栈就会展开,直到遇到有处理这种“异常”的try 语句。

48、运行时异常与受检异常有何异同?

答:异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常。异常和继承一样,是面向对象程序设计中经常被滥用的东西,神作《Effective Java》中对异常的使用给出了以下指导原则:

  • 不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常)
  • 对可以恢复的情况使用受检异常,对编程错误使用运行时异常
  • 避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)
  • 优先使用标准的异常
  • 每个方法抛出的异常都要有文档
  • 保持异常的原子性
  • 不要在catch中忽略掉捕获到的异常

49、列出一些你常见的运行时异常?

答:

ArithmeticException(算术异常)

ClassCastException (类转换异常)

IllegalArgumentException (非法参数异常)

IndexOutOfBoundsException (下表越界异常)

NullPointerException (空指针异常)

SecurityException (安全异常)

50、final, finally, finalize 的区别?

答:final:修饰符(关键字)有三种用法:如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。将变量声明为final,可以保证它们在使用中不被改变,被声明为final 的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为final 的方法也同样只能使用,不能在子类中被重写。finally:通常放在try…catch的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。finalize:Object类中定义的方法,Java中允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。