JavaSE

JavaSE

一,什么是JDK,JRE

1.JDK(Java Development Kit Java开发工具包)

JDK是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了JRE。所以安装了JDK,就不用在单独安装JRE了。
其中的开发工具:编译工具(javac.exe) 打包工具(jar.exe)等

2.JRE(Java Runtime Environment Java运行环境)

包括Java虚拟机(JVM Java Virtual Machine)和Java程序所需的核心类库等,如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。

3.jvm,jdk,jre关系图

在这里插入图片描述
在这里插入图片描述

二,关键字

1.用于定义数据类型的关键字
class, interface , enum , byte,short ,int , long ,float ,double,char,boolean ,void

2.用于定义流程控制的关键字
if , else,switch, case ,default,while ,do ,for ,break ,continue,return

3.用于定义访问权限修饰符的关键字
private,protected,public

4.用于定义类,函数,变量修饰符的关键字
abstract,final,static,synchronized

5.用于定义类与类之间关系的关键字
extends ,implements

6.用于定义建立实例及引用实例,判断实例的关键字
new ,this ,super ,instanceof

7.用于异常处理的关键字
try ,catch ,finally ,throw, throws

8.用于包的关键字
package ,import

9.其他修饰符关键字
native ,strictfp, transient, volatile ,assert

三,标识符

1.概念

Java 对各种变量、方法和类等要素命名时使用的字符序列称为标识符
技巧:凡是自己可以起名字的地方都叫标识符。

2.定义合法标识符规则

1.由26个英文字母大小写,0-9 ,_或 $ 组成
2.数字不可以开头。
3.不可以使用关键字和保留字,但能包含关键字和保留字。
4.Java中严格区分大小写,长度无限制。
5.标识符不能包含空格

3.Java中的名称命名规范

1.包名:多单词组成时所有字母都小写:xxxyyyzzz
2.类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
3.变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:xxxYyyZzz
4.常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ

四,变 量

1.变量的作用域

Java中每个变量必须先声明,后使用
使用变量名来访问这块区域的数据
变量的作用域:其定义所在的一对{ }内
变量只有在其作用域内才有效
同一个作用域内,不能定义重名的变量

2.变量的分类-按数据类型

在这里插入图片描述

3.变量的分类-按声明的位置的不同

 在方法体外,类体内声明的变量称为成员变量。
 在方法体内部声明的变量称为局部变量。
在这里插入图片描述

4.整数类型:byte、short、int、long

java的整型常量默认为 int 型,声明long型常量须后加‘l’或‘L’

5.浮点类型:float、double

 float:单精度,尾数可以精确到7位有效数字。很多情况下,精度很难满足需求。
 double:双精度,精度是float的两倍。通常采用此类型。
 Java 的浮点型常量默认为double型,声明float型常量,须后加‘f’或‘F’。

6.字符类型:char

char 型数据用来表示通常意义上“字符”(2字节)
Java中的所有字符都使用Unicode编码,故一个字符可以存储一个字母,一个汉字,或其他书面语的一个字符。
字符型变量的三种表现形式:
1.字符常量是用单引号(‘ ’)括起来的单个字符。例如:char c1 = ‘a’; char c2 = ‘中’; char c3 = ‘9’;
2.Java中还允许使用转义字符‘\’来将其后的字符转变为特殊字符型常量。例如:char c3 = ‘\n’; // '\n’表示换行符
3.直接使用 Unicode 值来表示字符型常量:‘\uXXXX’。其中,XXXX代表一个十六进制整数。如:\u000a 表示 \n。
char类型是可以进行运算的。因为它都对应有Unicode码

7.布尔类型:boolean

boolean类型数据只允许取值true和false,无null。

8.字符串类型:String

String不是基本数据类型,属于引用数据类型

五,基本数据类型转换

1.自动类型转换

1.容量小的类型自动转换为容量大的数据类型。
2.数据类型按容量大小排序为:
在这里插入图片描述
3.有多种类型的数据混合运算时,系统首先自动将所有数据转换成容量最大的那种数据类型,然后再进行计算。
4.byte,short,char之间不会相互转换,他们三者在计算时首先转换为int类型。
5.boolean类型不能与其它数据类型运算。
6.当把任何基本数据类型的值和字符串(String)进行连接运算时(+),基本数据类型的值将自动转化为字符串(String)类型。

2.强制类型转换

1.自动类型转换的逆过程,将容量大的数据类型转换为容量小的数据类型。使用时要加上强制转换符 (),但可能造成精度降低或溢出,格外要注意。
2.通常,字符串不能直接转换为基本类型,但通过基本类型对应的包装类则可以实现把字符串转换成基本类型。如: String a = “43”; int i = Integer.parseInt(a);

六,运算符

1.算术运算符

运算符运算范例结果
+正号+33
-负号b=4; -b-4
+
-
*
/
%取模(取余)7%52
++自增(前):先运算后取值a=2;b=++a;a=3;b=3
++自增(后):先取值后运算a=2;b=a++;a=3;b=2
自减(前):先运算后取值a=2;b=- -aa=1;b=1
自减(后):先取值后运算a=2;b=a- -a=1;b=2
+字符串连接“He”+”llo”“Hello”

如果对负数取模,可以把模数负号忽略不记,如:5%-2=1。 但被模数是负数则不可忽略。此外,取模运算的结果不一定总是整数。
对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。

2.赋值运算符

1.符号:=
2.当“=”两侧数据类型不一致时,可以使用自动类型转换或使用强制类型转换原则进行处理。
3.支持连续赋值。
4.扩展赋值运算符: +=, -=, *=, /=, %=
num+=2; 相当于 num=num+2;

3.比较运算符

在这里插入图片描述

4.逻辑运算符

在这里插入图片描述

“&”和“&&”的区别:

单&时,左边无论真假,右边都进行运算;
双&时,如果左边为真,右边参与运算,如果左边为假,那么右边不参与运算。 “|”和“||”的区别同理,||表示:当左边为真,右边不参与运算。

5.位运算符

位运算是直接对整数的二进制进行的运算
在这里插入图片描述

6.三元运算符

在这里插入图片描述
表达式1和表达式2为同种类型

七,程序流程控制

1.顺序结构

程序从上到下逐行地执行,中间没有任何判断和跳转。

2.分支结构

根据条件,选择性地执行某段代码。
有if…else和switch-case两种分支语句。

switch-case结构

switch(表达式){
case 常量1:
语句1;
// break;
case 常量2:
语句2;
// break; … …
case 常量N:
语句N;
// break;
default:
语句;
// break;
}

 switch(表达式)中表达式的值必须是下述几种类型之一:byte,short,char,int,枚举 (jdk 5.0),String (jdk 7.0);
 case子句中的值必须是常量,不能是变量名或不确定的表达式值;
 同一个switch语句,所有case子句中的常量值互不相同;
 break语句用来在执行完一个case分支后使程序跳出switch语句块;如果没有break,程序会顺序执行到switch结尾
 default子句是可任选的。同时,位置也是灵活的。当没有匹配的case时,执行default

3.循环结构

根据循环条件,重复性的执行某段代码。
有while、do…while、for三种循环语句。
注:JDK1.5提供了foreach循环,方便的遍历集合、数组元素。

for循环

1.语法格式
for (①初始化部分; ②循环条件部分; ④迭代部分){
③循环体部分;

2.执行过程:
①-②-③-④-②-③-④-②-③-④-…-②
3.说明:
 ②循环条件部分为boolean类型表达式,当值为false时,退出循环
 ①初始化部分可以声明多个变量,但必须是同一个类型,用逗号分隔
 ④可以有多个变量更新,用逗号分隔

    for (int i = 0,b=0,c=0; i < 100||b<200||c<201; i++,b++,c++) {
		
	}

while循环

1.语法格式
①初始化部分
while(②循环条件部分){
③循环体部分;
④迭代部分;
}
2.执行过程:
①-②-③-④-②-③-④-②-③-④-…-②
3.说明:
注意不要忘记声明④迭代部分。否则,循环将不能结束,变成死循环。
for循环和while循环可以相互转换

do-while循环

1.语法格式
①初始化部分;
do{
③循环体部分
④迭代部分
}while(②循环条件部分);

2.执行过程:
①-③-④-②-③-④-②-③-④-…②

3.说明:
do-while循环至少执行一次循环体。

八,Scanner从键盘获取值

		Scanner scanner=new Scanner(System.in);
		int nextInt = scanner.nextInt();
		System.out.println(nextInt);

九,关键字break、continue的使用

1.break的使用

break是终止本层循环。
break只能用于switch语句和循环语句中。
break语句用于终止某个语句块的执行
{ ……
break;
……
}
break语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块(否则默认跳出包裹此关键字最近的一层循环)(标号语句必须紧接在循环的头部。标号语句不能用在非循环语句的前面。)
label1: { ……
label2: { ……
label3: { ……
break label2;
……
} } }

2.continue的使用

continue是终止本次循环。
continue只能使用在循环结构中
continue语句用于跳过其所在循环语句块的一次执行,继续下一次循环
continue语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环

十,数组

1.概述

 数组(Array),是多个相同类型数据按一定顺序排列的集合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理。
 数组的常见概念:
1.数组名 2.下标(或索引) 3.元素 4.数组的长度
 数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型。
 创建数组对象会在内存中开辟一整块连续的空间,而数组名中引用的是这块连续空间的首地址。
 数组的长度一旦确定,就不能修改。
 我们可以直接通过下标(或索引)的方式调用指定位置的元素,速度很快。
 数组的分类:
1.按照维度:一维数组、二维数组、三维数组、…
2.按照元素的数据类型分:基本数据类型元素的数组、引用数据类型元素的数组(即对象数组)

2. 一维数组

 一维数组的声明方式:
type var[] 或 type[] var;
例如:
int a[];
int[] a1;
double b[];
String[] c; //引用类型变量数组

 Java语言中声明数组时不能指定其长度(数组中元素的数), 例如:( int a[5]; //非法)

动态初始化:

数组声明且为数组元素分配空间与赋值的操作分开进行

int[] arr = new int[3];
arr[0] = 3;
arr[1] = 9;
arr[2] = 8;

静态初始化:

在定义数组的同时就为数组元素分配空间并赋值。

int[] arr = new int[]{ 3, 9, 8};

定义并用运算符new为之分配空间后,才可以引用数组中的每个元素;

数组元素的引用方式:数组名[数组元素下标]
1.数组元素下标可以是整型常量或整型表达式。如a[3] , b[i] , c[6*i];
2.数组元素下标从0开始;长度为n的数组合法下标取值范围: 0 —>n-1;如int a[]=new int[3]; 可引用的数组元素为a[0]、a[1]、a[2]

每个数组都有一个属性length指明它的长度,例如:a.length 指明数组a的长度(元素个数)
数组一旦初始化,其长度是不可变的

数组是引用类型,它的元素相当于类的成员变量,因此数组一经分配空间,其中的每个元素也被按照成员变量同样的方式被隐式初始化。
1.对于基本数据类型而言,默认初始化值各有不同
2.对于引用数据类型而言,默认初始化值为null

3.二维数组

 对于二维数组的理解,我们可以看成是一维数组array1又作为另一个一维数组array2的元素而存在。其实,从数组底层的运行机制来看,其实没有多维数组。

格式1(动态初始化):int[][] arr = new int[3][2];
定义了名称为arr的二维数组
二维数组中有3个一维数组
每一个一维数组中有2个元素
一维数组的名称分别为arr[0], arr[1], arr[2]
给第一个一维数组1脚标位赋值为78写法是:arr[0][1] = 78;

格式2(动态初始化):int[][] arr = new int[3][];
二维数组中有3个一维数组。
每个一维数组都是默认初始化值null (注意:区别于格式1)
可以对这个三个一维数组分别进行初始化
arr[0] = new int[3]; arr[1] = new int[1]; arr[2] = new int[2];
注:(int[][]arr = new int[][3]; //非法)

格式3(静态初始化):int[][] arr = new int[][]{{3,8,2},{2,7},{9,0,1,6}};
定义一个名称为arr的二维数组,二维数组中有三个一维数组
每一个一维数组中具体元素也都已初始化
第一个一维数组 arr[0] = {3,8,2};
第二个一维数组 arr[1] = {2,7};
第三个一维数组 arr[2] = {9,0,1,6};
第三个一维数组的长度表示方式:arr[2].length;
在这里插入图片描述

4.Arrays工具类

java.util.Arrays类即为操作数组的工具类,包含了用来操作数组(比如排序和搜索)的各种方法。
在这里插入图片描述

5.数组使用中的常见异常

在这里插入图片描述

十一,面向对象

1.面向对象的三大特征

封装 (Encapsulation)
继承 (Inheritance)
多态 (Polymorphism)

2.成员变量与局部变量

 在方法体外,类体内声明的变量称为成员变量。
 在方法体内部声明的变量称为局部变量。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.方法

方法的声明格式:
修饰符 返回值类型 方法名(参数类型 形参1, 参数类型 形参2,….){
方法体程序代码
return 返回值;

方法的重载

重载的概念:
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。(与返回值类型、权限修饰符等无关,只看参数列表)

可变个数的形参

1 声明格式:方法名(参数的类型名 …参数名)
2.可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
3.可变个数形参的方法与同名的方法之间,彼此构成重载
4.可变参数方法的使用与方法参数部分使用数组是一致的
5.方法的参数部分有可变形参,需要放在形参声明的最后
6.在一个方法的形参位置,最多只能声明一个可变个数形参

方法参数的值传递机制(实参,形参)

形参:方法声明时的参数
实参:方法调用时实际传给形参的参数值

形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参

4.封装与隐藏

 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。

Java中通过将数据声明为私有的(private),再提供公共(public)
方法:getXxx()和setXxx()实现对该属性的操作,以实现下述目的:
1.隐藏一个类中不需要对外提供的实现细节;
2.使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作;
3.便于修改,增强代码的可维护性;

四种访问权限修饰符

Java权限修饰符public、protected、(缺省)、private置于类的成员定义前,用来限定对象对该类成员的访问权限。
在这里插入图片描述
四种访问权限修饰符可以用来修饰类(修饰类时只能用public或缺省)及类的内部结构:属性、方法、构造器、内部类。

5.关键字:this

(调用该方法的对象)
 它在方法内部使用,即这个方法所属对象的引用;
 它在构造器内部使用,表示该构造器正在初始化的对象。

在一个构造器中调用本类中其它的构造器

 可以在类的构造器中使用"this(形参列表)"的方式,调用本类中重载的其他的构造器!
 明确:构造器中不能通过"this(形参列表)"的方式调用自身构造器
 "this(形参列表)“必须声明在类的构造器的首行!
 在类的一个构造器中,最多只能声明一个"this(形参列表)”

6.package、import

package:
在这里插入图片描述
import注意:
如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类。

7.继承性

 Java只支持单继承和多层继承,不允许多重继承
1.一个子类只能有一个父类
2.一个父类可以派生出多个子类
子类不能直接访问父类中私有的(private)的成员变量和方法。(虽然不能直接调用但是子类已经获取可以通过其他方法调用)

方法的重写

定义:在子类中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
要求:
1.子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
2.父类被重写的方法的返回值类型是基本数据类型或者是void则子类重写的方法的返回值类型必须保持一致
父类被重写的方法的返回值类型是某一个类则子类重写的方法的返回值类型是这个类或是这个类的子类
3.子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限。子类不能重写父类中声明为private权限的方法
4. 子类方法抛出的异常不能大于父类被重写方法的异常
 注意:
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的。

关键字:super

父类的
在Java类中使用super来调用父类中的指定操作:
1.super可用于访问父类中定义的属性
2.super可用于调用父类中定义的成员方法
3.super可用于在子类构造器中调用父类的构造器

子类中所有的构造器默认都会访问父类中空参数的构造器 。当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的构造器。同时,只能”二选一”,且必须放在构造器的首行 。如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错(通过子类的构造器创建子类对象时,一定直接或者间接的调用了父类的构造器)

子类对象的实例化过程:
在这里插入图片描述

8.多态性

 前提:
1.需要存在继承或者实现关系
(父类类型=子类的对象,接口类型=实现类的对象)
2.有方法的重写

对象的多态性:父类的引用指向子类的对象
可以直接应用在抽象类和接口上

Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
若编译时类型和运行时类型不一致,就出现了对象的多态性
多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法) “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。(如果它不是晚绑定,它就不是多态)

instanceof 操作符

x instanceof A:检验x是否为类A的对象,返回值为boolean型。
1.要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
2.如果x属于类A的子类B,x instanceof A值也为true。

9. Object类的使用

在这里插入图片描述

==操作符与equals方法

= =:
1.基本类型比较值:只要两个变量的值相等,即为true。
2.引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,==才返回true。

 equals():
所有类都继承了Object,也就获得了equals()方法。还可以重写。
1.只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象。
格式:obj1.equals(obj2)
2.特例:当用equals()方法进行比较时,对类File、String、Date及包装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对象;原因:在这些类中重写了Object类的equals()方法。
3.当自定义使用equals()时,可以重写。用于比较两个对象的“内容”是否都相等

10.包装类

在这里插入图片描述

基本类型、包装类与String类间的转换

在这里插入图片描述

11.关键字:static

当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份
1.使用范围:
在Java类中,可用static修饰属性、方法、代码块、内部类
2.被修饰后具备以下特点:
随着类的加载而加载
优先于对象存在
修饰的成员,被所有对象所共享
访问权限允许时,可不创建对象,直接被类调用
 没有对象的实例时,可以用类名.方法名()的形式访问由static修饰的类方法。
 在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。
 因为不需要实例就可以访问static方法,因此static方法内部不能有this。(也不能有super)
 static修饰的方法不能被重写
3.类属性、类方法的设计思想
类属性作为该类各个对象之间共享的变量。在设计类时,分析哪些属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法。
如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。

12.单例 (Singleton)设计模式

设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。”套路”
 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构 造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。

饿汉式

class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化(静态方法只能用静态的)
private static Singleton single = new Singleton();
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
return single;
 } 
}

懒汉式

class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single = null;
// 3.提供公共的静态的方法,返回当前类的对象
public static synchronized Singleton getInstance() {
if(single == null) {
single = new Singleton();
}
return single;
 } 
}
class Singleton {
	// 1.私有化构造器
	private Singleton() {
	}

	// 2.内部提供一个当前类的实例
	// 4.此实例也必须静态化
	private static Singleton single = null;

	// 3.提供公共的静态的方法,返回当前类的对象
	public static Singleton getInstance() {
		if (single != null) {
			return single;
		}else {
			
			synchronized (Singleton.class) {
				if (single == null) {
					single = new Singleton();
				}
				return single;
			}
		}
	}
}

单例(Singleton)设计模式-应用场景:
 网站的计数器,一般也是单例模式实现,否则难以同步。
 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
 Application 也是单例的典型应用
 Windows的Task Manager (任务管理器)就是很典型的单例模式
 Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

13.main方法

 作为程序的入口
 由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
 又因为main() 方法是静态的,在此方法中我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。

14.代码块

 代码块(或初始化块)的作用:
对Java类或对象进行初始化
 代码块(或初始化块)的分类:
一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块(static block)。没有使用static修饰的,为非静态代码块。
 static代码块通常用于初始化static的属性:

class Person {
public static int total;
static {
total = 100;//为total赋初值
}
//其它属性或方法声明
}

静态代码块:用static 修饰的代码块

1.可以有输出语句。
2.可以对类的属性、类的声明进行初始化操作。
3.不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
4.若有多个静态的代码块,那么按照从上到下的顺序依次执行。
5.静态代码块的执行要先于非静态代码块。
6.静态代码块随着类的加载而加载同时执行,且只执行一次。

非静态代码块:没有static修饰的代码块

1.可以有输出语句。
2.可以对类的属性、类的声明进行初始化操作。
3.除了调用非静态的结构外,还可以调用静态的变量或方法。
4.若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
5.每次创建对象的时候,都会执行一次。且先于构造器执行。

15.程序中成员变量赋值的执行顺序

声明成员变量的默认初始化-------------> 显式初始化、多个初始化块依次被执行(同级别下按先后顺序执行)-------------> 构造器再对成员进行初始化操作------------> 通过”对象.属性”或”对象.方法”的方式,可多次给属性赋值

16.关键字:final

final标记的类不能被继承。提高安全性,提高程序的可读性。
final标记的方法不能被子类重写。
final标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次。(final标记的成员变量必须在声明时或在每个构造器中或代码块中显式赋值,然后才能使用)

17.抽象类与抽象方法

随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
 用abstract关键字来修饰一个类,这个类叫做抽象类。
 用abstract来修饰一个方法,该方法叫做抽象方法。
 抽象方法:只有方法的声明,没有方法的实现。以分号结束:
比如:public abstract void talk();
 含有抽象方法的类必须被声明为抽象类。
 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
 不能用abstract修饰变量、代码块、构造器;
 不能用abstract修饰私有方法、静态方法、final的方法、final的类。

抽象类(T)的匿名子类

	T t=new T() {
		@Override
		void a1() {
			//实现的父类中的抽象方法
		}
		
	};

模板方法设计模式(TemplateMethod)

抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。

解决的问题:
当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。

18. 接口(interface)

 接口(interface)是抽象方法和常量值定义的集合。

 接口的特点:
1.用interface来定义。
2.接口中的所有成员变量都默认是由public static final修饰的。
3.接口中的所有抽象方法都默认是由public abstract修饰的。
4.接口中没有构造器。
5.接口采用多继承机制

 定义Java类的语法格式:先写extends,后写implements
例: class SubClass extends SuperClass implements InterfaceA{ }
 一个类可以实现多个接口,接口也可以继承其它接口并且可以多继承
 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
 与继承关系类似,接口与实现类之间存在多态性

Java 8中,你可以为接口添加静态方法和默认方法。
静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。

默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。
注意:
1.若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现:接口冲突。
解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。
2.若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有相同名称和参数的默认方法会被忽略。

接口(Test)的匿名实现类

		Test n=new Test() {
			
			@Override
			public void a() {
				// 实现的接口中的抽象方法
				
			}

		};

代理模式(Proxy)

概述:
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。

public class T {
	public static void main(String[] args) {
		Network net = new ProxyServer(new RealServer());
		net.browse();
	}

}

interface Network {
	public void browse();
}

// 被代理类
class RealServer implements Network {
	@Override
	public void browse() {
		System.out.println("真实服务器上网浏览信息");
	}
}

//代理类
class ProxyServer implements Network {
	private Network network;

	public ProxyServer(Network network) {
		this.network = network;
	}

	public void check() {
		System.out.println("检查网络连接等操作");
	}

	public void browse() {
		check();
		network.browse();
	}
}

分类:
 静态代理(静态定义代理类)
 动态代理(动态生成代理类)

工厂方法模式

public class T {
	public static void main(String[] args) {
		Car a = new AudiFactory().getCar();
		Car b = new BydFactory().getCar();
		a.run();
		b.run();
	}
}

interface Car {
	void run();
}

//两个实现类
class Audi implements Car {
	public void run() {
		System.out.println("奥迪在跑");
	}
}

class BYD implements Car {
	public void run() {
		System.out.println("比亚迪在跑");
	}
}

//工厂接口
interface Factory {
	Car getCar();
}

//两个工厂类
class AudiFactory implements Factory {
	public Audi getCar() {
		return new Audi();
	}
}

class BydFactory implements Factory {
	public BYD getCar() {
		return new BYD();
	}
}

19.内部类

public class T {
	public static void main(String[] args) {
		
		T.A p1=new T.A();  // 实例化内部类(静态成员内部类)
		p1.a();
		
		T p=new T(); // 实例化内部类(非静态成员内部类)
		T.B a=p.new B();
		a.a();

	}
	
	
	static class A{
		
	    public static void a() {
	    	
	    }
	}
	
	
	
	class B{
		
	    public void a() {
	    	this.b(); // 调用非静态成员内部类B的方法b
	    	T.this.a();// 调用外部类T的方法a
	    }
	    
	    public void b() {
	    	
	    }
	}
	
	
	
    public void a() {
    	System.out.println("aasasda");
    	A.a(); // 调用静态成员内部类A的方法a
    }

    
    
}

注意:
非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员。

十二,异常处理

1.异常体系结构

在这里插入图片描述
红色:编译时异常
蓝色:运行时异常

2.异常处理机制:try-catch-finally

异常对象的生成:
1.由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例对象并抛出——自动抛出
2.由开发人员手动创建:Exception exception = new ClassCastException();——创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样。

try{
 //可能产生异常的代码
}
catch( NumberFormatException e ){
 //当产生NumberFormatException型异常时的处置措施
}
catch( ArrayStoreException e ){
 //当产生ArrayStoreException型异常时的处置措施
}finally{
 //无论是否发生异常,都无条件执行的语句
} 

getMessage() 获取异常信息,返回字符串
printStackTrace() 获取异常类名和异常信息,以及异常出
现在程序中的位置。返回值void。
不论在try代码块中是否发生了异常事件,catch语句是否执
行,catch语句是否有异常,catch语句中是否有return,finally块中的语句都会被执行。
finally语句和catch语句是任选的

3.异常处理机制:throws

throws:异常的处理方式,声明方法可能要抛出的各种异常类。

重写方法声明抛出异常的原则:

重写方法不能抛出比被重写方法范围更大的异常类型。

	public void a() throws Exception  {
		Exception exception = new Exception(); 
		throw exception;
	}

4.手动抛出异常

throw:异常的生成阶段,手动抛出异常对象
手动抛出的异常必须是Throwable或其子类的实例。

	public void a() throws Exception  {
		Exception exception = new Exception();
		throw exception;
	}
	public void a() {
		RuntimeException runtimeException = new RuntimeException(); // 运行时异常不必处理所以没有加throws
		throw runtimeException;
	}
	public static int a(int b) {
		if (b>0) {
			return 1;
		} else {

			RuntimeException runtimeException = new RuntimeException();
			throw runtimeException;
		}
	}

5.用户自定义异常类

public class Ex extends RuntimeException{

	/**
	 * 
	 */
	private static final long serialVersionUID = 6250516137986573513L;

	public Ex() {
		
	}
	
	public Ex(String message) {
		super(message);
	}
}

 一般地,用户自定义异常类都是RuntimeException的子类。
 自定义的异常通过throw抛出。
 自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字判断异常类型。

十三,多线程

1.基本概念:程序、进程、线程

1.程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
2.进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
3.线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
 若一个进程同一时间并行执行多个线程,就是支持多线程的
 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
一个进程中的多个线程共享相同的内存单元/内存地址空间---->它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
4.并行与并发
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

2.线程的创建和使用

Thread类构造器:
1.Thread():创建新的Thread对象
2.Thread(String threadname):创建线程并指定线程实例名
3.Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
4.Thread(Runnable target, String name):创建新的Thread对象

Thread类的有关方法:

void start(): 启动线程,并执行对象的run()方法
String getName(): 返回线程的名称
void setName(String name):设置该线程名称
static Thread currentThread(): 返回执行当前代码的线程(调用该方法的线程)。
static void yield():线程让步
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程(若队列中没有同优先级的线程,忽略此方法)
join():线程a中调用线程b的join( ),此时线程a就进入阻塞状态,直到线程b执行完后,线程a才结束阻塞状态。(低优先级的线程也可以获得执行)
static void sleep(long millis):(指定时间:毫秒)
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
抛出InterruptedException异常
stop(): 强制线程生命期结束,不推荐使用
boolean isAlive():返回boolean,判断线程是否还活着

线程的创建之继承Thread类

1.定义子类继承Thread类。
2.子类中重写Thread类中的run方法。
3.创建Thread子类对象,即创建了线程对象。
4.调用线程对象start方法:启动线程,调用run方法。

public class T {
	
	public static void main(String[] args) {
		
		ThreadTest a=new ThreadTest();
		ThreadTest b=new ThreadTest();
		a.start();
		b.start();
		for (int i = 0; i < 10; i++) {
			
			System.out.println("main"+i);
		}

	}
}

class ThreadTest extends Thread{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 10; i++) {
			
			System.out.println(this.getName()+i);
		}
	}	
}

线程的创建之实现Runnable接口

1.定义子类,实现Runnable接口。
2.子类中实现Runnable接口中的run方法。
3.创建实现类的对象。
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start( )

public class T1 {
	public static void main(String[] args) {
		RunnableTest runnableTest = new RunnableTest();
		Thread a = new Thread(runnableTest, "a");
		Thread b = new Thread(runnableTest, "b");

		a.start();
		b.start();
	}
}

class RunnableTest implements Runnable {

	int a = 20;
	int num = 0;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub

		while (a > 0) {
		
			a--;
			num++;
			System.out.println(Thread.currentThread().getName() + ":" + a);
		}
		System.out.println(num);

	}

}

线程的创建之实现Callable接口

1.创建一个实现Callable的实现类
2.实现call方法,线程需要执行的操作声明在call中
3.创建Callable实现类的对象
4.将Callable实现类的对象传递到FutureTask构造器中,创建FutureTask的对象
5.将FutureTask对象传递到Thread的构造器中,创建Thread的对象,并调用start( )
6.获取call方法的返回值

Future接口:
 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
 FutrueTask是Futrue接口的唯一的实现类
 FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
 Callable 需要依赖FutureTask ,FutureTask 也可以用作闭锁(闭锁详见juc)。

public class ThreadTest {

	public static void main(String[] args) {
		T a=new T();
		T c=new T();
		
		FutureTask futureTask = new FutureTask(a);
		FutureTask futureTask1 = new FutureTask(c);
		Thread b=new Thread(futureTask);
		Thread b1=new Thread(futureTask1);
		
		b.setName("b");
		b1.setName("b1");
		b.start();
		b1.start();
		
		try {
			Object object = futureTask.get();
			Object object2 = futureTask1.get();
			//get()方法执行完毕后main线程才会继续执行
			System.out.println("b执行了"+":"+object+"次");
			System.out.println("b1执行了"+":"+object2+"次");
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}
}


class T implements Callable{
   static int b=100;
    int num=0;
    static ReentrantLock lock=new ReentrantLock();
	@Override
	public Object call() throws Exception {
		// TODO Auto-generated method stub
		
		while(true) {
		lock.lock();
		try {
			if (b > 0) {
                num++;
				b--;
				System.out.println(Thread.currentThread().getName() +"操作:还剩"+ b);
				//System.out.println(lock);
			} else {
				break;
			}
		} finally {
			lock.unlock();
		}
	}
		return num;
		
	}

	
	
}

线程的创建之使用线程池

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。

线程池相关API:ExecutorService 和 Executors

 ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor:
1.void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
2. Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
3.void shutdown() :关闭连接池

 Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
1.Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
2.Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
3.Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
4.Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

 构造器解释:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
在这里插入图片描述
handler拒绝策略:
在这里插入图片描述maximumPoolSize最大线程数获取:
1.cpu密集型:cpu核数加1~2
2.IO密集型:
方法一:
由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2
方法二:
IO密集型时,大部分线程都阻塞,故需要多配置线程数:
参考公式:CPU核数 /(1 - 阻系数)
阻塞系数在0.8~0.9之间

int availableProcessors = Runtime.getRuntime().availableProcessors();
int maximumPoolSize=availableProcessors+1;

自定义线程池:

		ExecutorService a=new ThreadPoolExecutor(
				10,
				20,
				2L,
				TimeUnit.SECONDS ,
				new LinkedBlockingQueue<Runnable>(3),
				Executors.defaultThreadFactory(),
				new ThreadPoolExecutor.AbortPolicy()
				);

 流程:
在这里插入图片描述 例子:

public class Et {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10);
		Future submit = newFixedThreadPool.submit(new Te());
		Future submit1 = newFixedThreadPool.submit(new Te());
		System.out.println(submit1.get()+"  "+submit.get());
		newFixedThreadPool.shutdown();
	}
}

class Te implements Callable{
	   static int b=100;
	    int num=0;
	    static ReentrantLock lock=new ReentrantLock();
		@Override
		public Object call() throws Exception {
			// TODO Auto-generated method stub
			//Thread.sleep(100);
			while(true) {
			lock.lock();
			try {
				if (b > 0) {
	                num++;
					b--;
					System.out.println(Thread.currentThread().getName() +"操作:还剩"+ b);
					//System.out.println(lock);
				} else {
					break;
				}
			} finally {
				lock.unlock();
			}
		}
			return num;
			
		}
}

注意点:

1.如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
2.run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
3.想要启动多线程,必须调用start方法。
4.一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”。

3.线程的优先级

线程的优先级等级:
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5

涉及的方法

1.getPriority() :返回线程优先值
2.setPriority(int newPriority) :改变线程的优先级

说明:
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

4.线程的生命周期

要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
1.新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
2.就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
3.运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
4.阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
5.死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

在这里插入图片描述

5.线程的同步

操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行

Synchronized的使用方法

1.同步代码块:
synchronized (同步监视器){
// 需要被同步的代码
}
操作共享数据(多个线程共同操作的变量)的代码,即为需要被同步的代码

2.synchronized还可以放在方法声明中,表示整个方法为同步方法。
例如:
public synchronized void show (String name){

}

synchronized的锁:

1.任意对象都可以作为同步锁,但必须确保使用同一个资源的多个线程共用一把锁(共用的是同一个对象)。所有对象都自动含有单一的锁(监视器)。
2.同步方法的锁:静态方法(类名.class)、非静态方法(this)
3.同步代码块的锁:自己指定,很多时候也是指定为this或类名.class
4.释放锁的操作:
当前线程的同步方法、同步代码块执行结束。
当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

5.不会释放锁的操作:
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。

线程的死锁问题

1.不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
2.出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

解决方法:
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步

死锁代码演示:

public class DeadLockTest {
	public static void main(String[] args) {
		final StringBuffer s1 = new StringBuffer();
		final StringBuffer s2 = new StringBuffer();
		new Thread() {
			public void run() {
				synchronized (s1) {
					s2.append("A");
					synchronized (s2) {
						s2.append("B");
						System.out.print(s1);
						System.out.print(s2);
					}
				}
			}
		}.start();
		new Thread() {
			public void run() {
				synchronized (s2) {
					s2.append("C");
					synchronized (s1) {
						s1.append("D");
						System.out.print(s2);
						System.out.print(s1);
					}
				}
			}
		}.start();
	}
}

Lock(锁)的使用方法

 java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

public class T2 {

	public static void main(String[] args) {
	
		ThreadTest2 a=new ThreadTest2();
		ThreadTest2 b=new ThreadTest2();
		a.start();
		b.start();
		

	}
}

class ThreadTest2 extends Thread {

	private static int tick = 100;
	
	private static ReentrantLock lock=new ReentrantLock();

	@Override
	public void run() {
		while (true) {
			

					lock.lock();
					try {
						if (tick > 0) {

							System.out.println(Thread.currentThread().getName() + "售出车票,tick号为:" + tick--);
						} else {
							break;
						} 
					} finally {
						lock.unlock();
					}
	
		}

	}
}

6.线程的通信

wait() 与 notify() 和 notifyAll()

 wait():令当前线程挂起并放弃CPU、同步资源并等待(并释放锁),使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
 notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待。被唤醒的线程再抢到锁后会从上次的wait( )后继续执行。
 notifyAll ():唤醒正在排队等待资源的所有线程结束等待。
这三个方法只有在synchronized方法或synchronized代码块中才能使用三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会报java.lang.IllegalMonitorStateException异常。
 因为这三个方法的调用者必须是同步代码块或同步方法中的同步监视器(锁),而任意对象都可以作为synchronized的同步锁,因此这三个方法都定义在Object类中。
生产者消费者实例详见juc

Condition 控制线程通信

 Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同
 在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是await、signal 和 signalAll,它们在lock锁中使用
 Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得Condition 实例,请使用其 newCondition() 方法。

public class ApplicationMain {
	public static void main(String[] args) throws InterruptedException {
		RunnableTest a=new RunnableTest();
		Thread b=new Thread(a);
		b.start();
		for (int i = 0; i < 100; i++) {
			new Thread(a).start();
		}
		
	}

}

class RunnableTest implements Runnable{
	
    private int num=10;
    private int xnum=0;
    private ReentrantLock lock=new ReentrantLock();
    Condition a=lock.newCondition();
	@Override
	public void run() {

		lock.lock();
		try {	
			xnum++;
	
			if (num > 0) {
				
				if (xnum==2) {
					System.out.println(Thread.currentThread().getName());
					a.await();
					return ;
				}else if (xnum==5) {
					a.signalAll();
				} 
				
				num--;

				System.out.println(num+"成功"+Thread.currentThread().getName());
			} 
			
		} catch (InterruptedException e) {
	
			e.printStackTrace();
			
		} finally {
			lock.unlock();
		}

		
	}
	
	
	
}

十四,Java常用类

1.字符串相关的类

String的特性:

 String类:代表字符串。Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。
 String是一个final类,不可被继承。
 字符串代表不可变的字符序列(对字符串重新赋值时或对现有字符串进行连接操作时或调用String的replace( )方法修改指定字符时,都需要重新指定内存区域并赋值)。
 String对象的字符内容是存储在一个字符数组value[]中的。
通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串的值声明在字符串常量池中
字符串常量池中不会存储相同内容的字符串
 常量与常量的拼接结果在常量池。但拼接中只要其中有一个是变量,结果就在堆中(并指向常量池),如果拼接的结果调用intern()方法,返回值就在常量池中。

String str1 = “abc”;与String str2 = new String(“abc”);的区别:
在这里插入图片描述

字符串对象的存储:

在这里插入图片描述

String常用方法:

 int length():返回字符串的长度: return value.length
 char charAt(int index): 返回某索引处的字符return value[index]
 boolean isEmpty():判断是否是空字符串:return value.length == 0
 String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
 String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
 boolean equals(Object obj):比较字符串的内容是否相同
 boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
 String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
 int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
 boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
 boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
 boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
 int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
 int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
 int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
 int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
 String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用newChar 替换此字符串中出现的所有 oldChar 得到的。
 String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
 String replaceAll(String regex, String replacement) : 使 用 给 定 的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
 String replaceFirst(String regex, String replacement) : 使 用 给 定 的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
 boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
 String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
 String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

String与字符数组转换

 字符数组 ---------> 字符串
调用String 类的构造器:String(char[]) 和 String(char[],int offset,int length) 分别用字符数组中的全部字符和部分字符创建字符串对象。
 字符串 --------> 字符数组
public char[] toCharArray():将字符串中的全部字符存放在一个字符数组中的方法。
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):提供了将指定索引范围内的字符串存放到数组中的方法

String与字节数组转换

 字节数组 ----->字符串
1.String(byte[]):通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
2.String(byte[],int offset,int length) :用指定的字节数组的一部分,即从数组起始位置offset开始取length个字节构造一个字符串对象。
 字符串 -----> 字节数组
1.public byte[] getBytes() :使用平台的默认字符集将此 String 编码为byte 序列,并将结果存储到一个新的 byte 数组中。
2.public byte[] getBytes(String charsetName) :使用指定的字符集将此String编码到 byte 序列,并将结果存储到新的 byte 数组。

StringBuffer类

 代表可变的字符序列,可以对字符串内容进行增删,此时不会产生新的对象,效率低、线程安全。

 StringBuffer类不同于String,其对象必须使用构造器生成。有三个构造器:
1.StringBuffer():初始容量为16的字符串缓冲区
2.StringBuffer(int size):构造指定容量的字符串缓冲区
3.StringBuffer(String str):将内容初始化为指定字符串内容
注意:如果要添加的数据底层数组放不下了,就需要扩容数组(默认扩容为原来容量的2倍+2,并将原来数组中的元素复制到新的数组中)

常用方法:
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse() :把当前字符序列逆转
public int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
public String substring(int start,int end):返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串
public int length()
public char charAt(int n ):返回某索引处的字符
public void setCharAt(int n ,char ch):在指定索引处的字符被设置为ch

StringBuilder类

 可变字符序列、效率高、线程不安全。
常用方法:见StringBuffer类常用方法

2.日期时间API

1.java.lang.System类

System类提供的public static long currentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差(时间戳)。

2.java.util.Date类

表示特定的瞬间,精确到毫秒

构造器:
Date():使用无参构造器创建对象,获取本地当前时间。
Date(long date):创建指定毫秒数的date对象。

常用方法:
 getTime():返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
 toString():把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy 其中:dow 是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat),zzz是时间标准。

3.java.text.SimpleDateFormat类

java.text.SimpleDateFormat类是一个不与语言环境有关的方式来格式化和解析日期的具体类。
允许进行格式化:日期—>文本、允许解析:文本---->日期
构造器:
 SimpleDateFormat() :默认的模式和语言环境创建对象
 public SimpleDateFormat(String pattern):该构造方法可以用参数pattern指定的格式创建一个对象。

格式化:
public String format(Date date):方法格式化时间对象date

		SimpleDateFormat b=new SimpleDateFormat();
		Date a=new Date();
		
		String format = b.format(a);
		
		System.out.println(a.toString());
		System.out.println(format);
		
		/*打印的结果:
		 * Thu Jul 16 10:23:14 CST 2020 
		 * 
		 * 20-7-16 上午10:23
		 */

解析:
public Date parse(String source):从给定字符串的开始解析文本,以生成一个日期。

		SimpleDateFormat b=new SimpleDateFormat("yyyy年MM月dd日 EEE HH:mm:ss");
		
          String format = b.format(new Date());// 格式化
          System.out.println(format);
		try {
			Date parse = b.parse(format); // 解析 
			System.out.println(parse);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		/*
		 * 2020年07月16日 星期四 10:43:42 
		 * Thu Jul 16 10:43:42 CST 2020
		 */

在这里插入图片描述

4. java.util.Calendar(日历)类

Calendar是一个抽象基类,主用用于完成日期字段之间相互操作的功能。

获取Calendar实例(实例表示当前时间)的方法:
 使用Calendar.getInstance()方法
 调用它的子类GregorianCalendar的构造器。

常用方法:
 一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、MINUTE、SECOND

*注意: 获取月份时:一月是0,二月是1,以此类推,12月是11 获取星期时:周日是1,周二是2 , 。。。。周六是7 *所以推荐使用新时间日期API

		Calendar instance = Calendar.getInstance();// 获取当前时间的日历类
		int i = instance.get(Calendar.DAY_OF_MONTH);
		// 获得当前时间是这个月的第几天

 public void set(int field,int value)

// 修改当前时间为这个月的第2天
instance.set(Calendar.DAY_OF_MONTH, 2);

 public void add(int field,int amount)

// 在当前时间是这个月的第几天基础上减去1
instance.add(Calendar.DAY_OF_MONTH, 1);

 public final Date getTime()

// 转换为date
Date time = instance.getTime();

 public final void setTime(Date date)

// 将指定的时间转换为日历类
instance.setTime(new Date());

3.新时间日期API

 Java 8 吸收了 Joda-Time 的精华,以一个新的开始为 Java 创建优秀的 API。新的 java.time 中包含了所有关于本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了日期时间和本地化的管理。
java.time – 包含值对象的基础包
java.time.chrono – 提供对不同的日历系统的访问
java.time.format – 格式化和解析时间和日期
java.time.temporal – 包括底层框架和扩展特性
java.time.zone – 包含时区支持的类

LocalDate、LocalTime、LocalDateTime

LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储生日、纪念日等日期。
LocalTime表示一个时间,而不是日期。
LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。
在这里插入图片描述

	LocalDate now = LocalDate.now();

瞬时:Instant

Instant:时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳。Instant的精度可以达到纳秒级。
在这里插入图片描述

格式化与解析日期或时间

java.time.format.DateTimeFormatter 类:
 预定义的标准格式。如ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME

		LocalDate now = LocalDate.now();
		DateTimeFormatter a=DateTimeFormatter.ISO_DATE;
		String format = a.format(now);
		System.out.println(format);

 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.LONG)

		LocalDate now = LocalDate.now();
		DateTimeFormatter ofLocalizedDate = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);
		String format2 = ofLocalizedDate.format(now);
        System.out.println(format2);

 自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)

		LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
        
        String format3 = ofPattern.format(now); // 格式化
        TemporalAccessor parse = ofPattern.parse(format3); // 解析
        System.out.println(format3);
        System.out.println(parse);

在这里插入图片描述

Duration:用于计算两个“时间”间隔,以秒和纳秒为基准

		//Duration:用于计算两个“时间”间隔,以秒和纳秒为基准
		LocalTime localTime = LocalTime.now();
		LocalTime localTime1 = LocalTime.of(15, 23, 32);
		//between():静态方法,返回Duration对象,表示两个时间的间隔
		Duration duration = Duration.between(localTime1, localTime);
		System.out.println(duration);
		System.out.println(duration.getSeconds());
		System.out.println(duration.getNano());
		
		LocalDateTime localDateTime = LocalDateTime.of(2016, 6, 12, 15, 23, 32);
		LocalDateTime localDateTime1 = LocalDateTime.of(2017, 6, 12, 15, 23, 32);
		Duration duration1 = Duration.between(localDateTime1, localDateTime);
		System.out.println(duration1.toDays());

Period:用于计算两个“日期”间隔,以年、月、日衡量

//Period:用于计算两个“日期”间隔,以年、月、日衡量
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.of(2028, 3, 18);
Period period = Period.between(localDate, localDate1);
System.out.println(period);
System.out.println(period.getYears());
System.out.println(period.getMonths());
System.out.println(period.getDays());

与传统日期处理的转换

在这里插入图片描述

4. Java比较器

 在Java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题。
 Java实现对象排序的方式有两种:
1.自然排序:java.lang.Comparable
2.定制排序:java.util.Comparator

自然排序:java.lang.Comparable

 Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。
 实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。如果当前对象this大 于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回负整数,如果当前对象this等于形参对象obj,则返回零
实现Comparable接口的对象列表(和数组)可以通过 Collections.sort 或Arrays.sort进行自动排序

Comparable 的典型实现:(默认都是从小到大排列的,以下对应的类已经实现不用自己写)
1.String:按照字符串中字符的Unicode值进行比较
2.Character:按照字符的Unicode值来进行比较
3.数值类型对应的包装类以及BigInteger、BigDecimal:按照它们对应的数值大小进行比较
4.Boolean:true 对应的包装类实例大于 false 对应的包装类实例
5.Date、Time等:后面的日期时间比前面的日期时间大

实例:

public class Test {

	public static void main(String[] args) {

		Goods[] a=new Goods[5];
		a[0] = new Goods("a",12);
		a[1] = new Goods("d",12);
		a[2]= new Goods("f",11);
		a[3] = new Goods("a",12);
		a[4] = new Goods("a",12);
		Arrays.sort(a);
		for (Goods goods : a) {
			System.out.println(goods);
		}
	}

}

class Goods implements Comparable {
	private String name;
	private double price;

public Goods(String name, double price) {
		super();
		this.name = name;
		this.price = price;
	}

public String getName() {
		return name;
	}

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

	public double getPrice() {
		return price;
	}

	public void setPrice(double price) {
		this.price = price;
	}
	

	@Override
	public String toString() {
		return "Goods [name=" + name + ", price=" + price + "]";
	}


	@Override
	public int compareTo(Object o) {
		if (o instanceof Goods) {
			Goods other = (Goods) o;
			if (Double.compare(this.price, other.price)==0) {
				// 如果price一样再用name比较大小
				return this.name.compareTo(other.name);
			} else  {
				// 先price比较大小
				return Double.compare(this.price, other.price);
				
			}
			
		}
		throw new RuntimeException("输入的数据类型不一致");
	}
}

定制排序:java.util.Comparator

 当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 的对象来排序,强行对多个对象进行整体排序的比较。
 重写compare(Object o1,Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2
可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),从而允许在排序顺序上实现精确控制
 还可以使用 Comparator 来控制某些数据结构(如有序 set或有序映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序。

实例:

public class Test {

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static void main(String[] args) {
		
		
		Goods[] a=new Goods[5];
		a[0] = new Goods("a",12);
		a[1] = new Goods("d",12);
		a[2]= new Goods("f",11);
		a[3] = new Goods("a",129);
		a[4] = new Goods("a",12);
		Arrays.sort(a,new Comparator() {

			@Override
			public int compare(Object o1, Object o2) {
				if (o1 instanceof Goods && o2 instanceof Goods) {
					Goods o11 = (Goods) o1;
					Goods o22 = (Goods) o2;
					if (Double.compare(o11.getPrice(), o22.getPrice())==0) {
					return -o11.getName().compareTo(o22.getName());
						
					}else {
						
						return Double.compare(o11.getPrice(), o22.getPrice());
					}
					
					
					
				}
				throw new RuntimeException("非法数据");
			}
		});
		
		for (Goods goods : a) {
			System.out.println(goods);
		}
	}

}

class Goods implements Comparable {
	private String name;
	private double price;

public Goods(String name, double price) {
		super();
		this.name = name;
		this.price = price;
	}

public String getName() {
		return name;
	}

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

	public double getPrice() {
		return price;
	}

	public void setPrice(double price) {
		this.price = price;
	}
	

	@Override
	public String toString() {
		return "Goods [name=" + name + ", price=" + price + "]";
	}            

    
	/**
	 *上边使用了定制排序,以下的自然排序在本次排序中失效
	 */
	@Override
	public int compareTo(Object o) {
		if (o instanceof Goods) {
			Goods other = (Goods) o;
			if (Double.compare(this.price, other.price)==0) {
				// 如果price一样再用name比较大小
				return this.name.compareTo(other.name);
			} else  {
				// 先price比较大小
				return Double.compare(this.price, other.price);
				
			}
			
		}
		throw new RuntimeException("输入的数据类型不一致");
	}
}

5. System类

 System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。
 由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。
 成员变量
System类内部包含in、out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
 成员方法
1.native long currentTimeMillis(): 该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间(格林威治时间)1970年1月1号0时0分0秒所差的毫秒数。
2.void exit(int status): 该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。
3.String getProperty(String key): 该方法的作用是获得系统中属性名为key的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:
在这里插入图片描述

6.Math类

java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为double型。

abs 绝对值
acos,asin,atan,cos,sin,tan 三角函数
sqrt 平方根
pow(double a,doble b) a的b次幂
log 自然对数
exp e为底指数
max(double a,double b)
min(double a,double b)
random() 返回0.0到1.0的随机数
long round(double a) double型数据a转换为long型(四舍五入)
toDegrees(double angrad) 弧度—>角度
toRadians(double angdeg) 角度—>弧度

7.BigInteger与BigDecimal

BigInteger类
 Integer类作为int的包装类,能存储的最大整型值为231-1,Long类也是有限的,最大为263-1。如果要表示再大的整数,不管是基本数据类型还是他们的包装类都无能为力,更不用说进行运算了。
 java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger 提供所有 Java 的基本整数操作符的对应物,并提供 java.lang.Math 的所有相关方法。另外,BigInteger 还提供以下运算:模算术、GCD 计算、质数测试、素数生成、位操作以及一些其他操作。
 构造器 :
BigInteger(String val):根据字符串构建BigInteger对象
 常用方法:
1.ublic BigInteger abs():返回此 BigInteger 的绝对值的 BigInteger。
2.BigInteger subtract(BigInteger val) :返回其值为 (this - val) 的 BigInteger
3.BigInteger multiply(BigInteger val) :返回其值为 (this * val) 的 BigInteger
4.BigInteger divide(BigInteger val) :返回其值为 (this / val) 的 BigInteger。整数相除只保留整数部分。
5.BigInteger remainder(BigInteger val) :返回其值为 (this % val) 的 BigInteger。
6.BigInteger[] divideAndRemainder(BigInteger val):返回包含 (this / val) 后跟(this % val) 的两个 BigInteger 的数组。
7.BigInteger pow(int exponent) :返回其值为 (thisexponent) 的 BigInteger。
8.BigInteger add(BigInteger val) :返回其值为 (this + val) 的 BigInteger

BigDecimal类
 一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中,要求数字精度比较高,故用java.math.BigDecimal类。 BigDecimal类支持不可变的、任意精度的有符号十进制定点数。
 构造器:
public BigDecimal(double val)
public BigDecimal(String val)
 常用方法:
1.public BigDecimal add(BigDecimal augend)
2.public BigDecimal subtract(BigDecimal subtrahend)
3.public BigDecimal multiply(BigDecimal multiplicand)
4.public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)

十五,枚举类(enum)的使用

 类的对象只有有限个,确定的。
 当需要定义一组常量时,强烈建议使用枚举类
 若枚举只有一个对象, 则可以作为一种单例模式的实现方式。

使用说明:
使用 enum 定义的枚举类默认继承了 java.lang.Enum类,因此不能再继承其他类
枚举类的构造器只能使用 private 权限修饰符
枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾)。列出的实例系统会自动添加 public static final 修饰
必须在枚举类的第一行声明枚举类对象

Enum类的主要方法:
 values()方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
 valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如不是,会有运行时异常:IllegalArgumentException。
 toString():返回当前枚举类对象常量的名称

枚举类实例:

public enum Teste {

	SPRING("春天", "春风又绿江南岸"),
	SUMMER("夏天", "映日荷花别样红"),
	AUTUMN("秋天", "秋水共长天一色"),
	WINTER("冬天", "窗含西岭千秋雪");
	
	private final String seasonName;
	private final String seasonDesc;

	private Teste(String seasonName, String seasonDesc) {
		this.seasonName = seasonName;
		this.seasonDesc = seasonDesc;
	}

	public String getSeasonName() {
		return seasonName;
	}

	public String getSeasonDesc() {
		return seasonDesc;
	}

}
public class TesteApplication {
	
	public static void main(String[] args) {
		
		Teste valueOf = Teste.valueOf("SUMMER");
		System.out.println(valueOf.getSeasonDesc());
		
		Teste[] values = Teste.values();
		System.out.println(Arrays.toString(values));
	}
}

实现接口的枚举类:

若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可。
若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式, 则可以让每个枚举值分别来实现该方法

 enum T51 implements T5 {

	SPRING("春天", "春风又绿江南岸"){
		@Override
		public void a() {
			// TODO Auto-generated method stub
			System.out.println("春天");
		}
	},
	SUMMER("夏天", "映日荷花别样红"){
		@Override
		public void a() {
			// TODO Auto-generated method stub
			System.out.println("夏天");
		}
	},
	AUTUMN("秋天", "秋水共长天一色"),
	WINTER("冬天", "窗含西岭千秋雪");
	
	private final String seasonName;
	private final String seasonDesc;

	private T51(String seasonName, String seasonDesc) {
		this.seasonName = seasonName;
		this.seasonDesc = seasonDesc;
	}

	public String getSeasonName() {
		return seasonName;
	}

	public String getSeasonDesc() {
		return seasonDesc;
	}

	@Override
	public void a() {
		// TODO Auto-generated method stub
		System.out.println("季节");
	}


}

public interface T5{
	void a();
}
public class TesteApplication {
	
	public static void main(String[] args) {
		T51.WINTER.a();
		T51.SUMMER.a();

	}
}

十六,注解(Annotation)

Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。
Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation 的 “name=value” 对中。

自定义 Annotation:
 定义新的 Annotation 类型使用 @interface 关键字
 自定义注解自动继承了java.lang.annotation.Annotation接口
 Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组。
 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始值可使用 default 关键字
 如果只有一个参数成员,建议使用参数名为value
 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
 没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数据 Annotation。
注意:自定义注解必须配上注解的信息处理流程(使用反射)才有意义

JDK 中的元注解用于修饰其他注解的注解
@Retention: 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命周期, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用@Rentention 时必须为该 value 成员变量指定值:
1.RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
2.RetentionPolicy.CLASS:在class文件中有效(即class保留) , 当运行 Java 程序时, JVM 不会保留注解。 这是默认值
3.RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会保留注释。程序可以通过反射获取该注释。
在这里插入图片描述
@Target: 用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于修饰哪些程序元素。@Target 也包含一个名为 value 的成员变量。
在这里插入图片描述
@Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。定义为Documented的注解必须设置Retention值为RUNTIME。
@Inherited: 被它修饰的 Annotation 将具有继承性。如果某个类使用了被@Inherited 修饰的 Annotation, 则其子类将自动具有该注解。
可重复注解:
在这里插入图片描述
 JDK1.8之后,关于元注解@Target的参数类型ElementType枚举值多了两个:TYPE_PARAMETER,TYPE_USE。
1.ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
2.ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。

十七,Java集合

1.概述:

Java 集合可分为 Collection 和 Map 两种体系:

Collection接口:单列数据,定义了存取一组对象的方法的集合
1.List:元素有序、可重复的集合
2.Set:元素无序、不可重复的集合
 Map接口:双列数据,保存具有映射关系“key-value对”的集合

Collection接口继承树:

在这里插入图片描述

Map接口继承树:

在这里插入图片描述

2.Collection接口

概述

Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。

常用方法:

1.添加

 add(Object obj)
 addAll(Collection coll)
2.获取有效元素的个数
 int size()
3.清空集合
 void clear()

4.是否是空集合

 boolean isEmpty()

5.是否包含某个元素

 boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象
 boolean containsAll(Collection c):也是调用元素的equals方法来比较的。拿两个集合的元素挨个比较。
6.删除
 boolean remove(Object obj) :通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
 boolean removeAll(Collection coll):取当前集合的差集
7.取两个集合的交集
 boolean retainAll(Collection c):把交集的结果存在当前集合中,不影响c
8.集合是否相等
 boolean equals(Object obj)

9.转成对象数组

 Object[] toArray()
10.获取集合对象的哈希值
 hashCode()

11.使用 iterator 循环遍历集合元素

 iterator():返回迭代器对象,用于集合遍历
Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection 集合中的元素。Iterator 仅用于遍历集合,Iterator 本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
在这里插入图片描述
在调用it.next()方法之前必须要调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。
注意:
 Iterator可以删除集合的元素,但是遍历过程中通过迭代器对象的remove方法,不是集合对象的remove方法。
 如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。

12.使用 foreach 循环遍历集合元素

遍历集合的底层调用Iterator完成操作。

3.Collection子接口之:List接口

概述

 鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
 List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
 List接口的实现类常用的有:ArrayList、LinkedList和Vector。

List接口常用方法

List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法。

1.void add(int index, Object ele):在index位置插入ele元素
2.boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
3.Object get(int index):获取指定index位置的元素
4.int indexOf(Object obj):返回obj在集合中首次出现的位置
5.int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
6.Object remove(int index):移除指定index位置的元素,并返回此元素
7.Object set(int index, Object ele):设置指定index位置的元素为ele
8.List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合

List实现类之:ArrayList

 ArrayList 是 List 接口的典型实现类、主要实现类
 本质上,ArrayList是对象引用的一个”变长”数组

Arrays.asList(…)注意事项:

Arrays.asList(…) 方法返回的 List 集合,既不是 ArrayList 实例,也不是Vector 实例。 Arrays.asList(…) 返回值是一个固定长度的 List 集合。

List实现类之:LinkedList

对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高
LinkedList:双向链表,内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基本结构。Node除了保存数据,还定义了两个变量:
 prev变量记录前一个元素的位置
 next变量记录下一个元素的位置

新增方法:
 void addFirst(Object obj)
 void addLast(Object obj)
 Object getFirst()
 Object getLast()
 Object removeFirst()
 Object removeLast()

List 实现类之:Vector

Vector 是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的。Vector总是比ArrayList慢,所以尽量避免使用

4.Collection子接口之:Set接口

概述

 Set接口是Collection的子接口,set接口没有提供额外的方法
 Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set 集合中,则添加操作失败。
 Set 判断两个对象是否相同不是使用 == 运算符,而是根据 equals() 方法

Set实现类之:HashSet

HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。
HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。

HashSet 具有以下特点:
1.不能保证元素的排列顺序
2.HashSet 不是线程安全的
3.集合元素可以是 null

HashSet 集合判断两个元素相等的标准:两个对象通过hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等
对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”。
向HashSet中添加元素的过程:
1.当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计的越好)
2.如果两个元素的hashCode()值相等,会再继续调用equals方法,如果equals方法结果为true,添加失败;如果为false,那么会保存该元素,但是该数组的位置已经有元素了,那么会通过链表的方式继续链接。
如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
在这里插入图片描述

Set实现类之:LinkedHashSet

LinkedHashSet 是 HashSet 的子类
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的
LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能

Set实现类之:TreeSet

TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
TreeSet底层使用红黑树结构存储数据
TreeSet 两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序。

自然排序:
TreeSet 会调用集合元素的 compareTo(Object obj) 方法来比较元素之间的大小关系,然后将集合元素按升序(默认情况)排列
如果试图把一个对象添加到 TreeSet 时,则该对象的类必须实现 Comparable 接口。
向 TreeSet 中添加元素时,只有第一个元素无须比较compareTo()方法,后面添加的所有元素都会调用compareTo()方法进行比较。
因为只有相同类的两个实例才会比较大小,所以向 TreeSet 中添加的应该是同一个类的对象。
对于 TreeSet 集合而言,它判断两个对象是否相等的唯一标准是:两个对象通过 compareTo(Object obj) 方法比较返回值。
当需要把一个对象放入 TreeSet 中,重写该对象对应的 equals() 方法时,应保证该方法与 compareTo(Object obj) 方法有一致的结果:如果两个对象通过equals() 方法比较返回 true,则通过compareTo(Object obj) 方法比较应返回 0。否则,让人难以理解

定制排序:
定制排序,通过Comparator接口来实现。需要重写compare(T o1,T o2)方法。
要实现定制排序,需要将实现Comparator接口的实例作为形参传递给TreeSet的构造器。

新增的方法如下:
Comparator comparator()
Object first()
Object last()
Object lower(Object e)
Object higher(Object e)
SortedSet subSet(fromElement, toElement)
SortedSet headSet(toElement)
SortedSet tailSet(fromElement)

5.Map接口

概述

 Map 中的 key 和 value 都可以是任何引用类型的数据
 Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法(常用String类作为Map的“键”)。
 Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是 Map 接口使用频率最高的实现类。

常用方法

添加、删除、修改操作:

 Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
 void putAll(Map m):将m中的所有key-value对存放到当前map中  Object remove(Object key):移除指定key的key-value对,并返回value
 void clear():清空当前map中的所有数据

元素查询的操作:

 Object get(Object key):获取指定key对应的value
 boolean containsKey(Object key):是否包含指定的key
 boolean containsValue(Object value):是否包含指定的value
 int size():返回map中key-value对的个数
 boolean isEmpty():判断当前map是否为空
 boolean equals(Object obj):判断当前map和参数对象obj是否相等

遍历方式:

 Set keySet():返回所有key构成的Set集合
 Collection values():返回所有value构成的Collection集合
 Set entrySet():返回所有key-value对构成的Set集合

		Map<String, Integer> map = new HashMap();
		map.put("1", 1);
		map.put("2", 1);
		map.put("3", 1);
		map.put("4", 1);
		
		
		System.out.println("map的所有key:");
		Set keys = map.keySet();// HashSet
		for (Object key : keys) {
		System.out.println(key + "->" + map.get(key));
		}
		
		
		System.out.println("map的所有的value:");
		Collection values = map.values();
		for (Object object : values) {
			System.out.println(object);
		}
		Iterator iter = values.iterator();
		while (iter.hasNext()) {
		System.out.println(iter.next());
		}
		
		
		System.out.println("map所有的映射关系:");
		// 映射关系的类型是Map.Entry类型,它是Map接口的内部接口
		Set<Entry<String, Integer>> mappings = map.entrySet();
		for (Entry entry : mappings) {
			System.out.println(entry);
			System.out.println("key是:" + entry.getKey() + ",value是:" + entry.getValue());
		}

Map实现类之:HashMap

概述

HashMap是 Map 接口使用频率最高的实现类。
允许使用null键和null值,与HashSet一样,不保证映射的顺序。
所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()
所有的value构成的集合是Collection:无序的、可以重复的。value所在的类要重写:equals()
一个key-value构成一个entry
所有的entry构成的集合是Set:无序的、不可重复的
HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。

HashMap的存储结构:

JDK 8版本发布以后:HashMap是数组+链表+红黑树实现。
在这里插入图片描述

扩容规则:

当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)乘loadFactor 时 , 就会进行数组扩容 , loadFactor 的默认值 (DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数超过16乘0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
注:
threshold:扩容的临界值,=容量 乘以 填充因子
loadFactor:填充因子
DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
MAXIMUM_CAPACITY : HashMap的最大支持容量,2^30
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子

当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。
注:当实例化一个HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表中被称为容量(Capacity)。在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链。也可能是一个一个TreeNode对象。

添加元素的过程:

向HashMap中添加entry1(key,value),需要首先计算entry1中key的哈希值(根据key所在类的hashCode()计算得到),此哈希值经过处理以后,得到在底层Entry[]数组中要存储的位置i。如果位置i上没有元素,则entry1直接添加成功。如果位置i上已经存在entry2(或还有链表存在的entry3,entry4),则需要通过循环的方法,依次比较entry1中key和其他的entry。如果彼此hash值不同,则直接添加成功。如果hash值相同,继续比较二者是否equals。如果返回值为true,则使用entry1的value去替换equals为true的entry的value。如果遍历一遍以后,发现所有的equals返回都为false,则entry1仍可添加成功。

Map实现类之:LinkedHashMap

LinkedHashMap 是 HashMap 的子类
在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序
与LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致。

Map实现类之:TreeMap

 TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0。
TreeSet底层使用红黑树结构存储数据
TreeMap 的 Key 的排序:
自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable 接口。

Map实现类之:Hashtable(极其不常用)

 Hashtable是个古老的 Map 实现类,JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。
 Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
 与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
 与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
 Hashtable判断两个key相等、两个value相等的标准,与HashMap一致。

Map实现类之:Properties

Properties 类是 Hashtable 的子类,该对象用于处理属性文件
由于属性文件里的 key、value 都是字符串类型,所以Properties 里的 key 和 value 都是字符串类型
存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法

		Properties b=new Properties();
		b.load(new FileInputStream("a.properties"));
		System.out.println(b.get("name"));

6.Collections工具类

Collections 是一个操作 Set、List 和 Map 等集合的工具类
Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法

常用方法:

排序操作:(均为static方法)
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
查找、替换:
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中

           List a=new ArrayList();
           a.add("a");
           a.add("b");
           
           List<Object> b=Arrays.asList(new Object[a.size()]);
           Collections.copy(b,a);
           
           System.out.println(b+""+a);

boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换List 对象的所有旧值
同步控制:
Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。(返回值为线程安全的集合)
在这里插入图片描述

十八,泛型

泛型的设计背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型

泛型的概念

所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。泛型的类型必须是类不能是基本数据类型

自定义泛型类、泛型接口

1.泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
2.泛型类的构造器如下:public GenericClass(){}。
下面是错误的例子:public GenericClass< E >(){}
3.jdk1.7,泛型的简化操作:ArrayList< Fruit > flist = new ArrayList<>();
4.在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型
5.异常类不能是泛型的。
6.不能使用new E[]。但是可以使用:E[] elements = (E[])new Object[capacity];
7.父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:

class Father<K, V> {
	
}

// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {
}

// 2)具体类型并增加自己的泛型
class Son2<A> extends Father<Integer, String> {
}

// 子类保留父类的泛型
// 1)全部保留
class Son3<K,V> extends Father<K, V> {
}

// 2)部分保留
class Son4<V> extends Father<Integer, V> {
}

注意:子类除了指定或保留父类的泛型,还可以增加自己的泛型

自定义泛型方法

 方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。泛型方法在调用时指明泛型参数的类型所以泛型方法可以声明为static
 泛型方法的格式:
[访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常

通配符的使用

举例:

public class T12 {

	public static void main(String[] args) {

		List<String> l1 = new ArrayList<String>();
		List<Integer> l2 = new ArrayList<Integer>();
		l1.add("juc");
		l2.add(15);
		read(l1);
		read(l2);
	}
	public static void read(List<?> list) {
		for (Object o : list) {
			System.out.println(o);
		}
	}

}

注意:

如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G< B >并不是G< A >的子类,但二者的共同父类是G<?>
比如:String是Object的子类,但是List< String >并不是List< Object >的子类,但二者的共同父类是List<?>

Collection<?> list = new ArrayList< String >();
list.add(new Object()); // 编译时错误,将任意元素加入到其中不是类型安全的。
写入list中的元素时,不行。因为我们不知道?的元素类型,我们不能向其中添加对象。唯一的例外是null,它是所有类型的成员。
读取list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。

有限制的通配符

 通配符指定上限
上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
 通配符指定下限
下限super:使用时指定的类型不能小于操作的类,即>=

举例:

在这里插入图片描述

 <? extends Number> (无穷小 , Number]
只允许泛型为Number及Number子类的引用调用
 <? super Number> [Number , 无穷大) 只允许泛型为Number及Number父类的引用调用
 <? extends Comparable>
只允许泛型为实现Comparable接口的实现类的引用调用

十九,IO流

File类的使用

 java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关
 File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
 File对象可以作为参数传递给流的构造器

常用构造器:

 public File(String pathname) 以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。
绝对路径:是一个固定的路径,从盘符开始
相对路径:是相对于某个位置开始

 public File(String parent,String child)以parent为父路径,child为子路径创建File对象。
 public File(File parent,String child)根据一个父File对象和子文件路径创建File对象

路径分隔符

 路径分隔符和系统有关:
windows和DOS系统默认使用“\”来表示
UNIX和URL使用“/”来表示
 为了解决这个隐患,File类提供了一个常量:
public static final String separator。根据操作系统,动态的提供分隔符。

File file1 = new File("d:\\ab\\info.txt");
File file2 = new File("d:" + File.separator + "ab" + File.separator + "info.txt");

常用方法

File类的获取功能
 public String getAbsolutePath():获取绝对路径
 public String getPath() :获取路径
 public String getName() :获取名称
 public String getParent():获取上层文件目录路径。若无,返回null
 public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
 public long lastModified() :获取最后一次的修改时间,毫秒值
 public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
 public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
File类的重命名功能
 public boolean renameTo(File dest):把文件重命名为指定的文件路径
File类的判断功能
 public boolean isDirectory():判断是否是文件目录
 public boolean isFile() :判断是否是文件
 public boolean exists() :判断是否存在
 public boolean canRead() :判断是否可读
 public boolean canWrite() :判断是否可写
 public boolean isHidden() :判断是否隐藏
File类的创建功能
 public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
 public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
 public boolean mkdirs() :创建文件目录。如果上层文件目录不存在,一并创建。

注意事项:如果你创建文件或者文件目录没有写盘符路径,那么,默认在项目路径下。
File类的删除功能
 public boolean delete():删除文件或者文件夹
删除注意事项:
Java中的删除不走回收站。要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录

IO流原理及流的分类

Java IO原理:
 Java程序中,对于数据的输入/输出操作以“流(stream)” 的
方式进行。
从程序的角度:
输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。

流的分类:

按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
按数据流的流向不同分为:输入流,输出流
按流的角色的不同分为:节点流,处理流
在这里插入图片描述
 节点流:直接从数据源或目的地读写数据
 处理流:不直接连接到数据源或目的地,而是“连接”在已存
在的流(节点流或处理流)之上,通过对数据的处理为程序提
供更为强大的读写功能。
在这里插入图片描述

InputStream & Reader

InputStream 和 Reader 是所有输入流的基类。
程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件 IO 资源。为了流资源一定可以执行关闭操作,需要使用try-catch-finally
FileInputStream 用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader。
 字节流操作字节,比如:.mp3,.avi,.rmvb,mp4,.jpg,.doc,.ppt
 字符流操作字符,只能操作普通文本文件。最常见的文本文
件:.txt,.java,.c,.cpp 等语言的源代码。尤其注意.doc,excel,ppt这些不是文本文件。

Reader

 int read()
读取单个字符。作为整数读取的字符,范围在 0 到 65535 之间 (0x00-0xffff)(2个字节的Unicode码),如果已到达流的末尾,则返回 -1

public class T12 {

	public static void main(String[] args) {
		FileReader b = null;
		try {
			File a=new File("aa.properties");
			b = new FileReader(a);
			int reada; 
			while ((reada=b.read())!=-1) {
				System.out.print((char)reada);
			}
		} catch (FileNotFoundException e) {
			
			e.printStackTrace();
		} catch (IOException e) {
			
			e.printStackTrace();
		}finally {
			
			try {
				if (b!=null) {
					b.close();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		

	}

}

 int read(char[] cbuf)
将字符读入数组。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。

public class T12 {

	public static void main(String[] args) throws IOException {
		// 省略了try-catch-finally
		File a=new File("a.properties");
		char[] c = new char[4];
		FileReader b=new FileReader(a);

		int readd;
		while ((readd=b.read(c))!=-1) {
		// i<本次读取的字符数,不能是数组的长度
			for (int i = 0; i < readd; i++) {
				System.out.print(c[i]);
				
			}
	
		}
		
		b.close();
			
	}

}

 int read(char[] cbuf,int off,int len)
将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回 -1。否则返回本次读取的字符数。
 public void close() throws IOException
关闭此输入流并释放与该流关联的所有系统资源。

InputStream

 int read()
从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。
 int read(byte[] b)
从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。如果因为已经到达流末尾而没有可用的字节,则返回值 -1。否则以整数形式返回实际读取的字节数。
 int read(byte[] b, int off,int len)
将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值 -1。
 public void close() throws IOException
关闭此输入流并释放与该流关联的所有系统资源。

OutputStream & Writer

 FileOutputStream 用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter。
 定义文件路径时,注意:可以用“/”或者“\”。
 在写入一个文件时,如果使用构造器FileOutputStream(file),则目录下有同名文件将被覆盖。
 如果使用构造器FileOutputStream(file,true),则目录下的同名文件不会被覆盖,在文件内容末尾追加内容。

Writer

 void write(int c)
写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。 即写入0 到 65535 之间的Unicode码。
 void write(char[] cbuf)
写入字符数组。
 void write(char[] cbuf,int off,int len)
写入字符数组的某一部分。从off开始,写入len个字符
 void write(String str)
写入字符串。
 void write(String str,int off,int len)
写入字符串的某一部分。
void flush()
刷新该流的缓冲,则立即将它们写入预期目标。
 public void close() throws IOException
关闭此输出流并释放与该流关联的所有系统资源。

OutputStream

 void write(int b)
将指定的字节写入此输出流。write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。 即写入0~255范围的。
 void write(byte[] b)
将 b.length 个字节从指定的 byte 数组写入此输出流。write(b) 的常规协定是:应该与调用 write(b, 0, b.length) 的效果完全相同。
 void write(byte[] b,int off,int len)
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
 public void flush()throws IOException
刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。
 public void close() throws IOException
关闭此输出流并释放与该流关联的所有系统资源。

缓冲流

缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
 BufferedInputStream 和 BufferedOutputStream
 BufferedReader 和 BufferedWriter

转换流

转换流提供了在字节流和字符流之间的转换
Java API提供了两个转换流:
InputStreamReader:将InputStream转换为Reader
OutputStreamWriter:将Writer转换为OutputStream
 很多时候我们使用转换流来处理文件乱码问题。实现编码和
解码的功能。
在这里插入图片描述

public class T12 {

	public static void main(String[] args) throws IOException {
		// 省略了try-catch-finally
		char[] cbuf=new char[100];
		File a=new File("a.txt");
		File a1=new File("a1.txt");
		FileInputStream b=new FileInputStream(a);
		FileOutputStream b1=new FileOutputStream(a1);
		InputStreamReader c=new InputStreamReader(b);
		OutputStreamWriter d=new OutputStreamWriter(b1);
		int num;
		while ((num=c.read(cbuf))!=-1) {
			
			for (int i = 0; i < num; i++) {
				
				System.out.print(cbuf[i]);
			}
			d.write(cbuf, 0, num);
		}
		c.close();
		d.close();
	}

}

对象流

 ObjectInputStream和OjbectOutputSteam
 用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
 反序列化:用ObjectInputStream类读取基本类型数据或对象的机制
 ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量

对象的序列化

对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NotSerializableException异常。
1.Serializable
凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:private static final long serialVersionUID;
serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议,显式声明。
2.Externalizabl

使用对象流序列化对象

若某个类实现了 Serializable 接口,该类的对象就是可序列化的:
1.创建一个 ObjectOutputStream
2.调用 ObjectOutputStream 对象的 writeObject(对象) 方法输出可序列化对象
3.注意写出一次,操作flush()一次
反序列化
1.创建一个 ObjectInputStream
2.调用 readObject() 方法读取流中的对象

public class Test {

	public static void main(String[] args) throws Exception {
		// 省略了try-catch-finally
		// 序列化
		FileOutputStream b=new FileOutputStream(new File("a.bat"));
		ObjectOutputStream a=new ObjectOutputStream(b);
		a.writeObject(new People());
		a.flush();
		a.close();
		
		// 反序列化
		FileInputStream c=new FileInputStream(new File("a.bat"));
		ObjectInputStream d=new ObjectInputStream(c);
		People readObject = (People) d.readObject();
		System.out.println(readObject.getName());
		d.close();

	}

}

随机存取文件流(RandomAccessFile 类)

RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类。并且它实现了DataInput、DataOutput这两个接口,也就意味着这个类既可以读也可以写。
RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件
1.支持只访问文件的部分内容
2.可以向已存在的文件后追加内容
RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。RandomAccessFile 类对象可以自由移动记录指针:
1.long getFilePointer():获取文件记录指针的当前位置
2.void seek(long pos):将文件记录指针定位到 pos 位置
构造器
public RandomAccessFile(File file, String mode)
public RandomAccessFile(String name, String mode)
创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式:
1.r: 以只读方式打开
2.rw:打开以便读取和写入
3.rwd:打开以便读取和写入;同步文件内容的更新
4.rws:打开以便读取和写入;同步文件内容和元数据的更新
 如果模式为只读r。则不会创建文件,而是会去读取一个已经存在的文件,如果读取的文件不存在则会出现异常。 如果模式为rw读写。如果文件不存在则会去创建文件,如果存在则对原有文件内容进行覆盖(默认从头开始覆盖)。

public class Test {

	public static void main(String[] args) throws Exception {
		// 省略了try-catch-finally
		int num;
		byte[] b=new byte[10];
		RandomAccessFile a=new RandomAccessFile(new File("a.txt"), "r");
		RandomAccessFile a1=new RandomAccessFile(new File("a1.txt"), "rw");
		while ((num=a.read(b))!=-1) {
			a1.write(b,0,num);
		}		
		a.close();
		a1.close();

	}

}

二十,网络编程

网络编程的目的:
直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。

IP和端口号

IP 地址:InetAddress
1.唯一的标识 Internet 上的计算机(通信实体)
2.本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost

3.IP地址分类方式1:IPV4 和 IPV6
IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984

4.IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168.开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用。

5.InetAddress类没有提供公共的构造器,而是提供了如下几个静态方法来获取InetAddress实例
public static InetAddress getLocalHost()
public static InetAddress getByName(String host)
InetAddress提供了如下几个常用的方法
public String getHostAddress():返回 IP 地址字符串(以文本表现形式)。
public String getHostName():获取此 IP 地址的主机名
public boolean isReachable(int timeout):测试是否可以达到该地址

端口号:标识正在计算机上运行的进程(程序)
1.不同的进程有不同的端口号
2.被规定为一个 16 位的整数 0~65535。
3.端口分类:
公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
动态/私有端口:49152~65535。

端口号与IP地址的组合得出一个网络套接字:Socket

网络协议

TCP 和 UDP

TCP协议:
 使用TCP协议前,须先建立TCP连接,形成传输数据通道
 传输前,采用“三次握手”方式,点对点通信,是可靠的
 TCP协议进行通信的两个应用进程:客户端、服务端。
 在连接中可进行大数据量的传输
 传输完毕,需释放已建立的连接,效率低
UDP协议:
 将数据、源、目的封装成数据包,不需要建立连接
 每个数据报的大小限制在64K内
 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
 可以广播发送
 发送数据结束时无需释放资源,开销小,速度快

基于Socket的TCP编程

Socket:
 Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
 通信的两端都要有Socket,是两台机器间通信的端点。

 Socket类的常用方法:
1.public InputStream getInputStream()返回此套接字的输入流。可以用于接收网络消息
2.public OutputStream getOutputStream()返回此套接字的输出流。可以用于发送网络消息
3.public InetAddress getInetAddress()此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null。
4.public InetAddress getLocalAddress()获取套接字绑定的本地地址。 即本端的IP地址
5.public int getPort()此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0。
6.public int getLocalPort()返回此套接字绑定到的本地端口。 如果尚未绑定套接字,则返回 -1。即本端的端口号。
7.public void close()关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和OutputStream。
8.public void shutdownInput()如果在套接字上调shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据。
9.public void shutdownOutput()禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用shutdownOutput() 后写入套接字输出流,则该流将抛出IOException。即不能通过此套接字的输出流发送任何数据。
客户端创建Socket对象:
客户端程序可以使用Socket类创建对象,创建的同时会自动向服务器方发起连接。

Socket的构造器是:
1.Socket(String host,int port)throws UnknownHostException,IOException:向服务器(域名是host。端口号为port)发起TCP连接,若成功,则创建Socket对象,否则抛出异常。
2.Socket(InetAddress address,int port)throws IOException:根据InetAddress对象所表示的IP地址以及端口号port发起连接。
客户端:

		InetAddress b=InetAddress.getLocalHost();
		Socket a=new Socket(b,8899);
		OutputStream outputStream = a.getOutputStream();
		outputStream.write("dasd".getBytes());
		outputStream.close();
		a.close();

服务器建立 ServerSocket 对象:
服务器必须事先建立一个等待客户请求建立套接字连接的ServerSocket对象。
所谓“接收”客户的套接字请求,就是accept()方法会返回一个 Socket 对象
服务端:

		
		ServerSocket a=new ServerSocket(8899);
		Socket accept = a.accept();
		byte[] b1=new byte[10];
		int num;
		InputStream inputStream = accept.getInputStream();
		while ((num=inputStream.read(b1))!=-1) {
			for (int i = 0; i <num; i++) {
				byte b = b1[i];
				char c=(char) b;
				System.out.print(c);
				
			}
		}
		inputStream.close();
		accept.close();
		a.close();

UDP网络编程

 类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序。
 UDP数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
 DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
 UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。如同发快递包裹一样

DatagramSocket 类的常用方法:
 public DatagramSocket(int port)创建数据报套接字并将其绑定到本地主机上的指定端口。套接字将被绑定到通配符地址,IP 地址由内核来选择。
 public DatagramSocket(int port,InetAddress laddr)创建数据报套接字,将其绑定到指定的本地地址。本地端口必须在 0 到 65535 之间(包括两者)。如果 IP 地址为 0.0.0.0,套接字将被绑定到通配符地址,IP 地址由内核选择。
 public void close()关闭此数据报套接字。
 public void send(DatagramPacket p)从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。
 public void receive(DatagramPacket p)从此套接字接收数据报包。当此方法返回时,DatagramPacket的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。 此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。
 public InetAddress getLocalAddress()获取套接字绑定的本地地址。
 public int getLocalPort()返回此套接字绑定的本地主机上的端口号。
 public InetAddress getInetAddress()返回此套接字连接的地址。如果套接字未连接,则返回 null。
 public int getPort()返回此套接字的端口。如果套接字未连接,则返回 -1。
 public DatagramPacket(byte[] buf,int length)构造DatagramPacket,用来接收长度为 length 的数据包。 length 参数必须小于等于 buf.length。
 public DatagramPacket(byte[] buf,int length,InetAddress address,int port)构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length参数必须小于等于 buf.length。
 public InetAddress getAddress()返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。
 public int getPort()返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。
 public byte[] getData()返回数据缓冲区。接收到的或将要发送的数据从缓冲区中的偏移量 offset 处开始,持续 length 长度。
 public int getLength()返回将要发送或接收到的数据的长度。
发送端:

		DatagramSocket a=new DatagramSocket();
		InetAddress c=InetAddress.getLocalHost();
		byte[] bytes = "asd".getBytes();
		DatagramPacket b=new DatagramPacket(bytes,bytes.length,c,10000);
		a.send(b);
		a.close();

接收端:

		DatagramSocket a=new DatagramSocket(10000);
		byte[] b=new byte[1024];
		DatagramPacket c=new DatagramPacket(b, b.length);
		a.receive(c);
		a.close();
		System.out.println(new String(c.getData()));

URL编程

 URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表

一个URL对象生成后,其属性是不能被改变的,但可以通过它给定的方法来获取这些属性:
public String getProtocol( ) 获取该URL的协议名
public String getHost( ) 获取该URL的主机名
public String getPort( ) 获取该URL的端口号
public String getPath( ) 获取该URL的文件路径
public String getFile( ) 获取该URL的文件名
public String getQuery( ) 获取该URL的查询名

URL url = new URL("http://localhost:8080/img/a1.jpg");

URLConnection:表示到URL所引用的远程对象的连接。当与一个URL建立连接时,首先要在一个 URL 对象上通过方法openConnection() 生成对应的 URLConnection对象。如果连接过程失败,将产生IOException.

URL netchinaren = new URL ("http://asfsadfl"); 
URLConnectonn u = netchinaren.openConnection();

通过URLConnection对象获取的输入流和输出流,即可以与现有的CGI程序进行交互。
public Object getContent( ) throws IOException
public int getContentLength( )
public String getContentType( )
public long getDate( )
public long getLastModified( )
public InputStream getInputStream( )throws IOException
public OutputSteram getOutputStream( )throws IOException

        URL a=new URL("file:///E:/java%E7%BB%83%E4%B9%A0/java2/java/a.txt");
        URLConnection openConnection =  a.openConnection();
        byte[] a1=new byte[1024];
        int num;
        InputStream inputStream = openConnection.getInputStream();
        
        FileOutputStream b=new FileOutputStream(new File("aa.txt"));
        while ((num=inputStream.read(a1))!=-1) {
			b.write(a1,0,num);
		}
        b.close();
        inputStream.close();

二十一,Java反射机制

Java反射机制概述

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

Java反射机制提供的功能

在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理

反射相关的主要API

 java.lang.Class:代表一个类
 java.lang.reflect.Method:代表类的方法
 java.lang.reflect.Field:代表类的成员变量
 java.lang.reflect.Constructor:代表类的构造器

Class类

在Object类中定义了以下的方法,此方法将被所有子类继承:public final Class getClass()
以上的方法返回值的类型是一个Class类,此类是Java反射的源头,可以通过对象反射求出类的名称。
 Class本身也是一个类
Class 对象只能由系统建立对象
一个加载的类在 JVM 中只会有一个Class实例
一个Class对象对应的是一个加载到JVM中的一个.class文件
 每个类的实例都会记得自己是由哪个 Class 实例所生成
 通过Class可以完整地得到一个类中的所有被加载的结构
 Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象。

获取Class类的实例

1.若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高

Class<People> clazz=People.class;

2.已知某个类的实例,调用该实例的getClass()方法获取Class对象

		People a1=new People("a", 12);
		Class class1 =  a1.getClass();

3.已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException

Class forName = Class.forName("com.lool.t.People");

4.使用类的加载器

		ClassLoader classLoader = Test.class.getClassLoader();
		Class loadClass = classLoader.loadClass("com.lool.t.People");

拥有Class对象的类型

(1)class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举
(5)annotation:注解@interface
(6)primitive type:基本数据类型
(7)void

类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。
在这里插入图片描述
加载:程序经过javac.exe命令后,会生成一个或多个字节码文件(.class结尾)。然后使用java.exe命令对某个字节码文件进行解释运行,将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化
执行类构造器< clinit >()方法的过程。类构造器< clinit >()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
虚拟机会保证一个类的< clinit >()方法在多线程环境中被正确加锁和同步。

类加载器

JVM 规范定义了如下类型的类的加载器:
在这里插入图片描述

在这里插入图片描述
 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

ClassLoader相关方法

1.获取一个系统类加载器

		ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
		System.out.println(systemClassLoader);

2.获取系统类加载器的父类加载器,即扩展类加载器

		ClassLoader parent = systemClassLoader.getParent();

3.获取扩展类加载器的父类加载器,即引导类加载器

ClassLoader parent2 = parent.getParent();

4.获得某个类由哪个类加载器进行加载(获得某个类的加载器)

		ClassLoader classLoader2 = Class.forName("com.lool.t.People").getClassLoader();
		System.out.println(classLoader2);

5.关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流

		Properties n=new Properties();
		// Test是自定义的类
		ClassLoader classLoader = Test.class.getClassLoader();
		// a.properties放在src/main/resources下
		InputStream resourceAsStream = classLoader.getResourceAsStream("a.properties"); 
		n.load(resourceAsStream);
		Object object = n.get("name");
		System.out.println(object);

通过反射创建运行时类的对象

创建类的对象(构造器不带参):调用Class对象的 newInstance()方法
要求:
1)类必须有一个无参数的构造器。
2)类的构造器的访问权限需要足够。

		// Test是自定义的类
		Class<Test> a=Test.class;
		Test newInstance = a.newInstance();

创建类的对象(构造器带参)
1)通过Class类的getDeclaredConstructor(Class …parameterTypes)取得本类的指定形参类型的构造器
2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
3)通过Constructor实例化对象。

		Class<People> clazz=People.class;
		Constructor<People> declaredConstructor = clazz.getDeclaredConstructor(String.class,int.class);
		declaredConstructor.setAccessible(true);
		People newInstance2 = declaredConstructor.newInstance("a",12);
		System.out.println(newInstance2);

通过反射获取运行时类的完整结构

// 前提得获取Class的实例
Class<People> clazz=People.class;

1.获取实现的全部接口
public Class<?>[] getInterfaces()
确定此对象所表示的类或接口,实现的接口。
2.获取所继承的父类
public Class<? Super T> getSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的Class。
3.获取全部的构造器
 public Constructor[] getConstructors()
返回此 Class 对象所表示的类的所有public构造方法。
 public Constructor[] getDeclaredConstructors()
返回此 Class 对象表示的类声明的所有构造方法。
Constructor类中:
 取得修饰符: public int getModifiers();
 取得方法名称: public String getName();
 取得参数的类型:public Class<?>[] getParameterTypes();
4.获取全部的方法
 public Method[] getDeclaredMethods()
返回此Class对象所表示的类(不包含其父类)或接口的全部方法
 public Method[] getMethods()
返回此Class对象所表示的类(及其父类)或接口的public的方法
Method类中:
 public Class<?> getReturnType()取得全部的返回值
 public Class<?>[] getParameterTypes()取得全部的参数
 public int getModifiers()取得修饰符
 public Class<?>[] getExceptionTypes()取得异常信息
5.获取全部的Field(属性)
public Field[] getFields()
返回此Class对象所表示的类(及其父类)或接口的public的Field。
public Field[] getDeclaredFields()
返回此Class对象所表示的类(不包含其父类)或接口的全部Field。
Field方法中:
 public int getModifiers() 以整数形式返回此Field的修饰符
 public Class<?> getType() 得到Field的属性类型
 public String getName() 返回Field的名称。
6. 获取注解(Annotation)相关
 get Annotation(Class annotationClass)
 getDeclaredAnnotations()
7.获取泛型相关
获取父类泛型类型:Type getGenericSuperclass()
泛型类型:ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments()

// public class People extends L<Object,Object>
// 注意:子类继承父类时需要显示的标明给父类中传入的泛型类型
// 不标明下面的代码会报java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
		Class<People> clazz=People.class;
		Type genericSuperclass = clazz. getGenericSuperclass();
		ParameterizedType n1=(ParameterizedType) genericSuperclass;
		Type[] actualTypeArguments = n1.getActualTypeArguments();
		for (Type type : actualTypeArguments) {
			System.out.println(type.getTypeName());
		}

8.获取类所在的包 Package getPackage()

通过反射调用运行时类的指定结构

// 前提得获取Class的实例
Class<People> clazz=People.class;

1.调用指定方法
通过反射,调用类中的方法,通过Method类完成。步骤:
(1)通过Class类的getMethod或getDeclaredMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
(2)之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。
说明:
Object invoke(Object obj, Object … args)
(1)Object 对应原方法的返回值,若原方法无返回值,此时返回null
(2)若原方法若为静态方法,此时形参Object obj可为null
(3)若原方法形参列表为空,则Object[] args为null
(4)若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。

		Class<People> clazz=People.class;
		People p = clazz.newInstance();
		Method declaredMethod = clazz.getDeclaredMethod("a",String.class);
		declaredMethod.setAccessible(true);
		Object invoke = declaredMethod.invoke(p,"dasdsad");
		System.out.println(invoke);

2.调用指定属性
 public Field getField(String name) 返回此Class对象表示的类或接口的指定的public的Field。
 public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field。
在Field中:
 public Object get(Object obj) 取得指定对象obj上此Field的属性内容
 public void set(Object obj,Object value) 设置指定对象obj上此Field的属性内容

		Class<People> clazz=People.class;
		Field age = clazz.getDeclaredField("age");
		People p = clazz.newInstance();
		age.setAccessible(true);
		age.set(p, 12);
		Object object = age.get(p);

关于setAccessible方法的使用

 Method和Field、Constructor对象都有setAccessible()方法。
 setAccessible启动和禁用访问安全检查的开关。
 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
 使得原本无法访问的私有成员也可以访问
 参数值为false则指示反射的对象应该实施Java语言访问检查

反射的应用:动态代理

代理设计模式的原理:
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象。

Java动态代理相关API

 Proxy :专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。
 提供用于创建动态代理类和动态代理对象的静态方法
(1)static Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces) 创建一个动态代理类所对应的Class对象
(2)static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 直接创建一个动态代理对象

public interface Human {
	
	String getBelief();
	
	void eat(String food);

}
/**
 *   
 * @author Jack
 *   被代理类
 */
public class SuperMan implements Human{

	@Override
	public String getBelief() {
		// TODO Auto-generated method stub
		return "asd";
	}

	@Override
	public void eat(String food) {
		// TODO Auto-generated method stub
		System.out.println(food);
		
	}

}
class ProxyFactory {

	
	public static Object getProxyInstance(Object obj/* 被代理类对象 */) {
		
		MyInvocationHandler handler =new MyInvocationHandler();
		handler.bind(obj);
		Object newProxyInstance = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
		
		return newProxyInstance;
		
	}
}

class MyInvocationHandler implements InvocationHandler{
	
	private Object obj; // 被代理类对象
	
	public void bind(Object obj) {
		this.obj=obj;
	}
	
	@Override
	public Object invoke(Object proxy/* 代理类对象 */, Method method, Object[] args) throws Throwable {
		System.out.println("执行被代理类方法时,会跳转到这里执行");
		Object returnValue = method.invoke(obj, args);
		System.out.println("方法执行完毕,处理其返回结果中");
		if (returnValue==null) {
			System.out.println("未发现需要处理的结果");
		}else {
			Object	returnValue1 =returnValue+"(此结果已处理)";
			System.out.println("结果处理完毕");
			return returnValue1;
		}

		return returnValue;
	}
	
}

public class ProxyTest{
	public static void main(String[] args) {
		SuperMan s=new SuperMan();
		Human proxyInstance = (Human) ProxyFactory.getProxyInstance(s);
		proxyInstance.eat("饭思思"); // 执行此方法时会跳转到invoke执行
		String belief = proxyInstance.getBelief();
		System.out.println(belief);
	}
	
}

二十二,Java8的其它新特性

函数式(Functional)接口

 只包含一个抽象方法的接口,称为函数式接口。
 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
 Java 内置四大核心函数式接口
在这里插入图片描述
 其他接口
在这里插入图片描述

Lambda表达式

Lambda 表达式的本质:作为函数式接口的实例(匿名实现类)。
Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
左侧:指定了 Lambda 表达式需要的参数列表(接口中抽象方法的形参列表
右侧:指定了 Lambda 体,是重写的抽象方法的方法体,也即Lambda 表达式要执行的功能。
语法格式一:无参,无返回值

		Runnable a=() ->{ System.out.println("fsadfsaf");};
		Runnable b=new Runnable() {
			@Override
			public void run() {
				System.out.println("fsadfsaf");
				
			}
		};

语法格式二:Lambda 需要一个参数,但是没有返回值。

		Consumer<String> b= (String t) ->{System.out.println(t);};
		Consumer<String> a=new Consumer<String>() {
			
			@Override
			public void accept(String t) {
				// TODO Auto-generated method stub
				System.out.println(t);
			}
		};

语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”

		Consumer<String> b= (t) -> {System.out.println(t);};

语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略

		Consumer<String> b= t -> { System.out.println(t);};

语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值

		Comparator<Integer> a1=(o1,o2)  ->{return o1; };
		Comparator<Integer> a=new Comparator<Integer>() {
			
			@Override
			public int compare(Integer o1, Integer o2) {
	
				return o1;
			}
		};

语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略(需要同时省略)

Comparator<Integer> a1=(o1,o2)  -> o1;  
方法引用

传递给Lambda体的操作,已经有实现的方法了(由对象或类名直接调用的方法),可以使用方法引用。
 方法引用可以看做是Lambda表达式深层次的表达。
 要求:接口中的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致
 格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
对象::非静态方法名
类::静态方法名
类::非静态方法名

		// Consumer中的抽象方法void accept(T t)
		// lambda体中PrintStream中的void println(T t)
		Consumer<String> a=str ->System.out.println(str);
		a.accept("a");
		Consumer<String> b=System.out::println;
		b.accept("b");

在这里插入图片描述

构造器引用

格式: ClassName::new
要求:构造器参数列表要与接口中抽象方法的参数列表一致,且被实现的抽象方法的返回值即为构造器对应类的对象

		Supplier<String> a=new Supplier<String>() {
			
			@Override
			public String get() {
				
				return new String();
			}
		};
		Supplier<String> a1= ()-> new String();
		Supplier<String> a2= String::new;
数组引用

格式: type[] :: new

		Function<Integer, String[]> a=new Function<Integer, String[]>() {

			@Override
			public String[] apply(Integer t) {
				// TODO Auto-generated method stub
				return new String[t];
			}
		};
	Function<Integer, String[]> a1=t ->new String[t];
	Function<Integer, String[]> a2=String[]::new;

Stream API

 Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
 Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。

注意:

①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream 的操作三个步骤

1- 创建 Stream
一个数据源(如:集合、数组),获取一个流
2- 中间操作
一个中间操作链,对数据源的数据进行处理
3- 终止操作(终端操作)
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用。

创建 Stream的方式

1.通过集合
Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
 default Stream stream() : 返回一个顺序流
 default Stream parallelStream() : 返回一个并行流

		List<People> list =new ArrayList<People>();
		for (int i = 0; i < 10; i++) {
			list.add(new People("a"+i, i));
		}
		Stream<People> stream = list.stream();
		Stream<People> parallelStream = list.parallelStream();

2.通过数组
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
 static Stream stream(T[] array): 返回一个流

		int[] a=new int[10];
		IntStream stream2 = Arrays.stream(a);

3.通过Stream的of()
可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。
 public static Stream of(T… values) : 返回一个流

	Stream<People> of = Stream.of(new People());	

4.创建无限流
可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。
 迭代
public static Stream iterate(final T seed, final UnaryOperator f)
 生成
public static Stream generate(Supplier s)

		Stream<Integer> stream1 = Stream.iterate(0, x -> x + 2);
		stream1.limit(10).forEach(System.out::println);

		Stream<Double> stream22 = Stream.generate(Math::random);
		stream22.limit(10).forEach(System.out::println);
Stream 的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为“惰性求值”。
1.筛选与切片
在这里插入图片描述

		List<People> list =new ArrayList<People>();
		for (int i = 0; i < 10; i++) {
			list.add(new People("a"+i, i));
		}
		
		Stream<People> stream = list.stream();
		
		stream.filter(new Predicate<People>() {
			// 此处的匿名实现类建议使用lambda表达式替换
			@Override
			public boolean test(People t) {
				
				return t.getAge()>=5;
			}
		}).forEach(new Consumer<People>() {
			// 此处的匿名实现类建议使用lambda表达式替换
			@Override
			public void accept(People t) {
	
				System.out.println(t);
			}
		});

2.映 射
在这里插入图片描述

	List<String> asList = Arrays.asList("aa","b","cc","d","e");
	asList.stream().map(new Function<String, String>() {
	
		@Override
		public String apply(String t) {
			// TODO Auto-generated method stub
			return t.toUpperCase();
		}
	}).forEach(System.out::println);

体会筛选和映射:

		List<People> list =new ArrayList<People>();
		for (int i = 0; i < 10; i++) {
			list.add(new People("a"+i, i));
		}
		
		Stream<People> stream = list.stream();
		
		Stream<String> map = stream.map(new Function<People, String>() {

			@Override
			public String apply(People t) {
				// TODO Auto-generated method stub
				return t.getName();
			}
		});
		Stream<String> filter = map.filter(new Predicate<String>() {

			@Override
			public boolean test(String t) {
				// TODO Auto-generated method stub
				return t.length()>2;
			}
		});
		
		filter.forEach(new Consumer<String>() {

			@Override
			public void accept(String t) {
				// TODO Auto-generated method stub
				System.out.println(t);
			}
		});

3.排序
在这里插入图片描述

Stream 的终止操作

 终端操作会从流的流水线生成结果其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
 流进行了终止操作后,不能再次使用。
1.匹配与查找
在这里插入图片描述
在这里插入图片描述
2.归约
在这里插入图片描述

		List<Integer> list1=Arrays.asList(1,2,3,4,5,6,7,8,9);
		Stream<Integer> stream2 = list1.stream();
		Integer reduce = stream2.reduce(0,new BinaryOperator<Integer>() {
			
			@Override
			public Integer apply(Integer t, Integer u) {
			
				return Integer.sum(t, u);
			}
		});
		System.out.println(reduce);

3.收集
在这里插入图片描述
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。
另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
在这里插入图片描述
在这里插入图片描述

Optional类

 到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
 Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
 Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
 Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
创建Optional类对象的方法:
 Optional.of(T t) : 创建一个 Optional 实例,t必须非空;
 Optional.empty() : 创建一个空的 Optional 实例
 Optional.ofNullable(T t):t可以为null
判断Optional容器中是否包含对象:
 boolean isPresent() : 判断是否包含对象
 void ifPresent(Consumer<? super T> consumer) :如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。
获取Optional容器的对象:
 T get(): 如果调用对象包含值,返回该值,否则抛异常
 T orElse(T other) :如果有值则将其返回,否则返回指定的other对象。
 T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
 T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。

		People a=new People();
		a=null;
		Optional<People> ofNullable = Optional.ofNullable(a);
		System.out.println(ofNullable.orElse(new People("无值",12)));
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值