一、谈谈对 JVM、JRE 和 JDK 的理解。
JVM
Java 虚拟机(Java Virtual Machine, JVM):是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现,即相同的字节码文件,通过虚拟机都会输出相同的结果,这也是为什么 Java 是跨平台的。
JRE
Java Running Enviroment: Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,Java 命令和其他一些基础构件。但是,他不能用于创建新线程。
JDK
Java Development Kit: 它是功能强大的 Java SDK。拥有 JRE 所有的一切,还有编译器(javac) 和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
综上:JVM 是运行 Java 字节码的虚拟机,是保证“一次编译,随处运行”的关键;JRE 只能运行 Java 程序,如果需要 Java 编程相关的工作,就必须安装 JDK。
二、Java 和 C++ 的区别。
很多面试官都会问,所有有必要了解两种语言的区别。
- 都是面向对象的语言,所有两者都支持继承、封装、多态;
- Java 不提供指针来直接访问内存,程序会更加安全;
- Java 里的类只能单继承(Java 里的可以多继承接口),而 C++ 中的可以实现多继承;
- Java 里有自动内存管理机制,不需要程序员手动释放无用的内存。
三、重载和重写的区别?构造器是否可以被重写?(详情可见另一篇文章 https://www.cnblogs.com/reformdai/p/10678990.html)
- 重载:是在同一个类中,方法名相同,但是参数不同。注意这里的参数不同可以是参数的数量不同,也可以是参数的类型不同或者也可以是顺序不同;
- 重写:发生在不同的类中,而且还应该是继承关系,父子类中的方法名和参数必须相同,返回值范围小于等于父类的,抛出的异常范围小于等于父类的,访问修饰符范围大于等于父类;注意,如果父类中的方法的访问修饰符是 private,那么子类就不能重写父类的该方法。
在讲继承的时候我们就知道父类的私有属性和构造方法不能够被继承,所有 Constructor 也就不能被重写(override),但是可以被重载(overload),所以,一个类中可以出现多个构造函数的情况。
四、Java 中面向对象的三大特性:继承、封装、多态
(详见文章:https://www.cnblogs.com/reformdai/p/10604027.html)
五、String str = "abcd"; 与 String str1 = new String("abcd"); 一样吗?str 和 str1 相等吗?
首先,这两者创建的方法是有差别的。
- String str = "abcd";:这种创建方式会先检查字符串常量池中有没有 "abcd",如果没有,就会创建一个,然后 str 指向字符串常量池中的对象;如果有,则直接指向字符串常量池中的"abcd";
- String str1 = new String("abcd");:这种则是直接 new 了一个新的对象,在堆内存中创建的。
所以 str 和 str1 不相等,推荐使用第一种方式创建。
六、String、StringBuffer 和 StringBuilder 的区别是什么?String 为什么是不可变的?
- 可变性:
- String 类中使用的是 final 关键字修饰字符数组保存字符串的 (public final class String),所以 String 对象是不可变的。
- StringBuffer 和 StringBuilder 是继承 AbstractStringBuilder 类。从源码中可以看到,在抽象类 AbstractStringBuilder 中定义的字符串数组保存字符串 char[] value; 并没有用 final 修饰,所以这两者都是可变的。AbstractStringBuilder 的部分源码如下:
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
}
- 线程安全性:
- String 中的对象是不可变得,也就可以理解为是常量,是线程安全的;
- StringBuffer 中对方法使用了同步锁(synchronized),所以是线程安全的;
- StringBuilder 中并没有加锁,所以是线程不安全的。
综上:
- 操作少量的数据适合用 String;
- 单线程操作字符串缓冲区下操作大量数据适用 StringBuilder;
- 多线程操作字符串缓冲区下操作大量数据适用 StringBuffer。
七、接口和抽象类的区别是什么?抽象类必须要有抽象方法吗?抽象类能使用 final 修饰吗?
接口和抽象类的区别是什么:
(详见文章:https://www.cnblogs.com/reformdai/p/10646471.html)
抽象类必须要有抽象方法吗:
抽象类中不一定包含抽象方法,但是如果包含了抽象方法的一定要声明为抽象类。
抽象类能使用 final 修饰吗:
不能有 final 修饰,因为 final 修饰的类是不能够被继承的。而抽象方法不能被继承就失去了其存在的意义。
八、== 与 equals() 的区别?
==:是判断比较的两个对象的地址值是否相等。
equals():是判断两个对象是否相等,一般有两种情况:
- 类没有重写 equals() 方法时:比较的是两个对象的地址值是否相等,与 == 相同;
- 重写了 equals() 方法时:比较的是两个对象的值是否相等。如果值相等,则返回 true。
如下代码:
public class Test {
public static void main(String[] args) {
String s1 = new String("a");
String s2 = new String("a");
String s3 = "a";
String s4 = "a";
System.out.println(s1 == s2); // false
System.out.println(s3 == s4); // true
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s4)); // true
System.out.println(s1.equals(s2)); // true
System.out.println(s3.equals(s4)); // true
}
}
注意:
- 这里的 equals() 方法是被重写过的,因为默认的 equals() 是比较两个对象的地址。
- 当创建 String 类型的对象时,虚拟机会在常量池中产找有没有已经存在的值和要创建的值相同,如果有相同的,就把它赋给当前的引用,如果没有就在常量池中重新创建一个 String 对象。
九、hashCode() 与 equals()
hashCode():是获取哈希码,而哈希码实际上是一个 int 类型的整数。等价的两个对象的哈希码一定相同,但是反过来不对,即哈希码相同其值不一定相同。
那这个 hashCode() 有什么用呢?以 “HashSet 如何检查重复” 为例:当把对象加入到 HashSet 中时,会先计算对象的 hashCode 值,来判断加入对象的位置,同时也会与其他已经加入的对象的 hashCode 的值进行比较,如果没有相符的 hashCode,HashSet 会认为没有对象与新加入的对象相同。如果 hashCode 的相同,此时会调用 equals() 方法来检查 hashCode 相同的对象是否真的相同。如果真的相同, HashSet 就不会让其加入操作成功。如果不相同的话,则让其加入。这样就大大的减少了使用 equals() 的次数,大大的提高了运行速度。
实际上,hashCode() 得到的 int 类型的整数是确定对象在哈希表中的索引位置。所以也就是 hashCode() 只有在哈希表中才真正有用。
注意:hashCode() 与 equals() 的相关规定:
- 如果两个对象相等,则 hashCode 一定相同;
- 两个对象相等,对两个分别调用 equals() 方法,都会返回 true;
- 两个对象有相同的 hashCode 值,他们也不一定相等(因为有可能通过计算得到相同的 hash 值);
- 因此,equals() 方法被覆盖过,则 hashCode() 方法也必须被覆盖;
- hashCode 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
十、String 类的常用方法都有哪些?
- charAt(int index):返回指定索引处的char值。
- concat(String str):将指定的字符串连接到此字符串的末尾。
- equals(Object anObject):将此字符串与指定的对象进行比较。
- getBytes():返回字符串的 byte 类型数组。
- indexOf(String str):返回指定子字符串第一次出现的字符串中的索引。
- length():返回字符串的长度。
- replace(char oldChar, char newChar):字符串替换。
- split(String regex):分割字符串,返回一个分割后的字符串数组。
- toCharArray():将此字符串转换为新的字符数组。
- valueOf(char c):返回char参数的字符串表示形式。
- trim():去除字符串两端空白。
- substring(int beginIndex):返回一个字符串,该字符串是该字符串的子字符串。
十一、接口和抽象类有什么区别?
- 实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现;
- 构造函数:抽象类可以有构造函数;接口不能有;
- 实现数量:类可以有很多接口;但是只能继承一个抽象类(Java 中的类只能单继承)
- 访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。
十二、Java 中的 IO 流分为几种?BIO, NIO, AIO 有什么区别?
Java 中的 IO 流:
- 按功能分:输入流(Input) 和输出流(Output)。
- 按类型分:字节流 (Input / Output) 和字符流 (Writer / Reader)。
字节流和字符流的区别:字节流按 8 位传输,以字节为单位输入输出数据;字符流按 16 位传输,以字符为单位输入输出数据。
BIO, NIO, AIO 有什么区别:
- BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
- NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel (通道) 通讯,实现了多路复用。它支持面向缓冲的,基于通道的 IO 操作方法。 NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 IO 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
- AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非阻塞 IO,异步 IO 的操作基于事件和回调机制,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时得到通知,接着就由这个线程自行进行 IO 操作, IO 操作本身是同步的。目前 AIO 应用的不是很广泛。
十三、Files 的常用方法都有哪些?
- Files.exists():检查文件路径是否存在;
- Files.createFile():创建文件;
- Files.createDirectory():创建文件夹;
- Files.copy():复制一个文件;
- Files.move():移动文件;
- Files.size():查看文件个数;
- Files.read():读取文件;
- Files.write():写入文件。
十四、什么是反射机制?反射机制的应用场景有哪些?
反射机制介绍
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性(注意也包括 private 修饰的);这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
静态编译和动态编译
- 静态编译:在编译时确定类型,绑定对象。
- 动态编译:运行时确定类型,绑定对象。
反射机制优缺点
- 优点:运行期类型的判断,动态加载类,提高代码的灵活度。
- 缺点:性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 Java 代码要慢很多。
反射的应用场景
反射是框架设计的灵魂!
在平时的项目开发中,基本很少会直接使用到反射机制,但是这并不能说明反射机制没有用。实际上,很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring / Hibernate 等框架也大量使用到了反射机制。
举例:
- 我们在使用 JDBC 连接数据库时使用 Class.forName() 通过反射加载数据库驱动程序;
- Spring 框架也用到了很多反射机制,最经典的就是 XML 的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:①将程序内所有的 XML 的配置模式或者 Properties 配置文件加载入内存中;② Java 类里面解析 XML 或 Properties 里面的内容,得到对应实体类的字节码字符串以及相关的属性信息;③ 使用反射机制,根据这个字符串获得某个类的 Class 实例;④ 动态配置实例的属性。
十五、为什么 Java 中只有值传递
按值调用(call by value)表示方法接收的是调用者提供的值,而被引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。
Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。
example1:
public static void main(String[] args) {
int num1 = 20;
int num2 = 30;
exchange(num1, num2);
System.out.println("num1 = " + num1); // num1 = 20
System.out.println("num2 = " + num2); // num2 = 30
}
public static void exchange(int a, int b){
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a); // a = 30
System.out.println("b = " + b); // b = 20
}
在 exchange 方法中,a,b 值的交换不会影响到 num1,num2 的值。因为 a,b 中的值只是从 num1,num2 的复制过来的,也就是说 a,b 相当于是 num1,num2 的副本,不会影响到原件本身。
通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2。
example2:
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]); // 1
change(arr);
System.out.println(arr[0]); // 0
}
public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}
array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
example 3:
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
Student s1 = new Student("小张");
Student s2 = new Student("小李");
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName()); // s1:小张
System.out.println("s2:" + s2.getName()); // s2:小李
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName()); // x:小李
System.out.println("y:" + y.getName()); // y:小张
}
}
方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap 方法的参数 x 和 y 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝。
综上:
Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的。
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
- 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。
一个材料人跨行到互联网的研究僧
希望大家能多多关注~