• 中文
    • English
  • 注册
  • 查看作者
  • 三到五年互联网公司Java面试题大全

    数据类型之间的转换:

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

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

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

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

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

    方法很多,可以自己写实现也可以使用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); }

    怎样将smxfl.cn编码的字符串转换为lrzqb.cn编码的字符串?

    代码如下所示:

    String s1 = “你好”;

    String s2 = new String(s1.getBytes(“GB2312”), “ISO-8859-1”);

    打印昨天的当前时刻。

    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); }}

    比较一下Java和JavaSciprt。

    JavaScript 与Java是两个公司开发的不同的两个产品。Java 是原Sun

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

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

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

    解释和编译:Java的源代码在执行之前,必须经过编译。JavaScript是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行。(目前的浏览器几乎都使用了JIT(即时编译)技术来提升JavaScript的运行效率)强类型变量和类型弱变量:Java采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript的解释器在运行时检查推断其数据类型。代码格式不一样。

    补充:上面列出的四点是网上流传的所谓的标准答案。其实Java和JavaScript最重要的区别是一个是静态语言,一个是动态语言。目前的编程语言的发展趋势是函数式语言和动态语言。在Java中类(class)是一等公民,而JavaScript中函数(function)是一等公民,因此JavaScript支持函数式编程,可以使用Lambda函数和闭包(closure),当然Java8也开始支持函数式编程,提供了对Lambda表达式以及函数式接口的支持。对于这类问题,在面试的时候最好还是用自己的语言回答会更加靠谱,不要背网上所谓的标准答案。

    什么时候用断言(assert)?

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

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

    断言可以有两种形式:

    assert Expression1;

    assert Expression1 : Expression2 ;

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

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

    要在运行时启用断言,可以在启动JVM时使用-enableassertions或者-ea标记。要在运行时选择禁用断言,可以在启动JVM时使用-da或者-disableassertions标记。要在系统类中启用或禁用断言,可使用-esa或-dsa标记。还可以在包的基础上启用或者禁用断言。

    注意:断言不应该以任何方式改变程序的状态。简单的说,如果希望在不满足某些条件时阻止代码的执行,就可以考虑用断言来阻止它。

    Error和Exception有什么区别?

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

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

    会执行,在方法返回调用者前执行。

    注意:在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,就会返回修改后的值。显然,在finally中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java中也可以通过提升编译器的语法检查级别来产生警告或错误,Eclipse中可以在如图所示的地方进行设置,强烈建议将此项设置为编译错误。

    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语句或者最终将异常抛给JVM。

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

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

    Java中对异常的使用给出了以下指导原则:

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

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

    ArithmeticException(算术异常)

    ClassCastException (类转换异常)

    IllegalArgumentException (非法参数异常)

    IndexOutOfBoundsException (下标越界异常)

    NullPointerException (空指针异常)

    SecurityException (安全异常)

    阐述final、finally、finalize的区别。

    final:修饰符(关键字)有三种用法:如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。将变量声明为final,可以保证它们在使用中不被改变,被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为final的方法也同样只能使用,不能在子类中被重写。

    finally:通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。finalize:Object类中定义的方法,Java中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。

    List、Set、Map是否继承自Collection接口?

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

    阐述ArrayList、Vector、LinkedList的存储性能和特性。

    ArrayList 和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector中的方法由于添加了synchronized修饰,因此Vector是线程安全的容器,但性能上较ArrayList差,因此已经是Java中的遗留容器。LinkedList使用双向链表实现存储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。Vector属于遗留容器(Java早期的版本中提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),已经不推荐使用,但是由于ArrayList和LinkedListed都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类Collections中的synchronizedList方法将其转换成线程安全的容器后再使用(这是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强实现)。补充:遗留容器中的Properties类和Stack类在设计上有严重的问题,Properties是一个键和值都是字符串的特殊的键值对映射,在设计上应该是关联一个Hashtable并将其两个泛型参数设置为String类型,但是JavaAPI中的Properties直接继承了Hashtable,这很明显是对继承的滥用。这里复用代码的方式应该是Has-A关系而不是Is-A关系,另一方面容器都属于工具类,继承工具类本身就是一个错误的做法,使用工具类最好的方式是Has-A关系(关联)或Use-A关系(依赖)。同理,Stack类继承Vector也是不正确的。Sun公司的工程师们也会犯这种低级错误,让人唏嘘不已。

    Collection和Collections的区别?

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

    List、Map、Set三个接口存取元素时,各有什么特点?

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

    TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素?

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

    例子1:public class Student implements Comparable { private String name; // 名 private int age; // 年龄 public Student(String name, int age) {this.name = name;this.age = age; } @Override public String toString() { return “Student [name=” + name + “, age=” + age + “]”; } @Overridepublic int compareTo(Student o) {return this.age – o.age; // 比较年龄(年龄的升序) }}

    import java.util.Set;import java.util.TreeSet;class Test01 {public static void main(String[] args) {Set set = new TreeSet<>(); // Java 7的钻石语法(构造器后面的尖括号中不需要写类型)

    set.add(new Student(“Hao LUO”, 33));

    set.add(new Student(“XJ WANG”, 32));

    set.add(new Student(“Bruce LEE”, 60));

    set.add(new Student(“Bob YANG”, 22));

    for(Student stu : set) {

    System.out.println(stu);

    }

    // 输出结果:

    // Student [name=Bob YANG, age=22]

    // Student [name=XJ WANG, age=32]

    // Student [name=Hao LUO, age=33]

    // Student [name=Bruce LEE, age=60]

    }}

    例子2:

    public class Student {

    private String name; // 姓名

    private int age; // 年龄public Student(String name, int age) {

    this.name = name;

    this.age = age;

    } /** * 获取学生姓名 */ public String getName() {return name; } /** * 获取学生年龄 */ public int getAge() {return age; } @Override

    public String toString() {return “Student [name=” + name + “, age=” + age + “]”;}}

    java.util.ArrayList;

    import java.util.Collections;

    import java.util.Comparator;

    import java.util.List;

    class Test02 {public static void main(String[] args) { List list = new ArrayList<>(); // Java 7的钻石语法(构造器后面的尖括号中不需要写类型)

    list.add(new Student(“Hao LUO”, 33));

    list.add(new Student(“XJ WANG”, 32));

    list.add(new Student(“Bruce LEE”, 60));

    list.add(new Student(“Bob YANG”, 22));

    // 通过sort方法的第二个参数传入一个Comparator接口对象

    // 相当于是传入一个比较对象大小的算法到sort方法中

    // 由于Java中没有函数指针、仿函数、委托这样的概念

    // 因此要将一个算法传入一个方法中唯一的选择就是通过接口回调

    Collections.sort(list, new Comparator () { @Override

    public int compare(Student o1, Student o2) { return o1.getName().compareTo(o2.getName()); // 比较学生姓名 }

    });for(Student stu : list) { System.out.println(stu);}

    // 输出结果:

    // Student [name=Bob YANG, age=22]

    // Student [name=Bruce LEE, age=60]

    // Student [name=Hao LUO, age=33]

    // Student [name=XJ WANG, age=32]

    }

    }

    Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别?

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

    线程的sleep()方法和yield()方法有什么区别?

    ① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;

    ② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;

    ③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;

    ④ sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。

    当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?

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

    请说出与线程同步以及线程调度相关的方法。

    wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;

    sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;

    notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;

    notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

    补充:Java 通过Lock接口提供了显式的锁机制(explicit lock),增强了灵活性以及对线程的协调。Lock接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了newCondition()方法来产生用于线程之间通信的Condition对象;此外,Java还提供了信号量机制(semaphore),信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信号量的许可(调用Semaphore对象的acquire()方法);在完成对资源的访问后,线程必须向信号量归还许可(调用Semaphore对象的release()方法)。

    资料已整理成文档,需要获取的小伙伴可以+ VX: mxk6072

  • 0
  • 0
  • 0
  • 15
  • 请登录之后再进行评论

    登录
  • 任务
  • 实时动态
  • 发布
  • 单栏布局 侧栏位置: