乐趣区

关于java:Java基础面试题第二期2021最新版

本文收录于《面试小抄》系列,Github 地址(可下载 pdf):https://github.com/cosen1024/… 国内 Gitee(可下载 pdf):https://gitee.com/cosen1024/J…

本文蕴含了 String、包装类型、反射、泛型、序列化、异样和 IO 的常见面试题。

本文收录于《面试小抄》系列,Github 地址:https://github.com/cosen1024/…

蕴含了 Java 根底、Java 并发、JVM、MySQL、Redis、Spring、MyBatis、Kafka、操作系统和计算机网络等的常考面试题。

坐稳,要发车了~

1. 字符型常量和字符串常量的区别?

  1. 模式上: 字符常量是单引号引起的一个字符,字符串常量是双引号引起的若干个字符;
  2. 含意上: 字符常量相当于一个整型值(ASCII 值), 能够加入表达式运算;字符串常量代表一个地址值(该字符串在内存中寄存地位,相当于对象;
  3. 占内存大小:字符常量只占 2 个字节;字符串常量占若干个字节(至多一个字符完结标记) (留神: char 在 Java 中占两个字节)。

2. 什么是字符串常量池?

java 中常量池的概念次要有三个:全局字符串常量池 class 文件常量池 运行时常量池 。咱们当初所说的就是 全局字符串常量池,对这个想弄明确的同学能够看这篇 Java 中几种常量池的辨别。

jvm 为了晋升性能和缩小内存开销,防止字符的反复创立,其保护了一块非凡的内存空间,即字符串池,当须要应用字符串时,先去字符串池中查看该字符串是否曾经存在,如果存在,则能够间接应用,如果不存在,初始化,并将该字符串放入字符串常量池中。

字符串常量池的地位也是随着 jdk 版本的不同而地位不同。在 jdk6 中,常量池的地位在永恒代(办法区)中,此时常量池中存储的是 对象 。在 jdk7 中,常量池的地位在堆中,此时,常量池存储的就是 援用 了。在 jdk8 中,永恒代(办法区)被元空间取代了。

3. String str=”aaa” 与 String str=new String(“aaa”)一样吗?new String(“aaa”);创立了几个字符串对象?

  • 应用String a =“aaa”;,程序运行时会在常量池中查找”aaa”字符串,若没有,会将”aaa”字符串放进常量池,再将其地址赋给 a;若有,将找到的”aaa”字符串的地址赋给 a。
  • 应用 String b = new String(“aaa”);`,程序会在堆内存中开拓一片新空间寄存新对象,同时会将”aaa”字符串放入常量池,相当于创立了两个对象,无论常量池中有没有”aaa”字符串,程序都会在堆内存中开拓一片新空间寄存新对象。

具体分析,见以下代码:

 @Test
    public void test(){String s = new String("2");
        s.intern();
        String s2 = "2";
        System.out.println(s == s2);


        String s3 = new String("3") + new String("3");
        s3.intern();
        String s4 = "33";
        System.out.println(s3 == s4);
    }

运行后果:

jdk6
false
false

jdk7
false
true

这段代码在 jdk6 中输入是false false,然而在 jdk7 中输入的是false true。咱们通过图来一行行解释。

先来意识下 intern()函数

intern 函数的作用是将对应的符号常量进入非凡解决,在 JDK1.6 以前 和 JDK1.7 当前有不同的解决;

在 JDK1.6 中,intern 的解决是 先判断字符串常量是否在字符串常量池中,如果存在间接返回该常量,如果没有找到,则将该字符串常量退出到字符串常量区,也就是在字符串常量区建设该常量;

在 JDK1.7 中,intern 的解决是 先判断字符串常量是否在字符串常量池中,如果存在间接返回该常量,如果没有找到,阐明该字符串常量在堆中,则解决是把堆区该对象的援用退出到字符串常量池中,当前他人拿到的是该字符串常量的援用,理论存在堆中

JDK1.6

String s = new String("2");创立了两个对象,一个在堆中的 StringObject 对象,一个是在常量池中的“2”对象。
s.intern();在常量池中寻找与 s 变量内容雷同的对象,发现曾经存在内容雷同对象“2”,返回对象 2 的地址。
String s2 = "2";应用字面量创立,在常量池寻找是否有雷同内容的对象,发现有,返回对象 ”2″ 的地址。
System.out.println(s == s2);从下面能够剖析出,s 变量和 s2 变量地址指向的是不同的对象,所以返回 false

String s3 = new String("3") + new String("3");创立了两个对象,一个在堆中的 StringObject 对象,一个是在常量池中的“3”对象。两头还有 2 个匿名的 new String(“3”)咱们不去探讨它们。
s3.intern();在常量池中寻找与 s3 变量内容雷同的对象,没有发现“33”对象,在常量池中创立“33”对象,返回“33”对象的地址。
String s4 = "33";应用字面量创立,在常量池寻找是否有雷同内容的对象,发现有,返回对象 ”33″ 的地址。
System.out.println(s3 == s4);从下面能够剖析出,s3 变量和 s4 变量地址指向的是不同的对象,所以返回 false

JDK1.7

String s = new String("2");创立了两个对象,一个在堆中的 StringObject 对象,一个是在堆中的“2”对象,并在常量池中保留“2”对象的援用地址。
s.intern();在常量池中寻找与 s 变量内容雷同的对象,发现曾经存在内容雷同对象“2”,返回对象“2”的援用地址。
String s2 = "2";应用字面量创立,在常量池寻找是否有雷同内容的对象,发现有,返回对象“2”的援用地址。
System.out.println(s == s2);从下面能够剖析出,s 变量和 s2 变量地址指向的是不同的对象,所以返回 false

String s3 = new String("3") + new String("3");创立了两个对象,一个在堆中的 StringObject 对象,一个是在堆中的“3”对象,并在常量池中保留“3”对象的援用地址。两头还有 2 个匿名的 new String(“3”)咱们不去探讨它们。
s3.intern();在常量池中寻找与 s3 变量内容雷同的对象,没有发现“33”对象,将 s3 对应的 StringObject 对象的地址保留到常量池中,返回 StringObject 对象的地址。
String s4 = "33";应用字面量创立,在常量池寻找是否有雷同内容的对象,发现有,返回其地址,也就是 StringObject 对象的援用地址。
System.out.println(s3 == s4);从下面能够剖析出,s3 变量和 s4 变量地址指向的是雷同的对象,所以返回 true。

4. String 是最根本的数据类型吗?

不是。Java 中的根本数据类型只有 8 个:byte、short、int、long、float、double、char、boolean;除了根本类型(primitive type),剩下的都是援用类型(referencetype),Java 5 当前引入的枚举类型也算是一种比拟非凡的援用类型。

5. String 有哪些个性?

  • 不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创立一个新的对象,再把援用指向该对象。不变模式的次要作用在于当一个对象须要被多线程共享并频繁拜访时,能够保证数据的一致性;
  • 常量池优化:String 对象创立之后,会在字符串常量池中进行缓存,如果下次创立同样的对象时,会间接返回缓存的援用;
  • final:应用 final 来定义 String 类,示意 String 类不能被继承,进步了零碎的安全性。

6. 在应用 HashMap 的时候,用 String 做 key 有什么益处?

HashMap 外部实现是通过 key 的 hashcode 来确定 value 的存储地位,因为字符串是不可变的,所以当创立字符串时,它的 hashcode 被缓存下来,不须要再次计算,所以相比于其余对象更快。

7. 包装类型是什么?根本类型和包装类型有什么区别?

Java 为每一个根本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了主动装箱 / 拆箱机制,把根本类型转换成包装类型的过程叫做装箱(boxing);反之,把包装类型转换成根本类型的过程叫做拆箱(unboxing),使得二者能够互相转换。

Java 为每个原始类型提供了包装类型:

原始类型: boolean,char,byte,short,int,long,float,double

包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

根本类型和包装类型的区别次要有以下 几点

  • 包装类型能够为 null,而根本类型不能够。它使得包装类型能够利用于 POJO 中,而根本类型则不行。那为什么 POJO 的属性必须要用包装类型呢?《阿里巴巴 Java 开发手册》上有具体的阐明,数据库的查问后果可能是 null,如果应用根本类型的话,因为要主动拆箱(将包装类型转为根本类型,比如说把 Integer 对象转换成 int 值),就会抛出 NullPointerException 的异样。
  • 包装类型可用于泛型,而根本类型不能够。泛型不能应用根本类型,因为应用根本类型时会编译出错。

    List<int> list = new ArrayList<>(); // 提醒 Syntax error, insert "Dimensions" to complete ReferenceType
    List<Integer> list = new ArrayList<>();

因为泛型在编译时会进行类型擦除,最初只保留原始类型,而原始类型只能是 Object 类及其子类——根本类型是个特例。

  • 根本类型比包装类型更高效。根本类型在栈中间接存储的具体数值,而包装类型则存储的是堆中的援用。很显然,相比拟于根本类型而言,包装类型须要占用更多的内存空间。

8. 解释一下主动装箱和主动拆箱?

主动装箱:将根本数据类型从新转化为对象

    public class Test {public static void main(String[] args) {// 申明一个 Integer 对象,用到了主动的装箱:解析为:Integer num = Integer.valueOf(9);
            Integer num = 9;
        }  
    }  

9 是属于根本数据类型的,原则上它是不能间接赋值给一个对象 Integer 的。但 jdk1.5 开始引入了主动装箱 / 拆箱机制,就能够进行这样的申明,主动将根本数据类型转化为对应的封装类型,成为一个对象当前就能够调用对象所申明的所有的办法。

主动拆箱:将对象从新转化为根本数据类型

 public class Test {public static void main(String[] args) {  
            / / 申明一个 Integer 对象
            Integer num = 9;
            
            // 进行计算时隐含的有主动拆箱
            System.out.print(num--);
        }  
    }  

因为 对象时不能间接进行运算的,而是要转化为根本数据类型后能力进行加减乘除

9. int 和 Integer 有什么区别?

  • Integer 是 int 的包装类;int 是根本数据类型;
  • Integer 变量必须实例化后能力应用;int 变量不须要;
  • Integer 理论是对象的援用,指向此 new 的 Integer 对象;int 是间接存储数据值;
  • Integer 的默认值是 null;int 的默认值是 0。

10. 两个 new 生成的 Integer 变量的比照

因为 Integer 变量实际上是对一个 Integer 对象的援用,所以两个通过 new 生成的 Integer 变量永远是不相等的(因为 new 生成的是两个对象,其内存地址不同)。

Integer i = new Integer(10000);
Integer j = new Integer(10000);
System.out.print(i == j); //false

11. Integer 变量和 int 变量的比照

Integer 变量和 int 变量比拟时,只有两个变量的值是向等的,则后果为 true(因为包装类 Integer 和根本数据类型 int 比拟时,java 会主动拆包装为 int,而后进行比拟,实际上就变为两个 int 变量的比拟)

    int a = 10000;
    Integer b = new Integer(10000);
    Integer c=10000;
    System.out.println(a == b); // true
    System.out.println(a == c); // true

12. 非 new 生成的 Integer 变量和 new Integer()生成变量的比照

非 new 生成的 Integer 变量和 new Integer()生成的变量比拟时,后果为 false。(因为非 new 生成的 Integer 变量指向的是 java 常量池中的对象,而 new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同)

    Integer b = new Integer(10000);
    Integer c=10000;
    System.out.println(b == c); // false

13. 两个非 new 生成的 Integer 对象的比照

对于两个非 new 生成的 Integer 对象,进行比拟时,如果两个变量的值在区间 -128 到 127 之间,则比拟后果为 true,如果两个变量的值不在此区间,则比拟后果为 false

Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true

Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false

当值在 -128 ~ 127 之间时,java 会进行主动装箱,而后会对值进行缓存,如果下次再有雷同的值,会间接在缓存中取出应用。缓存是通过 Integer 的外部类 IntegerCache 来实现的。当值超出此范畴,会在堆中 new 出一个对象来存储。

给一个 Integer 对象赋一个 int 值的时候,会调用 Integer 类的静态方法 valueOf,源码如下:

public static Integer valueOf(String s, int radix) throws NumberFormatException {return Integer.valueOf(parseInt(s,radix));
    }
/**
 *(1)在 -128~127 之内:动态常量池中 cache 数组是 static final 类型,cache 数组对象会被存储于动态常量池中。* cache 数组外面的元素却不是 static final 类型,而是 cache[k] = new Integer(j++),* 那么这些元素是存储于堆中,只是 cache 数组对象存储的是指向了堆中的 Integer 对象(援用地址)* 
 *(2)在 -128~127 之外:新建一个 Integer 对象,并返回。*/
public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        if (i >= IntegerCache.low && i <= IntegerCache.high) {return IntegerCache.cache[i + (-IntegerCache.low)];
        }
        return new Integer(i);
    }

IntegerCache 是 Integer 的外部类,源码如下:

     /**
      * 缓存反对主动装箱的对象标识语义 -128 和 127(含)。* 缓存在第一次应用时初始化。缓存的大小能够由 -XX:AutoBoxCacheMax = <size> 选项管制。* 在 VM 初始化期间,java.lang.Integer.IntegerCache.high 属性能够设置并保留在公有零碎属性中
     */
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++) {cache[k] = new Integer(j++); // 创立一个对象
            }
        }

        private IntegerCache() {}
    }

14. 什么是反射?

反射是在运行状态中,对于任意一个类,都可能晓得这个类的所有属性和办法;对于任意一个对象,都可能调用它的任意一个办法和属性;这种动静获取的信息以及动静调用对象的办法的性能称为 Java 语言的反射机制。

15. 反射机制的优缺点有哪些?

长处:可能运行时动静获取类的实例,进步灵活性;可与动静编译联合Class.forName('com.mysql.jdbc.Driver.class');,加载 MySQL 的驱动类。

毛病:应用反射性能较低,须要解析字节码,将内存中的对象进行解析。其解决方案是:通过 setAccessible(true)敞开 JDK 的安全检查来晋升反射速度;屡次创立一个类的实例时,有缓存会快很多;ReflflectASM 工具类,通过字节码生成的形式放慢反射速度。

16. 如何获取反射中的 Class 对象?

  1. Class.forName(“类的门路”);当你晓得该类的全路径名时,你能够应用该办法获取 Class 类对象。

    Class clz = Class.forName("java.lang.String");
  2. 类名.class。这种办法只适宜在编译前就晓得操作的 Class。

    Class clz = String.class;
  3. 对象名.getClass()。

    String str = new String("Hello");
    Class clz = str.getClass();
  4. 如果是根本类型的包装类,能够调用包装类的 Type 属性来取得该包装类的 Class 对象。

17. Java 反射 API 有几类?

反射 API 用来生成 JVM 中的类、接口或则对象的信息。

  • Class 类:反射的外围类,能够获取类的属性,办法等信息。
  • Field 类:Java.lang.reflec 包中的类,示意类的成员变量,能够用来获取和设置类之中的属性值。
  • Method 类:Java.lang.reflec 包中的类,示意类的办法,它能够用来获取类中的办法信息或者执行办法。
  • Constructor 类:Java.lang.reflec 包中的类,示意类的构造方法。

18. 反射应用的步骤?

  1. 获取想要操作的类的 Class 对象,这是反射的外围,通过 Class 对象咱们能够任意调用类的办法。
  2. 调用 Class 类中的办法,既就是反射的应用阶段。
  3. 应用反射 API 来操作这些信息。

具体能够看上面的例子:

public class Apple {

    private int price;

    public int getPrice() {return price;}

    public void setPrice(int price) {this.price = price;}

    public static void main(String[] args) throws Exception{
        // 失常的调用
        Apple apple = new Apple();
        apple.setPrice(5);
        System.out.println("Apple Price:" + apple.getPrice());
        // 应用反射调用
        Class clz = Class.forName("com.chenshuyi.api.Apple");
        Method setPriceMethod = clz.getMethod("setPrice", int.class);
        Constructor appleConstructor = clz.getConstructor();
        Object appleObj = appleConstructor.newInstance();
        setPriceMethod.invoke(appleObj, 14);
        Method getPriceMethod = clz.getMethod("getPrice");
        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
    }
}

从代码中能够看到咱们应用反射调用了 setPrice 办法,并传递了 14 的值。之后应用反射调用了 getPrice 办法,输入其价格。下面的代码整个的输入后果是:

Apple Price:5
Apple Price:14

从这个简略的例子能够看出,个别状况下咱们应用反射获取一个对象的步骤:

  • 获取类的 Class 对象实例
Class clz = Class.forName("com.zhenai.api.Apple");
  • 依据 Class 对象实例获取 Constructor 对象
Constructor appleConstructor = clz.getConstructor();
  • 应用 Constructor 对象的 newInstance 办法获取反射类对象
Object appleObj = appleConstructor.newInstance();

而如果要调用某一个办法,则须要通过上面的步骤:

  • 获取办法的 Method 对象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
  • 利用 invoke 办法调用办法
setPriceMethod.invoke(appleObj, 14);

19. 为什么引入反射概念?反射机制的利用有哪些?

咱们来看一下 Oracle 官网文档中对反射的形容:

从 Oracle 官网文档中能够看出,反射次要利用在以下几方面:

  • 反射让开发人员能够通过外部类的全路径名创建对象,并应用这些类,实现一些扩大的性能。
  • 反射让开发人员能够枚举出类的全副成员,包含构造函数、属性、办法。以帮忙开发者写出正确的代码。
  • 测试时能够利用反射 API 拜访类的公有成员,以保障测试代码覆盖率。

也就是说,Oracle 心愿开发者将反射作为一个工具,用来帮忙程序员实现本不可能实现的性能。

举两个最常见应用反射的例子,来阐明反射机制的弱小之处:

第一种:JDBC 的数据库的连贯

在 JDBC 的操作中,如果要想进行数据库的连贯,则必须依照以上的几步实现

  1. 通过 Class.forName()加载数据库的驱动程序(通过反射加载,前提是引入相干了 Jar 包);
  2. 通过 DriverManager 类进行数据库的连贯,连贯的时候要输出数据库的连贯地址、用户名、明码;
  3. 通过 Connection 接口接管连贯。
public class ConnectionJDBC {  
  
    /** 
     * @param args 
     */  
    // 驱动程序就是之前在 classpath 中配置的 JDBC 的驱动程序的 JAR 包中  
    public static final String DBDRIVER = "com.mysql.jdbc.Driver";  
    // 连贯地址是由各个数据库生产商独自提供的,所以须要独自记住  
    public static final String DBURL = "jdbc:mysql://localhost:3306/test";  
    // 连贯数据库的用户名  
    public static final String DBUSER = "root";  
    // 连贯数据库的明码  
    public static final String DBPASS = "";  
      
      
    public static void main(String[] args) throws Exception {  
        Connection con = null; // 示意数据库的连贯对象  
        Class.forName(DBDRIVER); //1、应用 CLASS 类加载驱动程序 , 反射机制的体现 
        con = DriverManager.getConnection(DBURL,DBUSER,DBPASS); //2、连贯数据库  
        System.out.println(con);  
        con.close(); // 3、敞开数据库}  

第二种:Spring 框架的应用,最经典的就是 xml 的配置模式

Spring 通过 XML 配置模式装载 Bean 的过程:

  1. 将程序内所有 XML 或 Properties 配置文件加载入内存中;
  2. Java 类外面解析 xml 或 properties 外面的内容,失去对应实体类的字节码字符串以及相干的属性信息;
  3. 应用反射机制,依据这个字符串取得某个类的 Class 实例;
  4. 动静配置实例的属性。

Spring 这样做的益处是:

  • 不必每一次都要在代码外面去 new 或者做其余的事件;
  • 当前要改的话间接改配置文件,代码保护起来就很不便了;
  • 有时为了适应某些需要,Java 类外面不肯定能间接调用另外的办法,能够通过反射机制来实现。

模仿 Spring 加载 XML 配置文件:

public class BeanFactory {private Map<String, Object> beanMap = new HashMap<String, Object>();
       /**
       * bean 工厂的初始化.
       * @param xml xml 配置文件
       */
       public void init(String xml) {
              try {
                     // 读取指定的配置文件
                     SAXReader reader = new SAXReader();
                     ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                     // 从 class 目录下获取指定的 xml 文件
                     InputStream ins = classLoader.getResourceAsStream(xml);
                     Document doc = reader.read(ins);
                     Element root = doc.getRootElement();  
                     Element foo;
                    
                     // 遍历 bean
                     for (Iterator i = root.elementIterator("bean"); i.hasNext();) {foo = (Element) i.next();
                            // 获取 bean 的属性 id 和 class
                            Attribute id = foo.attribute("id");  
                            Attribute cls = foo.attribute("class");
                           
                            // 利用 Java 反射机制,通过 class 的名称获取 Class 对象
                            Class bean = Class.forName(cls.getText());
                           
                            // 获取对应 class 的信息
                            java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean);
                            // 获取其属性形容
                            java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors();
                            // 设置值的办法
                            Method mSet = null;
                            // 创立一个对象
                            Object obj = bean.newInstance();
                           
                            // 遍历该 bean 的 property 属性
                            for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) {Element foo2 = (Element) ite.next();
                                   // 获取该 property 的 name 属性
                                   Attribute name = foo2.attribute("name");
                                   String value = null;
                                  
                                   // 获取该 property 的子元素 value 的值
                                   for(Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();) {Element node = (Element) ite1.next();
                                          value = node.getText();
                                          break;
                                   }
                                  
                                   for (int k = 0; k < pd.length; k++) {if (pd[k].getName().equalsIgnoreCase(name.getText())) {mSet = pd[k].getWriteMethod();
                                                 // 利用 Java 的反射极致调用对象的某个 set 办法,并将值设置进去
                                                 mSet.invoke(obj, value);
                                          }
                                   }
                            }
                           
                            // 将对象放入 beanMap 中,其中 key 为 id 值,value 为对象
                            beanMap.put(id.getText(), obj);
                     }
              } catch (Exception e) {System.out.println(e.toString());
              }
       }
      
       //other codes
}

20. 反射机制的原理是什么?

Class actionClass=Class.forName(“MyClass”);
Object action=actionClass.newInstance();
Method method = actionClass.getMethod(“myMethod”,null);
method.invoke(action,null);

下面就是最常见的反射应用的例子,前两行实现了类的装载、链接和初始化(newInstance 办法实际上也是应用反射调用了 <init> 办法),后两行实现了从 class 对象中获取到 method 对象而后执行反射调用。

因反射原理较简单,上面简要形容下流程,想要具体理解的小伙伴,能够看这篇文章:https://www.cnblogs.com/youge…

  1. 反射获取类实例 Class.forName(),并没有将实现留给了 java, 而是交给了 jvm 去加载!次要是先获取 ClassLoader, 而后调用 native 办法,获取信息,加载类则是回调 java.lang.ClassLoader。最初,jvm 又会回调 ClassLoader 进类加载!
  2. newInstance() 次要做了三件事:

    • 权限检测,如果不通过间接抛出异样;
    • 查找无参结构器,并将其缓存起来;
    • 调用具体方法的无参构造方法,生成实例并返回。
  3. 获取 Method 对象,

下面的 Class 对象是在加载类时由 JVM 结构的,JVM 为每个类治理一个举世无双的 Class 对象,这份 Class 对象里保护着该类的所有 Method,Field,Constructor 的 cache,这份 cache 也能够被称作根对象。

每次 getMethod 获取到的 Method 对象都持有对根对象的援用,因为一些重量级的 Method 的成员变量(次要是 MethodAccessor),咱们不心愿每次创立 Method 对象都要从新初始化,于是所有代表同一个办法的 Method 对象都共享着根对象的 MethodAccessor,每一次创立都会调用根对象的 copy 办法复制一份:

 Method copy() { 

        Method res = new Method(clazz, name, parameterTypes, returnType,

                                exceptionTypes, modifiers, slot, signature,

                                annotations, parameterAnnotations, annotationDefault);

        res.root = this;

        res.methodAccessor = methodAccessor;

        return res;

    }
  1. 调用 invoke()办法。调用 invoke 办法的流程如下:

调用 Method.invoke 之后,会间接去调 MethodAccessor.invoke。MethodAccessor 就是下面提到的所有同名 method 共享的一个实例,由 ReflectionFactory 创立。

创立机制采纳了一种名为 inflation 的形式(JDK1.4 之后):如果该办法的累计调用次数 <=15,会创立出 NativeMethodAccessorImpl,它的实现就是间接调用 native 办法实现反射;如果该办法的累计调用次数 >15,会由 java 代码创立出字节码组装而成的 MethodAccessorImpl。(是否采纳 inflation 和 15 这个数字都能够在 jvm 参数中调整)
以调用 MyClass.myMethod(String s)为例,生成出的 MethodAccessorImpl 字节码翻译成 Java 代码大抵如下:

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {public Object invoke(Object obj, Object[] args)  throws Exception {
        try {MyClass target = (MyClass) obj;
            String arg0 = (String) args[0];
            target.myMethod(arg0);
        } catch (Throwable t) {throw new InvocationTargetException(t);
        }
    }
}

21. Java 中的泛型是什么 ?

泛型是 JDK1.5 的一个新个性,泛型就是将类型参数化,其在编译时才确定具体的参数。这种参数类型能够用在类、接口和办法的创立中,别离称为泛型类、泛型接口、泛型办法。

22. 应用泛型的益处是什么?

远在 JDK 1.4 版本的时候,那时候是没有泛型的概念的,如果应用 Object 来实现通用、不同类型的解决,有这么两个毛病:

  1. 每次应用时都须要强制转换成想要的类型
  2. 在编译时编译器并不知道类型转换是否失常,运行时才晓得,不平安。

如这个例子:

List list = new ArrayList();
list.add("www.cnblogs.com");
list.add(23);
String name = (String)list.get(0);
String number = (String)list.get(1);    //ClassCastException

下面的代码在运行时会产生强制类型转换异样。这是因为咱们在存入的时候,第二个是一个 Integer 类型,然而取出来的时候却将其强制转换为 String 类型了。Sun 公司为了使 Java 语言更加平安,缩小运行时异样的产生。于是在 JDK 1.5 之后推出了泛型的概念。

依据《Java 编程思维》中的形容,泛型呈现的动机在于:有许多起因促成了泛型的呈现,而最引人注意的一个起因,就是 为了创立容器类

应用泛型的益处有以下几点

  1. 类型平安

    • 泛型的次要指标是进步 Java 程序的类型平安
    • 编译期间就能够查看出因 Java 类型不正确导致的 ClassCastException 异样
    • 合乎越早出错代价越小准则
  2. 打消强制类型转换

    • 泛型的一个附带益处是,应用时间接失去指标类型,打消许多强制类型转换
    • 所得即所需,这使得代码更加可读,并且缩小了出错机会
  3. 潜在的性能收益

    • 因为泛型的实现形式,反对泛型(简直)不须要 JVM 或类文件更改
    • 所有工作都在编译器中实现
    • 编译器生成的代码跟不应用泛型(和强制类型转换)时所写的代码简直统一,只是更能确保类型平安而已

23. Java 泛型的原理是什么 ? 什么是类型擦除 ?

泛型是一种语法糖,泛型这种语法糖的基本原理是类型擦除。Java 中的泛型基本上都是在编译器这个档次来实现的,也就是说:泛型只存在于编译阶段,而不存在于运行阶段。在编译后的 class 文件中,是没有泛型这个概念的。

类型擦除:应用泛型的时候加上的类型参数,编译器在编译的时候去掉类型参数。

例如:

public class Caculate<T> {private T num;}

咱们定义了一个泛型类,定义了一个属性成员,该成员的类型是一个泛型类型,这个 T 具体是什么类型,咱们也不晓得,它只是用于限定类型的。反编译一下这个 Caculate 类:

public class Caculate{public Caculate(){}
    private Object num;
}

发现编译器擦除 Caculate 类前面的两个尖括号,并且将 num 的类型定义为 Object 类型。

那么是不是所有的泛型类型都以 Object 进行擦除呢?大部分状况下,泛型类型都会以 Object 进行替换,而有一种状况则不是。那就是应用到了 extends 和 super 语法的有界类型,如:

public class Caculate<T extends String> {private T num;}

这种状况的泛型类型,num 会被替换为 String 而不再是 Object。这是一个类型限定的语法,它限定 T 是 String 或者 String 的子类,也就是你构建 Caculate 实例的时候只能限定 T 为 String 或者 String 的子类,所以无论你限定 T 为什么类型,String 都是父类,不会呈现类型不匹配的问题,于是能够应用 String 进行类型擦除。

实际上编译器会失常的将应用泛型的中央编译并进行类型擦除,而后返回实例。然而除此之外的是,如果构建泛型实例时应用了泛型语法,那么编译器将标记该实例并关注该实例后续所有办法的调用,每次调用前都进行安全检查,非指定类型的办法都不能调用胜利。

实际上编译器不仅关注一个泛型办法的调用,它还会为某些返回值为限定的泛型类型的办法进行强制类型转换,因为类型擦除,返回值为泛型类型的办法都会擦除成 Object 类型,当这些办法被调用后,编译器会额定插入一行 checkcast 指令用于强制类型转换。这一个过程就叫做『泛型翻译』。

24. 什么是泛型中的限定通配符和非限定通配符 ?

限定通配符对类型进行了限度。有两种限定通配符,一种是 <? extends T> 它通过确保类型必须是 T 的子类来设定类型的上界,另一种是 <? super T> 它通过确保类型必须是 T 的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译谬误。

非限定通配符 , 能够用任意类型来代替。如 List<?> 的意思是这个汇合是一个能够持有任意类型的汇合,它能够是List<A>,也能够是List<B>, 或者List<C> 等等。

25. List<? extends T> 和 List <? super T> 之间有什么区别 ?

这两个 List 的申明都是限定通配符的例子,List<? extends T> 能够承受任何继承自 T 的类型的 List,而 List<? super T> 能够承受任何 T 的父类形成的 List。例如 List<? extends Number> 能够承受 List<Integer> 或 List<Float>。

26. 能够把 List<String>传递给一个承受 List<Object>参数的办法吗?

不能够。真这样做的话会导致编译谬误。因为 List<Object> 能够存储任何类型的对象包含 String, Integer 等等,而 List<String> 却只能用来存储 String。

List<Object> objectList;
List<String> stringList;
objectList = stringList;  //compilation error incompatible types

27. 判断 ArrayList<String>ArrayList<Integer>是否相等?

ArrayList<String> a = new ArrayList<String>();
ArrayList<Integer> b = new ArrayList<Integer>();
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(c1 == c2); 

输入的后果是 true。因为无论对于 ArrayList 还是 ArrayList,它们的 Class 类型都是始终的,都是 ArrayList.class。

那它们申明时指定的 String 和 Integer 到底体现在哪里呢?

答案是体现在类编译的时候。当 JVM 进行类编译时,会进行泛型查看,如果一个汇合被申明为 String 类型,那么它往该汇合存取数据的时候就会对数据进行判断,从而防止存入或取出谬误的数据。

补充: Array 中能够用泛型吗?

不能够。这也是为什么 Joshua Bloch 在《Effective Java》一书中倡议应用 List 来代替 Array,因为 List 能够提供编译期的类型平安保障,而 Array 却不能。

28. Java 序列化与反序列化是什么?

Java 序列化是指把 Java 对象转换为字节序列的过程,而 Java 反序列化是指把字节序列复原为 Java 对象的过程:

  • 序列化:序列化是把对象转换成有序字节流,以便在网络上传输或者保留在本地文件中。核心作用是对象状态的保留与重建。咱们都晓得,Java 对象是保留在 JVM 的堆内存中的,也就是说,如果 JVM 堆不存在了,那么对象也就跟着隐没了。

    而序列化提供了一种计划,能够让你在即便 JVM 停机的状况下也能把对象保留下来的计划。就像咱们平时用的 U 盘一样。把 Java 对象序列化成可存储或传输的模式(如二进制流),比方保留在文件中。这样,当再次须要这个对象的时候,从文件中读取出二进制流,再从二进制流中反序列化出对象。

  • 反序列化:客户端从文件中或网络上取得序列化后的对象字节流,依据字节流中所保留的对象状态及形容信息,通过反序列化重建对象。

29. 为什么须要序列化与反序列化?

简要形容:对内存中的对象进行长久化或网络传输, 这个时候都须要序列化和反序列化

深刻形容:

  1. 对象序列化能够实现分布式对象。

次要利用例如:RMI(即近程调用 Remote Method Invocation)要利用对象序列化运行近程主机上的服务,就像在本地机上运行对象时一样。

  1. java 对象序列化不仅保留一个对象的数据,而且递归保留对象援用的每个对象的数据。

能够将整个对象档次写入字节流中,能够保留在文件中或在网络连接上传递。利用对象序列化能够进行对象的 ” 深复制 ”,即复制对象自身及援用的对象自身。序列化一个对象可能失去整个对象序列。

  1. 序列化能够将内存中的类写入文件或数据库中。

比方:将某个类序列化后存为文件,下次读取时只需将文件中的数据反序列化就能够将原先的类还原到内存中。也能够将类序列化为流数据进行传输。

总的来说就是将一个曾经实例化的类转成文件存储,下次须要实例化的时候只有反序列化即可将类实例化到内存中并保留序列化时类中的所有变量和状态。

  1. 对象、文件、数据,有许多不同的格局,很难对立传输和保留。

序列化当前就都是字节流了,无论原来是什么货色,都能变成一样的货色,就能够进行通用的格局传输或保留,传输完结当前,要再次应用,就进行反序列化还原,这样对象还是对象,文件还是文件。

30. 序列化实现的形式有哪些?

实现 Serializable 接口或者 Externalizable 接口。

Serializable接口

类通过实现 java.io.Serializable 接口以启用其序列化性能 。可序列化类的所有子类型自身都是可序列化的。 序列化接口没有办法或字段,仅用于标识可序列化的语义。

如以下例子:

import java.io.Serializable;

public class User implements Serializable {
   private String name;
   private int age;
   public String getName() {return name;}
   public void setName(String name) {this.name = name;}

   @Override
   public String toString() {
       return "User{" +
               "name='" + name +
               '}';
   }
}

通过上面的代码进行序列化及反序列化:

public class SerializableDemo {public static void main(String[] args) {
       //Initializes The Object
       User user = new User();
       user.setName("cosen");
       System.out.println(user);

       //Write Obj to File
       try (FileOutputStream fos = new FileOutputStream("tempFile"); ObjectOutputStream oos = new ObjectOutputStream(fos)) {oos.writeObject(user);
       } catch (IOException e) {e.printStackTrace();
       }

       //Read Obj from File
       File file = new File("tempFile");
       try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {User newUser = (User)ois.readObject();
           System.out.println(newUser);
       } catch (IOException | ClassNotFoundException e) {e.printStackTrace();
       }
   }
}

//OutPut:
//User{name='cosen'}
//User{name='cosen'}

Externalizable接口

Externalizable继承自 Serializable,该接口中定义了两个形象办法:writeExternal()readExternal()

当应用 Externalizable 接口来进行序列化与反序列化的时候须要开发人员重写 writeExternal()readExternal()办法。否则所有变量的值都会变成默认值。

public class User implements Externalizable {

   private String name;
   private int age;

   public String getName() {return name;}
   public void setName(String name) {this.name = name;}
   public void writeExternal(ObjectOutput out) throws IOException {out.writeObject(name);
   }
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {name = (String) in.readObject();}

   @Override
   public String toString() {
       return "User{" +
               "name='" + name +
               '}';
   }
}

通过上面的代码进行序列化及反序列化:

public class ExternalizableDemo1 {public static void main(String[] args) {
      //Write Obj to file
      User user = new User();
      user.setName("cosen");
      try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"))){oos.writeObject(user);
      } catch (IOException e) {e.printStackTrace();
      }

      //Read Obj from file
      File file = new File("tempFile");
      try(ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file))){User newInstance = (User) ois.readObject();
          //output
          System.out.println(newInstance);
      } catch (IOException | ClassNotFoundException e) {e.printStackTrace();
      }
  }
}

//OutPut:
//User{name='cosen'}

两种序列化的比照

实现 Serializable 接口 实现 Externalizable 接口
零碎主动存储必要的信息 程序员决定存储哪些信息
Java 内建反对,易于实现,只须要实现该接口即可,无需任何代码反对 必须实现接口内的两个办法
性能略差 性能略好

31. 什么是 serialVersionUID?

serialVersionUID 用来表明类的不同版本间的兼容性

Java 的序列化机制是通过在运行时判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体(类)的 serialVersionUID 进行比拟,如果雷同就认为是统一的,能够进行反序列化,否则就会呈现序列化版本不统一的异样。

32. 为什么还要显示指定 serialVersionUID 的值?

如果不显示指定 serialVersionUID, JVM 在序列化时会依据属性主动生成一个 serialVersionUID, 而后与属性一起序列化, 再进行长久化或网络传输. 在反序列化时, JVM 会再依据属性主动生成一个新版 serialVersionUID, 而后将这个新版 serialVersionUID 与序列化时生成的旧版 serialVersionUID 进行比拟, 如果雷同则反序列化胜利, 否则报错.

如果显示指定了, JVM 在序列化和反序列化时依然都会生成一个 serialVersionUID, 但值为咱们显示指定的值, 这样在反序列化时新旧版本的 serialVersionUID 就统一了.

在理论开发中, 不显示指定 serialVersionUID 的状况会导致什么问题? 如果咱们的类写完后不再批改, 那当然不会有问题, 但这在理论开发中是不可能的, 咱们的类会一直迭代, 一旦类被批改了, 那旧对象反序列化就会报错. 所以在理论开发中, 咱们都会显示指定一个 serialVersionUID, 值是多少无所谓, 只有不变就行。

33. serialVersionUID 什么时候批改?

《阿里巴巴 Java 开发手册》中有以下规定:

想要深刻理解的小伙伴,能够看这篇文章:https://juejin.cn/post/684490…

34. Java 序列化中如果有些字段不想进行序列化,怎么办?

对于不想进行序列化的变量,应用 transient 关键字润饰。

transient 关键字的作用是控制变量的序列化,在变量申明前加上该关键字,能够阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。transient 只能润饰变量,不能润饰类和办法。

35. 动态变量会被序列化吗?

不会。因为序列化是针对对象而言的, 而动态变量优先于对象存在, 随着类的加载而加载, 所以不会被序列化.

看到这个论断, 是不是有人会问, serialVersionUID 也被 static 润饰, 为什么 serialVersionUID 会被序列化? 其实 serialVersionUID 属性并没有被序列化, JVM 在序列化对象时会主动生成一个 serialVersionUID, 而后将咱们显示指定的 serialVersionUID 属性值赋给主动生成的 serialVersionUID。

36. Error 和 Exception 区别是什么?

Java 中,所有的异样都有一个独特的先人 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类 Exception(异样)和 Error(谬误)。

ExceptionError 二者都是 Java 异样解决的重要子类,各自都蕴含大量子类。

  • Exception : 程序自身能够解决的异样,能够通过 catch 来进行捕捉,通常遇到这种谬误,应答其进行解决,使应用程序能够持续失常运行。Exception 又能够分为运行时异样 (RuntimeException, 又叫非受查看异样) 和非运行时异样(又叫受查看异样)。
  • ErrorError 属于程序无奈解决的谬误,咱们没方法通过 catch 来进行捕捉。例如,零碎解体,内存不足,堆栈溢出等,编译器不会对这类谬误进行检测,一旦这类谬误产生,通常应用程序会被终止,仅靠应用程序自身无奈复原。

37. 非受查看异样 (运行时异样) 和受查看异样 (个别异样) 区别是什么?

非受查看异样:包含 RuntimeException 类及其子类,示意 JVM 在运行期间可能呈现的异样。Java 编译器不会查看运行时异样。例如:NullPointException(空指针)NumberFormatException(字符串转换为数字)IndexOutOfBoundsException(数组越界)ClassCastException(类转换异样)ArrayStoreException(数据存储异样,操作数组时类型不统一)等。

受查看异样:是 Exception 中除 RuntimeException 及其子类之外的异样。Java 编译器会查看受查看异样。常见的受查看异样有:IO 相干的异样、ClassNotFoundExceptionSQLException等。

非受查看异样和受查看异样之间的区别:是否强制要求调用者必须解决此异样,如果强制要求调用者必须进行解决,那么就应用受查看异样,否则就抉择非受查看异样。

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

Java 中的异样解决除了包含捕捉异样和解决异样之外,还包含申明异样和拋出异样,能够通过 throws 关键字在办法上申明该办法要拋出的异样,或者在办法外部通过 throw 拋出异样对象。

throws 关键字和 throw 关键字在应用上的几点区别如下:

  • throw 关键字用在办法外部,只能用于抛出一种异样,用来抛出办法或代码块中的异样,受查异样和非受查异样都能够被抛出。
  • throws 关键字用在办法申明上,能够抛出多个异样,用来标识该办法可能抛出的异样列表。一个办法用 throws 标识了可能抛出的异样列表,调用该办法的办法中必须蕴含可解决异样的代码,否则也要在办法签名中用 throws 关键字申明相应的异样。

举例如下:

throw 关键字

public static void main(String[] args) {
        String s = "abc";
        if(s.equals("abc")) {throw new NumberFormatException();
        } else {System.out.println(s);
        }
        //function();}

throws 关键字

public static void function() throws NumberFormatException{
        String s = "abc";
        System.out.println(Double.parseDouble(s));
    }
    
    public static void main(String[] args) {
        try {function();
        } catch (NumberFormatException e) {System.err.println("非数据类型不能转换。");
            //e.printStackTrace();}
}

39. NoClassDefFoundError 和 ClassNotFoundException 区别?

NoClassDefFoundError 是一个 Error 类型的异样,是由 JVM 引起的,不应该尝试捕捉这个异样。引起该异样的起因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作产生在运行期间,即编译时该类存在,然而在运行时却找不到了,可能是变异后被删除了等起因导致。

ClassNotFoundException 是一个受查看异样,须要显式地应用 try-catch 对其进行捕捉和解决,或在办法签名中用 throws 关键字进行申明。当应用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 动静加载类到内存的时候,通过传入的类门路参数没有找到该类,就会抛出该异样;另一种抛出该异样的可能起因是某个类曾经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。

40. Java 常见异样有哪些?

  • java.lang.IllegalAccessError:守法拜访谬误。当一个利用试图拜访、批改某个类的域(Field)或者调用其办法,然而又违反域或办法的可见性申明,则抛出该异样。
  • java.lang.InstantiationError:实例化谬误。当一个利用试图通过 Java 的 new 操作符结构一个抽象类或者接口时抛出该异样.
  • java.lang.OutOfMemoryError:内存不足谬误。当可用内存不足以让 Java 虚拟机调配给一个对象时抛出该谬误。
  • java.lang.StackOverflowError:堆栈溢出谬误。当一个利用递归调用的档次太深而导致堆栈溢出或者陷入死循环时抛出该谬误。
  • java.lang.ClassCastException:类造型异样。假如有类 A 和 B(A 不是 B 的父类或子类),O 是 A 的实例,那么当强制将 O 结构为类 B 的实例时抛出该异样。该异样常常被称为强制类型转换异样。
  • java.lang.ClassNotFoundException:找不到类异样。当利用试图依据字符串模式的类名结构类,而在遍历 CLASSPAH 之后找不到对应名称的 class 文件时,抛出该异样。
  • java.lang.ArithmeticException:算术条件异样。譬如:整数除零等。
  • java.lang.ArrayIndexOutOfBoundsException:数组索引越界异样。当对数组的索引值为正数或大于等于数组大小时抛出。
  • java.lang.IndexOutOfBoundsException:索引越界异样。当拜访某个序列的索引值小于 0 或大于等于序列大小时,抛出该异样。
  • java.lang.InstantiationException:实例化异样。当试图通过 newInstance()办法创立某个类的实例,而该类是一个抽象类或接口时,抛出该异样。
  • java.lang.NoSuchFieldException:属性不存在异样。当拜访某个类的不存在的属性时抛出该异样。
  • java.lang.NoSuchMethodException:办法不存在异样。当拜访某个类的不存在的办法时抛出该异样。
  • java.lang.NullPointerException:空指针异样。当利用试图在要求应用对象的中央应用了 null 时,抛出该异样。譬如:调用 null 对象的实例办法、拜访 null 对象的属性、计算 null 对象的长度、应用 throw 语句抛出 null 等等。
  • java.lang.NumberFormatException:数字格局异样。当试图将一个 String 转换为指定的数字类型,而该字符串确不满足数字类型要求的格局时,抛出该异样。
  • java.lang.StringIndexOutOfBoundsException:字符串索引越界异样。当应用索引值拜访某个字符串中的字符,而该索引值小于 0 或大于等于序列大小时,抛出该异样。

41. try-catch-finally 中哪个局部能够省略?

catch 能够省略。更为严格的说法其实是:try 只适宜解决运行时异样,try+catch 适宜解决运行时异样 + 一般异样。也就是说,如果你只用 try 去解决一般异样却不加以 catch 解决,编译是通不过的,因为编译器硬性规定,一般异样如果抉择捕捉,则必须用 catch 显示申明以便进一步解决。而运行时异样在编译时没有如此规定,所以 catch 能够省略,你加上 catch 编译器也感觉无可非议。

实践上,编译器看任何代码都不悦目,都感觉可能有潜在的问题,所以你即便对所有代码加上 try,代码在运行期时也只不过是在失常运行的根底上加一层皮。然而你一旦对一段代码加上 try,就等于显示地承诺编译器,对这段代码可能抛出的异样进行捕捉而非向上抛出解决。如果是一般异样,编译器要求必须用 catch 捕捉以便进一步解决;如果运行时异样,捕捉而后抛弃并且 +finally 开头解决,或者加上 catch 捕捉以便进一步解决。

至于加上 finally,则是在不论有没捕捉异样,都要进行的“开头”解决。

42. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

会执行,在 return 前执行。

在 finally 中扭转返回值的做法是不好的,因为如果存在 finally 代码块,try 中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行结束之后再向调用者返回其值,而后如果在 finally 中批改了返回值,就会返回批改后的值。显然,在 finally 中返回或者批改返回值会对程序造成很大的困扰,Java 中也能够通过晋升编译器的语法查看级别来产生正告或谬误。
代码示例 1:

public static int getInt() {
    int a = 10;
    try {System.out.println(a / 0);
        a = 20;
    } catch (ArithmeticException e) {
        a = 30;
        return a;
        /*
         * return a 在程序执行到这一步的时候,这里不是 return a 而是 return 30;这个返回门路就造成了
         * 然而呢,它发现前面还有 finally,所以继续执行 finally 的内容,a=40
         * 再次回到以前的门路, 持续走 return 30,造成返回门路之后,这里的 a 就不是 a 变量了,而是常量 30
         */
    } finally {a = 40;}
    return a;
}

// 执行后果:30

代码示例 2:

public static int getInt() {
    int a = 10;
    try {System.out.println(a / 0);
        a = 20;
    } catch (ArithmeticException e) {
        a = 30;
        return a;
    } finally {
        a = 40;
        // 如果这样,就又从新造成了一条返回门路,因为只能通过 1 个 return 返回,所以这里间接返回 40
        return a; 
    }

}

// 执行后果:40

43. JVM 是如何解决异样的?

在一个办法中如果产生异样,这个办法会创立一个异样对象,并转交给 JVM,该异样对象蕴含异样名称,异样形容以及异样产生时应用程序的状态。创立异样对象并转交给 JVM 的过程称为抛出异样。可能有一系列的办法调用,最终才进入抛出异样的办法,这一系列办法调用的有序列表叫做调用栈。

JVM 会顺着调用栈去查找看是否有能够解决异样的代码,如果有,则调用异样解决代码。当 JVM 发现能够解决异样的代码时,会把产生的异样传递给它。如果 JVM 没有找到能够解决该异样的代码块,JVM 就会将该异样转交给默认的异样处理器(默认处理器为 JVM 的一部分),默认异样处理器打印出异样信息并终止应用程序。
想要深刻理解的小伙伴能够看这篇文章:https://www.cnblogs.com/qdhxh…

44. Java 的 IO 流分为几种?

  • 依照流的方向:输出流(inputStream)和输入流(outputStream);
  • 依照实现性能分:节点流(能够从或向一个特定的中央读写数据,如 FileReader)和解决流(是对一个已存在的流的连贯和封装,通过所封装的流的性能调用实现数据读写,BufferedReader);
  • 依照解决数据的单位:字节流和字符流。别离由四个抽象类来示意(每种流包含输出和输入两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java 中其余多种多样变动的流均是由它们派生进去的。

45. 字节流如何转为字符流?

字节输出流转字符输出流通过 InputStreamReader 实现,该类的构造函数能够传入 InputStream 对象。

字节输入流转字符输入流通过 OutputStreamWriter 实现,该类的构造函数能够传入 OutputStream 对象。

46. 字符流与字节流的区别?

  • 读写的时候字节流是按字节读写,字符流按字符读写。
  • 字节流适宜所有类型文件的数据传输,因为计算机字节(Byte)是电脑中示意信息含意的最小单位。字符流只可能解决纯文本数据,其余类型数据不行,然而字符流解决文本要比字节流解决文本要不便。
  • 在读写文件须要对内容按行解决,比方比拟特定字符,解决某一行数据的时候个别会抉择字符流。
  • 只是读写文件,和文件内容无关时,个别抉择字节流。

47. 什么是阻塞 IO?什么是非阻塞 IO?

IO 操作包含:对硬盘的读写、对 socket 的读写以及外设的读写。

当用户线程发动一个 IO 申请操作(本文以读申请操作为例),内核会去查看要读取的数据是否就绪,对于阻塞 IO 来说,如果数据没有就绪,则会始终在那期待,直到数据就绪;对于非阻塞 IO 来说,如果数据没有就绪,则会返回一个标记信息告知用户线程以后要读的数据没有就绪。当数据就绪之后,便将数据拷贝到用户线程,这样才实现了一个残缺的 IO 读申请操作,也就是说一个残缺的 IO 读申请操作包含两个阶段:

1)查看数据是否就绪;

2)进行数据拷贝(内核将数据拷贝到用户线程)。

那么阻塞(blocking IO)和非阻塞(non-blocking IO)的区别就在于第一个阶段,如果数据没有就绪,在查看数据是否就绪的过程中是始终期待,还是间接返回一个标记信息。

Java 中传统的 IO 都是阻塞 IO,比方通过 socket 来读数据,调用 read()办法之后,如果数据没有就绪,以后线程就会始终阻塞在 read 办法调用那里,直到有数据才返回;而如果是非阻塞 IO 的话,当数据没有就绪,read()办法应该返回一个标记信息,告知以后线程数据没有就绪,而不是始终在那里期待。

深刻理解可看这篇文章:https://mp.weixin.qq.com/s/p5…

48. BIO、NIO、AIO 的区别?

  • BIO:同步并阻塞,在服务器中实现的模式为 一个连贯一个线程 。也就是说,客户端有连贯申请的时候,服务器就须要启动一个线程进行解决,如果这个连贯不做任何事件会造成不必要的线程开销,当然这也能够通过线程池机制改善。BIO 个别实用于连贯数目小且固定的架构,这种形式对于服务器资源要求比拟高,而且并发局限于利用中,是 JDK1.4 之前的惟一抉择,但好在程序直观简略,易了解。
  • NIO:同步并非阻塞,在服务器中实现的模式为 一个申请一个线程,也就是说,客户端发送的连贯申请都会注册到多路复用器上,多路复用器轮询到有连贯 IO 申请时才会启动一个线程进行解决。NIO 个别实用于连贯数目多且连贯比拟短(轻操作)的架构,并发局限于利用中,编程比较复杂,从 JDK1.4 开始反对。
  • AIO:异步并非阻塞,在服务器中实现的模式为 一个无效申请一个线程,也就是说,客户端的 IO 申请都是通过操作系统先实现之后,再告诉服务器利用去启动线程进行解决。AIO 个别实用于连贯数目多且连贯比拟长(重操作)的架构,充沛调用操作系统参加并发操作,编程比较复杂,从 JDK1.7 开始反对。

49. Java IO 都有哪些设计模式?

应用了 适配器模式 装璜器模式

适配器模式

Reader reader = new INputStreamReader(inputStream);

把一个类的接口变换成客户端所期待的另一种接口,从而使本来因接口不匹配而无奈在一起工作的两个类可能在一起工作

  • 类适配器:Adapter 类(适配器)继承 Adaptee 类(源角色)实现 Target 接口(指标角色)
  • 对象适配器:Adapter 类(适配器)持有 Adaptee 类(源角色)对象实例,实现 Target 接口(指标角色)

装璜器模式

new BufferedInputStream(new FileInputStream(inputStream));

一种动静地往一个类中增加新的行为的设计模式。就性能而言,装璜器模式相比生成子类更为灵便,这样能够给某个对象而不是整个类增加一些性能。

  • ConcreteComponent(具体对象)和 Decorator(形象装璜器)实现雷同的 Conponent(接口)并且 Decorator(形象装璜器)外面持有 Conponent(接口)对象,能够传递申请。
  • ConcreteComponent(具体装璜器)笼罩 Decorator(形象装璜器)的办法并用 super 进行调用,传递申请。

这里也举荐一个我收集的计算机书籍仓库,仓库目前有上百本经典 cs 电子书,看经典的书籍会更悟得深~

点此链接即可中转书单,计算机必看经典书籍(含 pdf 下载)

Github 也有相应仓库,https://github.com/cosen1024/…
欢送 star。

退出移动版