文章大纲



一: 前言



  • 大家好,这里是IT学习日记,一个非双一流大学毕业的深漂族,年少曾憧憬大厂,面试过许多家公司,也曾踩过无数坑,深知面试技巧和知识广度与深度对一个应届生乃至工作多年的开发者的重要性
  • 故特意收集了各个公司、大厂的面试高频题,通过每天打卡的方式,和大家一起记录和学习,希望能够帮助到应届生和开发者们少走弯路,一起冲向大厂!!!

【秋招冲刺-每日打卡】应届生JAVA岗-每日5道高频面试题【Day4】-基础篇(4)_每日打卡




二: 面试题目


一: 深拷贝和浅拷贝的区别是什么?



  在讲解拷贝知识前,我们先来了解下JAVA中的数据类型,主要分为以下两种:


  1、基本类型: 也叫做值类型,主要是值JAVA自带的8种数据类型即:byte、char、short、int、long、float、double、boolean。


  2、引用类型: JAVA中除了基本类型,其他的称为引用类型,常见的如:对象、数组、枚举 等。


  3、在JAVA中,基本类型是存放在栈中的,而引用类型实际上是存在堆中,然后在栈中存在一个指针指向堆中的实际对象数据。



【秋招冲刺-每日打卡】应届生JAVA岗-每日5道高频面试题【Day4】-基础篇(4)_每日打卡_02


  拷贝: 实际上就是复制的意思,跟平常我们使用ctrl+c命令的效果一样,但在JAVA中,它是区分为浅拷贝和深拷贝两种。

(一) 浅拷贝:

  复制出来的对象跟原来的对象有相同的值,但是如果被复制对象中包含有其他类型对象时,只会复制这个对象的引用


  如:A对象中有一个属性叫demo是B类型的对象,那么浅拷贝时,复制出来的C对象里面的demo属性和A对象都是指向同一个对象,如果修改C对象里面的demo属性,那么A对象中的demo属性也会被修改,因为它们指向的是相同的一个对象。


  对象想具有拷贝功能,需要满足以下的两个条件(注意:Object提供的clone方法默认只实现浅拷贝):


  1、实现Clonable接口


  2、重写Clonable接口中的clone方法


  浅拷贝样例图和代码:



【秋招冲刺-每日打卡】应届生JAVA岗-每日5道高频面试题【Day4】-基础篇(4)_每日打卡_03



【秋招冲刺-每日打卡】应届生JAVA岗-每日5道高频面试题【Day4】-基础篇(4)_每日打卡_04


public class CloneDemo {
public static void main(String[] args) throws Exception{
Demo1 demo1 = new Demo1("1",2,new Demo2("3",4));
System.out.println("原来的对象数据:" + demo1);

// 浅拷贝
Demo1 cloneDemo = (Demo1) demo1.clone();
System.out.println("拷贝出来的对象数据:" + cloneDemo);

// 修改拷贝对象的属性
cloneDemo.setAge(100);
cloneDemo.setAgeCone(100);
cloneDemo.setUserName("test");
cloneDemo.setS1(Short.valueOf("200"));
cloneDemo.getDemo2().setDemo2Age(1000);
System.out.println();
System.out.println("修改拷贝出来的对象引用类型属性:被拷贝对象的数据" + demo1);
System.out.println("修改拷贝出来的对象引用类型属性:拷贝出来的对象数据" + cloneDemo);
}
}


@Data
class Demo1 implements Cloneable{
private String userName;
private Integer age;
private Short s1 = new Short("100");
private Integer ageCone = new Integer(900);
private Demo2 demo2;

public Demo1(String userName, Integer age, Demo2 demo2) {
this.userName = userName;
this.age = age;
this.demo2 = demo2;
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}

@Override
public String toString() {
return "Demo1{" +
"userName='" + userName + '\'' +
", age=" + age +
", s1=" + s1 +
", ageCone=" + ageCone +
", demo2=" + demo2 +
'}';
}
}

@Data
class Demo2 implements Cloneable{
private String demo2Username;
private Integer demo2Age;

public Demo2(String demo2Username, Integer demo2Age) {
this.demo2Username = demo2Username;
this.demo2Age = demo2Age;
}
}



(二) 深拷贝:

  复制出来的对象拥有和原来对象相同的一套属性值,里面的属性和被复制的对象是相互独立的,修改任何一个对象都不会对另外一个对象产生影响,


  从上面可以知道Object提供的clone方法只能实现浅拷贝,如果想实现深拷贝,可以采取以来两种方法:


  1、每个引用类型内部都实现cloneable接口并重写clone方法即可。


  2、使用序列化和反序列化(前提是类需要实现序列化接口Serializable)


  注意:序列化是将对象写到流中便于网络传输或者持久化到磁盘,而反序列化则是把对象从流/磁盘中读取出来。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以我们通过对象的序列化产生克隆对象,然后通过反序列化获取这个对象。


  实现深拷贝方式一: 每个引用类型都实现Cloneable接口并重写clone方法



【秋招冲刺-每日打卡】应届生JAVA岗-每日5道高频面试题【Day4】-基础篇(4)_高频面试题_05



【秋招冲刺-每日打卡】应届生JAVA岗-每日5道高频面试题【Day4】-基础篇(4)_每日打卡_06


public class CloneDemo {
public static void main(String[] args) throws Exception{
Demo1 demo1 = new Demo1("1",2,new Demo2("3",4));
System.out.println("原来的对象数据:" + demo1);

// 深拷贝
Demo1 cloneDemo = (Demo1) demo1.clone();
System.out.println("拷贝出来的对象数据:" + cloneDemo);

// 修改拷贝对象的属性
cloneDemo.setAge(100);
cloneDemo.setAgeCone(100);
cloneDemo.setUserName("test");
cloneDemo.setS1(Short.valueOf("200"));
cloneDemo.getDemo2().setDemo2Age(1000);
System.out.println();
System.out.println("修改拷贝出来的对象引用类型属性:被拷贝对象的数据" + demo1);
System.out.println("修改拷贝出来的对象引用类型属性:拷贝出来的对象数据" + cloneDemo);
}
}


@Data
class Demo1 implements Cloneable{
private String userName;
private Integer age;
private Short s1 = new Short("100");
private Integer ageCone = new Integer(900);
private Demo2 demo2;

public Demo1(String userName, Integer age, Demo2 demo2) {
this.userName = userName;
this.age = age;
this.demo2 = demo2;
}

@Override
public String toString() {
return "Demo1{" +
"userName='" + userName + '\'' +
", age=" + age +
", s1=" + s1 +
", ageCone=" + ageCone +
", demo2=" + demo2 +
'}';
}

@Override
protected Object clone() throws CloneNotSupportedException {
Demo1 demo1 = (Demo1) super.clone();
Demo2 demo2 = (Demo2) demo1.getDemo2().clone();
demo1.setDemo2(demo2);
return demo1;
}
}
@Data
class Demo2 implements Cloneable{
private String demo2Username;
private Integer demo2Age;

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}

public Demo2(String demo2Username, Integer demo2Age) {
this.demo2Username = demo2Username;
this.demo2Age = demo2Age;
}

}



  实现深拷贝方式二: 使用序列化和反序列化方式达到深拷贝

【秋招冲刺-每日打卡】应届生JAVA岗-每日5道高频面试题【Day4】-基础篇(4)_每日打卡_07


public class CloneDemo {
public static void main(String[] args) throws Exception{
Demo1 demo1 = new Demo1("1",2,new Demo2("3",4));
System.out.println("原来的对象数据:" + demo1);
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(demo1);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
// 深拷贝
Demo1 cloneDemo = (Demo1) ois.readObject();
System.out.println("拷贝出来的对象数据:" + cloneDemo);

// 修改拷贝对象的属性
cloneDemo.setAge(100);
cloneDemo.setAgeCone(100);
cloneDemo.setUserName("test");
cloneDemo.setS1(Short.valueOf("200"));
cloneDemo.getDemo2().setDemo2Age(1000);
System.out.println();
System.out.println("修改拷贝出来的对象引用类型属性:被拷贝对象的数据" + demo1);
System.out.println("修改拷贝出来的对象引用类型属性:拷贝出来的对象数据" + cloneDemo);
}
}


@Data
class Demo1 implements Cloneable, Serializable {
private String userName;
private Integer age;
private Short s1 = new Short("100");
private Integer ageCone = new Integer(900);
private Demo2 demo2;

public Demo1(String userName, Integer age, Demo2 demo2) {
this.userName = userName;
this.age = age;
this.demo2 = demo2;
}

@Override
public String toString() {
return "Demo1{" +
"userName='" + userName + '\'' +
", age=" + age +
", s1=" + s1 +
", ageCone=" + ageCone +
", demo2=" + demo2 +
'}';
}

@Override
protected Object clone() throws CloneNotSupportedException {
Demo1 demo1 = (Demo1) super.clone();
Demo2 demo2 = (Demo2) demo1.getDemo2().clone();
demo1.setDemo2(demo2);
return demo1;
}
}
@Data
class Demo2 implements Cloneable,Serializable{
private String demo2Username;
private Integer demo2Age;

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}

public Demo2(String demo2Username, Integer demo2Age) {
this.demo2Username = demo2Username;
this.demo2Age = demo2Age;
}

}



二: throw和throws的区别?



  throw关键字用于主动抛出java.lang.Throwable类的一个实例化对象,当某些业务可能存在异常,但是你并不想在此处处理这个异常,可以使用throw关键字将异常抛出。如:throw new Exception(“not need to deal″)。


  throws 的作用是作为方法声明和签名的一部分(放在方法声明处),可以接受多个异常,用逗号隔开,这个方法的调用者需要处理抛出的异常或者继续使用throws将异常网上抛出,最高可抛出到JVM进行处理。Java 中,任何未处理的受检查异常强制在 throws 子句中声明。



【秋招冲刺-每日打卡】应届生JAVA岗-每日5道高频面试题【Day4】-基础篇(4)_新星计划_08



三: 受检查异常和运行时异常与有何区别?



  受检查异常: 在编译阶段被强制检查的异常称为"受检查的异常",这种异常JAVA编译器要求必须处理,如IO异常


  运行时异常: 在编译阶段无法检测出来的,可能是由于开发者设计考虑不周全而引起的异常,这种异常只能在程序运行时才会发现,所以,处理这种异常要求开发者更加细致和有经验才能更好预测到。



【秋招冲刺-每日打卡】应届生JAVA岗-每日5道高频面试题【Day4】-基础篇(4)_新星计划_09



四: 列举一些工作中你常遇到的运行时异常




  • NullPointerException (空指针异常)
  • IndexOutOfBoundsException (下标越界异常)
  • IllegalArgumentException (非法参数异常)
  • ClassCastException (类转换异常)
  • ArithmeticException(算术异常)


五: SimpleDateFormat是线程安全的吗?如果不是,怎么解决它线程不安全的问题?



  SimpleDateFormat是DateFormat 的一个实现,而DateFormat的实现都是线程不安全的,所以SimpleDateFormat 都不是线程安全的,在多线程环境下,会存在线程安全问题。

  解决: 可以将 SimpleDateFormat 存放在 ThreadLocal 中,因为ThreadLocal是线程变量,每个线程都有一个单独的ThreadLocal ,在多线程环境下也不会出现线程安全问题。

  JDK8推荐使用DateTimeFormatter来代替SimpleDateFormat ,因为它是线程安全的。