【面试】Java基础
参考:https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/Java基础知识.md
1、String、StringBuffer、StringBuilder区别?
可变性:
简单来说:String类中使用final关键字修饰字符数组来保存字符串:private final char value[] ,所以String对象是不可变的。StringBuffer和StringBuilder都继承自AbstractStringBuilder类,AbstractStringBuilder也是使用字符数组来保存字符串的,但没有有final关键字修饰,所以这两种对象都是可变的。
线程安全性:
String中的对象是不可变的,即常量,线程安全。
StringBuffer对方法加了同步锁或对调用的方法加了同步锁,所以是线程安全的。
StringBuilder并没有对方法进行同步加锁,所以是非线程安全的。
性能:
每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象。
StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。
相同情况下使用StringBuilder相比StringBuffer性能仅提升10%-15%左右,但却要冒多线程不安全的风险。
2、在一个静态方法中调用一个非静态成员为什么是非法的?
由于静态方法可以不通过对象进行调用,(但非静态成员/方法需要先实例化一个对象)因此在静态方法中,不能调用其他非静态变量,也不可访问非静态变量成员。
知识点:类加载,内存分配
3、静态方法和实例方法有何不同?
1)在外部调用静态方法时,可以使用 类名.方法名的方式,也可以使用 对象名.方法名的方式;而实例方法只能使用第二种。也就是说,静态方法可以无需创建对象;
2)静态方法在访问本类的成员时,只允许访问静态成员(静态方法和静态变量),而不允许访问实例方法和实例成员变量;实例方法则无此限制。
4、在Java中定义一个不做事且没有参数的构造方法的作用?
Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中 “没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super() 来调用父类中特定的构造方法,则编译时会报错。因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类中加上一个不做事且没有参数的构造方法。
--就是为了调用父类的方法及属性。
知识点:构造方法的理解
5、接口和抽象类的区别是什么?
1)接口的方法默认是public,所有方法在接口中不能有具体实现(Java8开始接口方法可以有默认实现),而抽象类可以有非抽象的方法;
2)接口中除了static、final变量,不能有其他变量,而抽象类中则不一定有;
3)一个类可以实现多个接口,但只能实现一个抽象类。接口本身可以通过 extends 关键字扩展多个接口;
4)接口方法默认修饰符是public,抽象方法可以是public,protected,default这些修饰符(抽象方法就是为了被重写,所以不能使用private修饰符);
5)从设计层面来说,抽象是对类的抽象,是一种模板设计;而接口是对行为的抽象,是一种行为的规范。
知识点:接口与抽象类
6、== 与 equals
== 的作用是判断两个对象的地址是不是相等,即 判断两个对象是不是同一个对象(基本数据类型==比较的是值,引用数据类型==比较的是内存地址);
equals() 的作用也是判断两个对象是否相等,但一般分两种情况:
1)类没有重写equals(),则通过equals() 比较该类的两个对象时,等价于 “==” 比较两个对象;
2)类重写了equals()方法,即 判断两个对象的内容是不是相同的。
说明:
- String中的equals方法是被重写过的 ,Object的equals方法比较的是内存地址,而String的equals方法比较的是对象的值;
- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用,如果没有就在常量池中重新创建一个String对象。
7、hashCode与equals()?
为什么重写equals()方法时要重写hashCode()方法?
hashCode() 介绍:
hashCode() 的作用是获取哈希码,也称为散列码,它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含hashCode函数。
为什么要有hashCode?
我们先以“HashSet” 如何检测重复为例子来说明为什么要有hashCode?当你把对象加入hashSet时,hashSet会先计算对象的hashCode值来判断对象加入的位置,同时也会与其他已经加入对象的hashCode值作比较,如果没有相符的hashCode,hashSet会假设对象没有重复出现。但如果发现有相同hashCode值得对象,这时就会调用equals()方法来检查hashCode相等的对象是否真的相同。如果两者相同,hashSet就不会让其加入成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。
hashCode() 与equals() 的相关规定:
1)如果两个对象相等,则hashCode一定相等;
2)两个对象相等,调用equals()方法结果为true;
3)两个对象有相同的hashCode值,它们也不一定相等;
4)重写equals() 方法时必须重写hashCode() 方法;
5)hashCode() 的默认行为是对堆上的对象产生独特值,如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
8、线程
参考:https://www.cnblogs.com/Rain1203/p/10998031.html
9、关于final关键字的一些总结
final关键字主要用在三个地方:变量、方法 和 类。
1)对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能改变;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象;
2)当用final修饰一个类时,表明这个类不能被继承,final类中所有成员方法都会被隐式的指定为final方法;
3)使用final方法的原因有两个。第一个是把方法锁定,以防任何继承类修改它的含义;第二是因为效率,早早起Java版本中,会将final方法转为内嵌调用(现在版本不需要了)。
10、Java中的IO流:
参考:https://www.cnblogs.com/Rain1203/p/10770018.html
11、BIO、NIO、AIO 有什么区别?
BIO(Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数(单机不超过1000)不是特别高的情况下,这种模式是比较不错的,可以让每一个连接专注于自己的I/O,并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的BIO模式是无力的。因此,我们需要一种更高效的I/O处理模型来应对更高的并发量。
NIO(New I/O):NIO是一种同步非阻塞的I/O模式,在Java1.4中引入了NIO框架,对应java.nio包,提供Channel、Selector、Buffer等抽象。NIO中的N可以理解为Non-Blocking,不是单纯的New。它支持面向缓冲的,基于通道的I/O操作方法。NIO提供了与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的应用,应该使用NIO的非阻塞模式来开发。
AIO(Asynchronous I/O):AIO也就是NIO2。在Java7中引入了NIO的改进版NIO2,它是异步非阻塞的IO模型。异步IO是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会阻塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续操作。AIO是异步IO的缩写,虽然NIO在网络操作中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。
12、聊聊Java中的反射机制,有哪些应用场景?
Java反射机制是指在运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种运行时动态的获取信息以及动态调用对象方法的功能称为Java的反射机制。
Java反射机制的实现:
1)class对象的获取:有两种方式,第一种方式:Class对象调用newInstance()方法生成 Object obj = class.newInstance();
第二种方式:对象获得对应的Constructor对象,再通过该Constructor对象的newInstance()方法生成 Constructor<?> c = class.getDeclaredConstructor(new Class[]{String.class});obj = c.newInstance(new Object[]{"lcj"});
2)获取class对象的摘要信息;
3)获取class对象的属性,方法,构造函数等;
4)class对象动态生成;
5)动态调用函数;
6)通过反射机制获取泛型类型;
7)通过反射机制获取注解信息。
应用场景:
逆向代码,例如反编译;
动态生成类框架,例如Gson;
Spring中的AOP。