个人总结 -- Java基础查漏补缺
yuuki Lv5

1. Java 和 C++ 的区别?

  • 都是面向对象的语言,都支持封装、继承和多态
  • Java 不提供指针来直接访问内存,程序内存更加安全
  • Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
  • Java 有自动内存管理机制,不需要程序员手动释放无用内存
  • 在 C 语言中,字符串或字符数组最后都会有一个额外的字符‘\0’来表示结束。但是,Java 语言中没有结束符这一概念。

2. 什么是反射机制

反射机制是 Java 语言中一个非常重要的特性,它允许程序在运行时进行自我检查,同时也允许对其内部的成员进行操作。

反射机制提供的功能主要有:

  • 得到一个对象所属的类;
  • 获得一个类的所有成员变量和方法;
  • 在运行时创建对象;
  • 在运行时调用对象方法;

3. String、StringBuffer 和 StringBuilder 的区别是什么?String 为什么是不可变的?

String 类中使用 final 关键字修饰字符数组来保存字符串,private final char value[],所以 String 对象是不可变的。

补充(来自issue 675):在 Java 9 之后,String 类的实现改用 byte 数组存储字符串 private final byte[] value;

而 StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串char[]value 但是没有用 final 关键字修饰,所以这两种对象都是可变的。

线程安全性

String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

性能

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

对于三者使用的总结:

  1. 操作少量的数据: 适用 String
  2. 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
  3. 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer

4. 接口和抽象类的区别?

  1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),而抽象类可以有非抽象的方法。
  2. 接口中除了 static、final 变量,不能有其他变量,而抽象类中则不一定。
  3. 一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过 extends 关键字扩展多个接口。
  4. 接口方法默认修饰符是 public,抽象方法可以有 public、protected 和 default 这些修饰符(抽象方法就是为了被重写所以不能使用 private 关键字修饰!)。
  5. 从设计层面来说,抽象是对类的抽象,是一种模板设计,而接口是对行为的抽象,是一种行为的规范。

5. 为什么有些 Java 接口中没有任何方法

在 Java 语言中,有些接口内部没有声明任何方法,也就是说,实现这些接口的类不需要重写任何方法,这些没有任何方法声明的接口又被叫做标识接口,标识接口对实现它的类没有任何语义上的要求,它仅仅充当一个标识的作用用来表明它的类属于一个特定的类型。

6. 如何实现类似于 C 语言中函数指针的功能?

在 Java 中没有指针的概念,那么如果要实现类似于 C 语言中函数指针的功能,可以利用接口和类来实现同样的效果。具体来说,就是先定一个接口,然后在接口中声明要调用的方法,接着实现这个接口,最后把这个实现类的一个对象作为参数传递给调用的程序,调用程序通过这个参数来调用指定的函数,从而实现回调函数的功能。

7. Java 面向对象编程的三大特性:封装、继承和多态

  • 封装

    封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。

  • 继承

    继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。

  • 多态

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

8. Java 中提供了哪两种用于多态的机制

编译时多态和运行时多态。编译时多态是通过方法的重载实现的,运行时多态使用过方法的覆盖实现的。

9. this 与 super 有什么区别?

在 Java 中,this 用来指向当前实例对象;super 可以用来访问父类的方法或成员变量。

10. final、finally 和 finalize 有什么区别?

  • final用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖和类不可被继承(不能再派生出新的子类)。
  • finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定被执行,经常被用在需要释放资源的情况下。
  • finalize是Object类的一个方法,在垃圾回收器执行时会调用被回收对象的finalize()方法,可以覆盖此方法来实现对其他资源的回收,例如关闭文件等。需要注意的是,一旦垃圾回收器准备好释放对象占用的空间,将首先调用其finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。

11. strictfp 有什么作用?

strictfp 可以保证浮点数运算的精确性,而且在不同的硬件平台上会有一致的运行结果

关键字 strictfp 是 strict float point 的缩写,指的是精确浮点,它用来确保浮点数运算的准确性。JVM在执行浮点数运算时,如果没有指定 strictfp 关键字,此时计算结果可能会不精确,而且计算结果在不同平台或厂商的虚拟机上会有不同的结果,导致意想不到的错误。而一旦使用了 strictfp 来声明一个类、接口或者方法,那么在所声明的范围内,Java 编译器以及运行环境会完全依照 IEEE 二进制浮点数算术标准(IEEE 754)来执行,在这个关键字声明的范围内所有浮点数的计算都是精确的。

12. char 型变量中是否可以储存一个中文汉字?

在 Java 语言中,默认使用 Unicode 编码方式,即每个字符占用两个字节,因此可以用来储存中文。

13. “==”、equals 和 hashCode 有什么区别?

  1. “==”运算符用来比较两个变量的值是否相等(比较的是引用)。也就是说,该运算符用于比较变量对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能使用“==”运算符。
  2. equals是 Object 类提供的方法之一。每一个 Java 类都继承自 Object 类,所以每一个对象都具有 equals 这个方法。Object 类中定义的 equals(Object)方法是直接使用 “==” 运算符比较的两个对象,所以在没有覆盖 equals(Object)方法的情况下,equals(Object)与“==”运算符一样,比较的是引用。
  3. hashCode() 方法是从 Object 类中继承过来的,它也用来鉴定两个对象是否相等。Object类中的 hashCode() 方法返回对象在内存中地址转换成的一个 int 值,所以如果没有重写 hash-Code() 方法,任何对象的 hashCode() 方法都是不相等的。

14. 异常处理的原理是什么?

异常是指程序运行时(非编译时)所发生的非正常情况或错误,当程序违反了语义规则时,JVM就会将出现的错误表示为一个异常并抛出。这个异常可以在 catch 程序块中进行捕获然后处理。而异常处理的目的是为了提高程序的安全性和鲁棒性。

15. Java Socket 是什么?

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个双向链路的一端称为一个 Socket。Socket 也称为套接字,可以用来实现不同虚拟机或不同计算机之间的通信。

16. BIO, NIO, AIO 有什么区别?

  • BIO (Blocking I/O): 同步阻塞 I/O 模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机 1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
  • NIO (Non-blocking/New I/O): NIO 是一种同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 SocketServerSocket 相对应的 SocketChannelServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
  • AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO 操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
 评论