关于java异常基础知识/编码经验的一些总结(代码片段)

山河已无恙 山河已无恙     2022-12-27     221

关键词:

写在前面


  • 温习一下毕业以来学习的东西。准备做成一个系列。所以对于每一部分技术点进行一个笔记整理。更多详见 java面试的一些总结
  • 笔记主要是以网上开源的一本《Java核心面试知识整理》面试笔记为原型,结合工作中学习的知识。《Effective Java》《编写高质量代码(改善Java程序的151个建议)》这两本书为方向进行整理。
  • 笔记立足DevOps。开发+运维+测试三个方向 ,面向对JAVA有一定了解的小伙伴用于温习,因为理论较多一点在不断更新,博文内容理解不足之处请小伙伴留言指正

傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。--------王小波


一、JAVA 异常分类及处理

异常概述

程序中的错误可分为语法逻辑运行时错误。程序在运行时的错误被称为异常

白话讲解:如果已知某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。

Throwable

ThrowableJava语言中所有错误或异常的超类。下一层分为 ErrorException

如果面试被问到异常是如何定位到的异常位置的,你会怎样回答?:

JVM在创建一个Throwable类及其子类时会把当前线程的栈信息记录下来,以便在输出异常时准确定位异常原因,在出现异常时,JVM会通过fillInStackTrace(填写执行堆栈跟踪。)方法记录栈帧的信息,然后生成一个Throwable对象,可以知道类间的调用顺序方法名称当前行号


简单看一下源码

 ......
 * @since JDK1.0
 */
public class Throwable implements Serializable 
   /**
     * A shared value for an empty stack.
     */
    //出现异常的栈记录
    private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
    ......
    private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
    ......
     //构造函数记录栈帧
     public Throwable() 
        fillInStackTrace();
    
    .....
    //调用本地方法,抓取执行时的栈信息。
    public synchronized Throwable fillInStackTrace() 
        if (stackTrace != null ||
            backtrace != null /* Out of protocol state */ ) 
            fillInStackTrace(0);
            stackTrace = UNASSIGNED_STACK;
        
        return this;
    
   //本地方法.抓取执行时的栈信息。
    private native Throwable fillInStackTrace(int dummy);
    ......
    

Error

Error 类是指java 运行时系统的内部错误资源耗尽错误。应用程序不会抛出该类对象。如果出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。Error中面试常考OOM类型的问题, 内存溢出、泄露之类。

如果面试被问起来OOM,你会怎样简单描述下?:

所谓OOM,即 java.lang.OutOfMemoryError:翻译成中文就内存用完了,当JVM因为没有足够的内存来为对象分配空间,并且 垃圾回收器 也已经 没有空间可回收时,就会抛出这个error。我知道的,造成OOM的有两种,内存溢出GC占用大量时间释放很小空间(GC overhead limit exceeded)。

  • 内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出,可选的解决办法是调整JVM参数,造成溢出原因也可能是因为内存泄漏,所谓内存泄漏,即申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
  • GC占用大量时间释放很小空间JVM花费了98%的时间进行垃圾回收,而只得到2%可用的内存频繁的进行内存回收(最起码已经进行了5次连续的垃圾回收),JVM就会曝出java.lang.OutOfMemoryError: GC overhead limit exceeded错误。即超出了GC开销限制。是JDK6新添的错误类型。是发生在GC占用大量时间为释放很小空间的时候发生的,是一种保护机制。一般是因为堆太小,导致异常的原因:没有足够的内存。

如果面试官问OOM的发生在JVM那些内存区域:要怎么回答?:

OOM会发生在java虚拟机栈本地方法栈java 堆,以及运行时常量池中。

  • 对于java虚拟机栈来讲,当线程的请求栈深度大与虚拟机所允许的深度的时候。将抛出StackOverflowError异常。如果虚拟机栈可以动态扩展,但扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常.
  • 本地方法栈与虚拟机栈类似,区别是虚拟机栈为虚拟机执行的Java方法服务,而本地方法栈则是为虚拟机使用到的Native方法服务。也会抛出StackOverflowError异常,OutOfMemoryError异常。
  • 故所周知,java堆是虚拟机所管理的内存最大的一块,是被所有线程共享的一块内存区域。几乎所有的实例以及数组都要在堆上分配空间。同时,java堆也是GC的主要区域,也被称为GC堆,如果在堆中没有内存完成实例分配,并且堆中也无法扩展,会抛出OutOfMenory异常。
  • 运行时常量池,是方法区的一部分,当常量池无法在申请内存时会抛出OutOfMethodError异常。java8之后改变了永久代的位置,存放到了直接内存,所以这部分的溢出算到直接内存溢出。

关于更多这方面的知识推荐小伙伴去看《深入理解Java虚拟机》这本书。关于上面讲的溢出实例代码,小伙伴可移步到博客java中OOM错误浅析(没有深入太多,一些粗浅的知识,可以温习用)

Exception

Exception又有两个分支,一个是运行时异常RuntimeException ,一个是检查异常CheckedException,如 I/O 错误导致的 IOExceptionSQLException

如果面试官问受检异常和非受检异常的区别的时候,要怎么回答?

  • 非检查型异常RuntimeException 如 :NullPointerExceptionClassCastExceptionRuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。 如果出现RuntimeException,那么一定是程序员的错误.

  • 检查异常 CheckedException:一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch,该类异常一般包括几个方面:

    1. 试图在文件尾部读取数据
    2. 试图打开一个错误格式的 URL
    3. 试图根据给定的字符串查找 class 对象,而这个字符串表示的类并不存在

异常的处理方式

捕获异常

使用try……catch捕获异常。

将可能出现的异常的代码放到try语句中进行相应的隔离,如果遇到异常,程序会停止执行try块的代码,自动生成一个异常对象,该异常对象被提交给Java运行时环境(抛出异常),Java运行时环境收到异常对象时,跳到catch块中进行处理(即与catch中的异常对象一一匹配),匹配成功执行相应的catch块语句(捕获异常)。

如果面试官问异常捕获有几种方式,要怎么回答?

多异常单独捕获

try
    //可能出现异常的代码
	catch(异常类 异常对象)
	//该异常类的处理代码
	catch(异常类 异常对象)
	
	// 可以有多个catch语句。
	// 在执行多catch语句时,当异常对象被其中的一个catch语句捕获时, 剩下的catch语句将不会进行匹配。
	// 在安排顺序时,首先应该捕获一些子类异常,然后在捕获父类异常。即父类异常必须放到子类的后面,同一级不影响。

多异常统一捕获

try
	//业务实现代码(可能发生异常)
	catch(异常类1[|异常类2|异常类3……] 异常对象)
	//多异常捕获处理代码
	
	//多异常捕获时,异常变量默认是常量,因此不能对该异常变量重新赋值。

所有异常对象都包含一下几个常用的方法用于访问异常信息

  • getMessage() 返回该异常的详细描述字符串(类型,性质)
  • printStackTrace() 将该异常的跟踪栈信息输出到标准错误输出
  • printStackTrace(PrintStream s) 将该异常的跟踪栈信息出现在程序中的位置输出到指定的输出流
  • getStackTrace() 返回该异常的跟踪栈信息
  • toString() 返回异常发的类型与性质
使用try……catch……finally捕获异常。

防止try中出现异常无法回收物理资源finally块中放回收代码显示回收在try中打开的物理资源。不管try块中是否出现异常,也不管catch块中是否执行return语句,finally总会被执行(不被执行的情况:finally语句发生异常,使用了System.exit(0)语句,线程死亡,关闭cpu)。

如果面试官问异常捕获中 只有try和finally没有catch可以不,是语法错误吗?要怎么回答?

有catch的情况

try
	//可能发生异常的代码
	catch(异常类 异常类对象)
	//异常类的处理代码
	finally
	//资源回收语句
	//try块时必须的,catch块与finally块时可选的,即try……finally或try……catch……finally(顺序不能颠倒);

没有catch的情况
try块时必须的,catch块与finally块时可选的,即try……finally或try……catch……finally(顺序不能颠倒);

try
	//可能发生异常的代码
	finally
	//资源回收语句
	
使用自动关闭资源的try-with-resources语句:

1.7之后可以使用自动关闭的资源的try语句,但是被释放的资源必须实现AutoCloseable接口, AutoCloseable对象的close()方法在退出已在资源规范头中声明对象的try -with-resources块时自动调用。 这种结构确保迅速释放,避免资源耗尽异常和可能发生的错误

如果面试被问到try-with-resources的使用有什么限制么?怎么回答?

前提是必须实现了AutoCloseable接口才可以


如果面试被问到try-with-resources可以有catchfinally 吗,可以只有try吗?怎么回答?

有catch和finally 的情况

 try (Connection connection = DriverManager.getConnection("")) 
            
         catch (SQLException e) 
            e.printStackTrace();
        finally 

        
 	

没有catch块,受检异常需要显示声明

  static String readFirstLineFromFile(String path) throws IOException 

        try (BufferedReader br = new BufferedReader(new FileReader(path))) 
            return br.readLine();
        
    

try() 中()多条语句

 @Override
    public void run() 
        try(
         // 拿到输入流
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        // 拿到输出流
        PrintWriter out = new PrintWriter(socket.getOutputStream())
        )
            while (true)
                String str = in.readLine();
                if (str == null)
                    continue;
                
                System.out.println("接受原始消息"+str);
                // CONSUME表示消费一条消息
                if ("CONSUME".equals(str))
                    String s = Broker.consume();
                    out.println(s);
                    out.flush();
                else 
                    // 其他情况表示生产消息放到消息队列里面
                    Broker.produce(str);
                
            
        catch (Exception e)
            e.printStackTrace();
        
    
嵌套的try...catch语句:

如果面试被问到异常块可以嵌套么?怎么回答?

使用嵌套的try...catch语句,当内部的try块没有遇到匹配的catch块则检查外部的catch块。

	try 
            try 
                
            catch (ArrayIndexOutOfBoundsException e)
                e.printStackTrace();
            
            
        catch (Exception e )
            e.printStackTrace();
        
    public static String fileToBufferedReader(InputStreamPeocess inputStreamPeocess, File file) 
        try (FileInputStream fileInputStream = new FileInputStream(file)) 
            try (InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream)) 
                try (BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) 
                    return inputStreamPeocess.peocess(bufferedReader);
                
            
         catch (IOException e) 
            e.printStackTrace();
         
    

抛出异常:

如果面试官问throw和throws有啥区别?要怎么回答?

throw抛出一个异常对象

thorw:使用throw抛出一个异常对象,当程序出现异常时,系统会自动抛出异常,也可以使用throw语句自动抛出一个异常。throw语句抛出的不是异常类,而是一个异常实例对象,且每次只能抛出一个异常实例对象。

throw new  对象名()
throws声明抛出的异常类

thorws:使用throws声明抛出异常。使用throws声明一个异常序列:当前方法不知道如何处理异常时,该异常应有上一级调用者进行处理,可在定义该方法时使用throws声明抛出异常。语法:

[访问符] <返回类型> 方法名([参数表列]throws 异常类A [,异常类B,异常类C,……]
.

throws只是抛出异常,还需要进行捕获。如果是Error或者RuntimeException或他们的子类,可以不用throws关键字来声明要抛出的异常,编译仍能通过,运行时系统抛出。

  • throws 用来声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方式;throw抛出具体的问题对象,执行到throw,功能就已经结束了,跳转到调用者,并将具体的问题对象抛给调用者。也就是说 throw 语句独立存在时,下面不要定义其他语句,因为执行不到。
  • throws表示出现异常的一种可能性,并不一定会发生这些异常;throw 则是抛出了异常,执行throw一定抛出了某种异常对象
  • 两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

关于异常的一些其他编码经验:

如果面试问关于异常,平常开发中有哪些经验,要怎么回答?

  • 一个方法被覆盖时,覆盖它的方法必须抛出相同异常或异常的子类。如果父类抛出多个异常,则覆盖方法必须抛出那些异常的一个子集,不能抛出新异常。
  • 提倡异常的封装,可以提供系统的友好性,提高系统的可维护性,对异常进行分类,解决Java异常机制缺陷。进行异常封装 。
class MyException extends Exception
    private List<Throwable> causes = new ArrayList<>();
    public MyException(List<? extends Throwable> causes)
        this.causes.add((Throwable) causes);
    
    public List<Throwable> getException()
        return causes;
    
    public static void doStuff() throws MyException 
        List<Throwable> list = new ArrayList<>();
        try
            //异常代码
        catch(Exception e)
            list.add(e);
        
        try
            //异常代码
        catch (Exception e)
            list.add(e);
        
        if (list.size() > 0)
            throw new MyException(list);
    


  • 异常只为异常服务,只针对异常的情况才使用异常,异常应该只用于异常的情况下;它们永远不应该用于正常的控制流。即异常块不添加逻辑,添加逻辑会造成异常判断降低了系统性能,降低了代码可读性,隐藏了运行期可能产生的异常.
  • 多使用异常,把性能放一边,异常是主逻辑的例外逻辑,比如 在马路上走路(主逻辑),突然开过一辆车,我要避让(受检异常,必须处理),继续走路,突然一架飞机从我头顶飞过(非受检异常),我可以选择走路(不捕捉),也可以选择职责其噪音污染(捕捉,主逻辑的补充处理),在继续走下去,突然一棵流星砸下类,这是没有选择,属于错误,不能做任何处理。
  • 不要在构造函数中抛出异常,一个对象的创建要经过内存分配,静态代码初始化,构造函数执行等过程,一般不在构造函数中抛出异常,是程序员无法处理的,会加重上层代码的编写者的负担,后续代码不会执行,违背了里氏替换原则,父类能出现的地方子类就可以出现,而且将父类替换为子类也不会产生任何异常, java的构造函数允许子类的构造函数抛出更广泛异常类(正好与类方法的异常机制相反:子类方法的异常类型必须为父类方法的子类型,覆写要求),当替换时,需要增加catch块,构造函数没有覆写的概念,只有构造函数之间的引用调用而已,同时子类构造函数扩展受限
  • 使用Throwsable获得栈信息AOP编程可以亲松的控制一个方法调用那些类,也能控制那些方法允许别调用,一般来讲切面编程只能控制到方法级别,不能实现代码级别的植入(Weave),即不同类的不同方法参数相同调用相同方法返回不同的值。即要求被调用者具有识别调用者的能力,可以使用Throwable获得栈信息,然后鉴别调用者信息。
package com.liruilong.throwable_demo;

/**
 * @Classname Main
 * @Description TODO
 * @Date 2021/6/22 2:31
 * @Created Li Ruilong
 */
public class Main 
    public static void main(String[] args) 
      Invoker.m1();
      Invoker.m2();
    

class Foo
    public  static boolean m()
        // 堆栈跟踪中的一个元素,由Throwable.getStackTrace()返回。 每个元素表示单个堆栈帧。
        // 堆栈顶部除堆栈之外的所有堆栈都表示方法调用。 堆栈顶部的帧表示生成堆栈跟踪的执行点。
        // 通常,这是创建与堆栈跟踪相对应的throwable的点。
        //取得当前栈信息
        StackTraceElement []  stackTraceElements = new Throwable().getStackTrace();
        //检查是否是m1方法调用
        for(StackTraceElement st: stackTraceElements)
            System.out.println(st);
            if(st.getMethodName().equals("m1"))
                return true;
        
        return false;
        //throw new RuntimeException("除m1方法外,该方法不允许其他方法调用");
    

class  Invoker
    //该方法打印true
    public static void m1()
        System.out.println(Foo.m());
    
    //该方法打印false
    public  static void m2()
        System.out.println(Foo.m());
    


  • API设计中,对可恢复的情况使用受检异常,对编程错误使用运行时异常,如果期望调用者能够适当地恢复,对于这种情况就应该使用受检异常。通过抛出受检的异常,强迫调用者在一个catch子句中处理该异常,或者将它传播出去。用运行时异常来表明编程错误。你实现的所有未受检抛出结构都应该是RuntimeException的子类(直接的或者间接的),
  • 避免不必要地使用受检异常,受检异常可以提升程序的可读性;如果过度使用,将会使API使用起来非常痛苦。如果调用者无法恢复失败,就应该抛出未受检异常。如果可以恢复,并且想要迫使调用者处理异常的条件,首选应该返回一个optional值。当且仅当万一失败时,这些无法提供足够的信息,才应该抛出受检异常。
  • 优先使用标准的异常,重用标准的异常有多个好处:
    • 它使API更易于学习和使用,因为它与程序员已经熟悉的习惯用法一致。最主要的好处。
    • 对于用到这些API的程序而言,·它们的可读性会更好,因为它们不会出现很多程序员不熟悉的异常。
    • 异常类越少,意味着内存占用(footprint)就越小,装载这些类的时间开销也越少。最不重要的一点。

不要直接重用ExceptionRuntimeExceptionThrowable或者Error。对待"这些类要像对待抽象类一样。你无法可靠地测试这些异常,因为它们是一个方法可能抛出的其他异常超类

  • 抛出与抽象对应的异常
  • 每个方法抛出的所有异常都要建立文档,使用Javadoc@throws标签记录下一个方法可能抛出的每个未受检异常,但是不要使用throws关键字将未受检的异常包含在方法的声明中。如果一个类中的许多方法出于同样的原因而抛出同一个异常,在该类的文档注释中对这个异常建立文档,这是可以接受的,而不是为每个方法单独建立文档。
  • 努力使失败保持原子性,作为方法规范的一部分,产生的任何异常都应该让对象保持在调用该方法之前的状态。如果违反这条规则, API文档就应该清楚地指明对象将会处于什么样的状态.
  • 不要忽略异常忽略一个异常很容易,空的catch块会使异常达不到应有的目的.如果选择忽略异常, catch块中应该包含注释,说明为什么可以这么做,并且变量应该命名为ignored:

关于qt数据库相关开发的一些经验总结(代码片段)

一、前言近期花了两个多月时间,将数据库相关的代码重新封装成了各种轮子(这条路必须打通,打通以后,相关项目只需要引入这个组件pri即可),测试了从Qt4.7到Qt6.1的各种版本,测试了odbc、sqlite... 查看详情

c++11常用特性的使用经验总结(代码片段)

...结文章,在编写本博客之前,博主在工作和学习中学到的关于C++11方面的知识,也得益于很多其他网友的总结。本博客文章是在学习的基础上,加上博主在日常工作中的使用C++11的一些总结、经验和感悟,整理出来,分享给大家... 查看详情

总结django一些开发经验(代码片段)

...后端接口开发还是很高效的。特此总结一些Django开发的小经验。先说一些最最基础的吧。使用virtualenv隔离开发环境使用pip管理项目依赖,主要就是一个小技巧,使用pipfreeze>requirements.txt来保存依赖的模块和版本使用gitignore.io这... 查看详情

供应链业务mq应用场景经验总结(代码片段)

...中间件提供的功能大同小异,最基础的推消息、下游异常重试机制应该是都具备的,本文所述也是建立在这样的能力基础上的~场景一:削峰填谷,降低响应时间,下游异常自 查看详情

关于动态规划的一些经验与总结

   动态规划是一个考验技巧性的算法,对于动态规划算法,浅谈几点经验。   首先是设计状态,我们肯定是要有一个一维或多维的状态的,那么如何设计它们的意义呢,首先我们可以分析出题目中的一些... 查看详情

java基础总结三(泛型异常)(代码片段)

文章目录Java基础总结三(泛型、异常)泛型泛型的创建泛型类泛型接口泛型方法类型擦除泛型的协变与逆变异常异常体系异常处理Java基础总结三(泛型、异常)泛型泛型的创建泛型类我们最常用泛型的地方就是... 查看详情

《java程序设计》第六周学习总结(代码片段)

...周错题总结代码托管感想二、第七章知识总结:内部类与异常类内部类:在一个类中定义另一个类特点:外嵌类的成员在内部类仍然有效,内部类也可调用外嵌类的方法,内部类的类体不能声明类变量及类方法,非内部类不能是... 查看详情

java中异常状况总结

之前在《会当凌绝顶》这本书中学到过对于异常处理的知识,当时也是根据书上的代码,自己进行编写大概知道是怎么回事儿,王老师给我们上了一节课之后,发现异常处理可以发挥很大的作用。 通过在网络上搜索资料,对... 查看详情

深入理解pythonpython语法总结:基础知识和对python中对象的理解(代码片段)

...串与编码4.函数5.面向对象编程6.异常处理以及python的模块基础知识当然,在开始之前我们还要简单的回顾一下基本知识(当然仅仅是列出一些要注意的点):1.一切皆对象的思想对象是python中最基本的概念,在python中处理的每个... 查看详情

java面试题总结之java基础(代码片段)

 1、JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗?答:Java通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每个... 查看详情

关于deepin装机的一些经验总结

最近win10用的有一点腻,虚拟机很久之前就装上了deepin,乌班图,红帽等隶属于Linux一些操作系统,但是终究还是虚拟机所以还是想在真机上试试,所以照着deepin网上的流程装好了deepin并且成功开机运行,在这里分享一些经验。1.... 查看详情

关于集合中一些常考的知识点总结(代码片段)

本章主要总结了集合的一些基础但有重点的知识点,例如他们的底层数据结构以及集合之间的区别,其中HashMap最为重点。集合Java的集合框架中可以分为两大类:第一类是按照单个元素存储的Collection集合,其中Set,List,Queue都实... 查看详情

第四周总结报告

    本周看完了有关于Java的基本知识,手动练习了许多代码,看完了慕课网上的Java视频第一季和第二季,经过别人的讲解收获颇多。其中遇到的问题是对Java的封装不是太了解,然后我会在浏览器上查询关于它的一... 查看详情

2017年五月前端面试题目的总结(代码片段)

...伪类请列举出来7.pxemrem的区别?8.link和@import的区别是?9.关于兼容IE8你都经历过哪些坑?10.关于图片方面你有什么样的优化经验?11.关于手机端你遇见过哪些兼容问题?12.关于HTML5CSS3的新特性13.css如何引用外部字体14.关于响应式布... 查看详情

java基础总结三(泛型异常)(代码片段)

文章目录Java基础总结三(泛型、异常)泛型泛型的创建泛型类泛型接口泛型方法类型擦除泛型的协变与逆变异常异常体系异常处理Java基础总结三(泛型、异常)泛型泛型的创建泛型类我们最常用泛型的地方就是... 查看详情

《java程序设计》第七次学习总结(代码片段)

...学c语言时候就知道的用递归的方法来解决阶乘的问题。关于递归,新的收获有:其实一个递归有两部分组成:基础情形:basecase;调用自身的过程。这里的基础情 查看详情

实习总结java学习最佳实践!

...**Java进阶****编码规范****反射****注解****泛型****多线程****异常****集合框架****JVM相关****Class文件结构****类加载过程****GC垃圾回收原理、策略****Java8新特性****Stream****Lambda****Optional类****方法引用****单元 查看详情

java:java虚拟机的一些基本知识点总结回顾

...种内在情况,在大型项目中排查问题还是蛮重要的。现在关于虚拟机这块儿,真正想招初级Java的中小型公司,反而不会问你这些,他们会问你Java基础(真的是要注重基础,别整什么分布式微服务,高并发优化啥的,把基础答好... 查看详情