基础概念和常识
- 什么是字节码?采用字节码的好处是什么?
JVM 可以理解的代码就叫做字节码(即扩展名为 .class 的文件),不面向处理器只面向JVM。在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
.class->机器码 这一步:
传统:在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。
JIT(just-in-time compilation) 编译器:而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言 。
- Oracle JDK vs OpenJDK
- OpenJDK 是一个参考模型并且是完全开源的,而 Oracle JDK 是 OpenJDK 的一个实现,并不是完全开源的;
- Oracle JDK 比 OpenJDK 更稳定。OpenJDK 和 Oracle JDK 的代码几乎相同,但 Oracle JDK 有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择 Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用 OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到 Oracle JDK 就可以解决问题;
- 在响应性和 JVM 性能方面,Oracle JDK 与 OpenJDK 相比提供了更好的性能;
- Oracle JDK 不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;
- Oracle JDK 使用 BCL/OTN 协议获得许可,而 OpenJDK 根据 GPL v2 许可获得许可。
Java vs C++
字符型常量和字符串常量的区别?
- 形式:’’ 和””
- 含义:字符型常量是一个整形值(ASC II),字符串常量是一个地址(在内存中的位置)
- 内存大小:字符型占2个字节;字符串占若干
标识符和关键字的区别?
标识符就是编写程序时为方法、变量、类起的名字
关键字是被Java预定义的带有特殊含义的标识符
分类 | 关键字 | ||||||
---|---|---|---|---|---|---|---|
访问控制 | private | protected | public | ||||
类,方法和变量修饰符 | abstract | class | extends | final | implements | interface | native |
new | static | strictfp | synchronized | transient | volatile | enum | |
程序控制 | break | continue | return | do | while | if | else |
for | instanceof | switch | case | default | assert | ||
错误处理 | try | catch | throw | throws | finally | ||
包相关 | import | package | |||||
基本类型 | boolean | byte | char | double | float | int | long |
short | |||||||
变量引用 | super | this | void | ||||
保留字 | goto | const |
注意 ⚠️:虽然 true, false, 和 null 看起来像关键字但实际上他们是字面值,同时你也不可以作为标识符来使用。
- 无返回值方法中的return是什么意思?
表示结束方法的执行,下方的输出语句不会执行
静态方法 vs 实例方法
- 调用方式
- 访问类成员的权限:静态方法只能访问静态成员变量和静态方法
重载(Overload) vs 重写(Override)
- 重载:同一个类中,多个 同名方法 根据不同的传参来执行不同的逻辑处理
编译器必须挑选出具体执行哪个方法,它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。 如果编译器找不到匹配的参数, 就会产生编译时错误, 因为根本不存在匹配, 或者没有一个比其他的更好(这个过程被称为重载解析(overloading resolution))。
- 重写:发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。
- (两同,两小,一大)方法名、参数列表必须相同,子类方法返回值类型应比父类方法返回值类型更小或相等,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
- 如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
- 构造方法无法被重写,可以重载
总结:外壳一样,内部逻辑不一样
区别点 | 重载方法 | 重写方法 |
---|---|---|
发生范围 | 同一个类 | 子类 |
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可修改 | 子类方法返回值类型应比父类方法返回值类型更小或相等 |
异常 | 可修改 | 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等; |
访问修饰符 | 可修改 | 一定不能做更严格的限制(可以降低限制) |
发生阶段 | 编译期 | 运行期 |
- ==和equals()
- ==
- 基本类型比较值,
- 引用类型比较内存地址
- ==
因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址,所以比较的才是地址
- equals()
- 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。
- equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。而Object中定义得equals方法就是 用==号定义的,因此如果没有重写equals的话就等价于==。
String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(aa == bb);// true
System.out.println(a == b);// false
System.out.println(a.equals(b));// true
System.out.println(42 == 42.0);// true
- hashCode()和equals()
- hashCode()的作用?
Object 的 hashCode() 方法是本地方法,也就是用 C 语言或 C++ 实现的,该方法通常用来将对象的内存地址转换为整数之后返回。
散列表存储的是键值对,能根据“键”快速的检索出对应的“值”,需要用到散列码。
- 为什么要有hashCode()?
- HashSet是如何检查重复的?
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashCode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashCode 值作比较,如果没有相符的 hashCode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashCode 值的对象,这时会调用 equals() 方法来检查 hashCode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
2. 为什么 JDK 还要同时提供这两个方法?
效率角度:在HashSet、HashMap这样的容器中使用hashCode()判断元素是否在容器内效率高
3. 为什么不只用hashCode()?
hashCode相等并不能代表两个对象相等,哈希碰撞的话还是需要用equals()来判断是否真的相等
- 为什么重写 equals() 时必须重写 hashCode() 方法?
不重写可能导致equals 方法判断是相等的两个对象,hashCode 值却不相等,进而导致HashMap失效
- 可变长参数 ```java public static void method1(String… args) { //…… }
public static void method2(String arg1, String… args) { //…… }
方法重载时,编译器优先匹配固定参数的方法,因为其匹配度更高
<a name="h13DA"></a>
# 基本数据类型
1. **Java中的基本数据类型**
8种:
1. 6 种数字类型:
- 4 种整数型:byte、short、int、long
- 2 种浮点型:float、double
2. 1 种字符类型:char
2. 1 种布尔型:boolean
| **基本类型** | **位数** | **字节** | **默认值** | **取值范围** |
| --- | --- | --- | --- | --- |
| **byte** | 8 | 1 | 0 | -128 ~ 127 |
| **short** | 16 | 2 | 0 | -32768 ~ 32767 |
| **int** | 32 | 4 | 0 | -2147483648 ~ 2147483647 |
| **long** | 64 | 8 | 0L | -9223372036854775808 ~ 9223372036854775807 |
| **char** | 16 | 2 | 'u0000' | 0 ~ 65535 |
| **float** | 32 | 4 | 0f | 1.4E-45 ~ 3.4028235E38 |
| **double** | 64 | 8 | 0d | 4.9E-324 ~ 1.7976931348623157E308 |
| **boolean** | 1 | | false | true、false |
**注:**①.Java 的每种基本类型所占存储空间的大小不会像其他大多数语言那样随机器硬件架构的变化而变化。这种所占存储空间大小的不变性是 Java 程序比用其他大多数语言编写的程序更具可移植性的原因之一;<br />②.这八种基本类型都有对应的**包装类**分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean;包装类型不赋值就是 Null ,而基本类型有默认值且不是 Null<br />JVM角度:<br />基本数据类型直接存放在 Java 虚拟机**栈中的局部变量表**中,而包装类型属于对象类型,我们知道对象实例都存在于**堆**中。相比于对象类型, 基本数据类型占用的空间非常小。
2. **包装类型的常量池技术**
Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 **[-128,127]** 的相应类型的缓存数据,<br />Character 创建了数值在 **[0,127]** 范围的缓存数据,<br />Boolean 直接返回 **True or False**。<br />两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。(Double i3 = 1.2; 无法使用常量池中的对象,要创建新对象)
如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
```java
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1 == i2);
Integer i1=40 这一行代码会发生装箱,也就是说这行代码等价于 Integer i1=Integer.valueOf(40) 。因此,i1 直接使用的是常量池中的对象。而Integer i2 = new Integer(40) 会直接创建新的对象。
- 自动装箱与拆箱了解吗?原理是什么?
原理:装箱调用了 包装类的valueOf()方法,拆箱是调用了 xxxValue()方法。Integer i = 10; //装箱 等价于Integer i = Integer.valueOf(10);
int n = i; //拆箱 等价于int n = i.intValue();
面向对象
面向对象和面向过程
- 处理问题的模式不同:面向过程把解决问题的过程拆成一个个方法,通过这些方法解决问题;面向对象会先抽象出对象,然后用对象执行方法的方式解决问题
- 面向对象三大特性,更易维护、易复用、易扩展
- 性能角度:面向过程性能比面向对象高。
- 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发。
- 但是面向过程也需要分配内存,计算内存偏移量。Java性能差的主要原因并不是因为它是面向对象语言,而是Java是半编译语言,最终的执行代码并不是可以直接被CPU执行的二进制机械码。而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比Java好。
成员变量 vs 局部变量
- 语法形式:成员变量可以被public, private等访问控制符和static修饰,局部变量不能,但是两者都可以被final修饰
- 存储方式:static成员变量属于类,非静态成员变量属于对象,存储在堆中;局部变量存在于栈
- 生存周期:成员变量随着对象的创建而存在;局部变量随着方法的调用而自动消失
- 默认值:成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值);而局部变量则不会自动赋值。
对象实体与对象引用
new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。
https://www.cnblogs.com/vamei/archive/2013/04/01/2992484.html
Human aPerson = new Human(160);
右侧:new是在内存中为对象开辟空间。具体来说,new是在内存的堆(heap)上为对象开辟空间。这一空间中,保存有对象的数据和方法。
左侧:aPerson指代一个Human对象,被称为对象引用(reference)。实际上,aPerson并不是对象本身,而是类似于一个指向对象的指针。aPerson存在于内存的栈(stack)中。
用等号赋值时,是将右侧new在堆中创建对象的地址赋予给对象引用。
在Java中,我们不能跳过引用去直接接触对象。
在Java中,引用起到了指针的作用,但我们不能直接修改指针的值,比如像C语言那样将指针值加1。我们只能通过引用执行对对象的操作。这样的设计避免了许多指针可能引起的错误。
- 构造方法
构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
- 面向对象三大特性
- 封装 把一个对象的状态信息(属性)隐藏在内部,而不允许外部对象直接访问对象的内部信息,但可以提供用来被外界访问的方法
- 继承 使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展
- 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有;
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展;
- 子类可以用自己的方式实现父类的方法。
- 多态 指同一个类的不同实例调用名称相同的方法时,有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口,最常见的多态就是将子类传入父类参数中。
- 运行期多态,动态多态
需要满足三个条件:存在类继承或者接口实现、子类重写了父类的方法、父类的引用指向子类的对象
引申:但是,有些时候你用到的对象并不都是自己声明的。比如Spring 中的IOC出来的对象,你在使用的时候就不知道他是谁,或者说你可以不用关心他是谁。根据具体情况而定。
IOC,是Ioc—Inversion of Control 的缩写,中文翻译成“控制反转”,它是一种设计思想,意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
换句话说当我们使用Spring框架的时候,对象是Spring容器创建出来并由容器进行管理,我们只需要使用就行了。
2. 静态多态
Java中的函数重载
- 面向对象五大基本原则
- 单一职责原则 一个类,最好只做一件事(低耦合、高内聚在面向对象原则上的引申)
- 开放封闭原则
- 对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
- 对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
(核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改就是封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的。)
- 里氏替换原则 子类必须能够替换其基类,是关于继承机制的设计原则
- 依赖倒置原则 具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
- 接口隔离原则 使用多个小的专门的接口,而不要使用一个大的总接口。
- 接口和抽象类的共同点和区别
- 共同点:
- 不能被实例化
- 可以包含抽象方法
- 可以有默认的实现方法(接口中用default)
- 区别:
- 目的不同:抽象类是为了代码复用,接口是为了约束类的行为(Java中有的接口没有一行代码,只起到标志作用,比如Cloneable, Serializable)
- 个数不同:一个类只能继承一个类,但是可以实现多个接口
- 成员变量的类型不同:抽象类的成员变量默认default,可以在子类中被重新定义或重新赋值;接口中的成员变量是public static final,不能被修改且必须有初值
- 共同点:
- 浅拷贝、引用拷贝、深拷贝
- 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。Object类中提供的clone()方法就是浅拷贝。
- 引用拷贝:两个不同的引用指向同一个对象,没有发生对象的拷贝,拷贝的是栈地址。
- 深拷贝 :深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
Java 常见对象
- Object
所有类的父类
public final native Class<?> getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。
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 为true,x.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方法一样,只不过该方法一直等待,没有超时时间这个概念
protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作
String
String为什么是不可变的?StringBuilder 与 StringBuffer可变(它们的父类AbstractStringBuilder没有使用 final 和 private 关键字修饰)
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
//...
}
- 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
- String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
- StringBuilder 与 StringBuffer:线程安全,StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
- 三者比较:
- 可变性角度
- 性能角度:每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
- 从线程角度:单线程builder,多线程buffer
- Java中没有运算符重载,那String中的”+”和”+=”是怎么实现的?
对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。
在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。
直接使用 StringBuilder 对象进行字符串拼接的话,就不会存在这个问题
- String的equals()方法比较的是字符串的值是否相等,Object的equals()比较对象的内存地址
- 字符串常量池是针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
Java 泛型
- 泛型的作用?
编译时安全类型检测,允许程序员在编译时检测到非法的类型
本质是参数化类型,所操作的数据类型被指定为一个参数
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);
- 什么是类型擦除?
Java中的泛型是伪泛型,所有泛型信息在运行期间会被擦掉,即类型擦除
- 常用的通配符有哪些?
- ? 表示不确定的 Java 类型
- T (type) 表示具体的一个 Java 类型
- K V (key value) 分别代表 Java 键值中的 Key Value
- E (element) 代表 Element
- 哪里用得上泛型
解决通用问题
- 可用于定义通用返回结果 CommonResult
通过参数 T 可根据具体的返回类型动态指定结果的数据类型 - 定义 Excel 处理类 ExcelUtil
用于动态指定 Excel 导出的数据类型 - 用于构建集合工具类。参考 Collections 中的 sort, binarySearch 方法
反射
- 何为反射
框架的灵魂,运行时分析类和执行类中方法的能力(以泛型中的类型擦除为例)
通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
- 反射机制优缺点
- 优点:代码更加灵活,各种框架开箱即用的功能
- 缺点:
- 安全问题 无视泛型参数的安全检查
- 性能稍差(对框架影响不大):
- 编译器不能做优化因为不知道你在做什么
- 必须发现所有的invoke或者creat(类按照名称查找,方法按照匹配)
- 变量要拆箱装箱,InvocationTargetException异常
- 应用场景
Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制
这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射
注解 的实现也用到了反射:因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
注解Annontation
注解本质是一个继承了Annotation 的特殊接口:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
public interface Override extends Annotation{
}
注解只有被解析之后才会生效,常见的解析方法有两种:
- 编译期直接扫描 :编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用@Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
- 运行期通过反射处理 :像框架中自带的注解(比如 Spring 框架的 @Value 、@Component)都是通过反射来进行处理的。
JDK 提供了很多内置的注解(比如 @Override 、@Deprecated),同时,我们还可以自定义注解。
异常
- Exception和Error
所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类
Exception和Error是Throwable的两个子类
Exception是程序本身可以处理的异常,可以用catch捕获。可以分为Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)
Error程序无法处理,不建议用catch捕获(例如虚拟机运行错误,虚拟机内存不足,类定义错误),JVM这时一般会选择线程终止
Checked Exception和Unchecked Exception
- Checked Exception 在编译过程中,如果受检查异常没有被catch或者throw,则无法通过编译。主要包括:IO 相关的异常、ClassNotFoundException 、SQLException等。
- Unchecked Exception 不处理 不受检查异常,也能通过编译。主要包括:RuntimeException 及其子类(NullPointerException、NumberFormatException(字符串转换为数字)、ArrayIndexOutOfBoundsException(数组越界)、ClassCastException(类型转换错误)、ArithmeticException(算术错误))
Throwable类常用方法
- String getMessage(): 返回异常发生时的简要描述
- String toString(): 返回异常发生时的详细信息
- String getLocalizedMessage(): 返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。否则返回的信息与 getMessage()返回的结果相同
- void printStackTrace(): 在控制台上打印 Throwable 对象封装的异常信息
try-catch-finally怎么使用?
try { //try用于捕获异常
System.out.println("Try to do something");
throw new RuntimeException("RuntimeException");
} catch (Exception e) { //catch用于处理异常
System.out.println("Catch Exception -> " + e.getMessage());
} finally { //无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。
System.out.println("Finally");
}
不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。
finally中的代码一定会执行吗?
不一定。
比如finally 之前虚拟机被终止运行了
try {
System.out.println("Try to do something");
throw new RuntimeException("RuntimeException");
} catch (Exception e) {
System.out.println("Catch Exception -> " + e.getMessage());
// 终止当前正在运行的Java虚拟机
System.exit(1);
} finally {
System.out.println("Finally");
}
另外,在以下 2 种特殊情况下,finally 块的代码也不会被执行:
- 程序所在的线程死亡。
- 关闭 CPU
- 如何使用try-with-resources 代替 try-catch-finally?
应用场景:任何实现 java.lang.AutoCloseable或者 java.io.Closeable 的对象
面对必须要关闭的资源,我们总是应该优先使用 try-with-resources 而不是try-finally。
关闭资源和 finally 块的执行顺序:在声明的资源关闭后,才执行catch和finally块
//读取文本文件的内容 try-catch-finally
Scanner scanner = null;
try {
scanner = new Scanner(new File("D://read.txt"));
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (scanner != null) {
scanner.close();
}
}
//try-with-resources
try (Scanner scanner = new Scanner(new File("test.txt"))) {
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
}
//通过使用分号分隔,可以在try-with-resources块中声明多个资源。
try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
int b;
while ((b = bin.read()) != -1) {
bout.write(b);
}
}
catch (IOException e) {
e.printStackTrace();
}
IO
序列化和反序列化
- 目的:为了通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中,需要持久化Java对象
- 作用:序列化将数据结构或对象转换成二进制字节流的过程(对于Java来说,都是序列化对象Object),反序列化反之
transient关键字
序列化时有些字段不想序列化
- transient 只能修饰变量,不能修饰类和方法。
- transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是 0。
- static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化。
- 获取键盘输入的两种方法 ```java //1.Scanner Scanner input = new Scanner(System.in); String s = input.nextLine(); input.close();
//2.BufferedReader BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); String s = input.readLine(); ```
Java中的IO流分为几种?
- 流向:输入流 输出流
- 操作单元:字节流 字符流
- 流的角色:节点流 处理流
字节流和字符流 (不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?)
计算机处理字符数据时,会将读到的字节信号,先进行查指定的 charset (也就是我们说的编码表),然后找到对应关系后以字符信号的形式输出,而其它数据就不用转化。
这样处理起来较麻烦,于是就将编码表和字节流进行了封装,就成了字符流。
可以这样简单理解:字符流=字节流+编码表
音频文件、图片等媒体文件用字节流比较好
如果涉及到字符的话使用字符流比较好。