【面试题】Java基础篇-常见面试题总结p3

备战实习,会定期的总结常考的面试题,大家一起加油! 🎯

往期文章:

参考文章:

  • https://blog.csdn.net/qq_45966440/category_10889559.html
  • https://blog.csdn.net/weixin_43591980/category_10638797.html?spm=1001.2014.3001.5515
  • https://javaguide.cn/java/basis/java%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E6%80%BB%E7%BB%93/
  • https://pdai.tech/

注意:

如果本文中有错误的地方,欢迎评论区指正!🍭

1.说一下try-catch-finally各个部分的作用?

  • try块: 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally

  • catch块: 用于处理 try 捕获到的异常

  • finally 块: 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行

👨‍💻面试官又问:finally是不是一定会被执行到?

不一定的。在在以下 3 种特殊情况下,finally 块不会被执行:

  1. tryfinally块中用了 System.exit(int)退出程序。但是,如果 System.exit(int) 在异常语句之后,finally 还是会被执行
  2. 程序所在的线程死亡
  3. 关闭 CPU

👨‍💻面试官继续追问:ftry-catch-finally中那个部分可以省略?

catch可以省略

try 只适合处理运行时异常,try+catch 适合处理运行时异常+普通异常

也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略

2.说说Error 和 Exception 的区别?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7qu6dIKI-1640303890477)(【面试题】Java基础篇-常见面试题总结p3.assets/Java异常类层次结构图.png)]

Error类和Exception类的父类都是Throwable类。主要区别如下︰

  • Error类

    表示JVM 无法处理的错误,我们没办法通过 catch 来进行捕获 。例如,Java 虚拟机运行错误虚拟机内存不够错误类定义错误等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止

  • Exception类

    程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 受检查异常(必须处理) 和 不受检查异常(可以不处理)

👨‍💻面试官追问:受检查异常和不受检查异常有什么不同?

  • 受检查异常

    Java 代码在编译过程中,如果受检查异常没有被 catch/throw 处理的话,就没办法通过编译 。

    除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、ClassNotFoundExceptionSQLException

  • 不受检查异常

    Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。

    RuntimeException 及其子类都统称为非受检查异常,例如:NullPointerExceptionNumberFormatException(字符串转换为数字)、ArrayIndexOutOfBoundsException(数组越界)、ClassCastException(类型转换错误)、ArithmeticException(算术错误)…

👨‍💻面试官继续追问:说一下你了解的常见异常类有哪些?

  • NullPointerException:当应用程序试图访问空对象时,则抛出该异常。
  • SQLException:提供关于数据库访问错误或其他错误信息的异常。
  • IndexOutOfBoundsException︰指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
  • FileNotFoundException︰当试图打开指定路径名表示的文件失败时,抛出此异常
  • IOException:当发生某种/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。
  • ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常
  • IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。

3.说说运行时异常和非运行时异常有什么区别?

  • 运行时异常

    都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

    运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

  • 非运行时异常

    也叫编译异常。是RuntimeException以外的异常,类型上都属于Exception类及其子类

    从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常

4.说一下throw 和 throws 的区别?

  • throw

    异常的抛出,在方法体内部,表示抛出异常,由方法体内部的语句处理;throw是具体向外抛出异常的动作,所以它抛出的是一个异常实例

    public static double method(int value) {
        if(value == 0) {
            throw new ArithmeticException("参数不能为0"); //抛出一个运行时异常
        }
        return 5.0 / value;
    }
    
  • throws

    异常的申明,在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理;表示出现异常的可能性,并不一定会发生这种异常

    public static void method() throws IOException, FileNotFoundException{
        //something statements
    }
    

5.知道try-with-resources吗?

如果你的资源实现了 AutoCloseable 接口,你可以使用这个语法。大多数的 Java 标准资源都继承了这个接口。当你在 try 子句中打开资源,资源会在 try 代码块执行后或异常处理后自动关闭。任何 catch 或 finally 块在声明的资源关闭后运行

Java 中类似于InputStreamOutputStreamScannerPrintWriter等的资源都需要我们调用close()方法来手动关闭,一般情况下我们都是通过try-catch-finally语句来实现这个需求,如下:

//读取文本文件的内容
Scanner scanner = null;
try {
    scanner = new Scanner(new File("D://read.txt"));
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    if (scanner != null) {
        scanner.close();
    }
}

使用 Java 7 之后的 try-with-resources 语句改造上面的代码:

try (Scanner scanner = new Scanner(new File("test.txt"))) {
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException fnfe) {
    fnfe.printStackTrace();
}

通过使用分号分隔,可以在try-with-resources块中声明多个资源:

try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
     BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
    int b;
    while ((b = bin.read()) != -1) {
        bout.write(b);
    }
}
catch (IOException e) {
    e.printStackTrace();
}

6.说说你知道的Throwable 类常用方法?

  • public String getMessage():返回异常发生时的简要描述
  • public String toString():返回异常发生时的详细信息
  • public String getLocalizedMessage():返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
  • public void printStackTrace():在控制台上打印 Throwable 对象封装的异常信息

栗子:

/**
 * @author xppll
 * @date 2021/11/30 09:08
 */
public class testThrowable {
    public static void main(String[] args) {
        try {
            int a = 10 / 0;
        } catch (Exception e) {
            System.out.println("getMessage....."+e.getMessage());
            System.out.println("toString....."+e.toString());
            System.out.println("getLocalizedMessage....."+e.getLocalizedMessage());
            System.out.print("printStackTrace.....");
            e.printStackTrace();
        }
    }
}

输出:

getMessage...../ by zero
toString.....java.lang.ArithmeticException: / by zero
getLocalizedMessage...../ by zero
printStackTrace.....java.lang.ArithmeticException: / by zero
	at com.atguigu.mybatisplus.testThrowable.main(testThrowable.java:10)

7.说说什么是序列化?什么是反序列化?

如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。

简单来说:

  • 序列化:将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

总的来说序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Nvdaodn-1640303890481)(【面试题】Java基础篇-常见面试题总结p3.assets/a478c74d-2c48-40ae-9374-87aacf05188c.png)]

👨‍💻面试官追问:在Java序列化中如果有些字段不想进行序列化,怎么办?

对于不想进行序列化的变量,使用 transient 关键字修饰。它可以阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值也不会被持久化和恢复

例子:

创建实现Serializable接口的Person类:

/**
 * @author xppll
 * @date 2021/12/23 22:44
 */
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Person implements Serializable {
    private String name;
    private  transient  Integer age;
}

测试类:

/**
 * @author xppll
 * @date 2021/12/23 22:32
 */
public class test {
    public static void main(String[] args) {
        File file = new File("D://out.txt");
        //序列化对象
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(file)
        )) {
            Person person = new Person("dad", 13);
            oos.writeObject(person);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eVaajKDz-1640303890484)(【面试题】Java基础篇-常见面试题总结p3.assets/image-20211223231546852.png)]

可以看出没有age属性

👨‍💻面试官继续追问:关于transient的使用有什么需要注意的?

  • transient 只能修饰变量,不能修饰类和方法。
  • transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是 0
  • static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化

紧接上个测试案例,反序列化:

//反序列化
try (ObjectInputStream oos = new ObjectInputStream(
        new FileInputStream(file)
)) {
    Person res = (Person) oos.readObject();
    System.out.println(res
    );
} catch (IOException e) {
    e.printStackTrace();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

控制台输出:

可以看出transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值

8.说一下Java 中 IO 流分为几种?

  • 按照流的流向分,可以分为输入流输出流
  • 按照操作单元划分,可以划分为字节流字符流
  • 按照流的角色划分为节点流处理流

Java IO 流共涉及 40 多个类,这40 多个类都是从如下 4 个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

按操作方式分类结构图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rzdhNbiY-1640303890486)(【面试题】Java基础篇-常见面试题总结p3.assets/IO-操作方式分类.png)]

按操作对象分类结构图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IcBE5toh-1640303890488)(【面试题】Java基础篇-常见面试题总结p3.assets/IO-操作对象分类.png)]

👨‍💻面试官追问:既然有了字节流,为什么还要有字符流??

字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好

9.请你说一说BIO、NIO、AIO 有什么区别?

在对比这三者的区别之前,先了解一下什么是同步/异步、阻塞/非阻塞:

  • 同步:一个任务的完成之前不能做其他操作,必须等待(相当于在打电话)
  • 异步:一个任务的完成之前,可以进行其他操作(相当于在聊QQ)
  • 阻塞:是相对于CPU来说的, 挂起当前线程,不能做其他操作只能等待
  • 非阻塞:无须挂起当前线程,也可以去执行其他操作
  • BIO:Block IO 同步阻塞,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。每当有一个客户端向服务器发起请求时,服务器都要启动一个线程,无论客户端是否响应,线程都必须一直等待。如图所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L0DICxp1-1640303890490)(【面试题】Java基础篇-常见面试题总结p3.assets/20210205221539278.png)]

  • NIO:New IO 同步非阻塞,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。服务器用一个线程来处理多个请求,客户端发送的请求会注册到多路复用器(selector选择器)上,有I/O请求的客户端分配线程处理,如图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v6RjOLyg-1640303890493)(【面试题】Java基础篇-常见面试题总结p3.assets/2021020522291755.png)]

  • AIO:Asynchronous IO 异步非阻塞,是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的 操作。

10.Java中的深拷贝和浅拷贝有了解过吗?

  • 浅拷贝

    对于基础数据类型:直接复制数据值;对于引用数据类型:只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变。

  • 深拷贝

    对于基础数据类型:直接复制数据值;对于引用数据类型:开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。

下面举个详细的例子:

浅拷贝

/**
 * @author xppll
 * @date 2021/12/21 12:27
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address implements Cloneable{
    private String name;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
/**
 * @author xppll
 * @date 2021/12/21 12:29
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person implements Cloneable{
    private Address address;

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

测试:

/**
 * @author xppll
 * @date 2021/12/21 12:30
 */
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person(new Address("长沙"));
        Person person2 = (Person) person1.clone();
        System.out.println(person1.getAddress()==person2.getAddress());//true
    }
}

从结果可以看出, person1 的克隆对象和 person1 使用的仍然是同一个 Address 对象。

深拷贝

这里我们简单对 Person 类的 clone() 方法进行修改,连带着要把 Person 对象内部的 Address 对象一起复制。

/**
 * @author xppll
 * @date 2021/12/21 12:29
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person implements Cloneable {
    private Address address;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person person = (Person) super.clone();
        person.setAddress((Address) person.getAddress().clone());
        return person;
    }
}

再次测试:

/**
 * @author xppll
 * @date 2021/12/21 12:30
 */
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person(new Address("长沙"));
        Person person2 = (Person) person1.clone();
        System.out.println(person1.getAddress()==person2.getAddress());//false
    }
}

在这里插入图片描述
最后喜欢的小伙伴,记得三连哦!😏🍭😘

  • 32
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 33
    评论
java面试题真的很多,下面我来回答一个有关多线程的问题。 在Java中实现多线程有两种方式,一种是继承Thread类,另一种是实现Runnable接口。这两种方式有何区别? 继承Thread类的方式是直接定义一个类继承Thread,并重写它的run()方法。然后创建该类的对象,并调用对象的start()方法来启动线程。这种方式简单直接,但因为Java是单继承的,所以如果某个类已经继承了其他类,就不能再直接继承Thread类实现多线程。 实现Runnable接口的方式是定义一个类实现Runnable接口,并实现其唯一的抽象方法run()。然后创建Thread类的对象,将实现了Runnable的对象作为参数传递给Thread类的构造方法。最后调用Thread对象的start()方法来启动线程。这种方式灵活性更大,因为Java允许一个类实现多个接口,所以即使某个类已经继承了其他类,仍然可以通过实现Runnable接口来实现多线程。 另一个区别在于资源共享的问题。继承Thread类的方式,不管是数据还是方法,都是线程自己拥有的,不存在共享的情况。而实现Runnable接口的方式,多个线程可以共享同一个对象的数据和方法,因为多个线程共同操作的是同一个Runnable对象。 总结来说,继承Thread类方式简单直接,但只能通过单继承来实现多线程;实现Runnable接口方式更灵活,可以解决单继承的限制,并且多个线程可以共享同一个Runnable对象的数据和方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ΘLLΘ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值