这篇《Java 基础知识总结》是 JavaGuide 上浏览量最高的一篇文章,因为我对其进行了重构欠缺并且修复了很多小问题,所以,在 segmentfault 同步一下!
文章内容比拟多,目录如下:
根底概念与常识
Java 语言有哪些特点?
- 简略易学;
- 面向对象(封装,继承,多态);
- 平台无关性(Java 虚拟机实现平台无关性);
- 反对多线程(C++ 语言没有内置的多线程机制,因而必须调用操作系统的多线程性能来进行多线程程序设计,而 Java 语言却提供了多线程反对);
- 可靠性;
- 安全性;
- 反对网络编程并且很不便(Java 语言诞生自身就是为简化网络编程设计的,因而 Java 语言不仅反对网络编程而且很不便);
- 编译与解释并存;
修改(参见:issue#544):C++11 开始(2011 年的时候),C++ 就引入了多线程库,在 windows、linux、macos 都能够应用
std::thread
和std::async
来创立线程。参考链接:http://www.cplusplus.com/refe…
JVM vs JDK vs JRE
JVM
Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同零碎的特定实现(Windows,Linux,macOS),目标是应用雷同的字节码,它们都会给出雷同的后果。
什么是字节码? 采纳字节码的益处是什么?
在 Java 中,JVM 能够了解的代码就叫做
字节码
(即扩大名为.class
的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的形式,在肯定水平上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比拟高效,而且,因为字节码并不针对一种特定的机器,因而,Java 程序毋庸从新编译便可在多种不同操作系统的计算机上运行。
Java 程序从源代码到运行个别有上面 3 步:
咱们须要分外留神的是 .class-> 机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,而后通过解释器逐行解释执行,这种形式的执行速度会绝对比较慢。而且,有些办法和代码块是常常须要被调用的(也就是所谓的热点代码),所以前面引进了 JIT 编译器,而 JIT 属于运行时编译。当 JIT 编译器实现第一次编译后,其会将字节码对应的机器码保留下来,下次能够间接应用。而咱们晓得,机器码的运行效率必定是高于 Java 解释器的。这也解释了咱们为什么常常会说 Java 是编译与解释共存的语言。
HotSpot 采纳了惰性评估 (Lazy Evaluation) 的做法,依据二八定律,耗费大部分系统资源的只有那一小部分的代码(热点代码),而这也就是 JIT 所须要编译的局部。JVM 会依据代码每次被执行的状况收集信息并相应地做出一些优化,因而执行的次数越多,它的速度就越快。JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation),它是间接将字节码编译成机器码,这样就防止了 JIT 预热等各方面的开销。JDK 反对分层编译和 AOT 合作应用。然而,AOT 编译器的编译品质是必定比不上 JIT 编译器的。
总结:
Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同零碎的特定实现(Windows,Linux,macOS),目标是应用雷同的字节码,它们都会给出雷同的后果。字节码和不同零碎的 JVM 实现是 Java 语言“一次编译,随处能够运行”的关键所在。
JDK 和 JRE
JDK 是 Java Development Kit 缩写,它是功能齐全的 Java SDK。它领有 JRE 所领有的所有,还有编译器(javac)和工具(如 javadoc 和 jdb)。它可能创立和编译程序。
JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的汇合,包含 Java 虚拟机(JVM),Java 类库,java 命令和其余的一些根底构件。然而,它不能用于创立新程序。
如果你只是为了运行一下 Java 程序的话,那么你只须要装置 JRE 就能够了。如果你须要进行一些 Java 编程方面的工作,那么你就须要装置 JDK 了。然而,这不是相对的。有时,即便您不打算在计算机上进行任何 Java 开发,依然须要装置 JDK。例如,如果要应用 JSP 部署 Web 应用程序,那么从技术上讲,您只是在应用程序服务器中运行 Java 程序。那你为什么须要 JDK 呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且须要应用 JDK 来编译 servlet。
为什么说 Java 语言“编译与解释并存”?
高级编程语言依照程序的执行形式分为编译型和解释型两种。简略来说,编译型语言是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码;解释型语言是指解释器对源程序逐行解释成特定平台的机器码并立刻执行。比方,你想浏览一本英文名著,你能够找一个英文翻译人员帮忙你浏览,
有两种抉择形式,你能够先等翻译人员将全本的英文名著(也就是源码)都翻译成汉语,再去浏览,也能够让翻译人员翻译一段,你在旁边浏览一段,缓缓把书读完。
Java 语言既具备编译型语言的特色,也具备解释型语言的特色,因为 Java 程序要通过先编译,后解释两个步骤,由 Java 编写的程序须要先通过编译步骤,生成字节码(\*.class
文件),这种字节码必须由 Java 解释器来解释执行。因而,咱们能够认为 Java 语言编译与解释并存。
Oracle JDK 和 OpenJDK 的比照
可能在看这个问题之前很多人和我一样并没有接触和应用过 OpenJDK。那么 Oracle 和 OpenJDK 之间是否存在重大差别?上面我通过收集到的一些材料,为你解答这个被很多人漠视的问题。
对于 Java 7,没什么要害的中央。OpenJDK 我的项目次要基于 Sun 捐献的 HotSpot 源代码。此外,OpenJDK 被选为 Java 7 的参考实现,由 Oracle 工程师保护。对于 JVM,JDK,JRE 和 OpenJDK 之间的区别,Oracle 博客帖子在 2012 年有一个更具体的答案:
问:OpenJDK 存储库中的源代码与用于构建 Oracle JDK 的代码之间有什么区别?
答:十分靠近 – 咱们的 Oracle JDK 版本构建过程基于 OpenJDK 7 构建,只增加了几个局部,例如部署代码,其中包含 Oracle 的 Java 插件和 Java WebStart 的实现,以及一些关闭的源代码派对组件,如图形光栅化器,一些开源的第三方组件,如 Rhino,以及一些系统的货色,如附加文档或第三方字体。展望未来,咱们的目标是开源 Oracle JDK 的所有局部,除了咱们思考商业性能的局部。
总结:
- Oracle JDK 大略每 6 个月发一次次要版本,而 OpenJDK 版本大略每三个月公布一次。但这不是固定的,我感觉理解这个没啥用途。详情参见:https://blogs.oracle.com/java-platform-group/update-and-faq-on-the-java-se-release-cadence。
- 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 许可取得许可。
🌈 拓展一下:
- BCL 协定(Oracle Binary Code License Agreement):能够应用 JDK(反对商用),然而不能进行批改。
- OTN 协定(Oracle Technology Network License Agreement):11 及之后新公布的 JDK 用的都是这个协定,能够本人私下用,然而商用须要付费。
相干浏览👍:《Differences Between Oracle JDK and OpenJDK》
Java 和 C++ 的区别?
我晓得很多人没学过 C++,然而面试官就是没事喜爱拿咱们 Java 和 C++ 比呀!没方法!!!就算没学过 C++,也要记下来!
- 都是面向对象的语言,都反对封装、继承和多态
- Java 不提供指针来间接拜访内存,程序内存更加平安
- Java 的类是单继承的,C++ 反对多重继承;尽管 Java 的类不能够多继承,然而接口能够多继承。
- Java 有主动内存治理垃圾回收机制(GC),不须要程序员手动开释无用内存。
- C ++ 同时反对办法重载和操作符重载,然而 Java 只反对办法重载(操作符重载减少了复杂性,这与 Java 最后的设计思维不符)。
- ……
import java 和 javax 有什么区别?
刚开始的时候 JavaAPI 所必须的包是 java 结尾的包,javax 过后只是扩大 API 包来应用。然而随着工夫的推移,javax 逐步地扩大成为 Java API 的组成部分。然而,将扩大从 javax 包挪动到 java 包的确太麻烦了,最终会毁坏一堆现有的代码。因而,最终决定 javax 包将成为规范 API 的一部分。
所以,实际上 java 和 javax 没有区别。这都是一个名字。
根本语法
字符型常量和字符串常量的区别?
- 模式 : 字符常量是单引号引起的一个字符,字符串常量是双引号引起的 0 个或若干个字符
- 含意 : 字符常量相当于一个整型值(ASCII 值), 能够加入表达式运算; 字符串常量代表一个地址值(该字符串在内存中寄存地位)
-
占内存大小 :字符常量只占 2 个字节; 字符串常量占若干个字节 ( 留神:char 在 Java 中占两个字节),
字符封装类
Character
有一个成员常量Character.SIZE
值为 16, 单位是bits
, 该值除以 8(1byte=8bits
) 后就能够失去 2 个字节
java 编程思维第四版:2.2.2 节
正文
Java 中的正文有三种:
- 单行正文
- 多行正文
- 文档正文。
在咱们编写代码的时候,如果代码量比拟少,咱们本人或者团队其余成员还能够很轻易地看懂代码,然而当我的项目构造一旦简单起来,咱们就须要用到正文了。正文并不会执行(编译器在编译代码之前会把代码中的所有正文抹掉, 字节码中不保留正文),是咱们程序员写给本人看的,正文是你的代码说明书,可能帮忙看代码的人疾速天文清代码之间的逻辑关系。因而,在写程序的时候顺手加上正文是一个十分好的习惯。
《Clean Code》这本书明确指出:
代码的正文不是越具体越好。实际上好的代码自身就是正文,咱们要尽量标准和丑化本人的代码来缩小不必要的正文。
若编程语言足够有表达力,就不须要正文,尽量通过代码来论述。
举个例子:
去掉上面简单的正文,只须要创立一个与正文所言同一事物的函数即可
// check to see if the employee is eligible for full benefits if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))
应替换为
if (employee.isEligibleForFullBenefits())
标识符和关键字的区别是什么?
在咱们编写程序的时候,须要大量地为程序、类、变量、办法等取名字,于是就有了标识符,简略来说,标识符就是一个名字。然而有一些标识符,Java 语言曾经赋予了其非凡的含意,只能用于特定的中央,这种非凡的标识符就是关键字。因而,关键字是被赋予非凡含意的标识符。比方,在咱们的日常生活中,“警察局”这个名字曾经被赋予了非凡的含意,所以如果你开一家店,店的名字不能叫“警察局”,“警察局”就是咱们日常生活中的关键字。
Java 中有哪些常见的关键字?
访问控制 | private | protected | public | ||||
---|---|---|---|---|---|---|---|
类,办法和变量修饰符 | abstract | class | extends | final | implements | interface | native |
new | static | strictfp | synchronized | transient | volatile | ||
程序控制 | break | continue | return | do | while | if | else |
for | instanceof | switch | case | default | |||
错误处理 | try | catch | throw | throws | finally | ||
包相干 | import | package | |||||
根本类型 | boolean | byte | char | double | float | int | long |
short | null | true | false | ||||
变量援用 | super | this | void | ||||
保留字 | goto | const |
自增自减运算符
在写代码的过程中,常见的一种状况是须要某个整数类型变量减少 1 或缩小 1,Java 提供了一种非凡的运算符,用于这种表达式,叫做自增运算符(++)和自减运算符(–)。
++ 和 – 运算符能够放在变量之前,也能够放在变量之后,当运算符放在变量之前时(前缀),先自增 / 减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增 / 减。例如,当 b = ++a
时,先自增(本人减少 1),再赋值(赋值给 b);当 b = a++
时,先赋值(赋值给 b),再自增(本人减少 1)。也就是,++a 输入的是 a+1 的值,a++ 输入的是 a 值。用一句口诀就是:“符号在前就先加 / 减,符号在后就后加 / 减”。
continue、break、和 return 的区别是什么?
在循环构造中,当循环条件不满足或者循环次数达到要求时,循环会失常完结。然而,有时候可能须要在循环的过程中,当产生了某种条件之后,提前终止循环,这就须要用到上面几个关键词:
- continue:指跳出以后的这一次循环,持续下一次循环。
- break:指跳出整个循环体,继续执行循环上面的语句。
return 用于跳出所在办法,完结该办法的运行。return 个别有两种用法:
return;
:间接应用 return 完结办法执行,用于没有返回值函数的办法return value;
:return 一个特定值,用于有返回值函数的办法
Java 泛型理解么?什么是类型擦除?介绍一下罕用的通配符?
Java 泛型(generics)是 JDK 5 中引入的一个新个性, 泛型提供了编译时类型平安检测机制,该机制容许程序员在编译时检测到非法的类型。泛型的实质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
Java 的泛型是伪泛型,这是因为 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);
泛型个别有三种应用形式: 泛型类、泛型接口、泛型办法。
1. 泛型类:
// 此处 T 能够轻易写为任意标识,常见的如 T、E、K、V 等模式的参数罕用于示意泛型
// 在实例化泛型类时,必须指定 T 的具体类型
public class Generic<T>{
private T key;
public Generic(T key) {this.key = key;}
public T getKey(){return key;}
}
如何实例化泛型类:
Generic<Integer> genericInteger = new Generic<Integer>(123456);
2. 泛型接口:
public interface Generator<T> {public T method();
}
实现泛型接口,不指定类型:
class GeneratorImpl<T> implements Generator<T>{
@Override
public T method() {return null;}
}
实现泛型接口,指定类型:
class GeneratorImpl<T> implements Generator<String>{
@Override
public String method() {return "hello";}
}
3. 泛型办法:
public static < E > void printArray(E[] inputArray )
{for ( E element : inputArray){System.out.printf( "%s", element);
}
System.out.println();}
应用:
// 创立不同类型数组:Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3};
String[] stringArray = { "Hello", "World"};
printArray(intArray);
printArray(stringArray);
罕用的通配符为:T,E,K,V,?
- ?示意不确定的 java 类型
- T (type) 示意具体的一个 java 类型
- K V (key value) 别离代表 java 键值中的 Key Value
- E (element) 代表 Element
== 和 equals 的区别
对于根本数据类型来说,== 比拟的是值。对于援用数据类型来说,== 比拟的是对象的内存地址。
因为 Java 只有值传递,所以,对于 == 来说,不论是比拟根本数据类型,还是援用数据类型的变量,其本质比拟的都是值,只是援用类型变量存的值是对象的地址。
equals()
作用不能用于判断根本数据类型的变量,只能用来判断两个对象是否相等。equals()
办法存在于 Object
类中,而 Object
类是所有类的间接或间接父类。
Object
类 equals()
办法:
public boolean equals(Object obj) {return (this == obj);
}
equals()
办法存在两种应用状况:
- 类没有笼罩
equals()
办法 :通过equals()
比拟该类的两个对象时,等价于通过“==”比拟这两个对象,应用的默认是Object
类equals()
办法。 - 类笼罩了
equals()
办法 :个别咱们都笼罩equals()
办法来比拟两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
举个例子:
public class test1 {public static void main(String[] args) {String a = new String("ab"); // a 为一个援用
String b = new String("ab"); // b 为另一个援用, 对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
if (aa == bb) // true
System.out.println("aa==bb");
if (a == b) // false,非同一对象
System.out.println("a==b");
if (a.equals(b)) // true
System.out.println("aEQb");
if (42 == 42.0) { // true
System.out.println("true");
}
}
}
阐明:
String
中的equals
办法是被重写过的,因为Object
的equals
办法是比拟的对象的内存地址,而String
的equals
办法比拟的是对象的值。- 当创立
String
类型的对象时,虚构机会在常量池中查找有没有曾经存在的值和要创立的值雷同的对象,如果有就把它赋给以后援用。如果没有就在常量池中从新创立一个String
对象。
String
类 equals()
办法:
public boolean equals(Object anObject) {if (this == anObject) {return true;}
if (anObject instanceof String) {String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
hashCode()与 equals()
面试官可能会问你:“你重写过 hashcode
和 equals
么,为什么重写 equals
时必须重写 hashCode
办法?”
1)hashCode()介绍:
hashCode()
的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引地位。hashCode()
定义在 JDK 的 Object
类中,这就意味着 Java 中的任何类都蕴含有 hashCode()
函数。另外须要留神的是:Object
的 hashcode 办法是本地办法,也就是用 c 语言或 c++ 实现的,该办法通常用来将对象的 内存地址 转换为整数之后返回。
public native int hashCode();
散列表存储的是键值对(key-value),它的特点是:能依据“键”疾速的检索出对应的“值”。这其中就利用到了散列码!(能够疾速找到所须要的对象)
2)为什么要有 hashCode?
咱们以“HashSet
如何查看反复”为例子来阐明为什么要有 hashCode?
当你把对象退出 HashSet
时,HashSet
会先计算对象的 hashcode 值来判断对象退出的地位,同时也会与其余曾经退出的对象的 hashcode 值作比拟,如果没有相符的 hashcode,HashSet
会假如对象没有反复呈现。然而如果发现有雷同 hashcode 值的对象,这时会调用 equals()
办法来查看 hashcode 相等的对象是否真的雷同。如果两者雷同,HashSet
就不会让其退出操作胜利。如果不同的话,就会从新散列到其余地位。(摘自我的 Java 启蒙书《Head First Java》第二版)。这样咱们就大大减少了 equals 的次数,相应就大大提高了执行速度。
3)为什么重写 equals
时必须重写 hashCode
办法?
如果两个对象相等,则 hashcode 肯定也是雷同的。两个对象相等, 对两个对象别离调用 equals 办法都返回 true。然而,两个对象有雷同的 hashcode 值,它们也不肯定是相等的。因而,equals 办法被笼罩过,则 hashCode
办法也必须被笼罩。
hashCode()
的默认行为是对堆上的对象产生独特值。如果没有重写hashCode()
,则该 class 的两个对象无论如何都不会相等(即便这两个对象指向雷同的数据)
4)为什么两个对象有雷同的 hashcode 值,它们也不肯定是相等的?
在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。
因为 hashCode()
所应用的杂凑算法兴许刚好会让多个对象传回雷同的杂凑值。越蹩脚的杂凑算法越容易碰撞,但这也与数据值域散布的个性无关(所谓碰撞也就是指的是不同的对象失去雷同的 hashCode
。
咱们刚刚也提到了 HashSet
, 如果 HashSet
在比照的时候,同样的 hashcode 有多个对象,它会应用 equals()
来判断是否真的雷同。也就是说 hashcode
只是用来放大查找老本。
更多对于 hashcode()
和 equals()
的内容能够查看:Java hashCode() 和 equals()的若干问题解答
根本数据类型
Java 中的几种根本数据类型是什么?对应的包装类型是什么?各自占用多少字节呢?
Java 中有 8 种根本数据类型,别离为:
- 6 种数字类型:
byte
、short
、int
、long
、float
、double
- 1 种字符类型:
char
- 1 种布尔型:
boolean
。
这 8 种根本数据类型的默认值以及所占空间的大小如下:
根本类型 | 位数 | 字节 | 默认值 |
---|---|---|---|
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 |
另外,对于 boolean
,官网文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上了解是占用 1 位,然而理论中会思考计算机高效存储因素。
留神:
- Java 里应用
long
类型的数据肯定要在数值前面加上 L,否则将作为整型解析。 char a = 'h'
char : 单引号,String a = "hello"
: 双引号。
这八种根本类型都有对应的包装类别离为:Byte
、Short
、Integer
、Long
、Float
、Double
、Character
、Boolean
。
包装类型不赋值就是 Null
,而根本类型有默认值且不是 Null
。
另外,这个问题倡议还能够先从 JVM 层面来剖析。
根本数据类型间接寄存在 Java 虚拟机栈中的局部变量表中,而包装类型属于对象类型,咱们晓得对象实例都存在于堆中。相比于对象类型,根本数据类型占用的空间十分小。
《深刻了解 Java 虚拟机》:局部变量表次要寄存了编译期可知的根本数据类型 (boolean、byte、char、short、int、float、long、double)、 对象援用(reference 类型,它不同于对象自身,可能是一个指向对象起始地址的援用指针,也可能是指向一个代表对象的句柄或其余与此对象相干的地位)。
主动装箱与拆箱
- 装箱:将根本类型用它们对应的援用类型包装起来;
- 拆箱:将包装类型转换为根本数据类型;
举例:
Integer i = 10; // 装箱
int n = i; // 拆箱
下面这两行代码对应的字节码为:
L1
LINENUMBER 8 L1
ALOAD 0
BIPUSH 10
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
PUTFIELD AutoBoxTest.i : Ljava/lang/Integer;
L2
LINENUMBER 9 L2
ALOAD 0
ALOAD 0
GETFIELD AutoBoxTest.i : Ljava/lang/Integer;
INVOKEVIRTUAL java/lang/Integer.intValue ()I
PUTFIELD AutoBoxTest.n : I
RETURN
从字节码中,咱们发现装箱其实就是调用了 包装类的 valueOf()
办法,拆箱其实就是调用了 xxxValue()
办法。
因而,
Integer i = 10
等价于Integer i = Integer.valueOf(10)
int n = i
等价于int n = i.intValue()
;
8 种根本类型的包装类和常量池
Java 根本类型的包装类的大部分都实现了常量池技术。Byte
,Short
,Integer
,Long
这 4 种包装类默认创立了数值 [-128,127] 的相应类型的缓存数据,Character
创立了数值在 [0,127] 范畴的缓存数据,Boolean
间接返回 True
Or False
。
Integer 缓存源码:
/**
* 此办法将始终缓存 -128 到 127(包含端点)范畴内的值,并能够缓存此范畴之外的其余值。*/
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];}
Character
缓存源码:
public static Character valueOf(char c) {if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
private static class CharacterCache {private CharacterCache(){}
static final Character cache[] = new Character[127 + 1];
static {for (int i = 0; i < cache.length; i++)
cache[i] = new Character((char)i);
}
}
Boolean
缓存源码:
public static Boolean valueOf(boolean b) {return (b ? TRUE : FALSE);
}
如果超出对应范畴依然会去创立新的对象,缓存的范畴区间的大小只是在性能和资源之间的衡量。
两种浮点数类型的包装类 Float
,Double
并没有实现常量池技术。
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输入 true
Float i11 = 333f;
Float i22 = 333f;
System.out.println(i11 == i22);// 输入 false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输入 false
上面咱们来看一下问题。上面的代码的输入后果是 true
还是 flase
呢?
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);
Integer i1=40
这一行代码会产生拆箱,也就是说这行代码等价于 Integer i1=Integer.valueOf(40)
。因而,i1
间接应用的是常量池中的对象。而Integer i1 = new Integer(40)
会间接创立新的对象。
因而,答案是 false
。你答对了吗?
记住:所有整型包装类对象之间值的比拟,全副应用 equals 办法比拟。
办法(函数)
什么是办法的返回值?
办法的返回值是指咱们获取到的某个办法体中的代码执行后产生的后果!(前提是该办法可能产生后果)。返回值的作用是接管出后果,使得它能够用于其余的操作!
办法有哪几种类型?
1. 无参数无返回值的办法
// 无参数无返回值的办法(如果办法没有返回值,不能不写,必须写 void,示意没有返回值)
public void f1() {System.out.println("无参数无返回值的办法");
}
2. 有参数无返回值的办法
/**
* 有参数无返回值的办法
* 参数列表由零组到多组“参数类型 + 形参名”组合而成,多组参数之间以英文逗号(,)隔开,形参类型和形参名之间以英文空格隔开
*/
public void f2(int a, String b, int c) {System.out.println(a + "-->" + b + "-->" + c);
}
3. 有返回值无参数的办法
// 有返回值无参数的办法(返回值能够是任意的类型, 在函数外面必须有 return 关键字返回对应的类型)public int f3() {System.out.println("有返回值无参数的办法");
return 2;
}
4. 有返回值有参数的办法
// 有返回值有参数的办法
public int f4(int a, int b) {return a * b;}
5.return 在无返回值办法的非凡应用
// return 在无返回值办法的非凡应用
public void f5(int a) {if (a > 10) {return;// 示意完结所在办法(f5 办法)的执行, 下方的输入语句不会执行}
System.out.println(a);
}
在一个静态方法内调用一个非动态成员为什么是非法的?
这个须要联合 JVM 的相干常识,静态方法是属于类的,在类加载的时候就会分配内存,能够通过类名间接拜访。而非动态成员属于实例对象,只有在对象实例化之后才存在,而后通过类的实例对象去拜访。在类的非动态成员不存在的时候动态成员就曾经存在了,此时调用在内存中还不存在的非动态成员,属于非法操作。
静态方法和实例办法有何不同?
- 在内部调用静态方法时,能够应用 ” 类名. 办法名 ” 的形式,也能够应用 ” 对象名. 办法名 ” 的形式。而实例办法只有前面这种形式。也就是说,调用静态方法能够无需创建对象。
- 静态方法在拜访本类的成员时,只容许拜访动态成员(即动态成员变量和静态方法),而不容许拜访实例成员变量和实例办法;实例办法则无此限度。
为什么 Java 中只有值传递?
首先,咱们回顾一下在程序设计语言中无关将参数传递给办法(或函数)的一些专业术语。
按值调用 (call by value) 示意办法接管的是调用者提供的值, 按援用调用(call by reference) 示意办法接管的是调用者提供的变量地址。一个办法能够批改传递援用所对应的变量值,而不能批改传递值调用所对应的变量值。它用来形容各种程序设计语言(不只是 Java)中办法参数传递形式。
Java 程序设计语言总是采纳按值调用。也就是说,办法失去的是所有参数值的一个拷贝,也就是说,办法不能批改传递给它的任何参数变量的内容。
上面通过 3 个例子来给大家阐明
example 1
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 =" + num1);
System.out.println("num2 =" + num2);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a =" + a);
System.out.println("b =" + b);
}
后果:
a = 20
b = 10
num1 = 10
num2 = 20
解析:
在 swap 办法中,a、b 的值进行替换,并不会影响到 num1、num2。因为,a、b 中的值,只是从 num1、num2 的复制过去的。也就是说,a、b 相当于 num1、num2 的正本,正本的内容无论怎么批改,都不会影响到原件自身。
通过下面例子,咱们曾经晓得了一个办法不能批改一个根本数据类型的参数,而对象援用作为参数就不一样,请看 example2.
example 2
public static void main(String[] args) {int[] arr = {1, 2, 3, 4, 5};
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}
public static void change(int[] array) {
// 将数组的第一个元素变为 0
array[0] = 0;
}
后果:
1
0
解析:
array 被初始化 arr 的拷贝也就是一个对象的援用,也就是说 array 和 arr 指向的是同一个数组对象。因而,内部对援用对象的扭转会反映到所对应的对象上。
通过 example2 咱们曾经看到,实现一个扭转对象参数状态的办法并不是一件难事。理由很简略,办法失去的是对象援用的拷贝,对象援用及其他的拷贝同时援用同一个对象。
很多程序设计语言(特地是,C++ 和 Pascal)提供了两种参数传递的形式:值调用和援用调用。有些程序员(甚至本书的作者)认为 Java 程序设计语言对对象采纳的是援用调用,实际上,这种了解是不对的。因为这种误会具备肯定的普遍性,所以上面给出一个反例来具体地论述一下这个问题。
example 3
public class Test {public static void main(String[] args) {
// TODO Auto-generated method stub
Student s1 = new Student("小张");
Student s2 = new Student("小李");
Test.swap(s1, s2);
System.out.println("s1:" + s1.getName());
System.out.println("s2:" + s2.getName());
}
public static void swap(Student x, Student y) {
Student temp = x;
x = y;
y = temp;
System.out.println("x:" + x.getName());
System.out.println("y:" + y.getName());
}
}
后果:
x: 小李
y: 小张
s1: 小张
s2: 小李
解析:
替换之前:
替换之后:
通过下面两张图能够很清晰的看出:办法并没有扭转存储在变量 s1 和 s2 中的对象援用。swap 办法的参数 x 和 y 被初始化为两个对象援用的拷贝,这个办法替换的是这两个拷贝
总结
Java 程序设计语言对对象采纳的不是援用调用,实际上,对象援用是按
值传递的。
上面再总结一下 Java 中办法参数的应用状况:
- 一个办法不能批改一个根本数据类型的参数(即数值型或布尔型)。
- 一个办法能够扭转一个对象参数的状态。
- 一个办法不能让对象参数援用一个新的对象。
参考:
《Java 核心技术卷 Ⅰ》基础知识第十版第四章 4.5 大节
重载和重写的区别
重载就是同样的一个办法可能依据输出数据的不同,做出不同的解决
重写就是当子类继承自父类的雷同办法,输出数据一样,但要做出有别于父类的响应时,你就要笼罩父类办法
重载
产生在同一个类中(或者父类和子类之间),办法名必须雷同,参数类型不同、个数不同、程序不同,办法返回值和拜访修饰符能够不同。
上面是《Java 核心技术》对重载这个概念的介绍:
综上:重载就是同一个类中多个同名办法依据不同的传参来执行不同的逻辑解决。
重写
重写产生在运行期,是子类对父类的容许拜访的办法的实现过程进行从新编写。
- 返回值类型、办法名、参数列表必须雷同,抛出的异样范畴小于等于父类,拜访修饰符范畴大于等于父类。
- 如果父类办法拜访修饰符为
private/final/static
则子类就不能重写该办法,然而被 static 润饰的办法可能被再次申明。 - 构造方法无奈被重写
综上:重写就是子类对父类办法的从新革新,内部样子不能扭转,外部逻辑能够扭转
暖心的 Guide 哥最初再来个图表总结一下!
区别点 | 重载办法 | 重写办法 |
---|---|---|
产生范畴 | 同一个类 | 子类 |
参数列表 | 必须批改 | 肯定不能批改 |
返回类型 | 可批改 | 子类办法返回值类型应比父类办法返回值类型更小或相等 |
异样 | 可批改 | 子类办法申明抛出的异样类应比父类办法申明抛出的异样类更小或相等; |
拜访修饰符 | 可批改 | 肯定不能做更严格的限度(能够升高限度) |
产生阶段 | 编译期 | 运行期 |
办法的重写要遵循“两同两小一大”(以下内容摘录自《疯狂 Java 讲义》,issue#892):
- “两同”即办法名雷同、形参列表雷同;
- “两小”指的是子类办法返回值类型应比父类办法返回值类型更小或相等,子类办法申明抛出的异样类应比父类办法申明抛出的异样类更小或相等;
- “一大”指的是子类办法的拜访权限应比父类办法的拜访权限更大或相等。
⭐️ 对于 重写的返回值类 型 这里须要额定多阐明一下,下面的表述不太清晰精确:如果办法的返回类型是 void 和根本数据类型,则返回值重写时不可批改。然而如果办法的返回值是援用类型,重写时是能够返回该援用类型的子类的。
public class Hero {public String name() {return "超级英雄";}
}
public class SuperMan extends Hero{
@Override
public String name() {return "超人";}
public Hero hero() {return new Hero();
}
}
public class SuperSuperMan extends SuperMan {public String name() {return "超级超级英雄";}
@Override
public SuperMan hero() {return new SuperMan();
}
}
深拷贝 vs 浅拷贝
- 浅拷贝:对根本数据类型进行值传递,对援用数据类型进行援用传递般的拷贝,此为浅拷贝。
- 深拷贝:对根本数据类型进行值传递,对援用数据类型,创立一个新的对象,并复制其内容,此为深拷贝。
Java 面向对象
面向对象和面向过程的区别
- 面向过程 : 面向过程性能比面向对象高。 因为类调用时须要实例化,开销比拟大,比拟耗费资源,所以当性能是最重要的考量因素的时候,比方单片机、嵌入式开发、Linux/Unix 等个别采纳面向过程开发。然而,面向过程没有面向对象易保护、易复用、易扩大。
- 面向对象 : 面向对象易保护、易复用、易扩大。 因为面向对象有封装、继承、多态性的个性,所以能够设计出低耦合的零碎,使零碎更加灵便、更加易于保护。然而,面向对象性能比面向过程低。
参见 issue : 面向过程:面向过程性能比面向对象高??
这个并不是根本原因,面向过程也须要分配内存,计算内存偏移量,Java 性能差的次要起因并不是因为它是面向对象语言,而是 Java 是半编译语言,最终的执行代码并不是能够间接被 CPU 执行的二进制机械码。
而面向过程语言大多都是间接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比 Java 好。
成员变量与局部变量的区别有哪些?
- 从语法模式上看,成员变量是属于类的,而局部变量是在代码块或办法中定义的变量或是办法的参数;成员变量能够被
public
,private
,static
等修饰符所润饰,而局部变量不能被访问控制修饰符及static
所润饰;然而,成员变量和局部变量都能被final
所润饰。 - 从变量在内存中的存储形式来看, 如果成员变量是应用
static
润饰的,那么这个成员变量是属于类的,如果没有应用static
润饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。 - 从变量在内存中的生存工夫上看,成员变量是对象的一部分,它随着对象的创立而存在,而局部变量随着办法的调用而主动隐没。
- 从变量是否有默认值来看,成员变量如果没有被赋初,则会主动以类型的默认值而赋值(一种状况例外: 被
final
润饰的成员变量也必须显式地赋值),而局部变量则不会主动赋值。
创立一个对象用什么运算符? 对象实体与对象援用有何不同?
new 运算符,new 创建对象实例(对象实例在堆内存中),对象援用指向对象实例(对象援用寄存在栈内存中)。
一个对象援用能够指向 0 个或 1 个对象(一根绳子能够不系气球,也能够系一个气球); 一个对象能够有 n 个援用指向它(能够用 n 条绳子系住一个气球)。
对象的相等与指向他们的援用相等, 两者有什么不同?
对象的相等,比的是内存中寄存的内容是否相等。而援用相等,比拟的是他们指向的内存地址是否相等。
一个类的构造方法的作用是什么? 若一个类没有申明构造方法,该程序能正确执行吗? 为什么?
构造方法次要作用是实现对类对象的初始化工作。
如果一个类没有申明构造方法,也能够执行!因为一个类即便没有申明构造方法也会有默认的不带参数的构造方法。如果咱们本人增加了类的构造方法(无论是否有参),Java 就不会再增加默认的无参数的构造方法了,这时候,就不能间接 new 一个对象而不传递参数了,所以咱们始终在人不知; 鬼不觉地应用构造方法,这也是为什么咱们在创建对象的时候前面要加一个括号(因为要调用无参的构造方法)。如果咱们重载了有参的构造方法,记得都要把无参的构造方法也写进去(无论是否用到),因为这能够帮忙咱们在创建对象的时候少踩坑。
构造方法有哪些特点?是否可被 override?
特点:
- 名字与类名雷同。
- 没有返回值,但不能用 void 申明构造函数。
- 生成类的对象时主动执行,无需调用。
构造方法不能被 override(重写), 然而能够 overload(重载), 所以你能够看到一个类中有多个构造函数的状况。
面向对象三大特色
封装
封装是指把一个对象的状态信息(也就是属性)暗藏在对象外部,不容许内部对象间接拜访对象的外部信息。然而能够提供一些能够被外界拜访的办法来操作属性。就如同咱们看不到挂在墙上的空调的外部的整机信息(也就是属性),然而能够通过遥控器(办法)来管制空调。如果属性不想被外界拜访,咱们大可不必提供办法给外界拜访。然而如果一个类没有提供给外界拜访的办法,那么这个类也没有什么意义了。就如同如果没有空调遥控器,那么咱们就无奈操控空凋制冷,空调自身就没有意义了(当然当初还有很多其余办法,这里只是为了举例子)。
public class Student {
private int id;//id 属性私有化
private String name;//name 属性私有化
// 获取 id 的办法
public int getId() {return id;}
// 设置 id 的办法
public void setId(int id) {this.id = id;}
// 获取 name 的办法
public String getName() {return name;}
// 设置 name 的办法
public void setName(String name) {this.name = name;}
}
继承
不同类型的对象,相互之间常常有肯定数量的共同点。例如,小明同学、小红同学、小李同学,都共享学生的个性(班级、学号等)。同时,每一个对象还定义了额定的个性使得他们不同凡响。例如小明的数学比拟好,小红的性情惹人青睐;小李的力量比拟大。继承是应用已存在的类的定义作为根底建设新类的技术,新类的定义能够减少新的数据或新的性能,也能够用父类的性能,但不能选择性地继承父类。通过应用继承,能够疾速地创立新的类,能够进步代码的重用,程序的可维护性,节俭大量创立新类的工夫,进步咱们的开发效率。
对于继承如下 3 点请记住:
- 子类领有父类对象所有的属性和办法(包含公有属性和公有办法),然而父类中的公有属性和办法子类是无法访问,只是领有。
- 子类能够领有本人属性和办法,即子类能够对父类进行扩大。
- 子类能够用本人的形式实现父类的办法。(当前介绍)。
多态
多态,顾名思义,示意一个对象具备多种的状态。具体表现为父类的援用指向子类的实例。
多态的特点:
- 对象类型和援用类型之间具备继承(类)/ 实现(接口)的关系;
- 援用类型变量收回的办法调用的到底是哪个类中的办法,必须在程序运行期间能力确定;
- 多态不能调用“只在子类存在但在父类不存在”的办法;
- 如果子类重写了父类的办法,真正执行的是子类笼罩的办法,如果子类没有笼罩父类的办法,执行的是父类的办法。
String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
可变性
简略的来说:String
类中应用 final 关键字润饰字符数组来保留字符串,private final char value[]
,所以String
对象是不可变的。
补充(来自 issue 675):在 Java 9 之后,String、
StringBuilder
与StringBuffer
的实现改用 byte 数组存储字符串private final byte[] value
而 StringBuilder
与 StringBuffer
都继承自 AbstractStringBuilder
类,在 AbstractStringBuilder
中也是应用字符数组保留字符串char[]value
然而没有用 final
关键字润饰,所以这两种对象都是可变的。
StringBuilder
与 StringBuffer
的构造方法都是调用父类构造方法也就是AbstractStringBuilder
实现的,大家能够自行查阅源码。
AbstractStringBuilder.java
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
AbstractStringBuilder(int capacity) {value = new char[capacity];
}}
线程安全性
String
中的对象是不可变的,也就能够了解为常量,线程平安。AbstractStringBuilder
是 StringBuilder
与 StringBuffer
的公共父类,定义了一些字符串的基本操作,如 expandCapacity
、append
、insert
、indexOf
等公共办法。StringBuffer
对办法加了同步锁或者对调用的办法加了同步锁,所以是线程平安的。StringBuilder
并没有对办法进行加同步锁,所以是非线程平安的。
性能
每次对 String
类型进行扭转的时候,都会生成一个新的 String
对象,而后将指针指向新的 String
对象。StringBuffer
每次都会对 StringBuffer
对象自身进行操作,而不是生成新的对象并扭转对象援用。雷同状况下应用 StringBuilder
相比应用 StringBuffer
仅能取得 10%~15% 左右的性能晋升,但却要冒多线程不平安的危险。
对于三者应用的总结:
- 操作大量的数据: 实用
String
- 单线程操作字符串缓冲区下操作大量数据: 实用
StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 实用
StringBuffer
Object 类的常见办法总结
Object 类是一个非凡的类,是所有类的父类。它次要提供了以下 11 个办法:
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 {}// 实例被垃圾回收器回收的时候触发的操作
反射
何为反射?
如果说大家钻研过框架的底层原理或者咱们本人写过框架的话,肯定对反射这个概念不生疏。
反射之所以被称为框架的灵魂,次要是因为它赋予了咱们在运行时剖析类以及执行类中办法的能力。
通过反射你能够获取任意一个类的所有属性和办法,你还能够调用这些办法和属性。
反射机制优缺点
- 长处:能够让咱们的代码更加灵便、为各种框架提供开箱即用的性能提供了便当
- 毛病:让咱们在运行时有了剖析操作类的能力,这同样也减少了平安问题。比方能够忽视泛型参数的安全检查(泛型参数的安全检查产生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说理论是影响不大的。Java Reflection: Why is it so slow?
反射的利用场景
像咱们平时大部分时候都是在写业务代码,很少会接触到间接应用反射机制的场景。
然而,这并不代表反射没有用。相同,正是因为反射,你能力这么轻松地应用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量应用了反射机制。
这些框架中也大量应用了动静代理,而动静代理的实现也依赖反射。
比方上面是通过 JDK 实现动静代理的示例代码,其中就应用了反射类 Method
来调用指定的办法。
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理类中的实在对象
*/
private final Object target;
public DebugInvocationHandler(Object target) {this.target = target;}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {System.out.println("before method" + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method" + method.getName());
return result;
}
}
另外,像 Java 中的一大利器 注解 的实现也用到了反射。
为什么你应用 Spring 的时候,一个 @Component
注解就申明了一个类为 Spring Bean 呢?为什么你通过一个 @Value
注解就读取到配置文件中的值呢?到底是怎么起作用的呢?
这些都是因为你能够基于反射剖析类,而后获取到类 / 属性 / 办法 / 办法的参数上的注解。你获取到注解之后,就能够做进一步的解决。
异样
Java 异样类层次结构图
<p style=”font-size:13px;text-align:right”> 图片来自:https://simplesnippets.tech/e…</p>
<p style=”font-size:13px;text-align:right”> 图片来自:https://chercher.tech/java-pr…</p>
在 Java 中,所有的异样都有一个独特的先人 java.lang
包中的 Throwable
类。Throwable
类有两个重要的子类 Exception
(异样)和 Error
(谬误)。Exception
能被程序自身解决(try-catch
),Error
是无奈解决的(只能尽量避免)。
Exception
和 Error
二者都是 Java 异样解决的重要子类,各自都蕴含大量子类。
Exception
: 程序自身能够解决的异样,能够通过catch
来进行捕捉。Exception
又能够分为 受查看异样(必须解决) 和 不受查看异样(能够不解决)。Error
:Error
属于程序无奈解决的谬误,咱们没方法通过catch
来进行捕捉。例如,Java 虚拟机运行谬误(Virtual MachineError
)、虚拟机内存不够谬误(OutOfMemoryError
)、类定义谬误(NoClassDefFoundError
)等。这些异样产生时,Java 虚拟机(JVM)个别会抉择线程终止。
受查看异样
Java 代码在编译过程中,如果受查看异样没有被 catch
/throw
解决的话,就没方法通过编译。比方上面这段 IO 操作的代码。
除了 RuntimeException
及其子类以外,其余的 Exception
类及其子类都属于受查看异样。常见的受查看异样有:IO 相干的异样、ClassNotFoundException
、SQLException
…。
不受查看异样
Java 代码在编译过程中,咱们即便不解决不受查看异样也能够失常通过编译。
RuntimeException
及其子类都统称为非受查看异样,例如:NullPointerException
、NumberFormatException
(字符串转换为数字)、ArrayIndexOutOfBoundsException
(数组越界)、ClassCastException
(类型转换谬误)、ArithmeticException
(算术谬误)等。
Throwable 类罕用办法
public string getMessage()
: 返回异样产生时的简要形容public string toString()
: 返回异样产生时的详细信息public string getLocalizedMessage()
: 返回异样对象的本地化信息。应用Throwable
的子类笼罩这个办法,能够生成本地化信息。如果子类没有笼罩该办法,则该办法返回的信息与getMessage()
返回的后果雷同public void printStackTrace()
: 在管制台上打印Throwable
对象封装的异样信息
try-catch-finally
try
块: 用于捕捉异样。其后可接零个或多个catch
块,如果没有catch
块,则必须跟一个finally
块。catch
块: 用于解决 try 捕捉到的异样。finally
块: 无论是否捕捉或解决异样,finally
块里的语句都会被执行。当在try
块或catch
块中遇到return
语句时,finally
语句块将在办法返回之前被执行。
在以下 3 种非凡状况下,finally
块不会被执行:
- 在
try
或finally
块中用了System.exit(int)
退出程序。然而,如果System.exit(int)
在异样语句之后,finally
还是会被执行 - 程序所在的线程死亡。
- 敞开 CPU。
上面这部分内容来自 issue:https://github.com/Snailclimb…。
留神: 当 try 语句和 finally 语句中都有 return 语句时,在办法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会笼罩原始的返回值。如下:
public class Test {public static int f(int value) {
try {return value * value;} finally {if (value == 2) {return 0;}
}
}
}
如果调用 f(2)
,返回值将是 0,因为 finally 语句的返回值笼罩了 try 语句块的返回值。
应用 try-with-resources
来代替try-catch-finally
- 适用范围(资源的定义): 任何实现
java.lang.AutoCloseable
或者java.io.Closeable
的对象 - 敞开资源和 finally 块的执行程序: 在
try-with-resources
语句中,任何 catch 或 finally 块在申明的资源敞开后运行
《Effecitve Java》中明确指出:
面对必须要敞开的资源,咱们总是应该优先应用
try-with-resources
而不是try-finally
。随之产生的代码更简短,更清晰,产生的异样对咱们也更有用。try-with-resources
语句让咱们更容易编写必须要敞开的资源的代码,若采纳try-finally
则简直做不到这点。
Java 中相似于 InputStream
、OutputStream
、Scanner
、PrintWriter
等的资源都须要咱们调用 close()
办法来手动敞开,个别状况下咱们都是通过 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();
}
}
应用 Java 7 之后的 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-catch-finally
可能会带来很多问题。
通过应用分号分隔,能够在 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();
}
I\O 流
什么是序列化? 什么是反序列化?
如果咱们须要长久化 Java 对象比方将 Java 对象保留在文件中,或者在网络传输 Java 对象,这些场景都须要用到序列化。
简略来说:
- 序列化:将数据结构或对象转换成二进制字节流的过程
- 反序列化:将在序列化过程中所生成的二进制字节流的过程转换成数据结构或者对象的过程
对于 Java 这种面向对象编程语言来说,咱们序列化的都是对象(Object)也就是实例化后的类 (Class),然而在 C++ 这种半面向对象的语言中,struct(构造体) 定义的是数据结构类型,而 class 对应的是对象类型。
维基百科是如是介绍序列化的:
序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格局(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在雷同或另一台计算机环境中,能复原原先状态的过程。按照序列化格局从新获取字节的后果时,能够利用它来产生与原始对象雷同语义的正本。对于许多对象,像是应用大量援用的简单对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。
综上:序列化的次要目标是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
<p style=”text-align:right;font-size:13px;color:gray”>https://www.corejavaguru.com/…</p>
Java 序列化中如果有些字段不想进行序列化,怎么办?
对于不想进行序列化的变量,应用
transient 关键字润饰。
transient
关键字的作用是:阻止实例中那些用此关键字润饰的的变量序列化;当对象被反序列化时,被 transient
润饰的变量值不会被长久化和复原。transient
只能润饰变量,不能润饰类和办法。
获取用键盘输入罕用的两种办法
办法 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 流分为几种?
- 依照流的流向分,能够分为输出流和输入流;
- 依照操作单元划分,能够划分为字节流和字符流;
- 依照流的角色划分为节点流和解决流。
Java Io 流共波及 40 多个类,这些类看上去很芜杂,但实际上很有规定,而且彼此之间存在十分严密的分割,Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生进去的。
- InputStream/Reader: 所有的输出流的基类,前者是字节输出流,后者是字符输出流。
- OutputStream/Writer: 所有输入流的基类,前者是字节输入流,后者是字符输入流。
按操作形式分类结构图:
按操作对象分类结构图:
既然有了字节流, 为什么还要有字符流?
问题实质想问:不论是文件读写还是网络发送接管,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
答复:字符流是由 Java 虚拟机将字节转换失去的,问题就出在这个过程还算是十分耗时,并且,如果咱们不晓得编码类型就很容易呈现乱码问题。所以,I/O 流就罗唆提供了一个间接操作字符的接口,不便咱们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比拟好,如果波及到字符的话应用字符流比拟好。
4. 参考
- https://stackoverflow.com/que…
- https://www.educba.com/oracle…
- https://stackoverflow.com/que… 根底概念与常识