Interview 1#java basics

1. Generics

1. What is a generic?

Generics are a new syntactic sugar added in Java5. This technology can mention errors that occur during compilation to runtime, greatly improving coding efficiency.

By default, the type elements added to the collection are treated as Object types. When the program removes the object from the collection, it needs to perform forced type conversion. This forced type conversion not only makes the code bloated, but also easily causes ClassCastExeception exceptions. However, there is no problem with this exception during compilation, and the compiler will pass. But an exception is thrown during runtime. By introducing the syntax of generics, the type of added elements is restricted during compilation and the occurrence of type conversion is avoided.

2. What is the difference between the generic wildcard <?> and the type parameter variable Class <T>{}?
  • The wildcard <?> is a type argument rather than a type parameter

  • List<?> is logically the parent class of List and List<concrete type parameter>, and its use is more flexible than the type parameter T.

  • The incoming wildcard usually performs many operations that have nothing to do with the specific type. If it involves operations related to specific types and return values, you still need to use the generic method T.

  //虽然Object是所有类的基类,但是List<Object>在逻辑上与List<Integer>等并没有继承关系,
  //这个方法只能传入List<Object>类型的数据 
   public static void showOList(List<Object> list){
    
    
        System.out.println(list.size());
    }
    //同理,这个方法只能传入List<Number>类型的数据,并不能传入List<Integer>
    public static void showList(List<Number> list){
    
    
        System.out.println(list.size());
    }
    //使用通配符,List<?>在逻辑上是所有List<Number>,List<Integer>,List<String>……的父类,
    //可以传递所有List类型的数据,但是不能在List<?>类型的数据进行于具体类型相关的操作,如add
    public static void showList2(List<?> list){
    
    
        System.out.println("<?>");
        System.out.println(list.size());
    }
    //设置通配符上限,可以传入List<Number及Number的子类>
    public static void showNumList(List<? extends Number> list){
    
    
        System.out.println(list.size());
    }
   //设置通配符下限,List<? super Number>只可以传入List<Number及其父类>
    public static boolean Compare(List<? super Number> list1,List<? super Integer> list2){
    
    
        return list1.size()>list2.size();
    }
3. Do you understand generic erasure? Why type erasure? Side effects of type erasure?

Generic erasure:

Java's parameterized type information only exists during the compilation phase. During the compilation process, after the javac compiler correctly checks the generic result, it will erase the relevant information of the generic, and when the object enters and leavesmethodAdd type checking and type conversion methods at the boundary, that is to say, generic information will not enter the runtime.

Why you need generic erasure:

For backward compatibility, generics are a syntax that appeared in Java 5. Before that, there were no generics. The main purpose of introducing generics at that time was to solve the problem of always having to manually force transformation when traversing a collection. Elevate exceptions that are easily caused during runtime to compile time. In order to keep the JVM backward compatible, the last resort is type erasure.

Consequences of type erasure:

  • There is still a risk that ClassCastException may occur. Because variables of generic types and variables of primitive types can be assigned to each other and no compilation error will be reported, it is easy to cause ClassCastException.
    public static void main(String[] args) {
    
    

        List<Integer> integerList = new ArrayList<>(); //带泛型类型的变量
        integerList.add(1);
        List rawList = integerList; //rawList  原始类型的变量
        List<String> stringList = new ArrayList<>(); //带泛型类型的变量
        stringList = rawList; //编译时只会警告,但通过
        System.out.println(stringList.get(0)); //运行时java.lang.ClassCastException

    }
  • Method signatures may conflict
   // 编译后后T被还原为原始的Object类型。编译后的方法可以看做和下面的方法一样。
   // 注意泛型擦除的疑惑点,这里编译后方法签名信息还是会保留到class文件中的,方法体中的语句与泛型相关的都被擦除。在
   // 编译期间自动添加强制类型转化。
    public boolean test(T t) {
    
    
        return true;
    }

    public boolean test(Object object) {
    
    
        return true;
    }
  • Generic argument types do not support basic data types: T in the original type will be replaced by Object after generic erasure, and Object cannot store basic data types. So in order to solve this problem, wrapper classes appeared.
  • You cannot use instanceof on generic arguments (except with wildcards)

        // instanceof 存在继承实现关系即true。

        List<String> list = new ArrayList<>();

        System.out.println(list instanceof List<?>); //true

        System.out.println(list instanceof List<String>); // 编译报错,擦除后String丢失
  • Static methods | Generic parameters cannot be used in static classes: Generic parameters in a generic class are specified when the object is created, but static ones do not need to create objects, so generics cannot be used.
  • Cannot create generic instance: erasure exists, type undefined.
    /**
     * 无法为泛型创建实例(反射可以)
     * */
    public static <E> void test(List<E> eList, Class<E> eClass) {
    
    
        // E e = new E(); // 编译报错,信息擦除。
        try {
    
    
            // 编译通过
            E e = eClass.newInstance(); // Class<E> eClass 泛型作为了方法的参数,作为了元数据存在。可以使用这个信息。
            eList.add(e);
        } catch (InstantiationException | IllegalAccessException e1) {
    
    
            e1.printStackTrace();
        }
    }
  • No generic array

reference

4. Since erasure exists, why can we still use reflection to obtain specific generic types at runtime?

In fact, everything except structured information is erased - here structured information refers to information related to the class structure, that is, metadata related to the class and its fields and method type parameters are retained. , which can be obtained through reflection.


    public static <E> void test(List<E> eList, Class<E> eClass) {
    
     // 如方法签名相关信息都是元数据

        // 如下:涉及到泛型的代码会被擦除为原始类型
        List<String> list = new ArrayList<>();
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
    
    
            String s = it.next();
        }
        //如上

        // 上面代码擦除后和如下代码字节码一致

        List list = new ArrayList();
        Iterator it = list.iterator();
        while (it.hasNext()) {
    
    
            String s = (String) it.next();
        }
    }

In fact, the so-called type erasure of generics refers to restoring a specific generic reference to the Object type after completing the type check at compile time, adding forced type conversion in the necessary places in the code, and losing it. Type information assigned at runtime.

At runtime, you can only obtain the generic type that contains generic information in the current class file, but you cannot dynamically obtain the type of a generic reference at runtime.

After type erasure, there is no way to obtain the type information of E inside the test method in the following code. This is the actual effect after erasure. The generic information you said can be obtained through reflection must be the specific generic type of a class as a member variable, method return value, etc., for example:


    public static <E> void test(List<E> eList, Class<E> eClass) {
    
    
        // E e = new E(); // 编译报错,信息擦除。无法确定E的实际类型。
        try {
    
    
            // 反射代码,编译通过。
            E e = eClass.newInstance(); // Class<E> eClass 泛型作为了方法的参数,作为了元数据存在。可以使用这个信息。
            eList.add(e);
        } catch (InstantiationException | IllegalAccessException e1) {
    
    
            e1.printStackTrace();
        }
    }

reference

5. Generic scenarios used in your work

(1) Extraction of mvp Base class

(2) Network request interface Restful style Base bean object encapsulation

(3) Encapsulation of general tool classes: any array reverse.

6. Do you understand generic wildcards and upper and lower bounds? What are the PECS principles?

Their purpose is to make the method interface more flexible and accept a wider range of types.

<? super E> is used for flexible writing or comparison, so that objects can be written to the container of the parent type, so that the comparison method of the parent type can be applied to subclass objects.

Generics can accept the specified E class and its parent class type (just remember it according to the function of super)

<? extends E> is used for flexible reading, allowing the method to read container objects of E or any subtype of E.

Generics can accept E class and its subclass types (just remember it according to the extends function)

Use a phrase from "Effective Java" to deepen your understanding:
In order to obtain maximum flexibility, use wildcards on the input parameters representing producers or consumers. The rule used is: producers have upper limits and consumers have lower limits. .

PECS: producer-extends, costumer-super

If the parameterized type represents a producer of T, use <? extends T>;
if it represents a consumer of T, use <? super T>;
if it is both a producer and a consumer, then there is no point in using wildcards. Because what you need is the exact parameter type.

2. Reflection

1. What is reflection?

A technology provided by jdk that uses reflection API to dynamically dissect the components of a class during program running.

2. What are the application scenarios for reflection?

(1) Classes in the running configuration file: Common in frameworks, generally expressed as fully qualified name strings of configuration classes in xml or configuration files. At this time, objects of the class can be dynamically generated through reflection.
(2) Runtime processing of annotations
(3) Dynamic prompts of the compiler: Development tools use reflection to dynamically analyze the type and structure of objects, and dynamically prompt the properties and methods of objects.
(4) Implementation of dynamic proxy: jdk dynamic proxy is implemented using reflection. In fact, dynamic proxies such as cglib can also be implemented using the high-performance bytecode operation framework ASM.
(5) In JDBC, reflection is used to dynamically load the database driver
(6) In the Web server, reflection is used to call the Sevlet service method

3. Advantages and disadvantages of reflection

Advantages: Dynamic execution, dynamic acquisition of class components during runtime maximizes the flexibility of Java.

Disadvantages: Mainly performance overhead. Having an impact on performance, reflection operations are always slower than executing Java code directly.

  • Performance overhead: Reflection involves dynamic resolution of types, so the JVM cannot optimize these codes. Therefore, the reflection operation is inefficient and try to avoid using reflection under high performance requirements.
  • Security restrictions: Using reflection requires that the program must be run in an environment without security restrictions. Otherwise, you will not get the expected results.
  • Internal Exposure: Because reflection allows code to perform operations that are not normally allowed, using reflection can cause unintended side effects.
4. Reflection principle, how does JVM implement reflection?

(1) First, give a general introduction

There are two main types of reflection-related classes under Java's reflect package: Class and Member. Class is easy to understand as a class, which encapsulates all the information of a certain class.
Class has members, and the members of Class include three types: constructors, methods, and fields. Members can be obtained through the Class api.

Member is an interface that mainly defines the getName and getModifiers interfaces. There are three most common implementation classes: Field, Method, and Constructor.

These three implementation classes have the same parent class, AccessibleObject. When accessing private properties, you need to set setAccessible to true. Turning off the system's access permission check comes from the method in this class.

Insert image description here

Insert image description here

Insert image description here

(2) Specific implementation

Here we take the source code of Method as an example to explain:

    public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
    
    
     ...
     ...
     // 检查访问权限
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
    
    
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }

The reflection execution method will call the Method#invoke method, and the method is internally delegated to the MethodAccessor class for processing. MethodAccessor is an interface that has two existing specific implementations. One is to implement reflection through local methods (NativeMethodAccessorImpl), referred to as local implementation; the other uses the delegation mode (DelegatingMethodAccessorImpl), referred to as delegated implementation.
Insert image description here
So when to use native implementation? When to use delegate implementation? First, let’s look at the creation of MethodAccessor instances. MethodAccessor instances are created in ReflectionFactory. ReflectionFactory is a reflection factory class that is responsible for creating FieldAccessor corresponding to Field, MethodAccessor corresponding to Method, and ConstructorAccessor corresponding to Constructor.

public class ReflectionFactory {
    
    

    private static boolean initted = false;
    // 反射工厂类,负责创建FieldAccessor 、MethodAccessor 、ConstructorAccessor 
    private static final ReflectionFactory soleInstance = new ReflectionFactory();
    private static volatile LangReflectAccess langReflectAccess;
    private static volatile Method hasStaticInitializerMethod;

    // "Inflation" mechanism. Loading bytecodes to implement
    // Method.invoke() and Constructor.newInstance() currently costs
    // 3-4x more than an invocation via native code for the first
    // invocation (though subsequent invocations have been benchmarked
    // to be over 20x faster). Unfortunately this cost increases
    // startup time for certain applications that use reflection
    // intensively (but only once per class) to bootstrap themselves.
    // To avoid this penalty we reuse the existing JVM entry points
    // for the first few invocations of Methods and Constructors and
    // then switch to the bytecode-based implementations.
    //
    // Package-private to be accessible to NativeMethodAccessorImpl
    // and NativeConstructorAccessorImpl
    private static boolean noInflation        = false; // noInflation 默认关闭。关闭时会采用本地实现。
    //[ˈθreʃhəʊld]  阈;
    private static int     inflationThreshold = 15; // 阈值,发射次数大于inflationThreshold 时则noInflation为true
    
    //MethodAccessor 对象创建
    public MethodAccessor newMethodAccessor(Method method) {
    
    
        checkInitted();

        if (Reflection.isCallerSensitive(method)) {
    
    
            Method altMethod = findMethodForReflection(method);
            if (altMethod != null) {
    
    
                method = altMethod;
            }
        }

        // use the root Method that will not cache caller class
        Method root = langReflectAccess.getRoot(method);
        if (root != null) {
    
    
            method = root;
        }

        // 这里需要注意一点,VMAnonymousClass 并不是指匿名内部类
        // 它可以看做是 JVM 里面的一个模板机制
        if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
    
    
            //动态生成字节码技术
            return new MethodAccessorGenerator().
                generateMethod(method.getDeclaringClass(),
                               method.getName(),
                               method.getParameterTypes(),
                               method.getReturnType(),
                               method.getExceptionTypes(),
                               method.getModifiers());
        } else {
    
    
            //本地实现
            NativeMethodAccessorImpl acc = new NativeMethodAccessorImpl(method);
            //委派实现,代理本地实现。
            DelegatingMethodAccessorImpl res = new DelegatingMethodAccessorImpl(acc);
            acc.setParent(res);
            return res;
        }
    }
} 

It can be concluded that when reflection is called for the first time, the noInflation field value is obviously false by default. At this time, a delegation implementation will be generated, and the specific implementation of the delegation implementation is a local implementation.

In fact, we can also use a chestnut to verify the above conclusion:

/**
 * Create by SunnyDay on 2022/04/19 17:14
 */
public class TestReflectTrack {
    
    

    /**
     * 执行方法时直接搞个异常
     * */
    public static void showTrack(int i) {
    
    
        new Exception("#" + i).printStackTrace();
    }

    public static void main(String[] args) throws Exception {
    
    
        Class<?> clazz = Class.forName("TestReflectTrack");
        Method method = clazz.getMethod("showTrack", int.class);
        method.invoke(null, 0);
    }
}

Insert image description here

The local implementation is well understood as the implementation of calling the native method. After entering the Java virtual machine, we have the specific address of the method pointed to by the Method instance. At this time, the reflection call is nothing more than preparing the incoming parameters and then calling the target method.

Why do we need to use delegation implementation as the middle layer for reflection calls? Isn't it possible to just hand it over to the local implementation?

In fact, Java's reflective calling mechanism also sets up another implementation of dynamically generated bytecode (referred to as dynamic implementation), which directly uses the invoke instruction to call the target method. The reason for using delegation implementation is to enable local implementation and dynamic implementation. Switch during implementation.

As mentioned in the ReflectionFactory comment, the dynamic implementation is 20 times faster than the local implementation. This is because the dynamic implementation does not need to switch from Java to C++ and then to Java. However, because generating bytecode is very time-consuming, if it is called only once, On the contrary, the local implementation is 3 to 4 times faster.

Considering that many reflection calls will only be executed once, the Java virtual machine sets a threshold of 15. When the number of calls for a certain reflection call is below 15, local implementation is used; when it reaches 15, bytecode starts to be dynamically generated. And switch the delegation object of the delegation implementation to dynamic implementation. This process is called Inflation.

Now that we know inflationThreshold = 15, let’s see how this control logic is handled?

/** Used only for the first few invocations of a Method; afterward,
    switches to bytecode-based implementation */

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    
    
    private final Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method method) {
    
    
        this.method = method;
    }

    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
    
    
        // We can't inflate methods belonging to vm-anonymous classes because
        // that kind of class can't be referred to by name, hence can't be
        // found from the generated bytecode.
        
        //invoke方法没次被调用时计数器+1,当计数器值大于15时执行如下逻辑
        if (++numInvocations > ReflectionFactory.inflationThreshold()
                && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
    
    
            // 生成java版的MethodAccessor 实现类
            MethodAccessorImpl acc = (MethodAccessorImpl)
                new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
             //  改变委托实现DelegatingMethodAccessorImpl 的引用为java版。之后就是java动态实现了。                  
            parent.setDelegate(acc);
        }
        return invoke0(method, obj, args);
    }

    void setParent(DelegatingMethodAccessorImpl parent) {
    
    
        this.parent = parent;
    }

    private static native Object invoke0(Method m, Object obj, Object[] args);
}

inflationThreshold = 15 is determined in the NativeMethodAccessorImpl class. Each time the NativeMethodAccessorImpl.invoke method is called, a counter will be incremented to see if the threshold is exceeded; once exceeded, MethodAccessorGenerator.generateMethod is called to generate the Java version of the MethodAccessor implementation class, and change the MethodAccessor referenced by DelegatingMethodAccessorImpl to the Java version. Subsequent calls via DelegatingMethodAccessorImpl.invoke are the Java version implementations.

Insert image description here
Summary of factors affecting performance:

External factor:

Class.forName will call the local method, which takes many steps compared to calling the method directly using the Java object. Class.getMethod will traverse the public methods of the class. If there is no match, it will also traverse the public methods of the parent class. As you can imagine, both operations are very time-consuming.

It is worth noting that the search method operation represented by getMethod will return a copy of the search result. Therefore, we should avoid using the getMethods or getDeclaredMethods methods that return Method arrays in hotspot code to reduce unnecessary heap space consumption.

Own factors:

  • Method.invoke is a variable-length parameter method, and its last parameter at the bytecode level will be an Object array. The Java compiler will generate an Object array with a length equal to the number of incoming parameters at the method call point, and store the incoming parameters one by one in the array.
  • Object arrays cannot store basic types, and the Java compiler will automatically box the passed in basic data types.

Therefore, the reflection time-consuming factors are as follows:

  • Method table lookup: Traverse all methods of the class, and possibly the parent class.
  • Constructing Object arrays and possible automatic packing and unboxing operations
  • Runtime permission checking: Each reflection call checks the permissions of the target method, and this check can also be turned off in Java code.
  • method inline

How to avoid reflection performance overhead?

  • Try to avoid reflection calls to virtual methods: For invokevirtual or invokeinterface, the Java virtual machine records the specific type of the caller, which we call the type profile. When there are many virtual methods, the virtual machine cannot record so many classes at the same time, so it may cause the reflection being tested The call is not inlined.
  • Turn off runtime permission checking.
  • It may be necessary to increase the wrapper class cache corresponding to the basic data type
  • Turn off the Inflation mechanism (can be set through parameters)
  • Improve the JVM profile on the number of types that can be recorded for each call (default 2 can be adjusted through virtual machine parameters)

3. Notes

1. What is annotation?

Annotation can be understood as a label of Java code. This label provides some data for the marked code.

2. The period of existence of the annotation

This corresponds to the parameter value of the meta-annotation Retention. The parameter values ​​are the three enumeration values ​​in RetentionPolicy, SOURCE, CLASS, and RUNTIME. Represents existence in source code stage, existence in class file, and existence in class file respectively. There is a difference between CLASS and RUNTIME. The value of CLASS means that the jvm will discard the annotation information when reading the class file, while the value of RUNTIME means that the jvm will read the annotation value in the class file.

3. What is meta-annotation?

Meta-annotations are annotations that act on annotations. Java provides four meta-annotations.

  • @Retention: The duration of the annotation. The default value is CLASS
  • @Target: The target of the annotation. The default is to work with any code.
  • @Inherited: [ɪnˈherɪtɪd], whether to allow subclasses to inherit the annotations of the parent class, the default is false.
  • @Documented: Whether the annotation information can be saved in javadoc
4. Annotation processing scenarios
  • Compile-time processing (ButterKnife framework implementation principle, implementation of LayerHelper in work projects)
  • Runtime processing. (Implementation principle of ViewUtils in Uutils framework)
5. General process of using annotation processor

(1) What is APT?

APT: The abbreviation of AnnotationProcessorTool. The annotation processor is used by the compiler to allow the compiler to process annotations according to our wishes.

(2) Working principle of APT

The compilation process of Java source code is divided into three steps:

Parse the source file into an abstract syntax tree -> call the registered annotation processor -> generate bytecode

If a new source file is generated during the second step of calling the annotation processor, the compiler will repeat the first and second steps to parse and process the newly generated source file. Therefore, it can be understood that the annotation processor is used by the compiler, allowing the compiler to process annotations according to our wishes.

(3) Common uses of annotation processors

  • The first is to define compilation rules and check the compiled source files (such as @Override that comes with java)

  • The second is to modify the existing source code (this method is rarely used and involves the internal API of the Java compiler, which may cause compatibility issues)

  • The third is to generate new source code (common, currently the most commonly used method. Such as Butterknife, EventBus and other frameworks,)

(4) General usage process of annotation processor

You can customize the annotation processing logic by inheriting AbstractProcress, but you still need to register the annotation processor with the compiler. This is a very troublesome thing. You need to manually register it in the META-INFO directory, usually by relying on Google's AutoService library. solve.

public interface Processor {
    
    

  //一般做一些初始化工作可通过ProcessingEnvironment#getXXX来获取相应的对象如:
  //filer:用于给注解处理器创建文件,可把文件保存到本地。生成文件时经常会使用。
  //Messager:打印编译处理期间的信息。可在build->outPut 窗口查看。
  void init(ProcessingEnvironment processingEnv);
  
  // 获取注解处理器所支持的注解类型,一般固定写法,生成、返回一个set即可。
  Set<String> getSupportedAnnotationTypes();
  // 注解处理器支持的java版本,一般与编译器保持一致即可,固定写法。
  SourceVersion getSupportedSourceVersion();
  /**
    注解处理器的核心处理方法。
    annotations:注解处理器能够处理的注解类型。同上getSupportedAnnotationTypes。
    roundEnv:封装了当前轮抽象语法树的注解信息。一般通过如下方式处理:
    
 Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);//BindView 为自定义的注解
  Element是一个接口,代表元素,他的实现类有很多如
  TypeElement:表示一个类或接口程序元素
  VariableElement:表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
  这样通过相应的api就可以获取注解信息进行处理了。

  */
  boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);
  
  ...
}

4. Abnormality

1. Talk about exceptions in java

Throwable is the root class of all exceptions in the Java language. Throwable derives two direct subclasses, Error and Exception.

Error represents a serious problem that the application itself cannot overcome and recover from. When an Error is triggered, the thread or even the virtual machine will be terminated. Generally, errors related to virtual machines such as system crash, insufficient memory, stack overflow, etc.

Exceptions represent problems that the program can overcome and recover from. Exceptions can be divided into compile-time exceptions and run-time exceptions according to processing timing.

Compile-time exceptions are exceptions that can be repaired. During code compilation, the Java program must explicitly handle compile-time exceptions, otherwise it cannot be compiled. Runtime exceptions are usually problems caused by poor consideration by software developers. Software users cannot overcome and recover from this problem. However, the software system may continue to run under such problems. In serious cases, the software system will die.

2. How does jvm handle exceptions?

When the class file is compiled into bytecode, each method is accompanied by an exception table. Each entry in the exception table represents an exception handler.

The processor consists of a from pointer, a to pointer, a target pointer, and the captured exception type. The value of these pointers is the bytecode index used to locate the bytecode.

  • from and to represent the monitoring range of the exception handler, that is, the range monitored by the try code block.
  • target represents the starting position of the exception handler, that is, the starting position of the catch.
  • The exception type is xxxException.

When a program triggers an exception, the Java Virtual Machine generates an exception instance to be thrown, and then traverses all entries in the exception table from top to bottom. When the index value of the bytecode that triggers the exception is within the monitoring range of an exception table entry, the Java virtual machine determines whether the exception to be thrown matches the exception that the entry wants to catch. If there is a match, the Java virtual machine transfers control flow to the bytecode pointed to by the entry's target pointer.

If all exception table entries are traversed and the Java virtual machine still does not match an exception handler, it will pop up the Java stack frame corresponding to the current method and repeat the above operations in the caller. In the worst case, the Java virtual machine needs to traverse the exception table of all methods on the current thread's Java stack. Finally throw the exception.

The compilation of the finally code block is more complicated. The current version of the Java compiler copies the contents of the finally code block and places them in the exits of all normal execution paths and abnormal execution paths of the try-catch code block. To ensure that finally must be executed.

3. Which part of try-catch-finally can be omitted?

In fact, try is only suitable for handling runtime exceptions, and try+catch is suitable for handling runtime exceptions + compile-time exceptions.

In other words, if you only use try to handle compile-time exceptions without using catch, the compilation will not pass, because the compiler rigidly stipulates that if you choose to catch compile-time exceptions, you must use catch to explicitly declare them for further processing. There is no such provision for runtime exceptions at compile time, so catch can be omitted, and the catch compiler finds it understandable.

Theoretically, the compiler will look at any code and think it may have potential problems, so even if you add try to all the code, the code will just add a layer of skin on the basis of normal operation during runtime. But once you add try to a piece of code, you are explicitly promising the compiler to catch the exceptions that may be thrown by this piece of code instead of throwing them upward. If it is a compile-time exception, the compiler requires that it must be caught with catch for further processing; if it is a run-time exception,Capture then discard and +finally clean up, or add catch for further processing.

     try{
    
    
       // 运行时异常|编译时异常
     }catch(XXXException e){
    
    
      //1、try中为运行时异常时,这里catch 块可无。但是finally必须有。
      //2、try中为编译时异常时,cacth必须有,finally可有可无。
     }finally{
    
    
     // finally 块可有可无,做资源处理。
     }

       //栗子
        try{
    
    
           // 运行时异常
           int a = 1/0;
        }finally {
    
    
         // 必须有
        }
4. In try-catch-finally, if return occurs in catch, will finally still be executed?

will be executed.

The exception mechanism has such a principle. If a return or exception is encountered in the catch, which can cause the function to terminate, then finally must first execute the code in the finally code block and then return to the throw or return point in the catch. (Except for actively exiting the virtual machine, such as System.exit() in catch, finally will not be executed)

However, there are exceptions to the above execution mechanism. Generally, you can use Java7's try-catch-resource syntax sugar processing. However, the original intention of try catch-resource design is to optimize the bloated code of try-catch-finally resource closing. At the same time, Supressed exceptions are introduced to automatically use Supressed exceptions at the bytecode level. This new feature allows developers to attach one exception to another. Therefore, the thrown exception can be accompanied by multiple exception information.

5. What is the difference between NoClassDefFoundError and ClassNotFoundException?

NoClassDefFoundError is an exception of type Error, which is caused by the JVM. You should not try to catch this exception. The reason for this exception is that the JVM or ClassLoader cannot find the definition of a certain class in the memory when trying to load it. This action occurs during runtime, that is, the class exists at compile time, but cannot be found at runtime. It may be that It was deleted after mutation and other reasons.

ClassNotFoundException is a checked exception and needs to be caught and handled explicitly using try-catch or declared using the throws keyword in the method signature. When using Class.forName, ClassLoader.loadClass or ClassLoader.findSystemClass to dynamically load a class into memory and the class is not found through the passed class path parameter, this exception will be thrown; another possible reason for throwing this exception A class has been loaded into memory by one class loader, and another loader tries to load it.

5. Differences between interface abstract classes

1. The difference between members

Abstract class
1. Construction method: There is a construction method for instantiation of subclasses.
2. Member variables: They can be variables or constants.
3. Member methods: They can be abstract or non-abstract.

Interface
1. Constructor: no constructor
2. Member variables: can only be constants. The default modifier is public static final
3. Member methods: jdk1.7 can only be abstract. The default modifier is public abstract. jdk1.8 can write specific methods starting with default and static.

2. Differences in relationships

Classes and classes:
1. Inheritance relationship, only single inheritance. Multiple levels of inheritance are possible.

Classes and interfaces:
1. Implementation relationship: It can be implemented singly or in multiple ways.
2. A class can also implement multiple interfaces while inheriting a class.

Interfaces and interfaces:
1. Inheritance relationship: Single inheritance or multiple inheritance is possible.

3. Different concepts

1. What is defined in abstract classes are common contents in an inheritance system.
2. An interface is a collection of functions, an additional function of a system, and an exposed rule.

4. Choice of interfaces and abstract classes

The concepts of interfaces and abstract classes are different. The interface is an abstraction of actions, indicating what the object can do, and the abstract class is an abstraction of the source, indicating what the object is. When you focus on the essence of a thing, use abstract classes; when you focus on an operation, use interfaces.

chestnut:

Men, women, these two classes, their abstract class is people. Explain that they are all human beings. People can eat, and dogs can also eat. You can define "eating" as an interface. Therefore, in high-level languages, a class can only inherit one class (just as a person cannot be human and non-human at the same time), but can implement multiple interfaces (eating interface, walking interface).

The functionality of abstract classes far exceeds that of interfaces, but the cost of defining abstract classes is high. Because in high-level languages ​​(and in terms of actual design), each class can only inherit one class. In this class, you must inherit or write out all the common features of all its subclasses. Although the interface will be much weaker in function, it is only a description of an action. And you can implement multiple interfaces in one class at the same time. Difficulty will be reduced during the design phase.

Guess you like

Origin blog.csdn.net/qq_38350635/article/details/124252614