《八股文》Java基础,温故而知新

《八股文》Java基础,温故而知新

参考:三分恶、疯狂Java讲义、图解Java

1. Java语言有哪些特点?

Java语言有很多优秀(可吹)的特点,以下几个是比较突出的:

在这里插入图片描述

  • 面向对象(封装,继承,多态);

  • 平台无关性,平台无关性的具体表现在于,Java 是“一次编写,到处运行(Write Once,Run any Where)”的语言,因此采用 Java 语言编写的程序具有很好的可移植性,而保证这一点的正是 Java 的虚拟机机制。

  • 支持多线程。C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持;

  • 编译与解释并存;

2. JVM、JDK 和 JRE 有什么区别?

JVMJava Virtual Machine,Java虚拟机,Java程序运行在Java虚拟机上。针对不同系统的实现(Windows,Linux,macOS)不同的JVM,因此Java语言可以实现跨平台。

JREJava Runtime Environment,Java 运⾏时环境。它是运⾏已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,Java 命令和其他的⼀些基础构件。但是,它不能⽤于创建新程序。

JDK: Java Development Kit,它是功能⻬全的 Java SDK。它拥有 JRE 所拥有的⼀切,还有编译器(javac)和⼯具(如 javadoc 和 jdb)。它能够创建和编译程序。

简单来说,JDK包含JRE,JRE包含JVM。
在这里插入图片描述

3. 说说什么是跨平台性?原理是什么

所谓跨平台性,是指Java语言编写的程序,一次编译后,可以在多个系统平台上运行。

实现原理:Java程序是通过Java虚拟机在系统平台上运行的,只要该系统可以安装相应的Java虚拟机,该系统就可以运行java程序。

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

所谓的字节码,就是Java程序经过编译之类产生的.class文件,字节码能够被虚拟机识别,从而实现Java程序的跨平台性。

Java 程序从源代码到运行主要有三步

  • 编译:将我们的代码(.java)编译成虚拟机可以识别理解的字节码(.class)

  • 解释:虚拟机执行Java字节码,将字节码翻译成机器能识别的机器码

  • 执行:对应的机器执行二进制机器码

在这里插入图片描述
只需要把Java程序编译成Java虚拟机能识别的Java字节码,不同的平台安装对应的Java虚拟机,这样就可以可以实现Java语言的平台无关性。

5. 为什么说 Java 语言“编译与解释并存”?

高级编程语言按照程序的执行方式分为编译型解释型两种。

简单来说,

  • 编译型语言是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码;

  • 解释型语言是指解释器对源程序逐行解释成特定平台的机器码并立即执行。

比如,你想读一本外国的小说,你可以找一个翻译人员帮助你翻译,有两种选择方式,你可以先等翻译人员将全本的小说(也就是源码)都翻译成汉语,再去阅读,也可以让翻译人员翻译一段,你在旁边阅读一段,慢慢把书读完。

Java 语言既具有编译型语言的特征,也具有解释型语言的特征,因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(.class 文件),这种字节码必须再经过JVM,解释成操作系统能识别的机器码,在由操作系统执行。因此,我们可以认为 Java 语言编译与解释并存。
在这里插入图片描述

6. Java有哪些数据类型?

定义:Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。

Java语言数据类型分为两种:基本数据类型引用数据类型
在这里插入图片描述
基本数据类型

  • 数值型

    • 整数类型(byte、short、int、long

    • 浮点类型(float、double

  • 字符型(char

  • 布尔型(boolean

Java基本数据类型范围和默认值:
在这里插入图片描述
引用数据类型

  • 类(class

  • 接口(interface

  • 数组([])

7. 自动类型转换、强制类型转换?看看这几行代码?

Java 所有的数值型变量可以相互转换,当把一个表数范围小的数值或变量直接赋给另一个表数范围大的变量时,可以进行自动类型转换;反之,需要强制转换。
在这里插入图片描述
这就好像,小杯里的水倒进大杯没问题,但大杯的水倒进小杯就不行了,可能会溢出。

8. 什么是自动拆箱/封箱?

  • 装箱:将基本类型用它们对应的引用类型包装起来;

  • 拆箱:将包装类型转换为基本数据类型;

Java可以自动对基本数据类型和它们的包装类进行装箱和拆箱。
在这里插入图片描述

Integer i = 10;  //装箱
int n = i;   //拆箱

9. &和&&有什么区别?

&运算符有两种用法:短路与逻辑与

&&之所以称为短路运算是因为,如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&

例如在验证用户登录时判定用户名不是 null 而且不是空字符串,应当写为username != null && !username.equals(""),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的 equals 比较,否则会产生 NullPointerException 异常。

10. switch 是否能作用在 byte/long/String上?

Java5 以前 switch(expr)中,expr 只能是 byte、short、char、int

从 Java 5 开始,Java 中引入了枚举类型, expr 也可以是 enum 类型

从 Java 7 开始,expr还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。

11. break ,continue ,return 的区别及作用?

  • break 跳出整个循环,不再执行循环(结束当前的循环体)

  • continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)

  • return 结束当前的方法

在这里插入图片描述

12. 用最有效率的方法计算2乘以8?

2 << 3位运算,数字的二进制位左移三位相当于乘以2的三次方。

13. 说说自增自减运算?看下这几个代码运行结果?

++--运算符可以放在变量之前,也可以放在变量之后。

当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减。

例如,当 b = ++a 时,先自增(自己增加 1),再赋值(赋值给 b);当 b = a++ 时,先赋值(赋值给 b),再自增(自己增加 1)。也就是,++a 输出的是 a+1 的值,a++输出的是 a 值。

用一句口诀就是:“符号在前就先加/减,符号在后就后加/减”。

int i  = 1;
i = i++;
System.out.println(i); // 1
int count = 0;
for(int i = 0; i < 100; i++) {
    count = count++;
}
System.out.println("count = "+count); // 0

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

面向过程 :面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的一次调用就可以。

面向对象 :面向对象,把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述某个事件在解决整个问题的过程所发生的行为。目的是为了写出通用的代码,加强代码的重用,屏蔽差异性。

15. 面向对象有哪些特性

1、封装

封装把⼀个对象的属性私有化,同时提供⼀些可以被外界访问的属性的⽅法。

2、继承

继承是使⽤已存在的类的定义作为基础创建新的类,新类的定义可以增加新的属性或新的方法,也可以继承父类的属性和方法。通过继承可以很方便地进行代码复用。

关于继承有以下三个要点:

  1. ⼦类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⽗类中的私有属性和⽅法⼦类是⽆法访问,只是拥有。

  2. ⼦类可以拥有⾃⼰属性和⽅法,即⼦类可以对⽗类进⾏扩展。

  3. ⼦类可以⽤⾃⼰的⽅式实现⽗类的⽅法。

3、多态

所谓多态就是指程序中定义的引⽤变量所指向的具体类型和通过该引⽤变量发出的⽅法调⽤在编程时并不确定,⽽是在程序运⾏期间才确定,即⼀个引⽤变量到底会指向哪个类的实例对象,该引⽤变量发出的⽅法调⽤到底是哪个类中实现的⽅法,必须在由程序运⾏期间才能决定。

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

16. 重载(overload)和重写(override)的区别?

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

  • 重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;

  • 重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。

方法重载的规则:

  1. 方法名一致,参数列表中参数的顺序,类型,个数不同。

  2. 重载与方法的返回值无关,存在于父类和子类,同类中。

  3. 可以抛出不同的异常,可以有不同修饰符。

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

Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

  • default (即默认,什么也不写):在同一包内可见,不使用任何修饰符。可以修饰在类、接口、变量、方法。

  • private:在同一类内可见。可以修饰变量、方法。注意:不能修饰类(外部类)

  • public:对所有类可见。可以修饰类、接口、变量、方法

  • protected:对同一包内的类和所有子类可见。可以修饰变量、方法。注意:不能修饰类(外部类)。

在这里插入图片描述

18. this关键字有什么作用?

this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针

this的用法在Java中大体可以分为3种:

  1. 普通的直接引用,this相当于是指向当前对象本身

  2. 形参与成员变量名字重名,用this来区分:

    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }
    
  3. 引用本类的构造函数

19. 抽象类(abstract class)和接口(interface)有什么区别?

  1. 接口的⽅法默认是 public ,所有⽅法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。

  2. 接口中除了 staticfinal 变量,不能有其他变量,而抽象类中则不⼀定。

  3. ⼀个类可以实现多个接口,但只能实现⼀个抽象类。接口自己本身可以通过 extends 关键字扩展多个接口。

  4. 接口方法默认修饰符是 public ,抽象方法可以有 public 、 protected 和 default 这些修饰符(抽象方法就是为了被重写所以不能使用 private 关键字修饰)。

  5. 从设计层⾯来说,抽象是对类的抽象,是⼀种模板设计,而接口是对行为的抽象,是⼀种行为的规范。

总结⼀下 jdk7~jdk9 Java 中接口的变化:

jdk 7 或更早版本中,接口里面只能有常量变量和抽象⽅法。这些接口方法必须由选择实现接口的类实现。

jdk 8 的时候接口可以有默认方法和静态方法功能。

jdk 9 在接口中引入了私有方法和私有静态方法。

20. 成员变量与局部变量的区别有哪些?

  1. 从语法形式上看:成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public , private , static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。

  2. 从变量在内存中的存储⽅式来看:如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。对象存于堆内存,如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引用数据类型,那存放的是指向堆内存对象的引用或者是指向常量池中的地址。

  3. 从变量在内存中的⽣存时间上看:成员变量是对象的⼀部分,它随着对象的创建而存在,而局部变量随着⽅法的调用而自动消失。

  4. 成员变量如果没有被赋初值:则会⾃动以类型的默认值而赋值(⼀种情况例外:被 final 修饰的成员变量必须显式地赋值),而局部变量则不会自动赋值。

21. 静态变量和实例变量的区别?静态方法、实例方法呢?

静态变量:是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个副本。

实例变量:必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。

类似地。

静态方法:static修饰的方法,也被称为类方法。在外部调⽤静态方法时,可以使⽤"类名.⽅法名"的方式,也可以使⽤"对象名.⽅法名"的方式。静态方法里不能访问类的非静态成员变量和方法

实例方法:依存于类的实例,需要使用"对象名.⽅法名"的方式调用;可以访问类的所有成员变量和方法。

22. final关键字有什么作用?

final表示不可变的意思,可用于修饰类、属性和方法:

  • 被final修饰的类不可以被继承

  • 被final修饰的方法不可以被重写

  • 被final修饰的变量不可变,被final修饰的变量必须被显式第指定初始值,还得注意的是,这里的不可变指的是变量的引用不可变,不是引用指向的内容的不可变

例如:

final StringBuilder sb = new StringBuilder("abc");
sb.append("d");
System.out.println(sb);  //abcd

在这里插入图片描述

23. final、finally、finalize的区别?

  • final 用于修饰变量、方法和类:final修饰的类不可被继承;修饰的方法不可被重写;修饰的变量不可变。

  • finally 作为异常处理的一部分,它只能在 try/catch 语句中,并且附带一个语句块表示这段语句最终一定被执行(无论是否抛出异常),经常被用在需要释放资源的情况下,System.exit(0) 可以阻断 finally 执行。

  • finalize 是在 java.lang.Object 里定义的方法,也就是说每一个对象都有这么个方法,这个方法在 gc 启动,该对象被回收的时候被调用。

    一个对象的 finalize 方法只会被调用一次,finalize 被调用不一定会立即回收该对象,所以有可能调用 finalize 后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会再次调用 finalize 了,进而产生问题,因此不推荐使用 finalize 方法。

24. == 和 equals 的区别?

== : 它的作⽤是判断两个对象的地址是不是相等。即,判断两个对象是不是同⼀个对象(基本数据类型==比较的是值,引⽤数据类型==比较的是内存地址)。

equals() : 它的作⽤也是判断两个对象是否相等。但是这个“相等”一般也分两种情况:

  • 默认情况:类没有覆盖 equals() ⽅法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象,还是相当于比较内存地址。

  • 自定义情况:类覆盖了 equals() ⽅法。我们平时覆盖的 equals()方法一般是比较两个对象的内容是否相同,自定义了一个相等的标准,也就是两个对象的值是否相等。

25. hashCode与 equals?

这个也是面试常问——“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写hashCode ⽅法?

什么是HashCode?

hashCode() 的作⽤是获取哈希码,也称为散列码;它实际上是返回⼀个 int 整数,定义在 Object 类中, 是一个本地⽅法,这个⽅法通常⽤来将对象的内存地址转换为整数之后返回。

哈希码主要在哈希表这类集合映射的时候用到,哈希表存储的是键值对(key-value),它的特点是:能根据“键”快速的映射到对应的“值”。这其中就利⽤到了哈希码!

为什么要有 hashCode?

上面已经讲了,主要是在哈希表这种结构中用的到。

例如HashMap怎么把key映射到对应的value上呢?用的就是哈希取余法,也就是拿哈希码和存储元素的数组的长度取余,获取key对应的value所在的下标位置

为什么重写 quals 时必须重写 hashCode ⽅法?

如果两个对象相等,则 hashcode ⼀定也是相同的。两个对象相等,对两个对象分别调⽤ equals⽅法都返回 true。反之,两个对象有相同的 hashcode 值,它们也不⼀定是相等的 。因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖。

hashCode() 的默认行为是对堆上的对象产⽣独特值。如果没有重写 hashCode() ,则该class的两个对象是不会相等的(即使这两个对象指向相同的数据)

为什么两个对象有相同的 hashcode值,它们也不⼀定是相等的?

因为可能会碰撞hashCode() 所使⽤的散列算法也许刚好会让多个对象传回相同的散列值。越糟糕的散列算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。

26. Java是值传递,还是引用传递?

Java语言是值传递Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。。

JVM 的内存分为堆和栈,其中栈中存储了基本数据类型和引用数据类型实例的地址,也就是对象地址
在这里插入图片描述

27. 深拷贝和浅拷贝?

  • 浅拷贝:仅拷贝被拷贝对象的成员变量的值,也就是基本数据类型变量的值,和引用数据类型变量的地址值,而对于引用类型变量指向的堆中的对象不会拷贝

  • 深拷贝:完全拷贝一个对象,拷贝被拷贝对象的成员变量的值,堆中的对象也会拷贝一份

例如现在有一个order对象,里面有一个products列表,它的浅拷贝和深拷贝的示意图:
在这里插入图片描述
因此深拷贝是安全的,浅拷贝的话如果有引用类型,那么拷贝后对象,引用类型变量修改,会影响原对象。

浅拷贝如何实现呢?

Object类提供的clone()方法可以非常简单地实现对象的浅拷贝。

深拷贝如何实现呢?

重写克隆方法:重写克隆方法,引用类型变量单独克隆,这里可能会涉及多层递归。

序列化:可以先讲原对象序列化,再反序列化成拷贝对象。

28. Java 创建对象有哪几种方式?

Java中有以下四种创建对象的方式:

  • new创建新对象

  • 通过反射机制

  • 采用clone机制

  • 通过序列化机制

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

29. String 是 Java 基本数据类型吗?可以被继承吗?

String是Java基本数据类型吗?

不是。String是一个比较特殊的引用数据类型。

String 类可以继承吗?

不行。String 类使用 final 修饰,是所谓的不可变类,无法被继承。

30. String和StringBuilder、StringBuffer的区别?

  • String:String 的值被创建后不能修改,任何对 String 的修改都会引发新的 String 对象的生成。

  • StringBuffer:跟 String 类似,但是值可以被修改,使用 synchronized 来保证线程安全。

  • StringBuilder:StringBuffer 的非线程安全版本,性能上更高一些。

31. String str1 = new String(“abc”)和String str2 = “abc” 区别?

两个语句都会去字符串常量池中检查是否已经存在 “abc”,如果有则直接使用,如果没有则会在常量池中创建 “abc” 对象。
在这里插入图片描述
但是不同的是,String str1 = new String("abc") 还会通过 new String() 在堆里创建一个 “abc” 字符串对象实例。

String s = new String("abc")创建了几个对象?

一个或两个? 如果字符串常量池已经有“abc”,则是一个;否则,两个。

当字符创常量池没有 “abc”,此时会创建如下两个对象:

  • 一个是字符串字面量 “abc” 所对应的字符串常量池中的实例

  • 另一个是通过 new String() 创建并初始化在堆中的,内容与"abc"相同的实例。

32. String不是不可变类吗?字符串拼接是如何实现的?

String的确是不可变的,“+”的拼接操作,其实是会生成新的对象。

例如:

String a = "hello ";
String b = "world!";
String ab = a + b;

jdk1.8之前,a和b初始化时位于字符串常量池,ab拼接后的对象位于堆中。经过拼接新生成了String对象。如果拼接多次,那么会生成多个中间对象。

Java8对“+”号拼接进行了优化,上面所写的拼接方式会被优化为基于StringBuilder的append方法进行处理。Java会在编译期对“+”号进行处理。

也就是说其实上面的代码其实相当于:

String a = "hello ";
String b = "world!";
StringBuilder sb = new StringBuilder();
sb.append(a);
sb.append(b);
String ab = sb.toString();

此时,如果再笼统的回答:通过加号拼接字符串会创建多个String对象,因此性能比StringBuilder差,就是错误的了。因为本质上加号拼接的效果最终经过编译器处理之后和StringBuilder是一致的

当然,循环里拼接还是建议用StringBuilder的appent方法,为什么,因为如果用"+",那么循环一次就会创建一个新的StringBuilder对象

33. intern方法有什么作用?

JDK源码里已经对这个方法进行了说明:

* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>

如果当前字符串内容存在于字符串常量池(即equals()方法为true,也就是内容一样),直接返回字符串常量池中的字符串。否则,将此String对象添加到池中,并返回String对象的引用

34. Integer a= 127,Integer b = 127;Integer c= 128,Integer d = 128;,相等吗?

a和b相等,c和d不相等。

  • 对于基本数据类型==比较的值

  • 对于引用数据类型==比较的是地址

Integer a= 127这种赋值,是用到了Integer自动装箱的机制。自动装箱的时候会去缓存池里取Integer对象,没有取到才会创建新的对象。

如果整型字面量的值在-128~127之间,那么自动装箱时不会new新的Integer对象,而是直接引用缓存池中的Integer对象

Integer a = 127;
Integer b = 127;
Integer b1 = new Integer(127);
System.out.println(a == b); //true
System.out.println(b == b1); //false

Integer c = 128;
Integer d = 128;
System.out.println(c == d);  //false

什么是Integer缓存?

因为根据实践发现大部分的数据操作都集中在值比较小的范围,因此 Integer 搞了个缓存池,默认范围是 -128~127,可以根据通过设置JVM-XX:AutoBoxCacheMax=XXX来修改缓存的最大值,最小值改不了。

35. String怎么转成Integer的?原理?

String转成Integer,主要有两个方法:

  • Integer.parseInt(String s)

  • Integer.valueOf(String s)

不管哪一种,最终还是会调用Integer类内中的parseInt(String s, int radix)方法。

抛去一些边界之类的看看核心代码:

public static int parseInt(String s, int radix)
                throws NumberFormatException
    {

        int result = 0;
        //是否是负数
        boolean negative = false;
        //char字符数组下标和长度
        int i = 0, len = s.length();
        ……
        int digit;
        //判断字符长度是否大于0,否则抛出异常
        if (len > 0) {
            …… 
            while (i < len) {
                // Accumulating negatively avoids surprises near MAX_VALUE
                //返回指定基数中字符表示的数值。(此处是十进制数值)
                digit = Character.digit(s.charAt(i++),radix);
                //进制位乘以数值
                result *= radix;        
                result -= digit;
            }
        } 
        //根据上面得到的是否负数,返回相应的值
        return negative ? result : -result;
    }

可以看出,就是一个字符串遍历计算,不过计算方式有点反常规,是用负的值累减。
在这里插入图片描述

36. Object 类的常见方法?

Object 类是一个特殊的类,是所有类的父类,也就是说所有类都可以调用它的方法。它主要提供了以下 11 个方法,大概可以分为六类:
在这里插入图片描述
对象比较:

  • public native int hashCode():native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。

  • public boolean equals(Object obj):用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。

对象拷贝:

  • protected native Object clone() throws CloneNotSupportedException:naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为truex.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。

对象转字符串:

  • public String toString():返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。

多线程调度:

  • public final native void notify():native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。

  • public final native void notifyAll():native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。

  • public final native void wait(long timeout) throws InterruptedException:native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。

  • public final void wait(long timeout, int nanos) throws InterruptedException:多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。所以超时的时间还需要加上nanos毫秒。

  • public final void wait() throws InterruptedException:跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念

反射:

  • public final native Class<?> getClass():native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。

垃圾回收:

  • protected void finalize() throws Throwable :通知垃圾收集器回收对象。

37. Java 中异常处理体系?

Java的异常体系是分为多层的。
在这里插入图片描述
Throwable是 Java 语言中所有错误或异常的基类。Throwable 又分为ErrorException,其中Error是系统内部错误,比如虚拟机异常,是程序无法处理的。Exception是程序问题导致的异常,又分为两种:

  • CheckedException编译时异常:编译器会强制检查并要求处理的异常。

  • RuntimeException运行时异常:程序运行中出现异常,比如我们熟悉的空指针、数组下标越界等等

38. 异常的处理方式?

针对异常的处理主要有两种方式:
在这里插入图片描述

  • 遇到异常不进行具体处理,而是继续抛给调用者 (throw,throws)

    抛出异常有三种形式:一是 throw,一个 throws,还有一种系统自动抛异常。

    throws 用在方法上,后面跟的是异常类,可以跟多个;而 throw 用在方法内,后面跟的是异常对象。

  • try catch 捕获异常

    在catch语句块中补货发生的异常,并进行处理。

    try {
        //包含可能会出现异常的代码以及声明异常的方法
    }catch(Exception e) {
        //捕获异常并进行处理
    }finally {                                                       }
        //可选,必执行的代码
    }
    

    try-catch捕获异常的时候还可以选择加上finally语句块,finally语句块不管程序是否正常执行,最终它都会必然执行。

题目一

public static void main(String[] args) {
    System.out.println(test());
}

public static int test() {
    try {
        return 1;
    } catch (Exception e) {
        return 2;
    } finally {
        System.out.print("3");
    }
}

执行结果:31。

在 return 前会先执行 finally 语句块,所以是先输出 finally 里的 3,再输出 return 的 1。

题目二

public static void main(String[] args) {
    System.out.println(test1());
}

public static int test1() {
    try {
        return 2;
    } finally {
        return 3;
    }
}

执行结果:3。

try 返回前先执行 finally,结果 finally 里不按套路出牌,直接 return 了,自然也就走不到 try 里面的 return 了。

题目三

public static void main(String[] args) {
    System.out.println(test1());
}

public static int test1() {
    int i = 0;
    try {
        i = 2;
        return i;
    } finally {
        i = 3;
    }
}

执行结果:2。

大家可能会以为结果应该是 3,因为在 return 前会执行 finally,而 i 在 finally 中被修改为 3 了,那最终返回 i 不是应该为 3 吗?

但其实,在执行 finally 之前,JVM 会先将 i 的结果暂存起来,然后 finally 执行完毕后,会返回之前暂存的结果,而不是返回 i,所以即使 i 已经被修改为 3,最终返回的还是之前暂存起来的结果 2。

39. Java 中 IO 流分为几种?

流按照不同的特点,有很多种划分方式。

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

  • 按照操作单元划分,可以划分为字节流字符流

  • 按照流的角色划分为节点流处理流

Java Io流共涉及40多个类,看上去杂乱,其实都存在一定的关联, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。

  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

在这里插入图片描述

IO流用到了什么设计模式?

其实,Java的IO流体系还用到了一个设计模式——装饰器模式

InputStream相关的部分类图如下
在这里插入图片描述

40. 既然有了字节流,为什么还要有字符流?

其实字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还比较耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。

所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。

41. BIO、NIO、AIO?

在这里插入图片描述

BIO(blocking I/O) :就是传统的IO,同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过连接池机制改善(实现多个客户连接服务器)。

NIO :全称 java non-blocking IO,是指 JDK 提供的新 API。从JDK1.4开始,Java 提供了一系列改进的输入/输出的新特性,被统称为NIO(即New IO)。

NIO是同步非阻塞的,服务器端用一个线程处理多个连接,客户端发送的连接请求会注册到多路复用器上,多路复用器轮询到连接有IO请求就进行处理,NIO的数据是面向缓冲区Buffer的,必须从Buffer中读取或写入。
在这里插入图片描述
可以看出,NIO的运行机制:

  • 每个Channel对应一个Buffer。

  • Selector对应一个线程,一个线程对应多个Channel。

  • Selector会根据不同的事件,在各个通道上切换。

  • Buffer是内存块,底层是数据。

AIO:JDK 7 引入了 Asynchronous I/O,是异步不阻塞的 IO。在进行 I/O 编程中,常用到两种模式:Reactor 和 Proactor。Java 的 NIO 就是 Reactor,当有事件触发时,服务器端得到通知,进行相应的处理,完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。

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

什么是序列化,序列化就是把Java对象转为二进制流,方便存储和传输。所以反序列化就是把二进制流恢复成对象
在这里插入图片描述
类比我们生活中一些大件物品的运输,运输的时候把它拆了打包,用的时候再拆包组装。

Serializable接口有什么用?

这个接口只是一个标记,没有具体的作用,但是如果不实现这个接口,在有些序列化场景会报错,所以一般建议,创建的JavaBean类都实现 Serializable

serialVersionUID 又有什么用?

serialVersionUID 就是起验证作用。

private static final long serialVersionUID = 1L;

我们经常会看到这样的代码,这个 ID 其实就是用来验证序列化的对象和反序列化对应的对象ID 是否一致。

这个 ID 的数字其实不重要,无论是 1L 还是 IDE自动生成的,只要序列化时候对象的 serialVersionUID 和反序列化时候对象的 serialVersionUID 一致的话就行。

如果没有显示指定 serialVersionUID ,则编译器会根据类的相关信息自动生成一个,可以认为是一个指纹。

所以如果你没有定义一个 serialVersionUID, 结果序列化一个对象之后,在反序列化之前把对象的类的结构改了,比如增加了一个成员变量,则此时的反序列化会失败。

因为类的结构变了,所以 serialVersionUID 就不一致。

Java 序列化不包含静态变量?

序列化的时候是不包含静态变量的。

如果有些变量不想序列化,怎么办?

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

transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法

43. 说说有几种序列化方式?

Java序列化方式有很多,常见的有三种:

在这里插入图片描述

  • Java对象流列化:Java原生序列化方法即通过Java原生流(InputStream和OutputStream之间的转化)的方式进行转化,一般是对象输出流 ObjectOutputStream和对象输入流ObjectI叩utStream

  • Json序列化:这个可能是我们最常用的序列化方式,Json序列化的选择很多,一般会使用jackson包,通过ObjectMapper类来进行一些操作,比如将对象转化为byte数组或者将json串转化为对象。

  • ProtoBuff序列化ProtocolBuffer是一种轻便高效的结构化数据存储格式,ProtoBuff序列化对象可以很大程度上将其压缩,可以大大减少数据传输大小,提高系统性能。

44. Java 泛型了解么?什么是类型擦除?介绍一下常用的通配符?

Java 泛型(generics)是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

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

list.add(12);
//这里直接添加会报错
list.add("a");
Class<? extends List> clazz = list.getClass();
Method add = clazz.getDeclaredMethod("add", Object.class);
//但是通过反射添加,是可以的
add.invoke(list, "kl");

System.out.println(list);

在这里插入图片描述
泛型常用的通配符有哪些?

常用的通配符为:T,E,K,V,?

  • ? 表示不确定的 java 类型
  • T (type) 表示具体的一个 java 类型
  • K V (key value) 分别代表 java 键值中的 Key Value
  • E (element) 代表 Element

什么是泛型擦除?

所谓的泛型擦除,官方名叫“类型擦除”。

Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的类型信息都会被擦掉。也就是说,在运行的时候是没有泛型的。

例如这段代码,往一群猫里放条狗:

LinkedList<Cat> cats = new LinkedList<Cat>();
LinkedList list = cats;  // 注意我在这里把范型去掉了,但是list和cats是同一个链表!
list.add(new Dog());  // 完全没问题!

因为Java的范型只存在于源码里,编译的时候给你静态地检查一下范型类型是否正确,而到了运行时就不检查了。上面这段代码在JRE(Java运行环境)看来和下面这段没区别:

LinkedList cats = new LinkedList();  // 注意:没有范型!
LinkedList list = cats;
list.add(new Dog());

为什么要类型擦除呢?

主要是为了向下兼容,因为JDK5之前是没有泛型的,为了让JVM保持向下兼容,就出了类型擦除这个策略。

45. 说一下你对注解的理解?

Java注解本质上是一个标记,可以理解成生活中的一个人的一些小装扮,比如戴什么什么帽子,戴什么眼镜。
在这里插入图片描述
注解可以标记在类上、方法上、属性上等,标记自身也可以设置一些值,比如帽子颜色是绿色。

例如我们常见的AOP,使用注解作为切点就是运行期注解的应用;比如lombok,就是注解在编译期的运行。

注解生命周期有三大类,分别是:

  • RetentionPolicy.SOURCE:给编译器用的,不会写入 class 文件

  • RetentionPolicy.CLASS:会写入 class 文件,在类加载阶段丢弃,也就是运行的时候就没这个信息了

  • RetentionPolicy.RUNTIME:会写入 class 文件,永久保存,可以通过反射获取注解信息

所以我上文写的是解析的时候,没写具体是解析啥,因为不同的生命周期的解析动作是不同的。

像常见的:
在这里插入图片描述
再比如 Spring 常见的 Autowired ,就是 RUNTIME 的,所以在运行的时候可以通过反射得到注解的信息,还能拿到标记的值 required
在这里插入图片描述

46. 什么是反射?应用?原理?

我们通常都是利用new方式来创建对象实例,这可以说就是一种“正射”,这种方式在编译时候就确定了类型信息。

而如果,我们想在时候动态地获取类信息、创建类实例、调用类方法这时候就要用到反射

通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。

反射最核心的四个类:
在这里插入图片描述
反射的应用场景?

一般我们平时都是在写业务代码,很少会接触到直接使用反射机制的场景。

但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。

像Spring里的很多 注解 ,它真正的功能实现就是利用反射。

就像为什么我们使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?

这些都是因为我们可以基于反射操作类,然后获取到类/属性/方法/方法的参数上的注解,注解这里就有两个作用,一是标记,我们对注解标记的类/属性/方法进行对应的处理;二是注解本身有一些信息,可以参与到处理的逻辑中。

反射的原理?

我们都知道Java程序的执行分为编译和运行两步,编译之后会生成字节码(.class)文件,JVM进行类加载的时候,会加载字节码文件,将类型相关的所有信息加载进方法区,反射就是去获取这些信息,然后进行各种操作。

  • 3
    点赞
  • 11
    收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:创作都市 设计师:CSDN官方博客 返回首页
评论 1

打赏作者

一个写湿的程序猿

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值