廖雪峰Java教程学习笔记

Posted Spring-_-Bear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了廖雪峰Java教程学习笔记相关的知识,希望对你有一定的参考价值。

原文链接:Java教程 - 廖雪峰的官方网站

一、Java 快速入门

Java 简介

  • Java 特点
  1. Java 是基于 JVM 虚拟机的跨平台语言,一次编写到处运行
  2. Java 程序易于编写且有垃圾回收机制,不必考虑内存管理
  3. Java 虚拟机拥有工业级的稳定性和高度优化的性能,经受了长时间的考验
  4. Java 拥有最广泛的开源社区支持,各种高质量组件随时可用
  • Java 应用领域
  1. 互联网和企业应用,这是 Java EE 的长期优势和市场地位
  2. 大数据平台,主要有 Hadoop、Spark、Flink 等
  3. 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.*;
  • 简单类名的查找顺序
  1. 查找当前 package 是否存在此类
  2. 查找 import 的包中是否存在此类
  3. 查找 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 这个类时,会依次查找
  1. <当前目录>\\abc\\xyz\\Hello.class
  2. C:\\work\\project1\\bin\\abc\\xyz\\Hello.class
  3. 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.basejava.xmlhello.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的 Stringchar 在内存中总是以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 类的 xy不允许负数,我们就得给 Point 的构造方法加上检查逻辑
public final class Point extends Record 
     public Point 
        if (x < 0 || y < 0) 
            throw new IllegalArgumentException();
        
    

  • 注意到方法 public Point ... 被称为 Compact Constructor,它的目的是让我们编写检查逻辑
  • 作为 recordPoint 仍然可以添加静态方法。一种常用的静态方法是 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

  • 通过 BigDecimalstripTrailingZeros() 方法,可以将一个 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()); 
  • 如果一个 BigDecimalscale() 方法返回负数,例如,-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教程学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

廖雪峰Git教程学习笔记

廖雪峰Git教程学习笔记

python廖雪峰教程 学习笔记

廖雪峰SQL教程学习笔记

廖雪峰SQL教程学习笔记

廖雪峰Git教程学习笔记