前端速学 java 基础语法

风希落 / 2024-04-19 / 原文

前端开发快速入门 java 语法(如果对 TS 和 es6 的 class 面向对象熟悉,更容易理解 java 的基础语法)。

课程来自B站黑马,本文是对学习过程中的总结和记录。

入门

IDEA、Java 环境这些就不介绍。

IDEA 部分快捷设置

  • 自动导包。
    image
  • 忽略大小写(String 时有提示,string 无提示且报错,可取消该设置)
    image

更多的 IDEA 常用设置可参考另一篇文章 IDEA常用代码规范设置

代码快捷键

.var 可以直接自动补充前缀类型和属性名

Person person = new Person();
// 可以使用 new Person().var 来自动补充前缀的类型 Person 和变量名 person

alt + 左键选中 可以快速批量选中某些代码片段,快速对其进行更改

ctrl + alt + L 快速格式化代码

shift + enter 换行

ctrl + P 快速弹出参数提示

alt + enter 可以对错误进行快速修复纠正

alt + insert 可以快速生成 get set constructor 等方法。

运算符

  • 隐式转换

    • 运算的取值范围:byte(1) < short(2) < int(4) < long(8) < float(4) < double(8)
      • floatdouble,浮点数类型,用于表示带有小数部分的数值,其内部存储结构和整型不同,因此不能单纯按照数值范围来比较它们的字节大小
    • 运算时数据类型不一致,则会隐式转换为一致的数据类型再计算
    • 小范围的会先转为大范围的,比如 float 和 double 相加,会统一提升为 double
    • byte short char 运算时,会直接先提升为 int 类型,比如 byte 和 short 类型相加时,都会提升为 int 进行运算。
  • 强制转换

    • 将大范围类型的转换为小范围类型的数据,必须依靠强制转换,但是强制转换的数据有可能出现一些错误
      byte a = 100;
      byte b = 100;
      byte num = (byte)(a+b);  // -57 强制转换成功,但结果错误
      
  • 字符相加

    • 普通字符相加,都是直接拼接的关系,比如 'a' + 'b' = "ab";
    • 当 char 字符和 数字相加时,java 会首先去 AscII 码表中查找 字符对应的数字,然后相加
      char a1 = 'a';  // ASCII 对应 97
      int a2 = 1;
      System.out.println(a1 + a2);  // 98
      
  • 自增自减
    ++aa++ ,在单独一行使用时,效果都是一样的,当赋值时,则出现区别

    • a++ 是先用后加
    • ++a 是先加后用
      int a = 10;
      int b = a++; // b = 10
      int b = ++a; // b = 11
      
  • 赋值运算符

    • a += b; 等价于 a = a + b;_= *= /= % = 同理
  • 关系运算符:== != > >= < <=(功能和js一致,略)

  • 逻辑运算符

    • & | ^ !
    • ^ 异或:即判断两个是否为不同,不同为 true。例如 true ^ true 打印为 false,true ^ false 打印为 true。
  • 短路逻辑运算符

    • && ||
    • && ||& | 的效果一致,即和 js 中的& || 一样。
  • 逻辑/短路运算符区别

    • & | 无论左边 true false,右边都执行
    • && || 如果左边能确定整个表达式结果,右边不执行
    • && || 和 js 中的 & || 表达效果一致
  • 三元运算符:a>b?a:b(功能和js一致,略)

判断和循环

  • if 语句,和 js 效果一致,略
  • switch 语句,和 js 效果一致
    • JDK 12 新特性写法
    int num = 10;
    // 传统写法
    switch (num) {
    	case 1:
    		System.out.println("一");
    		break;
    	case 2:
    		System.out.println("二");
    		break;
    	case 3:
    		System.out.println("三");
    		break;
    	default:
    		System.out.println("DEFAULT");
    		break;
    }
    // JDK 12 新写法
    switch (num) {
    	case 1 -> System.out.println("一");
    	case 2 -> System.out.println("二");
    	case 3 -> System.out.println("三");
    	// 只有一行的情况下,括号可省略
    	default -> {
    		System.out.println("DEFAULT");
    	}
    }
    
    • swtich 匹配赋值也可以简写
    // 通过 swtich 匹配,给数据赋值,正常 swtich 简写
     String s = "2";
     String str;
     switch (s) {
         case "1" -> str = "一";
         case "2" -> str = "二";
         case "3" -> str = "三";
         default -> str = "no result";
     }
    
     // 简写,case 匹配到的时候 return 了匹配的值,被 str 接收
     String s = "2";
     String str = switch (s) {
         case "1" -> "一";
         case "2" -> "二";
         case "3" -> "三";
         default ->  "no result";
     };
     System.out.println(str);
    
  • for 循环语句,和 js 效果一致,略
  • while 循环语句,和 js 效果一致,略

数组

  • 初始化数组
String[] strArr = {"张三","李四"};  // 静态初始化
System.out.println(strArr);  // [Ljava.lang.String;@776ec8df 数组的堆内存地址

String[] strs = new String[2];  // 动态初始化,固定长度不能扩展
System.out.println(strs[0]);  // null  未赋值的默认为 null
System.out.println(strs[3]);  // 异常:Index 3 out of bounds for length 2
  • 元素访问和赋值(与 js 保持一致,略)

  • 数组循环,IDEA 快捷键 [数组名].fori(其余与 js 保持一致,略)

  • 数组内存

    • 主入口 main 方法执行时会入栈,方法执行完毕出栈
    • new 出来的都在堆内存中,开辟一个内存空间,并占位。

方法

  • 跟 js [class 类]/[函数] 的比较相似,但是 js 的方法命名在同模块下不能重名,同名的后边的会把前边的覆盖掉。
  • java 可以在同一个类下边命名重复的方法,只要变量入参不同即可,即 方法重载
public static String getStr() {
    // ...逻辑代码
    return "aa";
}
// 方法重载
public static String getStr(String a) {
    return a;
}
  • 方法内存

    • 方法在运行时,都在入栈运行,运行完毕出栈
    • new 出来的,都在堆内存中有其对应的内存地址
    • 方法运行完毕出栈时,其内部的变量能都会随之销毁
    • 多个方法运行时,栈内存先进后出
  • 基本数据类型 引用数据类型

    • 基本数据类型:值存储在自己的空间,赋值时,是真实的数据值
    • 引用数据类型:值存储在其他空间,赋值时,是空间的内存地址
      public static void main(String[] args) {
      	String[] strArr = {"张三", "李四"};
      	changeArr(strArr);  // 直接修改的是数组内存地址中的值
      	System.out.println(strArr[1]);  // 赵六
      }
      
      public static void changeArr(String[] arr) {
      	arr[1] = "赵六";
      }
      

面向对象

js 中 class 类的使用就是面向对象。class AA const aa = new AA() aa.x
对 es6 class 熟悉的,会更容易理解 java 中的面向对象

java 中,一个 java 文件中允许存在多个类名,但只有一个类名能用 public 修饰,且 public 修饰的类名必须和文件名保持一致

// Person 类
public class Person {
    String name;  // 属性(成员变量)
    String age;

    // 行为(方法)
    public void printCode() {
        System.out.println(name + "在码字");
    }
}

// 调用/实例化 Person
Person person = new Person();
person.name = "张三";
person.printCode();  // 张三在码字

成员变量在初始化时,若未手动声明默认值,则自带一个初始化的默认值

  • 基本数据类型:byte short int long。默认值 0。float double。默认值 0.0。boolean 默认值 false
  • 引用类型:类、接口、数组、String。默认值 null

JavaBean

private

私有属性修饰符,被 private 修饰的变量,只能内部访问,无法通过 new 实例进行访问。
在 js 中,private String name 就相当于 #name。(js class 中,# 前缀的属性只能在 class 内部访问)。
私有属性想要外部访问/赋值,需要提供一个 getter/setter 方法,来确保数据的安全性。

// HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        Person person = new Person();
        // person.name  // 'name' has private access in 'com.jsonq.demo1.Person'
        // private 修饰的,如果想要外部访问,就必须提供相应的 getter 方法
        System.out.println(person.getName());
    }
}

// Person.java
public class Person {
    private String name;  // 私有成员变量,无法通过外部访问
    private Integer age;

    public String getName() {
        return name;  // 私有属性必须通过提供 getter 和 setter 来获取和赋值
    }
    public void getName(String name) {
        this.name = name;
    }
}

就近原则和 this 关键字

class 类中和 方法中,字段名重复的情况下,谁近就取谁
this 用于区分 成员变量局部变量

public class Person {
    private int age;  // 成员变量

    public void test() {
        int age = 20;  // 局部变量
        System.out.println(age);  // 20 就近原则
        System.out.println(this.age);  // 0 this 直接指向的是 class 中的成员变量
    }
}

构造方法

分为 空参构造方法有参构造方法

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

    // 空参构造器,如果没有写,会默认自动创建
    public Person() {

    }

    // 有参构造器
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

// 使用
new Person()  // 无参构造器,name 和 age 是默认初始值
new Person("张三", 20)  // 有参构造器,初始化时直接赋参数

注意: 一旦使用了有参构造器,不会再自动创建无参构造器,必须手动声明

  • 存在 有参构造器,就必须存在 无参构造器
  • 没有 有参构造器无参构造器 写不写都可以

推荐在使用时,有参构造和无参构造,都手动声明一下。

标准 JavaBean 类

JavaBean 是一个标准类的统称,常见情况下,结合的就是 private this 和构造方法来组成的 JavaBean

一个标准的 JavaBean 类,大约有如下几个特点:

  • 类名见名知意
  • 成员变量使用 private 修饰
  • 提供至少两个构造方法
    • 无参构造方法
    • 全参(全部参数)构造方法
  • 成员方法
    • 每个 private 修饰的成员变量,需要提供对应的 getset 方法
    • 若有其它行为,同样需要写上
public class Person {
    private String account;
    private String password;
    private Integer age;

    // 空参构造
    public Person() {
    }

    public Person(String account, String password, Integer age) {
        this.account = account;
        this.password = password;
        this.age = age;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    // ...
}

IDEA 快速生成 getset 方法快捷键:Alt + Ins 或者 Alt + Fn + Ins
如果 Fn 是用来切换的快捷键方式的,记得调整 Fn,让其处于普通快捷键模式。

image

快捷键提示中的 Constructor 可以快速生成 无参/有参/全参构造器,点击 Select None 生成的就是无参构造。
也可以单独生成 get 方法Getter,和 set 方法 Setter

数据类型

基本数据类型

  • 整数
  • 浮点数
  • 布尔
  • 字符

基本数据类型存储再 栈内存 中,栈内存 中存储的都是真实的数据值。

引用类型

除了基本类型,剩下的都是引用类型,对 js 的数据类型熟悉的会很容易理解。

引用数据类型存储在 堆内存 中,比如 Student s = new Student()s 本身只是记录一个地址值(指针),真正的数据存储在 堆内存 中,访问 s 时,会根据指针找到 堆内存 中真正的数据值

image

  • 基本数据类型
    • 数据值存储在自己空间(栈)
    • 赋值给其它变量时,赋值的是真实值
  • 引用数据类型
    • 数据值存储在其它空间(堆)
    • 自己空间存储的是地址值
    • 赋值给其它变量时,赋值的时地址值

成员变量和局部变量

image

字符串

创建方式

两种创建方式

  • 直接赋值 String name="张三",一般用这种较多
  • new 创建实例 String name = new String("张三");
    • 如果 new String() 时不传任何值,创建的结果值可以理解为 "" 空字符串
    • 传入 char 类型创建字符串(仅了解)
      image
    • 传入 byte 类型创建字符串(仅了解)
      image

比较

==

== 号,基本数据类型比较的是值,而引用数据类型比较的是地址值

String a = "张三";
String b = "张三";
System.out.println(a == b);  // true 比较值

String c = new String("李四");
String d = new String("李四");
System.out.println(c == d);  // false 比较地址值

equals 和 equalsIgnoreCase

开发过程中,字符串比较绝大部分使用的都是 equals

  • equals 比较两个字符串的值是否完全一样,完全一样 true,否则 false
  • equalsIgnoreCase 语义上就可以看出,比较两个字符串的值是否一样(但是忽略大小写)
String a = new String("张三");
String b = new String("张三");
System.out.println(a.equals(b));  // true

String c = "AAA";
String d = "aAa";
System.out.println(c.equalsIgnoreCase(d)); // true

其它常用方法

获取字符串长度和根据索引访问字符串某一个字符

字符串变量并取对应的索引

public static void main(String[] args) {
    String str = "张三zhangsan good";

    for (int i = 0; i < str.length(); i++) {
        char c = str.charAt(i);  // 依次取字符串对应索引的字符
        System.out.println(c);
    }
}

image

str.length() 取到字符串的长度,str.chartAt(i) 取到字符串中第 i 项的字符。

字符串截取substring和替换replace

substring 有两种传入方式

substring(3):将字符串截掉前3位字符,返回剩余的字符
substring(1,3):将字符串截取索引第1项到第3项(不包含第3项),返回截取出的字符,即截取出 2 个字符

String str = "张三zhangsan good";
System.out.println(str.substring(3));  // hangsan good
System.out.println(str.substring(1, 3));  // 三z

replace 常用的就是一种

replace("CNM","***") 会将字符串中所有的 CNM 替换成 ***

String str = "别玩了,CNM,垃圾";
System.out.println(str.replace("CNM", "***"));  // 别玩了,***,垃圾

字符串拼接 StringBuilder

str = str + "abc" 这种普通的字符串拼接缺陷是很大的,执行速度极慢

public static void main(String[] args) {
    String str = "张三";
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++) {
        str = str + "abc";
    }
    long endTime = System.currentTimeMillis();
    System.out.printf("执行时长:%d 毫秒.", (endTime - startTime));  // 执行时长:3100 毫秒.
}

使用 StringBuilder 执行

public static void main(String[] args) {
    StringBuilder str = new StringBuilder("张三");
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++) {
        str.append("abc");
    }
    long endTime = System.currentTimeMillis();
    // 计算执行时间
    System.out.printf("执行时长:%d 毫秒.", (endTime - startTime));  // 执行时长:16 毫秒.
}

使用 str = str + "abc" 的方式循环拼接,执行时间是 3100 毫秒(3.1s),而使用 StringBuilder 则仅仅耗时 16 毫秒(0.016 s),高低立判。

常用操作

StringBuilder 还提供了很多常用的字符串操作方法

append 拼接字符串

repeat 重复拼接的次数(循环拼接的次数)

比如刚才的 for 循环 append 添加字符串就可以简写

image

reverse 反转字符串

跟 js 的反转字符串效果一致。

StringBuilder str = new StringBuilder("张三abc");
System.out.println(str.reverse());  // cba三张

length 获取字符串长度

效果和普通字符串的 length 一样 sb.length()

toString 将 StringBuilder 转为 String

StringJoiner(JDK8 开始支持)

StringBuilder 补足了字符串常规拼接性能上的缺点,而 StringBuilder 本身在进行自定义字符串拼接时就比较麻烦,此时就由 StringJoiner 进行补足。

比如一个数组 {1, 2, 3, 4},想要将其转换为字符串 [1, 2, 3, 4] 的格式,两者的使用如下

image

创建方式

StringJoiner 由两种传参方式

  • StringJoiner(间隔符号),指定数组转字符串时,各项之间的拼接符号
  • StringJoiner(间隔符号, 起始符号, 结束符号)

成员方法

常用方法和 StringBuilder 一致

  • addStringBuilderappend 效果一致
  • lengthStringBuilderlength 效果一致
  • toStringStringBuildertoString 效果一致

集合 ArrayList

ArrayList 和 普通数组的区别是:

  • 普通数组直接打印出来的是地址值,
    image
  • 普通数组的长度是不可变的,ArrayList 数据长度可任意扩充缩减
    image
  • 普通数组是可以存储基本类型和引用类型,ArrayList 不能直接存储基本类型,需要借助包装类
    image

增删改查

  • add(Entity entity) 添加元素
  • remove(Entity entity) 删除指定元素
  • remove(int index) 删除指定索引的元素
  • set(int index, Eneity entity) 修改指定索引下的元素
  • get(int index) 获取指定索引的元素
  • size() 获取集合的长度(集合中元素个数)
ArrayList<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");

list.remove("张三"); // 删除 ”张三“
System.out.println(list);  // [李四, 王五, 赵六]

list.remove(2); // 删除索引为 2: ”赵六“
System.out.println(list);  // [李四, 王五]

list.set(0,"张四");  // 设置第 0 项 “李四” -> “张四”
System.out.println(list);  // [张四, 王五]

String s = list.get(1);// 获取第 1 项 ”王五“

int size = list.size();  // 长度 2

包装类

  • byte -> Byte
  • short -> Short
  • char -> Character
  • int -> Integer
  • long -> Long
  • float -> Float
  • double -> Double
  • boolean -> Boolean

面向对象进阶

[修饰符] static

static 修饰的成员变量,会在堆内存中单独开辟一块静态区域

  • static 修饰的被所有对象共享
  • static 修饰的,不属于对象,属于类
  • 随着类的加载而加载,优先于对象存在

static 静态变量

正常情况下,一个 class 每次被 new,都会创建一个新的实例,每个实例的内部熟悉和方法都是初始的。
class 内部,被 static 修饰的变量,只需要被赋值一次,后续 new 的实例都会共享该数据

image

  • static 修饰的变量,可以被赋值一次,后续所有的 new 实例都可以共享该数据
  • static 修饰的变量,如果在 class 内部访问,可以通过 class.xxx 或者 xxx 直接访问
  • static 修饰的变量,如果在其它 class 中访问和赋值,通过 class.xxx 进行赋值和访问,也可以 new 一个实例,通过 实例.xxx 赋值。

static 静态方法

static 静态方法常用于工具类的封装,工具类大致有以下几个特点:

  • 可在内部提供一个私有构造器,防止外部对象调用时实例化。
  • 内部对外放开的方法,大部分都是用 public static 修饰,
  • 内部私有的方法,则用 private 修饰

工具类例子如下:

public class ArrayUtil {

    //私有化构造方法,目的时为了不让外部创建其对象
    private ArrayUtil() {
    }

    public static String printArr(int[] arr) {
        StringJoiner joiner = new StringJoiner(",", "[", "]");
        for (int i = 0; i < arr.length; i++) {
            joiner.add(arr[i] + "");
        }
        return joiner.toString();
    }
}

使用时,直接调用 ArrayUtil.printArr(入参) 就可以。

static 注意事项

  • 静态方法中,只能访问静态变量和静态方法
  • 非静态方法中可以访问所有
  • 静态方法中没有 this 关键字

image

其中 this 指向的就是当前调用改方法的对象,a.test(),那 this 指向的就是 a,即谁调用,this 指向谁,这点和 js 是一样的

封装

对象代表什么,就封装对应的数据,并提供对应的行为,这是一个设计思想。
就比如使用 private static final 等修饰符来对常用的变量和方法进行提取封装。

继承 extends

类与类之间存在重复共有的属性和方法,就能提取到一个公共类中,可以通过 extends 来继承公共类,和 js es6 中的 class 使用上一样。
一个类只能 extends 一个类。不能 public A extends B extends C

image

子类继承父类的规则

  • 成员变量,不管是否私有,子类都可以继承
  • 成员方法,父类私有的成员方法(private 修饰),子类无法继承
  • 构造方法,子类无法继承

image

继承成员变量的特点

变量访问特点
  • 成员变量的访问会逐层向上找,就近取值

image

  • 继承中,如果父类中有 name 属性,子类中也有 name 属性,但是类型不一样,则以子类的为准
重名访问特点

重名时,访问成员变量是有 3 种情况的
注:super 甚至可以理解为父类,super() 父类的空参构造,super(...args) 父类的全参构造,super.xx 父类的成员方法和变量

System.out.println(name);  // 从局部位置向上查找
System.out.println(this.name);  // 从本类成员位置向上查找
System.out.println(super.name);  // 从父类成员位置向上查找

总结:

成员变量就近取值,成员变量重名则优先子类

继承中成员方法的特点

重名访问特点

image

  • 方法重写
    当父类种有一个成员方法,子类继承父类时,若父类方法不能满足需求,可对父类方法进行重写。
  • 书写格式
    子类中出现和父类种一摸一样的方法声明,那子类的这个方法就是对父类方法的重写
  • @Override
    建议所有重写后的方法都加上 @Override,以保证重写方法的安全性(@Override可以检查重写的方法是否有语法错误,与父类方法格式不一致会标注报错)

image

继承的构造方法和this、super关键字

this 是本类方法调用者的地址,super 是父类的存储空间

  • 父类的构造方法不会被子类继承
  • 子类所有的构造方法会先访问父类的构造方法 再执行自己。
  • 子类构造方法执行时,必须会执行 super() 就算不写也存在,且必须在第一行
  • 如果要调用父类的构造方法,必须手动写 super 进行调用。

比如子类 Student 没有有参构造,但是可以通过使用 Person 父类的有参构造来创建对象

image

其输出结果是

image

如果是 Student 空参构造,则会先执行父类的空参构造函数,然后执行子类的空参构造

image

多态

多态的应用场景:

当拥有多种身份的人,需要多种不同的注册方法时,图中的写法是最笨重的写法,也可以由此引申出多态。

image

让多种身份的人的类都继承一个基类 Person,则所有的子类都可以被 Person 类所接受。

image

多态调用成员变量和方法的特点

  • 变量调用:编译看左边,运行也看左边
  • 方法调用:编译看左边,运行看右边

准备三个类,Animal 为父类,Cat Dog 为子类分别继承 Animal

image

编译看左边:

image

编译报错,就是 IDEA 报错,IDEA 没报错,运行后报的错是运行时错误

运行:变量运行看左边,方法运行看右边:

    public static void main(String[] args) {
        Animal animal = new Cat();
        System.out.println(animal.name);  // 动物
        animal.say();  // 猫 say
    }

变量看左边:animal.name 访问的是成员变量,左侧是 Animal,跟右侧无关,调用的是 Animal 中的 name。运行的结果就是 【动物】,而非 【猫】
方法看右边:animal.say() 访问的是成员方法,右边是 Cat,跟左侧无关,调用的是 Cat 中的 say 方法。则运行的结果就是 【猫 say】,而非 【动物 say】

多态的优点和弊端

  • 优势:
    • 父类型作为参数,可接受所有的子类对象
  • 弊端:
    • 不能使用子类的独有功能(比如父类中没有 say 方法,但是子类中有,也不能使用 say 方法)
  • 弊端解决方法
    • 强制类型转换。一般都会做强制类型转换,从而将父类转换成真正的子类类型,从而调用子类中的独有功能。
    • 转换时可以用 instanceof 进行判断,根据传入的子类,将父类转成对应的子类,就可以调用子类独有功能。
public void callback(Person p) {
    if (p instanceof Cat) {
        Cat cat = (Cat)p;
        cat.eat();
    }
}

package 包 和 final 关键字

package 包:可以理解为前端的 import require
package 包跟前端 importrequire 类似,引入别的文件才能使用这个文件中的功能

image

final:可以理解为 js 中的 const
基本数据类型修饰的不能更改,引用数据类型的可以更改内部属性,但地址值不能变

image

权限修饰符

image

名词说明:

  • 同一个类:同一个 class 内部
  • 同一个包的其他类:com.jsonq 下的 Cat 类,com.jsonq 下的 Dog 类,在一个 package 下。
  • 不同包下的子类:com.acom.b 就是不同包,子类就是 extends 继承了此类
  • 不同包下的无关类:不同的 package 包,也没有任何关系

实际开发中,基本只用 privatepublic 修饰符

抽象类抽象方法 abstract

不确定业务的具体实现,但是确定有这个业务,且该业务用于很多类中,可以使用抽象类和抽象方法。
抽象方法只声明业务方法,但是具体的业务逻辑需要子类去重写。
子类继承抽象类,则必须重写抽象类中的抽象方法(强制)。

基本抽象类、抽象方法示例:

public abstract class Person {
    // 不写 {},直接分号结束,逻辑交给子类实现
    public abstract void say();
}

// 子类继承抽象父类
public class Student extends Person {
    @Override
    public void say() {
        System.out.println("Student say");
    }
}
  • 抽象类不能实例化,比如 Person 是抽象类,则不能 new Person
  • 抽象类中不一定存在抽象方法,比如 public abstract Person{...},通过 abstract 修饰成抽象类,但是 class 内没有 abstract 修饰的方法
  • 有抽象方法的类一定是抽象类,class 中有抽象方法,class 就必须使用 abstract 修饰
  • 子类 extends 继承抽象类
    • @Override 标注重写抽象类中的所有抽象方法(常规用法)
    • 子类也标识为抽象类(不推荐)

接口 implements

接口就是一种规则。implements 有点像 extendsabstract 抽象类的结合。
implements 可以实现多个类,且实现时必须重写内部所有 abstract 修饰的方法。

image

接口的成员特点

implements 可以多实现,比如 public class A implements C,D,E
其中 extends 是单继承,implements 可以是多实现,这点和 js 中的 extends 和 ts 中的 implements 是类似的。

在 interface 接口中成员特点:

  • 成员变量
    • 只能是常量
    • 默认修饰符 public static final
    • String name = "张三"; 等价于 public static final String name = "张三";
  • 构造方法
    • 没有
  • 成员方法
    • 只能是抽象方法
    • 默认修饰符 public abstract
    • void swim(); 等价于 public abstract void swim();

接口扩展

以下都是 JDK8 开始支持的写法。

  • 接口中的方法默认是 abstract 抽象方法,子类必须重写,如果不让子类强制重写,可以添加 default
    • 例如:defailt void eat()public 可以省略,这样子类也可以不重写该方法。
    • 当实现的多个接口中的方法重名时,必须重写该方法
  • static 静态方法
    • 接口中使用 static 修饰的方法,只能通过接口名调用,不能使用类名或对象名调用
    • 例如:static void eat(),只能通过该 interface 的接口名.eat() 调用,其继承和实现的都不能调用。

常用 API

Math

和 js 的 Math 方法大致差不多,js 中常用的,诸如 Math.ceil、Math.floor、Math.random、Math.max 等等都是类似的

Object 和 Objects

  • Object 是 Java 中的顶级父类。所有的类都直接或间接继承 Object
  • Object 类中的方法可以被所有子类访问。

image

Object

toString

  • js 中的对象 toString 方法,不论什么对象,都会转成 [object Object]
  • Java 中 Object 的 toString 方法,打印出的是:包名 + 地址值。

如果想要 toString 时看到数据的详细内容,而不是地址值,就需要重写 class 类的 toString 方法

public class Person {

    private String name;

    // ....

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

alt + insert 快捷键可以直接生成 toString 方法,此时再调用 toString() 打印出来的就是详细数据

equals

比较两个对象是否相等(对比的是地址值)

Person p1 = new Person("张三");
Person p2 = new Person("张三");

boolean isSame = p1.equals(p2);
System.out.println(isSame);  // false

equals 对比的是两个对象的地址值是否相等,即是否为同一个对象,但实际场景中,对比的往往是两个对象的数据值,此时就需要重写 equals 方法。
快捷键 alt + insert,选择 equals and hashCode,然后一路回车就行,可以生成以下代码。

image

此时再次允许上边 p1.equals(p2) 的代码,得到的结果就是 true

clone

  1. 重写 clone 方法(clone 方法是由 protected 修饰,只允许在本包及其子类下使用,因此只能重写)
    image

  2. 实现 Clonable 接口
    image

此时就实现了 clone 方法,在使用时需要抛出一个异常

    public void Test throws CloneNotSupportedException {
        Person p1 = new Person("张三","18");
        Person p2 = (Person)p1.clone();

        System.out.println(p1); // Person{name='张三', age='18'}
        System.out.println(p2); // Person{name='张三', age='18'}
    }

注意: 该方式是属于浅克隆,对象中基本数据类型没问题,但是 clone 引用数据类型,其指向的是同一个地址值。该实现只能克隆简单的对象,含有引用数据类型的对象不适合

image

深克隆需要自己重写方法或者使用第三方库。

Objects

image

BigInteger 和 BigDecimal

BigInter

字面意思:大整数

image

  • 其取值范围比 Long 还要广,当 Long 不足以存储和表示数据时,使用 BigInteger
  • 同时,在进行大整数的加减法时,可以有效的避免精度丢失的问题

BigDecimal

大小数、

image

  • BigDecimal 在解决浮点数/小数等运算的精度丢失上,是比较特殊的
  • new BigDecimal(0.01) 这种表示方式,得到的不是 0.01,而是精确了很多位的浮点数
  • new BigDecimal("0.01"),传递字符串,最后得到的才是精确的数据值

正则表达式

和 js 的正则表达式规则基本一样,只是写法略微不同,Java 中的正则是纯字符串,也没有 RegExp

image

System.out.println("a".matches("[abc]")); // true

时间

JDK7 及之前的时间类

Date 时间

和 js 的 Date 用法相似,空参构造就是当前时间,传入时间戳就生成国际时间,获取时间戳 getTime(),设置时间戳 setTime(Long l)

注意:Date 类虽然支持传入年月日时分秒来创建时间对象,但是此方式已被 Calendar 代替,所以参数普遍只有空参和 long 类型时间戳

image

SimpleDateFormat 格式化时间

  • 把时间转成指定的格式,跟 moment dayjs 的基础功能一样
  • 把字符串表示的时间转成 Date 对象

image

时间格式转换:年:yyyy 月:MM 日:dd 时分秒:HH mm ss 周几:EE 时间段(上下午):a

image

时间格式解析:字符串转 Date 对象,new 的 SimpleDateFormat 格式必须和转换的字符串格式一致

image

Calendar 日历

Calendar 可以说是 Date 的补足,来确定年月日,星期几这些。

Calendar 是一个抽象类,不能直接创建,必须使用方法 getInstance() 来获取当前时间的 Calendar 对象

image

Calendar 基本使用

image

月份是由 0~11,而不是 1~12 组成的
星期几:星期日是 1,即一周中的第一天

JDK8 时间

image

Instant ZonedDateTime

这两个基本不用,生成时间常用 LocalDateTime

Instant 创建的时间是不带时区的,想要正确的时区需要 +8 小时,或者使用 atZone 指定时区,就是转成了 ZonedDateTime 对象。

ZonedDateTime 创建的时间是带时区的,用法和 Instant 基本一致

基本使用

image

DateTimeFormatter

ofPattern 用来指定格式化的字符串格式。parse 解析,但是基本不用该方法解析

基本使用

image

LocalDate LocalTime LocalDateTime

  • now() 获取当前时间
  • of(xxx) 获取指定时间的对象
  • get 开头的方法 获取年月日时分秒等
  • isBefore isAfter 对比两个 LocalDate LocalDateTime

LocalDate 有单独的 isLeapYear 获取当前年是否是闰年,返回 true 是闰年

of 获取指定的时间,LocalDateLocalTime 同理

image

LocalDateTime 获取年月日等

image

Arrays

image

基本使用

image

copyOfcopyOfRange 对于基本类型是深拷贝,对于复杂类型是浅拷贝,内部对象还是指向原来的地址。

sort 排序使用

image

sort 的排序规则和 js 的 sort 用法相似

Lambda 表达式

Lambda 是从 JDK8 开始支持的写法(函数式编程),可以将其理解为 js 中的箭头函数(用法有点相似)

初体验 Lambda 表达式

image

  • 参数类型类型也可以省略不写,比如 (Integer o1, Integer o2) 可以简写为 (o1, o2)
  • 如果参数只有一个,小括号也可以省略不写
  • 如果代码只有一行,函数体括号 {} 关键字 return 结尾分号 ; 可以省略不写,和 js 的简写规则一样

Lambda 表达式必须是一个 interface 接口,且必须有且仅有一个抽象方法才能使用 Lambda 表达式,可以在接口上添加 @FunctionalInterface 注解验证是否可以简写为 Lamda 表达式。

image