面试官你好,我叫王志宇,就读于西安电子科技大学,是研二在读的一名学生。我今天应聘的是Java后端开发岗位。学习Java源于兴趣,自己在平时也学习了一些后端开发的基础知识,并且在开源社区上也发了10多篇学习笔记。项目方面自己做了一个Echo社区平台,他可以实现登录、注册、发帖、评论、回复等功能,并且我在后台也用Redis做了一些优化,并且利用Kafka、ES组件实现了系统通知、关键词搜索等功能。社团活动的话,自己在大学期间参加了勤工助学部门的宣传部,主要是来负责微信公众号的运营等方面。我的大体情况就是这样,感谢公司能够给我这一次面试机会。

Java概述

Java语言有哪些特点?

  • 面向对象(封装,继承,多态);
  • 平台无关性,平台无关性的具体表现在于,Java 是“一次编写,到处运行(Write Once,Run any Where)”的语言,因此采用 Java 语言编写的程序具有很好的可移植性,而保证这一点的正是 Java 的虚拟机机制。在引入虚拟机之后,Java 语言在不同的平台上运行不需要重新编译。
  • 可靠性、安全性;
  • 支持多线程。C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持;
  • 支持网络编程并且很方便。Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便;
  • 编译与解释并存;

    Java和C++有什么关系,它们有什么区别?

  • 都是面向对象的语言,都支持封装、继承和多态;

  • C++ 支持指针,而 Java 没有指针的概念;
  • C++ 支持多继承,而 Java 不支持多重继承,但允许一个类实现多个接口;
  • Java 是完全面向对象的语言,并且还取消了 C/C++ 中的结构和联合,使编译程序更加简洁;
  • Java 自动进行无用内存回收操作,不再需要程序员进行手动删除,而 C++ 中必须由程序释放内存资源,这就增加了程序员的负担。
  • Java 不支持操作符重载,操作符重载则被认为是 C++ 的突出特征;
  • Java 允许预处理,但不支持预处理器功能,所以为了实现预处理,它提供了引入语句(import),但它与 C++ 预处理器的功能类似;
  • Java 不支持缺省参数函数,而 C++ 支持;
  • C 和 C++ 不支持字符串变量,在 C 和 C++ 程序中使用“Null”终止符代表字符串的结束。在 Java 中字符串是用类对象(String 和 StringBuffer)来实现的;
  • goto 语句是 C 和 C++ 的“遗物”,Java 不提供 goto 语句,虽然 Java 指定 goto 作为关键字,但不支持它的使用,这使程序更简洁易读;
  • Java 不支持 C++ 中的自动强制类型转换,如果需要,必须由程序显式进行强制类型转换。

    JVM、JRE和JDK的关系是什么?

    JDK是(Java Development Kit)的缩写,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
    JRE是Java Runtime Environment缩写,它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。
    JDK包含JRE,JRE包含JVM。
    image.png

    什么是字节码?

    Java之所以可以“一次编译,到处运行”,一是因为JVM针对各种操作系统、平台都进行了定制,二是因为无论在什么平台,都可以编译生成固定格式的字节码(.class文件)供JVM使用。因此,也可以看出字节码对于Java生态的重要性。
    之所以被称之为字节码,是因为字节码文件由十六进制值组成,而JVM以两个十六进制值为一组,即以字节为单位进行读取。在Java中一般是用javac命令编译源代码为字节码文件。

    采用字节码的好处是什么?

    Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。

    基础语法

    Java有哪些数据类型?

    Java 语言的数据类型分为两种:基本数据类型和引用数据类型。
    image.png
基本类型 位数 字节 默认值
int 32 4 0
short 16 2 0
long 64 8 0L
byte 8 1 0
char 16 2 ‘u0000’
float 32 4 0f
double 64 8 0d
boolean 1 false

switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上?

Java5 以前 switch(expr)中,expr 只能是 byte、short、char、int。
从 Java 5 开始,Java 中引入了枚举类型, expr 也可以是 enum 类型。
从 Java 7 开始,expr还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。

访问修饰符public、private、protected、以及不写(默认)时的区别?

  • default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  • public : 对所有类可见。使用对象:类、接口、变量、方法
  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

    关键字

    final、finally、finalize的区别?

  • final 可以⽤来修饰类、⽅法、变量,分别有不同的意义。修饰class 类时表明这个类不能被继承;修饰的变量不可以修改;修饰⽅法的时候,表明这个⽅法不能被重写。

  • finally 则是 Java 保证重点代码⼀定要被执⾏的⼀种机制。可以使⽤ try-finally 或者try-catch-finally 来进⾏类似关闭 JDBC 连接、保证 unlock 锁等动作。
  • finalize 是基础类 java.lang.Object 的⼀个⽅法,它的设计⽬的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使⽤,并且在 JDK 9 开始被标记为deprecated

    为什么要用static关键字?

    通常来说,用new创建类的对象时,数据存储空间才被分配,方法才供外界调用。但有时我们只想为特定域分配单一存储空间,不考虑要创建多少对象或者说根本就不创建任何对象,再就是我们想在没有创建对象的情况下也想调用方法。在这两种情况下,static关键字,满足了我们的需求。

    “static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?

    “static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。
    Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。

    是否可以在static环境中访问非static变量?

    static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对static变量进行初始化。如果你的代码尝试不用实例来访问非static的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。

    static静态方法能不能引用非静态资源?

    不能,new的时候才会产生的东西,对于初始化后就存在的静态资源来说,根本不认识它。

    static静态方法里面能不能引用静态资源?

    可以,因为都是类初始化的时候加载的,大家相互都认识。

    java静态变量、代码块、和静态方法的执行顺序是什么?

    基本上代码块分为三种:Static静态代码块、构造代码块、普通代码块
    代码块执行顺序静态代码块——> 构造代码块 ——> 构造函数——> 普通代码块
    继承中代码块执行顺序:父类静态块——>子类静态块——>父类代码块——>父类构造器——>子类代码块——>子类构造器

    面向对象

    面向对象和面向过程的区别?

    面向过程

  • 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。

  • 缺点:没有面向对象易维护、易复用、易扩展。

面向对象

  • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
  • 缺点:性能比面向过程低。

    讲讲面向对象三大特性

    Java语⾔的三⼤特性分别是封装、继承和多态。

  • 封装是指将对象的属性私有化,提供⼀些可以访问属性的⽅法,我们通过访问这些⽅法得到对象的属性。

  • 继承是指某新类继承已经存在的类,该新类拥有被继承的类的所有属性和⽅法,并且新类可以根据⾃⼰的情况拓展属性或⽅法。其中新类称为⼦类,原存在的类被称为⽗类。

注意:Java不⽀持多继承

  • 多态是同⼀个⾏为具有多个不同表现形式或形态的能⼒。多态就是同⼀个接口,使⽤不同的实例⽽执⾏不同操作

在 Java 中有两种形式可以实现多态:继承(多个⼦类对同⼀⽅法的重写)和接口(实现接口并覆盖接口中同⼀⽅法)。

Java语言是如何实现多态的?

本质上多态分两种:
1、编译时多态(又称静态多态)
2、运行时多态(又称动态多态)
重载(overload)就是编译时多态的一个例子,编译时多态在编译时就已经确定,运行的时候调用的是确定的方法。
我们通常所说的多态指的都是运行时多态,也就是编译时不确定究竟调用哪个具体方法,一直延迟到运行时才能确定。这也是为什么有时候多态方法又被称为延迟方法的原因。
Java实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。

  • 继承:在多态中必须存在有继承关系的子类和父类。
  • 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
  • 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。

    重载(Overload)和重写(Override)的区别是什么?

    方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

  • 重载是发⽣在同⼀个类中,具有相同的⽅法名,但是有不同的参数,参数的个数不⼀样、参数的位置不⼀样,这就叫重载,常见的就⽐如构造⽅法,有有参构造和⽆参构造。

  • 重写是发⽣在当⼦类继承⽗类时,对⽗类中的⼀些⽅法根据⾃⼰的需求进⾏重写操作。

image.png

重载的方法能否根据返回值类型进行区分?

不能根据返回值类型来区分重载的方法。因为调用时不指定类型信息,编译器不知道你要调用哪个函数。
当调用max(1,2);时无法确定调用的是哪个,单从这一点上来说,仅返回值类型不同的重载是不应该允许的。

构造器(constructor)是否可被重写(override)?

构造器不能被继承,因此不能被重写,但可以被重载。每一个类必须有自己的构造函数,负责构造自己这部分的构造。子类不会覆盖父类的构造函数,相反必须一开始调用父类的构造函数。

接口和抽象类有什么共同点和区别?

相同点:
(1)都不能被实例化
(2)接口的实现类或抽象类的⼦类都只有实现了接口或抽象类中的⽅法后才能实例化。
不同点:
(1)接口只有定义,不能有⽅法的实现,但java 1.8中可以定义default⽅法体,⽽抽象类可以有定义与实现,⽅法可在抽象类中实现。
(2)实现接口的关键字为implements,继承抽象类的关键字为extends。⼀个类可以实现多个接口,但⼀个类只能继承⼀个抽象类。所以,使⽤接口可以间接地实现多重继承。
(3)接口强调特定功能的实现,而抽象类强调所属关系。
(4)接口方法默认修饰符是 public,抽象⽅法可以有 public、protected 和 default 这些修饰符(抽象方法就是为了被重写所以不能使⽤ private关键字修饰!)。
(5)接口被⽤于常⽤的功能,便于⽇后维护和添加删除,⽽抽象类更倾向于充当公共类的⾓⾊,不适⽤于⽇后重新对⽴⾯的代码修改。从设计层⾯来说,抽象是对类的抽象,是⼀种模板设计,⽽接口是对⾏为的抽象,是⼀种⾏为的规范。

抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类

Java中的内部类

内部类有四种,分别是静态内部类、局部内部类、匿名内部和成员内部类

  • 静态内部类:常见的main函数就是静态内部类,调⽤静态内部类通过“外部类.静态内部类”
  • 局部内部类:定义在⽅法中的类叫做局部内部类。
  • 匿名内部类:是指继承⼀个⽗类或者实现⼀个接⼜的⽅式直接定义并使⽤的类,匿名内部类没有class关键字,因为匿名内部类直接使⽤new⽣成⼀个对象
  • 成员内部类:最常见的内部类就是成员内部类,也称作普通内部类。定义了成员内部类后,必须使用外部类对象来创建内部类对象,而不能直接去 new 一个内部类对象

    java 创建对象有哪几种方式?

    java中提供了以下四种创建对象的方式:

  • new创建新对象

  • 通过反射机制
  • 采用clone机制
  • 通过序列化机制

前两者都需要显式地调用构造方法。对于clone机制,需要注意浅拷贝和深拷贝的区别,对于序列化机制需要明确其实现原理,在java中序列化可以通过实现Externalizable或者Serializable来实现。

深拷贝和浅拷贝区别了解吗?什么是引用拷贝?

关于深拷贝和浅拷贝区别,我这里先给结论:

  • 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
  • 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
  • 那什么是引用拷贝呢? 简单来说,引用拷贝就是两个不同的引用指向同一个对象。

image.png

深拷贝的实现方式有哪些?

深拷贝的实现⽅式主要通过序列化递归实现
(1)序列化:以使⽤json序列化为例,通过将原对象序列化成json格式,然后通过json的反序列化功能,⾃动创建新的内存来存放新对象
(2)递归实现:递归实现是深拷贝的常⽤解决⽅式,利⽤层层递归的⽅式,拷贝所有内部的成员对象。

什么是不可变对象?好处是什么?

不可变对象指对象一旦被创建,状态就不能再改变,任何修改都会创建一个新的对象,如 String、Integer及其它包装类.不可变对象最大的好处是线程安全.

值传递和引用传递的区别的什么?为什么说Java中只有值传递?

值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相关了。
引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的是引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。
基本类型作为参数被传递时肯定是值传递;引用类型作为参数被传递时也是值传递,只不过“值”为对应的引用。

对象相等判断

== 和 equals 区别是什么?

== : 它的作⽤是判断两个对象的地址是不是相等。即,判断两个对象是不是同⼀个对象(基本数据类型==⽐较的是值,引⽤数据类型==⽐较的是内存地址)。
eaquels:
情况 1:类没有重写 equals() ⽅法。则通过 equals() ⽐较该类的两个对象时,等价于通过“==”⽐较这两个对象。
情况 2:类重写了 equals() ⽅法。⼀般,我们都重写 equals() ⽅法来⽐较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。

hashCode(),equals()两种方法是什么关系?

  • 如果两个对象相等,则hashcode一定也是相同的;
  • 两个对象相等,对两个对象分别调用equals方法都返回true;
  • 两个对象有相同的hashcode值,它们也不一定是相等的;

    为什么重写 equals 方法必须重写 hashcode 方法 ?

    判断的时候先根据hashcode进行的判断,相同的情况下再根据equals()方法进行判断。如果只重写了equals方法,而不重写hashcode的方法,会造成hashcode的值不同,而equals()方法判断出来的结果为true。
    在Java中的一些容器中,不允许有两个完全相同的对象,插入的时候,如果判断相同则会进行覆盖。这时候如果只重写了equals()的方法,而不重写hashcode的方法,Object中hashcode是根据对象的存储地址转换而形成的一个哈希值。这时候就有可能因为没有重写hashcode方法,造成相同的对象散列到不同的位置而造成对象的不能覆盖的问题。

    String,StringBuffer, StringBuilder 的区别是什么?

    1.可变与不可变。
    String类中使用字符数组保存字符串,因为有“final”修饰符,所以string对象是不可变的。对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去.
    String类利用了final修饰的char类型数组存储字符,源码如下:
    private final char value[];
    StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,这两种对象都是可变的。
    源码如下:
    char[] value;
    2.是否多线程安全。
    String中的对象是不可变的,也就可以理解为常量,显然线程安全。
    StringBuilder是非线程安全的。
    StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
    3.性能
    每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

    String为什么要设计成不可变的?

    1.便于实现字符串池(String pool)
    在Java中,由于会大量的使用String常量,如果每一次声明一个String都创建一个String对象,那将会造成极大的空间资源的浪费。Java提出了String pool的概念,在堆中开辟一块存储空间String pool,当初始化一个String变量时,如果该字符串已经存在了,就不会去创建一个新的字符串变量,而是会返回已经存在了的字符串的引用。
    如果字符串是可变的,某一个字符串变量改变了其值,那么其指向的变量的值也会改变,String pool将不能够实现!
    2.使多线程安全
    在并发场景下,多个线程同时读一个资源,是安全的,不会引发竞争,但对资源进行写操作时是不安全的,不可变对象不能被写,所以保证了多线程的安全。
    3.避免安全问题
    在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。
    4.加快字符串处理速度
    由于String是不可变的,保证了hashcode的唯一性,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。

    String

    String在JDK9之后的改变?

    底层由char数组变为byte数组。

    什么是字符串常量池?

    java中常量池的概念主要有三个:全局字符串常量池,class文件常量池,运行时常量池。我们现在所说的就是全局字符串常量池,对这个想弄明白的同学可以看这篇Java中几种常量池的区分。
    jvm为了提升性能和减少内存开销,避免字符的重复创建,其维护了一块特殊的内存空间,即字符串池,当需要使用字符串时,先去字符串池中查看该字符串是否已经存在,如果存在,则可以直接使用,如果不存在,初始化,并将该字符串放入字符串常量池中。
    字符串常量池的位置也是随着jdk版本的不同而位置不同。在jdk6中,常量池的位置在永久代(方法区)中,此时常量池中存储的是对象。在jdk7中,常量池的位置在堆中,此时,常量池存储的就是引用了。在jdk8中,永久代(方法区)被元空间取代了

    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”字符串,程序都会在堆内存中开辟一片新空间存放新对象。

    在使用 HashMap 的时候,用 String 做 key 有什么好处?

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

    包装类型

    包装类型是什么?基本类型和包装类型有什么区别?

    Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,把基本类型转换成包装类型的过程叫做装箱(boxing);反之,把包装类型转换成基本类型的过程叫做拆箱(unboxing),使得二者可以相互转换。
    基本类型和包装类型的区别主要有以下 几点

  • 包装类型可以为 null,而基本类型不可以。它使得包装类型可以应用于 POJO 中,而基本类型则不行。那为什么 POJO 的属性必须要用包装类型呢?《阿里巴巴 Java 开发手册》上有详细的说明, 数据库的查询结果可能是 null,如果使用基本类型的话,因为要自动拆箱(将包装类型转为基本类型,比如说把 Integer 对象转换成 int 值),就会抛出 NullPointerException 的异常。

  • 包装类型可用于泛型,而基本类型不可以。泛型不能使用基本类型,因为使用基本类型时会编译出错。
  • 基本类型比包装类型更高效。基本类型在栈中直接存储的具体数值,而包装类型则存储的是堆中的引用。 很显然,相比较于基本类型而言,包装类型需要占用更多的内存空间。

    解释一下自动装箱和自动拆箱?

    自动装箱:将基本数据类型重新转化为对象
    自动拆箱:将对象重新转化为基本数据类型

    两个非new生成的Integer对象的对比

    对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false
    当值在 -128 ~ 127之间时,java会进行自动装箱,然后会对值进行缓存,如果下次再有相同的值,会直接在缓存中取出使用。缓存是通过Integer的内部类IntegerCache来完成的。当值超出此范围,会在堆中new出一个对象来存储。
    image.png

    反射

    什么是反射机制?

    JAVA反射机制是在程序运行过程中,对于任意一个类或对象,都能够知道这个类或对象的所有属性和方法,这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
    静态编译和动态编译

  • 静态编译:在编译时确定类型,绑定对象

  • 动态编译:在运行时确定类型,绑定对象

    反射机制优缺点

  • 优点 :运行期类型的判断,动态加载类,提高代码的灵活性。

  • 缺点:性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。

    反射为什么慢

  1. 反射调用过程中会产生大量的临时对象,这些对象会占用内存,可能会导致频繁 gc,从而影响性能。
  2. 反射调用方法时会从方法数组中遍历查找,并且检查可见性等操作会比较耗时。
  3. 反射在达到一定次数时,会动态编写字节码并加载到内存中,这个字节码没有经过编译器优化,也不能享受 JIT 优化。
  4. 反射一般会涉及自动装箱/拆箱和类型转换,都会带来一定的资源开销。

    反射的应用场景

    像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
    但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
    这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。
    另外,像 Java 中的一大利器 注解 的实现也用到了反射。

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

    ```java //Class.forName(“类的路径”);当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。 Class clz = Class.forName(“java.lang.String”);

//类名.class。这种方法只适合在编译前就知道操作的 Class。 Class clz = String.class;

//对象名.getClass()。 String str = new String(“Hello”); Class clz = str.getClass();

//如果是基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象。 ```

异常

image.png

Exception 和 Error 有什么区别?

  • Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
  • Error :Error 属于程序无法处理的错误 ,不建议通过catch捕获 。例如 Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。

    什么是运行时异常,编译时异常?什么是受检异常与非受检异常?

    运行时异常 = 非受检异常
    编译时异常 = 受检异常
    受检异常
    编译器要求必须处理的异常。Exception 中除 RuntimeException 及其子类之外的异常都属于受检异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。
    非受检异常
    编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException及其子类)和错误(Error)。
    RuntimeException及其子类都统称为非受检查异常,常见的有(建议记下来,日常开发中会经常用到):

  • NullPointerException(空指针错误)

  • IllegalArgumentException(参数错误比如方法入参类型错误)
  • NumberFormatException(字符串转换为数字格式错误,IllegalArgumentException的子类)
  • ArrayIndexOutOfBoundsException(数组越界错误)
  • ClassCastException(类型转换错误)
  • ArithmeticException(算术错误)
  • SecurityException(安全错误比如权限不够)
  • UnsupportedOperationException(不支持的操作错误比如重复创建同一用户)

    Throwable 类常用方法有哪些?

  • String getMessage(): 返回异常发生时的简要描述

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

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

    答:会执行,在 return 前执行。
    注意:在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,如果在 finally 中修改了返回值,就会返回修改后的值。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也可以通过提升编译器的语法检查级别来产生警告或错误。

    finally 中的代码一定会执行吗?

    不一定的!在某些情况下,finally 中的代码不会被执行。
    就比如说 finally 之前虚拟机被终止运行的话,finally 中的代码就不会被执行。
    另外,在以下 2 种特殊情况下,finally 块的代码也不会被执行:
  1. 程序所在的线程死亡。
  2. 关闭 CPU。

    throw 和 throws 的区别是什么?

    Java 中的异常处理除了包括捕获和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在方法上声明该方法要拋出的异常,或者在方法内部通过 throw 拋出异常。
    throws 关键字和 throw 关键字在使用上的几点区别如下
  • throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常。
  • throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。调用该方法的方法必须包含可处理异常的代码,否则也要在方法声明中用 throws 关键字声明相应的异常。

    IO

    什么是序列化?什么是反序列化?

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

  • 序列化: 将数据结构或对象转换成二进制字节流的过程ObjectOutputStream.writeObject()

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

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

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

对于不想进行序列化的变量,使用 transient关键字修饰。
transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient修饰的变量值不会被持久化和恢复。
关于 transient还有几点注意:

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

    序列化实现的方式有哪些?

    实现Serializable接口或者Externalizable接口。

    Java 中 IO 流分为几种?

  • 按照流的流向分,可以分为输入流和输出流;

  • 按照操作单元划分,可以划分为字节流和字符流;
  • 按照流的角色划分为节点流和处理流。

Java基础 - 图7

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
  • InputStreamReader 实现从字节流解码成字符流;
  • OutputStreamWriter 实现字符流编码成为字节流。

    Java 中 3 种常见 IO 模型

    BIO (Blocking I/O)

    BIO 属于同步阻塞 IO 模型
    同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。
    在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
    Java基础 - 图8

    NIO (Non-blocking/New I/O)

    Java 中的 NIO 于 Java 1.4 中引入,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它是支持面向缓冲的,基于通道的 I/O 操作方法。 对于高负载、高并发的(网络)应用,应使用 NIO 。
    Java 中的 NIO 可以看作是 I/O 多路复用模型。也有很多人认为,Java 中的 NIO 属于同步非阻塞 IO 模型。
    Java基础 - 图9
    IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间 -> 用户空间)还是阻塞的。
    IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。
    Java 中的 NIO ,有一个非常重要的选择器 ( Selector ) 的概念,也可以被称为 多路复用器。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。
    Java基础 - 图10

    AIO (Asynchronous I/O)

    AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。
    异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
    Java基础 - 图11

    BIO/NIO/AIO三者的区别

    image.png

  • NIO 是非阻塞的;

  • NIO 面向块,BIO 面向流。
  • AIO 的应用还不是很广泛。