JAVASE

文章目录

1. 计算机语言概述

1.1 JAVA 诞生

1995年诞生于 SUN(Standford University Network 斯坦福大学网络公司)

1.2 就业方向

JAVA EE
Android
JAVA ME已过时

1.3 Oracle 简介

开发语言:JAVA
服务端操作系统:Solaris
数据库:Oracle

1.4 微软简介

开发语言:C#
服务端操作系统:Windows 2008、Windows 10…(Windows XP是客户端操作系统)
数据库:SQL Server

1.5 关于苹果

手机端操作系统:IOS
PC 端操作系统:MAC

2. 跨平台原理

JVM(Java Virtual Machine):JAVA虚拟机
JVM 作为 JAVA 语言和操作系统之间的桥梁,使得 JAVA 语言具有了跨平台性
但 JVM 本身并不具备跨平台性,对于不同的操作系统有不同版本的 JVM

3. JDK&JRE

JRE(Java Runtime Environment Java 运行环境):仅需要运行 Java 程序时使用,包括JVM + 核心类库
JDK(Java Development Kit Java 开发工具包):面向开发人员,包括JRE + 开发工具,典型的开发工具包括编译工具(javac.exe)、打包工具(jar.exe)
在这里插入图片描述

4. JDK 的下载与安装

JAVA SE 最新版本 官网下载地址
JAVA SE 历史版本 官网下载地址
Windows操作系统下的 JDK 和 JRE 安装一次后,可以直接将生成的目录拷贝到 U 盘中,当做绿色版使用,不需要再次安装,但需要重新配置环境变量
JDK 本身内置了 JRE,而在 JDK 安装包中还含有独立的 JRE 安装程序,JDK 安装完毕后可以直接取消 JRE 安装
JDK 1.6 版本安装程序中,仅内置了 JRE 安装程序,不需要安装
JDK 1.7 版本安装程序中,内置了 JRE、 JAVA FX SDK(JAVA Software Developer Kit JAVA 软件开发者工具包)安装程序,都不需要安装
在这里插入图片描述
在这里插入图片描述

5. 环境变量配置-临时配置方式

通过 DOS 命令中的set命令完成

  1. set
    用于查看本机中的所有环境变量的值
  2. set path
    仅查看 path 环境变量的值
  3. set path=值
    给 path 环境变量赋值
    注:若执行 set path= 会删除掉 path 环境变量
    在这里插入图片描述
  4. set path=新值;%path%
    在原来的 path 的值的基础上添加新值
    JAVA_HOME 环境变量是必须创建的,为了避免因 JAVA 路径更换需要修改 path 环境变量存在的,有了 JAVA_HOME ,当 JAVA 路径更换需要更改环境变量时就只需要改变 JAVA_HOME 的值,而不需要对 path 做改动,因为 path 中很多是系统路径,修改过程中容易误操作导致系统路径出现问题

6. CLASSPATH 环境变量

CLASSPATH(classpath) 环境变量面向 JVM(java.exe 程序),指明了使用 java.exe 执行 class 文件时程序寻找 class 文件的路径,若不设置该环境变量,默认只在当前目录下查找,若找到,则执行,若没有找到,则报错
接下来说明几种为 CLASSPATH 环境变量配置不同值的情况:

  1. CLASSPATH=E:\
    直接去指定的路径下(E:\)查找 class 文件
  2. CLASSPATH=E:\;
    除了去环境变量指定的目录下查找以外,找不到的时候会查找当前目录下
  3. CLASSPATH=.;E:\
    先查找当前目录下,找不到的时候会接着到指定的目录下查找

2中当前目录为隐式指定,不推荐使用,3中为显示指定,推荐使用
javac 编译文件时需要携带 .java 后缀名,java 执行程序时不能带 .class 后缀名
对于一个类文件,若类的权限为 public ,则要求该类的 .java 文件名必须与类名保持一致

7. 注释

文档注释
/**
*/

单行注释
//

多行注释
/*
*/

文档注释,可以通过 bin 目录下 javadoc.exe 工具生成 API 文档
单行注释内可以嵌套单行注释、多行注释
多行注释内只可以嵌套单行注释,不可以嵌套多行注释
注释经过编译后不会留在 .class 文件中

8. 代码结构

/*
需求说明:
......
思路:
......
步骤:
......
*/

具体代码及必要的行注释

9. 常量

分类:

  1. 整数常量
    表现形式
    1.1 二进制,以B结尾表示,1001 0111B
    1.2 八进制,以0开头表示,0123
    1.3 十进制,123
    1.4 十六进制 ,以0x开头表示,0x12
  2. 小数常量
  3. 布尔(boolean)常量:true 和 false
  4. 字符常量
  5. 字符串常量
  6. null 常量:null

10. 数据类型

Java中有两种数据类型,基本数据类型和引用数据类型(内存空间本身代表的是引用地址)
引用数据类型

10.1 基本数据类型

基本数据类型是CPU可以直接进行运算的类型。即内存空间本身代表的就是数据本身

  1. 整数类型:byte,short,int,long
  2. 浮点数类型:float,double
  3. 字符类型:char
  4. 布尔类型:boolean

不同的数据类型占用的字节数不同
在这里插入图片描述
JVM内部会把boolean表示为4字节整数
Java的char类型保存的是Unicode字符

11. var 关键字

省略变量类型,java10以后支持

12. 运算

12.1 整数运算

加减乘除、求余、位运算、移位运算

整数的除法对于除数为0时运行时将报错,但编译不会报错。
整数由于存在范围限制,如果计算结果超出了范围,就会产生溢出,而溢出不会出错

移位运算
对byte和short类型进行移位时,会首先转换为int再进行移位。

类型自动提升
如果参与运算的两个数类型不一致,那么计算结果为较大类型的整型

强制转型
即将大范围的整数转型为小范围的整数。

12.2 浮点数运算

加减乘除

无法精确表示
浮点数运算在除数为0时,不会报错,但会返回几个特殊值:

  1. NaN表示Not a Number
  2. Infinity表示无穷大
  3. -Infinity表示负无穷大
double d1 = 0.0 / 0; // NaN
double d2 = 1.0 / 0; // Infinity
double d3 = -1.0 / 0; // -Infinity

比较两个浮点数是否相等的方法:判断两个浮点数之差的绝对值是否小于一个很小的数

类型自动提升
参与运算的两个数其中一个是整型,那么整型可以自动提升到浮点型,但在一个复杂的四则运算中,两个整数的运算不会出现自动提升的情况

强制转型
可以将浮点数强制转型为整数。在转型时,浮点数的小数部分会被丢掉。如果转型后超过了整型能表示的最大范围,将返回整型的最大值
如果要进行四舍五入,可以对浮点数加上0.5再强制转型

package com.lxf.pro;

public class BaseDataType {
    public static void main(String[] args) {
        double d = 2.5;
        int n = (int)(d+0.6);
        System.out.println(n);
    }
}

在这里插入图片描述

12.3 布尔运算

短路运算。如果一个布尔运算的表达式能提前确定结果,则后续的计算不再执行(&&和||)

12.4 字符串

引用类型,不可变

用+连接字符串和其他数据类型,会将其他数据类型先自动转型为字符串,再连接

12.5 数组

引用类型,大小不可变
数组所有元素初始化为默认值,整型都是0,浮点型是0.0,布尔型是false;

12.5.1 遍历数组

  1. for循环
  2. for each循环

12.5.2 打印数组元素

  1. for循环
  2. for each循环
  3. java.util标准库提供的Arrays.toString()方法
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] ns = { 1, 1, 2, 3, 5, 8 };
        System.out.println(Arrays.toString(ns));
    }
}

12.5.3 数组排序

  1. 双层for循环
  2. java.util标准库提供的Arrays.sort()方法,该方法修改了数组本身
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] ns = { 28, 12, 89, 73, 65, 18, 96, 50, 8, 36 };
        Arrays.sort(ns);
        System.out.println(Arrays.toString(ns));
    }
}

12.5.4 多维数组

  1. 多维数组的每个数组元素的长度并不要求相同
int[][] ns = {
    { 1, 2, 3, 4 },
    { 5, 6 },
    { 7, 8, 9 }
};

在这里插入图片描述
2. 打印二维数组
java.util标准库提供的Arrays.deepToString()方法

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[][] ns = {
            { 1, 2, 3, 4 },
            { 5, 6, 7, 8 },
            { 9, 10, 11, 12 }
        };
        System.out.println(Arrays.deepToString(ns));
    }
}

12.5.5 命令行参数

命令行参数由JVM接收用户输入并传给main方法

12.6 流程控制

12.6.1 输入和输出

12.6.1.1 输出

System.out.println() 换行输出
System.out.print() 直接输出
System.out.print() 格式化输出

12.6.1.2 输入

Scanner对象

12.6.2 if判断

if … else if … 串联使用多个if时按照判断范围从大到小依次判断或从小到大依次判断
==表示“引用是否相等”
判断引用类型的变量内容是否相等,必须使用equals()方法
执行语句s1.equals(s2)时,如果变量s1为null,会报NullPointerException

package com.lxf.pro;

public class BaseDataType {
    public static void main(String[] args) {
        String s1 = null;
        if (s1.equals("hello")) {
            System.out.println("hello");
        }
    }
}
package com.lxf.pro;

public class BaseDataType {
    public static void main(String[] args) {
        String s1 = null;

        //要避免NullPointerException错误,可以利用短路运算符&&
        if (s1 != null && s1.equals("hello")) {
            System.out.println("hello");
        }
    }
}

12.6.3 switch多重选择

如果有几个case语句执行的是同一组语句块,可以这么写:

public class Main {
    public static void main(String[] args) {
        int option = 2;
        switch (option) {
        case 1:
            System.out.println("Selected 1");
            break;
        case 2:
        case 3:
            System.out.println("Selected 2, 3");
            break;
        default:
            System.out.println("Not selected");
            break;
        }
    }
}

Tip:

  1. 使用switch语句时,只要保证有break,case的顺序不影响程序逻辑,但是仍然建议按照自然顺序排列,便于阅读。
  2. switch语句还可以匹配字符串。字符串匹配时,是比较“内容相等”。
  3. switch语句可以使用枚举类型
  4. Java 12以后的新特性
    switch语句升级为更简洁的表达式语法,并且不需要break语句。新语法使用->,如果有多条语句,需要用{}括起来。如果需要复杂的语句,可以写很多语句,放到{…}里,然后,用yield返回一个值作为switch语句的返回值。yield的作用类似于return
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);
    }
}

12.6.4 for循环

Tip:

  1. 在for循环的循环体中,不要修改计数器的值。计数器的初始化、判断条件、每次循环后的更新条件统一放到for()语句中可以一目了然。
  2. 使用for循环时,计数器变量i要尽量定义在for循环中而不是循环外,如果变量i定义在for循环外,那么,退出for循环后,变量i仍然可以被访问,这就破坏了变量应该把访问范围缩到最小的原则。
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=0; i<ns.length; i++) {
    System.out.println(ns[i]);
}
// 无法访问i
int n = i; // compile error!
int[] ns = { 1, 4, 9, 16, 25 };
int i;
for (i=0; i<ns.length; i++) {
    System.out.println(ns[i]);
}
// 仍然可以使用i
int n = i;

13. 面向对象

13.1 方法

13.1.1 可变参数

支持版本:Java 1.5
可变参数用类型…定义,可变参数相当于String[]数组类型,与数组不同的是String[]数组类型在调用时需要构造String[]

class Group {
    private String[] names;

    public void setNames(String[] names) {
        this.names = names;
    }
}
Group g = new Group();
g.setNames(new String[] {"Xiao Ming", "Xiao Hong", "Xiao Jun"}); // 传入1个String[]

13.1.2 参数绑定

基本类型参数的传递,是调用方值的复制。双方各自的后续修改,互不影响。
在这里插入图片描述
引用类型参数的传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方(因为指向同一个对象嘛)。字符串类型的参数除外,因为字符串是不可变的。

13.1.3 private方法

仅供类内部的方法调用

13.2 属性

13.2.1 private 属性

避免外部代码直接访问属性,通过get、set方法让外部代码间接访问属性,在方法内部,可以检查参数是否合法,从而使得外部代码没有任何机会把age设置成不合理的值。

13.2.2 this变量

解决局部变量与属性名命名冲突

class Person {
    private String name;

    public void setName(String name) {
        this.name = name; // 前面的this不可少,少了就变成局部变量name了
    }
}

13.3 构造方法

解决的问题:创建对象实例时就把内部属性全部初始化为合适的值
Tip:

  1. 调用构造方法,必须用new操作符。如:Person p = new Person(“Xiao Ming”, 15);
  2. 如果自定义了一个构造方法,编译器就不再自动创建默认构造方法
  3. 没有在构造方法中初始化字段时,引用类型的字段默认是null,数值类型的字段用默认值,int类型默认值是0,布尔类型默认值是false
  4. 字段初始化有两种方法,对字段直接进行初始化和通过构造方法进行初始化,在Java中,创建对象实例的时候,按照如下顺序进行初始化:
    先初始化字段,例如,int age = 10;表示字段初始化为10,double salary;表示字段默认初始化为0,String name;表示引用类型字段默认初始化为null;然后执行构造方法的代码进行初始化。
  5. 一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是this(…)
class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name) {
        this(name, 18); // 调用另一个构造方法Person(String, int)
    }

    public Person() {
        this("Unnamed"); // 调用另一个构造方法Person(String)
    }
}

13.4 方法重载 Overload

功能类似,方法名相同,但各自的参数不同,称为方法重载。
注:方法重载的返回值类型通常都是相同的

13.5 继承

  1. 最重要的就是代码复用,子类继承父类的属性和方法,并在此之上进行属性和功能的扩展,通过继承,子类只需要编写额外的功能,不再需要重复代码。(子类自动获得了父类的所有字段,严禁定义与父类重名的字段!)
  2. 子类无法继承父类的private字段或者private方法。要想实现继承,需要将private替换为protected,protected关键字可以把字段和方法的访问权限控制在继承树内部。protected字段和方法可以被其子类,以及子类的子类所访问。

13.5.1 super关键字

应用场景:在Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();如果父类的构造方法不是默认的空构造方法,子类就必须通过super关键字显式的调用父类的构造方法。

  1. 子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。
  2. 子类引用父类的字段时,可以用super.fieldName。

13.5.2 Java 15 继承新特性

final关键字修饰的类一律不准继承,而sealedpermits关键字限定了继承的范围,除了范围之内的其他类不可进行继承。

public sealed class Shape permits Rect, Circle, Triangle {
    ...
}

permits Rect, Circle, Triangle明确给出了能够从Shape类继承的子类名称。
子类可以通过下列方式进行继承

public final class Rect extends Shape {...}

注:sealed类在Java 15中目前是预览状态,要启用它,必须使用参数--enable-preview和--source 15

13.5.3 向上转型

已有继承树:Student > Person > Object
父类引用指向子类对象

Person p = new Student(); // upcasting, ok
Object o1 = new Person(); // upcasting, ok
Object o2 = new Student(); // upcasting, ok

13.5.4 向下转型

向下转型需要确定实际类型是否为子类类型,如果是子类类型就可以进行向下转型,因为子类功能多,父类功能少,功能多的可以转功能少的,而功能少的不能转功能多的

13.5.4.1 instanceof关键字

判断变量所指向的实例是否是指定类型,或者是指定类型的子类。如果一个引用变量为null,那么对任何instanceof的判断都为false。
向下转型之前,通过该关键字进行判断

Person p = new Student();
if (p instanceof Student) {
    // 只有判断成功才会向下转型:
    Student s = (Student) p; // 一定会成功
}
13.5.4.2 Java 14 instanceof新特性

判断instanceof后,可以直接转型为指定变量,避免再次强制转型。
在这里插入图片描述

13.5.5 继承和组合

继承表示的是is关系,如:Student是Person的一种,所以Student继承Person;
组合表示的是has关系,如:Student可以持有一个Book,所以Book作为属性存在于Student中

class Student extends Person {
    protected Book book;
    protected int score;
}

13.6 多态

13.6.1 覆写 Override

发生在子类与父类之间,方法的方法签名相同,并且返回值也相同

@Override可以让编译器帮助检查是否进行了正确的覆写。即以覆写的标准对方法进行检查。

13.6.2 多态

针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。

优势:
允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。

Tip:

  1. 在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用。

13.6.3 final 关键字

  1. final修饰的方法可以阻止被覆写;
  2. final修饰的class可以阻止被继承;
  3. final修饰的field必须在创建对象时初始化,随后不可修改。
    通常在构造方法中初始化final字段
//实例一旦创建,其final字段就不可修改。
class Person {
    public final String name;
    public Person(String name) {
        this.name = name;
    }
}
  1. final修饰局部变量可以阻止被重新赋值
package abc;

public class Hello {
    protected void hi(final int t) {
        t = 1; // error!
    }
}

13.7 抽象类

13.7.1 应用场景

父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它

13.7.2 面向抽象编程

引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。
抽象方法实际上相当于定义了“规范”。
本质

  1. 上层代码只定义规范(例如:abstract class Person);
  2. 不需要子类就可以实现业务逻辑(正常编译);
  3. 具体的业务逻辑由不同的子类实现,调用者并不关心。

13.8 接口

接口定义的所有方法默认都是publicabstract

13.8.1 接口继承

接口继承接口使用extends关键字,相当于扩展了接口的方法。

13.8.2 JDK 1.8 新特性

13.8.2.1 default 方法

在接口中,可以定义default方法

13.8.2.2 应用场景

如果没有default 方法,接口增加新方法时,接口的实现类就会挂掉,而通过使用default 方法,已经存在的实现类就不会挂掉,只需要在需要覆写的地方去覆写新增方法。

13.9 静态字段和静态方法

  1. 静态方法无法访问实例字段,只能访问静态字段和静态方法
  2. interface是可以有静态字段的,并且静态字段必须为final类型,所以接口中字段的修饰词就只能是public static final,编写接口时,可以省略这些修饰符
  3. 静态方法常用于工具类和辅助方法(如:main方法)。

13.10 包

13.10.1 应用场景

防止类命名冲突

13.10.2 Tip

  1. 没有定义包名的类,使用的是默认包,非常容易引起名字冲突
  2. 源码和编译后的.class文件都按照包结构存放
    在这里插入图片描述

13.10.3 包作用域

位于同一个包的类,可以访问包作用域(不用public、protected、private修饰的字段和方法以及不用public、private修饰的类)的字段和方法。

13.10.4 import 关键字

13.10.4.1 应用场景

在类中便捷的引用其他类

13.10.4.2 引用方式
  1. 引用时写出完整类名
  2. 通过import引入指定的类推荐
  3. import …* 引用指定包下的所有类(但不包括子包的类)
  4. import static:导入一个类的静态字段和静态方法
    在这里插入图片描述
13.10.4.3 类名的查找路径
  1. 如果是完整类名,就直接根据完整类名查找这个class;
  2. 如果是简单类名,按下面的顺序依次查找:
    2.1 查找当前package是否存在这个class;
    2.2 查找import的包是否包含这个class;
    2.3 查找java.lang包是否包含这个class。
13.10.4.4 编译器的import动作
  1. 自动import当前package的其他class
  2. 自动import java.lang.*
    导入的是java.lang下的类,对于java.lang.reflect这样的子包需要单独导入
13.10.4.5 包名命名规范
  1. 使用倒置的域名来确保唯一性。
    如:org.apache、com.liaoxuefeng.sample
  2. 不要和java.lang包的类重名
  3. 不要和JDK常用类重名

13.11 作用域

  1. 嵌套类拥有访问private的权限
    在这里插入图片描述

13.12 内部类

被定义在另一个类的内部

13.12.1 Inner Class

class Outer {
    class Inner {
        // 定义了一个Inner Class
    }
}

Inner Class的实例不能单独存在,必须依附于一个Outer Class的实例。

package com.lxf.ss;

public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer("Nested"); // 实例化一个Outer
        Outer.Inner inner = outer.new Inner(); // 实例化一个Inner
        inner.hello();
    }
}

class Outer {
    private String name;

    Outer(String name) {
        this.name = name;
    }

    class Inner {
        void hello() {
            //Inner Class除了有一个this指向它自己,还隐含地持有一个Outer Class实例,可以用Outer.this访问这个实例。
            System.out.println("修改前:");
            System.out.println("Hello, " + Outer.this.name);

            //能访问Outer Class的private字段和方法
            System.out.println("\n修改后:");
            Outer.this.name = "nihao";
            System.out.println("Hello, " + Outer.this.name);
        }
    }
}

Outer类被编译为Outer.class,而Inner类被编译为Outer$Inner.class
在这里插入图片描述

13.12.2 Anonymous Class

定义在方法内部
Outer类被编译为Outer.class,而匿名类被编译为Outer$1.class。如果有多个匿名类,Java编译器会将每个匿名类依次命名为Outer$1、Outer$2、Outer$3……
在这里插入图片描述

13.12.3 Static Nested Class

无法引用Outer.this,可以访问Outer的private静态字段和静态方法。

13.13 classpath和jar 包

classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class。

13.13.1 classpath

13.13.1.1 设定方法
  1. 在系统环境变量中设置classpath环境变量不推荐
  2. 在启动JVM时设置classpath变量推荐
    2.1 对于java命令就是
    java -classpath/-cp .;C:\work\project1\bin;C:\shared abc.xyz.Hello
    2.2 对于IDE就是
    当前工程的bin目录和引入的jar包。

注:没有设置系统环境变量,也没有传入-cp参数,那么JVM默认的classpath为.

13.13.2 jar包

13.13.2.1 应用场景

把package组织的目录层级,以及各个目录下的所有文件(包括.class文件和其他文件)都打成一个jar文件

13.13.2.2 特点
  1. jar包实际上就是一个zip格式的压缩文件
  2. jar包相当于目录
13.13.2.3 执行jar包

如果我们要执行一个jar包的class,就可以把jar包放到classpath中

java -cp ./hello.jar abc.xyz.Hello
13.13.2.4 创建jar包

在资源管理器中,找到正确的目录,点击右键,在弹出的快捷菜单中选择“发送到”,“压缩(zipped)文件夹”,就制作了一个zip文件。然后,把后缀从.zip改为.jar
注:jar包的第一层目录不能是bin,而应该直接是最外层的包

13.13.2.5 简化jar包执行

在jar包的根目录下创建/META-INF/MANIFEST.MF文件,该文件可以指定Main-Class和其它信息。JVM会自动读取这个MANIFEST.MF文件,如果存在Main-Class,我们就不必在命令行指定启动的类名,而是使用

java -jar hello.jar

直接运行jar包。
注:如果jar包包含其他jar包,就需要在MANIFEST.MF文件里配置classpath

13.14 模块

https://www.liaoxuefeng.com/wiki/1252599548343744/1281795926523938

14. Java核心类

14.1 字符串

不同版本的JDK,String类在内存中有不同的优化方式。早期JDK版本的String总是以char[]存储,较新的JDK版本的String则以byte[]存储。

14.1.1 字符串不可变性的实现原理

通过内部的private final char[]字段,以及没有任何修改char[]的方法实现的。

14.1.2 字符串内容的比较

equals()方法,若忽略大小写比较,使用equalsIgnoreCase()方法。

14.1.3 常用方法

14.1.3.1 搜索子串
方法功能
contains()是否包含子串
indexOf()第一个子串索引
lastIndexOf()最后一个子串索引
startsWith()是否以指定子串开头
endsWith()是否以指定子串结尾
14.1.3.2 提取子串

substring()方法

14.1.3.3 去除首尾空白字符

空白字符包括\t\r\n空格
空白字符串:只包含空白字符的字符出串

方法功能
trim()去除首尾空白字符,返回一个新字符串
strip()去除首尾空白字符,包括中文空格字符\u3000
stripLeading()去除首部空白字符,包括中文空格字符\u3000
stripTrailing()去除尾部空白字符,包括中文空格字符\u3000
isEmpty()判断字符串是否为空
isBlank()判断字符串是否为空白字符串
14.1.3.4 替换子串
方法功能
replace()根据字符或字符串进行替换,返回一个新字符串
replaceAll()根据正则表达式进行替换
/*
根据字符或字符串进行替换
 */
String s = "hello";
s.replace('l', 'o');//heooo
s.replace("l", "w");//hewwo

/*
根据正则表达式进行替换
\, 匹配,
\; 匹配;
\s 匹配空白字符
 */
String ss = "A,,B;C ,D";
ss.replaceAll("[\\,\\;\\s]+", "s");//AsBsCsD
14.1.3.5 分割字符串
方法功能
split()通过正则表达式分割字符串
/*
通过正则表达式分割字符串
 */
String s = "A,B,C,D";
String[] ss = s.split("\\,");//{"A", "B", "C", "D"}
14.1.3.6 拼接字符串
方法功能
String.join()用指定的字符串连接字符串数组
/*
用指定的字符串连接字符串数组
 */
String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"
14.1.3.7 格式化字符串
方法功能
String.format()格式化字符串
/*
格式化字符串
 */
String.format("Hi %s, your score is %.2f!", "Bob", 59.5);//Hi Bob, your score is 59.50!

注:参数类型要和占位符一致,%s可以显示任何数据类型.
常用占位符

占位符功能
%s显示字符串,可以显示任何数据类型
%d显示整数
%x显示十六进制整数
%f显示浮点数

14.1.4 类型转换

14.1.4.1 基本类型或引用类型转换为字符串

String.valueOf()方法

String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
String.valueOf(new Object()); // 类似java.lang.Object@636be97c
14.1.4.2 字符数组转换为字符串
char [] cs = {'a','b','c'};
String s = new String(cs); // char[] -> String
System.out.println(s);//abc
14.1.4.3 字符串转换为int类型

Integer.parseInt() 方法

int n1 = Integer.parseInt("123"); // 123
int n2 = Integer.parseInt("ff", 16); // 按十六进制转换,255

容易被误解的方法:Integer.getInteger()
把字符串对应的系统变量转换为Integer

14.1.4.4 字符串转换为boolean类型

Boolean.parseBoolean()方法

boolean b1 = Boolean.parseBoolean("true"); // true
14.1.4.5 字符串转换为字符数组
char[] cs = "Hello".toCharArray(); // String -> char[]
for (char c: cs) {
    System.out.print(c);//Hello
}

14.2 字符编码

14.2.1 常见编码形式

编码编码范围制定组织占用空间特性编码示例
ASCII英文字母、数字和常用符号ANSI1字节最高位始终为0‘A’(0x41)
GB2312中文、英文字母、数字和常用符号2字节最高位始终为1‘中’(0xd6d0)
Shift_JIS日文、英文字母、数字和常用符号
EUC-KR韩文、英文字母、数字和常用符号
Unicode统一全球所有语言的编码全球统一码联盟两个或者更多字节英文字符的Unicode编码就是简单地在ASCII编码前面添加一个00字节‘A’(0x0041)
UTF-81~4字节的变长编码依靠高字节位来确定一个字符究竟是几个字节‘A’(0x41)、‘中’(0xe4b8ad)

14.2.2 Java中的编码转换

Java的char类型就是两个字节Unicode编码

14.2.2.1 字符串转换成其他编码

转换为byte[]时,优先考虑UTF-8编码

byte[] b1 = "Hello".getBytes(); // 按系统默认编码转换,不推荐
byte[] b2 = "Hello".getBytes("UTF-8"); // 按UTF-8编码转换
byte[] b2 = "Hello".getBytes("GBK"); // 按GBK编码转换
byte[] b3 = "Hello".getBytes(StandardCharsets.UTF_8); // 按UTF-8编码转换
14.2.2.2 已知编码的字节数组转换为字符串
byte[] b = ...
String s1 = new String(b, "GBK"); // 按GBK转换
String s2 = new String(b, StandardCharsets.UTF_8); // 按UTF-8转换

14.3 StringBuilder和StringBuffer

14.3.1 应用场景

背景:
通过+直接拼接字符串每次循环都会创建新的字符串对象,然后扔掉旧的字符串,浪费内存,影响GC效率。
StringBuilder 优点:
往StringBuilder中新增字符时,不会创建新的临时对象,还可以进行链式操作。

核心类应用场景
StringBuilder高效拼接字符串
StringBufferStringBuilder的线程安全版本很少使用

注:StringBuffer线程安全但执行速度慢

14.3.2 拼接字符串示例

StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
    sb.append(',');
    sb.append(i);
}
String s = sb.toString();

14.4 StringJoiner和String.join()

14.4.1 应用场景

核心类功能
StringJoiner用分隔符拼接数组(可以指定“开头”和“结尾”)
String.join()用分隔符拼接数组

14.4.2 示例

StringJoiner

String[] names = {"Bob", "Alice", "Grace"};
//"Hello " 指定开头
//"!" 指定结尾
StringJoiner sj = new StringJoiner(", ", "Hello ", "!");
for (String name : names) {
    sj.add(name);
}
System.out.println(sj.toString());//Hello Bob, Alice, Grace!

String.join()

String[] names = {"Bob", "Alice", "Grace"};
String s = String.join(", ", names);
System.out.println(s);//Bob, Alice, Grace

14.5 包装类

14.5.1 应用场景

把基本类型视为对象

14.5.2 种类

基本类型包装类
booleanjava.lang.Boolean
bytejava.lang.Byte
shortjava.lang.Short
intjava.lang.Integer
longjava.lang.Long
floatjava.lang.Float
doublejava.lang.Double
charjava.lang.Character

14.5.3 Auto Boxing/Auto Unboxing(不推荐使用)

14.5.3.1 应用场景

自动装箱和自动拆箱只发生在编译阶段,面向编译器,目的是简化代码。

//Auto Boxing 自动装箱
Integer n = 100; // 编译器自动使用Integer.valueOf(int)
//Auto Unboxing 自动拆箱
int x = n; // 编译器自动使用Integer.intValue()
14.5.3.2 特点
  1. 所有的包装类都是不变类
  2. 包装类的比较通过equals()方法比较
14.5.3.3 静态工厂方法

静态工厂方法:能创建“新”对象的静态方法

Integer包装类创建对象时,通过静态工厂方法Integer.valueOf()方法创建,它尽可能地返回缓存的实例以节省内存。

以下两种创建新对象方法的比较

//new总是创建新的Integer实例
Integer n = new Integer(100);
//尽可能地返回缓存的实例以节省内存
Integer n = Integer.valueOf(100);

14.5.4 进制转换

十进制转其他进制的整数

int x2 = Integer.parseInt("100", 16); // 256,因为按16进制解析

十进制转其他进制的字符串

System.out.println(Integer.toString(100)); // "100",表示为10进制
System.out.println(Integer.toString(100, 36)); // "2s",表示为36进制
System.out.println(Integer.toHexString(100)); // "64",表示为16进制
System.out.println(Integer.toOctalString(100)); // "144",表示为8进制
System.out.println(Integer.toBinaryString(100)); // "1100100",表示为2进制

14.5.5 处理无符号整型

14.6 JavaBean

14.6.1 应用场景

  1. 把一组数据组合成一个JavaBean便于传输
  2. JavaBean可以方便地被IDE工具分析,生成读写属性的代码

14.6.2 定义

字段的读写方法符合以下命名规范的类

// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)

注:boolean字段比较特殊,它的读方法一般命名为isXxx()
JavaBean是一种符合命名规范的class

14.6.3 属性

一组对应的读方法(getter)和写方法(setter)称为属性(property),不一定需要对应的字段

public class Person {
    private String name;
    private int age;

    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return this.age; }
    public void setAge(int age) { this.age = age; }

    public boolean isChild() {
        return age <= 6;
    }
}

child只读属性就不存在对应的字段。

14.6.4 枚举属性

package com.lxf.pro.string;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;

public class StringProject {
    public static void main(String[] args) throws IntrospectionException {
        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());
        }
    }
}

class Person {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

在这里插入图片描述
注:class属性是从Object继承的getClass()方法带来的

14.7 枚举类

14.7.1 应用场景

  1. 让编译器能自动检查某个值在枚举的集合内
  2. 不同用途的枚举需要不同的类型来标记,不能混用

14.7.2 特点

  1. enum虽然是引用类型,但enum的比较可以用==
  2. 定义的enum类型总是继承自java.lang.Enum,且无法被继承
  3. 只能定义出enum的实例,而无法通过new操作符创建enum的实例
  4. 定义的每个实例都是引用类型的唯一实例
  5. 可以将enum类型用于switch语句
  6. enum的构造方法要声明为private,字段强烈建议声明为final

14.7.3 编译后的枚举类

编译前

public enum Color {
    RED, GREEN, BLUE;
}

编译后

public final class Color extends Enum { // 继承自Enum,标记为final class
    // 每个实例均为全局唯一:
    public static final Color RED = new Color();
    public static final Color GREEN = new Color();
    public static final Color BLUE = new Color();
    // private构造方法,确保外部无法调用new操作符:
    private Color() {}
}

14.7.4 方法

方法功能
name()返回常量名
ordinal()返回定义的常量的顺序,从0开始计数

14.7.5 自定义private构造方法

14.7.5.1 应用场景

在枚举常量和int转换时,如果通过ordinal()实现,会引起因枚举变量定义顺序不同而导致的ordinal()的返回值不同,这样,如果在其他位置引用了枚举变量,会产生逻辑错误,使得程序缺失健壮性,好的方法是通过自定义private构造方法为枚举常量添加字段,从而将int值与枚举常量进行绑定。

14.7.5.2 实现方式
package com.lxf.pro.string;

import java.beans.IntrospectionException;

public class StringProject {
    public static void main(String[] args) throws IntrospectionException {
        Weekday day = Weekday.SUN;
        if (day.dayValue == 6 || day.dayValue == 0) {
            System.out.println("Work at home!");
        } else {
            System.out.println("Work at office!");
        }
    }
}

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;
    }
}

14.7.6 覆写toString()方法

14.7.6.1 应用场景

默认情况下,对枚举常量调用toString()会返回和name()一样的字符串。为了使枚举常量在输出时更具可读性,可对toString()方法进行覆写。
注:判断枚举常量的名字,要始终使用name()方法,绝不能调用toString()!

package com.lxf.pro.string;

import java.beans.IntrospectionException;

public class StringProject {
    public static void main(String[] args) throws IntrospectionException {
        Weekday day = Weekday.SUN;
        if (day.dayValue == 6 || day.dayValue == 0) {
            System.out.println("Today is " + day + ". Work at home!");
        } else {
            System.out.println("Today is " + day + ". Work at office!");
        }
    }
}

enum Weekday {
    MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日");

    public final int dayValue;
    private final String chinese;

    private Weekday(int dayValue, String chinese) {
        this.dayValue = dayValue;
        this.chinese = chinese;
    }

    @Override
    public String toString() {
        return this.chinese;
    }
}

在这里插入图片描述

14.8 记录类

14.8.1 支持版本

Java 14

14.8.2 应用场景

用于简化Data Class(一种不变类)的定义过程

14.8.3 不变类

  1. 定义class时使用final,无法派生子类;
  2. 每个字段使用final,保证创建实例后无法修改任何字段。
  3. 为了保证不变类的比较,还需要正确覆写equals()和hashCode()方法,这样才能在集合类中正常使用
public final class Point {
    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;
    }
	//覆写equals()方法

	//覆写hashCode()方法

	//覆写toString()方法(如果有需要)
}

14.9 BigInteger

14.9.1 应用场景

Java中,由CPU原生提供的整型最大范围是64位long型整数。java.math.BigInteger就是用来表示任意大小的整数。包括超过long型范围的整数。

14.10 BigDecimal

14.10.1 应用场景

BigDecimal可以表示一个任意大小且精度完全准确的浮点数。

14.11 常用工具类

Math:数学计算
Random:生成伪随机数
SecureRandom:生成安全的随机数

15. 异常处理

15.1 异常

15.1.1 调用方获知调用失败信息的方式

  1. 约定返回错误码,如:返回0,表示成功,返回其他整数,表示约定的错误码,常用于c语言。
int code = processFile("C:\\test.txt");
if (code == 0) {
    // ok:
} else {
    // error:
    switch (code) {
    case 1:
        // file not found:
    case 2:
        // no read permission:
    default:
        // unknown error:
    }
}
  1. 在语言层面上提供一个异常处理机制。

15.1.2 异常体系

在这里插入图片描述
Error及其子类程序不可捕获,Exception及其子类可捕获
Checked Exception(图中红色部分)必须进行捕获
RuntimeException及其子类按需进行捕获,通常不需要捕获

15.2 捕获异常

  1. 一个catch语句可以匹配多个非继承关系的异常。一般用于多个异常的处理逻辑相同,但异常之间不存在继承关系场景下。
public static void main(String[] args) {
    try {
        process1();
        process2();
        process3();
    } catch (IOException | NumberFormatException e) { // IOException或NumberFormatException
        System.out.println("Bad input");
    } catch (Exception e) {
        System.out.println("Unknown error");
    }
}
  1. 如果使用try…finally结构,需要在方法上声明可能抛出的异常。

15.3 抛出异常

15.3.1 步骤

  1. 创建某个Exception的实例
  2. 用throw语句抛出
void process2(String s) {
    if (s==null) {
    	//下面两行常缩写为
    	//throw new NullPointerException();
        NullPointerException e = new NullPointerException();
        throw e;
    }
}

15.3.2 “转换”抛出的异常类型

如果一个方法捕获了某个异常后,又在catch子句中抛出新的异常,就相当于把抛出的异常类型“转换”了

package com.lxf.ss;

public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
        	//process1检测到process2抛出的NullPointerException异常后重新抛出了IllegalArgumentException,导致第一现场的信息丢失了
            throw new IllegalArgumentException();
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}

注:直接抛出新的异常会导致第一现场的信息丢失
在这里插入图片描述
解决方法:

package com.lxf.ss;

public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
        	//抛出新异常时传入捕获到的异常,新的Exception就可以持有原始Exception信息,从而能够定位到第一现场的信息。
            throw new IllegalArgumentException(e);
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}

在这里插入图片描述
注: 捕获到异常并再次抛出时,一定要留住原始异常

15.3.3 仅在catch中抛出异常

public class Main {
    public static void main(String[] args) {
        try {
            Integer.parseInt("abc");
        } catch (Exception e) {
            System.out.println("catched");
            //异常会在执行完finally块之后抛出
            throw new RuntimeException(e);
        } finally {
            System.out.println("finally");
        }
    }
}

在这里插入图片描述

15.3.4 在catch和finally中均抛出异常

public class Main {
    public static void main(String[] args) {
        try {
            Integer.parseInt("abc");
        } catch (Exception e) {
            System.out.println("catched");
            //RuntimeException被屏蔽了
            throw new RuntimeException(e);
        } finally {
            System.out.println("finally");
            throw new IllegalArgumentException();
        }
    }
}

在这里插入图片描述
抛出了finally块中的异常,catch块中的异常被屏蔽了,该未抛出的异常被称为Suppressed Exception
程序执行过程中只能抛出一个异常,所以,finally块中的异常抛出后,catch块中的异常就被屏蔽了

获知所有异常的方法如下:

public class Main {
    public static void main(String[] args) throws Exception {
        Exception origin = null;
        try {
            System.out.println(Integer.parseInt("abc"));
        } catch (Exception e) {
			//1.用origin变量保存原始异常
            origin = e;
            throw e;
        } finally {
            Exception e = new IllegalArgumentException();
            if (origin != null) {
            	//2.添加原始异常到现有异常中
                e.addSuppressed(origin);
            }
            //3.抛出现有异常
            throw e;
        }
    }
}

在这里插入图片描述
注:Throwable.getSuppressed()可以获取所有的Suppressed Exception。

15.4 自定义异常

  1. 抛出异常时,尽量复用JDK已定义的异常类型
  2. 自定义异常体系时,推荐从RuntimeException派生“根异常”,再派生出业务异常
  3. 自定义异常时,应该提供多种构造方法(实际上就是RuntimeException的构造方法),可以通过IDE根据父类快速生成子类的构造方法

15.5 NullPointerException(NPE)

通常由JVM抛出

15.5.1 处理NullPointerException

  1. 不能使用catch隐藏该错误
// 错误示例: 捕获NullPointerException
try {
    transferMoney(from, to, amount);
} catch (NullPointerException e) {
	//捕获后什么处理措施都没有,该错误就被隐藏了
}
  1. 早暴露,早修复

15.5.2 预防产生NullPointerException

  1. 成员变量在定义时初始化使用空字符串""而不是默认的null
  2. 方法返回空字符串""、空数组而不是null,这样调用方不需要检查结果是否为null,如果调用方一定要根据null判断,比如返回null表示文件不存在,那么考虑返回Optional<T>,这样调用方必须通过Optional.isPresent()判断是否有结果。
public Optional<String> readFromFile(String file) {
    if (!fileExist(file)) {
        return Optional.empty();
    }
    ...
}

15.5.3 定位NullPointerException(Java 14)

报错时JVM可以给出详细的信息告诉我们null对象到底是谁
默认关闭,通过-XX:+ShowCodeDetailsInExceptionMessages参数开启

java -XX:+ShowCodeDetailsInExceptionMessages Main.java

15.6 断言【很少使用】

15.6.1 应用场景

开发和测试阶段调试使用,断言失败时抛出AssertionError,程序结束退出;断言成功掠过断言语句,继续执行程序

15.6.2 添加断言消息

在这里插入图片描述

15.6.3 启用断言

JVM默认关闭断言指令,遇到assert语句自动忽略,不执行

//-ea:-enableassertions
java -ea Main.java

//对特定类启用断言
-ea:com.itranswarp.sample.Main

//对特定包启用断言
-ea:com.itranswarp.sample...

15.7 JDK Logging【很少使用】

java.util.logging

15.7.1 应用场景

取代System.out.println()调试方式

15.7.2 日志级别

JDK的Logging定义了7个日志级别,从严重到普通:

  1. SEVERE
  2. WARNING
  3. INFO
  4. CONFIG
  5. FINE
  6. FINER
  7. FINEST

默认级别是INFO,INFO以下的日志,不会被打印出来,如FINE级别的日志

15.7.3 局限性

  1. Logging系统在JVM启动时读取配置文件并完成初始化,一旦开始运行main()方法,就无法修改日志配置
  2. 配置不方便,需要在JVM启动时传递参数-Djava.util.logging.config.file=<config-file-name>

15.8 Commons Logging

org.apache.commons.logging,第三方日志库,Apache创建的日志模块

15.8.1 应用场景

可以挂接不同的日志系统,并通过配置文件指定挂接的日志系统。默认情况下,Commons Loggin自动搜索并使用Log4j(Log4j是另一个流行的日志系统),如果没有找到Log4j,再使用JDK Logging。

15.8.2 日志级别

  1. FATAL
  2. ERROR
  3. WARNING
  4. INFO
  5. DEBUG
  6. TRACE

默认级别是INFO,INFO以下的日志,不会被打印出来,如DEBUG级别的日志

15.8.3 使用

注:使用需要下载第三方jar包

  1. 通过LogFactory获取Log类的实例;
  2. 使用Log实例的方法打日志。
15.8.3.1 静态方法中引用Log

直接定义一个静态类型变量

// 在静态方法中引用Log:
public class Main {
    static final Log log = LogFactory.getLog(Main.class);

    static void foo() {
        log.info("foo");
    }
}
15.8.3.2 实例方法中引用Log

定义一个实例变量

// 在实例方法中引用Log:
public class Person {
    protected final Log log = LogFactory.getLog(getClass());

    void foo() {
        log.info("foo");
    }
}

注:LogFactory.getLog(getClass())也可以用LogFactory.getLog(Person.class),前者的好处是子类可以直接使用父类的log实例

15.8.4 日志方法的增强

Commons Logging的日志方法,例如info(),除了标准的info(String)外,还提供了一个非常有用的重载方法:info(String, Throwable),使得记录异常更加简单,通过

try {
    ...
} catch (Exception e) {
    log.error("got exception!", e);
}

实现。

15.9 Log4j

15.9.1 特点

  1. Log4j可以把同一条日志输出到不同的目的地
  2. 在输出日志的过程中,通过Filter来过滤哪些log需要被输出,哪些log不需要被输出。例如,仅输出ERROR级别的日志。
  3. 通过Layout来格式化日志信息,例如,自动添加日期、时间、方法名称等信息。
  4. 配合Commons Logging一起使用
  5. 通过配置文件进行配置

15.9.2 使用

注:使用需要下载第三方jar包
与Log4j相关的jar包

  1. log4j-api-2.x.jar
  2. log4j-core-2.x.jar
  3. log4j-jcl-2.x.jar

15.10 SLF4J和Logback

Commons Logging和Log4j的升级版,SLF4J对应Commons Logging,Logback对应Log4j

16. 反射

程序在运行期可以拿到一个对象的所有信息。即通过Class实例获取class信息

16.1 Class类

16.1.1 应用场景

反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。

16.1.2 关于Class类

  1. 每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。
  2. Class类的构造方法是private,只有JVM能创建Class实例
  3. 一个Class实例包含了该class的所有完整信息
  4. 获取到某个Class对象时,实际上就获取到了一个类的类型

16.1.2 获取class的Class实例的方式

  1. 通过class的静态变量class获取
  2. 通过实例变量提供的getClass()方法获取
  3. 通过静态方法Class.forName(完整类名)获取

注:上述方法获取的Class实例是同一个实例。可以用==比较两个Class实例

16.1.3 通过Class实例来创建对应类型的实例

// 获取String的Class实例:
Class cls = String.class;
// 创建一个String实例:
//(String) cls.newInstance() 相当于 new String()
String s = (String) cls.newInstance();

注:与new String()不同,只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。

16.1.4 几种特殊的Class

  1. 数组(如String[])也是一种Class,类名是[Ljava.lang.String
  2. JVM为每一种基本类型如int也创建了Class

16.1.5 instanceof和==

== 精确类型比较
instanceof不但匹配指定类型,还匹配指定类型的子类

16.1.6 动态加载

JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。可以在运行期根据条件来控制加载class

16.2 访问字段

通过反射读写字段是一种非常规方法,它会破坏对象的封装。

16.2.1 获取字段

Class提供了几个方法来获取字段

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

16.2.2 获取字段信息

Field提供了几个方法来获取字段信息(名称、类型、修饰符)

方法功能
getName()返回字段名称
getType()返回字段类型
getModifiers()返回字段的修饰符
get(Object)获取指定实例的指定字段的值
set(Object, Object)设置字段的值,第一个Object参数是指定的实例,第二个Object参数是待修改的值

注:通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段。

16.3 访问方法

16.3.1 获取方法

Class提供了几个方法来获取方法

方法功能
Method getMethod(name, Class…)获取某个public的Method(包括父类)
Method getDeclaredMethod(name, Class…)获取当前类的某个Method(不包括父类)
Method[] getMethods()获取所有public的Method(包括父类)
Method[] getDeclaredMethods()获取当前类的所有Method(不包括父类)

16.3.2 获取方法信息

方法功能
getName()返回方法名称
getReturnType()返回方法返回值类型,也是一个Class实例
getParameterTypes()返回方法的参数类型,是一个Class数组
getModifiers()返回方法的修饰符,它是一个int,不同的bit表示不同的含义。

注:通过Method实例可以访问某个类的方法,如果存在访问限制,要首先调用setAccessible(true)来访问非public方法。

16.3.3 调用方法

方法功能
invoke(Method实例, 实例方法的参数列表…)调用实例方法
invoke(null, 静态方法的参数列表…)调用静态方法
16.3.3.1 调用实例方法
package com.lxf.ss;

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        // String对象:
        String s = "Hello world";
        // 获取String substring(int)方法,参数为int:
        Method m = String.class.getMethod("substring", int.class, int.class);
        // 在s对象上调用该方法并获取结果:
        String r = (String) m.invoke(s, 6, 8);
        // 打印调用结果:
        System.out.println(r);
    }
}
16.3.3.2 多态

使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)。

Method m = Person.class.getMethod("hello");
m.invoke(new Student());

相当于

Person p = new Student();
p.hello();

16.4 调用构造方法

16.4.1 实现方式

  1. Class提供的newInstance()方法
    只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
  2. 通过Constructor实例创建对象
    可以调用任意的构造方法

16.4.2 获取Constructor

方法功能
getConstructor(Class…)获取某个public的Constructor
getDeclaredConstructor(Class…)获取某个Constructor
getConstructors()获取所有public的Constructor
getDeclaredConstructors()获取所有Constructor

注:调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。

16.4.3 示例

package com.lxf.ss;

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取构造方法Integer(int):
        Constructor cons1 = Integer.class.getConstructor(int.class);
        // 通过Constructor实例调用构造方法:
        Integer n1 = (Integer) cons1.newInstance(123);
        System.out.println(n1);
    }
}

16.5 获取继承关系

方法功能
getSuperclass()获取当前类的父类的Class
getInterfaces()获取当前类直接实现的接口类型,如果一个类没有实现任何interface,那么getInterfaces()返回空数组。

注:getInterfaces()只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型

16.5.1 instanceof和isAssignableFrom()

方法功能
instanceof判断一个实例是否是某个类型
isAssignableFrom()两个Class实例,判断一个向上转型是否成立

16.6 动态代理

JDK提供的动态创建接口对象的方式,就叫动态代理。

16.6.1 应用场景

不编写实现类,直接在运行期动态创建某个interface的实例

16.6.2 步骤

  1. 定义一个InvocationHandler实例,它负责实现接口的方法调用;
  2. 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
    2.1 使用的ClassLoader,通常就是接口类的ClassLoader;
    2.2 需要实现的接口数组,至少需要传入一个接口进去;
    2.3 用来处理接口方法调用的InvocationHandler实例。
  3. 将返回的Object强制转型为接口。

17. 注解

放在Java源码的方法字段参数前的一种特殊“注释”

17.1 使用注解

17.1.1 注解与注释

  1. 注释会被编译器直接忽略
  2. 注解可以被编译器打包进入class文件

17.1.2 分类

17.1.2.1 由编译器使用的注解

这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。

17.1.2.2 由工具处理.class文件使用的注解

这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。

17.1.2.3 程序运行期能够读取的注解

在加载后一直存在于JVM中,是最常用的注解。

17.1.3 配置参数

17.2 定义注解

17.2.1 格式

public @interface Report {
	//注解的参数类似无参数方法
    int type() default 0;
    String level() default "info";
    String value() default "";
}

注:default用于设定默认值,最常用的参数应当命名为value

17.2.2 元注解

可以修饰其他注解的注解被称为元注解
注:通常只需要使用Java标准库提供的元注解,不需要自己去编写元注解

17.2.2.1 @Target

定义注解能够被应用于源码的哪些位置

  1. 应用于类或接口:ElementType.TYPE;
  2. 应用于字段:ElementType.FIELD;
  3. 应用于方法:ElementType.METHOD;
  4. 应用于构造方法:ElementType.CONSTRUCTOR;
  5. 应用于方法参数:ElementType.PARAMETER。
    注:如果想要指定多个位置,需要把@Target注解参数变为数组,如@Target({ElementType.METHOD,ElementType.FIELD})
17.2.2.2 @Retention

定义了Annotation的生命周期

  1. 仅编译期:RetentionPolicy.SOURCE;
  2. 仅class文件:RetentionPolicy.CLASS;
  3. 运行期:RetentionPolicy.RUNTIME。
    注:如果@Retention不存在,则该Annotation默认为CLASS,自定义注解务必要加上@Retention(RetentionPolicy.RUNTIME)
17.2.2.3 @Repeatable【不常用】

定义Annotation是否可重复,经过@Repeatable修饰后,在某个类型声明处,就可以添加多个@Report注解

@Report(type=1, level="debug")
@Report(type=2, level="warning")
public class Hello {
}
17.2.2.4 @Inherited

定义子类是否可继承父类定义的Annotation
注:@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效

17.2.3 步骤

  1. 用@interface定义注解
  2. 添加参数、默认值
    把最常用的参数定义为value(),推荐所有参数都尽量设置默认值。
  3. 用元注解配置注解
    必须设置@Target和@Retention,@Retention一般设置为RUNTIME,以便在运行期读取该注解,一般情况下,不必写@Inherited和@Repeatable

17.3 处理注解

17.3.1 关于注解

SOURCE类型的注解主要由编译器使用,只使用,不编写;CLASS类型的注解主要由底层工具库使用,很少使用;RUNTIME类型的注解,经常使用,经常编写

17.3.2 读取RUNTIME类型的注解

使用反射API读取Annotation

方法功能
Class.isAnnotationPresent(Class)判断某个注解是否存在于Class
Field.isAnnotationPresent(Class)判断某个注解是否存在于Field
Method.isAnnotationPresent(Class)判断某个注解是否存在于Method
Constructor.isAnnotationPresent(Class)判断某个注解是否存在于Constructor
Class.getAnnotation(Class)读取Class注解
Field.getAnnotation(Class)读取Field注解
Method.getAnnotation(Class)读取Method注解
Constructor.getAnnotation(Class)读取Constructor注解

17.3.3 读取RUNTIME类型的注解的方法

  1. 先判断Annotation是否存在,如果存在,就直接读取
  2. 直接读取Annotation,如果Annotation不存在,将返回null

17.3.4 使用注解

注解如何使用,完全由程序自己决定。定义了注解,本身对程序逻辑没有任何影响。我们必须自己编写代码来使用注解。检查逻辑完全是我们自己编写的,JVM不会自动给注解添加任何额外的逻辑。

17.3.5 注解的应用

  1. 对JavaBean的属性值按规则进行检查;

18. 泛型

18.1 应用场景

编写一次,万能匹配(即编写模板代码来适应任意类型),同时通过编译器保证了类型安全

18.2 向上转型

ArrayList<T>可以向上转型为List<T>,但不能把ArrayList<Integer>向上转型为ArrayList<Number>List<Number>

18.3 使用泛型

18.3.1 ArrayList<T>

泛型就是定义一种模板,例如ArrayList,然后在代码中为用到的类创建对应的ArrayList<类型>

  1. 使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是Object
  2. 泛型可以进行简写,编译器能自动推断
// 可以省略后面的Number,编译器可以自动推断泛型类型:
List<Number> list = new ArrayList<>();

18.3.2 泛型接口(在接口中使用泛型)

  1. 可以在接口中定义泛型类型,实现此接口的类必须实现正确的泛型类型。
  2. Arrays.sort(Object[]),可以对任意数组进行排序,但待排序的元素必须实现Comparable<T>这个泛型接口

18.4 编写泛型

18.4.1 步骤

  1. 按照某种类型来编写类
  2. 标记所有的特定类型
  3. 把特定类型String替换为T,并申明<T>

18.4.2 静态方法

  1. 泛型类型<T>不能用于静态方法,无法在静态方法的方法参数和返回类型上使用泛型类型T
  2. 静态方法使用泛型类型,需要单独改写为“泛型”方法,需要使用另一个类型<K>

18.4.3 多个泛型类型

不总是存储两个类型一样的对象,就可以使用类型<T, K>
如Map<K, V>

18.5 擦拭法

不同语言的泛型实现方式不一定相同,Java语言的泛型实现方式是擦拭法
即虚拟机对泛型其实一无所知,所有的工作都是编译器做的。

18.5.1 特点

  1. 编译器把类型<T>视为Object;
  2. 编译器根据<T>实现安全的强制转型。
  3. Java的泛型是由编译器在编译时实行的
  4. 编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。
  5. 编译器会阻止一个实际上会变成覆写的泛型方法定义

18.5.2 局限

  1. <T>不能是基本类型
  2. 无法取得带泛型的Class(所有泛型实例,无论T的类型是什么,getClass()返回同一个Class实例,因为编译后它们全部都是Pair<Object>。)
  3. 无法判断带泛型的类型
  4. 不能实例化T类型,实例化T类型,须借助额外的Class<T>参数

18.5.3 泛型继承

一个类可以继承自一个泛型类。

public class IntPair extends Pair<Integer> {
}

在继承了泛型类型的情况下,子类可以获取父类的泛型类型<T>。

19. 集合

19.1 简介

可以在内部持有若干其他Java对象,并对外提供访问接口的对象称为集合。

19.1.1 特点

  1. 接口和实现类相分离
  2. 支持泛型,可以限制在一个集合中只能放入同一种数据类型的元素
  3. 访问集合通过迭代器来实现,这种方式的好处是无需知道集合内部元素是按什么方式存储的

19.1.2 所在位置

java.util

19.1.3 遗留类和接口

尽量不要使用遗留类和接口
遗留类
Hashtable:一种线程安全的Map实现;
Vector:一种线程安全的List实现;
Stack:基于Vector实现的LIFO的栈。

遗留接口
Enumeration<E>:已被Iterator<E>取代。

19.1.4 分类

List,Set和Map
注:数组也是集合

19.2 使用List

实现类:ArrayList和LinkedList

19.2.1 ArrayList

数组实现

当数组已满,继续添加元素时,ArrayList先创建一个更大的新数组,然后把旧数组的所有元素复制到新数组,紧接着用新数组取代旧数组

19.2.2 LinkedList

链表实现

19.2.3 ArrayList和LinkedList的比较

比较点ArrayListLinkedList
获取指定元素速度很快需要从头开始查找元素
添加元素到末尾速度很快速度很快
在指定位置添加/删除需要移动元素不需要移动元素
内存占用较大

注:优先使用ArrayList

19.2.4 特点

  1. List允许添加重复的元素
  2. List允许添加null

19.2.5 使用

19.2.5.1 创建List
  1. 使用ArrayList,List<String> list = new ArrayList<>();
  2. 使用LinkedList,List<String> list = new LinkedList<>();
  3. 通过List接口提供的of()方法,根据给定元素快速创建【java 9可用】
    List<Integer> list = List.of(1, 2, 5);
    注:List.of()方法不接受null值
19.2.5.2 遍历List
  1. for循环根据索引配合get(int)方法
    不推荐,get(int)方法只有ArrayList的实现是高效的,换成LinkedList后,索引越大,访问速度越慢。
  2. 迭代器Iterator
    通过Iterator遍历List永远是最高效的方式
List<String> list = new ArrayList<>();
        list.add(null); // size=2
        list.add("112");
        list.add("24esf");
        for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
            String s = it.next();
            System.out.println(s);
        }
  1. for each循环
    Java编译器会自动把for each循环变成Iterator的调用
19.2.5.3 List和Array转换
19.2.5.3.1 List -》Array
  1. 调用List的toArray()方法
    返回Object[]数组,会丢失类型信息,少用
  2. 给toArray(T[])传入一个类型相同的Array,List内部自动把元素复制到传入的Array中
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add(null); // size=2
        list.add("112");
        list.add("24esf");
        String[] array = list.toArray(new String[3]);
        //如果传入的数组size小于List实际长度,List自动创建一个与实际List长度相同的数组,填充后返回。
        //String[] array = list.toArray(new String[1]);
        //如果传入的数组size大于List实际长度,填充完元素后,剩下的数组元素填充为null
        //String[] array = list.toArray(new String[6]);
        //最常用的是传入一个恰好大小的数组。
        //String[] array = list.toArray(new String[list.size()]);
        for (String n : array) {
            System.out.println(n);
        }
        System.out.println(array.length);
    }
}
  1. 还有一种简洁的函数式写法
String[] array = list.toArray(String[]::new);
19.2.5.3.2 Array -》List
  1. List.of(T…) 【Java 9】
    返回的是一个只读List
  2. Arrays.asList(T…)
    返回的是一个只读List
Integer[] array = { 1, 2, 3 };
List<Integer> list = Arrays.asList(array);
for (Integer x:list) {
	System.out.println(x);
}

19.3 编写equals方法

List的contains()、indexOf()方法,不是通过==判断两个元素是否相等,而是使用equals()方法判断两个元素是否相等

package com.lxf.fx;

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Person> list = new ArrayList<>();
        list.add(new Person("Xiao Ming"));
        list.add(new Person("Xiao Hong"));
        list.add(new Person("Bob"));

        System.out.println(list.contains(new Person("Bob"))); // false
    }
}

class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
}

原因就是没有覆写equals()方法,默认使用Object对象的equals()方法
在这里插入图片描述
该方法用于判断地址是否相对,引用到上面的例子,就是循环遍历List中的元素,调用new Person(“Bob”)的equals()方法,传入元素,比较List中是否存在地址与new Person(“Bob”)地址相同的元素,故返回false

19.3.1 步骤

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

注:如果不调用List的contains()、indexOf()这些方法,那么放入的元素就不需要实现equals()方法。即如果不在List中查找元素,就不必覆写equals()方法。

19.4 使用Map

19.4.1 应用场景

通过一个键去查询对应的值,如在List中根据name查找某个指定的Student的分数,使用List来实现存在效率非常低的问题。

19.4.2 实现类

最常用的实现类是HashMap

19.4.3 特点

  1. 通过key获取value,如果key不存在,get()方法返回null
  2. 一个key只能关联一个value,放入相同的key,只会把原有的key-value对应的value给替换掉,put()方法会返回被删除的旧的value,否则,返回null。
  3. Map中,虽然key不能重复,但value是可以重复的
  4. Map不保证顺序

19.4.4 遍历Map

  1. 遍历key:使用for each循环遍历Map实例的keySet()方法返回的Set集合
  2. 同时遍历key和value:使用for each循环遍历Map对象的entrySet()集合

19.5 编写equals和hashCode

19.5.1 Map模型

数组实现
在这里插入图片描述

19.5.2 Map工作原理

根据键取值:首先判断给定的键和Map中的某个键是相等的,然后通过键计算出对应的索引,取到对应的值

HashMap初始化时如果不指定大小默认的数组大小只有16,如果添加的元素个数超过了16,HashMap会在内部自动扩容,每次扩容一倍,即长度为16的数组扩展为长度32,扩容会导致重新分布已有的key-value,因此,扩容后需要重新确定hashCode()计算的索引位置,频繁扩容对HashMap的性能影响很大。如果能够确定使用的HashMap容量,最好的方式是创建时指定容量

Map<String, Integer> map = new HashMap<>(10000);

虽然指定了10000,但HashMap内部的数组长度总是2^n,因此,实际数组长度被初始化为比10000大的16384(214)。

键值放入HashMap后,如果需要取值,通过给定键的hashCode()方法计算出存储对应value的索引,如果在存储键值时,有两个键不同(equals()),但两个键的hashCode()方法计算出的索引相同,那么,计算出索引后,还需要遍历List,因为,此时指定索引下存储的不再是简单的value,而是键值(Entry)构成的List,List中包含多个Entry

根据key直接计算出value应该存储在哪个索引

19.6 使用EnumMap

19.6.1 使用场景

作为key的对象是enum类型

19.6.2 优点

根据enum类型的key直接定位到内部数组的索引,并不需要计算hashCode()

19.7 使用TreeMap

在这里插入图片描述

19.7.1 使用场景

遍历时以Key的顺序来进行排序

19.7.2 注意事项

  1. 放入的Key必须实现Comparable接口,作为Value的对象没有任何要求。
  2. 如果作为Key的class没有实现Comparable接口,那么,必须在创建TreeMap时同时指定一个自定义排序算法。
  3. 作为Key的class如果实现了Comparable接口,返回值中要包含0,1,-1三种值。
  4. TreeMap不使用equals()和hashCode(),即不需要进行覆写。

19.7.3 使用

定义作为Key的class

package com.lxf.treemap;

public class Person implements Comparable{

    private String name;
    private Integer score;

    public Integer getScore() {
        return score;
    }

    public void setScore(Integer score) {
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int compareTo(Object o) {
        Person b = (Person) o;

        if (this.score.equals(b.score)){
            return 0;
        }
        return this.score.compareTo(b.score)>0 ? 1:-1;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

返回值中包含了三种情况
在这里插入图片描述
测试

package com.lxf.treemap;

import java.util.Map;
import java.util.TreeMap;

public class TestTreeMap2 {

    public static void main(String[] args) {

        Map<Person, Integer> personTreeMap = new TreeMap<>();
        Person a = new Person();
        a.setName("bhhh");
        a.setScore(15);
        Person b = new Person();
        b.setName("aaaa");
        b.setScore(12);
        Person c = new Person();
        c.setName("bhhh");
        c.setScore(15);
        personTreeMap.put(a, 1);
        personTreeMap.put(b, 2);

        for (Person d : personTreeMap.keySet()){
            System.out.println(d);
        }
        System.out.println(personTreeMap.get(c));

    }
}

19.8 使用Properties

规范

  1. 创建HashMap时如果能够确定容量就直接指定,防止频繁扩容带来的效率问题
  2. equals()用到的用于比较的每一个字段,都必须在hashCode()中用于计算;equals()中没有使用到的字段,绝不可放在hashCode()中计算。

规范

  1. 类中的private方法放在其他方法的后面,private方法用于类内部使用。
  2. 捕获了异常需要进行处理,最简单的处理方法是调用printStackTrace()打印异常栈。
  3. 多个catch的顺序,子类放在前面,多个catch语句只有一个能被执行
  4. 捕获异常并再次抛出新的异常时,应该持有原始异常信息
  5. 提问时异常信息要包含最关键的Caused by: xxx
  6. 通常不要在finally中抛出异常。如果在finally中抛出异常,应该原始异常加入到原有异常中。

FAQ

1. 三元运算符

JDK版本:1.8
参考网址:https://www.liaoxuefeng.com/wiki/1252599548343744/1255938640048480
中提到的b ? x : y中x和y的类型必须相同

package com.lxf.pro;

public class BaseDataType {
    public static void main(String[] args) {
        System.out.println(3>2?1:'中');
    }
}

在这里插入图片描述

2. 类的 private 引用类型字段如何确保不变性

从String的不变性设计可以看出,如果传入的对象有可能改变,我们需要复制而不是直接引用。
现象:

package com.lxf.pro.string;

import java.util.Arrays;

public class StringProject {
    public static void main(String[] args) {
        int[] scores = new int[] { 88, 77, 51, 66 };
        Score s = new Score(scores);
        s.printScores();//[88, 77, 51, 66]
        scores[2] = 99;
        s.printScores();//[88, 77, 99, 66]
    }
}

class Score {
    private int[] scores;
    public Score(int[] scores) {
        this.scores = scores;
    }

    public void printScores() {
        System.out.println(Arrays.toString(scores));
    }
}

解决:

package com.lxf.pro.string;

import java.util.Arrays;

public class StringProject {
    public static void main(String[] args) {
        int[] scores = new int[] { 88, 77, 51, 66 };
        Score s = new Score(scores);
        s.printScores();//[88, 77, 51, 66]
        scores[2] = 99;
        s.printScores();//[88, 77, 51, 66]
    }
}

class Score {
    private int[] scores;
    public Score(int[] scores) {
        //将scores的引用直接赋给了this.scores
        //this.scores = scores;
        //拷贝一份赋给scores
        this.scores = Arrays.copyOf(scores, scores.length);
    }

    public void printScores() {
        System.out.println(Arrays.toString(scores));
    }
}
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值