廖雪峰Java教程学习笔记
Posted Spring-_-Bear
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了廖雪峰Java教程学习笔记相关的知识,希望对你有一定的参考价值。
原文链接:Java教程 - 廖雪峰的官方网站
一、Java 快速入门
Java 简介
- Java 特点
- Java 是基于 JVM 虚拟机的跨平台语言,一次编写到处运行
- Java 程序易于编写且有垃圾回收机制,不必考虑内存管理
- Java 虚拟机拥有工业级的稳定性和高度优化的性能,经受了长时间的考验
- Java 拥有最广泛的开源社区支持,各种高质量组件随时可用
- Java 应用领域
- 互联网和企业应用,这是 Java EE 的长期优势和市场地位
- 大数据平台,主要有 Hadoop、Spark、Flink 等
- android 移动平台
- JDK、JRE、JVM 之间的关系
- JSR(
Java Specification Request
):规范 - JCP(
Java Community Process
):组织 - RI(
Reference Implementation
):基于 JSR 的参考实现 - TCK(
Technology Compatibility Kit
):兼容性测试套件
安装 JDK
JAVA_HOME -
D:\\Java\\jdk1.8.0_131
Path -
%JAVA_HOME%\\bin
CLASSPATH -
.;%JAVA_HOME%\\lib\\dt.jar;%JAVA_HOME%\\lib\\tools.jar;
- jdb.exe:Java调试器,用于开发阶段的运行调试
第一个 Java 程序
- java Hello.java - Java 11 新特性,可以直接运行一个单文件源码
Java 代码助手
使用 IDE
使用 IDE 练习插件
Java 程序基础
Java 程序基本结构
变量和数据类型
- Java语言对布尔类型的存储并没有做规定,因为理论上存储布尔类型只需要 1 bit,但是通常 JVM 内部会把 boolean 表示为4字节整数
- 省略变量类型声明
var sb = new StringBuilder();
整数运算
- 对 byte 和 short 类型进行移位时,会首先转换为 int 再进行移位
浮点数运算
- 浮点数运算和整数运算相比,只能进行加减乘除这些数值计算,不能做位运算和移位运算
- 整数运算在除数为 0 时会报错,而浮点数运算在除数为 0 时,不会报错,但会返回以下几个特殊值
NaN: Not a Number
Infinity: 无穷大
-Infinity:负无穷大
- 如果强制类型转换后超过了整型能表示的最大范围,将返回整型的最大值
- 如果要进行四舍五入,可以对浮点数加上 0.5 再强制转型
布尔运算
字符和字符串
- Java 在内存中总是使用
Unicode
表示字符,所以,一个英文字符和一个中文字符都用一个char
类型表示,它们都占用两个字节 - Java 13开始,字符串可以用
"""..."""
表示多行字符串。多行字符串的排版不规则,则总是以最短的行首空格为基准
String s = """
SELECT * FROM
users
WHERE id > 100
ORDER BY name DESC
""";
- 注意要区分空值 null 和空字符串 “”,空字符串是一个有效的字符串对象,它不等于 null
数组类型
- 数组所有元素初始化为默认值
流程控制
输入和输出
- 使用
System.out.printf("%%");
输出 %
if 判断
switch 多重选择
- 从 Java 12开始,switch 语句升级为更简洁的表达式语法,使用类似模式匹配(
Pattern Matching
)的方法,保证只有一种路径会被执行,并且不需要 break 语句 - 大多数时候,在 switch 表达式内部,我们会返回简单的值。但是,如果需要复杂的语句,我们也可以写很多语句,放到 … 里,然后,用 yield 返回一个值作为 switch 语句的返回值
public class Main
public static void main(String[] args)
String fruit = "orange";
int opt = switch (fruit)
case "apple" -> 1;
case "pear", "mango" -> 2;
default ->
int code = fruit.hashCode();
yield code; // switch语句返回值
;
System.out.println("opt = " + opt);
do while 循环
for 循环
- 使用 for 循环避免在循环体中去修改 i
break 和 continue
数组操作
遍历数组
数组排序
多维数组
- Java 多维数组每一维元素的个数可以不相同
- 遍历多维数组
int[][] array =new int[][]
1, 2, 3,
2, 3,
54, 234, 3, 131, 34
;
System.out.println(Arrays.deepToString(array));
命令行参数
二、面向对象编程
面向对象基础
方法
构造方法
- 没有在构造方法中初始化字段时,引用类型的字段默认是 null,数值类型的字段用默认值,int类型默认值是 0,布尔类型默认值是 false
方法重载
继承
- 子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的
- 从 Java 15开始,允许使用 sealed 关键字修饰 class,并通过 permits 明确写出能够从该 class 继承的子类名称
public sealed class Shape permits Rect, Circle, Triangle
instanceof
实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类- 从 Java 14开始,判断 instanceof 后,可以直接转型为指定变量,避免再次强制转型
Object obj = "hello";
if (obj instanceof String s)
System.out.println(s.toUpperCase());
多态
- 多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法
抽象类
接口
- Java 的接口特指
interface
的定义,表示一个接口类型和一组方法签名,而编程接口泛指接口规范,如方法签名,数据格式,网络协议等
静态字段和静态方法
- 因为静态方法属于 class 而不属于实例,因此,静态方法内部,无法访问 this 变量,也无法访问实例字段,它只能访问静态字段
包
- 导入System类的所有静态字段和静态方法:
import static java.lang.System.*;
- 简单类名的查找顺序
- 查找当前 package 是否存在此类
- 查找 import 的包中是否存在此类
- 查找 java.lang 包中是否存在此类
- 自动导入的是 java.lang 包,但类似 java.lang.reflect 这些包仍需要手动导入
作用域
内部类
- 内部类与普通类有个最大的不同,就是内部类的实例不能单独存在,必须依附于一个外部类的实例
- 内部类除了有一个 this 指向它自己,还隐含地持有一个外部类实例,可以用 Outer.this 访问这个实例
- map1 是一个普通的 HashMap 实例,map2 是一个匿名类实例,该匿名类继承自HashMap。map3 也是一个继承自 HashMap 的匿名类实例,并且添加了 static 代码块来初始化数据。编译输出可发现 Main$1.class 和 Main$2.class 两个匿名类文件
import java.util.HashMap;
public class Main
public static void main(String[] args)
HashMap<String, String> map1 = new HashMap<>();
HashMap<String, String> map2 = new HashMap<>() ;
HashMap<String, String> map3 = new HashMap<>()
put("A", "1");
put("B", "2");
;
System.out.println(map3.get("A"));
classpath 和 jar
- classpath 是 JVM 用到的一个环境变量,它用来指示 JVM 如何搜索 class
- 假设 classpath 是
.;C:\\work\\project1\\bin;C:\\shared
,当JVM在加载abc.xyz.Hello
这个类时,会依次查找
- <当前目录>\\abc\\xyz\\Hello.class
- C:\\work\\project1\\bin\\abc\\xyz\\Hello.class
- C:\\shared\\abc\\xyz\\Hello.class
- 启动 JVM 时 设置 classpath
java -classpath .;C:\\work\\project1\\bin;C:\\shared abc.xyz.Hello
- 运行示例:
// cmd command
java -classpath c:\\\\users\\\\admin\\\\desktop com.springbear.test.HelloWorld
// output
Hello World!
模块
- 运行一个引入第三方库的 Java 程序:
java -classpath app.jar:a.jar:b.jar:c.jar com.liaoxuefeng.sample.Main
- Java 9 开始引入的模块,主要是为了解决 “class 文件之间依赖” 的问题
- 从 Java 9 开始,原有的 Java 标准库已经由一个单一巨大的
rt.jar
分拆成了几十个模块,这些模块以.jmod
扩展名标识,可以在$JAVA_HOME/jmods
目录下找到它们。这些.jmod
文件每一个都是一个模块,模块名就是文件名。例如:模块java.base
对应的文件就是java.base.jmod
。模块之间的依赖关系已经被写入到模块内的module-info.class
文件了。所有的模块都直接或间接地依赖java.base
模块,只有java.base
模块不依赖任何模块,它可以被看作是 “根模块”,好比所有的类都是从Object
直接或间接继承而来 - 把一堆 .class 封装为 jar 仅仅是一个打包的过程,而把一堆 .class 封装为 module 不仅需要打包,还需要写入依赖关系,并且还可以包含二进制代码(通常是 JNI 扩展)。此外,模块支持多版本,即在同一个模块中可以为不同的 JVM 提供不同的版本
- 编写模块,只需在工程 src 目录下新增
module-info.java
test
├── bin
├── build.sh
└── src
├── com
│ └── itranswarp
│ └── sample
│ ├── Greeting.java
│ └── Main.java
└── module-info.java
module
是关键字,后面的hello.world
是模块的名称,它的命名规范与包一致。花括号里的requires xxx;
表示这个模块需要引用的其他模块名。java.base
可以被自动引入,这里我们引入了一个java.xml
的模块
module hello.world
requires java.base; // 可不写,任何模块都会自动引入 java.base
requires java.xml;
- 当我们使用模块声明了依赖关系后,才能使用引入的模块
package com.itranswarp.sample;
// 必须引入 java.xml 模块后才能使用其中的类:
import javax.xml.XMLConstants;
public class Main
public static void main(String[] args)
Greeting g = new Greeting();
System.out.println(g.hello(XMLConstants.XML_NS_PREFIX));
- 用 JDK 提供的命令行工具来编译并创建模块。把目录切换到
oop-module
,在当前目录下编译所有的.java
文件,并存放到bin
目录下javac -d bin src/module-info.java src/com/itranswarp/sample/*.java
oop-module
├── bin
│ ├── com
│ │ └── itranswarp
│ │ └── sample
│ │ ├── Greeting.class
│ │ └── Main.class
│ └── module-info.class
└── src
├── com
│ └── itranswarp
│ └── sample
│ ├── Greeting.java
│ └── Main.java
└── module-info.java
-
注意到
src
目录下的module-info.java
被编译到bin
目录下的module-info.class
-
下一步,我们需要把
bin
目录下的所有 .class 文件先打包成 jar,在打包的时候,注意传入--main-class
参数,让这个 jar 包能自己定位main
方法所在的类:jar --create --file hello.jar --main-class com.itranswarp.sample.Main -C bin .
-
现在我们就在当前目录下得到了
hello.jar
这个 jar 包,它和普通 jar 包并无区别,可以直接使用命令java -jar hello.jar
来运行它。但是我们的目标是创建模块,所以,继续使用 JDK 自带的jmod
命令把一个 jar 包转换成模块jmod create --class-path hello.jar hello.jmod
-
要运行一个 jar,我们使用
java -jar xxx.jar
命令。要运行一个模块,我们只需要指定模块名java --module-path hello.jar --module hello.world
-
打包 jre :
jlink --module-path hello.jmod --add-modules java.base,java.xml,hello.world --output jre/
,我们在--module-path
参数指定了我们自己的模块hello.jmod
,然后,在--add-modules
参数中指定了我们用到的3个模块java.base
、java.xml
和hello.world
,用,
分隔。最后,在--output
参数指定输出目录 -
运行 jre :
jre/bin/java --module hello.world
。要分发我们自己的 Java 应用程序,只需要把这个jre
目录打个包给对方发过去,对方直接运行上述命令即可,既不用下载安装 JDK,也不用知道如何配置我们自己的模块,极大地方便了分发和部署 -
模块进一步隔离了代码的访问权限,如果外部代码想要访问我们的
hello.world
模块中的com.itranswarp.sample.Greeting
类,我们必须将其导出:
module hello.world
exports com.itranswarp.sample;
requires java.base;
requires java.xml;
Java 核心类
字符串和编码
- 以忽略大小写的方式比较两个字符串的内容是否相同
if ("true".equalsIgnoreCase("True"))
System.out.println("true" );
strip()
移除字符串首尾空白字符,它和trim()
不同的是,类似中文的空格字符\\u3000
也会被移除- 字符串连接,使用静态方法
String.join()
,使用指定的字符串依次连接数组元素
String[] hello = "h", "e";
String lcx = String.join("lcx", hello);
// output:hlcxe
System.out.println(lcx);
- 把任意基本类型或引用类型转换为字符串,可以使用静态方法
String.valueOf()
Integer
有个getInteger(String)
方法,它不是将字符串转换为 int,而是把该字符串对应的系统变量转换为 Integer
// java 版本号
Integer.getInteger("java.version");
UTF-8
编码的另一个好处是容错能力强。如果传输过程中某些字符出错,不会影响后续字符,因为UTF-8
编码依靠高字节位来确定一个字符究竟是几个字节,所以它经常用来作为传输编码- 字符串与编码之间的转换
// 按UTF-8编码转换
byte[] b3 = "Hello".getBytes(StandardCharsets.UTF_8);
byte[] b = ...
// 按UTF-8转换
String s2 = new String(b, StandardCharsets.UTF_8);
- Java的
String
和char
在内存中总是以Unicode编码表示 - String 类在内存存储方式的变化
// 早期 JDK 版本 String 总是以 private final char value[] 存储
public final class String
private final char[] value;
private final int offset;
private final int count;
/*
* 较新的 JDK 版本的 String 则以 byte[] 存储,如果 String 仅包含 ASCII 字符,则每个 byte 存储一个字符,
* 否则,每两个 byte 存储一个字符,这样做的目的是为了节省内存,因为大量的长度较短的 String 通常仅包含 ASCII 字符
*/
public final class String
private final byte[] value;
private final byte coder; // 0 = LATIN1, 1 = UTF16
StringBuilder
- 为了能高效拼接字符串,Java 标准库提供了
StringBuilder
,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder
中新增字符时,不会创建新的临时对象
// 实现链式操作的关键是返回实例本身
@Override
public StringBuilder append(String str)
super.append(str);
return this;
- 对于普通的字符串
+
操作,并不需要我们将其改写为StringBuilder
,因为 Java 编译器在编译时就自动把多个连续的+
操作编码为StringConcatFactory
的操作。在运行期,StringConcatFactory
会自动把字符串连接操作优化为数组复制或者StringBuilder
操作
StringJoiner
- 不指定开头结尾数组元素之间的连接
String[] names = "Bob", "Alice", "Grace";
StringJoiner stringJoiner = new StringJoiner(", ");
for (String name : names)
stringJoiner.add(name);
// output: Bob, Alice, Grace
System.out.println(stringJoiner);
- 指定开头结尾的字符串数组连接
String[] names = "Bob", "Alice", "Grace";
StringJoiner sj = new StringJoiner(", ", "Hello ", "!");
for (String name : names)
sj.add(name);
// output: Hello Bob, Alice, Grace!
System.out.println(sj);
String
类提供了一个静态方法join()
,这个方法在内部使用了StringJoiner
来拼接字符串,在不需要指定 “开头” 和 “结尾” 的时候,用String.join()
更方便
包装类型
- 装箱和拆箱会影响代码的执行效率,因为编译后的
class
代码是严格区分基本类型和引用类型的。并且,自动拆箱执行时可能会报NullPointerException
- 我们把能创建 “新” 对象的静态方法称为静态工厂方法。
Integer.valueOf()
就是静态工厂方法,它尽可能地返回缓存的实例以节省内存
public static Integer valueOf(int i)
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
Integer
还可以把整数格式化为指定进制的字符串
// 16 进制
System.out.println(Integer.toHexString(100));
// 8 进制
System.out.println(Integer.toOctalString(100));
// 2 进制
System.out.println(Integer.toBinaryString(100));
// 转换为无符号数
System.out.println(Byte.toUnsignedInt(x));
JavaBean
- 枚举一个 JavaBean 类的所有属性,使用 Java 核心库提供的
Introspector
// 枚举 Person POJO 类的所有属性
BeanInfo info = Introspector.getBeanInfo(Person.class);
for (PropertyDescriptor pd : info.getPropertyDescriptors())
System.out.println(pd.getName());
System.out.println(" " + pd.getReadMethod());
System.out.println(" " + pd.getWriteMethod());
枚举类
enum
类型的每个常量在 JVM 中只有一个唯一实例,所以可以直接用==
比较
// ok
if (day == Weekday.FRI)
// ok, but more code!
if (day.equals(Weekday.SUN))
- 判断枚举常量的名字,要始终使用
name()
方法,绝不能调用toString()
,因为toString()
方法可以被重写
纪录类
- 为了保证不变类的比较,还需要正确覆写
equals()
和hashCode()
方法,这样才能在集合类中正常使用 - 从 Java 14 开始,引入了新的
Record
类。我们定义Record
类时,使用关键字record
,
public record Point(int x, int y)
// 上下两个 Pioint 类等价
public final class Point extends Record
private final int x;
private final int y;
public Point(int x, int y)
this.x = x;
this.y = y;
public int x()
return this.x;
public int y()
return this.y;
public String toString()
return String.format("Point[x=%s, y=%s]", x, y);
public boolean equals(Object o)
...
public int hashCode()
...
- 假设
Point
类的x
、y
不允许负数,我们就得给Point
的构造方法加上检查逻辑
public final class Point extends Record
public Point
if (x < 0 || y < 0)
throw new IllegalArgumentException();
- 注意到方法
public Point ...
被称为 Compact Constructor,它的目的是让我们编写检查逻辑 - 作为
record
的Point
仍然可以添加静态方法。一种常用的静态方法是of()
方法,用来创建Point
public record Point(int x, int y)
public static Point of()
return new Point(0, 0);
public static Point of(int x, int y)
return new Point(x, y);
BigInteger
- 将
BigInteger
转换成基本类型时可使用longValueExact()
等方法保证结果准确
BigDecimal
- 通过
BigDecimal
的stripTrailingZeros()
方法,可以将一个BigDecimal
格式化为一个相等的,但去掉了末尾0的BigDecimal
BigDecimal d1 = new BigDecimal("123.4500");
BigDecimal d2 = d1.stripTrailingZeros();
// output: 4(小数位数)
System.out.println(d1.scale());
// output: 2
System.out.println(d2.scale());
- 如果一个
BigDecimal
的scale()
方法返回负数,例如,-2
,表示这个数是个整数,并且末尾有 2 个 0
BigDecimal d3 = new BigDecimal("1234500");
BigDecimal d4 = d3.stripTrailingZeros();
// output: 0
System.out.println(d3.scale());
// output: 2
System.out.println(d4.scale(以上是关于廖雪峰Java教程学习笔记的主要内容,如果未能解决你的问题,请参考以下文章