【面试高高手】—— Java进阶

文章目录

    • 1. 什么是异常?
    • 2.你是如何理解Java中的异常体系的 ?
    • 3.Error和Exception的区别是什么 ?
    • 4.throw 和 throws的区别是什么 ?
    • 5.Java中的常见异常有哪些 ?
    • 6. 说说你对内存可见性的理解?
    • 7.说下你对volatile关键字的理解?
    • 8.说下Java8有哪些新特性?
    • 9.请你说一下Java中Sync和lock的区别。
    • 10.请说一下Java的线程中sleep和wait的区别?

1. 什么是异常?

程序中的错误统称为异常。

2.你是如何理解Java中的异常体系的 ?

  • Throwable是所有异常类的父类;
    • Error 程序不可处理,如内存溢出,JVM异常
    • Exception 程序可处理。
    • 可查异常:最典型的是IO类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
    • 不可检查异常:
      • 运行时异常 使用try…catch捕获
      • 非运行时异常 编译不通过。

3.Error和Exception的区别是什么 ?

  1. Error类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA应用程序也不应对这类错误进行捕获,一旦这类错误发生,应用程序通常会被终止,仅靠应用程序本身无法恢复;
  2. Exception类型的异常是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。

4.throw 和 throws的区别是什么 ?

  • throw关键字用来抛出方法或者代码块中的异常对象,检查异常和非检查异常都可以被抛出,在方法内部使用;
  • throws关键字用来标识该方法可以抛出的异常类型列表,在方法定义时,在参数列表之后和方法体之前进行定义。

5.Java中的常见异常有哪些 ?

  1. NullPointerException 空指针异常:调用未实例化的null引用,引发该异常;
  2. ClassNotFoundException 找不到类异常:按照类的完全限定名加载一个不存在的类(如反射时),会引发该异常;
  3. NumberFormatException 数字格式化异常:将字符串转换为数字时,如果该字符串中包含非数字内容时,会引发该异常;
  4. IndexOutOfBoundsException 下标越界异常:数组或字符串下标超出范围时,会引发该异常;
  5. IllegalArgumentException 不合法参数异常:传入参数不合法,引发该异常;
  6. ClassCastException 类型转换异常:转换不符合的Class类型,引发该异常;
  7. SQLException SQL异常:在操作数据库时,产生SQL语法错误时,会引发该异常;
  8. IOException 读写异常:对文件流进行IO读写操作发生错误时,会引发该异常;
  9. NoSuchMethodException 方法不存在异常:找不到调用方法,引发该异常。

6. 说说你对内存可见性的理解?

可以举例说明,一个公共变量a,三个线程,一个线程修改了a的值,其他两个线程可能看不到a变化后的值,这就是内存可见性问题。
原因是:为了提高处理速度,每个线程都会在 CPU 中维护一份共享变量的本地缓存,而线程对共享变量的所有操作都会在自己的本地缓存中进行。如果线程 A 更改了一个共享变量,线程 B 有可能看不到线程 A 的修改
解决方法:

  • 使用volatile关键字:
    将变量声明为volatile,这会告诉Java虚拟机确保所有线程都能看到最新的值。
    当一个线程修改了volatile变量的值,这个变化会立即被写入主内存,并且其他线程在读取该变量时会从主内存中获取最新值。

  • 使用synchronized关键字:
    使用synchronized块来对读写操作进行同步,确保同一时间只有一个线程能够访问共享变量。
    当一个线程获取了锁并修改了共享变量后,其他线程必须等待该线程释放锁才能访问该变量,这样可以确保可见性。

  • 使用java.util.concurrent包中的工具类:
    Java提供了一些并发工具类,如AtomicInteger、CountDownLatch、CyclicBarrier等,它们可以用来处理多线程可见性问题,而无需手动编写同步代码。
    这些工具类提供了原子操作和同步机制,可以确保对共享变量的修改对其他线程可见。

7.说下你对volatile关键字的理解?

volatile 是 Java 中的关键字,用于修饰变量。它的主要作用是确保多线程环境下的可见性和有序性,这意味着当一个线程修改了 volatile 变量的值时,其他线程可以立即看到这个修改。
volatile关键字的作用:

  • 可见性(Visibility): 在多线程环境下,当一个线程修改了 volatile 变量的值,这个变化对其他线程是可见的。这意味着当一个线程修改了 volatile 变量后,其他线程不会读取到过期的缓存值,而是能够看到最新的值。
  • 禁止指令重排序(Ordering): volatile 关键字还可以防止编译器和处理器对指令进行重排序。这确保了 volatile 变量的读写操作按照代码的顺序执行,而不会出现意外的指令重排。
  • 不保证原子性(Atomicity): volatile 关键字仅确保可见性和有序性,但不保证原子性。如果多个线程同时对同一个 volatile 变量进行写操作,可能会出现竞态条件。对于需要原子性操作的场景,应该使用 synchronized 或 java.util.concurrent 中的原子类。

适用场景: volatile 适用于一些简单的标志位或状态标识的操作,例如线程之间的信号通知。它不适合复杂的操作,如累加操作。

8.说下Java8有哪些新特性?

(1)接口的默认方法和静态方法:之前接口只能够做方法的声明,没有实现,Java8以后允许接口有一个默认的实现,必须使用default修饰符标记;

default void test(){}
static void test2(){}
  • 作用:
    • 向已有接口添加新方法:默认方法允许在已有的接口中添加新方法,而不会破坏已经实现了该接口的类。在Java 8之前,如果要向接口中添加新方法,所有实现该接口的类都必须提供该方法的实现,这可能导致破坏现有代码。
    • 接口的扩展性:默认方法提高了接口的扩展性。新的方法可以添加到接口中,而不会打破已有的实现类。这对于面向接口的编程非常有用,因为它允许接口逐渐演进而不影响已有代码。
    • Lambda 表达式和函数式编程:默认方法的引入与Lambda表达式一起,使接口更容易用于函数式编程。例如,Java标准库中的java.util.function包中的函数式接口使用了默认方法,这使得在使用Lambda表达式时,可以只实现一个或少数几个抽象方法。

(2)Lambda表达式:Lambda最直观的是将代码变得整洁。

     

(3)函数式接口:

  • Comparetor
  • Consumer
  • predicate(断言式接口)
  • Supplier
  • Function(功能型接口)

(4)方法引用:是用来直接访问类或者实例中的方法或者构造方法,这样代码的可读性会更高一些。
就是使用::来调用类中的方法:

在这里插入代码片

(5)Stream流:它允许你以声明式的方式处理数据集合。

  • 特点:
    • 内部迭代:
    • 只能遍历一次:当流遍历完成后,这个流就被消费掉了。
    • 可以并行处理: select.stream().parallel()

(6)Optional:为了解决空指针异常。并且让代码更加简洁,使用它我们不需要显式的进行空指针检测。
Optional + lambda实现比较字符串,并找到最长的字符串

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class OptionalLambdaExample {

    public static void main(String[] args) {
        List<String> stringList = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");

        Optional<String> longestString = stringList.stream()
                .reduce((s1, s2) -> s1.length() > s2.length() ? s1 : s2);

        longestString.ifPresent(s -> System.out.println("最长的字符串是: " + s));
    }
}

(7)Date/Time
(8)重复注解
(9)扩展注解的支持
(10)Base64
(11)JavaFx

9.请你说一下Java中Sync和lock的区别。

  • 实现方式:

synchronized 是Java的关键字,直接内置在Java语言中。您可以使用synchronized关键字来实现同步块或同步方法。
Lock 是一个接口,它在java.util.concurrent.locks包中定义。Java提供了多种Lock的实现,包括ReentrantLock、ReadWriteLock等。您可以使用这些Lock实现来管理同步。

  • 灵活性:

Lock 提供了更多的灵活性。您可以使用Lock接口的不同实现来满足特定的同步需求。例如,ReentrantLock支持可重入锁,而ReadWriteLock支持读写锁。
synchronized更简单,但在某些情况下可能不够灵活,例如无法轻松实现尝试锁定、定时锁定等功能。

  • 锁定粒度:

synchronized关键字用于锁定整个方法或代码块,这可能会导致性能问题,特别是在高并发情况下。
Lock允许您更细粒度地控制锁的范围,可以只锁定需要同步的关键部分,从而提高并发性能。

  • 异常处理:

使用synchronized关键字时,如果发生异常,锁将自动释放。
使用Lock时,您需要在try-finally块中手动释放锁,以确保在发生异常时锁定资源得到释放。

  • 条件等待:

Lock接口提供了Condition对象,可以用于实现条件等待和通知机制。这使得线程能够更灵活地等待某些条件的发生,而不需要一直忙等。
总之,synchronized适合简单的同步需求,而Lock适用于更复杂、灵活的同步需求。选择哪种同步方式取决于您的具体需求和性能考虑。在Java并发编程中,通常建议优先使用Lock接口,因为它提供了更多的控制和灵活性。但要注意,Lock使用起来相对复杂,需要小心处理异常和锁定资源的释放。

10.请说一下Java的线程中sleep和wait的区别?

sleep() 用于线程的暂时休眠,不释放锁,通常用于时间延迟。
wait() 用于线程之间的等待和唤醒机制,会释放锁,必须在synchronized块中使用。
sleep()是Thread类的方法,而wait()是Object类的方法。

你可能感兴趣的:(面试,面试,java,python)