接下来几篇文章准备系统整理一下有关 Java 的面试题, 分为基础篇, javaweb 篇, 框架篇, 数据库篇, 多线程篇, 并发篇, 算法篇等等, 陆续更新中. 其他方面如前端后端等等的面试题也在整理中, 都会有的.
注: 文末有福利!
1,String s = new String("xyz"); 创建了几个 StringObject? 是否可以继承 String 类?
两个或一个都有可能,"xyz" 对应一个对象, 这个对象放在字符串常量缓冲区, 常量 "xyz" 不管出现多少遍, 都是缓冲区中的那一个. NewString 每写一遍, 就创建一个新的对象, 它使用常量 "xyz" 对象的内容来创建出一个新 String 对象. 如果以前就用过'xyz', 那么这里就不会创建 "xyz" 了, 直接从缓冲区拿, 这时创建了一个 StringObject; 但如果以前没有用过 "xyz", 那么此时就会创建一个对象并放入缓冲区, 这种情况它创建两个对象. 至于 String 类是否继承, 答案是否定的, 因为 String 默认 final 修饰, 是不可继承的.
2,String 和 StringBuffer 的区别
JAVA 平台提供了两个类: String 和 StringBuffer, 它们可以储存和操作字符串, 即包含多个字符的字符数据. 这个 String 类提供了数值不可改变的字符串. 而这个 StringBuffer 类提供的字符串可以进行修改. 当你知道字符数据要改变的时候你就可以使用 StringBuffer. 典型地, 你可以使用 StringBuffers 来动态构造字符数据.
3, 下面这条语句一共创建了多少个对象: String s="a"+"b"+"c"+"d";
对于如下代码:
- String s1 = "a";
- String s2 = s1 + "b";
- String s3 = "a" + "b";
- System.out.println(s2 == "ab");
- System.out.println(s3 == "ab");
第一条语句打印的结果为 false, 第二条语句打印的结果为 true, 这说明 javac 编译可以对字符串常量直接相加的表达式进行优化, 不必要等到运行期再去进行加法运算处理, 而是在编译时去掉其中的加号, 直接将其编译成一个这些常量相连的结果.
题目中的第一行代码被编译器在编译时优化后, 相当于直接定义了一个 "abcd" 的字符串, 所以, 上面的代码应该只创建了一个 String 对象. 写如下两行代码,
- String s ="a" + "b" +"c" + "d";
- System.out.println(s== "abcd");
最终打印的结果应该为 true.
4,try {}里有一个 return 语句, 那么紧跟在这个 try 后的 finally{}里的 code 会不会被执行, 什么时候被执行, 在 return 前还是后?
我们知道 finally{}中的语句是一定会执行的, 那么这个可能正常脱口而出就是 return 之前, return 之后可能就出了这个方法了, 鬼知道跑哪里去了, 但更准确的应该是在 return 中间执行, 请看下面程序代码的运行结果:
- public classTest {
- public static void main(String[]args) {
- System.out.println(newTest().test());;
- }
- static int test()
- {
- intx = 1;
- try
- {
- returnx;
- }
- finally
- {
- ++x;
- }
- }
- }
--------- 执行结果 ---------
1
运行结果是 1, 为什么呢? 主函数调用子函数并得到结果的过程, 好比主函数准备一个空罐子, 当子函数要返回结果时, 先把结果放在罐子里, 然后再将程序逻辑返回到主函数. 所谓返回, 就是子函数说, 我不运行了, 你主函数继续运行吧, 这没什么结果可言, 结果是在说这话之前放进罐子里的.
5,final, finally, finalize 的区别.
final 用于声明属性, 方法和类, 分别表示属性不可变, 方法不可覆盖, 类不可继承. 内部类要访问局部变量, 局部变量必须定义成 final 类型.
finally 是异常处理语句结构的一部分, 表示总是执行.
finalize 是 Object 类的一个方法, 在垃圾收集器执行的时候会调用被回收对象的此方法, 可以覆盖此方法提供垃圾收集时的其他资源回收, 例如关闭文件等. 但是 JVM 不保证此方法总被调用
6, 运行时异常与一般异常有何异同?
异常表示程序运行过程中可能出现的非正常状态, 运行时异常表示虚拟机的通常操作中可能遇到的异常, 是一种常见运行错误. java 编译器要求方法必须声明抛出可能发生的非运行时异常, 但是并不要求必须声明抛出未被捕获的运行时异常.
7,error 和 exception 有什么区别?
error 表示恢复不是不可能但很困难的情况下的一种严重问题. 比如说内存溢出. 不可能指望程序能处理这样的情况. exception 表示一种设计或实现问题. 也就是说, 它表示如果程序运行正常, 从不会发生的情况.
8, 简单说说 Java 中的异常处理机制的简单原理和应用.
异常是指 java 程序运行时 (非编译) 所发生的非正常情况或错误, 与现实生活中的事件很相似, 现实生活中的事件可以包含事件发生的时间, 地点, 人物, 情节等信息, 可以用一个对象来表示, Java 使用面向对象的方式来处理异常, 它把程序中发生的每个异常也都分别封装到一个对象来表示的, 该对象中包含有异常的信息.
Java 对异常进行了分类, 不同类型的异常分别用不同的 Java 类表示, 所有异常的根类为 java.lang.Throwable,Throwable 下面又派生了两个子类:
Error 和 Exception,Error 表示应用程序本身无法克服和恢复的一种严重问题, 程序只有奔溃了, 例如, 说内存溢出和线程死锁等系统问题.
Exception 表示程序还能够克服和恢复的问题, 其中又分为系统异常和普通异常:
系统异常是软件本身缺陷所导致的问题, 也就是软件开发人员考虑不周所导致的问题, 软件使用者无法克服和恢复这种问题, 但在这种问题下还可以让软件系统继续运行或者让软件挂掉, 例如, 数组脚本越界(ArrayIndexOutOfBoundsException), 空指针异常(NullPointerException), 类转换异常(ClassCastException);
普通异常是运行环境的变化或异常所导致的问题, 是用户能够克服的问题, 例如, 网络断线, 硬盘空间不够, 发生这样的异常后, 程序不应该死掉.
java 为系统异常和普通异常提供了不同的解决方案, 编译器强制普通异常必须 try..catch 处理或用 throws 声明继续抛给上层调用方法处理, 所以普通异常也称为 checked 异常, 而系统异常可以处理也可以不处理, 所以, 编译器不强制用 try..catch 处理或用 throws 声明, 所以系统异常也称为 unchecked 异常.
9,Java 中堆和栈有什么区别?
JVM 中堆和栈属于不同的内存区域, 使用目的也不同. 栈常用于保存方法帧和局部变量, 而对象总是在堆上分配. 栈通常都比堆小, 也不会在多个线程之间共享, 而堆被整个 JVM 的所有线程共享.
栈: 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配, 当在一段代码块定义一个变量时, Java 就在栈中为这个变量分配内存空间, 当超过变量的作用域后, Java 会自动释放掉为该变量分配的内存空间, 该内存空间可以立即被另作它用.
堆: 堆内存用来存放由 new 创建的对象和数组, 在堆中分配的内存, 由 Java 虚拟机的自动垃圾回收器来管理. 在堆中产生了一个数组或者对象之后, 还可以在栈中定义一个特殊的变量, 让栈中的这个变量的取值等于数组或对象在堆内存中的首地址, 栈中的这个变量就成了数组或对象的引用变量, 以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对象, 引用变量就相当于是为数组或者对象起的一个名称.
10, 能将 int 强制转换为 byte 类型的变量吗? 如果该值大于 byte 类型的范围, 将会出现什么现象?
我们可以做强制转换, 但是 Java 中 int 是 32 位的, 而 byte 是 8 位的, 所以, 如果强制转化, int 类型的高 24 位将会被丢弃, 因为 byte 类型的范围是从 -128 到 127.
11,a.hashCode() 有什么用? 与 a.equals(b) 有什么关系?
hashCode() 方法对应对象整型的 hash 值. 它常用于基于 hash 的集合类, 如 Hashtable,HashMap,LinkedHashMap 等等. 它与 equals() 方法关系特别紧密. 根据 Java 规范, 两个使用 equal() 方法来判断相等的对象, 必须具有相同的 hash code.
12, 字节流与字符流的区别
要把一段二进制数据数据逐一输出到某个设备中, 或者从某个设备中逐一读取一段二进制数据, 不管输入输出设备是什么, 我们要用统一的方式来完成这些操作, 用一种抽象的方式进行描述, 这个抽象描述方式起名为 IO 流, 对应的抽象类为 OutputStream 和 InputStream, 不同的实现类就代表不同的输入和输出设备, 它们都是针对字节进行操作的.
计算机中的一切最终都是二进制的字节形式存在. 对于经常用到的中文字符, 首先要得到其对应的字节, 然后将字节写入到输出流. 读取时, 首先读到的是字节, 可是我们要把它显示为字符, 我们需要将字节转换成字符. 由于这样的需求很广泛, Java 专门提供了字符流包装类.
底层设备永远只接受字节数据, 有时候要写字符串到底层设备, 需要将字符串转成字节再进行写入. 字符流是字节流的包装, 字符流则是直接接受字符串, 它内部将串转成字节, 再写入底层设备, 这为我们向 IO 设备写入或读取字符串提供了一点点方便.
字符向字节转换时, 要注意编码的问题, 因为字符串转成字节数组, 其实是转成该字符的某种编码的字节形式, 读取也是反之的道理.
13, 什么是 java 序列化, 如何实现 java 序列化? 或者请解释 Serializable 接口的作用.
我们有时候将一个 java 对象变成字节流的形式传出去或者从一个字节流中恢复成一个 java 对象, 例如, 要将 java 对象存储到硬盘或者传送给网络上的其他计算机, 这个过程我们可以自己写代码去把一个 java 对象变成某个格式的字节流再传输.
但是, jre 本身就提供了这种支持, 我们可以调用 OutputStream 的 writeObject 方法来做, 如果要让 java 帮我们做, 要被传输的对象必须实现 serializable 接口, 这样, javac 编译时就会进行特殊处理, 编译的类才可以被 writeObject 方法操作, 这就是所谓的序列化. 需要被序列化的类必须实现 Serializable 接口, 该接口是一个 mini 接口, 其中没有需要实现方法, implements Serializable 只是为了标注该对象是可被序列化的.
例如, 在 web 开发中, 如果对象被保存在了 Session 中, tomcat 在重启时要把 Session 对象序列化到硬盘, 这个对象就必须实现 Serializable 接口. 如果对象要经过分布式系统进行网络传输, 被传输的对象就必须实现 Serializable 接口.
14, 描述一下 JVM 加载 class 文件的原理机制?
JVM 中类的装载是由 ClassLoader 和它的子类来实现的, Java ClassLoader 是一个重要的 Java 运行时系统组件. 它负责在运行时查找和装入类文件的类.
15,heap 和 stack 有什么区别.
java 的内存分为两类, 一类是栈内存, 一类是堆内存. 栈内存是指程序进入一个方法时, 会为这个方法单独分配一块私属存储空间, 用于存储这个方法内部的局部变量, 当这个方法结束时, 分配给这个方法的栈会释放, 这个栈中的变量也将随之释放.
堆是与栈作用不同的内存, 一般用于存放不在当前方法栈中的那些数据, 例如, 使用 new 创建的对象都放在堆里, 所以, 它不会随方法的结束而消失. 方法中的局部变量使用 final 修饰后, 放在堆中, 而不是栈中.
16,GC 是什么? 为什么要有 GC?
GC 是垃圾收集的意思(Gabage Collection), 内存处理是编程人员容易出现问题的地方, 忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃, Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的, Java 语言没有提供释放已分配内存的显示操作方法.
17, 垃圾回收的优点和原理. 并考虑 2 种回收机制.
Java 语言中一个显著的特点就是引入了垃圾回收机制, 使 c++ 程序员最头疼的内存管理的问题迎刃而解, 它使得 Java 程序员在编写程序的时候不再需要考虑内存管理. 由于垃圾回收机制, Java 中的对象不再有 "作用域" 的概念, 只有对象的引用才有 "作用域".
垃圾回收可以有效的防止内存泄露, 有效的使用可以使用的内存. 垃圾回收器通常是作为一个单独的低级别的线程运行, 不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收, 程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收.
回收机制有分代复制垃圾回收和标记垃圾回收, 增量垃圾回收.
18, 垃圾回收器的基本原理是什么? 垃圾回收器可以马上回收内存吗? 有什么办法主动通知虚拟机进行垃圾回收?
对于 GC 来说, 当程序员创建对象时, GC 就开始监控这个对象的地址, 大小以及使用情况. 通常, GC 采用有向图的方式记录和管理堆 (heap) 中的所有对象. 通过这种方式确定哪些对象是 "可达的", 哪些对象是 "不可达的". 当 GC 确定一些对象为 "不可达" 时, GC 就有责任回收这些内存空间.
程序员可以手动执行 System.gc(), 通知 GC 运行, 但是 Java 语言规范并不保证 GC 一定会执行.
19,Java 中, throw 和 throws 有什么区别
throw 用于抛出 java.lang.Throwable 类的一个实例化对象, 意思是说你可以通过关键字 throw 抛出一个 Exception, 如:
throw new IllegalArgumentException("XXXXXXXXX)
而 throws 的作用是作为方法声明和签名的一部分, 方法被抛出相应的异常以便调用者能处理. Java 中, 任何未处理的受检查异常强制在 throws 子句中声明.
20,java 中会存在内存泄漏吗, 请简单描述.
先解释什么是内存泄漏: 所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中. java 中有垃圾回收机制, 它可以保证当对象不再被引用的时候, 对象将自动被垃圾回收器从内存中清除掉.
由于 Java 使用有向图的方式进行垃圾回收管理, 可以消除引用循环的问题, 例如有两个对象, 相互引用, 只要它们和根进程不可达, 那么 GC 也是可以回收它们的.
java 中的内存泄露的情况: 长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露, 尽管短生命周期对象已经不再需要, 但是因为长生命周期对象持有它的引用而导致不能被回收, 这就是 java 中内存泄露的发生场景, 通俗地说, 就是程序员可能创建了一个对象, 以后一直不再使用这个对象, 这个对象却一直被引用, 即这个对象无用但是却无法被垃圾回收器回收的, 这就是 java 中可能出现内存泄露的情况, 例如, 缓存系统, 我们加载了一个对象放在缓存中(例如放在一个全局 map 对象中), 然后一直不再使用它, 这个对象一直被缓存引用, 但却不再被使用.
来源: https://www.cnblogs.com/Lovebugs/p/8721532.html