Java是⼀门⾯向对象的编程语⾔,不仅吸收了C++语⾔的各种优点,还摒弃了C++⾥难以理解
的多继承、指针等概念,因此Java语⾔具有功能强⼤和简单易⽤两个特征。Java语⾔作为静态
⾯向对象编程语⾔的优秀代表,极好地实现了⾯向对象理论,允许程序员以优雅的思维⽅式
进⾏复杂的编程 。

3.JVM、JDK 和 JRE 有什么区别?
JVM:Java Virtual Machine,Java虚拟机,Java程序运⾏在Java虚拟机上。针对不同系统的实
现(Windows,Linux,macOS)不同的JVM,因此Java语⾔可以实现跨平台。
JRE: Java 运⾏时环境。它是运⾏已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟
机(JVM),Java 类库,Java 命令和其他的⼀些基础构件。但是,它不能⽤于创建新程序。
JDK: Java Development Kit,它是功能⻬全的 Java SDK。它拥有 JRE 所拥有的⼀切,还有编
译器(javac)和⼯具(如 javadoc 和 jdb)。它能够创建和编译程序。
4.说说什么是跨平台性?原理是什么?
所谓跨平台性,是指Java语⾔编写的程序,⼀次编译后,可以在多个系统平台上运⾏。
实现原理:Java程序是通过Java虚拟机在系统平台上运⾏的,只要该系统可以安装相应的Java
虚拟机,该系统就可以运⾏java程序。
5.什么是字节码?采⽤字节码的好处是什么?
所谓的字节码,就是Java程序经过编译之类产⽣的.class⽂件,字节码能够被虚拟机识别,从
⽽实现Java程序的跨平台性。
Java 程序从源代码到运⾏主要有三步:
编译 :将我们的代码(.java)编译成虚拟机可以识别理解的字节码(.class)
解释 :虚拟机执⾏Java字节码,将字节码翻译成机器能识别的机器码
执⾏ :对应的机器执⾏⼆进制机器码
6.为什么说 Java 语⾔“编译与解释并存”?
⾼级编程语⾔按照程序的执⾏⽅式分为编译型和解释型两种。
简单来说,编译型语⾔是指编译器针对特定的操作系统将源代码⼀次性翻译成可被该平台执
⾏的机器码;解释型语⾔是指解释器对源程序逐⾏解释成特定平台的机器码并⽴即执⾏。
⽐如,你想读⼀本外国的⼩说,你可以找⼀个翻译⼈员帮助你翻译,有两种选择⽅式,你可
以先等翻译⼈员将全本的⼩说(也就是源码)都翻译成汉语,再去阅读,也可以让翻译⼈员
翻译⼀段,你在旁边阅读⼀段,慢慢把书读完。
Java 语⾔既具有编译型语⾔的特征,也具有解释型语⾔的特征,因为 Java 程序要经过先编
译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,⽣成字节码( \*.class ⽂
件),这种字节码必须再经过JVM,解释成操作系统能识别的机器码,在由操作系统执⾏。
因此,我们可以认为 Java 语⾔编译与解释并存。

7.Java有哪些数据类型?
定义:Java语⾔是强类型语⾔,对于每⼀种数据都定义了明确的具体的数据类型,在内存中分
配了不同⼤⼩的内存空间。
Java语⾔数据类型分为两种:基本数据类型和引⽤数据类型


8.⾃动类型转换、强制类型转换?看看这⼏⾏代码?
Java 所有的数值型变量可以相互转换,当把⼀个表数范围⼩的数值或变量直接赋给另⼀个表
数范围⼤的变量时,可以进⾏⾃动类型转换;反之,需要强制转换。

9.什么是⾃动拆箱/封箱?
装箱 :将基本类型⽤它们对应的引⽤类型包装起来;
拆箱 :将包装类型转换为基本数据类型;
Java可以⾃动对基本数据类型和它们的包装类进⾏装箱和拆箱。

10.&和&&有什么区别?
&运算符有两种⽤法: 短路与 、 逻辑与 。
&&运算符是短路与运算。逻辑与跟短路与的差别是⾮常巨⼤的,虽然⼆者都要求运算符左右
两端的布尔值都是true 整个表达式的值才是 true。
&&之所以称为短路运算是因为,如果&&左边的表达式的值是 false,右边的表达式会被直接
短路掉,不会进⾏运算。很多时候我们可能都需要⽤&&⽽不是&。
例如在验证⽤户登录时判定⽤户名不是 null ⽽且不是空字符串,应当写为 username !=
null &&!username.equals("") ,⼆者的顺序不能交换,更不能⽤&运算符,因为第⼀个
条件如果不成⽴,根本不能进⾏字符串的 equals ⽐较,否则会产⽣ NullPointerException 异
常。
注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
11.switch 是否能作⽤在 byte/long/String上?
Java5 以前 switch(expr)中,expr 只能是 byte、short、char、int。
从 Java 5 开始,Java 中引⼊了枚举类型, expr 也可以是 enum 类型。
从 Java 7 开始,expr还可以是字符串(String),但是长整型(long)在⽬前所有的版本中都是不可
以的。
12.break ,continue ,return 的区别及作⽤?
break 跳出整个循环,不再执⾏循环( 结束当前的循环体 )
continue 跳出本次循环,继续执⾏下次循环( 结束正在执⾏的循环 进⼊下⼀个循环条件 )
return 程序返回,不再执⾏下⾯的代码( 结束当前的⽅法 直接返回 )
13.⽤最有效率的⽅法计算2乘以8?
2 << 3。位运算,数字的⼆进制位左移三位相当于乘以2的三次⽅。
14.说说⾃增⾃减运算?看下这⼏个代码运⾏结果?
在写代码的过程中,常见的⼀种情况是需要某个整数类型变量增加 1 或减少 1,Java 提供了
⼀种特殊的运算符,⽤于这种表达式,叫做⾃增运算符(++)和⾃减运算符(--)。
++和--运算符可以放在变量之前,也可以放在变量之后。
当运算符放在变量之前时(前缀),先⾃增/减,再赋值;当运算符放在变量之后时(后缀),先赋
值,再⾃增/减。
⾯向对象
15.⾯向对象和⾯向过程的区别?
⾯向过程 :⾯向过程就是分析出解决问题所需要的步骤,然后⽤函数把这些步骤⼀步⼀
步实现,使⽤的时候再⼀个⼀个的⼀次调⽤就可以。
⾯向对象 :⾯向对象,把构成问题的事务分解成各个对象,⽽建⽴对象的⽬的也不是为
了完成⼀个个步骤,⽽是为了描述某个事件在解决整个问题的过程所发⽣的⾏为。 ⽬的是
为了写出通⽤的代码,加强代码的重⽤,屏蔽差异性。

封装
封装把⼀个对象的属性私有化,同时提供⼀些可以被外界访问的属性的⽅法。
继承
15
继承是使⽤已存在的类的定义作为基础创建新的类,新类的定义可以增加新的属性或新的
⽅法,也可以继承⽗类的属性和⽅法。通过继承可以很⽅便地进⾏代码复⽤。
关于继承有以下三个要点:
1. ⼦类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⽗类中的私有属
性和⽅法⼦类是⽆法访问,只是拥有。
2. ⼦类可以拥有⾃⼰属性和⽅法,即⼦类可以对⽗类进⾏扩展。
3. ⼦类可以⽤⾃⼰的⽅式实现⽗类的⽅法。
多态
所谓多态就是指程序中定义的引⽤变量所指向的具体类型和通过该引⽤变量发出的⽅法调
⽤在编程时并不确定,⽽是在程序运⾏期间才确定,即⼀个引⽤变量到底会指向哪个类的
实例对象,该引⽤变量发出的⽅法调⽤到底是哪个类中实现的⽅法,必须在由程序运⾏期
间才能决定。
在 Java 中有两种形式可以实现多态:继承(多个⼦类对同⼀⽅法的重写)和接⼜(实现
接⼜并覆盖接⼜中同⼀⽅法)。
17.重载(overload)和重写(override)的区别?
⽅法的重载和重写都是实现多态的⽅式,区别在于前者实现的是编译时的多态性,⽽后者实
现的是运⾏时的多态性。
重载发⽣在⼀个类中,同名的⽅法如果有不同的参数列表(参数类型不同、参数个数不同
或者⼆者都不同)则视为重载;
重写发⽣在⼦类与⽗类之间,重写要求⼦类被重写⽅法与⽗类被重写⽅法有相同的返回类
型,⽐⽗类被重写⽅法更好访问,不能⽐⽗类被重写⽅法声明更多的异常(⾥⽒代换原
则)。
⽅法重载的规则:
1. ⽅法名⼀致,参数列表中参数的顺序,类型,个数不同。
2. 重载与⽅法的返回值⽆关,存在于⽗类和⼦类,同类中。
3. 可以抛出不同的异常,可以有不同修饰符。
18.访问修饰符public、private、protected、以及不写(默认)时的区别?
Java中,可以使⽤访问控制符来保护对类、变量、⽅法和构造⽅法的访问。Java ⽀持 4 种不
同的访问权限。
default (即默认,什么也不写): 在同⼀包内可见,不使⽤任何修饰符。可以修饰在类、
接⼜、变量、⽅法。
private : 在同⼀类内可见。可以修饰变量、⽅法。 注意:不能修饰类(外部类)
public : 对所有类可见。可以修饰类、接⼜、变量、⽅法
protected : 对同⼀包内的类和所有⼦类可见。可以修饰变量、⽅法。 注意:不能修饰类
(外部类) 。
19.this关键字有什么作⽤?
this是⾃⾝的⼀个对象,代表对象本⾝,可以理解为:指向对象本⾝的⼀个指针。
this的⽤法在Java中⼤体可以分为3种:
1. 普通的直接引⽤,this相当于是指向当前对象本⾝
2. 形参与成员变量名字重名,⽤this来区分:
20.抽象类(abstract class)和接⼜(interface)有什么区别?
1. 接⼜的⽅法默认是 public ,所有⽅法在接⼜中不能有实现(Java 8 开始接⼜⽅法可以有默
认实现),⽽抽象类可以有⾮抽象的⽅法。
2. 接⼜中除了 static 、 final 变量,不能有其他变量,⽽抽象类中则不⼀定。
3. ⼀个类可以实现多个接⼜,但只能实现⼀个抽象类。接⼜⾃⼰本⾝可以通过 extends 关键
字扩展多个接⼜。
4. 接⼜⽅法默认修饰符是 public ,抽象⽅法可以有 public 、 protected 和 default 这些修饰符
(抽象⽅法就是为了被重写所以不能使⽤ private 关键字修饰!)。
5. 从设计层⾯来说,抽象是对类的抽象,是⼀种模板设计,⽽接⼜是对⾏为的抽象,是⼀种
⾏为的规范。
1. 在 JDK8 中,接⼜也可以定义静态⽅法,可以直接⽤接⼜名调⽤。实现类和实现是不
可以调⽤的。如果同时实现两个接⼜,接⼜中定义了⼀样的默认⽅法,则必须重写,
不然会报错。
2. jdk9 的接⼜被允许定义私有⽅法 。
总结⼀下 jdk7~jdk9 Java 中接⼜的变化:
1. 在 jdk 7 或更早版本中,接⼜⾥⾯只能有常量变量和抽象⽅法。这些接⼜⽅法必须由选择
实现接⼜的类实现。
2. jdk 8 的时候接⼜可以有默认⽅法和静态⽅法功能。
3. jdk 9 在接⼜中引⼊了私有⽅法和私有静态⽅法。
21.成员变量与局部变量的区别有哪些?
1. 从语法形式上看 :成员变量是属于类的,⽽局部变量是在⽅法中定义的变量或是⽅法的
参数;成员变量可以被 public , private , static 等修饰符所修饰,⽽局部变量不能被访问控
制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
2. 从变量在内存中的存储⽅式来看 :如果成员变量是使⽤ static 修饰的,那么这个成员变量
是属于类的,如果没有使⽤ static 修饰,这个成员变量是属于实例的。对象存于堆内存,
如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引⽤数据类型,那存放的
是指向堆内存对象的引⽤或者是指向常量池中的地址。
3. 从变量在内存中的⽣存时间上看 :成员变量是对象的⼀部分,它随着对象的创建⽽存
在,⽽局部变量随着⽅法的调⽤⽽⾃动消失。
4. 成员变量如果没有被赋初值 :则会⾃动以类型的默认值⽽赋值(⼀种情况例外:被 final 修
饰的成员变量也必须显式地赋值),⽽局部变量则不会⾃动赋值。
22.静态变量和实例变量的区别?静态⽅法、实例⽅法呢?
静态变量和实例变量的区别?
静态变量: 是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何⼀个对
象,⼀个类不管创建多少个对象,静态变量在内存中有且仅有⼀个副本。
实例变量: 必须依存于某⼀实例,需要先创建对象然后通过对象才能访问到它。静态变量可以
实现让多个对象共享内存。
静态⽅法和实例⽅法有何不同?
类似地。
静态⽅法:static修饰的⽅法,也被称为类⽅法。在外部调⽤静态⽅法时,可以使⽤"类名.⽅
法名"的⽅式,也可以使⽤"对象名.⽅法名"的⽅式。静态⽅法⾥不能访问类的⾮静态成员变量
和⽅法。
实例⽅法:依存于类的实例,需要使⽤"对象名.⽅法名"的⽅式调⽤;可以访问类的所有成员
变量和⽅法。
24.final关键字有什么作⽤?
final表⽰不可变的意思,可⽤于修饰类、属性和⽅法:
被final修饰的类不可以被继承
被final修饰的⽅法不可以被重写
被final修饰的变量不可变,被final修饰的变量必须被显式第指定初始值,还得注意的是,
这⾥的不可变指的是变量的引⽤不可变,不是引⽤指向的内容的不可变。

25.final、finally、finalize的区别?
final ⽤于修饰变量、⽅法和类:final修饰的类不可被继承;修饰的⽅法不可被重写;修饰
的变量不可变。
finally 作为异常处理的⼀部分,它只能在 try/catch 语句中,并且附带⼀个语句块表⽰
这段语句最终⼀定被执⾏(⽆论是否抛出异常),经常被⽤在需要释放资源的情况
下, System.exit (0) 可以阻断 finally 执⾏。
finalize 是在 java.lang.Object ⾥定义的⽅法,也就是说每⼀个对象都有这么个⽅
法,这个⽅法在 gc 启动,该对象被回收的时候被调⽤。
⼀个对象的 finalize ⽅法只会被调⽤⼀次,finalize 被调⽤不⼀定会⽴即回收该对象,所以
有可能调⽤ finalize 后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为
前⾯调⽤过⼀次,所以不会再次调⽤ finalize 了,进⽽产⽣问题,因此不推荐使⽤ finalize
⽅法
26.==和 equals 的区别?
== : 它的作⽤是判断两个对象的地址是不是相等。即,判断两个对象是不是同⼀个对象(基本
数据类型==⽐较的是值,引⽤数据类型==⽐较的是内存地址)。
equals() : 它的作⽤也是判断两个对象是否相等。但是这个“相等”⼀般也分两种情况:
默认情况:类没有覆盖 equals() ⽅法。则通过 equals() ⽐较该类的两个对象时,等价于通
过“ == ”⽐较这两个对象,还是相当于⽐较内存地址。
⾃定义情况:类覆盖了 equals() ⽅法。我们平时覆盖的 equals()⽅法⼀般是⽐较两个对象
的内容是否相同,⾃定义了⼀个相等的标准,也就是两个对象的值是否相等。
27.hashCode与 equals?
这个也是⾯试常问——“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写
hashCode ⽅法?”
什么是HashCode?
hashCode() 的作⽤是获取哈希码,也称为散列码;它实际上是返回⼀个 int 整数,定义在
Object 类中, 是⼀个本地⽅法,这个⽅法通常⽤来将对象的内存地址转换为整数之后返回。
public native int hashCode();
哈希码主要在哈希表这类集合映射的时候⽤到,哈希表存储的是键值对(key-value),它的特点
是:能根据“键”快速的映射到对应的“值”。这其中就利⽤到了哈希码!
为什么要有 hashCode?
上⾯已经讲了,主要是在哈希表这种结构中⽤的到。
例如HashMap怎么把key映射到对应的value上呢?⽤的就是哈希取余法,也就是拿哈希码和存
储元素的数组的长度取余,获取key对应的value所在的下标位置。
为什么重写 quals 时必须重写 hashCode ⽅法?
如果两个对象相等,则 hashcode ⼀定也是相同的。两个对象相等,对两个对象分别调⽤
equals⽅法都返回 true。反之,两个对象有相同的 hashcode 值,它们也不⼀定是相等的 。因
此,equals ⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖。
hashCode() 的默认⾏为是对堆上的对象产⽣独特值。如果没有重写 hashCode() ,则该class 的
两个对象⽆论如何都不会相等(即使这两个对象指向相同的数据)
为什么两个对象有相同的 hashcode值,它们也不⼀定是相等的?
因为可能会碰撞, hashCode() 所使⽤的散列算法也许刚好会让多个对象传回相同的散列值。
越糟糕的散列算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是
不同的对象得到相同的 hashCode )。
28.Java是值传递,还是引⽤传递?
Java语⾔是值传递。Java 语⾔的⽅法调⽤只⽀持参数的值传递。当⼀个对象实例作为⼀个参
数被传递到⽅法中时,参数的值就是对该对象的引⽤。对象的属性可以在被调⽤过程中被改
变,但对对象引⽤的改变是不会影响到调⽤者的。
JVM 的内存分为堆和栈,其中栈中存储了基本数据类型和引⽤数据类型实例的地址,也就是
对象地址。
⽽对象所占的空间是在堆中开辟的,所以传递的时候可以理解为把变量存储的对象地址给传
递过去,因此引⽤类型也是值传递。

29.深拷贝和浅拷贝?
浅拷贝 :仅拷贝被拷贝对象的成员变量的值,也就是基本数据类型变量的值,和引⽤数
据类型变量的地址值,⽽对于引⽤类型变量指向的堆中的对象不会拷贝。
深拷贝 :完全拷贝⼀个对象,拷贝被拷贝对象的成员变量的值,堆中的对象也会拷贝⼀
份。
例如现在有⼀个order对象,⾥⾯有⼀个products列表,它的浅拷贝和深拷贝的⽰意图

因此深拷贝是安全的,浅拷贝的话如果有引⽤类型,那么拷贝后对象,引⽤类型变量修改,
会影响原对象。
浅拷贝如何实现呢?
Object类提供的clone()⽅法可以⾮常简单地实现对象的浅拷贝。
深拷贝如何实现呢?
重写克隆⽅法:重写克隆⽅法,引⽤类型变量单独克隆,这⾥可能会涉及多层递归。
序列化:可以先讲原对象序列化,再反序列化成拷贝对象。
new创建新对象
通过反射机制
采⽤clone机制
通过序列化机制
前两者都需要显式地调⽤构造⽅法。对于clone机制,需要注意浅拷贝和深拷贝的区别,对于序
列化机制需要明确其实现原理,在Java中序列化可以通过实现Externalizable或者Serializable来
实现。
31.String 是 Java 基本数据类型吗?可以被继承吗?
String是Java基本数据类型吗?
不是。Java 中的基本数据类型只有8个:byte、short、int、long、float、double、char、
boolean;除了基本类型(primitive type),剩下的都是引⽤类型(reference type)。
String是⼀个⽐较特殊的引⽤数据类型。
String 类可以继承吗?
不⾏。String 类使⽤ final 修饰,是所谓的不可变类,⽆法被继承。
32.String和StringBuilder、StringBuffer的区别?
String:String 的值被创建后不能修改,任何对 String 的修改都会引发新的 String 对象的⽣
成。
StringBuffer:跟 String 类似,但是值可以被修改,使⽤ synchronized 来保证线程安全。
StringBuilder:StringBuffer 的⾮线程安全版本,性能上更⾼⼀些。
33.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"相同的实例,在堆中。
34.String不是不可变类吗?字符串拼接是如何实现的?
String的确是不可变的,“+”的拼接操作,其实是会⽣成新的对象。
例如:
String a = "hello ";
String b = "world!";
String ab = a + b;
在jdk1.8之前,a和b初始化时位于字符串常量池,ab拼接后的对象位于堆中。经过拼接新⽣成
了String对象。如果拼接多次,那么会⽣成多个中间对象。
36.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对象,超过范围 a1==b1的结果是false
什么是Integer缓存?
因为根据实践发现⼤部分的数据操作都集中在值⽐较⼩的范围,因此 Integer 搞了个缓存池,
默认范围是 -128 到 127,可以根据通过设置 JVM-XX:AutoBoxCacheMax= 来修改缓存的最⼤
值,最⼩值改不了。
实现的原理是int 在⾃动装箱的时候会调⽤Integer.valueOf,进⽽⽤到了 IntegerCache。
很简单,就是判断下值是否在缓存范围之内,如果是的话去 IntegerCache 中取,不是的话就
创建⼀个新的Integer对象。
IntegerCache是⼀个静态内部类, 在静态块中会初始化好缓存值。


Throwable 是 Java 语⾔中所有错误或异常的基类。 Throwable 又分为 Error 和
Exception ,其中Error是系统内部错误,⽐如虚拟机异常,是程序⽆法处理
的。 Exception 是程序问题导致的异常,又分为两种:
CheckedException受检异常:编译器会强制检查并要求处理的异常。
RuntimeException运⾏时异常:程序运⾏中出现异常,⽐如我们熟悉的空指针、数组下标
越界等等

遇到异常不进⾏具体处理,⽽是继续抛给调⽤者 (throw,throws)
抛出异常有三种形式,⼀是 throw,⼀个 throws,还有⼀种系统⾃动抛异常。
throws ⽤在⽅法上,后⾯跟的是异常类,可以跟多个;⽽ throw ⽤在⽅法内,后⾯跟的是异
常对象。
try catch 捕获异常
在catch语句块中补货发⽣的异常,并进⾏处理。
try {
//包含可能会出现异常的代码以及声明异常的⽅法
}catch(Exception e) {
//捕获异常并进⾏处理
}finally { }
//可选,必执⾏的代码
}
try-catch捕获异常的时候还可以选择加上finally语句块,finally语句块不管程序是否正常执⾏,
最终它都会必然执⾏。
流按照不同的特点,有很多种划分⽅式。
按照流的流向分,可以分为 输⼊流 和 输出流 ;
按照操作单元划分,可以划分为 字节流 和 字符流 ;
按照流的⾓⾊划分为 节点流 和 处理流
Java Io流共涉及40多个类,看上去杂乱,其实都存在⼀定的关联, Java I0流的40多个类都是
从如下4个抽象类基类中派⽣出来的。
InputStream / Reader : 所有的输⼊流的基类,前者是字节输⼊流,后者是字符输⼊流。
OutputStream / Writer : 所有输出流的基类,前者是字节输出流,后者是字符输出流。

IO流⽤到了什么设计模式?
其实,Java的IO流体系还⽤到了⼀个设计模式——装饰器模式。
InputStream相关的部分类图如下

43.既然有了字节流,为什么还要有字符流?
其实字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还⽐较耗时,并且,如
果我们不知道编码类型就很容易出现乱码问题。
所以, I/O 流就⼲脆提供了⼀个直接操作字符的接口,⽅便我们平时对字符进⾏流操作。如
果⾳频⽂件、图⽚等媒体⽂件⽤字节流⽐较好,如果涉及到字符的话使⽤字符流⽐较好。

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

BIO⽅式适⽤于连接数⽬⽐较⼩且固定的架构,这种⽅式对服务器资源要求⽐较⾼,并发局限
于应⽤中,JDK1.4 以前的唯⼀选择,程序简单易理解。
NIO :全称 java non-blocking IO,是指 JDK 提供的新 API。从JDK1.4开始,Java 提供了⼀系
列改进的输⼊/输出的新特性,被统称为NIO(即New IO)。
NIO是同步⾮阻塞的,服务器端⽤⼀个线程处理多个连接,客户端发送的连接请求会注册到
多路复⽤器上,多路复⽤器轮询到连接有IO请求就进⾏处理:

NIO的数据是⾯向缓冲区Buffer的,必须从Buffer中读取或写⼊。
所以完整的NIO⽰意图:

可以看出,NIO的运⾏机制:
每个Channel对应⼀个Buffer。
Selector对应⼀个线程,⼀个线程对应多个Channel。
Selector会根据不同的事件,在各个通道上切换。
Buffer是内存块,底层是数据。
AIO:JDK 7 引⼊了 Asynchronous I/O,是异步不阻塞的 IO。在进⾏ I/O 编程中,常⽤到两
种模式:Reactor 和 Proactor。Java 的 NIO 就是 Reactor,当有事件触发时,服务器端得到通
知,进⾏相应的处理,完成后才通知服务端程序启动线程去处理,⼀般适⽤于连接数较多且
连接时间较长的应⽤。
45.什么是序列化?什么是反序列化?
什么是序列化,序列化就是把Java对象转为⼆进制流,⽅便存储和传输。
所以反序列化就是把⼆进制流恢复成对象。


Java对象流列化 :Java原⽣序列化⽅法即通过Java原⽣流(InputStream和OutputStream之间
的转化)的⽅式进⾏转化,⼀般是对象输出流 ObjectOutputStream 和对象输⼊流
ObjectI叩utStream 。
Json序列化:这个可能是我们最常⽤的序列化⽅式,Json序列化的选择很多,⼀般会使⽤
jackson包,通过ObjectMapper类来进⾏⼀些操作,⽐如将对象转化为byte数组或者将json串
转化为对象。
ProtoBuff序列化:ProtocolBuffer是⼀种轻便⾼效的结构化数据存储格式,ProtoBuff序列化
对象可以很⼤程度上将其压缩,可以⼤⼤减少数据传输⼤⼩,提⾼系统性能。
47.Java 泛型了解么?什么是类型擦除?介绍⼀下常⽤的通配符?
什么是泛型?
Java 泛型(generics)是 JDK 5 中引⼊的⼀个新特性, 泛型提供了编译时类型安全检测机制,
该机制允许程序员在编译时检测到⾮法的类型。泛型的本质是参数化类型,也就是说所操作
的数据类型被指定为⼀个参数。
泛型⼀般有三种使⽤⽅式:泛型类、泛型接⼜、泛型⽅法。

泛型常⽤的通配符有哪些?
常⽤的通配符为: 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保持向下兼容,就出了类型
擦除这个策略。
48.说⼀下你对注解的理解?
Java注解本质上是⼀个标记,可以理解成⽣活中的⼀个⼈的⼀些⼩装扮,⽐如戴什么什么帽
⼦,戴什么眼镜。
注解可以标记在类上、⽅法上、属性上等,标记⾃⾝也可以设置⼀些值,⽐如帽⼦颜⾊是绿
⾊。
有了标记之后,我们就可以在编译或者运⾏阶段去识别这些标记,然后搞⼀些事情,这就是
注解的⽤处。
例如我们常见的AOP,使⽤注解作为切点就是运⾏期注解的应⽤;⽐如lombok,就是注解在
编译期的运⾏。
注解⽣命周期有三⼤类,分别是:
RetentionPolicy.SOURCE:给编译器⽤的,不会写⼊ class ⽂件
RetentionPolicy.CLASS:会写⼊ class ⽂件,在类加载阶段丢弃,也就是运⾏的时候就没
这个信息了
RetentionPolicy.RUNTIME:会写⼊ class ⽂件,永久保存,可以通过反射获取注解信息
所以我上⽂写的是解析的时候,没写具体是解析啥,因为不同的⽣命周期的解析动作是不同
的。
就是给编译器⽤的,编译器编译的时候检查没问题就over了,class⽂件⾥⾯不会有 Override 这
个标记。
再⽐如 Spring 常见的 Autowired ,就是 RUNTIME 的,所以在运⾏的时候可以通过反射得到
注解的信息,还能拿到标记的值 required 。
49.什么是反射?应⽤?原理?
什么是反射?
我们通常都是利⽤ new ⽅式来创建对象实例,这可以说就是⼀种“正射”,这种⽅式在编译时
候就确定了类型信息。
⽽如果,我们想在时候动态地获取类信息、创建类实例、调⽤类⽅法这时候就要⽤到反射。
通过反射你可以获取任意⼀个类的所有属性和⽅法,你还可以调⽤这些⽅法和属性。
反射最核⼼的四个类:

反射的应⽤场景?
⼀般我们平时都是在在写业务代码,很少会接触到直接使⽤反射机制的场景。
但是,这并不代表反射没有⽤。相反,正是因为反射,你才能这么轻松地使⽤各种框架。像
Spring/Spring Boot、MyBatis 等等框架中都⼤量使⽤了反射机制。
像Spring⾥的很多 注解 ,它真正的功能实现就是利⽤反射。
就像为什么我们使⽤ Spring 的时候 ,⼀个 @Component 注解就声明了⼀个类为 Spring Bean
呢?为什么通过⼀个 @Value 注解就读取到配置⽂件中的值呢?究竟是怎么起作⽤的呢?
这些都是因为我们可以基于反射操作类,然后获取到类/属性/⽅法/⽅法的参数上的注解,注解
这⾥就有两个作⽤,⼀是标记,我们对注解标记的类/属性/⽅法进⾏对应的处理;⼆是注解本
⾝有⼀些信息,可以参与到处理的逻辑中。
反射的原理?
我们都知道Java程序的执⾏分为编译和运⾏两步,编译之后会⽣成字节码(.class)⽂件,JVM进
⾏类加载的时候,会加载字节码⽂件,将类型相关的所有信息加载进⽅法区,反射就是去获
取这些信息,然后进⾏各种操作。

接⼜默认⽅法:Java 8允许我们给接⼜添加⼀个⾮抽象的⽅法实现,只需要使⽤ default关
键字修饰即可
Lambda 表达式和函数式接⼜:Lambda 表达式本质上是⼀段匿名内部类,也可以是⼀段
可以传递的代码。Lambda 允许把函数作为⼀个⽅法的参数(函数作为参数传递到⽅法
中),使⽤ Lambda 表达式使代码更加简洁,但是也不要滥⽤,否则会有可读性等问题,
《Effective Java》作者 Josh Bloch 建议使⽤ Lambda 表达式最好不要超过3⾏。
Stream API:⽤函数式编程⽅式在集合类上进⾏复杂操作的⼯具,配合Lambda表达式可以
⽅便的对集合进⾏处理。
Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进⾏的操作,可以执⾏⾮常
复杂的查找、过滤和映射数据等操作。使⽤Stream API 对集合数据进⾏操作,就类似于使
⽤ SQL 执⾏的数据库查询。也可以使⽤ Stream API 来并⾏执⾏操作。
简⽽⾔之,Stream API 提供了⼀种⾼效且易于使⽤的处理数据的⽅式。
⽇期时间API:Java 8 引⼊了新的⽇期时间API改进了⽇期时间的管理。
Optional 类:⽤来解决空指针异常的问题。很久以前 Google Guava 项⽬引⼊了 Optional
作为解决空指针异常的⼀种⽅式,不赞成代码被 null 检查的代码污染,期望程序员写整
洁的代码。受Google Guava的⿎励,Optional 现在是Java 8库的⼀部分。
51.Lambda 表达式了解多少?
Lambda 表达式本质上是⼀段匿名内部类,也可以是⼀段可以传递的代码。
⽐如我们以前使⽤Runnable创建并运⾏线程:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread is running before Java8!");
}
}).start();
这是通过内部类的⽅式来重写run⽅法,使⽤Lambda表达式,还可以更加简洁:
new Thread( () -> System.out.println("Thread is running since Java8!")
).start();
当然不是每个接⼜都可以缩写成 Lambda 表达式。只有那些函数式接⼜(Functional
Interface)才能缩写成 Lambda 表⽰式。
所谓函数式接⼜(Functional Interface)就是只包含⼀个抽象⽅法的声明。针对该接⼜类型的
所有 Lambda 表达式都会与这个抽象⽅法匹配。
Java8有哪些内置函数式接⼜?
JDK 1.8 API 包含了很多内置的函数式接⼜。其中就包括我们在⽼版本中经常见到的
Comparator 和 Runnable,Java 8 为他们都添加了 @FunctionalInterface 注解,以⽤来⽀持
Lambda 表达式。
除了这两个之外,还有Callable、Predicate、Function、Supplier、Consumer等等。
52.Optional了解吗?
Optional 是⽤于防范 NullPointerException 。
可以将 Optional 看做是包装对象(可能是 null , 也有可能⾮ null )的容器。当我们定
义了 ⼀个⽅法,这个⽅法返回的对象可能是空,也有可能⾮空的时候,我们就可以考虑⽤
Optional 来包装它,这也是在 Java 8 被推荐使⽤的做法。
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
Version:0.9 StartHTML:0000000105 EndHTML:0000028344 StartFragment:0000000141 EndFragment:0000028304
53.Stream 流⽤过吗?
Stream 流,简单来说,使⽤ java.util.Stream 对⼀个包含⼀个或多个元素的集合做各
种操作。这些操作可能是 中间操作 亦或是 终端操作。 终端操作会返回⼀个结果,⽽中间操
作会返回⼀个 Stream 流。
Stream流⼀般⽤于集合,我们对⼀个集合做⼏个常见操作:
List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");
Filter 过滤
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa2", "aaa1"
Sorted 排序
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa1", "aaa2"
Map 转换
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Match 匹配
// 验证 list 中 string 是否有以 a 开头的, 匹配到第⼀个,即返回 true
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
// 验证 list 中 string 是否都是以 a 开头的
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
// 验证 list 中 string 是否都不是以 z 开头的,
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
Count 计数
count 是⼀个终端操作,它能够统计 stream 流中的元素总数,返回值是 long 类型。
58
// 先对 list 中字符串开头为 b 进⾏过滤,让后统计数量
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
Reduce
Reduce 中⽂翻译为: 减少、缩⼩。通过⼊参的 Function ,我们能够将 list 归约成⼀
个值。它的返回类型是 Optional 类型。
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
以上是常见的⼏种流式操作,还有其它的⼀些流式操作,可以帮助我们更便捷地处理集合数
据。

本文探讨了Java的面向对象特性,包括JVM、JDK与JRE的区别,跨平台性原理,字节码的作用,以及数据类型、自动/强制类型转换、自增自减运算等。此外,文章还涵盖了多态、访问修饰符、final关键字等核心概念。
707

被折叠的 条评论
为什么被折叠?



