大家好,我是大彬~
今天给大家分享Java基础高频面试题(第二弹),助力春招!
讲讲深拷贝和浅拷贝?
浅拷贝:拷⻉对象和原始对象的引⽤类型引用同⼀个对象。
以下例子,Cat对象里面有个Person对象,调用clone之后,克隆对象和原对象的Person引用的是同一个对象,这就是浅拷贝。
代码语言:javascript复制public class Cat implements Cloneable {
private String name;
private Person owner;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Cat c = new Cat();
Person p = new Person(18, "程序员大彬");
c.owner = p;
Cat cloneCat = (Cat) c.clone();
p.setName("大彬");
System.out.println(cloneCat.owner.getName());
}
//output
//大彬
}
深拷贝:拷贝对象和原始对象的引用类型引用不同的对象。
以下例子,在clone函数中不仅调用了super.clone,而且调用Person对象的clone方法(Person也要实现Cloneable接口并重写clone方法),从而实现了深拷贝。可以看到,拷贝对象的值不会受到原对象的影响。
代码语言:javascript复制public class Cat implements Cloneable {
private String name;
private Person owner;
@Override
protected Object clone() throws CloneNotSupportedException {
Cat c = null;
c = (Cat) super.clone();
c.owner = (Person) owner.clone();//拷贝Person对象
return c;
}
public static void main(String[] args) throws CloneNotSupportedException {
Cat c = new Cat();
Person p = new Person(18, "程序员大彬");
c.owner = p;
Cat cloneCat = (Cat) c.clone();
p.setName("大彬");
System.out.println(cloneCat.owner.getName());
}
//output
//程序员大彬
}
两个对象的hashCode()相同,则 equals()是否也一定为 true?
equals与hashcode的关系:
- 如果两个对象调用equals比较返回true,那么它们的hashCode值一定要相同;
- 如果两个对象的hashCode相同,它们并不一定相同。
hashcode方法主要是用来提升对象比较的效率,先进行hashcode()的比较,如果不相同,那就不必在进行equals的比较,这样就大大减少了equals比较的次数,当比较对象的数量很大的时候能提升效率。
之所以重写equals()
要重写hashcode()
,是为了保证equals()
方法返回true的情况下hashcode值也要一致,如果重写了equals()
没有重写hashcode()
,就会出现两个对象相等但hashcode()
不相等的情况。这样,当用其中的一个对象作为键保存到hashMap、hashTable或hashSet中,再以另一个对象作为键值去查找他们的时候,则会查找不到。
Java创建对象有几种方式?
Java创建对象有以下几种方式:
- 用new语句创建对象。
- 使用反射,使用Class.newInstance()创建对象。
- 调用对象的clone()方法。
- 运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法。
说说类实例化的顺序
Java中类实例化顺序:
- 静态属性,静态代码块。
- 普通属性,普通代码块。
- 构造方法。
public class LifeCycle {
// 静态属性
private static String staticField = getStaticField();
// 静态代码块
static {
System.out.println(staticField);
System.out.println("静态代码块初始化");
}
// 普通属性
private String field = getField();
// 普通代码块
{
System.out.println(field);
System.out.println("普通代码块初始化");
}
// 构造方法
public LifeCycle() {
System.out.println("构造方法初始化");
}
// 静态方法
public static String getStaticField() {
String statiFiled = "静态属性初始化";
return statiFiled;
}
// 普通方法
public String getField() {
String filed = "普通属性初始化";
return filed;
}
public static void main(String[] argc) {
new LifeCycle();
}
/**
* 静态属性初始化
* 静态代码块初始化
* 普通属性初始化
* 普通代码块初始化
* 构造方法初始化
*/
}
equals和==有什么区别?
- 对于基本数据类型,==比较的是他们的值。基本数据类型没有equal方法;
- 对于复合数据类型,==比较的是它们的存放地址(是否是同一个对象)。
equals()
默认比较地址值,重写的话按照重写逻辑去比较。
常见的关键字有哪些?
static
static可以用来修饰类的成员方法、类的成员变量。
static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
以下例子,age为非静态变量,则p1打印结果是:Name:zhangsan, Age:10
;若age使用static修饰,则p1打印结果是:Name:zhangsan, Age:12
,因为static变量在内存只有一个副本。
public class Person {
String name;
int age;
public String toString() {
return "Name:" name ", Age:" age;
}
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "zhangsan";
p1.age = 10;
Person p2 = new Person();
p2.name = "lisi";
p2.age = 12;
System.out.println(p1);
System.out.println(p2);
}
/**Output
* Name:zhangsan, Age:10
* Name:lisi, Age:12
*///~
}
static方法一般称作静态方法。静态方法不依赖于任何对象就可以进行访问,通过类名即可调用静态方法。
代码语言:javascript复制public class Utils {
public static void print(String s) {
System.out.println("hello world: " s);
}
public static void main(String[] args) {
Utils.print("程序员大彬");
}
}
静态代码块只会在类加载的时候执行一次。以下例子,startDate和endDate在类加载的时候进行赋值。
代码语言:javascript复制class Person {
private Date birthDate;
private static Date startDate, endDate;
static{
startDate = Date.valueOf("2008");
endDate = Date.valueOf("2021");
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
}
静态内部类
在静态方法里,使用⾮静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。⽽静态内部类不需要。
代码语言:javascript复制public class OuterClass {
class InnerClass {
}
static class StaticInnerClass {
}
public static void main(String[] args) {
// 在静态方法里,不能直接使用OuterClass.this去创建InnerClass的实例
// 需要先创建OuterClass的实例o,然后通过o创建InnerClass的实例
// InnerClass innerClass = new InnerClass();
OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
StaticInnerClass staticInnerClass = new StaticInnerClass();
outerClass.test();
}
public void nonStaticMethod() {
InnerClass innerClass = new InnerClass();
System.out.println("nonStaticMethod...");
}
}
final
- 基本数据类型用final修饰,则不能修改,是常量;对象引用用final修饰,则引用只能指向该对象,不能指向别的对象,但是对象本身可以修改。
- final修饰的方法不能被子类重写
- final修饰的类不能被继承。
this
this.属性名称
指访问类中的成员变量,可以用来区分成员变量和局部变量。如下代码所示,this.name
访问类Person当前实例的变量。
/**
* @description:
* @author: 程序员大彬
* @time: 2021-08-17 00:29
*/
public class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
this.方法名称
用来访问本类的方法。以下代码中,this.born()
调用类 Person 的当前实例的方法。
/**
* @description:
* @author: 程序员大彬
* @time: 2021-08-17 00:29
*/
public class Person {
String name;
int age;
public Person(String name, int age) {
this.born();
this.name = name;
this.age = age;
}
void born() {
}
}
super
super 关键字用于在子类中访问父类的变量和方法。
代码语言:javascript复制class A {
protected String name = "大彬";
public void getName() {
System.out.println("父类:" name);
}
}
public class B extends A {
@Override
public void getName() {
System.out.println(super.name);
super.getName();
}
public static void main(String[] args) {
B b = new B();
b.getName();
}
/**
* 大彬
* 父类:大彬
*/
}
在子类B中,我们重写了父类的getName()
方法,如果在重写的getName()
方法中我们要调用父类的相同方法,必须要通过super关键字显式指出。
final, finally, finalize 的区别
- final 用于修饰属性、方法和类, 分别表示属性不能被重新赋值,方法不可被覆盖,类不可被继承。
- finally 是异常处理语句结构的一部分,一般以
try-catch-finally
出现,finally
代码块表示总是被执行。 - finalize 是Object类的一个方法,该方法一般由垃圾回收器来调用,当我们调用
System.gc()
方法的时候,由垃圾回收器调用finalize()
方法,回收垃圾,JVM并不保证此方法总被调用。
final关键字的作用?
- final 修饰的类不能被继承。
- final 修饰的方法不能被重写。
- final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。
方法重载和重写的区别?
同个类中的多个方法可以有相同的方法名称,但是有不同的参数列表,这就称为方法重载。参数列表又叫参数签名,包括参数的类型、参数的个数、参数的顺序,只要有一个不同就叫做参数列表不同。
重载是面向对象的一个基本特性。
代码语言:javascript复制public class OverrideTest {
void setPerson() { }
void setPerson(String name) {
//set name
}
void setPerson(String name, int age) {
//set name and age
}
}
方法的重写描述的是父类和子类之间的。当父类的功能无法满足子类的需求,可以在子类对方法进行重写。方法重写时, 方法名与形参列表必须一致。
如下代码,Person为父类,Student为子类,在Student中重写了dailyTask
方法。
public class Person {
private String name;
public void dailyTask() {
System.out.println("work eat sleep");
}
}
public class Student extends Person {
@Override
public void dailyTask() {
System.out.println("study eat sleep");
}
}
接口与抽象类区别?
1、语法层面上的区别
- 抽象类可以有方法实现,而接口的方法中只能是抽象方法;
- 抽象类中的成员变量可以是各种类型的,接口中的成员变量只能是public static final类型;
- 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
2、设计层面上的区别
- 抽象层次不同。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口只是对类行为进行抽象。继承抽象类是一种"是不是"的关系,而接口实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是具备不具备的关系,比如鸟是否能飞。
- 继承抽象类的是具有相似特点的类,而实现接口的却可以不同的类。
门和警报的例子:
代码语言:javascript复制class AlarmDoor extends Door implements Alarm {
//code
}
class BMWCar extends Car implements Alarm {
//code
}
常见的Exception有哪些?
常见的RuntimeException:
ClassCastException
//类型转换异常IndexOutOfBoundsException
//数组越界异常NullPointerException
//空指针ArrayStoreException
//数组存储异常NumberFormatException
//数字格式化异常ArithmeticException
//数学运算异常
unchecked Exception:
NoSuchFieldException
//反射异常,没有对应的字段ClassNotFoundException
//类没有找到异常IllegalAccessException
//安全权限异常,可能是反射时调用了private方法
Error和Exception的区别?
Error:JVM 无法解决的严重问题,如栈溢出StackOverflowError
、内存溢出OOM
等。程序无法处理的错误。
Exception:其它因编程错误或偶然的外在因素导致的一般性问题。可以在代码中进行处理。如:空指针异常、数组下标越界等。
运行时异常和非运行时异常(checked)的区别?
unchecked exception
包括RuntimeException
和Error
类,其他所有异常称为检查(checked)异常。
RuntimeException
由程序错误导致,应该修正程序避免这类异常发生。checked Exception
由具体的环境(读取的文件不存在或文件为空或sql异常)导致的异常。必须进行处理,不然编译不通过,可以catch或者throws。
throw和throws的区别?
- throw:用于抛出一个具体的异常对象。
- throws:用在方法签名中,用于声明该方法可能抛出的异常。子类方法抛出的异常范围更加小,或者根本不抛异常。
BIO/NIO/AIO区别的区别?
同步阻塞IO : 用户进程发起一个IO操作以后,必须等待IO操作的真正完成后,才能继续运行。
同步非阻塞IO: 客户端与服务器通过Channel连接,采用多路复用器轮询注册的Channel
。提高吞吐量和可靠性。用户进程发起一个IO操作以后,可做其它事情,但用户进程需要轮询IO操作是否完成,这样造成不必要的CPU资源浪费。
异步非阻塞IO: 非阻塞异步通信模式,NIO的升级版,采用异步通道实现异步通信,其read和write方法均是异步方法。用户进程发起一个IO操作,然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知。类似Future模式。
守护线程是什么?
- 守护线程是运行在后台的一种特殊进程。
- 它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
- 在 Java 中垃圾回收线程就是特殊的守护线程。
Java支持多继承吗?
java中,类不支持多继承。接口才支持多继承。接口的作用是拓展对象功能。当一个子接口继承了多个父接口时,说明子接口拓展了多个功能。当一个类实现该接口时,就拓展了多个的功能。
Java不支持多继承的原因:
- 出于安全性的考虑,如果子类继承的多个父类里面有相同的方法或者属性,子类将不知道具体要继承哪个。
- Java提供了接口和内部类以达到实现多继承功能,弥补单继承的缺陷。
如何实现对象克隆?
- 实现
Cloneable
接口,重写clone()
方法。这种方式是浅拷贝,即如果类中属性有自定义引用类型,只拷贝引用,不拷贝引用指向的对象。如果对象的属性的Class也实现Cloneable
接口,那么在克隆对象时也会克隆属性,即深拷贝。 - 结合序列化,深拷贝。
- 通过
org.apache.commons
中的工具类BeanUtils
和PropertyUtils
进行对象复制。
同步和异步的区别?
同步:发出一个调用时,在没有得到结果之前,该调用就不返回。
异步:在调用发出后,被调用者返回结果之后会通知调用者,或通过回调函数处理这个调用。
阻塞和非阻塞的区别?
阻塞和非阻塞关注的是线程的状态。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会恢复运行。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
举个例子,理解下同步、阻塞、异步、非阻塞的区别: 同步就是烧开水,要自己来看开没开;异步就是水开了,然后水壶响了通知你水开了(回调通知)。阻塞是烧开水的过程中,你不能干其他事情,必须在旁边等着;非阻塞是烧开水的过程里可以干其他事情。
Java8的新特性有哪些?
- Lambda 表达式:Lambda允许把函数作为一个方法的参数
- Stream API :新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中
- 默认方法:默认方法就是一个在接口里面有了一个实现的方法。
- Optional 类 :Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Date Time API :加强对日期与时间的处理。
什么是序列化和反序列化?
序列化:把内存中的对象转换为字节序列的过程。
反序列化:把字节序列恢复为Java对象的过程。
如何实现序列化
实现Serializable
接口即可。序列化的时候(如objectOutputStream.writeObject(user)
),会判断user是否实现了Serializable
,如果对象没有实现Serializable
接口,在序列化的时候会抛出NotSerializableException
异常。源码如下:
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() "n" debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
什么是反射?
动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
在运行状态中,对于任意一个类,能够知道这个类的所有属性和方法。对于任意一个对象,能够调用它的任意一个方法和属性。
反射有哪些应用场景呢?
- JDBC连接数据库时使用
Class.forName()
通过反射加载数据库的驱动程序 - Eclispe、IDEA等开发工具利用反射动态解析对象的类型与结构,动态提示对象的属性和方法
- Web服务器中利用反射调用了Sevlet的
service
方法 - JDK动态代理底层依赖反射实现
(完)
我是大彬,非科班转码,大三开始自学Java,校招斩获京东、携程等offer。作为一名转码选手,深感这一路的不易。希望我的分享可以帮助更多的小伙伴,我踩过的坑你们不要再踩!