从零开始的Java学习生活

从零开始的Java学习生活

在这里插入图片描述

一、Java基础语法

1.1 注释

注释是在程序指定位置添加的说明性信息,即对代码的一种解释

注释分类:

  • 单行注释
// System.out.println();
  • 多行注释
/*
	System.out.println();
*/
  • 文档注释
/**
	System.out.println();
*/

注释内容不会参与编译和运行

1.2 关键字

被Java赋予了特定含义的英文单词,不可用于常量、变量和任何标识符的名称

1.3 字面量

数据在程序中的书写格式

  • 字符串 “Hello” 必须加双引号
  • 字符 ‘A’ 必须加单引号,且只能有一个字符
    • 有的特殊字符无法直接表示,需要通过转义字符表示
    • 例如:换行符(enter) 制表符(Tab)
    • 换行符(enter)‘\n’
    • 制表符 (Tab) ‘\t’
    • 转义字符 \ 和其后面的第一个字母构成特定的含义
  • 布尔值 true false

1.4 变量

内存中的存储空间,控件中存储着经常发生变化的数据

  • 定义:数据类型 变量名 = 数据值;

  • int age = 22;
    int id;
    

tips:

  • 变量名不允许重复定义
  • 一条语句可以定义多个变量
  • 变量在使用之前一定要进行赋值
  • 要注意变量的作用域范围

1.5 标识符

给类、方法、变量等起名字的符号

命名规则:

  • 数字字母下划线(_)、**美元符($)**组成
  • 不能以数字开头
  • 不能是关键字
  • 区分大小写

命名规范:

  • 小驼峰命名法:变量
    • 标识符为一个单词时,所有字母小写
    • name
    • 标识符为多个单词时,第一个单词首字母小写,其他大写
    • firstName
  • 大驼峰命名法:
    • 标识符为一个单词时,首字母大写
    • Student
    • 标识符为多个单词时,每个单词的首字母都大写
    • GoodStudent

1.6 数据类型

基本数据类型

数据类型关键字取值范围内存占用默认值
整数byte-128~12710
short-32768~3276720
int (默认)-231~231-140
long-263~263-180L
浮点数float单精度 32位40.0f
double(默认)双精度 64位80.0d
字符char0~655352
布尔booleantrue,false1false

引用数据类型

  1. 数组
  2. 接口

1.7 Scanner 键盘输入

Scanner sc = new Scanner(System.in);
int age = sc.nextInt();
String name = sc.next();
/*
	nextLine()方法返回的是Enter键之前的所有字符,它是可以得到带空格的字符串的。
	next()会自动消去有效字符前的空格,只返回输入的字符,不能得到带空格的字符串。
*/

二、运算符

2.1 算数运算符

运算符:对字面量或者变量进行操作的符号

表达式:用运算符把字面量或者变量连接起来符合Java语法的狮子就可以称为表达式

int a = 12;
int b = 20;
int c = a + b;

+ - * / %(取余)

Tips:

  • 除法
    • 如果两侧是整数,结果是整数
    • 如果有一侧是浮点数,结果是浮点数
    • 对精度要求比较高的场合不要使用基本数据类型进行运算

2.2 自增自减运算符

++ 自增

– 自减

使用方式:

  • 单独使用

    • ++ – 无论是放在变量前还是变量后,结果一样
  • 参与操作使用

    • 如果放在变量的后面,先拿变量的值进行运算,再对变量的值进行+1、-1 ,先运算再加减、

      int a = 10;

      int b = a++;

      — b = 10; a = 11;

    • 如果放在变量的前面,先加减再进行运算

      int a = 10;

      int b = ++a;

      — b = 11; a = 11;

    • ++、-- 只能操作变量,不能操作常量

2.3 类型转换

  • 隐式转换
    • 把一个取值范围小的数值或者变量,赋值给另一个取值范围大的变量
    • 取值范围小的数据,和取值范围大的数据进行运算,小的会先提升称为大的之后,再进行运算
    • byte short char 三种数据在算术运算的时候,都会提升为int,再进行运算
    • 当把任何基本类型的值和字符串值进行连接运算时(+),基本类型的值将自动转化为字符串类型(基本数据类型转换成字符串)。
byte a = 10;
byte b = 20;
byte c = (byte)(a + b);
  • 强制转换
    • 把一个取值范围很大的数值或者变量,赋值给另一个取值范围小的变量,不允许直接赋值,需要进行强制转换
    • 格式:目标数据类型 变量名 = ( 目标数据类型 ) 被强制转换的数据;
double a = 8.88;
int b = (int) a;
//输出:	8

2.4 赋值运算符

符号作用说明
=赋值a = 10; 将10赋值给变量a
+=加后赋值a += b; 将 a + b 的和赋给 a
-=减后赋值a -= b; 将 a - b 的差赋给 a
*=乘后赋值a *= b; 将 a * b 的积赋给 a
/=除后赋值a /= b; 将 a / b 的商赋给 a
%=取余后赋值a %= b; 将 a % b 的余数赋给 a

2.5 关系运算符

符号说明
==a == b,判断a和b的值是否相等,成立为true,不成立为false
!=a != b,判断a和b的值是否不相等,成立为true,不成立为false
>a > b,判断a是否大于b,成立为true,不成立为false
>=a >= b,判断a是否大于等于b,成立为true,不成立为false
<a < b,判断a是否小于b,成立为true,不成立为false
<=a <= b,判断a是否小于等于b,成立为true,不成立为false

关系运算符的结果都是 boolean 类型,要么是true要么是false

2.6 逻辑运算符

连接布尔类型的表达式,或者是值

整合多个条件,为一段整体的逻辑

符号介绍说明
&逻辑与并且,有false则false
|逻辑或或者,有true则true
逻辑非取反
^逻辑异或二者相同为true,不同为false
&&短路与作用与 & 相同,但是有短路效果
||短路或作用与 | 相同,但是有短路效果
  • 逻辑与 & ,无论左边是什么, 右边都要执行

    短路与&&,只要左边为false,总体为false,不再执行右边

  • 逻辑或 |,无论左边是什么,右边都要执行

    短路或 ||,只要左边为true,总体为true,不再执行右边

2.7 三元运算符

格式:判断条件 表达式1 : 表达式2;

执行流程:

  • 计算判断条件
    • 若为 true,结果返回表达式1的值
    • 若为false,结果返回表达式2的值

总结:根据判断条件,从两份数据中二者选其一,正确选左边,错误选右边

2.8 运算符优先级

在这里插入图片描述


三、方法

3.1 方法介绍

方法:一段具有特定功能的代码块

  • 按照功能进行分类
  • 提高代码复用性

相同功能的代码进行抽取,在需要的场合去使用 – 方法/函数

3.2 定义及调用

定义格式:

修饰符] void 方法名() {
    //方法体
}
[修饰符] 返回值类型 方法名(参数类型 参数名1, 参数类型 参数名2) {
    //方法体
    return 返回值;
}
/*
1.修饰符可以省略,目前统一写 public static 
2.返回值类型本质是数据类型(基本数据类型、引用数据类型)
3.方法名本质是起名字,要符合标识符的命名规范
4.参数列表 - 格式:
		数据类型1 参数名1, 数据类型2 参数名2, 数据类型3 参数名3
			*方法接收几个参数,就要定义几个参数
			*最后一个参数的后面没有" , "
			*如果方法没有输入,只写小括号,小括号里没有东西
			*数据类型用来限制输入的参数类型
			*参数名 - 要符合标志符的命名规范
5.{}中的内容是方法体,方法完成的功能都要写在方法体中
6.*“return 返回值;” 表示方法返回给外界的内容
  *“返回值” 要和 “返回值类型”兼容
  *方法可以没有返回值,此时“返回值类型要写成void”
  	*不写return语句
  	*return;
7.方法要定义在类中,目前要和main平级

首先要确定返回值类型、方法名、参数列表

 */

、 用格式

方法名();
方法名(参数1, 参数2);
  • 形参:形式参数,定义方法时,所声明的参数
  • 实参:实际参数,调用方法时,所传入的参数

Tips:

  • 方法不调用就不执行
  • 方法与方法之间是平级关系,不能嵌套定义
  • 方法的编写顺序和执行顺序无关
  • 方法的返回值类型为void,表示该方法没有返回值,没有返回值的方法可以省略return语句不写如果要编写return,后面不能跟具体的数据
  • return语句下面,不能编写代码,因为永远执行不到,属于无效的代码

3.3 方法重载

在同一个类中,定义了多个同名的方法,但每个方法具有不同的参数类型参数个数

Tips:

  • 识别方法之间是否是重载关系,只看方法名和参数,跟返回值无关
  • 在调用时,JVM通过参数列表的不同来区分同名方法
public class MethodDemo {
	public static void fn(int a, double b) {
		//方法体
	}
	public static void fn(double a, int b) {
		//方法体
	}
}
/*
	1.相同 - 同一个类中,方法名相同
	2.不同 - 参数列表不同
		1)个数不同
		2)参数类型不同
		3)顺序不同
		4)和参数的名字没有关系
	3.方法重载和返回值类型没有关系                         
*/
public class MethodDemo {
	public static int fn(int a, int b) {
		return a + b;
	}
	public static int fn(int a, int b, int c) {
		return fn(a, b) + c;
	}
}
/*
	1.方法可以根据需要调用多次 
	2.自定义的方法可以调用其他方法(自定义方法、官方提供的方法)
	3.调用方法之后,运行的流程就从目前的流程脱离,进入被调用的方法,被调用的方法运行结束之后,会将结果返回给调用者,调用者继续执行后续流程
*/

方法递归 – 方法调用方法自身

合法的递归调用

1. 方法调用方法自身
1. 要有明确的退出条件
1. 如果没有明确的推出条件会出现栈溢出,

四、控制流程

4.1 流程控制语句

通过一些语句,来控制程序的执行流程

  • 顺序结构
  • 分支结构
  • 循环结构

4.2 顺序结构

Java程序默认的执行流程,没有特定的语法结构,按照代码的先后顺序执行

public class Test {
	public static void main(String[] args) {
		System.out.println("A");
		System.out.println("B");
		System.out.println("C");
	}
}

4.3 分支结构

代码要执行具体的逻辑运算进行判断,逻辑运算的结果有两个或多个,所以产生选择,按照不同的选择执行不同的代码

4.3.1 if 语句
  1. if

格式:

if (判断条件) {
	语句体;
}

执行流程:

  1. 首先计算判断条件的结果

  2. 如果条件的结果为true就执行语句体

  3. 如果条件的结果为false就不执行语句体

  4. if… else…

格式:

if (判断条件) {
	语句体1;
} else {
    语句体2;
}

执行流程:

  1. 首先计算判断条件的结果

  2. 如果条件的结果为true就执行语句体1

  3. 如果条件的结果为false就执行语句体2

  4. if… else if… else if… else

格式:

if (判断条件1) {
    语句体1;
} else if (判断条件2) {
    语句体2;
}
...
else {
    语句体n;
}

执行流程:

  1. 首先计算判断条件1的值
  2. 如果值为true就执行语句体1;如果值为false就计算判断条件2的值
  3. 如果值为true就执行语句体2;如果值为false就计算判断条件3的值
  4. 如果没有任何判断条件为true,就执行语句体n。

Tips:

  • if语句中,如果大括号控制的是一条语句,大括号可以省略不写
  • if语句的( )和{ }之间不要写分号
4.3.2 switch 语句

格式:

switch (表达式) { 
    case1:
        语句体1;
		break;
    case2:
        语句体2;
	break;default:
        语句体n+1;
}

格式说明:

  • 表达式:(将要匹配的值)取值为byte、short、int、char 在JDK5以后可以是枚举,JDK7以后可以是String
  • case:后面跟的是要和表达式进行比较的值(被匹配的值)
  • break:表示中断,结束的意思,用来结束switch语句。
  • default:表示所有情况都不匹配的时候,就执行该处的内容,和if语句的else相似

执行流程:

  1. 拿着表达式的值,依次和case后面的值进行匹配,匹配成功,就会执行对应的语句,在执行的过程中,遇到break就会结束
  2. 如果所有的case都不匹配,就会执行default里面的语句体,然后程序结束掉

switch穿透:

现象:当开始case穿透,后续的case就不会具有匹配效果,内部的语句都会执行直到看见break,或者将整体switch语句执行完毕,才会结束

原因:由于某个case语句中缺少或者漏写break语句所导致的结果

int input = sc.nextInt();
		switch (input) {
			case 1:
				System.out.println("输入为:" + 1);
				//break;
			case 2:
				System.out.println("输入为:" + 2);
			case 3:
				System.out.println("输入为:" + 3);
			case 4:
				System.out.println("输入为:" + 4);
				break;
			default:
				System.out.println("输入错误");
		}
//当input为1时,输出结果为1 2 3 4 ,运行直至遇到case 4中的break;才停止

Tips:

  • case给出的值不允许重复
  • case后面的值只能是常量,不能是变量
  • default后面可以不加break;

4.4 循环语句

在某些条件满足的情况下,反复运行某段代码

4.4.1 for 语句

格式:

for(初始化语句; 条件语句; 控制语句) {
    //循环体语句
}

执行流程:

  1. 执行初始化语句

  2. 执行条件语句,看其结果是true还是false

    • 如果是false,循环结束
    • 如果是true,继续执行
  3. 执行循环体语句

  4. 执行控制语句

  5. 回到2继续

流程图:

在这里插入图片描述

Tips:

  • 循环{ }中定义的变量,在每一轮循环结束后,都会从内存中释放
  • 循环( )中定义的变量,在整个循环结束后,都会从内存中释放
  • 循环语句( )和{ }之间不要写分号

在这里插入图片描述

循环嵌套

​ 在循环语句中,继续出现循环语句

for (int i = 1; i <= 9; i++) {
    for (int j = 1; j <= i; j++) {
		System.out.print(i + "*" + j + "=" + i * j + "\t");
	}
	System.out.println();
}
4.4.2 while 语句
初始化语句;
while (条件判断语句) {
	循环体语句;
	条件控制语句;
}
4.4.3 do… while 语句
初始化语句;
do {
    循环体语句;
    条件控制语句;
} while (条件判断语句);

Tips:无论条件是否满足,至少执行一次

4.5 三种循环的区别

区别:

  • for 循环 和 while 循环 都是先判断再执行
  • do… while 循环是先执行再判断
  • for 循环中,控制循环的变量,在循环结束之后不能被再次访问
  • while 循环中,控制循环的变量,在循环结束之后可以被再次访问

4.6 跳转控制语句

  • break:终止循环体内容的执行,也就是说结束当前的整个循环
  • continue:跳过某次循环体内容的执行,继续下一次的执行

Tips:

  • break:只能在循环和switch当中进行使用
  • continue:只能在循环中进行使用

4.7 Random 随机数

方法:

Random r = new Random();
int a = r.nextInt(100);
System.out.println(a);

Tips:

r.nextInt(100);中的 100 ,指的是产生0~99的随机数,取不到100


五、数组

5.1 数组

数组是一种容器,是存储同一种数据类型的多个元素的集合

  • 数组既可以存储基本数据类型,也可以存储引用数据类型
  • 数组属于引用数据类型,可以理解为对象(Object),数组中的每个元素相当于该对象的成员变量
  • 数组一旦初始化,长度不可变 数组长度:数组名.length
5.1.1 数组静态初始化

初始化:就是在内存中,为数组容器开辟空间,并将数据存入容器中的过程

数组定义格式:

  • 格式一:
    • int[] array;
  • 格式二:
    • int array[];
	int[] array1;
    int array2[];

完整格式:

数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, 元素3};

int[] array1 = new int[]{11, 22, 33};
double array2[] = new double[]{1.1, 2.2, 3.3};

简化格式:

数据类型[] 数组名 = {元素1, 元素2, 元素3};

int[] array3 = {11, 22, 33};
5.1.2 数组元素访问

格式:数组名[索引];

索引:数组容器中空间的编号,编号从0开始,逐个+1增长

int[] array3 = {11, 22, 33};
System.out.println(array3[0]);
//输出值为 11 
5.1.3 数组遍历访问

数组遍历:将数组中所有的内容取出来,取出来之后进行操作

int[] array3 = {11, 22, 33};
for (int i = 0; i < array3.length; i++) {
    System.out.println(array3[i]);
}

增强for循环(foreach)

 /*
	增强for循环
	for(数据类型 变量 : 数组) {
		循环体;
}
	每次循环,会将数组中的元素依次赋值给变量
*/
for (int item : arr)
	System.out.println(item);

Tips:

  • 只能从头到尾的遍历数组或集合,而不能只遍历部分
  • 在遍历List或数组时,不能获取当前元素下标
5.1.4 数组动态初始化

动态初始化:初始化时只指定数组的长度,由系统为数组分配初始值

  • 格式:数据类型[] 数组名 = new 数据类型[数组长度];
int[] arr = new int[5];

初始值:

  • 基本数据类型

    • byte : 0

    • short : 0

    • int : 0

    • long : 0

    • float : 0.0

    • double : 0.0

    • char : `\u0000`

    • boolean : false

  • 存放引用数据类型(类、接口、数组)的数组,每个元素的初始值为 null

5.1.5 两种初始化的区别
  • 静态初始化:手动指定数组元素,系统根据数组元素个数,计算数组长度
  • 动态初始化:手动指定数组长度,由系统给出默认初始化值
5.1.6 数组常见异常

索引越界异常

  • ArrayIndexOutOfBoundsException
  • 访问了数组中不存在的索引

空指针异常

  • NullPointerException
  • 当引用数据类型变量被赋值为null之后,地址的指向被切断,还继续访问堆内存数据,就会引发空指针异常
int[] arr1 = new int[]{1, 2, 5, 0};
System.out.println(arr1[4]);
int[] arr2 = null;
System.out.println(arr2[0]);
5.1.7 可变长参数

在Java5 中提供了变长参数(varargs),也就是在方法定义中可以使用个数不确定的参数,对于同一方法可以使用不同个数的参数调用

本质就是数组

  • 语法:数据类型… 参数名

在定义方法时,在最后一个形参后加上三点 ,就表示该形参可以接受多个参数值,多个参数值被当成数组传入

public class Varargs {
    public static void test(String... args) {
        for(String arg : args) {
            System.out.println(arg);
        }
    }  
    public static void main(String[] args) {
        test();//0个参数
        test("a");//1个参数
        test("a","b");//多个参数
        test(new String[] {"a", "b", "c"});//直接传递数组
    }
}

Tips:

  • 可变参数只能作为函数的最后一个参数,但其前面可以有也可以没有任何其他参数
  • 由于可变参数必须是最后一个参数,所以一个函数最多只能有一个可变参数
  • Java的可变参数,会被编译器转型为一个数组
  • 变长参数在编译为字节码后,在方法签名中就是以数组形态出现的。这两个方法的签名是一致的,不能作为方法的重载。如果同时出现,是不能编译通过的。可变参数可以兼容数组,反之则不成立
  • 方法重载时,优先匹配固定参数,调用一个被重载的方法时,如果此调用既能够和固定参数的重载方法匹配,也能够与可变长参数的重载方法匹配,则选择固定参数的方法
  • 调用一个被重载的方法时,如果此调用既能够和两个可变长参数的重载方法匹配,则编译出错

5.2 二维数组

一种容器,用于存储一维数组

5.2.1 二维数组静态初始化

格式:

数据类型[][] 数组名 = new 数据类型[][]{{元素1, 元素2}, {元素3, 元素4}, {元素5, 元素6}};

int[][] arr = new int[][]{{11, 22}, {33, 44}};

简化格式:

数据类型[][] 数组名 = {{元素1, 元素2}, {元素1, 元素2}};

int[][] arr1 = {{11, 22}, {33, 44}};
5.2.2 二维数组元素访问

格式: 数组名 [索引][索引];

int[][] arr = new int[][]{{11, 22}, {33, 44}};
arr[1][0];
5.2.3 二维数组元素遍历

思路:

  1. 遍历二维数组,取出里面每一个一维数组
  2. 在遍历的过程中,对每一个一维数组继续完成遍历,获取内部存储的每一个元素
int[][] arr = {{11, 12, 13, 14, 15}, {21, 22, 23, 24}, {31, 32, 33}};
for (int i = 0; i < arr.length; i++) {
	for (int j = 0; j < arr[i].length; j++) {
		System.out.print(arr[i][j] + "\t");
	}
	System.out.println();
}
//foteach
for (int[] i : arr) {
	for (int j : i) {
		System.out.print(j + "\t");
	}
System.out.println();
}
5.2.4 二维数组动态初始化

格式: 数据类型[][] 数组名 = new 数据类型[m][n];

m表示这个二维数组可以存放多少个一维数组,若把二维数组看作一个矩阵,则为矩阵的行数

n表示这个二维数组的某行可以放多少个元素,即为矩阵的列数

int[][] arr = new int[2][3];
//二维数组arr有2个一维数组,每个一维数组可以放3个元素

Tips:

可以在二维数组中存放创建好的一维数组

int[][] arr = new int[2][3];
int[] arr1 = {11, 22, 33};
int[] arr2 = {44, 55, 66};
arr[0] = arr1;
arr[1] = arr2;

六、面向对象

6.1 类和对象

面向对象编程(Object Oriented Programming)

  • 是对一类事物描述,是抽象的、概念上的定义,类指的是一组相关属性和行为的集合,可以将其看作为时一张对象的设计图
  • 对象是实际存在的该类事务的每个个体,因而也称实例(instance)
    • 一切客观存在的事物,万物皆对象
    • 任何对象,一定有自己的特征和行为

类和对象的关系:

  • 依赖关系:
    • Java中要想创建对象,必须要先有类的存在,Java需要根据类创建对象
  • 数量关系:
    • 一个类,可以创建出多个对象

类的组成:

  • 类的本质:对事务进行的描述
  • 属性:在代码中使用成员变量表示,成员变量跟之前定义变量的格式一样,只不过位置发生了变化,类中方法外
  • 方法:在代码中使用成员方法表示,成员方法跟之前定义方法的格式一样,只不过要去掉 static 关键字
public class Student {
    public String name ;
    public int age;
    public void study() {
        System.out.println("学习");
    }
    public void eat() {
        System.out.println("吃饭");
    }
}

创建对象及对象使用格式

  1. 创建对象

    类名 对象名 = new 类名();

  2. 变量使用格式

    对象名.变量名;

  3. 方法使用格式

    对象名.方法名(实际参数);

public class TestStudent {
    public static void main(String[] args) {
        //创建对象
        Student stu = new Student();
        //使用成员变量
        stu.name = "王瑞宁";
        stu.age = 20;
        System.out.println(stu.name);
        System.out.println(stu.age);
        //使用成员方法
        stu.study();
        stu.eat();
    }
}

6.2 成员变量和局部变量

区别成员变量局部变量
定义位置类的内部,方法外面方法中
初始化值有默认初始化值没有,使用之前完成赋值
内存位置堆内存栈内存
生命周期随着对象的创建而存在,随着对象的消失而消失随着方法的调用而存在,随着方法的运行结束而消失
作用区域在自己归属的大括号中在自己归属的大括号中

6.3 构造方法

6.3.1 构造方法概述
  • 构造器

    初始化一个新建的对象

    构建、创造对象的时候,所调用的方法

  • 格式:

    1. 方法名与类名相同,大小也要一致
    2. 没有返回值类型,连void都没有
    3. 没有具体的返回值
  • 执行时机:

    1. 创建对象的时候调用,每创建一次对象,就会执行一次构造方法
    2. 不能手动调用构造方法
6.3.2 构造方法作用
  • 本质作用:创建对象
  • 结合构造方法执行时机:给对象中的属性(成员变量)进行初始化
6.3.3 构造方法注意事项
class Student {
    int age;
    //默认构造方法
    public Student() {
        
    }
    //带参构造方法
    public Student(int age) {
        this.age = 18;
    }
}
  1. 构造方法的创建
    • 如果没有定义构造方法,系统将给出一个默认无参数构造方法
    • 如果定义了构造方法,系统将不再提供默认的构造方法
  2. 构造方法的重载
    • 构造方法也是方法,允许重载
  3. 推荐的使用方式
    • 无参数构造方法和带参构造方法一起给出

6.4 this 关键字

当局部变量和成员变量出现了重名的情况时,java使用的时就近原则

6.4.1 this 关键字介绍

this 代表当前类对象的引用(地址)

在java中**表示当前类的对象,可以理解成指向对象本身的一个指针。通俗地说就是表示当前类对象”自己“,它是在对象被创建时自动产生的。我们使用this,可以用来调用本类的属性、方法、构造方法。**当我们在构造方法中使用this时,this表示的是当前类的成员变量。

6.4.2 this 关键字作用
  • 表示当前类对象
  • 调用当前类中的属性(成员变量)
  • 调用当前类中的方法或构造方法
public class Phone {

    public String brand;
    public String color;
    public int price;
    public Phone() {
        this(null, null, 0);
    }
    public Phone(String brand, int price) {
        this(brand, null, 0);
        this.price = price;
    }
    public Phone(String brand, String color, int price) {

    }
    public void call() {
        this.sendMessage();
        System.out.println("使用" + color + brand + "打电话");
    }
    public void sendMessage() {
        System.out.println("使用" + color + brand + "发消息");
    }
}

七、面向对象三大特征

7.1封装

7.1.1 何为封装

使用类设计对象时,将需要处理的数据,以及处理这些数据的方法,涉及到对象中,尽可能隐藏对象内部的实现细节,控制对象的修改及访问的权限

封装的设计规范:

  • 合理隐藏,合理暴露

作用:

  • 更好地维护数据
  • 使用者无需关心内部实现,只要知道如何使用即可
7.1.2 权限修饰符
同一个类中同一个包中不同包的子类不同包的无关类
private×××
default××
protected×
public

访问修饰控制符 – “权限”

属性/方法被不同的访问控制符修饰,外界的访问权限是不同的

​ 三个修饰符 private protected public

​ 四个级别 private default protected public default是默认的

alt + enter:创建子类

  • private(私有的)

    • 可以修饰成员变量,成员方法,构造方法,不能修饰外部类,被private修饰的成员只能在其修饰的本类中访问,在其它类中不能被调用。对于所有的成员变量,可以通过setXXX和getXXX向外界提供访问方式
  • default(默认的)

    • 不写任何关键字,可以修饰类,成员变量,成员方法,构造方法。只能被本类以及同包下的其他类访问
  • protected(受保护的)

    • 可以修饰成员变量、成员方法、构造方法,但不能修饰类。只能被同包下的其他类访问,如果不同包下的类要访问被protected修饰的成员,这个类必须是子类
  • public(公共的)

    • 权限最大的修饰符,可以在任意一个类中使用,不管同不同包
7.1.3 如何封装

将属性使用访问修饰符private进行修饰,被private修饰后,属性仅在本类可见。

提供公共的方法(被public修饰)getXXXsetXXX实现对该属性的操作。

在公共的访问方法内部,添加逻辑判断,进而过滤掉非法数据,以保证数据安全(可选的)。

JavaBean:

package ithm;
/**
 * @author wxb
 * @version 1.0
 * @intro: 练习使用类
 */
public class Student {
    public String name ;
    public int age;
    public Student() {
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }
    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }
    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }
    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }
    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }
}

Tips:

  • 不可以直接访问属性,仅可以访问公共方法
  • get/set方法是外界访问对象私有属性的唯一通道,方法内部可以对数据进行检验和过滤

7.2 继承

7.2.1 何为继承

继承:让类与类之间产生关系(子父类关系),子类可以直接使用父类非私有的成员

程序中的继承,是类与类之间特征与行为的一种赠与或获得

两个类之间的继承关系,必须满足 is a 关系

  • Dog is an Animal.
  • Cat is an Animal.

根据上面的关系可以设计三个类:Dog、Cat、Animal

  • Dog 继承 Animal
  • Cat 继承 Animal

被继承的类为父类,即Animal

继承的类称为子类,即Dog和Cat

如何选择父类:

  • 功能越细致,重合点越多,越接近直接父类
  • 功能越粗略,重合点越少,越接近Object类

在实际开发中,可根据程序需要使用到的多个具体类,进行共性提取,进而定义父类

在一组相同或类似的类中,抽取出共性的特征和行为,定义在父类中,实现重用

作用:

提高了代码的复用性和可扩展性

 1. 公共代码抽取
 2. is  a 的关系
 3. 目前存在的问题  --  代码冗余/重复,修改不方便
 4. 如何解决? -- 抽取公共代码   --  继承
 5. Java程序中的继承是类与类之间的一种关系   ---   最少要有两个类
 6. 两个类之间的继承关系,必须满足 is a 的关系
    ​	如果不满足is a 的关系,即便有公共的代码,也不能使用继承
    ​		学生 is a Person 使用
    ​		教师 is a Person 使用
    ​		Cat is not a Person 不能继承
 7. 被继承的类,成为“父类” Person
    ​	继承的类,成为“子类”   Student Teacher
 8. 如何实现继承?
    ​	[修饰符]	class  子类名  extends  父类名 {
    ​			属性;
    ​			构造方法;
    ​			方法;
    }
     1. 修饰符可以省略,目前统一写public
     2. 子类名和父类名都是类名,要符合命名规范
     3. 子类/多个子类公共的内容定义在父类中
     4. 子类中定义子类自己特有的内容
     5. 实现继承关系之后,“子类可以使用父类的属性和方法”
     6. extends  --  “扩展”   --   子类是对父类的扩展
        ​	父类不能满足要求,使用子类进行扩展
 9. 关于单继承
    1.  什么是单继承?一个类只能有一个”直接父类“
    ​            extends后面只能写一个父类
    2. 一个类可以有多个”间接父类“
       Object类是所有类的”父类“
       定义类时如果指定了父类,Object就是间接父类
       定义类时如果没有指定父类,Object就是直接父类
       ===> 任何的类都可以使用Object类的属性和方法
7.2.2 如何继承
 //继承语法
class 子类名 extends 父类名 { //定义子类时,指定其父类
	//属性(成员变量)
    //构造方法
    //成员方法
}

产生继承关系后,子类可以使用父类中的属性和方法,也可以定义子类独有的属性和方法

继承特点:Java为单继承,一个类只能有一个直接父类,但是可以多继承,属性和方法逐级叠加

//父类
class Employee {
    String name;
    int age;
    double salary;
}
//子类继承父类
class Coder extends Employee{

}

关于不可继承:

  • 类中的构造方法,只负责创建本类对象,不可继承
  • private修饰的属性和方法,仅本类可见
  • 父子类不在同一个package中时,default 修饰的属性和方法
7.2.3 方法重写

当父类提供的方法无法满足子类需求时,可以在子类中定义和父类相同的方法进行重写(Override)

方法重写原则:

  • 子类重写父类方法,方法声明完全一致,方法名称、参数列表、返回值类型必须与父类相同
  • 访问修饰符可与父类相同或是比父类更宽泛,访问权限必须大于等于父类
  • 父类中分私有方法不可被重写

方法重写的执行:

  • 根据就近原则,子类重写父类方法后,调用时优先执行子类重写后的方法

@Override – 注解

  1. 书写位置:写在重写的方法上面
  2. 作用:告诉编译器,当前的方法是重写的方法,让编译器协助检查是否符合重写的规范,如果不符合就报错
  3. 建议:只要重写方法就要在重写的方法上添加这个注解

重写方法的调用

  1. 如果通过子类对象调用重写方法,会优先调用子类自己重写的方法,如果子类没有重写方法,就调用父类的方法
  2. 如果通过父类调用方法(这个方法被子类重写过),此时调用的是父类的方法

alt + enter:创建重写

public class Dog extends Animal {
    private String color;//毛色
    //跑
    public void run() {
        System.out.println("run...");
    }
    //子类重写父类中的方法,方法名称、参数列表、返回值类型必须与父类相同。
    @Override
    public void eat() {
        System.out.println("狗吃骨头...");
    }
}
public class MyTest1 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat(); //调用子类中重写的方法。
    }
}
7.2.4 super 关键字

子类继承父类后,可以直接访问从父类中继承到的属性和方法,但当父类和子类中的属性或方法存在重名的情况(属性遮蔽、方法重写)时,需要加以区分,通过this.调用本类方法和属性,使用super.调用父类中的方法和属性

public class Dog extends Animal {
    private String color;//毛色
    //跑
    public void run() {
        System.out.println("run...");
    }
    //子类重写父类中的方法,方法名称、参数列表、返回值类型必须与父类相同。
    @Override
    public void eat() {
        super.eat();
        System.out.println("狗吃骨头...");
    }
}

super调用父类成员的省略规则:

  • super.父类成员变量;
  • super.父类成员方法();
    • 被调用的成员变量或者成员方法在子类中不存在时,可以省略super.

super关键字可以在子类中访问父类的方法,使用super.的形式访问父类的方法,进而完成在子类中的复用,叠加额外的功能代码,组成新的功能

由于父类与子类的同名属性不存在重写关系,所以两块空间同时存在,但在使用中,由于就近原则,子类中的属性会遮蔽父类属性,所以需要使用不同的前缀进行访问

this 和 super

  • this:代表本类对象的引用
  • super:代表父类存储空间的标识
关键字访问成员变量访问成员方法访问构造方法
thisthis.本类成员变量;this.本类成员方法;this(); this(…);本类构造方法
supersuper.父类成员变量;super.父类成员方法;super(); super(…);父类构造方法

Tips:

父子类的同名属性不存在重写关系,两块空间同时存在(子类遮蔽父类属性),需使用不同前缀进行访问

public class A {
    int value = 10;
}
public class B extends A {
    int value = 20; //子类属性遮蔽父类属性
    public void print() {
        int value = 30;
        System.out.println(value); //访问局部变量,输出B成员方法中的value 为30 
        System.out.println(this.value); //访问本类的属性,输出B中的成员变量 value 为20
        System.out.println(super.value); //访问父类的属性,输出父类中的成员变量 value为 10
    }
}
public class MyTest2 {
    public static void main(String[] args) {
        B b = new B();
        b.print();//分别输出30、20、10
    }
}

继承中的对象创建:

先初始化父类属性,再调用构造方法

在具有继承关系的对象创建中,构建子类对象会先构建父类对象,由父类的共性内容,叠加子类的都有内容,组成完整的子类对象

public class X {
    public X() {
        System.out.println("X的构造方法...");
    }
}
public class Y {
    public Y() {
        System.out.println("Y的构造方法...");
    }
}
public class A {
	private X x = new X();
    
    public A() {
        System.out.println("A的构造方法...");
    }
}
public class B extends A {
    private Y y = new Y();
    
    public B() {
        System.out.println("B的构造方法...");
    }
}
public class C extends B {
    public C() {
        System.out.println("C的构造方法...");
    }    
}
public class MyTest3 {
    public static void main(String[] args) {
        C c = new C();
    }
}
/*	输出:
	X的构造方法...
	A的构造方法...
    Y的构造方法...
	B的构造方法...
	C的构造方法...
*/

构建过程:

  1. A类
    • 默认构建父类对象Object
    • 初始化A的属性;
    • 执行A的构造方法代码。
  2. B类
    • 构建父类对象A;
    • 初始化B的属性;
    • 执行B的构造方法代码。
  3. C类
    • 构建父类对象B;
    • 初始化C的属性;
    • 执行C的构造方法代码。

代表父类构造方法

super();:表示调用父类无参构造方法,若没有显示书写,隐式存在于子类构造方法的首行

super(参数);:表示调用父类有参构造方法

public class A {
	private X x = new X();    
    public A() {
        System.out.println("A的构造方法...");
    }    
    public A(int value) {
        System.out.println("A的构造方法..." + value);
    }
}
public class B extends A {
    private Y y = new Y();   
    public B() {
        //super(); //默认存在
        System.out.println("B的构造方法...");
    }  
    public B(int value) {
        super(value);
        System.out.println("B的构造方法..." + value);
    }
}
public class MyTest4 {
    public static void main(String[] args) {
        B b = new B(100);
    }
}
/*  输出:
	X的构造方法...
    A的构造方法...100
    Y的构造方法...
    B的构造方法...100
*/

thissuper使用在构造方法中时,都要求在首行

当子类构造中使用了this()this(参数),就不可再同时书写super()super(参数),会由this()指向的构造方法完成super()的调用

this()或this (参数)结合第4点会默认调用父类的构造方法,如果再调用super()或者super (参数)就会创建多个父类对象

public class A {
    private X x = new X();

    public A() {
        System.out.println("A的无参构造...");
    }

    public A(int value) {
        System.out.println("A的有参构造..." + value);
    }
}

public class B extends A {
    private Y y = new Y();
    public B() {
        super();
        System.out.println("B的无参方法...");
    }
    public B(int value) {
        this();
        System.out.println("B的有参方法..." + value);
    }
}
public class MyTest5 {
    public static void main(String[] args) {
        B b = new B(100);
    }
}
/* 输出:
	X的构造方法...
    A的无参构造...
    Y的构造方法...
    B的无参方法...
    B的有参方法...100
*/

super总结:

两种用法:

  • 在子类方法中使用super.的形式访问父类的属性和方法
  • 在子类的构造方法的首行,使用super()super(参数),调用父类构造方法

注意:

  • 如果子类构造方法中,没有显式定义super()super(参数),则默认提供super()
  • 同一个子类构造方法中,super()this()不可同时存在

7.3 多态

7.3.1 何为多态

多种形态,同一个行为具有多个不同表现形式或形态的能力,父类引用指向子类对象,从而产生多种形态

多态前提:

  • 有继承/实现关系
  • 有方法重写
  • 有父类引用指向子类对象

面对对象三大特征之一
多态 - 多种状态 – 编译时和运行时呈现出不同的状态就是多态

  1. 多态的代码如何编写 –

    子类对象赋值给父类引用/

    父类引用指向子类对象
    Animal a1 = new Dog();
    Animal a2 = new Cat();

  2. 方法调用规律
    1)编译时,“看左边”,左边有哪个方法就可以调用哪个方法
    2)运行时,“看右边”。右边实际是哪个方法,运行时就调用哪个方法

  3. 多态的应用
    之前存在的问题 – 针对不同的类型要写特定的方法单独进行处理,产生代码冗余
    – 设置一个方法可以兼容多种类型
    1) 父类作为方法的参数,实现多态 – 方法的参数可以兼容所有的子类对象
    2) 子类作为方法的返回值类型,实现多态

向下转型

  1. (类型)引用

  2. 可能存在的问题 java.lang.ClassCastException 类型转换异常

  3. 如何解决2的问题,转换之前先判断引用代表的对象本质上是什么类型 – 如何判断?

    引用 instanceof 类型

    返回值

    ​ true 引用是后面的类型

    ​ false 引用不上后面的类型


八、 常用类

8.1 API

8.1.1 什么是API

定义:

Application Programming InterfaceAPI是应用程序编程接口,指一些预先定义好的类。

要求:

  • 会用
  • 现用现查

JavaSE8官方API JavaAPI

中文官方API 中文JavaAPI

8.1.2 Java常用类库简介

Java程序设计就是定义类的过程,定义的类分为两大类:

  • 系统定义的类:即Java类库中的类。这部分类是已经设计好的,直接用就可以。也是我们这里所指的Java类库中的主要内容;
  • 用户程序自定义的类:需要开发人员自己设计实现。

重要的包及其类:

  • java.lang:包括了Java语言程序设计的基础类;
  • java.util:包含集合、日期和各种实用工具类;
  • java.io:包含可提供数据输入、输出相关功能的类;
  • java.net:提供用于实现Java网络编程的相关功能类;
  • java.sql:提供数据库操作相关功能类。

注意:java.lang是默认会导入的包,不需要手动导入。

8.2 Object 类

8.2.1 何为Object类
  1. 是所有类的直接父类或间接父类
  2. 设计一个类时,如果没有指定父类,那么它的父类是Object
  3. 所有类都具有Object类的属性和方法
  4. Object类可以存储任何对象

作用:

  • 作为参数,可以接收任何对象
  • 作为返回值,可以返回任何对象
8.2.2 常用方法
  • toString方法 - 自身没有打印功能

  • 定义:public String toString()

  • 作用:返回对象的字符串表示形式

  • 返回内容:默认返回当前对象在堆内存中的地址信息:全类名@hash值

  • 注意:

    • Object类是所有类的父类,其他的类都具有toString方法
    • 使用System.out.println打印引用,会自动调用toString方法
      • System.out.println(s.toString());
      • System.out.println(s);
    • 多数时候继承自Object类的toString方法无法满足要求,需要在子类中重写toString方法
    • 重写toString方法通常是为了能够打印对象属性的值,使用Idea生成
  • equals方法

  • ==的作用

    • 判断基本数据类型的值是否相等

      • int a = 10;
        int b = 5;
        System.out.println(a == b); //false
        
    • 判断两个对象是否是同一个对象/判断两个对象的地址是否相同/判断俩个引用存储的地址是否相同

      • Student s2 = new Student();
        System.out.println(s1 == s2); //false
        s1 = s2;
        System.out.println(s1 == s2); //true
        
  • Object类中的equals方法

    • 定义:public static equals(Object o)

    • 返回内容:默认比较当前对象与另一个对象的地址是否相同

    • 注意:

 * Object类中的equals的作用和==的作用相同,都用来判断两个对象是否是同一个对象

 * Object类是所有类的父类,其他的类都具有equals方法,如果子类中没有重写equals方法
      那么子类对象调用equals实际调用的是Object类的equals方法

 * 在多数情况下,我们希望equals方法能够判断对象的“值”(属性)是否相同
   Object类的equals方法无法满足要求,因此需要在子类中重写equals方法

   * ```java
     Student s5 = new Student("Java01", "zs", 20);
     Student s6 = new Student("Java01", "zs", 20);
     System.out.println(s5.equals(s6)); //true
     ```

 * 判断String的内容是否相同使用equals,为什么?
     String类重写了Object类中的equals方法
//被重写后的equals方法和hashCode方法
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Student student = (Student) o;
    return age == student.age && Objects.equals(num, student.num) && Objects.equals(name, student.name);
}

@Override
public int hashCode() {
    return Objects.hash(num, name, age);
}
  • hashCode方法

  • 定义:public int hashCode() {}

  • 返回内容:对象的十进制哈希值,哈希值是通过哈希算法根据对象的地址计算出来的int类型的数组

  • 注意:

    • Object类的hashCode()方法为不同的对象返回不同的值,Object类的hashCode值,可以认为其表示对象的地址
    • 如果equals()判断两个对象相等,那么他们的hashCode()方法应该返回同样的值
  • == 和 equals的区别

    • == 判断的是基本数据类型的值是否相等或者是两个对象的地址是否相同
    • equals在Object类中,判断的是两个对象的地址是否相同,重写后判断的是两个对象存储的内容是否相同
  • isNull方法

  • 定义:public static boolean isNull(Object obj)

  • 返回内容:判断变量是否为null

8.3 包装类

8.3.1 问题引入

目前,8种基本数据类型无法作为引用数据类型使用

8.3.2 如何解决

包装类

  • 用一种方式将8种基本数据类型当成引用类使用
  • 基本数据类型,包装成
8.3.3 包装类
基本数据类型引用数据类型
byteByte
shortShort
intInteger
longLong
charCharacter
floatFloat
doubleDouble
booleanBoolean
8.3.4 装箱和拆箱 - Integer类
  • 装箱:基本数据类型转换成包装类

  • 拆箱:包装类转换成基本数据类型

    • 手动拆装箱

      • //手动装箱
        //基本数据类型 -- 包装类
        int a = 100;
        Integer i = Integer.valueOf(a);
        System.out.println(i); //100
        System.out.println(i.toString()); //100
        //手动拆箱
        //包装类 -- 基本数据类型
        int b = i.intValue();
        System.out.println(b);
        
    • 自动拆装箱 - 直接赋值

      • //自动装箱
        int a = 100;
        Integer i1 = a;
        System.out.println(i1); //100
        //自动拆箱
        int c = i1;
        System.out.println(c); //100
        
8.3.5 基本数据类型和字符串转换
  • 基本数据类型 --> 字符串
//1. 基本数据类型 + "";
int a = 100;
String s1 = a + ""; //100
//2. String.valueOf(基本数据类型);
String s2 = String.valueOf(a); //100
  • 字符串 --> 基本数据类型
int b = Integer.parseInt("100");
int b = Integer.valueof("100");
System.out.println(b); //100
8.3.6 常量池
Integer i1 = new Integer(127);
Integer i2 = new Integer(127);
System.out.println(i1 == i2);   //true
System.out.println(i1.equals(i2)); //true

Integer i3 = new Integer(128);
Integer i4 = new Integer(128);
System.out.println(i3 == i4); //false
System.out.println(i3.equals(i4)); //true

在上面的代码中,当我们新建两个值为127的Integer类时,通过==比较地址和通过重写equals比较内容都是相同的

但是在第二段代码中国,我们设置的值为128时,他们的地址就不再一样了

为什么呢?

  • 池化技术

    • 内存池
    • 线程池
    • 连接池
  • 包装类的常量池 - Integer

    • public static Integer valueOf(int i) {
          if (i = IntegerCache.low && i <= IntegerCache.high)
              return IntegerCache.cache[i + (-IntegerCache.low)];
          return new Integer(i);
      }
      
    • 在类加载时,创建-128 ~ 127范围数字对应的Integer类的对象,它们都存放在常量池中,如果后面需要使用该范围内的数字,就从常量池中获取;如果没有,就需要重新创建。

      • 所以,在最开始的代码中,127时,是从常量池中取到的两个地址相同的数字,128时,是两个新建的对象
  • 如何判断包装类的对象是是否相等?

    • equals
  • 哪些包装类没有常量池?

    • Float
    • Double

8.4 String 类

8.4.1 String类的特点

特点:

  1. Java程序中 ,所有双引号字符串,都是String这个类的对象

  2. 字符串一旦被创建,就不可以被更改,字符串内容不可变

    ​ 如果想要更改,只能使用新的对象,做替换

  3. String字符串虽然不可改变,但是可以共享

    ​ 字符串常量池:当使用双引号创建字符串对象时,会检查常量池中是否存在该数据

    ​ 不存在:创建

    ​ 存在:复用

public static void main(String[] args) {
        String s1 = "abc";
        String s2 = "abc";
        System.out.println(s1 == s2);
}
//true
8.4.2 String类的创建
  • String s1 = “abc”;
  • String s2 = new String(“abc”);
8.4.3 String类常量池

在Java7之前,字符串常量池位于方法区中

在Java7之后,字符串常量池被移入了堆内存中,而运行时常量池仍然位于方法区中

  1. 字符串都放在字符串常量池中
  2. 相同内容的字符串在常量池中只有一份

在这里插入图片描述

String s1 = "多喝热水";
String s2 = "多喝热水";
System.out.println(s1 == s2); //true
System.out.println(s1.equals(s2)); //true

System.out.println("------------------------------------");
String s3 = new String("多喝热水");
System.out.println(s1 == s3); //false
System.out.println(s1.equals(s3)); //true

System.out.println("------------------------------------");
String s4 = "多喝" + "热水";
System.out.println(s1 == s4); //true
System.out.println(s1.equals(s4)); //true

System.out.println("------------------------------------");
String s5 = "多喝";
String s6 = "热水";
String s7 = s5 + "热水";
System.out.println(s1 == s7); //false
System.out.println(s1.equals(s7)); //true

System.out.println("------------------------------------");
String s8 = s5 + s6;
System.out.println(s1 == s8); //false
System.out.println(s1.equals(s8)); //true

System.out.println("------------------------------------");
String  s9 = "多" + "喝" + "热" + "水";
System.out.println(s1 == s9); //true
System.out.println(s1.equals(s9)); //true

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  1. 在这里插入图片描述

  2. true

在这里插入图片描述

8.4.4 String类遍历功能
  • public char[] toCharArray() 将此字符串转换为一个新的字符数组
String s = "raining";
char[] chs = s.toCharArray();
for (char a : chs) {
    System.out.println(a);
}
  • public char charAt(int index) 返回指定索引处的 char 值
String s = "raining";
for (int i = 0; i < s.length(); i++) {
    System.out.println(s.charAt(i));
}
  • public int length() 返回字符串的长度(有"()")
8.4.5 String类判断功能
  • public boolean euqals(Object obj)
    • 判断字符串的内容是否相同
String s1 = "raining";
String s2 = new String("raining");
System.out.println(s1.equals(s2)); //true
  • public boolean equalsIgnoreCase(String str)
    • 忽略大小写判断字符串内容是否相等
String s1 = "raining";
String s2 = "Raining";
System.out.println(s1.equalsIgnoreCase(s2)); //true
  • public boolean contains(String str)
    • 判断字符串是否包含特定的字符串
String s1 = "raining";
System.out.println(s1.contains("rain")); //true
  • public boolean startsWith(String str)
    • 判断字符串是否以特定的字符串开头
String s1 = "raining";
System.out.println(s1.startsWith("rain")); //true
System.out.println(s1.startsWith("ain", 1)); //true
  • public boolean endsWith(String str)
    • 判断字符串是否以特定的字符串结尾
String s1 = "raining";
System.out.println(s1.endsWith("ning")); //true
  • public boolean isEmpty()
    • 判断字符串是否包含特定的字符串
System.out.println("".isEmpty()); //true
8.4.6 String类获取功能
  • public int length()
    • 获取字符串长度
String s = "raining";
System.out.println(s.length()); //7
  • public char charAt(int index)
    • 返回特定位置的索引
String s = "raining";
System.out.println(s.charAt(2)); //i
  • public int indexOf(int ch)
    • 返回特定字符首次出现的位置
String s = "raining";
System.out.println(s.indexOf('a')); //1
  • public int indexOf(String str)
    • 返回特定字符串首次出现的位置
String s = "raining";
System.out.println(s.indexOf("ai")); //1
  • public int indexOf(int ch, int fromIndex)
    • 返回特定字符首次出现的位置,从fromIndex开始
String s = "raining";
System.out.println(s.indexOf('i', 3)); //4
  • public String substring(int start)
    • 获取字串,从start索引开始
String s = "raining";
System.out.println(s.substring(0)); //raining
  • public String substring(int start, int end)
    • 获取字串,从start索引到end [start, end)
String s = "raining";
System.out.println(s.substring(0, 3)); //rai
8.4.7 String类转换功能
  • public char[] toCharArray()
    • 将字符串转化为char数组
String s = "raining";
System.out.println(s.toCharArray()); //raining
  • public static String valueOf(char[] chs)
    • 将字符串转化为String类
char[] chs = {'r', 'a', 'i', 'n','i', 'n', 'g'};
System.out.println(String.valueOf(chs)); //raining
  • public String toLowerCase()
    • 全部转换为小写字母
String s = "RAINING";
System.out.println(s.toLowerCase()); //raining
  • public String toUpperCase()
    • 全部转换为大写字母
String s = "raining";
System.out.println(s.toUpperCase()); //RAINING
  • public String concat(String str)
    • 字符串拼接
String s = "rain";
System.out.println(s.concat("ing")); //raining
8.4.8 String类替换功能
  • String replace(char old, char new)
    • 将old字符替换为new字符
String s = "raining";
System.out.println(s.replace('i', '*')); //ra*n*ng
  • String replace(String old, String new)
    • 将old字符串替换为new字符串
String s = "raining";
System.out.println(s.replace('ing', 'ed')); //rained
8.4.9 String类去空格功能
  • trim()
    • 去除首位空格
String s = "  rainin g   ";
System.out.println(s);
System.out.println(s.length());
System.out.println(s.trim());
System.out.println(s.trim().length());
/*
  rainin g   
13
rainin g
8
*/
8.4.10 可变字符串
  • StringBuffer
    • 字符串缓冲区,用于存储可变字符序列的容器
    • 解决了String用字符串做拼接,既费时又耗内存的问题
    • 可对字符串进行修改 - 动态拼接
    • 长度可变
StringBuffer sbf = new StringBuffer("hello");
//拼接
sbf.append(" world");
sbf.append(" hahahahaha");
//StringBuffer -- String
str = sbf.toString();
System.out.println(str);
/*
hello world hahahahaha
*/
  • StringBuilder
    • 与StringBuffer功能相同
  • 二者区别
    • StringBuffer线程同步,线程安全,常用于多线程,效率低
    • StringBuilder线程不同步,线程不安全,通常用于单线程,效率较高

8.5 数学相关类

8.5.1 Math类

位置:

  • java.lang包

常量:

  • PI 圆周率
  • E 自然对数底数
System.out.println(Math.PI);
System.out.println(Math.E);

常用方法:

  • random():返回带正号的double值,该值大于等于0.0且小于1.0 [0.0, 1.0)
//生成[0.0, 1.0)的随机数
for(int i = 0; i < 10; i++) {
    System.out.println(Math.random());
}
System.out.println("-----------------------------");
//生成[0, 9]的随机数 - 整数
for(int i = 0; i < 10; i++) {
    System.out.println((int)(Math.random() * 10));
}

System.out.println("-----------------------------");
//生成[1, 10]的随机数 - 整数
for(int i = 0; i < 10; i++) {
    System.out.println((int)(Math.random() * 10) + 1);
}
System.out.println("-----------------------------");
//生成[1, 5]的随机数 - 整数
//[0, 4]
for(int i = 0; i < 30; i++) {
    System.out.println((int)(Math.random() * 5) + 1);
}
8.5.2 BigDecimal类

位置:

  • java.lang包

作用:

  • 精确计算浮点整数
  • 解决double存在的精度缺失问题

常用方法:

  • add(BigDecimal augend)加
  • subtract(BigDecimal subtrahend)减
  • multiply(BigDecimal multiplicand)乘
  • divide(BigDecimal divisor)除

进行除法运算时,如果不能准确的计算出结果时需要指定保留的位数和取舍方式。

divide(BigDecimal divisor, int scale, int roundingMode)
  • scale:指定精确到小数点后几位
  • mode:指定小数部分的取舍模式,通常采用四舍五入的模式,取值为BigDecimal.ROUND_HALF_UP
public static void main(String[] args) {
    double d = 10.0;
    System.out.println(d / 3);

    BigDecimal d1 = new BigDecimal("10");
    BigDecimal d2 = new BigDecimal("3");

    //加
    BigDecimal d3 = d1.add(d2);
    //BigDecimal -- int
    System.out.println(d3.intValue());
    //减
    System.out.println(d1.subtract(d2));
    //乘法
    System.out.println(d1.multiply(d2));
    //除法
    System.out.println(d1.divide(d2, 5, BigDecimal.ROUND_HALF_UP));
}
/*
3.3333333333333335
13
7
30
3.33333
*/

8.6 日期时间相关类

8.6.1 Date

表示特定的瞬间,精确到毫秒。要注意该类中很多方法已经过时。

构造方法

  • Date():分配Date对象并用当前时间初始化此对象,以表示分配它的时间(精确到毫秒)
  • Date(long date):分配Date对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即1970年1月1日 00:00:00 GMT)以来的指定毫秒数。

常用方法

  • boolean after(Date anotherDate):测试此日期是否在指定日期之后

  • boolean before(Date anotherDate):测试此日期是否在指定日期之前

  • int compareTo(Date anotherDate):比较两个日期的顺序

  • long getTime():返回自1970年1月1日00:00:00 GMT以来此Date对象表示的毫秒数

  • void setTime(long time):以long类型参数time设置此Date对象,以表示1970年 1 月 1 日 00:00:00 GMT以后指定毫秒的时间点

  • String toString():默认实现是把此Date对象转换为以下形式的字符串

    dow mon dd hh:mm:ss zzz yyyy
    
    • dow是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)
    • mon是月份 (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec)
    • dd是一月中的某一天(01 至 31),显示为两位十进制数
    • hh是一天中的小时(00 至 23),显示为两位十进制数
    • mm是小时中的分钟(00 至 59),显示为两位十进制数
    • ss是分钟中的秒数(00 至 61),显示为两位十进制数
    • zzz是时区(并可以反映夏令时)。标准时区缩写包括方法 parse 识别的时区缩写。如果不提供时区信息,则 zzz 为空,即根本不包括任何字符
    • yyyy是年份,显示为 4 位十进制数
public static void main(String[] args) {
    //创建Date对象
    Date date = new Date(); //当前时刻
    System.out.println(date);

    //获取时间戳
    //什么是时间戳 - 从1970年1月1日00:00:00到现在经历的毫秒值
    //Unix 1969/1970
    System.out.println(date.getTime());

    //另一种创建Date对象的方法
    Date date1 = new Date(1706249472439L);
    System.out.println(date1);
}
/*
Sat Jan 27 11:42:34 CST 2024
1706326954812
Fri Jan 26 14:11:12 CST 2024
*/
8.6.2 SimpleDateFormat

以指定格式输出日期和时间。

Date类型与字符串转换:

  • Date转换成字符串:format()
  • 字符串转为Dateparse()
public static void main(String[] args) throws ParseException {
    Date date = new Date();
    //使用XXXX年XX月XX日 XX:XX:XX形式输出当前时间
    /*
        * 对日期时间格式化操作
        * 1. 创建SimpleDateFormat对象
        * 2. 使用该对象的format方法进行格式化
        * */
    SimpleDateFormat sft = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
    //将Date对象格式化为特定格式的字符串
    String dateStr = sft.format(date);
    System.out.println(dateStr);

    SimpleDateFormat sft1 = new SimpleDateFormat("yyyy年MM月dd日 KK:mm:ss a");
    String dateStr1 = sft1.format(date);
    System.out.println(dateStr1);

    SimpleDateFormat sft2 = new SimpleDateFormat("HH:mm:ss");
    String dateStr2 = sft2.format(date);
    System.out.println(dateStr2);

    /*
        * 将特定格式的表示时间日期的字符串装换成Date对象
        * SimpleDateFormat的parse()方法
        *
        * SimpleDateFormat的对象要和被转换的字符串兼容
        * */
    String dateStr3 = "2024年01月26日 14:25:14";
    Date date1 = sft.parse(dateStr3);
    System.out.println(date1);
}
/*
2024年01月27日 11:42:12
2024年01月27日 11:42:12 上午
11:42:12
Fri Jan 26 14:25:14 CST 2024
*/
8.6.3 Calendar

单独获取当前日期和时间中的年月日和星期。

获取Calendar对象:Calendar.getInstance()

常用字段:

  • Calendar.YEAR年份
  • Calendar.MONTH月份,从0开始
  • Calendar.DATE日期
  • Calendar.DAY_OF_MONTH日期,和Calendar.DATE完全相同
  • Calendar.HOUR12小时制的小时数
  • Calendar.HOUR_OF_DAY24小时制的小时数
  • Calendar.MINUTE分钟
  • Calendar.SECOND
  • Calendar.DAY_OF_WEEK星期几

常用方法:

  • set()
  • get()
public static void main(String[] args) {
    //获取Calendar对象
    Calendar c = Calendar.getInstance();
    //System.out.println(c);
    //get - 获取
    System.out.println("年:" + c.get(Calendar.YEAR));
    System.out.println("月:" + c.get(Calendar.MONTH) + 1);
    System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
    System.out.println("星期:" + c.get(Calendar.DAY_OF_WEEK)); //星期天开始的

    //set - 设置
    c.set(Calendar.YEAR, 2024); //2024年
    c.set(Calendar.MONTH, 4); //5月
    c.set(Calendar.DAY_OF_MONTH, 1); //1日
    System.out.println("星期:" + c.get(Calendar.DAY_OF_WEEK));
}
/*
年:2024
月:01
日:27
星期:7
星期:4
*/

打印万年历

public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    /*
        * 输入年、月,打印当月的万年历
        * 1. 确定当月有多少天
        * 2. 当月的第一天是周几
        *
        * */
    System.out.print("请输入年:");
    int year = sc.nextInt();
    System.out.print("请输入月:");
    int month = sc.nextInt();
    //该月的总天数
    int days = 0;

    //确定当月一共有几天
    switch (month) {
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
        case 12:
            days = 31;
            break;
        case 4:
        case 6:
        case 9:
        case 11:
            days = 30;
            break;
        case 2:
            if((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
                days = 29;
            } else {
                days = 28;
            }
            break;
    }

    //当月的第一天是周几
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.YEAR, year);
    calendar.set(Calendar.MONTH, month - 1);
    calendar.set(Calendar.DAY_OF_MONTH, 1);
    int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); //从周日开始

    int count = 0; //用来控制换行的计数器
    System.out.println("日\t一\t二\t三\t四\t五\t六");
    //先输出1号之前的空格
    for (int i = 0; i < dayOfWeek - 1; i++) {
        System.out.print('\t');
        count++;
    }

    //输出日期
    for (int i = 1; i <= days; i++) {
        System.out.print(i + "\t");
        count++;
        if(count % 7 == 0) {
            System.out.println();
        }
    }
}
/*
请输入年:2024
请输入月:1
日	一	二	三	四	五	六
	1	2	3	4	5	6	
7	8	9	10	11	12	13	
14	15	16	17	18	19	20	
21	22	23	24	25	26	27	
28	29	30	31	
*/
8.6.4 JDK8中新日期时间API
8.6.4.1 概述

JDK 1.0中包含了 一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用 了。而Calendar并不比Date好多少,它们面临的问题是:

  • 可变性:像日期和时间这样的类应该是不可变的;
  • 偏移性:Date中的年份是从1900开始的,而月份都从0开始;
  • 格式化:格式化只对Date有用,Calendar则不行;
  • 此外,它们也不是线程安全的;不能处理闰秒等。

对日期和时间的操作一直是Java程序员最痛苦的地方之一。

Java 8吸收了Joda-Time的精华,以一个新的开始为Java创建优秀的API。 新的java.time中包含了所有关于本地日期LocalDate、本地时间LocalTime、本地日期时间LocalDateTime、时区ZonedDateTime 和持续时间Duration的类。历史悠久的Date类新增了toInstant()方法, 用于把Date转换成新的表示形式。这些新增的本地化时间日期API大大简化了日期时间和本地化的管理。

8.6.4.2 LocalDateLocalTimeLocalDateTime

LocalDateLocalTimeLocalDateTime类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。 它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。

  • LocalDate代表IOS格式yyyy-MM-dd的日期,可以存储 生日、纪念日等日期;
  • LocalTime表示一个时间,而不是日期;
  • LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。
方法描述
now()/now(ZoneId zone)静态方法,根据当前时间创建对象/指定时区的对象
of()静态方法,根据指定日期/时间创建对象
getDayOfMonth()/getDayOfYear()获得月份天数(1-31)/获得年份天数(1-366)
getDayOfWeek()获得星期几(返回一个 DayOfWeek 枚举值)
getMonth()获得月份, 返回一个 Month 枚举值
getMonthValue()/getYear()获得月份(1-12)/获得年份
getHour()/getMinute()/getSecond()获得当前对象对应的小时、分钟、秒
withDayOfMonth()/withDayOfYear()/ withMonth()/withYear()将月份天数、年份天数、月份、年份修改为指定的值并返回新的对象
plusDays()/plusWeeks()/plusMonths()/plusYears()/plusHours()向当前对象添加几天、几周、几个月、几年、几小时
minusMonths()/minusWeeks()/minusDays()/minusYears()/minusHours()从当前对象减去几月、几周、几天、几年、几小时
public static void main(String[] args) {
    //创建LocalDateTime对象 -- 当前的时刻
    LocalDateTime dateTime = LocalDateTime.now();
    System.out.println(dateTime);

    //创建LocalDateTime对象 -- 特定的时刻
    LocalDateTime dateTime1 = LocalDateTime.of(2024, 2, 10, 10, 10, 10);
    System.out.println(dateTime1);

    System.out.println(dateTime.getDayOfMonth()); //26
    System.out.println(dateTime.getDayOfYear());  //26
    System.out.println(dateTime.getDayOfWeek().getValue());
    System.out.println(dateTime.getMonth().getValue());
    System.out.println(dateTime.getHour());
    System.out.println(dateTime.getMinute());
    System.out.println(dateTime.getSecond());

    LocalDateTime dateTime2 = dateTime.withMonth(5);
    LocalDateTime dateTime3 = dateTime.withDayOfMonth(30);
    System.out.println(dateTime);
    System.out.println(dateTime2);
    System.out.println(dateTime3);

    LocalDateTime dateTime4 = dateTime.plusDays(1);
    System.out.println(dateTime4);

}
/*
2024-01-27T11:58:09.378
2024-02-10T10:10:10
27
27
6
1
11
58
9
2024-01-27T11:58:09.378
2024-05-27T11:58:09.378
2024-01-30T11:58:09.378
2024-01-28T11:58:09.378
*/
8.6.4.3 格式化与解析日期或时间

DateTimeFormatter提供了三种格式化方法:

  • 预定义的标准格式
  • 自定义的格式
  • 本地化相关的格式
public static void main(String[] args) {
    /*
        * 格式化日期时间 -- 产生特定格式的字符串
        * */
    LocalDateTime dateTime = LocalDateTime.now();
    System.out.println(dateTime);
    DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
    String dateTimeStr = dateTime.format(fmt);
    System.out.println(dateTimeStr);

    //xx时xx分xx秒
    DateTimeFormatter fmt1 = DateTimeFormatter.ofPattern("HH时mm分ss秒");
    String dateTimeStr1 = dateTime.format(fmt1);
    System.out.println(dateTimeStr1);

    String dateTimeStr2 = dateTime.format(DateTimeFormatter.BASIC_ISO_DATE);
    System.out.println(dateTimeStr2);

    String dateTimeStr3 = dateTime.format(DateTimeFormatter.ISO_DATE_TIME);
    System.out.println(dateTimeStr3);

    //将特定格式的字符串转换成日期时间对象
    LocalDateTime localDateTime = LocalDateTime.parse("2024年01月26日 16:03:26", fmt);
    System.out.println(localDateTime);
    LocalDateTime localDateTime1 = LocalDateTime.parse("2024-01-26T16:07:36.644");
    System.out.println(localDateTime1);

}
/*
2024-01-27T12:00:05.830
2024年01月27日 12:00:05
12时00分05秒
20240127
2024-01-27T12:00:05.83
2024-01-26T16:03:26
2024-01-26T16:07:36.644
*/
public static void main(String[] args) {
    LocalDateTime dateTime = LocalDateTime.now();
    DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
    //LocalDateTime -- 特定格式的字符串
    String str = fmt.format(dateTime);
    System.out.println(str);

    //特定格式的字符串 -- LocalDateTime
    TemporalAccessor temporalAccessor = fmt.parse("2024年01月26日 16:25:46");
    //TemporalAccessor -- LocalDateTime
    LocalDateTime dateTime1 = LocalDateTime.from(temporalAccessor);
    System.out.println(dateTime1);
}
/*
2024年01月27日 12:00:33
2024-01-26T16:25:46
*/

九、 三个修饰符

9.1 final 关键字 – 最终的

  1. 表示最终的

  2. 可以修饰变量、修改方法、修饰类

  3. 修饰变量

    1. 这个变量就变成了常量

    2. 定义是可以不初始化,但是只要使用必须初始化,初始化之后不能修改值

    3. 常量所有字母都要大写,单词之间使用_分隔

      ​ MAX_AGE PI E

    4. final如果修饰引用数据类型,地址不可变

  4. 修饰方法 – 最终的方法 – 不能被重写

  5. 修饰类 – 最终的类 — 不能被继承

9.2 abstract 抽象 不具体

9.2.1 抽象类:

需要设计这个类,通常充当父类,但是不需要创建对象 – 设计为抽象类

抽象类
* 1. 什么样的类要被设计为抽象类?
*   作为父类,被其它类继承,不需要创建对象
* 2.如何设计一个抽象类
*   使用修饰符abstract
*       public abstract class 类名 {
*
*       }
* 3.抽象类无法实例化
* 4.可以在抽象类中设计构造方法,但是不能通过构造方法创建对象
9.2.2 抽象方法
  1. 什么样的方法要被定义为抽象方法?

    ​ 需要定义,但是不需要方法体

  2. 如何定义抽象方法?

    1. 使用abstract关键字
    2. 没有方法体,{} 用;代替
    3. 只有方法声明,没有方法实现
  3. 抽象类和抽象方法的关系

    1. 有抽象方法的类,一定是抽象类

    2. 抽象类中不一定要有抽象方法

    3. 如果一个类继承了抽象类,那么就要重写所有的抽象方法

      如果子类没有重写所有的抽象方法,那么子类也要定义为抽象类

9.3 static 静态

9.3.1 何为静态

static – 静态的

  1. 可以修饰属性和方法
  2. 被static修饰的属性在内存中只有一份
  3. 被static修饰的属性或方法如何访问:
    1. 类名.成员 //建议使用
    1. 对象名.成员
  4. 如果修改了被static修饰的属性的值,那么所有的对象都会被观察到
  5. static修饰的属性随着类的加载而加载,和是否创建对象无关 (不创建对象,static也存在)
    实例属性只能创建对象之后才能存在
9.3.2 实例属性(没有被static修饰的属性)和静态属性对比
  1. 实例属性是每个对象独有的,修改了一个对象的实例属性,对其他同名实例属性没有影响 – 拥有自己独立的空间
  2. 静态属性在内存中只有一份,是属于类的,修改了该属性的值,任何的对象都会观察到对该属性的修改
9.3.3 静态方法
  1. 什么是静态方法?就是被static修饰的方法
  2. 如何调用静态方法?
    1. 类名.方法名
    2. 对象名.方法名
  3. static修饰方法可以访问当前类被static修饰的属性,不能直接访问当前类的实例属性
    static的能够直接访问static的
  4. static修饰的方法不能出现this和super关键字
9.3.4 动态代码块

代码块 { } 方法 类 if switch 循环
动态代码块和静态代码块
共性:和属性、方法、构造方法平级

代码块:使用{ } 括起来的代码被称为代码块

动态代码块(构造代码块)

位置:类中方法外的一对大括号

  1. 创建对象时被调用,创建一次,调用一次
  2. 代码块执行和实例属性初始化优先于构造方法执行
  3. 代码块执行和实例属性初始化的顺序由代码编写的顺序决定,写在前面的先运行
  4. 应用:
    1. 可谓实例属性赋值,或必要的初始行为
    2. 将多个构造方法中,重复的代码,抽取到构造代码块中,从而提升代码的复用性
public class test {
    {
        //动态代码块
    }
    public static void main(String[] args) {

    }
}
9.3.5 静态代码块

位置:类中方法外的一对大括号,需要加入static关键字

  1. 类加载时被调用,只会调用一次
  2. 静态代码块和静态属性初始化优先于构造方法执行
  3. 静态代码块和静态属性初始化的顺序由代码编写的顺序决定,写在前面的先执行
  4. 应用:
    1. 可为静态属性赋值,或必要的初始行为
    2. 对数据进行初始化
static {
    //动态代码块
}
9.3.6 局部代码块

位置:方法中的一对大括号

作用:限定变量的生命周期,提早的释放内存

void method() {
    {
        //局部代码块
        int a = 10;
    }
}
9.3.7 对象创建过程
public class X {
    public X() {
        System.out.println("实例属性");
    }
}

public class Y {
    public Y() {
        System.out.println("静态属性");
    }
}

public class MyClass3 {
    private X x = new X();
    private static Y y = new Y();

    //动态代码块
    {
        System.out.println("动态代码块");
    }

    //静态代码块
    static {
        System.out.println("静态代码块");
    }
    
    public MyClass3() {
        System.out.println("构造方法...");
    }
}


public class MyTest5 {
    public static void main(String[] args) {
        MyClass3 c1 = new MyClass3();
        System.out.println("==========================");
        MyClass3 c2 = new MyClass3();
    }
}
/*
静态属性
静态代码块
实例属性
动态代码块
构造方法...
==========================
实例属性
动态代码块
构造方法...
*/
9.3.8 带有继承的对象创建过程
public class X {
    public X() {
        System.out.println("父类实例属性");
    }
}

public class Y {
    public Y() {
        System.out.println("父类静态属性");
    }
}

public class M {
    public M() {
        System.out.println("子类实例属性");
    }
}

public class N {
    public N() {
        System.out.println("子类静态属性");
    }
}

public class SuperClass {
    private X x = new X();
    private static Y y = new Y();

    //动态代码块
    {
        System.out.println("父类动态代码块");
    }

    //静态代码块
    static {
        System.out.println("父类静态代码块");
    }
    
    public SuperClass() {
        System.out.println("父类构造方法...");
    }
}

public class SubClass extends SuperClass {
    private M m = new M();
    private static N n = new N();

    //动态代码块
    {
        System.out.println("子类动态代码块");
    }

    //静态代码块
    static {
        System.out.println("子类静态代码块");
    }
    
    public SubClass() {
        System.out.println("子类构造方法...");
    }
}

public class MyTest6 {
    public static void main(String[] args) {
        SubClass subClass = new SubClass();
        System.out.println("***************************");
        SubClass subClass1 = new SubClass();
    }
}
/*
父类静态属性
父类静态代码块
子类静态属性
子类静态代码块
父类实例属性
父类动态代码块
父类构造方法...
子类实例属性
子类动态代码块
子类构造方法...
***************************
父类实例属性
父类动态代码块
父类构造方法...
子类实例属性
子类动态代码块
子类构造方法...
*/

代码块和属性的执行顺序和在类中定义的顺序有关

9.3.7 单例设计模式

什么是设计模式? – 解决问题的套路

1.单例设计模式 – 单个 一个 – 符合单例设计模式的类,对象只能创建一个
God Sun Moon 有且只能有一个
配置类 维护程序的配置 只能有一个
2.如何使既符合单例设计模式的类?
之前是如何创建对象的? new + 构造方法();
之前为什么能够创建多个对象? 构造方法可以被调用多次
关键:构造方法只能被调用一次/构造方法不能随便被调用
3.如何判断两个对象是同一个对象?/如何判断两个引用指向同一个对象?

​ == 的作用

  1. 判断基本数据类型的值是否相等
  2. 判断两个对象是否为同一个对象/判断两个引用是否指向同一个对象
//符合单例设计模式的类  饿汉式
public class SingleObj1 {
    private static SingleObj1 obj = new SingleObj1();
    private SingleObj1() {

    }

    public static SingleObj1 getInstance() {
        return obj;
    }
}
/*
是否 Lazy 初始化:否

是否多线程安全:是

实现难度:易

描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
*/

//单例设计模式    --  懒汉式
public class SingleObj2 {
    private static SingleObj2 obj;

    public SingleObj2() {
    }
    public static SingleObj2 getInstance() {
        if (obj == null) {
            obj = new SingleObj2();
        }
        return obj;
    }
}
/*
是否 Lazy 初始化:是

是否多线程安全:否

实现难度:易

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
*/

public class MyTest {
    public static void main(String[] args) {
        //SingleObj1 obj1 = new SingleObj1(); //不能被调用
        SingleObj1 obj1 = SingleObj1.getInstance();
        SingleObj1 obj2 = SingleObj1.getInstance();

        //如何判断两个对象是同一个对象?
        System.out.println(obj1 == obj2); //true

        SingleObj2 obj3 = SingleObj2.getInstance();
        SingleObj2 obj4 = SingleObj2.getInstance();

        System.out.println(obj3 == obj4); //true
    }
}

可以在更多工具的TODO中找到

-site:csdn.net 不想搜索到的关键字

暂时无法完成的内容,加 //TODO


十、 接口和内部类

10.1 何为接口

	Java接口是一系列方法的声明,是一些方法特征的集合,**一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)**。

	接口可以理解为一种特殊的类,里面全部是由***全局常量***和**公共的抽象方法**所组成。接口是解决***Java无法使用多继承***的一种手段,但是接口在实际中更多的作用是***制定标准***的。或者我们可以直接把接口理解为***100%的抽象类***,既接口中的方法**必须全部**是抽象方法。(JDK1.8之前可以这样理解)
 接口
1.生活中的接口?
  网口、USB接口、Type-C、VGA、HDMI
  1.“规范”
  2.“能力”
2.Java接口
  1.“规范”
  2.“能力”  **能不能**

声明规则/规范

10.2 接口定义

[修饰符] interface 接口名 {
    常量;
    抽象方法;
}
  1. 修饰符 public

  2. 接口使用interface关键字定义

  3. 接口名,起名字,要符合标识符的命名规范

  4. 接口中定义的属性都是常量,默认使用public static final 修饰
    public static final 默认有,可以不写
    int a = 10;

  5. 接口中的方法都是抽象方法,默认使用public abstract修饰
    public abstract 默认有,可以不写
    int test();

  6. 接口没有构造方法 – 接口不能创建对象

  7. 接口可以继承接口,并且支持多继承

    交通工具
    1.交通工具 - 能力
    2.加油 - 能力

10.3 如何使用

使用接口(如何实现接口 - 实现类实现接口 - 定义接口的实现类)

  • 类 - 继承
  • 接口 - 实现
[修饰符] class 实现类类名 implements 接口名1, 接口名2, 接口名3 {
    	属性
        方法
}
public class Car implements Vehicle, Energy {

}
  1. 修饰符 public
  2. 实现类类名,起名字,符合标志符的命名规则
  3. 一个实现类可以实现多个接口
  4. 如果一个类实现了接口,那么就要重写接口的所有抽象方法
    如果实现类没有重写所有的抽象方法,那么这个实现类就要被定义为抽象类
  5. 如果实现类既要继承父类,又要实现接口 – 先继承,再实现
[修饰符] class 实现类类名 extends 父类 implements 接口名1, 接口名2, 接口名3 {
    	属性
        方法
}
public class Car extends Cars implements Vehicle, Energy {

}

10.4 Java8关于接口的升级

JDK8接口特性

  1. 允许在接口中定义非抽象方法,但是需要使用关键字default修饰,这些方法就是默认方法

    • 作用:解决接口升级的问题

    • 注意事项:

      1. public可以省略,但是default不能省略
      2. 默认方法,实现类允许重写(不强制重写),但是要去掉default修饰符
      3. 如果实现了多个接口,多个接口中存在相同的默认方法,实现类必须重写默认方法

    默认方法定义格式

public default 返回值类型 方法名() {

}
public default void show() {}
  1. 允许定义静态方法

    • 理解:既然接口已经允许带有方法体了,干脆也放开静态方法,可以类名调用

    • 注意事项:

      1. public可以省略,但是static不能省略

      2. 接口中的静态方法,只允许接口名进行调用,不允许实现类通过对象调用

        ​ 接口.静态方法();

JDK9接口特性

  • 接口中允许定义私有方法 - 提升复用性,减少代码冗余

  • 接口升级,不能兼容已经存在的实现类的代码,必须对已经存在的实现类代码进行修改 – 需要修改的地方太多,成本很高

– 希望达到的效果 - 既要对接口进行升级,有对已经存在的实现类代码不会产生影响

–默认方法:

1. 被default关键字修饰的方法,可以有方法体
2. 通过实现类对象调用
3. 如果默认方法不能满足实现类的要求,就需要在实现类中对默认方法进行重写

静态方法 - 被static修饰 – 通过接口.静态方法()调用

可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性

public interface A {
    double PI = 3.14; //常量
    default void m1() {
        System.out.println("test1...");
    }
    
    public static void m2() {
        System.out.println("test2...");
    }
}

public class MyClass implements A {

}

public class MyTest3 {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.m1(); //通过实现类对象来调用
        A.m2(); //调用接口中的静态方法
    }
}

​ 若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现接口冲突

解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。

public interface M {
    default void test() {
        System.out.println("M....");
    }
}

public interface N {
    default void test() {
        System.out.println("N....");
    }
}

public class MyClass1 implements M, N {
    //实现类必须覆盖接口中同名同参数的方法,来解决冲突
    @Override
    public void test() {
        M.super.test();
        N.super.test();
        System.out.println("MyClass1....");
    }
}

public class MyTest4 {
    public static void main(String[] args) {
        MyClass1 myClass1 = new MyClass1();
        myClass1.test();
    }
}

若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有相同名称和参数的默认方法会被忽略。

public class SuperClass {
    public void test() {
        System.out.println("SuperClass....");
    }
}

public class MyClass2 extends SuperClass implements M, N {

}

public class MyTest5 {
    public static void main(String[] args) {
        MyClass2 myClass2 = new MyClass2();
        myClass2.test();
    }
}

10.5 接口多态

  1. 关于继承的多态
    子类对象赋值给父类引用/父类引用指向子类对象
  2. 关于接口的多态
    实现类对象赋值给接口引用/接口引用指向实现类对象
    Vehicle v1 = new Car();
    Vehicle v2 = new Fighter();
    编译时 - 看左边 - 在编译时,左边有的方法才可以调用
    运行时 - 看右边 - 运行期间,实际运行的是右边对象拥有的方法
  3. 接口多态的应用
    • 接口类型作为方法的参数
    • 接口类型作为方法的返回值
  4. 向下转型
    (类型)引用
    注意:向下转型要先判断引用本身的类型 - instanceof
public class MyTest {
    public static void main(String[] args) {
        Vehicle v1 = new Car();
        Vehicle v2 = new Fighter();
        test(v1);
        test(v2);
    }

	public static void test(Vehicle v) {
        if (v instanceof Car) {
            Car c = (Car) v;
            c.didi();
        } else if (v instanceof Fighter) {
            Fighter f = (Fighter) v;
            f.fight();
        }
    }
}

public static Vehicle create(String name) {
    switch (name) {
        case "car":
            return new Car();
        case "fighter":
            return new Fighter();
        default:
            return new Car();
    }
}

10.6 内部类

10.6.1 何为内部类
  • 定义在一个类里面的类
class Outer { //外部类
    //内部类
    class Inner {

    }
}
  • 创建对象的格式
//格式:外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象();
Outer.Inner in = new Outer().new Inner();
  • 成员访问细节
    1. 内部类中,访问外部类成员 : 直接访问,包括私有
    2. 外部类中,访问内部类成员 : 需要创建对象访问
    3. 在成员类中,访问所在外部类对象,格式:外部类名.this
public class MyTest1 {
    public static void main(String[] args) {
        Outer.Inner oi = new Outer().new Inner();
        System.out.println(oi.num);
        oi.show();
    }
}
class Outer {
    int num = 10;
    private void method() {
        System.out.println("method...");
        Inner i = new Inner();
        System.out.println(i.num);
    }
    class Inner {

        int num = 20;

        public void show() {
            int num = 30;

            System.out.println(num); //30
            System.out.println(this.num); //20
            System.out.println(Outer.this.num); //10

            System.out.println("show...");
            method();
        }
    }
}
/*
10
show...
method...
10
*/
  • 静态内部类 static修饰的成员内部类
class Outer {
    static class Inner {
        
    }
}
  • 创建对象格式
//格式:外部类名.内部类名 对象名 = new 外部类对象.内部类对象();
Outer.Inner in = new Outer.Inner();
  • 静态只能访问静态
public class MyTest1 {
    public static void main(String[] args) {
        Outer.Inner.show();
    }
}
class Outer {
    int num1 = 10;
    static int num2 = 20;
    static class Inner {
        public static void show() {
            System.out.println("show...");
            Outer outer = new Outer();
            //静态无法直接访问非静态
            System.out.println(outer.num1);
            System.out.println(num2);
        }
    }
}
/*
show...
10
20
*/
  • 局部内部类 放在方法、代码块、构造器等执行体中
public class MyTest1 {
    public static void main(String[] args) {
        //Outer.Inner.show();
        A a = new A();
        a.show();
    }
}
class A {
    public void show() {
        class B {
            public void method() {
                System.out.println("method...");
            }
        }
        B b = new B();
        b.method();
    }
}

内部类 - 在类的内部定义的类 - 匿名内部类
场景:类的对象只会创建一次,以后再也不会创建这个类的对象了 – 代码结构变得复杂

10.6.2 匿名内部类

本质:一个特殊的局部内部类(定义在方法内部)

前提:需要存在一个接口或类

格式:

new 类名/接口名() {
    
}
/*
new 类名() {}  : 代表继承这个类
new 接口名() {} : 代表实现这个接口
*/

个人理解:

​ 我们现在有一个方法,我们希望传入一个接口类型,由于接口类无法创建实例,所以我们只能传入该接口的实现类对象

1. 我们要创建一个实现类
1. 实现类中重写接口的抽象方法
1. 在调用的时候创建一个实现类对象,传入到方法中,运用多态的原理
public class MyTest1 {
    public static void main(String[] args) {
        useInter(new InterImpl());//传入的是新建的实现类对象
    }
    public static void useInter(Inter i ) {
        i.show();
    }
}
interface Inter {
    void show();
}
//创建接口的实现类
class InterImpl implements Inter {
    @Override
    public void show() {
        System.out.println("InterImpl...show...");
    }
}

​ 假若我们使用匿名内部类的话,只需要一步就可以完成

public class MyTest1 {
    public static void main(String[] args) {
        useInter(new Inter() {
            @Override
            public void show() {
            	System.out.println("匿名内部类");
            }
        });
    }
    public static void useInter(Inter i ) {
        i.show();
    }
}
interface Inter {
    void show();
}
public static void main(String[] args) {
    /*
        * {} - 匿名内部类的定义
        * new ... - 匿名内部类的对象
        * */
    test(new Vehicle() {
        @Override
        public void start() {
            System.out.println("start...");
        }

        @Override
        public void run() {
            System.out.println("run...");
        }

        @Override
        public void stop() {
            System.out.println("stop...");
        }
    });
}

10.7 Lambda表达式

10.7.1 何为Lambda
  • Lambda表达式是JDK8开始后的一中新语法形式

  • 作用:简化匿名内部类的代码写法

  • 格式:

    • () -> {}

    • () :匿名内部类被重写方法的形参列表

    • {} :被重写方法的方法体代码

    • -> :语法形式,无实际含义

    • //匿名内部类
      useInterA(new InterA() {
          @Override
          public void show() {
              System.out.println("匿名内部类重写后的方法");
          }
      });
      //Lambda
      useInterA(() -> {
          System.out.println("Lambda表达式重写后的方法");
      });
      
  • 限制:

    • Lambda表达式,只允许操作函数式编程接口 : 有,且只有一个抽象方法的接口

10.8 包

10.8.1 问题引入

苹果 Apple.java

苹果手机 Apple.java

  • 没有包存在的问题?
    无法创建同名的Java文件
    |
    想一种方式能够对同名文件进行区分
    |
    放在不同的文件夹中
10.8.2 何为包

包(package):Java提供的一种区别类的名字空间的机制,是类的组织方式,是一组相关类和接口的集合,它提供了访问权限和命名的管理机制。

  • 本质:文件夹/目录,功能相似的类放在同一目录下;
  • 对类进行了包装,在不同的包中允许有相同类名存在,在一定程度上可以避免命名冲突
10.8.3 包的使用
  1. 声明包
    package 包名;
    告诉编译器当前的类应该在哪个包下 – 报错
  2. 使用包
    什么时候用到? 使用到了特定的类时,就需要使用import导入
    全类名 = 包名 + 类名;
  • import 全类名;

    • import java.util.Scanner;
  • import 包名.*;

    • import java.util.*;
  • java.lang包是系统默认导入的包,不需要手动导入
    System String

    1. JDK 常用包介绍

java.lang:包括了Java语言程序设计的基础类;

java.util:包含集合、日期和各种实用工具类;

java.io:包含可提供数据输入、输出相关功能的类;

java.net:提供用于实现Java网络编程的相关功能类;

java.sql:提供数据库操作相关功能类。

注意:java.lang是默认会导入的包,不需要手动导入。

十一、 集合

11.1 何为接口

ArrayList

存放对象的容器,长度可变,定义了对多个对象操作的常用方法,可以实现类似数组的功能

与数组的区别:

  • 数组长度固定,集合长度可变
  • 数组可以存放基本数据类型和引用数据类型,集合只能存放引用数据类型

ArrayList长度可变原理:

  1. 当创建ArrayList集合容器的时候,底层会存在一个长度为10个大小的数组
  2. 当长度不满足要求时,扩容原数组1.5倍大小的新数组
  3. 将原数组数据,拷贝到新数组中
  4. 将新元素添加到新数组中

集合与数组的使用选择:

  • 数组:存储的元素个数固定不变
  • 集合:存储的元素个数经常发生改变

11.2 如何使用

  1. 构造方法:
    public ArrayList():创建一个空的集合容器
  2. 集合容器的创建细节:
    ArrayList list = new ArrayList();
    • 现象:可以添加任意类型数据
    • 弊端:数据不严谨
  3. 引入泛型
    <> :泛型
    • 作用:使用泛型,可以对集合中存储的数据,进行类型限制
    • 细节:反省中,不允许编写基本数据类型
    • 问题:怎样存储基本数据类型?
    • 解决:使用基本数据类型所对应的包装类
ArrayList list = new ArrayList();
list.add("张三");
list.add("李四");
list.add("王五");
System.out.println(list);
System.out.println(list.size());

ArrayList<Double> list1 = new ArrayList<>();
list1.add(11.1);
list1.add(22.2);
list1.add(33.3);
System.out.println(list1);
/*
[张三, 李四, 王五]
3
[11.1, 22.2, 33.3]
*/
  1. ArrayList常用方法

      • public void add(E element)
        • 添加指定元素
      • public void add(int index, E element)
        • 在指定索引位置,添加对应的元素
      ArrayList<String> list = new ArrayList<>();
      list.add("a");
      list.add("b");
      list.add("c");
      list.add(3, "d");
      System.out.println(list);
      /*
      [a, b, c, d]
      */
      
      • public E remove(int index)
        • 根据索引做删除,返回被删除掉的元素
      • public boolean remove(Obejct o)
        • 根据元素做删除,返回是否删除成功的状态
      System.out.println(list);
      list.remove(0);
      System.out.println(list);
      list.remove("b");
      System.out.println(list);
      /*
      [a, b, c, d]
      [b, c, d]
      [c, d]
      */
      
      • public E set(int index, E element)
        • 修改指定索引位置,改为对应元素,返回被覆盖掉的元素
      System.out.println(list);
      list.set(0, "a");
      System.out.println(list.set(1, "b"));
      System.out.println(list);
      /*
      [c, d]
      d
      [a, b]
      */
      
      • public E get(int index)
        • 根据索引,获取集合中的元素
      • public int size()
        • 返回集合中元素的个数
      System.out.println(list);
      System.out.println(list.get(0));
      System.out.println(list.size());
      /*
      [a, b]
      a
      2
      */
      
  2. ArrayList遍历

  • Lambda表达式是JDK8开始后的一中新语法形式

  • 作用:简化匿名内部类的代码写法

  • 格式:

    • () -> {}

    • () :匿名内部类被重写方法的形参列表

    • {} :被重写方法的方法体代码

    • -> :语法形式,无实际含义

    • //匿名内部类
      useInterA(new InterA() {
          @Override
          public void show() {
              System.out.println("匿名内部类重写后的方法");
          }
      });
      //Lambda
      useInterA(() -> {
          System.out.println("Lambda表达式重写后的方法");
      });
      
  • 限制:

    • Lambda表达式,只允许操作函数式编程接口 : 有,且只有一个抽象方法的接口

10.8 包

10.8.1 问题引入

苹果 Apple.java

苹果手机 Apple.java

  • 没有包存在的问题?
    无法创建同名的Java文件
    |
    想一种方式能够对同名文件进行区分
    |
    放在不同的文件夹中
10.8.2 何为包

包(package):Java提供的一种区别类的名字空间的机制,是类的组织方式,是一组相关类和接口的集合,它提供了访问权限和命名的管理机制。

  • 本质:文件夹/目录,功能相似的类放在同一目录下;
  • 对类进行了包装,在不同的包中允许有相同类名存在,在一定程度上可以避免命名冲突
10.8.3 包的使用
  1. 声明包
    package 包名;
    告诉编译器当前的类应该在哪个包下 – 报错
  2. 使用包
    什么时候用到? 使用到了特定的类时,就需要使用import导入
    全类名 = 包名 + 类名;
  • import 全类名;

    • import java.util.Scanner;
  • import 包名.*;

    • import java.util.*;
  • java.lang包是系统默认导入的包,不需要手动导入
    System String

    1. JDK 常用包介绍

java.lang:包括了Java语言程序设计的基础类;

java.util:包含集合、日期和各种实用工具类;

java.io:包含可提供数据输入、输出相关功能的类;

java.net:提供用于实现Java网络编程的相关功能类;

java.sql:提供数据库操作相关功能类。

注意:java.lang是默认会导入的包,不需要手动导入。

十一、 集合

11.1 何为接口

ArrayList

存放对象的容器,长度可变,定义了对多个对象操作的常用方法,可以实现类似数组的功能

与数组的区别:

  • 数组长度固定,集合长度可变
  • 数组可以存放基本数据类型和引用数据类型,集合只能存放引用数据类型

ArrayList长度可变原理:

  1. 当创建ArrayList集合容器的时候,底层会存在一个长度为10个大小的数组
  2. 当长度不满足要求时,扩容原数组1.5倍大小的新数组
  3. 将原数组数据,拷贝到新数组中
  4. 将新元素添加到新数组中

集合与数组的使用选择:

  • 数组:存储的元素个数固定不变
  • 集合:存储的元素个数经常发生改变

11.2 如何使用

  1. 构造方法:
    public ArrayList():创建一个空的集合容器
  2. 集合容器的创建细节:
    ArrayList list = new ArrayList();
    • 现象:可以添加任意类型数据
    • 弊端:数据不严谨
  3. 引入泛型
    <> :泛型
    • 作用:使用泛型,可以对集合中存储的数据,进行类型限制
    • 细节:反省中,不允许编写基本数据类型
    • 问题:怎样存储基本数据类型?
    • 解决:使用基本数据类型所对应的包装类
ArrayList list = new ArrayList();
list.add("张三");
list.add("李四");
list.add("王五");
System.out.println(list);
System.out.println(list.size());

ArrayList<Double> list1 = new ArrayList<>();
list1.add(11.1);
list1.add(22.2);
list1.add(33.3);
System.out.println(list1);
/*
[张三, 李四, 王五]
3
[11.1, 22.2, 33.3]
*/
  1. ArrayList常用方法

      • public void add(E element)
        • 添加指定元素
      • public void add(int index, E element)
        • 在指定索引位置,添加对应的元素
      ArrayList<String> list = new ArrayList<>();
      list.add("a");
      list.add("b");
      list.add("c");
      list.add(3, "d");
      System.out.println(list);
      /*
      [a, b, c, d]
      */
      
      • public E remove(int index)
        • 根据索引做删除,返回被删除掉的元素
      • public boolean remove(Obejct o)
        • 根据元素做删除,返回是否删除成功的状态
      System.out.println(list);
      list.remove(0);
      System.out.println(list);
      list.remove("b");
      System.out.println(list);
      /*
      [a, b, c, d]
      [b, c, d]
      [c, d]
      */
      
      • public E set(int index, E element)
        • 修改指定索引位置,改为对应元素,返回被覆盖掉的元素
      System.out.println(list);
      list.set(0, "a");
      System.out.println(list.set(1, "b"));
      System.out.println(list);
      /*
      [c, d]
      d
      [a, b]
      */
      
      • public E get(int index)
        • 根据索引,获取集合中的元素
      • public int size()
        • 返回集合中元素的个数
      System.out.println(list);
      System.out.println(list.get(0));
      System.out.println(list.size());
      /*
      [a, b]
      a
      2
      */
      
  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值