美文网首页
Java面试题笔记(上)(二)

Java面试题笔记(上)(二)

作者: quiterr | 来源:发表于2017-09-02 21:32 被阅读0次

    31、String s = new String("xyz");创建了几个字符串对象?
    答:两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。

    32、接口是否可继承(extends)接口?抽象类是否可实现(implements)接口?抽象类是否可继承具体类(concrete class)?
    答:接口可以继承接口,而且支持多重继承。抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类。

    33、一个".java"源文件中是否可以包含多个类(不是内部类)?有什么限制?
    答:可以,但一个源文件中最多只能有一个公开类(public class)而且文件名必须和公开类的类名完全保持一致。

    34、Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口? 答:可以继承其他类或实现其他接口,在Swing编程和Android开发中常用此方式来实现事件监听和回调。

    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、数据类型之间的转换:
    如何将字符串转换为基本数据类型?
    如何将基本数据类型转换为字符串?
    答:调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)即可返回相应基本类型;
    一种方法是将基本数据类型与空字符串("")连接(+)即可获得其所对应的字符串;另一种方法是调用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 = new String(s1.getBytes("GB2312"), "ISO-8859-1");
    

    41、日期和时间:
    - 如何取得年月日、小时分钟秒?
    - 如何取得从1970年1月1日0时0分0秒到现在的毫秒数?
    - 如何取得某月的最后一天?
    - 如何格式化日期?
    答:问题1:创建java.util.Calendar 实例,调用其get()方法传入不同的参数即可获得参数所对应的值。Java 8中可以使用java.time.LocalDateTime来获取,代码如下所示。

    public class DateTimeTest { 
    public static void main(String[] args) { 
    Calendar cal = Calendar.getInstance(); 
    System.out.println(cal.get(Calendar.YEAR)); 
    System.out.println(cal.get(Calendar.MONTH)); // 0 - 11 
    System.out.println(cal.get(Calendar.DATE)); 
    System.out.println(cal.get(Calendar.HOUR_OF_DAY)); 
    System.out.println(cal.get(Calendar.MINUTE)); 
    System.out.println(cal.get(Calendar.SECOND)); 
    
    // Java 8 
    LocalDateTime dt = LocalDateTime.now(); 
    System.out.println(dt.getYear()); 
    System.out.println(dt.getMonthValue()); // 1 - 12 
    System.out.println(dt.getDayOfMonth()); 
    System.out.println(dt.getHour()); 
    System.out.println(dt.getMinute()); 
    System.out.println(dt.getSecond()); 
    }}
    

    问题2:以下方法均可获得该毫秒数。

    new Date().getTime();
    Calendar.getInstance().getTimeInMillis();
    System.currentTimeMillis();
    Clock.systemDefaultZone().millis(); // Java 8
    

    问题3:代码如下所示。

    Calendar time = Calendar.getInstance();
    time.getActualMaximum(Calendar.DAY_OF_MONTH);
    

    问题4:利用java.text.DateFormat 的子类(如SimpleDateFormat类)中的format(Date)方法可将日期格式化。Java 8中可以用java.time.format.DateTimeFormatter来格式化时间日期,代码如下所示。

    import java.text.SimpleDateFormat;
    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    import java.util.Date;
    
    class DateFormatTest { public static void main(String[] args) { 
    SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd"); 
    Date date1 = new Date(); 
    System.out.println(oldFormatter.format(date1)); 
    
    // Java 8 
    DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd"); 
    LocalDate date2 = LocalDate.now(); 
    System.out.println(date2.format(newFormatter)); }}
    

    补充:Java的时间日期API一直以来都是被诟病的东西,为了解决这一问题,Java 8中引入了新的时间日期API,其中包括LocalDate、LocalTime、LocalDateTime、Clock、Instant等类,这些的类的设计都使用了不变模式,因此是线程安全的设计。如果不理解这些内容,可以参考我的另一篇文章《关于Java并发编程的总结和思考》。

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

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

    在Java 8中,可以用下面的代码实现相同的功能。

    import java.time.LocalDateTime;
    class YesterdayCurrent { 
    public static void main(String[] args) { 
    LocalDateTime today = LocalDateTime.now(); 
    LocalDateTime yesterday = today.minusDays(1); 
    System.out.println(yesterday); }}
    

    43、比较一下Java和JavaSciprt。

    • 面向对象和基于对象
      Java是一种真正的面向对象的语言;JavaScript是种脚本语言,是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,它本身提供了非常丰富的内部对象供设计人员使用。
    • 解释和编译
      Java的源代码在执行之前,必须经过编译。JavaScript是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行。
    • 强类型变量和类型弱变量:Java采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript的解释器在运行时检查推断其数据类型。
    • 代码格式不一样。

    44、什么时候用断言(assert)?
    答:测试。

    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。如果写了不能迅速收敛的递归,则很有可能引发栈溢出的错误,如下所示:

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

    提示:用递归编写程序时一定要牢记两点:1. 递归公式;2. 收敛条件(什么时候就不再继续递归)

    46、try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后?
    答:会执行,在方法返回调用者前执行。
    注意:在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,就会返回修改后的值。

    47、Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用?
    答:在Java中,每个异常都是一个对象,它是Throwable类或其子类的实例。一般情况下是用try来执行一段程序,如果系统会抛出(throw)一个异常对象,可以通过它的类型来捕获(catch)它,或通过总是执行代码块(finally)来处理;try用来指定一块预防所有异常的程序;catch子句紧跟在try块后面,用来指定你想要捕获的异常的类型;throw语句用来明确地抛出一个异常;throws用来声明一个方法可能抛出的各种异常(当然声明异常时允许无病呻吟);finally为确保一段代码不管发生什么异常状况都要被执行;try语句可以嵌套,每当遇到一个try语句,异常的结构就会被放入异常栈中,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理,异常栈就会执行出栈操作,直到遇到有处理这种异常的try语句或者最终将异常抛给JVM。

    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()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

    51、类ExampleA继承Exception,类ExampleB继承ExampleA。
    有如下代码片断:

    try { 
    throw new ExampleB("b")} 
    catch(ExampleA e){ 
    System.out.println("ExampleA");} 
    catch(Exception e){ 
    System.out.println("Exception");
    }
    

    请问执行此段代码的输出是什么?
    答:输出:ExampleA。(根据里氏代换原则[能使用父类型的地方一定能使用子类型],抓取ExampleA类型异常的catch块能够抓住try块中抛出的ExampleB类型的异常)

    面试题 - 说出下面代码的运行结果。(此题的出处是《Java编程思想》一书)

    class Annoyance extends Exception {}
    class Sneeze extends Annoyance {}
    class Human { 
    public static void main(String[] args) throws Exception { 
    try { 
    try { 
    throw new Sneeze(); } 
    catch ( Annoyance a ) { 
    System.out.println("Caught Annoyance"); 
    throw a; 
    } } catch ( Sneeze s ) { 
    System.out.println("Caught Sneeze"); return ; } 
    finally { System.out.println("Hello World!"); } 
    }}
    

    52、List、Set、Map是否继承自Collection接口? 答:List、Set 是,Map 不是。Map是键值对映射容器,与List和Set有明显的区别,而Set存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List是线性结构的容器,适用于按数值索引访问元素的情形。

    53、阐述ArrayList、Vector、LinkedList的存储性能和特性。
    答:ArrayList 和Vector都是使用数组方式存储数据,查询速度较快,Vector是线程安全的容器,但性能上较ArrayList差。LinkedList使用双向链表实现存储,插入速度较快。Vector属于遗留容器(Java早期的版本中提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),已经不推荐使用,但是由于ArrayList和LinkedListed都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类Collections中的synchronizedList方法将其转换成线程安全的容器后再使用(这是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现)。

    补充:遗留容器中的Properties类和Stack类在设计上有严重的问题,Properties是一个键和值都是字符串的特殊的键值对映射,在设计上应该是关联一个Hashtable并将其两个泛型参数设置为String类型,但是Java API中的Properties直接继承了Hashtable,这很明显是对继承的滥用。这里复用代码的方式应该是Has-A关系而不是Is-A关系,另一方面容器都属于工具类,继承工具类本身就是一个错误的做法,使用工具类最好的方式是Has-A关系(关联)或Use-A关系(依赖)。同理,Stack类继承Vector也是不正确的。Sun公司的工程师们也会犯这种低级错误,让人唏嘘不已。

    54、Collection和Collections的区别?
    答:Collection是一个接口,它是Set、List等容器的父接口;Collections是个一个工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、排序、线程安全化等等。

    55、List、Map、Set三个接口存取元素时,各有什么特点?
    答:List以特定索引来存取元素,可以有重复元素。Set不能存放重复元素(用对象的equals()方法来区分元素是否重复)。Map保存键值对(key-value pair)映射,映射关系可以是一对一或多对一。Set和Map容器都有基于哈希存储和排序树的两种实现版本,基于哈希存储的版本理论存取时间复杂度为O(1),而基于排序树版本的实现在插入或删除元素时会按照元素或元素的键(key)构成排序树从而达到排序和去重的效果。

    56、TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素? 答:TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java中对函数式编程的支持)。

    57、Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别? 答:sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态,请参考第66题中的线程状态转换图)。wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。
    补充:可能不少人对什么是进程,什么是线程还比较模糊,对于为什么需要多线程编程也不是特别理解。简单的说:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位;线程是进程的一个实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位。线程的划分尺度小于进程,这使得多线程程序的并发性高;进程在执行时通常拥有独立的内存单元,而线程之间可以共享内存。使用多线程的编程通常能够带来更好的性能和用户体验,但是多线程的程序对于其他程序是不友好的,因为它可能占用了更多的CPU资源。当然,也不是线程越多,程序的性能就越好,因为线程之间的调度和切换也会浪费CPU时间。时下很时髦的Node.js就采用了单线程异步I/O的工作模式。

    58、线程的sleep()方法和yield()方法有什么区别? 答:① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;④ sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。

    59、当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B? 答:不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的synchronized修饰符要求执行方法时要获得对象的锁,如果已经进入A方法说明对象锁已经被取走,那么试图进入B方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。

    60、请说出与线程同步以及线程调度相关的方法。 答:- wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;- notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
    提示:关于Java多线程和并发编程的问题,建议大家看我的另一篇文章《关于Java并发编程的总结和思考》。
    补充:Java 5通过Lock接口提供了显式的锁机制(explicit lock),增强了灵活性以及对线程的协调。Lock接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了newCondition()方法来产生用于线程之间通信的Condition对象;此外,Java 5还提供了信号量机制(semaphore),信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信号量的许可(调用Semaphore对象的acquire()方法);在完成对资源的访问后,线程必须向信号量归还许可(调用Semaphore对象的release()方法)。

    相关文章

      网友评论

          本文标题:Java面试题笔记(上)(二)

          本文链接:https://www.haomeiwen.com/subject/mrybpttx.html