廖雪峰的零基础Java教程笔记



  • 网址: 零基础Java教程笔记

    Java简介

    Java的版本区分:

    • 标准版: Java SE, 包含语言特征, 标准库
    • 企业版: web相关
    • 特殊版: 针对嵌入式(不考虑)

    学习流程:
    标准版 => 企业版
    (私以为是从语言和基础库特征, 到框架和具体应用)

    相关名词:

    • JRE -- java运行时
    • JDK -- 开发环境
    • JSR -- 语言规范请求, 需要参考实现 (RI:Reference Implementation), 兼容性套件(TCK:Technology Compatibility Kit) -- 类TDD的思想
    • JCP -- 审核上面的组织

    第一个Java程序

    这是Java11可以直接运行一个单文件源码.



  • This post is deleted!


  • Java程序基础

    Java程序基本结构
    变量和数据类型

    常量

    final double PI = 3.14156;

    var关键字

    StringBuilder sb = new StringBuilder();
    =>
    var sb = new StringBuilder(); // 自动类型推导

    整数运算
    整数溢出
    自动类型转换
    强制类型转换: 例 (int)

    浮点数运算
    浮点数无法精确表示

    布尔运算

    字符和字符串

    字符类型:
    char -- 单个unicode字符, 单引号
    unicode字符 与 整型 之间的转化(char, int)

    字符串类型:
    String -- 双引号
    字符串连接可以用+号
    三引号与排版

    数组类型

    附加: 数组的遍历

    • for
    • Arrays.toString
    • foreach: for(var a: array)

    流程控制

    输入和输出

    输出

    print, println, printf
    格式化输出: https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/util/Formatter.html#syntax

    输入

    java.util.Scanner

    if判断

    NullPointerException:
    if (s1 != null && ...)

    switch多重选择

    Java12的switch语法:

    switch(test_var) {
      case VALUE -> statement;
      default -> {
        statement;
        yield returned_value;
      }
    }
    

    while循环
    do while循环
    for循环
    foreach: for(var i: ...)
    break和continue



  • 数组操作

    遍历

    数组排序

    排序算法有冒泡排序、插入排序和快速排序

    多维数组

    deepToString

    命令行参数



  • 面向对象编程

    面向对象基础

    方法

    可变参数

    type ...

    构造方法
    方法重载

    继承

    • 构造函数不会继承
    • 子类构造函数需要首先调用父类构造函数(或编译器自动增加)

    Java 15开始, 限定继承范围:
    public sealed class Shape permits Rect, Circle, Triangle

    • upcasting
    • downcasting

    instanceof

    区分继承和组合

    is, has

    多态
    覆写Object方法

    • toString
    • equals
    • hashCode

    final避免继承与override方法

    抽象类
    public abstract void run();

    接口
    interface

    接口继承

    extends

    default方法

    静态字段和静态方法

    接口的静态字段

    public interface Person {
        public static final int MALE = 1;
        public static final int FEMALE = 2;
    }
    

    • 完整类名引用(包含包的完整路径信息)
    • import (可以用 *)
    • import static (可以导入一个类的静态字段和静态方法, 很少用到)

    作用域
    最佳实践:
    如果不确定是否需要public,就不声明为public,即尽可能少地暴露对外的字段和方法。
    把方法定义为package权限有助于测试,因为测试类和被测试类只要位于同一个package,测试代码就可以访问被测试类的package权限方法。
    一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。

    内部类
    需要先有外部实例, 通过外部实例的new 才能定义:
    Outer.Inner inner = outer.new Inner();

    匿名类:

        void asyncHello() {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    System.out.println("Hello, " + Outer.this.name);
                }
            };
            new Thread(r).start();
        }
    

    附加: 在c++中,内部类貌似是叫 friend class.

    classpath和jar
    java -classpath
    java -cp

    jar与MANIFEST.MF

    模块

    module hello.world {
    	requires java.base; // 可不写,任何模块都会自动引入java.base
    	requires java.xml;
    }
    

    (~有更多内容~)



  • Java核心类

    字符串和编码
    (~有更多内容~)

    StringBuilder

    StringJoiner
    String.join调用了StringJoiner, StringJoiner可以有开头和结尾.

    包装类型
    (~有更多内容~)

    JavaBean

    枚举类

    enum Weekday {
        MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6), SUN(0);
    
        public final int dayValue;
    
        private Weekday(int dayValue) {
            this.dayValue = dayValue;
        }
    }
    

    记录类
    record
    从Java 14开始,引入了新的Record类
    public record Point(int x, int y) {}

    BigInteger
    (~有更多内容~)
    BigDecimal
    (~有更多内容~)

    常用工具类

    Math

    Random

    SecureRandom



  • 异常处理

    Java的异常
    try... catch... finally...

    Checked Exception:

    • 必须捕获的异常,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常称为Checked Exception。
    • 不需要捕获的异常,包括Error及其子类,RuntimeException及其子类。

    捕获异常

    捕获多种异常

    } catch (IOException | NumberFormatException e) {

    自定义异常

    常用异常:

    Exception
    │
    ├─ RuntimeException
    │  │
    │  ├─ NullPointerException
    │  │
    │  ├─ IndexOutOfBoundsException
    │  │
    │  ├─ SecurityException
    │  │
    │  └─ IllegalArgumentException
    │     │
    │     └─ NumberFormatException
    │
    ├─ IOException
    │  │
    │  ├─ UnsupportedCharsetException
    │  │
    │  ├─ FileNotFoundException
    │  │
    │  └─ SocketException
    │
    ├─ ParseException
    │
    ├─ GeneralSecurityException
    │
    ├─ SQLException
    │
    └─ TimeoutException
    
    • 抛出异常时,尽量复用JDK已定义的异常类型;
    • 自定义异常体系时,推荐从RuntimeException派生“根异常”,再派生出业务异常;
    • 自定义异常时,应该提供多种构造方法。

    NullPointerException

    使用断言
    assert x >= 0 : "x must >= 0";
    只能用于开发和测试阶段, JVM默认关闭断言指令

    使用JDK Logging

    使用Commons Logging

    使用Log4j

    使用SLF4J和Logback

    附加: 咋配置文件不用json呢?



  • 反射

    Class类
    附加: 像是class的metadata

    方法一:直接通过一个class的静态变量class获取
    Class cls = String.class;
    方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:

    String s = "Hello";
    Class cls = s.getClass();
    

    方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:
    Class cls = Class.forName("java.lang.String");

    动态加载
    附加: 动态加载特征在动态语言中常见, 但是对编译类的静态语言, 其中会有不少实现细节

    访问字段

    • Field getField(name):根据字段名获取某个public的field(包括父类)
    • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
    • Field[] getFields():获取所有public的field(包括父类)
    • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

    Field:

    • getName():返回字段名称,例如,"name";
    • getType():返回字段类型,也是一个Class实例,例如,String.class;
    • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。

    获取字段值

    用Field去获取值
    如果是私有, 需要首先 setAccessible(true);

    设置字段值

    调用方法
    类似 获取字段.
    invoke

    使用反射调用方法时,仍然遵循多态原则

    调用构造方法
    newInstance
    getConstructor

    获取继承关系
    getSuperclass
    getInterfaces (不包含父类接口)

    isAssignableFrom

    动态代理
    ...



  • 注解

    使用注解

    注解的作用

    • 第一类是由编译器使用的注解 , 例: @Override, @SuppressWarningss
    • 第二类是由工具处理.class文件使用的注解
    • 第三类是在程序运行期能够读取的注解

    如果参数名称是value,且只有一个参数,那么可以省略参数名称。

    定义注解
    @interface

    public @interface Report {
        int type() default 0;
        String level() default "info";
        String value() default "";
    }
    

    元注解

    @Target
    • 类或接口:ElementType.TYPE;
    • 字段:ElementType.FIELD;
    • 方法:ElementType.METHOD;
    • 构造方法:ElementType.CONSTRUCTOR;
    • 方法参数:ElementType.PARAMETER

    例子:

    @Target(ElementType.METHOD)
    public @interface Report {
        int type() default 0;
        String level() default "info";
        String value() default "";
    }
    
    @Retention
    • 仅编译期:RetentionPolicy.SOURCE;
    • 仅class文件:RetentionPolicy.CLASS;
    • 运行期:RetentionPolicy.RUNTIME
    @ Repeatable
    @ Inherited (继承注解, 仅针对class)

    处理注解
    判断某个注解是否存在于Class、Field、Method或Constructor:

    • Class.isAnnotationPresent(Class)
    • Field.isAnnotationPresent(Class)
    • Method.isAnnotationPresent(Class)
    • Constructor.isAnnotationPresent(Class)

    使用反射API读取Annotation:

    • Class.getAnnotation(Class)
    • Field.getAnnotation(Class)
    • Method.getAnnotation(Class)
    • Constructor.getAnnotation(Class)

    方法参数的注解

    // 获取Method实例:
    Method m = ...
    // 获取所有参数的Annotation:
    Annotation[][] annos = m.getParameterAnnotations();
    // 第一个参数(索引为0)的所有Annotation:
    Annotation[] annosOfName = annos[0];
    for (Annotation anno : annosOfName) {
        if (anno instanceof Range) { // @Range注解
            Range r = (Range) anno;
        }
        if (anno instanceof NotNull) { // @NotNull注解
            NotNull n = (NotNull) anno;
        }
    }
    

    使用注解

    ...



  • 泛型

    使用泛型

    编写泛型
    静态方法的泛型:

    public class Pair<T> {
        private T first;
        private T last;
        public Pair(T first, T last) {
            this.first = first;
            this.last = last;
        }
        public T getFirst() { ... }
        public T getLast() { ... }
    
        // 静态泛型方法应该使用其他类型区分:
        public static <K> Pair<K> create(K first, K last) {
            return new Pair<K>(first, last);
        }
    }
    

    擦拭法
    泛型是基于编译器实现的, 类似一种模板的作用, 本质是Object. 所以有一定的局限性.

    extends通配符, super通配符

    Pair<? extends Number> 因为泛型之间并不是继承关系 所以需要额外手段来支持这种继承.
    ...



  • This post is deleted!


  • 集合

    使用List

    ArrayList, LinkedList:

    • 在末尾添加一个元素:boolean add(E e)
    • 在指定索引添加一个元素:boolean add(int index, E e)
    • 删除指定索引的元素:E remove(int index)
    • 删除某个元素:boolean remove(Object e)
    • 获取指定索引的元素:E get(int index)
    • 获取链表大小(包含元素的个数):int size()

    List.of, toArray, asList

    遍历List:

    • for
    • iterator
    • foreach

    编写equals方法
    List的contains, indexOf需要通过equals来进行相等的比较:

    • 先确定实例“相等”的逻辑,即哪些字段相等,就认为实例相等;
    • 用instanceof判断传入的待比较的Object是不是当前类型,如果是,继续比较,否则,返回false;
    • 对引用类型用Objects.equals()比较,对基本类型直接用==比较。

    使用Map

    遍历Map

    // 1.
           for (String key : map.keySet()) {
                Integer value = map.get(key);
                System.out.println(key + " = " + value);
            }
    // 2.
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                String key = entry.getKey();
                Integer value = entry.getValue();
                System.out.println(key + " = " + value);
            }
    

    Map中key自定义需要实现的方法:

    • 作为key的对象必须正确覆写equals()方法,相等的两个key实例调用equals()必须返回true;
    • 作为key的对象还必须正确覆写hashCode()方法,且hashCode()方法要严格遵循以下规范:

    使用EnumMap

    使用TreeMap

    • 使用TreeMap时,放入的Key必须实现Comparable接口
    • 另外,注意到Person类并未覆写equals()和hashCode(),因为TreeMap不使用equals()和hashCode()。
      key的compare 需要三种情况

    使用Properties
    用Properties读取配置文件,一共有三步:

    • 创建Properties实例;
    • 调用load()读取文件;
    • 调用getProperty()获取配置。

    写入配置文件

    Properties props = new Properties();
    props.setProperty("url", "http://www.liaoxuefeng.com");
    props.setProperty("language", "Java");
    props.store(new FileOutputStream("C:\\conf\\setting.properties"), "这是写入的properties注释");
    

    编码

    Properties props = new Properties();
    props.load(new FileReader("settings.properties", StandardCharsets.UTF_8));
    

    使用Set
    HashSet, TreeSet (Comparator)

    使用Queue
    LinkedList 既实现了List, 也实现了Queue的接口

    使用PriorityQueue

    使用Deque
    ...

    使用Stack

    使用Iterator

    在编写Iterator的时候,我们通常可以用一个内部类来实现Iterator接口,这个内部类可以直接访问对应的外部类的所有字段和方法。例如,上述代码中,内部类ReverseIterator可以用ReverseList.this获得当前外部类的this引用,然后,通过这个this引用就可以访问ReverseList的所有字段和方法。

            @Override
            public T next() {
                index--;
                return ReverseList.this.list.get(index);
            }
    

    使用Collections
    Collections是JDK提供的工具类,同样位于java.util包中。它提供了一系列静态方法,能更方便地操作各种集合。

    排序 (sort)

    洗牌 (shuffle)

    不可变集合



  • IO

    File对象
    ...

    InputStream
    Java 7引入的新的try(resource)

    InputStream实现类: FileInputStream, ByteArrayInputStream

    OutputStream

    Filter模式
    装饰器模式避免产生更多子类.

    操作Zip

    读取classpath资源

    try (InputStream input = getClass().getResourceAsStream("/default.properties")) {
        if (input != null) {
            // TODO:
        }
    }
    

    序列化
    java.io.Serializable
    serialVersionUID 序列化版本信息

    Reader
    FileReader, CharArrayReader, StringReader, InputStreamReader

    Writer
    FileWriter, CharArrayWriter, StringWriter, OutputStreamWriter

    PrintStream和PrintWriter
    使用Files

    byte[] data = Files.readAllBytes(Paths.get("/path/to/file.txt"));
    // 默认使用UTF-8编码读取:
    String content1 = Files.readString(Paths.get("/path/to/file.txt"));
    // 可指定编码:
    String content2 = Files.readString(Paths.get("/path/to/file.txt"), StandardCharsets.ISO_8859_1);
    // 按行读取并返回每行内容:
    List<String> lines = Files.readAllLines(Paths.get("/path/to/file.txt"));
    


  • 日期与时间

    System.currentTimeMillis()

    LocalDateTime
    ...

    SKIP



  • 单元测试
    JUnit

    异常测试

    assertThrows

    条件测试

    @ EnableOnOs, @ DisabledOnOs, @ DisabledOnJre,@ EnabledIfSystemProperty,

    @ EnabledIfEnvironmentVariable

    @Test
    @EnabledIfEnvironmentVariable(named = "DEBUG", matches = "true")
    void testOnlyOnDebugMode() {
        // TODO: this test is only run on DEBUG=true
    }
    

    参数化测试
    @ ParameterizedTest
    @ ValueSource, @ MethodSource, @ CsvSource, @ CsvFileSource



  • 正则表达式

    正则表达式简介
    java.util.regex

    匹配规则
    如果想匹配非ASCII字符,例如中文,那就用\u####的十六进制表示,例如:a\u548cc匹配字符串"a和c",中文字符和的Unicode编码是548c。

    复杂匹配规则

    分组匹配

    public class Main {
        public static void main(String[] args) {
            Pattern p = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
            Matcher m = p.matcher("010-12345678");
            if (m.matches()) {
                String g1 = m.group(1);
                String g2 = m.group(2);
                System.out.println(g1);
                System.out.println(g2);
            } else {
                System.out.println("匹配失败!");
            }
        }
    }
    

    非贪婪匹配
    要让\d+尽量少匹配,让0*尽量多匹配,我们就必须让\d+使用非贪婪匹配。在规则\d+后面加个?即可表示非贪婪匹配

    搜索和替换

    String.split

    搜索字符串

    public class Main {
        public static void main(String[] args) {
            String s = "the quick brown fox jumps over the lazy dog.";
            Pattern p = Pattern.compile("\\wo\\w");
            Matcher m = p.matcher(s);
            while (m.find()) {
                String sub = s.substring(m.start(), m.end());
                System.out.println(sub);
            }
        }
    }
    

    替换字符串

    String.replaceAll

    反向引用

    如果我们要把搜索到的指定字符串按规则替换,比如前后各加一个<b>xxxx</b>,这个时候,使用replaceAll()的时候,我们传入的第二个参数可以使用$1、$2来反向引用匹配到的子串。例如:

    ...



  • 加密与安全

    URL编码

    URLEncoder

    Base64编码

    哈希算法
    Java字符串的hashCode()就是一个哈希算法,它的输入是任意字符串,输出是固定的4字节int整数:

    BouncyCastle
    是一个哈希算法和加密算法的第三方库

    Hmac算法

    public class Main {
        public static void main(String[] args) throws Exception {
            KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
            SecretKey key = keyGen.generateKey();
            // 打印随机生成的key:
            byte[] skey = key.getEncoded();
            System.out.println(new BigInteger(1, skey).toString(16));
            Mac mac = Mac.getInstance("HmacMD5");
            mac.init(key);
            mac.update("HelloWorld".getBytes("UTF-8"));
            byte[] result = mac.doFinal();
            System.out.println(new BigInteger(1, result).toString(16));
        }
    }
    
    • 通过名称HmacMD5获取KeyGenerator实例;
    • 通过KeyGenerator创建一个SecretKey实例;
    • 通过名称HmacMD5获取Mac实例;
    • 用SecretKey初始化Mac实例;
    • 对Mac实例反复调用update(byte[])输入数据;
    • 调用Mac实例的doFinal()获取最终的哈希值。

    对称加密算法
    口令加密算法
    密钥交换算法
    非对称加密算法
    签名算法

    数字证书
    keytool -storepass 123456 -genkeypair -keyalg RSA -keysize 1024 -sigalg SHA1withRSA -validity 3650 -alias mycert -keystore my.keystore -dname "CN=www.sample.com, OU=sample, O=sample, L=BJ, ST=BJ, C=CN"

    • keyalg:指定RSA加密算法;
    • sigalg:指定SHA1withRSA签名算法;
    • validity:指定证书有效期3650天;
    • alias:指定证书在程序中引用的名称;
    • dname:最重要的CN=www.sample.com指定了Common Name,如果证书用在HTTPS中,这个名称必须与域名完全一致。


  • 多线程

    创建新线程

    线程的状态

    • New:新创建的线程,尚未执行;
    • Runnable:运行中的线程,正在执行run()方法的Java代码;
    • Blocked:运行中的线程,因为某些操作被阻塞而挂起;
    • Waiting:运行中的线程,因为某些操作在等待中;
    • Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
    • Terminated:线程已终止,因为run()方法执行完毕。

    中断线程
    interrupt(), isInterrupted()

    线程间共享变量需要使用volatile关键字标记

    守护线程

    Thread t = new MyThread();
    t.setDaemon(true);
    t.start();
    

    守护线程是为其他线程服务的线程;
    所有非守护线程都执行完毕后,虚拟机退出;
    守护线程不能持有需要关闭的资源(如打开文件等)。
    ...

    线程同步
    synchronized

    class Counter {
        public static final Object lock = new Object();
        public static int count = 0;
    }
    
    class AddThread extends Thread {
        public void run() {
            for (int i=0; i<10000; i++) {
                synchronized(Counter.lock) {
                    Counter.count += 1;
                }
            }
        }
    }
    
    class DecThread extends Thread {
        public void run() {
            for (int i=0; i<10000; i++) {
                synchronized(Counter.lock) {
                    Counter.count -= 1;
                }
            }
        }
    }
    

    同步方法
    如果一个类被设计为允许多线程正确访问,我们就说这个类就是“线程安全”的(thread-safe),上面的Counter类就是线程安全的。Java标准库的java.lang.StringBuffer也是线程安全的。
    还有一些不变类,例如String,Integer,LocalDate,它们的所有成员变量都是final,多线程同时访问时只能读不能写,这些不变类也是线程安全的。
    最后,类似Math这些只提供静态方法,没有成员变量的类,也是线程安全的。

    死锁

    使用wait和notify
    因此,多线程协调运行的原则就是:当条件不满足时,线程进入等待状态;当条件满足时,线程被唤醒,继续执行任务。

    public synchronized String getTask() {
        while (queue.isEmpty()) {
            this.wait();
        }
        return queue.remove();
    }
    

    wait()方法调用时,会释放线程获得的锁,wait()方法返回后,线程又会重新试图获得锁。

    public synchronized void addTask(String s) {
        this.queue.add(s);
        this.notify(); // 唤醒在this锁等待的线程
    }
    

    notifyAll()

    使用ReentrantLock

    使用Condition
    synchronized和 wait, notify适配, 那么ReentrantLock与Condition搭配
    本质是条件不满足时如何等待/唤醒的机制

    使用ReadWriteLock

    使用StampedLock
    StampedLock提供了乐观读锁,可取代ReadWriteLock以进一步提升并发性能;
    StampedLock是不可重入锁

    使用Concurrent集合
    后面是线性安全的版本~

    • List ArrayList CopyOnWriteArrayList
    • Map HashMap ConcurrentHashMap
    • Set HashSet / TreeSet CopyOnWriteArraySet
    • Queue ArrayDeque / LinkedList ArrayBlockingQueue / LinkedBlockingQueue
    • Deque ArrayDeque / LinkedList LinkedBlockingDeque

    使用Atomic
    CAS扩展

    使用线程池
    ExecutorService:

    • FixedThreadPool
    • CachedThreadPool
    • SingleThreadExecutor

    ScheduledThreadPool

    使用Future
    一个Future<V>接口表示一个未来可能会返回的结果,它定义的方法有:

    • get():获取结果(可能会等待)
    • get(long timeout, TimeUnit unit):获取结果,但只等待指定的时间;
    • cancel(boolean mayInterruptIfRunning):取消当前任务;
    • isDone():判断任务是否已完成。

    使用CompletableFuture
    anyOf

    使用ForkJoin
    Java 7开始引入了一种新的Fork/Join线程池,它可以执行一种特殊的任务:把一个大任务拆成多个小任务并行执行。
    ...

    使用ThreadLocal
    ThreadLocal基于thread的独立存储空间



  • Maven基础

    依赖管理

    • compile 编译时需要用到该jar包(默认) commons-logging
    • test 编译Test时需要用到该jar包 junit
    • runtime 编译时不需要,但运行时需要用到 mysql
    • provided 编译时需要用到,但运行时由JDK或某个服务器提供 servlet-api

    构建流程
    mvn clean:清理所有生成的class和jar;
    mvn clean compile:先清理,再执行到compile;
    mvn clean test:先清理,再执行到test,因为执行test前必须执行compile,所以这里不必指定compile;
    mvn clean package:先清理,再执行到package。

    ...略...



  • 网络编程

    TCP编程
    ...

    UDP编程
    ...

    发送Email
    JavaMail

    接收Email
    ...

    HTTP编程
    HttpURLConnection, HttpClient

    RMI远程调用
    更通用的: gRPC


Log in to reply