Java基础
Java概述
程序:计算机执行某些操作解决问题而编写的指令的集合。
Java主要特点
-
Java语言是面向对象的(oop)。
-
Java语言是健壮的 ---- Java的强类型机制,异常处理机制,垃圾的自动收集是java程序健壮性的重要保证。
-
Java语言是跨平台性的。
-
Java语言是解释类型的。
解释性语言:Javascript,PHP,Java 编译性语言: c / c++
解释性语言和编译性语言的区别:
解释性语言编译后的代码不能直接由机器执行,需要由解释器来执行,编译性语言,编译后的代码可以直接被机器执行。
Java运行机制及运行过程
jave 核心机制-Java虚拟机(JVM)
JVM是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令,管理数据,内存,寄存器,包含在JDK中。
对于不同的平台,有不同的虚拟机。
Java虚拟机机制同步了底层运行平台的差别,实现了一次编译到处运行。
运行过程: 将编写的java文件编译为字节码文件,在使用解释器运行。
JDK,JRE,JVM简介
-
JDK基本介绍
-
JDK的全称是Java Development Kit(开发者工具包);
JDK = JRE + java的开发工具[Java,javac,javadoc,javap]
-
JDK是提供给开发人员使用的,其中包括了Java的开发工具,也包括了JRE。
-
-
JRE基本介绍
-
JRE的全称是Java Runtime Environment (Java运行环境)
JRE = JVM + java核心类库
-
包括Java虚拟机和Java程序所需运行的核心类库,如果想运行一个开发好的Java程序,计算机中只需要安装JRE即可。
-
java开发注意事项和细节说明
- java源文件以.java为扩展名,源文件的基本组成部分是类(class)。
- java应用程序的执行入口是main()方法。他有固定的书写格式。
public static void mian(String[] srgs){。。。。}
- java语言严格区分大小写。
- Java语言由一条条语句构成,每个语句以“;”结束。
- 大括号都是成对出现的。
- 一个源文件只能有一个public类,其他的类的个数不限。
- 如果源文件中包含一个public类,则文件名必须按照该类名命名。
- 一个源文件只能有一个public类,其他的类的个数不限,也可以将main方法写在别的public类中,然后指定运行非public类,这样入口方法就是非public的main方法。
一,java语法
1. Java转义字符
常用转义字符:
转义字符 | 意义 | 备注 |
---|---|---|
\t | 一个制表符 | |
\n | 换行符 | |
\\ | 一个反斜杠 | |
" | 一个双引号 | |
’ | 一个单引号 | |
\r | 一个回车 | 比较特殊 |
public class Changechar{
public static void main(String[] arge){
System.out.println("生活你好\r世界"); //输出:世界你好
}
}
// \r会使输出光标重新返回改行前面,使后输出的内容覆盖已经输出的内容
2. Java注释
注释是解释程序的文字,注释提高了代码的可读性,将自己的思想通过注释整理出来,再用代码去体现。
被注释的语句不会被JVM虚拟机执行。
1.单行注释
//这是一个单行注释
2.多行注释
/* 这是一个多行注释 */
- 多行注释不能嵌套
3.文档注释
文档注释内容可以被JDK所提供的工具javadoc所解析,生成一套以网页文件形式体现的该程序的说明文档,一般写在类中。
基本格式:
/**
*@author
*@version
*/
生成对应的文档注释:
javadoc -d 生成后放置目录 -author -version 文件名.java
3. 变量
变量是程序的基本组成单位。
变量相当于内存中一个数据存储空间的表示,可以通过变量名访问到变量值。不同的变量,类型不同占用的空间大小也不同。(int占据四个字节,double占据八个字节)
该区域必须先声明,后使用。不允许在同一个作用域中重名。
变量 = 变量名 + 值 + 数据类型
public class Var{ public static void main(String[] arge){ int a; //第一种方式 a = 520; //第二种方式 int b = 521; } }
4.数据类型
每一种数据都定义了明确的数据类型,在内存中分配大小不同的内存空间(字节).
Java数据类型:
1. 基本数据类型: * 数值型 * 整数类型 (byte[1],short[2],int[4],long[8]) * 浮点类型 (float[4],double[8]) * 字符型 (char[2]存放单个字符) * 布尔型 (boolean[1] true,false) 2. 引用数据类型: * 类 class * 接口 interface * 数组 []
整型
java各整数类型有固定的范围和字段长度,不受具体的os的影响,以保证java程序的可移植性。
java中的整形默认常量为int型,声明long类型常量后需要加 L或者l。
java程序中变量常声明为int型。
浮点型
浮点数在机器的存放形式: 浮点数 = 符号位 + 指数位 + 尾数位
java各浮点数类型有固定的范围和字段长度,不受具体的os的影响,以保证java程序的可移植性。
java中的浮点数默认常量为double型,声明float类型常量后需要加 F或者f。
尾数部分可能丢失。
对运算结果是小数的进行相等判断要小心,应该是求两个数的差值的绝对值在某个精度范围内。
Math.abs(num1 - num2)
字符类型
字符类型可以表示单个字符,字符类型是char是两个字节,可以存放汉字,多个字符使用String。
- 字符常量是用单引号括起来的单个字符。
- Java允许使用转义字符’\‘来将其后的字符转变为特殊字符型常量。
char c = '\n'; // 表示换行符
- 在java中,char的本质就是一个整数,在输出的时候,是unicode码对应的字符。
- 可以给char赋一个整数,然后在输出的时候会按照对应的unicode字符进行输出。
- char类型可以进行运算,相当于一个整数。
public class Changechar{ public static void main(String[] arge){ char c =97; System.out.println(c); //会输出97所编码的字符 System.out.println('a' + 100); //得出结果197 } }
字符型存储到计算机中:
- ‘a’ —> 码值97 —> 转换为二进制 ---->
常用编码:
ASCII: 使用一个字节表示,一共128个字符,实际上可以表示256个字符只使用了128个
Unicode: 固定大小编码,使用两个字节表示,字母和汉字统一都占两个字节。Unicode兼容ASCII
utf-8:大小可变编码,字母使用一个字节,汉字使用三个字节。
gbk: 大小可变,字母使用一个字节,汉字使用两个字节。
gb312:可以表示汉字,gb2312 < gbk。
big5:繁体中文。
boolean类型
布尔类型只允许使用ture和false,无null
boolean类型占一个字节。
boolean类型适用于逻辑运算,一般用于流程控制。
不可以用0或者其他数字代替。
5. 加号的使用
当左右两边都是数值型时,做加法运算。
当左右两边有一方为字符串时,为拼接运算。
public class Changechar{
public static void main(String[] arge){
System.out.println(100 + 98); //输出加法运算
System.out.println("100" + 98); //字符串拼接
System.out.println(100 + 98 + "hello"); //先加法再字符串拼接
System.out.println("hello" + 100 + 98); //先字符串拼接,再字符串拼接
char a = '男';
char b = '女';
System.out.println(a + b); //cahr类型进行运算先转换为int类型,以码值相加
}
}
6. 基本数据类型转换
当java程序在进行赋值或者运算时,精度小的类型可以自动转换为精度大的数据类型,这个就是自动类型转换。
数据类型按精度大小排序:
char < int < long < float < double
byte < short < int < long < float < double
自动数据类型转换
当有多种类型的数据混合运算的时候,系统首先将所有数据转换成容量最大的那种数据类型,然后再进行计算。
当把精度大的数据类型赋值给精度小的数据类型时会报错,将精度低的赋值给精度高的是,会自动类型转换。
boolean不参与转换。
byte,short和char他们三者可以计算,在计算时首先转换为int,不论是单独还是混合出现。
//给byte类型变量赋值时,先判断该数是否再byte范围中,如果可以就成功 byte b = 10; //short s = 10; //byte类型精度不足以存放int类型数值 int i = 10; byte b = i;
byte 和 short 类型数据进行运算后,精度会被直接提升到int
byte b = 10;
short s = 10;
int i = b + s; //byte 和 short 类型数据进行运算后,精度会被直接提升到int
强制类型转换
自动类型转换的逆过程,将容量大的数据库类型转换为容量小的数据类型。使用时需要加上强制类型转换符
() 可能造成精度降低或者溢出,要格外小心。
int i = (int)1.9; //会将小数位删除造成数据丢失
强制符号只针对最近的操作数有效,往往使用小括号提升优先级
char类型可以保存int类型的常量值,但是不能保存int类型的变量值,需要使用强制类型转换。
byte和short,char类型进行运算的时候,当成int类型处理。
7. 基本数据类型转换为String
经常需要将基本数据类型转换为字符串,或者将字符串转换为基本数据类型。
基本数据类型转换为字符串只需要将值加上 " " .
String s1 = 1 + "";
String类型转换为基本数据类型需要调用包装类的parseXX方法。
Integer.parseInt(""); //如果不能成功转换抛出异常 Double.parseDouble(""); Float.parseFloat(""); Short.parseShort(""); Long.ParseLong(""); Boolean.parseBoolean(""); Byte.ParseByte(""); //char类型通过取出字符串类型中的字符得到 变量.charAt();
8. 运算符
运算符是一种特殊的符号,用以表似乎数据的运算,赋值和比较等。
运算符有:
算数运算符,赋值运算符,关系运算符(比较运算符),逻辑运算符,位运算符,三元运算符
8.1算数运算符
描述 | 运算符 |
---|---|
加-两个对象相加 | + |
减-得到复数或是一个数减去另一个数 | - |
乘-两个数相乘或是返回一个被重复若干次的字符串 | * |
除-X除以Y,两个整数相除得到一个浮点数 | / |
取模-返回除法的余数 本质: a % b = a - a /b * b | % |
幂-返回x的y次幂 | ** |
取整数- 向下取接近除数的整数 | // |
字符串相加 | + |
自加运算(分为前加加和后加加) | ++ |
自减运算(分为前减减和后减减) | – |
如果a%b时a为小数: a % b = a - (int)a / b * b
8.2关系运算符
关系运算符的结果都是boolean类型的,常常使用在if结构中或者循环结构中。
描述 | 运算符 |
---|---|
等于-比较对象是否相等 | == |
不等于-比较两个对象是否不相等 | != |
大于-返回X是否大于Y | > |
小于-返回X是否小于Y(所有比较运算符返回1为真,返回0表示假) | < |
大于等于-返回X是否大于等于Y | >= |
小于等于-返回X是否小于等于Y | <= |
检查是否是类的对象(运行类型) | instanceof |
8.3 逻辑运算符
用于链接多个条件,最终结果也是个boolean类型的值。
短路效率比较高,只运行短路部分。
运算符 | 描述 |
---|---|
& | 逻辑与 |
| | 逻辑或 |
^ | 逻辑异或 |
&& | 短路逻辑与 |
|| | 短路逻辑或 |
! | 取反 |
8.4 赋值运算符
赋值运算符就是将某个运算后的值,赋给指定的变量。
基本赋值运算符: =
复合赋值运算符: +=,-=,*=,%=
复合赋值运算符会进行类型的转换
byte b = 3; b += 2 // b = (byte)(b + 2)
运算符 | 描述 |
---|---|
= | 等于- a=1 |
+= | 加法赋值语句-“a+=b” = “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=a * *b” |
//= | 加法赋值语句-“a//=b” = “a=a//b” |
8.5 三元运算符
条件表达式 ?表达式1 :表达式2;
如果条件表达式的结果为true,运算后的结果为表达式1;
如果条件表达式的结果为false,运算后的结果为表达式2;
表达式1和表达式2要为可以赋给接收变量的类型(或可以自动转换)
int a,b; int c = a > b ?a : b;
可以在三元运算符中使用强转:
a,b; int c = a > b ?(int) 1.1: (int)2.2;
8.6 运算符的优先级
运算符具有不同的优先级。
只有单目运算符和赋值运算符是从右向左执行的。
运算符 | 描述 |
---|---|
++ – ~ !(data type) | R —> L |
* / % | L —> R |
+ - | L —> R |
>> << >>> | L —> R |
<= < > >= instanceof | L —> R |
== != | L —> R |
& | L —> R |
^ | L —> R |
| | L —> R |
&& | L —> R |
|| | L —> R |
= += -= *= /= %= &= ^= <<= >>= >>>= | |
8.7 位运算
运算符 | 描述 |
---|---|
& | 按位与运算符- 参与运算的两个值相应位为1则结果为1,否则为0 |
| | 按位或运算符- 参与运算的其中一个值为1,结果为1. |
^ | 按位异或运算符- 当两相应的二进制位相异时,结果为1. |
~ | 按位取反运算符- 对数据的每个二进制位取反,即把1变为0,把0变为1. |
<< | 左移动运算符- 运算数的各二进制位全部左移若干位由"<<"指定移动的位数。 |
>> | 右移动运算符-运算数的各二进制位全部右移若干位由"<<"指定移动的位数。 |
>>> | 无符号右移 |
8.8 原码,反码,补码
二进制的最高位是符号位,0表示正数,1表示负数
正数的源码,补码,反码都一样。(三码合一)
负数的反码 = 它的原码除符号位不变,其余位取反。
复数的补码 = 它的反码 + 1; 复数的反码 = 复数的补码 - 1;
0 的反码,补码都是0.
Java没有无符号数,java中的数都是有符号的。
在计算机运算的时候,都是以补码的方式来运算的。
看运算结果时,要看它的原码。
9. 键盘输入语句
在编程中,需要接收用户输入的数据,就可以使用键盘输入语句来获取/
需要一个扫描器,Scanner。
//导入该类所在的包 //创建该类的对象 //使用方法 import java.util.Scanner; //导入该类所在的包 public class Changechar{ public static void main(String[] arge){ String name; Scanner sa = new Scanner(System.in); //创建该类的对象,传入标准输入流 System.out.print("请输入:"); name = sa.next(); //使用方法 System.out.print(a); } }
Scanner对象.next() : 接收一个字符串
Scanner对象.nextInt() : 接受一个整数
Scanner对象.nextLong() : 接收一个长整型
Scanner对象.nextShort() : 接收一个短整型
Scanner对象.nextFloat() : 接收一个单精度
Scanner对象.nextDouble() : 接收一个双精度
10. 控制结构
在程序中,程序运行的流程控制决定程序是如何执行的,是我们必须掌握的,主要有三大流程控制语句。
- 顺序控制
- 分支控制
- 循环控制
10.1 顺序控制
程序从上到下逐行的执行,中间没有任何判断和跳转。
向前引用
10.2 分支控制
经过判断指定的条件表达式,达到执行响应代码的作用,分为但分支,双分支和多分支。
分支控制之间可以嵌套执行。
在一个分支结构中又完整的嵌套了另一个完整的分支结构,里面的分支结构成为内层分支外面的分支结构称为外层分支。
if判断:
if(条件表达式){ 执行代码块; } else if(){ 执行代码 } else { 执行代码 }
switch分支结构
switch(表达式){ case 常量一: 语句一;break; case 常量二: 语句二;break; case 常量三: 语句三;break; case 常量四: 语句四;break; default:语句;break; //default后的break不重要,有没有都会退出 }
细节:
- 表达式数据类型,应该和case后的常量类型一致,或者可以自动转换成可以比较的类型。
- switch(表达式) 中的表达式的返回值必须是byte,short,int,cahr,enum,String。
- case后的值必须是常量不能是变量。
- break语句用来跳出switch循环,如果没有写会顺序执行到结尾。
分支控制的选择:
- 如果判断的具体数值不多,而且符合byte,short,int,char,enum,String。
- 对于区间判断,对结构未boolean类型判断,使用if,if的范围比较广。
10.3 循环控制
for循环控制:
- for关键字,表示循环控制。
- for有四要素: 循环变量初始化,循环条件,循环操作,循环变量迭代。
for(循环变量初始化;循环条件;循环变量迭代){
循环操作(可以多条语句);
}
注意:
- 循环条件是返回一个布尔值的表达式。
- 如果循环变量初始化定义变量,此变量的适用范围只有for循环中。
- for(;循环判断条件;) ,中的初始化和变量迭代可以写到其他地方,但是两边的分号不能省略。
- 循环初始值可以有多条初始化语句,但是要求类型一样,并且中间用逗号隔开,循环变量迭代也可以有多条变量迭代语句,中间用逗号隔开。
while循环控制
循环变量初始化; while(循环条件){ 循环体; 循环变量迭代; }
注意:
- while也有四要素,只是放置四要素的位置和for循环不一样。
- 循环条件返回一个布尔值的表达式。
- while循环是先判断再循环。
do-while循环控制
do{ 循环体; 循环变量迭代; }while(循环条件);
注意:
- do-while循环至少会循环一次。
- while也有四要素。
11. 跳转控制语句
- break语句用于终止某个语句块的执行,一般用于switch和循环中。
{ ………… break; ………… }
break语句出现在多层嵌套的语句块中时,可以通过标签指明要退出的是哪一层语句块。
label1:{ label2: { label3: { break label2; } } }
注意:
- break 语句可以指定推出哪层。
- label1 是标签名,名字由人指定。
- break 后指定到哪个lable就退出到哪里。
- 如果没有指定break,则默认推出最近的循环体。
- continue语句用于结束本次循环,继续执行下一次循环。
{ ………… continue; ………… }
注意:
- Continue语句出现在多层嵌套语句体中时,可以通过标签名指定要跳过的是哪一层循环,和break的标签使用方法相同。
label1:{ label2: { label3: { continue label2; } } }
return
return语句表示跳出所在的方法。如果在主方法中写return表示推出程序。
12. 数组
数组可以放置多个同一类型的数据,数组也是一种数据类型,使引用类型。
数组类型[] 数组名 = {};
可以通过数组名[下标] 来访问数组元素。
下标是从零开始。
数组名.length 属性表示数组的长度
注意:
数组是多个相同类型数据的组合,实现对这些数据的统一管理。
数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型,但是不能混用。
数组创建之后,如果没有赋值,有默认值:
- int 0 short 0 long 0 byte 0 float 0.0 double 0.0 char \u0000 boolean false String null
使用数组的步骤: 1.声明数组并开辟空间 2.给数组各个元素赋值3.使用数组
数组的下标从0开始。
数组下标必须在指定范围内使用,否则抛出异常:下标越界异常,比如:
int a[] = new int[5]; 有效下标为0-4.
数组属于引用类型,数组型数据是对象。
12.1 动态初始化
数据类型[] 数组名 = new 数据类型[大小];
定义数组可以不定义大小,开辟空间必须定义大小。
数组初始化未赋值时初始化为0。
第一种:int[] a = new int[5]; 第二种:int a[] = new int[5]; 第三种:int a[]; a = new int[5]; int i; Scanner scan = new Scanner(System.in); for(i=0;i<a.length;i++){ a[i] = scan.nextInt(); }
12.2 静态初始化
数据类型 数组名[] = {元素值,元素值}
int a[] = {1,2,3};
12.3 数组赋值的分配机制
基本数据类型赋值,这个值就是具体的数据,不会相互影响。
数组默认情况下时引用传递,赋的是地址,会相互影响。
int x = 1; int y = x; y = 2; //值传递,变量不会相互影响 int a[] = {1,2,3}; int b[] = a; b[0] = 2; //址传递,会相互影响
13 二维数组
二维数组由多个一维数组组成,他的各个一维数组的长度可以相同,也可以不相同。
二维数组定义时可以不定义大小,开辟空间时必须指定大小。
int[][] arr = {{},{},{}}; int arr[][] = {{},{},{}}; int[] arr[] = {{},{},{}};
二维数组动态初始化:
已知二维数组项数
int[][] arr = new int[5][5];
for(int i=0;i< arr.length;i++){
for(int j = 0; j < arr[i].length; i++){
arr[i][j] = 1;
}
}
int[][] arr = {{1,2,3},{1,2},{1}}
未知二维数组项数
int[][] arr = new int[5][];
for(int i=0;i< arr.length;i++){
arr[i] = new int[i + 1];
}
14 类与对象
使用类的过程:
- 定义相应的类
- 创建实例对象
- 先加载类信息(属性和方法信息,只会加载一次(在方法区加载),下次创建实例不加载)
- 在堆中分配空间,进行默认初始化
- 把堆中地址分配给对象
- 进行指定初始化
- 调用类的成员或者方法
class 类名{ 属性; 属性; 方法(){}; } public class doubao{ public static void void(String[] arge){ 类名 对象名 = new 类名(); \ 类名 对象名; 对象名.属性; \ 对象名 = new 类名; 对象名.方法(); \ } }
java内存分配机制
栈:一般存放基本数据类型(局部变量)
堆: 存放对象,数组
方法区:常量池(常量,比如字符串),类加载信息
14.1 属性
从概念叫法上看,成员变量 = 属性 。
属性是类的一个组成部分,一般是基本数据类型,也可以是引用数据类型(对象,数组)。
属性的定义语法同变量:
//访问修饰符 属性类型 属性名; public int age;
控制属性的访问范围
- public
- proctected
- 默认
- private
属性可以定义为任何类型,包括基本类型和引用类型。
属性如果不赋值,有默认值,同数组。
- int 0 short 0 long 0 byte 0 float 0.0 double 0.0 char \u0000 boolean false String null
14.2 成员方法
成员方法的定义:
- 提高了代码复用性
- 可以将实现的细节封装起来,然后供其他的用户使用
- 成员方法中不能再定义方法
class clssname{ String name; //属性 访问修饰符 返回数据类型 方法名(参数列表……){ //方法= 语句; return 返回值; } }
-
访问修饰符控制方法的使用范围。
-
数据类型(返回类型): 表示成员方法输出,void表示没有返回值。
- 返回类型可以为任意类型,包含基本类型或引用类型,一个方法只有一个返回值。
- return语句不是必须的 ,但是如果方法要求有返回的数据类型,则必须要有return语句且返回数据类型必须和return的值类型一致或者兼容。
-
形参列表:表示成员方法的输入。
- 一个方法可以有零个参数也可以有多个参数,中间用逗号隔开。
- 参数类型可以为任意类型,包含基本类型或者引用类型。
- 调用带参数的方法时,一定对应这参数列表传入相同类型或者兼容类型的参数。
- 方法定义时的参数被称为形式参数,简称形参,方法调用时的参数被称为实际参数,简称实参。实参和形参的类型要一致或者兼容,个数顺序必须一致。
方法调用小结:
- 当程序执行到方法时就会开辟一个独立的空间(栈空间)。
- 当方法执行完毕或者执行到return时就会返回。
- 返回到调用方法的地方。
- 返回后,继续执行方法后面的代码。
- 当main方法(栈执行完毕,整个程序退出)。
14.3 方法的重载
java中允许同一个类中,多个同名方法的存在,但是要求形参列表不一致(形参名字无所谓,主要是形参类型和顺序)。
class Myclass{
public void myOverload(int i){ }
public void myOverload(char name){ }
public void myOverload(String age){ }
}
14.4 可变参数
java中允许将同一个类中多个同名同功能但是参数不同的方法封装为一个方法。
//基本语法: 访问修饰符 返回类型 方法名( 数据类型... 形参名 ){ } // int...表示接受的是可变参数,类型是int,使用可变参数可以当作数组使用 //使用形参名.length可以得到输入参数的长度 class Myclass{ public void myOverload(int... nums){ } }
- 可变参数的实参可以是0个也可以是任意多个。
- 可变参数的实参可以是数组。
- 可变参数的本质就是数组。
- 可变参数可以和普通参数一起放在形参列表,但是可变参数必须放在最后.
class Myclass{ public void myOverload(String name,int... nums){ } }
- 一个形参列表只能出现一个可变参数。
14.5 构造器
构造器又叫构造方法,是类的一种特殊方法,可以在创建类的对象时传入参数,完成数据的初始化。
创建对象时,先创建属性,然后赋默认值,再调用构造器进行初始化。
[修饰符] 方法名(形参列表) { 方法体; }
构造器的修饰符有可以默认,构造器没有返回值。
构造器的方法名必须和类名一致。
构造器由系统调用。
class doubao{ String name; int age; public doubao(String name){ //构造器没有返回值 this.name = name; } public doubao(int age){ //构造器没有返回值 this.age = age; } }
如果用户没有定义构造器,系统会自动给类生成一个默认无参数的构造器(也叫默认构造器)。
一旦自己定义了构造器,系统就不会生成默认构造器。
14.6 this关键字
this 关键字用来指代当前调用this的对象。
this可以看作时指代对象的属性,保存着指代对象的地址。
this关键字可以用来访问本类的属性,方法,构造器。
this用于区分当前类的属性和局部变量。
访问成员方法的语法:this.方法名(参数列表);
访问构造器语法this(参数列表),只能在构造器中使用。 且必须放在第一行
class doubao{ String name; int age; public doubao(){ this(12,"doubao"); //无参构造器访问了有参数构造器 public doubao(int age,String name){ //构造器没有返回值 this.age = age; } }
this不能再类定义的外部使用,只能再类中的方法中使用。
15 递归
执行一个方法时,就创建一个新的相互独立的空间(栈空间);
方法的局部变量时独立的不会互相影响。
如果方法中使用的是引用类型的变量,就会共享引用类型变量的数据。
递归必须向推出递归条件逼近,否则就是无限递归。
16. 作用域
作用域细节:
在Java中,主要的变量就是属性(成员变量)和局部变量。
局部变量一般是指在成员方法中定义的变量。
- 全局变量:就是属性,作用域为整个类体。可以被本类使用,也可以在其他类使用。
- 局部变量:也就是属性之外的其他变量,作用于定义它的代码块中。
全局变量可以不赋值,直接使用,因为有默认值,局部变量必须赋值使用,没有默认值。
- 属性和局部变量可以重名,访问遵循就近原则。
- 在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名。
- 属性生命周期长,伴随着对象的创建而创建,伴随着对象的死亡而死亡,局部变量,生命周期短,伴随着他的代码块的执行而创建,伴随着代码块的结束而死亡。
- 全局变量可以加访问修饰符,局部变量不能加访问修饰符。
17. 包
本质:实际上就是创建不同的文件夹保存文件。
作用:区分相同名字的类,控制管理类。控制类的访问范围。
命名规则:
只能包含数字,字母,下划线,小圆点,但是不能以数字开头,不能是关键字或者保留字。
命名规范:
一般是小写字母 + 小圆点,com.公司名.项目名.业务模块名
java.lang.* 基本包,默认引入,不需要再引入
java.util.* util包,系统提供的工具包,工具类,使用Scanner
java.net.* 网络包,网络开发
java.awt.* 是做Java的界面开发。GUI
package 的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package。
import指令位置放在package的下面,再类定义前面,可以有多句没有顺序。
18. 访问修饰符
java一共提供了四种访问修饰符号,用于控制方法和属性(成员变量)的访问权限(范围)。
1.公开级别: public,对外公开。
2.受保护级别:protected,对子类和同一个包中的类公开。
3.默认级别:没有符号,向同一个包的类公开。
4.私有级别:private修饰,只允许类本身可以访问,不公开。
注意事项:
- 修饰符可以用来修饰类中的属性,成员方法以及类。
- 只有默认的和public才能修饰类,并且遵循以上的访问权限的特点。
- 成员方法的访问规则和属性完全一样。
19. 面向对象—封装
面向对象三大特征:封装,继承,多态。
封装:就是把抽象出来的对象[属性]和对数据的操作[方法]封装一起,数据被保护在内部程序的其他部分只有通过被授权的操作[方法],才能对数据进行操作。
- 隐藏实现细节。
- 可以对数据进行验证,保证安全合理。
封装实现步骤:
- 将属性进行私有化。
- 提供一个公共的set方法,用于对属性赋值。
- 提供一个公共的get方法,用于获取属性的值。
20. 面向对象—继承
继承可以解决代码复用,让我们的编程更加接近人类思维,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义相同的属性和方法,所有的子类不许要重新定义这些方法和属性,只需要通过extends来声明继承父类即可。
class 子类 extends 父类{ }
子类自动拥有父类定义的属性和方法。
父类又叫超类,基类。
子类又叫派生类。
继承细节:
- 子类继承了所有的属性和方法,但是私有属性和方法不能在子类中直接访问,要通过父类提供的公共的方法访问。(继承了包括私有的所有属性方法只是不能访问)
- 子类必须调用父类的构造器完成父类的初始化。
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super去指定使用父类的哪个构造器完成对父类的初始化工作,否则编译不会通过。
- 如果希望指定去调用父类的某个构造器,则显示的调用一下。
super(参数列表);
- super在使用时,需要放在构造器的第一行。
- super()和this()都只能放在构造器的第一行,因此这两个方法不能共存在一个构造器中。
- java的所有类都是object的子类。
- 父类构造器的调用不限于直接父类,将一直追溯到object类。
- 子类最多只能直接继承一个父类。
- 不能滥用继承,子类和父类之间必须满足is-a的逻辑关系。
super关键字
super代表父类的引用,用于访问父类属性,方法,构造器。
- 访问父类的属性,但是不能访问父类的private属性。
super.属性名;
- 访问父类的方法,不能访问父类的private方法。
super.方法名(参数列表);
- 访问父类的构造器。
super(参数列表); //只能放在构造器的第一句,只能出现一句。
区别点 | this | super | |
---|---|---|---|
访问属性 | 访问本类中的属性,如果本类中没有此属性则从父类中继续查找 | 访问父类中的属性 | |
调用方法 | 访问本类中的方法,如果本类中没有此方法则从父类继续查找 | 直接访问父类中的方法 | |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 | |
特殊 | 表示当前对象 | 子类中访问父类对象 |
21. 重写(override)
子类 有一个方法,和父类的某个方法的名称,返回类型,参数是一样的,那么我们可以说这个方法覆盖了父类的方法。
方法重写细节:
子类方法的参数,方法名称,要和父类方法的参数,方法名称完全一样。
子类方法的返回类型和父类方法的返回类型一样,或者父类是返回类型的子类。
例如:父类返回类型:object 子类返回类型: String
子类方法不能缩小父类方法的访问权限。
名称 | 发生范围 | 方法名 | 参数列表 | 返回类型 | 修饰符 |
---|---|---|---|---|---|
重载 | 本类 | 必须一样 | 类型,个数,顺序至少一个不同 | 无要求 | 无要求 |
重写 | 父子类 | 必须一样 | 必须完全相同 | 子类重写的方法和父类返回的类型一致或者是其子类 | 子类方法不能缩小父类的访问范围 |
22.面向对象—多态
多态:方法或对象具有多种形态,多态是建立在封装和继承基础之上的。
多态有利于代码维护和代码复用性。
22.1 方法的多态
方法的多态:重载:传入不同的参数会调用不同的方法。
重写:自动定位使用的方法。
22.2 对象的多态:
- 一个对象的编译类型和运行类型可以不一致。
- instanceof用于判断对象的运行类型是否为指定类型或者指定类型的子类型。
- 编译类型在定义对象时就确定了,不能改变。
- 运行类型是可以变化的。
- 编译类型看定义时 = 号的左边,运行类型看定义时 = 号的右边 。
- 属性没有重写之说,属性的值看编译类型。
Animal animal = new Dag();
多态的使用前提: 两个对象之间存在继承关系。
本质:父类的引用指向了子类的对象(多态向上转型)
多态的向上转型:
语法: 父类类型 引用名 = new 子类类型()
特点: 可以调用父类中的所有成员(需要遵循访问权限),不能调用子类中的特有成员,最终结果看子类的具体实现。 (在编译阶段,能和调用哪些成员由编译器决定)
Animal animal = new Dag();
多态的向下转型:
语法: 子类类型 引用名 = (子类类型)父类引用;
只能强转父类的引用,不能强转父类的对象。(改变的只是编译器所限定的调用范围 )
要求父类的引用必须指向的是当前目标类型的对象。
向下转型后可以调用子类类型中所有的成员。
Animal animal = new Dag();
Dag dag = (Dag)animal; //可以使用子类全部方法
22.3 动态绑定机制
当调用对象的方法时,该方法会和该对象的内存地址/运行类型绑定
当调配用对象属性时,没有动态绑定机制,哪里声明,哪里使用。
发挥作用的顺序: 动态绑定机制 继承机制
22.4 多态数组
可以将数组编译类型定义为父类类型,向数组中添加多种子类类型
Object[] obj = new Object[5];
obj[0] = new Object();
obj[1] = new Ingter(2);
22.5 多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型。
23. Object类方法
23.1 equals()与 ==
== :即可以判断基本类型,又可以判断引用类型。
==: 如果判断基本类型,判断的是值是否相等。
==:如果判断引用类型,判断的是地址是否相等,即是不是同一个对象。(无论是否向上转型,只要地址相同就返回true)
equals: 是Object的方法,只能判断引用类型。
用于判断地址是否相等,子类中往往重写该方法,用于判断内容是否相等。
- Object中equals()判断地址是否相同。
- 其他重写后的equals()方法比较两个值是否相同。
object.equals(obj);
23.2 hashCode
返回对象的哈希码值,支持此方法是为了提高哈希表的性能。(一般是通过该内部地址转换成一个整数来实现的)
两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
两个引用,如果指向的不是同一个对象,则哈希值是不一样的。
哈希值主要是根据地址号来的,不能完全将哈希值等价于地址。
hashCode()如果需要,也可以重写。使相同的类的对象返回hashCode码相同。
object.hashCode()
23.3 toString()
默认返回: 全类名(包名 + 类名 ) + @ + 哈希值的十六进制
子类往往会重写toString()方法来返回对象的属性信息。
当直接输出一个对象时,toString()会默认被调用。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
object.toString()
23.4 finalize()
当对象被回收时,系统自动调用该对象的finalize()方法,默认处理。子类可以重写该方法,做一些释放资源的操作。
回收条件:当某个对象没有任何引用,则jvm就会认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finelize()方法。可以重写finalize()方法,释放资源。
垃圾回收机制的调用,是由系统来决定,也可以通过System.gc()主动触发垃圾回收机制。
System.cg();
24. 类变量和类方法(static)
类变量又叫静态变量,该变量会被该类所有实例对象共享。
静态变量在类加载的时候就生成了,即使没有创建对象也可以使用。
定义类变量:
访问修饰符 static 数据类型 变量名;
static 访问修饰符 数据类型 变量名;
class xxx{ public static int count; static public int count }
访问变量: -----类变量的访问,必须遵循相关的访问权限。
对象名.类变量名;
类名.类变量名;
类方法也叫静态方法。
不创建实例也可以调用方法(当作工具使用),使用静态方法。
定义静态方法:
访问修饰符 static 返回数据类型 方法名;
static 访问修饰符 返回数据类型 方法名;
public static void 方法名{ }
访问方法: -----类方法的访问,必须遵循相关的访问权限。
对象名.方法名;
类名.方法名;
注意事项:
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法去,类方法中无this参数,普通方法隐含着this参数。
- 类方法不能使用和对象相关的关键字。
- 类方法可以通过类名调用,也可以通过对象名调用。
- 普通方法和对象有关,需要通过对象名调用。不能通过类名调用。
- 普通成员方法,既可以访问非静态成员,也可以访问静态成员。
- 静态成员方法,只能访问静态成员!
25. main方法
- java虚拟机需要调用类的main()方法,所以该方法的访问权限必须时public。
- java虚拟机在执行main方法时不创建对象,所以该方法必须是static
- 该方法接收Sting类型的数组参数,该数组中保存着执行java命令时传递给所运行的类的参数。
26.代码块
代码块又称初始化块,属于类中的成员[即类中的一部分],类似方法,将逻辑语句封装在方法体中,通过{}包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或者类显示调用,而是加载类时,或者创建对象时隐式调用。
先调用代码块,后调用构造器。
定义语法:
[修饰符]{
代码
}
static{ 代码; } { 代码; }
- 修饰符可选,只能填写static
- 代码块分为静态代码块和非静态代码块
- 逻辑语句可以为任何逻辑语句。(输入,输出,方法调用,循环,判断)
- ; 号可以写上也可以省略。
代码块细节:
static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而加载,并且只会执行一次(类只加载一次)。如果时普通代码块,每创建一个对象就执行一次。
类加载:
- 创建对象实例
- 创建子类对象实例,父类也会加载
- 使用类的静态成员
普通代码块在创建对象实例时,会隐式调用,被创建一次就调用一次,如果只是使用类的静态成员,普通代码块不会执行。
创建一个对象时,在一个类的调用顺序:
- 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按照他们定义的顺序调用)
- 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按照定义顺序调用。)
- 调用构造方法
构造方法的前面其实隐含了super()和调用普通代码块。静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优于构造器和普通代码块执行。
public 构造器(){ super(); 普通代码块; }
静态代码块,静态属性,普通代码块,普通属性初始化,构造方法的调用顺序:
- 父类的静态代码块和静态属性
- 子类的静态代码块和静态属性
- 父类的普通代码块和普通属性初始化
- 父类的构造方法
- 子类的普通代码块和普通属性初始化
- 子类的构造方法
静态代码块只能调用静态成员,普通代码块可以调用任意成员。
27. final关键字
final可以修饰类,属性,方法,和局部变量。
final的使用场景
不希望类被继承时,可以使用final修饰。
final class A { } //这个类将无法被继承
不希望父类的某个方法被重写/覆盖时可以使用final关键字修饰。
class A { public final void hi(){ //此方法不能被修改 } }
不希望类的某个属性的值被修改可以使用final修饰。
class A { public void hi(){ //此方法不能被修改 public final int i = 0.1; //此属性不能被修改 } }
不希望某个局部变量被修改,可以使用final修饰。
使用细节:
final修饰的属性又叫常量,一般使用大写来命名。
final修饰的属性在定义时,必须赋初值,且以后不允许被修改,赋值可以在的位置:
- 定义时: final int i = x;
- 构造器中
- 代码块中
如果final修饰的属性时静态的,则初始化位置只能在
- 定义时
- 静态代码块中
final类不能继承,但是可以实例化对象
如果类不是final类。但是含有final方法,则该类虽然不能重写但是可以被继承。
一般来说,一个类已经是final,就没必要将方法修饰成final方法了。
final不能修饰构造方法。
final和sitatic往往搭配使用,效率更高,底层编译器做了优化处理。
public static int cun = 1; //只是用static修饰属性,使用时需要加载类 public final static int num = 1; //将final和static一起使用,不需要加载类,直接加载属性
包装类(integer,Double,Float,Boolean等都是final),String也是final类。
28. 抽象类
使用abstract关键字修饰一个类时,这个类就是抽象类。
抽象类不能被实例化。
使用abstract关键字来修饰一个方法时,这个方法就是抽象方法。
抽象方法不能有方法体。
abstract class cat{ public abstract void getInt(); }
抽象类的价值更多作用在设计,时设计者设计好后,让子类继承并实现抽象类。
抽象类细节:
- 抽象类不能被实例化。
- 抽象类可以不包含抽象方法。
- 一旦一个类包含了抽象方法,则必须声明为从抽象类。
- abstract只能修饰类和方法,不能修饰属性。
- 抽象类可以有任意成员(非抽象方法,构造器,静态属性)。
- 如果一个类继承了抽象类,则它必须实现(只要有方法体不管为不为空)抽象类的所有抽象方法,除非它自己也声明为抽象类。
- 抽象方法不能使用private,final,static来修饰,因为这些关键字都是和重写相违背的。
29. 接口
接口就是给出一些没有实现的方法,封装到一起,到某个要使用的时候。就根据具体情况把这些方法写出来。
在接口中可以省略abstract
interface 接口名{ 属性; 方法; //1.抽象方法 2.默认实现方法 3.静态方法 } class 类名 implements 接口{ 实现的方法; }
- 接口在jdk7.0之前接口中所有方法都没有方法体,即都是抽象方法。
- 在jdk8.0及以后可以定义静态方法,和已经实现的方法,但是必须使用default修饰。
接口的细节:
-
接口不能被实例化,
-
接口中的方法都是public修饰的方法,在接口中的抽象方法可以省略abstract。
-
一个普通类实现接口,就要把接口中的所有抽象方法都实现。
-
抽象类实现接口可以不用全部实现接口的抽象方法。
-
一个类可以同时实现多个接口。
-
接口中的属性只能是final,而且是public static final 修饰符
int a = 1 //实际上是 public static final interesting a = 1;
-
接口中的属性的访问方式: 接口名.属性名。
-
一个接口不能继承其他的类,但是可以继承多个别的接口。
interface 接口名 extends 接口,接口;
-
接口的修饰符只能是public和默认,这点和类相同。
接口与抽象类的区别
- 接口和继承所解决的问题不同:
- 继承的价值主要在于:解决代码的复用性和可维护性。
- 接口的价值主要在于:设计。设计好各种规范(方法),让其他类去实现这些方法。
- 接口比继承更加灵活。
- 接口在一等程度上实现代码解耦。
接口的多态特性
- 多态参数。 接口参数可以接收实现了接口的实例。
- 多态数组。 可以定义接口类型的数组,存储实现接口的对象。
- 接口存在多态传递现象。
30. 内部类
一个类的内部又完整的嵌套了另一个类结构,被嵌套的类被称为内部类。嵌套其它类的类被称为外部类,这是类的的第五大成员。(属性,方法,构造器,代码块,内部类)
内部类的最大特点就是可以直接访问私有属性,并且可以体现类于类之间的包含关系。
class 外部类{ class 内部类{ } }
内部类分类:
- 定义在外部类局部位置上(比如方法内,代码块中)
- 局部内部类(有类名)
- 匿名内部类(没有类名)
- 定义在外部类的成员位置上:
- 成员内部类(没用static修饰)
- 静态内部类(使用static修饰)
30.1 局部内部类
局部内部类时定义在外部类的局部位置,比如方法中,代码块中。并且有类名。
class Outer02 { private int n1 =100; public void m1(){ class Outer01{ private int n2 = 10; } } } }
可以直接访问外部类的所有成员,包括私有的。
不能添加访问修饰符,因为他的地位就是一个局部变量。局部变量是不能使用修饰符的,但可以使用final修饰,因为局部变量也可以使用final。(使用final禁止继承)
作用域:仅仅在定义它的方法或代码块中。
局部内部类访问外部类的成员:直接访问。
外部类访问局部内部类的成员: 创建对象再访问(需要再作用域中)
外部类不能直接访问局部内部类(局部内部类是一个局部变量)
如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员 可以使用 外部类名.this.成员去访问。(外部类.this实质就是外部类的对象)
class Outer02 { private int n1 =100; public void m1(){ class Outer01{ private int n2 = 10; public void m2(){ System.out.println('内部类的n1'+ n1 + '外部类的n1'+ Outter02.this.n1) } } } } }
30.2 匿名内部类
本质是类,内部类,同时还是一个对象。
匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征。
可以直接访问外部类的所有成员包含私有的。
不能添加访问修饰符,因为它的地位就是一个局部变量。
作用域: 仅仅是在定义他的方法块中。
该类没有指定的名字,系统自动分配名字。(外部类名$1…)
匿名内部类定义在外部类的局部位置,比如方法中并且没有类名。
匿名内部类可以实现普通类,接口,抽象类,简化编程。
语法:
new 类名或接口(参数列表){ 类体; }; 接口或类类型 对象名 = new 类名或接口(参数列表){ 类体 }; //底层代码 1. 实现指定接口或者类的方法 2. 返回对象 3. 销毁实现的类
匿名内部类访问外部类成员: 直接访问。
外部其他类不能访问匿名内部类(匿名内部类的地位就是一个局部变量)
如果外部类和内部类的成员重名时,内部类的访问遵循就近原则,如果想访问内部类的成员可以使用 外部类.this.成员 去访问。( 外部类.this 表示指定类的实例 )
匿名内部类可以简化开发。
匿名内部类的编译类型就是指定的类型,运行类型就是系统自动指定的类型,匿名内部类使用一次后就销毁。(实现的类无法使用,对象可以重复使用)
使用方法数量由编译类型限定,方法内容由运行类型限定。
匿名内部类实践:
//定义接口和指定方法 interface Doubao{ public void show(); } public static void f1(Doubal doubao){ doubao.show(); } //使用方法 f1( //直接向方法种传入匿名内部类。 new Double(){ public void show(){ System.out.println("匿名内部类重写方法"); } } );
30.3 成员内部类
成员内部类定义在外部类的成员位置,并且没有static修饰。
可以直接访问外部类的所有成员,包含私有。
可以任意添加访问修饰符(public ,protected, 默认 ,private),因为它就是类的一个成员。
class Doubao{ class Dahei{ } }
作用域: 和外部类的其他成员一样。
成员内部类访问外部类: 直接访问
外部类访问内部类: 创建对象再访问
外部其他类访问成员内部类:
1. 外部类.成员内部类 对象名 = 外部类对象.new 内部类();
//定义内部类和外部类 class Doubao{ class Dahei{ } } //使用内部类 Doubao doubao = new Doubao(); Doubao.Dahei = doubao.new Dahei();
- 在外部类中编写方法,返回内部类的实例。
外部类和内部类的成员重名时,内部类访问的话,遵循就近原则,如果向访问外部类的成员可以使用外部类.this.成员 去访问。( 外部类.this 表示指定类的实例)
30.4 静态内部类
静态内部类是定义在外部类的成员位置,并且有static修饰。
可以直接访问外部类的所有静态成员,包含私有的,但是不能直接访问非静态成员。
可以添加任意访问修饰福(public,protected,默认,private),因为它的地位就是一个成员。
作用域:同其他成员,为整个类体。
//定义静态内部类和外部类 class Doubao{ static class Dahei{ } }
静态内部类访问外部类(比如静态属性): 直接访问所有静态成员
外部类访问静态内部类 : 创建对象再访问。
外部其他类访问静态内部类:
外部类名.静态内部类名 对象名 = new 外部类名.静态内部类名();
//定义静态内部类和外部类 class Doubao{ static class Dahei{ } } //使用静态内部类 Doubao.Dahei 对象名 = new Doubao.Dahei(); //因为是静态内部类,是可以通过类名直接访问(前提满足访问权限)
2.编写方法,返回静态内部类实例。
如果外部类和静态内部类的成员重名,静态内部类访问的时候,默认遵循就近原则,如果向访问外部类的成员,则可以试用(外部类名.成员)去访问。
31 枚举(eunmeration)
把具体对象一个一个列举。
枚举是一组常量的集合。可以理解为:枚举属于一种特殊的类,里面只包含一组有限的特定的对象。
得到枚举类型的方法:
- 自定义枚举类型
- 使用系统关键字得到枚举类型
31.1 自定义实现枚举类型
不需要提供set方法,因此枚举对象值通常设置为只读。
枚举类的构造器是私有修饰符。
对枚举对象/属性使用final+static共同修饰,实现底层优化。
枚举对象名通常使用全部大写,常量的命名规范。
枚举根据需要,也可以有多个属性。
特点:
- 构造器私有化
- 本类内部创建一组对象。
- 对外暴露对象(通过为对象添加public final static修饰符)
- 可以提供get方法,但是保护要提供set。
1.构造器私有化防止外部创建对象。
2.去掉set方法,防止属性被修改。
3.再枚举类内部直接创建固定的对象。
4.优化,加入final修饰符。
class Season{
private String name;
private String desc;//描述
public static final Season SPRING = new Season("春天","温暖");
public static final Season WINTER = new Season("夏天","温暖");
//构造器需要私有,防止外部调用new创建对象。
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
}
31.2 enum关键字实现枚举
使用enum关键字实现枚举类
使用关键字enum代替class
将public static final Season SPRING = new Season(“春天”,“温暖”);改为:
SPRING(“春天”,“温暖”); ( 枚举类对象名(参数列表) )
如果有多个常量(对象),使用 , 间隔即可。
如果使用enum来实现,需要将常量定义语句现在最前面。
使用enum关键字后不能继承其他类了,因为enum会隐式继承Enum,而java是单继承机制。
枚举类和普通类一样,可以实现接口。
enum Season{
SPRING("春天","温暖"), //使用逗号间隔
WINTER("夏天","温暖");
private String name;
private String desc;//描述
// public static final Season SPRING = new Season("春天","温暖");
// public static final Season WINTER = new Season("夏天","温暖");
//构造器需要私有,防止外部调用new创建对象。
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
- 当我们使用enum关键字开发一个枚举类时,默认会继承enum类。
- 传统的public static dinal Season2 SPEING = new Seasonal(“春天”,“温暖”);
简化成SPRING(“春天”,“温暖”),这里必须知道,它调用的是哪个构造器。
如果使用无参构造器创建枚举对象,则实参列表和小括号都可以省略。
enum Season{ SPRING("春天","温暖"), //使用逗号间隔 WINTER("夏天","温暖"), WUCAN; //等价于调用无参构造器WUCAN() private String name; private String desc;//描述 private Season() { } private Season(String name, String desc) { this.name = name; this.desc = desc; } }
枚举对象必须放在枚举类的首行。
- 使用enum关键字会隐式继承Enum类,这样就可以使用Enum类的相关的 方法。
方法名 | 详细描述 |
---|---|
valueOf | 传递给方法枚举常量的字符串,得到相应常量名的对象。 |
toString | 得到当前枚举常量的的名称。你可以通过重写这个方法使得到的结果更加易读。 |
equals | 再枚举类型中直接使用“==”判断枚举常量是否相等,equals为了再Set,List和Map中使用。equals()是不可变的。 |
hashCode | 实现了hashCode和equals保持不变 |
getDeclanngClass | 得到枚举常量所属枚举类型Class对象,可以用来判断两个枚举常量是否属于同一枚举类型。 |
name | 得到当前枚举常量的名称。建议有限使用toString |
ordinal | 得到当前枚举常量的次序。从0开始编号。 |
compareTo | 枚举类型实现Comparable接口,这样可以比较两个枚举常量编号大小 |
clone | 枚举类型不能被Clone,为了防止子类实现克隆方法,Enum实现了一个仅抛出CloneNotSupportedExcrption异常的不变Clone() |
values | 得到对应枚举类中的所有对象 |
31 注解
注解(Annotation),用于修饰解释包,类,方法,属性,构造器,局部变量等数据信息。
和注释一样,注解不影响程序逻辑,但是注解可以被编译或者运行,相当于嵌入在代码中的补充信息。
在javaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等,在JavaEE中注解占据了更重要的角色,例如用来配置应用成秩序的任何切面,代替javaEE旧版中所遗留的繁荣代码和XML配置等。
使用Annotation时要在其前面增加@符号,并把该Annotation当成一个修饰符号使用,用于修饰它支持的程序元素
三个基本的Annotation:
@Override:限定于某个方法,是重写父类方法,该注释只能用于方法。
@Deprecated:用于表示某个程序员元素(类,方法)已经过时。
@Suppress Warnings:抑制编译器警告。
@Target :修饰注解的注解,被称为元注解。
31.1 @Override
如果写了@Override注释,编译器就会去检查该方法是否真的重写父类的方法,如果的确重写了 ,则编译通过,如果没有构成重写,则编译错误。
@Override只能修饰方法。
31.2 @Deprecated
使用@Deprecated修饰某个元素,表示该元素已经过时
即不再推荐使用,但是仍然可以使用。
用于表示某个程序元素(类,方法等)已过时。
可以修饰方法,字段,包,参数,类等。
@Deprecated的作用可以做到新旧版本的兼容和过度。
31.3 @Suppress Warnings
当不希望看到警告时,可以使用@Suppress Warnings抑制警告信息。
@Suppress Warnings({“警告信息”})
@Suppress Warnings({"指定警告"})
在方法前填写抑制范围为整个方法体。
在语句上填写一致范围为单独语句。
31.4 元注解
JDK的元注解用于修饰其他的Annotation,元注释就是修饰其他注释的注释。
元注解的种类:
Retention //指定注解的作用范围: SOURCE,CLASS,RUNTIME
- RetentionPolicy.SOURCE:编译器使用后,直接丢弃这种策略的注释。
- RetentionPolicy.CLASS:编译器将把注解记录在class文件中,当运行Java程序时,JVM不会保留注释,这是默认值。
- RetentionPolicy.RUNTIME:编译器将把注释记录在class文件中,当运行java、程序时,JVM会保留注解,程序可以通过反射获取该注释。
Target //指定注释可以在哪些地方使用。
@Target(value={ CONSTRUCTOR,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER,TYPE})
Documented //指定该注释是否会在javadoc体现。
Inherited //子类会继承父类注释。
- 被它修饰的Annptation将具有继承性,如果某个类使用了被@inherited修饰的Annotation,则其子类将自动具有该注解。
32. 异常
将程序中发生的不正常情况称为“异常”,程序就会抛出异常 :XXXXException
可以使用 Ctrl + Alt + T 快捷处理
执行过程中发生的异常可以分为两类:
- Error(错误): java虚拟机无法解决的严重问题。如:JVM系统内部错误,资源耗尽等严重情况。
- Exception(异常):其他因为编程错误或者偶然的外在因素所导致的一般性问题,可以使用针对的代码进行处理,例如空指针访问,试图读取不存在的文件,网络链接中断。Exception分为两大类:运行时异常和编译异常。
当程序抛出异常后程序就退出,下面的代码就不再执行了。
可以使用try-catch异常处理机制来捕获异常。保证程序的健壮性。
使用了异常处理机制。即使出现了异常,也会继续执行。(从出现异常的try块中语句跳转到catch或finally中,不会继续执行try块中的语句)
try{ }catch { } finally { }
32.1 异常体系图
所有异常的都继承了Serializable接口,继承了Object类
- 异常分为两大类,运行时异常和编译时异常。
- 运行时异常,编译器检测不出来。编译器不要求强制处理的异常,一般只是编程时的逻辑错误,是成员需要应该避免其出现的异常。java.long.RuntimeException类及他的子类都是运行时异常。
- 对于运行时异常,可以不做处理,因为这列异常很普遍,如果全处理可能会对程序的可读性和运行效率产生影响,
- 编译时异常,是编译器要求必须处理的异常。方法抛出编译异常,使用方法时必须处理异常。
31.2 常见的运行时异常
- NullPointerException 空指针异常
- ArithmeticException 数学运算异常
- ArrayIndexOutOfBundsException 数组下标越界异常
- ClassCastException 类型转换异常
- NumberFormatException 字符串不能转换为数字
31.3 常见的编译异常
编译异常是只在编译期间,就必须处理的异常,否则代码不能通过编译。
SQLException 操作数据库时,查询表可能发生异常。
IOException 操作文件时,发生的异常。
FileNotFoundException 当操作一个不存在的文件时,发生异常
ClassNotFoundException 加载类,而该类不存在时,发生异常。
EOFException 操作文件,到文件末尾。发生异常。
IllegalArguementException 参数异常
31.4 异常处理
异常处理就是当发生异常时,对异常处理的方式。
- try—catch—finally 程序员在代码中捕获发生的异常,自行处理。
- throws 将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理就是JVM。(JVM的处理方法就是打印异常,终止程序)
try—catch—finally
java提供try和catch块来处理异常,try块用于包含可能出错的代码,catch块用与处理try块中发生的异常,可以根据需要在程序中有多个try- catch块。
可以省略finally
细节:
如果异常发生了,则异常发生后面的代码不会立即执行,直接进入到catch块中。
如果异常没有发生,则顺序执行try的代码块,不会进入到catch中。
如果希望不管是否发生异常,都执行某段代码,使用finally块。(当出现异常却抛出时,会优先执行finally再往外继续处理)
可以有多个catch语句,不同的异常(进行不同的业务处理),要求父类异常在后,子类异常在前,如果发生异常,只会匹配一个catch。
try { String str = "数字转换"; } catch (子类异常 e) { //同时可以捕获多种异常 //处理异常代码 } catch (父类异常 e) { //处理异常代码 }
可以直接进行try-finally配合使用,这种用法相当于没有捕获异常,因此程序会直接崩掉。但是finally中语句会执行。
try {
可能有异常的代码
} catch (Exception e) {
//当系统发生异常时,系统将异常封装成Exception对象 e传递给catchS
//得到异常对象后可以自己处理
//如果try代码块未产生异常,catch不会执行。
}
finally {
//不管try代码块是否有异常产生,始终要执行finally。
//通常将释放资源的代码放置
}
//例子:
try {
String str = "数字转换";
}
catch (NumberFormatException e) { //同时可以捕获多种异常
int a = Integer.pareInt(str);
}
catch (Exception e) {
int a = Integer.pareInt(str);
}
finally {
System.out.println("输出");
}
throws
使用throws抛出异常最终被抛出到JVM,JVM的处理方式为;输出异常信息,退出程序。
如果没有显示的处理异常,默认就是throws。(方法会有一个不显示的throws Exception)
如果一个方法可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示的声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
在方法声明中使用throws可以声明抛出异常的列表,throws后面的异常类型可以时方法中产生的异常类型,也可以是他的父类。
细节:
- 对于编译异常,程序中必须处理,
- 对于运行时异常,方法中如果没有处理,默认就是throws的处理方式。
- 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型。(子类不能扩大父类抛出异常的类型)
- 在throws中,如果有方法try-catch,就相当于处理异常,就可以不必throws。
- 方法抛出编译异常时,使用该方法必须处理编译时异常。
- 方法抛出运行时异常时,使用该方法不需要处理异常,由默认的处理异常机制。
public static void main(String[] args) throws Exception { //可以是父类也可以是发生的异常,也可以是一个异常的列表,可以抛出多个异常
}
31.5 自定义异常
当程序中出现某些‘错误‘,但该错误信息并没有在Throwable子类中描述处理,这个时候可以自己设计异常类用于描述该错误信息。
自定义异常的步骤
定义类:自定义异常类名,继承Exception或者RuntimeException
如果继承Exception,属于编译异常。
如果继承RuntimeException,属于运行异常。(一般来说继承RunTimeException)
在自定义异常类中创建构造器,使用super()方法向父类构造器传递异常显示信息。(super方法接收的是一个字符串类型)
在方法体中,使用throw关键字手动调用异常。
throw new 异常类(异常输出字符);
public class exection {
public static void main(String[] args) {
throw new AgeException("异常");//手动调用自定义异常
}
}
//自定义异常类,继承RuntimeException或者Exception
class AgeException extends RuntimeException{
public AgeException(String message) {
super(message);
}
}
意义 | 位置 | 后接 | |
---|---|---|---|
throws | 异常处理的一种方式 | 方法声明处 | 异常类型 |
throw | 手动生成异常的关键字 | 方法体中 | 异常对象 |
32 包装类
针对八种基本数据类型的引用类型–包装类(wrapper)
有类的特点,就可以调用类中的方法。
基本数据类型 | 包装类 |
---|---|
boolean | Boolean |
char | Character |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
Byte,Integer,Long,Float,Double,Short 都继承Number
Boolean,Character继承objhect
32.1 包装类和基本数据的转换
包装类和基本数据类型可以互相转换。
jdk5 前的手动装箱和拆箱方式,装箱:基本类型 -> 包装类型
拆箱和装箱有两种方式。
int i = 10; //手动装箱 Integer integer = new Integer(i); Integer integer = Integer.valueOf(i); //手动拆箱 int i= integer.intValue();
jdk5 以后(含jdk5)的自动拆箱和装箱方式。
自动装箱使用的ValueOf()
int i = 10; //自动装箱 Integer integer = i; //底层实际上使用Integer.valueOf(i); //自动拆箱 int i = integer;
自动装箱底层调用的是valueOf方法。
Integer.valueOf()中接收的值如果在-128到127范围内,不会new Integer;在创建时就会创建一个IntegerCache.cache数组,数组范围在-128到127,只要在此范围内不需要重新new。
含有缓冲数组的包装类:Integer,Char,Short
包装类和基本类作比较,只要存在基本数据类型,“ == ”号比较的就是值是否相等。
public static Integer valueOf(int i) { //IntegerCache.low 为-128 IntegerCache.high为 127 if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
三元运算符补充:
三元运算符为一个整体,会提升精度
Object obj = true?new Integer(1):new Double(2.0);
System.out.println(obj);//输出为1.0
32.2 包装类和String数据类型的相互转换。
包装类和String类型可以相互转换。
//包装类 ----> String Integer i = 10; //方法一 String str = i.toString(); //方式二 String str1 = String.valueOf(i); //方式三 String str2 = i + "";
//String ----> 包装类 String str = "1"; //方式一 Integer j = new Integer(s1); //方式二 Integer j = Integer.valueOf(s2);
33. String类
- String 对象用于保存字符串,也就是一组字符序列。
- 字符串常量对象用双括号括起来的字符序列。
String str = "doubao";字符串的字符使用Unicode字符编码,一个字符(不区分字母和汉字)占用两个字节。(char占用两个字节)
- String类是被final修饰的,不允许被继承。
- String类有许多构造方法,常用的构造方法:
String str = new String(); String str = new String(String original); String str = new String(char[] a); String str = new String(char[] a,int statIndex,int count);
- String类实现了接口 Serializable说明String可以串行化。(可以在网络传输)
- String类实现了接口Comparable说明String类可以比较。
- String类中有属性private final char value[]用于存放字符串内容。value是final类型的,不能修改。(value[]的地址不能修改)但是单个字符的内容可以修改。
33.1 String创建
String 创建对象的两种方式。
方式一: 直接赋值 String str = “douabo”;
- 先从常量池查看是否有“doubao”的数据空间,如果有,直接指向,如果没有则重新创建,然后指向,str最终指向的是常量池的空间地址。
方式二:调用构造器 String s = new String(“doubao”);
- 现在堆中创建空间,里面维护了value属性,指向常量池的“doubao”。如果常量池中没有“douabo”,重新创建,如果有,直接通过value指向,最终指向的是堆中的空间地址。
33.2 String类的特性:
编译器会进行优化,不会浪费资源。
String str = "dou" + "bao";//两个常量相加看池中, String str1 = str2 + str3; //两个变量相加看堆
33.3 String类的常用方法
String 类是保存字符串常量的,每次更新都需要开辟空间,效率较低,因此java设计者提供了StringBuilder和StringBuffer来增强String的功能,并提高效率。
方法 | 作用 | 备注 |
---|---|---|
equals | 判断内容是否相等 | |
equalslgnoreCase | 忽略大小写判断内容是否相等 | |
length | 获得字符的个数,字符串的长度 | |
indexOf | 获取字符在字符串中第一次出现的索引,索引从0开始 | 未找到返回-1 |
lastIndexOf | 获取字符在字符串中最后一次出现的索引,索引从0开始 | 未找到返回-1 |
substring | 截取指定范围的字串 | 包括开始,不包括结束 |
trim | 去前后空格 | |
charAt | 获取某索引处的字符 | 不能使用Str[index] |
toUpperCase | 将字符串转换为大写 | |
toLowerCase | 将字符串转换为小写 | |
concat | 拼接字符串 | |
compareTo | 比较两个字符串的大小 | |
toCharArray | 转换成字符数组 | |
format | 使用占位符格式化字符串 | 有点像c语言 |
replace | 替换字符串中的字符 | 对原本字符串无影响,返回替换后的字符串 |
split | 分割字符串 | 传入分割依据为分隔参数 |
34. StringBuffer
- java .long.StringBuffer代表可变字符序列。可以对字符串的内容进行增删。
- StringBuffer是线程安全的。
- 很多方法与String相同,但是StringBuffer是可变长度的。
- StringBuffer是一个容器。
- StringBuffer实现了Serializable,即StringBuffer的对象是可以串行化的。
- StringBuffer继承了父类AbstractStringBuilder,在父类中,有属性char[] value,不是final。
- 该value数组存放字符串内容,存放在堆中。(String存放在常量池)
- StringBuffer是一个final类。不能被继承。
System.out.priintln(strBuffer)
实际上就是调用StringBuilder的toString()方法。
String类与StringBuffer类区别
String 保存的是字符串常量,里面的值不能修改,每次String类的更新实际上就是更改地址,效率较低。
private final char value[];
StringBuffer 存的是字符串变量,里面的值可以更改,每次StringBuffer 更新实际上可以更新内容,不用每次更新地址,效率比较高。
char[] value;
(空间大小不够时也会更新地址)
StringBuffer构造器
StringBuffer()构造器
- StringBuffer() 构造的字符串缓冲区,其初始容量为16个字符。
- StringBuffer(CharSequence seq) 构造一个字符串缓冲区,他包含指定的CharSequence相同的字符。
- StringBuffer(int 容量) 构造一个不带字符,但是具有指定初始容量的字符串缓冲区,即对char[]大小进行指定。
- StringBuffer(String str) 构造一个字符串缓冲区。将其内容初始化为指定的字符串内容。 //其中value数组的长度为str.length + 16
String 与StringBuffer互相进行转换
String转换为stringBuffer
- 调用new StringBuffer(str)构造器后如果传入字符为null则会报错。抛出空指针异常。
//String----> StringBuffer String str = "douabo"; //方式一 StringBuffer strb = new StringBuffer(str); //返回的才是StringBuffer类型的对象与原来字符串无影响 //方式二 StringBuffer strb2 = new StringBuffer(); strb2.append(str); //返回的才是StringBuffer类型的对象与原来字符串无影响 //StringBuffer---->String StringBuffer strb = new StringBuffer("douabo"); //方式一 String str = strb.toString(); //方式二 String str2 = new String(strb);
StringBuffer常用方法
增加: append()
删除:delete(start,end)
改:replace(start,end,string)
查找 : indexOf(str)
插入: insert()
获取长度: length
append() 增加
使用append()方法向指定的StringBuffer对象最后添加指定字符串。
- 当append(str)添加的字符串为null时,不会报错,而是返回一个字符串长度为四的"null"。(调用构造方法时输入null会报错)
StringBuffer strb = new StringBuffer("douabo"); System.out.println(strb); strb.append("xin"); System.out.println(strb);
delete() 删除
使用delete(start,end)删除指定开始到指定末尾之间的元素。包括开始元素不包括结束元素。
StringBuffer strb = new StringBuffer("douabo"); strb.delete(0,3); //包含开始元素不包含结束元素 System.out.println(strb);
replace() 替换
使用replace(start,end,replace) 将指定开始和指定结束之间的字符使用指定的字符进行替换。包含开始元素不包含结束元素。
StringBuffer strb = new StringBuffer("douabo"); strb.replace(0,3,"豆包"); System.out.println(strb);
indexOf(str)
查找字符串中指定字符串第一次出现的索引。如果没有查找到返回-1.
StringBuffer strb = new StringBuffer("douabo");
System.out.println(strb.indexOf("a")); //3
insert(start,string) 插入
在指定索引处插入指定字符串。原本指定索引位置的字符串后移。
StringBuffer strb = new StringBuffer("douabo"); strb.insert(3,"豆包"); System.out.println(strb);//dou豆包abo
35. StringBuilder
一个可变字符序列,为此类提供一个与StringBuffer兼容的API,但不保证同步,该类被设计用作StringBuffer的一个建议替换,用在字符串缓冲区被单个线程使用的时候,如果可能,建议采用该类,因为在大多数实现中,StringBuilder要比StringBuffer要快。
但是StringBuilder不是线程安全。
StringBuilder是一个final类,是不可以被继承的。
在StringBuilder上的主要操作是append和insert方法,可重载这些方法,以接收任意类型的数据。
- StringBuilder实现了Serializable,即StringBuilder的对象是可以串行化的。(对象可以网络传输,可以保存到文件)
- StringBuilder继承了父类AbstractStringBuilder,在父类中,有属性char[] value,不是final。因此字符序列是存储在堆中的。
- StringBuilder的方法,没有做互斥的处理,即没有synchronized关键字,因此在单线程的情况下使用。
35.1 StringBuilder常用方法
StringBuilder和StringBuffer均代表可变的字符序列,方法是一样的,所以使用和StringBuffer一样。(StringBuilder和StringBuffer都继承了AbstractStringBuilder父类)
增加: append()
删除:delete(start,end)
改:replace(start,end,string)
查找 : indexOf(str)
插入: insert()
获取长度: length
append() 增加
使用append()方法向指定的StringBuffer对象最后添加指定字符串。
- 当append(str)添加的字符串为null时,不会报错,而是返回一个字符串长度为四的"null"。
StringBuffer strb = new StringBuffer("douabo"); System.out.println(strb); strb.append("xin"); System.out.println(strb);
delete() 删除
使用delete(start,end)删除指定开始到指定末尾之间的元素。包括开始元素不包括结束元素。
StringBuilder strb = new StringBuilder("douabo"); strb.delete(0,3); //包含开始元素不包含结束元素 System.out.println(strb);
replace() 替换
使用replace(start,end,replace) 将指定开始和指定结束之间的字符使用指定的字符进行替换。包含开始元素不包含结束元素。
StringBuilder strb = new StringBuilder("douabo"); strb.replace(0,3,"豆包"); System.out.println(strb);
indexOf(str)
查找字符串中指定字符串第一次出现的索引。如果没有查找到返回-1.
StringBuilder strb = new StringBuilder("douabo");
System.out.println(strb.indexOf("a")); //3
insert(start,string) 插入
在指定索引处插入指定字符串。原本指定索引位置的字符串后移。
StringBuilder strb = new StringBuilder("douabo"); strb.insert(3,"豆包"); System.out.println(strb);//dou豆包abo
35.2 String 与 StringBuffer 与 StringBuilder的比较
StringBuilder 和 StringBuffer非常类似,均代表可变的字符序列,而且方法也一样。
String: 不可变字符序列,效率低,但是复用率高。
StringBuffer: 可变字符序列,效率较高(增删),线程安全。
StringBuffer中的方法前加修饰符 synchronized(同步)
StringBuilder:可变字符序列,效率最高,线程不安全。
35.3 String 与 StringBuffer 与 StringBuilder的选择
如果字符串存在大量的修改操作,一般使用StringBuffer 与 StringBuilder。
如果字符串存在大量的修改操作,并在单线程的情况下,使用StringBuilder
如果字符串存在大量的修改操作,并且在多线程的情况下,使用StringBuffer
如果字符串很少修改,并且被多个对象引用,比如配置信息,使用String
36. Math
Math类包含用于执行基本数学运算的方法,如初等指数,对数,平方根和三角函数。
常用方法:
Math中的类大多数都是静态方法
abs() 绝对值
pow() 求幂
pow(2,4); 求2的4次方
ceil() 向上取整
floor() 向下取整
round() 四舍五入
sqrt() 求开方
random() 求随机数,返回的是0–1之间的随机小数(能取到0不饿能取到1)
//求a--b之间的整数倍 (int)(a + Math.random() * (b-a +1))
max() 求两个数的最大值
min() 求两个数的最小值
37. Arrays
Arrays里面包含了一系列静态方法,用于管理或者操作数组。(比如排序和搜索)
37.1 Arrays常用方法
toString()
Arrays.toString(arr)
返回数组的字符串形式
Integer[] integers = {1,2,3,4,5,7,6};
System.out.println(Arrays.toString(integers));
sort排序(自然排序和定制排序)
Arrays.sort(arr)可以对传入的数组进行排序。
数组为引用类型,所以通过sort排序后,会直接影响到实参。
Integer[] integers = {1,2,3,4,5,7,6};
Arrays.sort(integers);
System.out.println(Arrays.toString(integers));
sort是重载的,可以通过传入一个接口Comparator实现定制排序。
调用定制排序时,传入两个参数
- 需要排序的数组
- 实现了Comparator接口的匿名内部类,要求实现compare方法
- 实现接口中compare()方法的返回值大于或者小于0会影响排序的内部逻辑。本质就是冒泡排序中控制顺序的语句由compare方法返回值控制。
Integer[] integers = {1,2,3,4,5,7,6}; Arrays.sort(integers, new Comparator() { @Override public int compare(Object o1, Object o2) { Integer i1 = (Integer)o1; Integer i2 = (Integer)o2; return i2-i1; } }); System.out.println(Arrays.toString(integers));
binarySeach 二分法查找
通过二分搜索法进行查找,要求必须已经有顺序。
Arrays.binarySearch(数组,元素);
如果数组中不存在返回-(low + 1)。(应该存在的位置+1取负数)
public static void main(String[] args) { Integer[] integers = {1,2,3,4,5,7,6}; Arrays.sort(integers); Arrays.binarySearch(integers,1); System.out.println(Arrays.binarySearch(integers,1)); }
copyOf 数组复制
Arrays.copyOf(数组,元素个数);
从指定数组中复制指定个数个元素组成新的数组返回.
如果需要拷贝的个数大于原本的个数,则在多余的部分赋值为null。
如果需要拷贝的个数为负数,则抛出异常。
Integer[] arr = {1,2,3,4,5,7,6}; Integer[] newarr = Arrays.copyOf(arr,arr.length);
fill 数组填充
Arrays.fill(数组,元素);
向指定数组中填充数组,可以理解成替换原本的元素。
Integer[] integers = new Integer[]{1,2,3,4,5,7,6}; Arrays.fill(integers,55); System.out.println(Arrays.toString(integers));
equals 比较
比较两个数组元素内容是否完全一致
Arrays.equals(数组,数组)
如果一致返回true如果不一致返回false。
Integer[] integers = new Integer[]{1,2,3,4,5,7,6}; Integer[] integers1 = new Integer[]{1,2,3,4,5,7,6}; boolean istrue = Arrays.equals(integers,integers1); System.out.println(istrue);
asList 转换列表
将一组值转换为list
Arrays.asList(1,2,3,4,5)
asList的运行类型为 java.util.Arrays#ArrayList,是内部类。
List<Integer> asList = Arrays.asList(1,2,3,4,5); System.out.println(asList);
38. System
38.1 System常见方法
exit()
退出当前程序
System.exit(退出状态);
0:表示正常退出。
Sysyem.exit(0);
arraycopy()
复制数组元素,比较适合底层调用,一般使用Arrays.copyOf完成复制数组。(Arrays.copyOf()底层使用arraycopy()方法完成数组的复制)
System.arraycopy(原数组,元素值索引,被拷贝数据,被拷贝数组开始索引,拷贝数量)
如果越界会被抛出异常。
System.arraycopy(src,0,dest,0,3)
currentTimeMillens
返回当前时间距离1970-1-1的毫秒数。
System.currentTimeMillens()
long time = System.currentTimeMillis(); System.out.println(time);
gc
运行垃圾回收机制。
并不是百分之百运行垃圾回收机制。
System.gc()
39. BigInteger与BigDecimal
BigInteger适合保存比较大的整型。
BigDecimal适合保存精度更高的浮点数(小数)。
39.1 BigInteger
在编程中可能需要处理很长的整数,long把不够用。
BigInteger bigInteger = new BigInteger(数字);
BigInteger bigInteger = new BigInteger(“数字”);
可以向BigInteger中传入字符串,底层会将字符串转换为数据类型。
BigInteger bigInteger = new BigInteger(123456789123456789); BigInteger bigInteger = new BigInteger(“123456789123456789”);
39.2 BigInteger方法
在BigInteger中进行加减乘除时,不能直接进行,需要使用对应的方法。
add() 加法
BigInteger.add(数字);
可以与BigInteger类型相加也可以和long类型相加。
BigInteger bg = new BigInteger("12435680987"); BigInteger bg1 = new BigInteger("12435680987"); bg = bg.add(bg1); System.out.println(bg);
subtract() 减法
BigInteger.subtract(数字);
可以与BigInteger类型相减也可以和long类型相减。
BigInteger bg = new BigInteger("12435680987"); BigInteger bg1 = new BigInteger("12435680987"); bg = bg.subtract(bg1); System.out.println(bg);
multiply() 乘法
BigInteger.add(数字);
可以与BigInteger类型相乘也可以和long类型相乘。
BigInteger bg = new BigInteger("12435680987"); BigInteger bg1 = new BigInteger("12"); bg = bg.multiply(bg1); System.out.println(bg);
divide() 除法
BigInteger.divide(数字);
可以与BigInteger类型相加也可以和long类型相加。
BigInteger bg = new BigInteger("12435680987"); BigInteger bg1 = new BigInteger("10"); bg = bg.divide(bg1); System.out.println(bg);
39.3 BigDicimal
当需要存储精度特别大的小数时可以使用BigDecimal来存储。
BigDicimal bigdicimal = new BigDicimal(数字);
BigDicimal bigdicimal = new BigDicimal(“数字”);
可以向BigDicimal 中传入字符串,底层会将字符串转换为数据类型。
BigDecimal bigDecimal = new BigDecimal("1.23242354235454");
39.3 BigDicimal方法
不能直接对BigDicimal进行加减运算,只能调用相应的方法。
add() 加法
BigDicimal.add(数字);
只能与BigDicimal类型相加。
BigDicimal bg = new BigDicimal("1.2435680987"); BigDicimal bg1 = new BigDicimal("1.2435680987"); bg = bg.add(bg1); System.out.println(bg);
subtract() 减法
BigDicimal.subtract(数字);
只能与BigDicimal类型相减。
BigDicimal bg = new BigDicimal("1.2435680987"); BigDicimal bg1 = new BigDicimal("1.2435680987"); bg = bg.subtract(bg1); System.out.println(bg);
multiply() 乘法
BigDicimal.add(数字);
只能与BigDicimal类型相乘
BigDicimal bg = new BigDicimal("1.2435680987"); BigDicimal bg1 = new BigDicimal("1.2"); bg = bg.multiply() (bg1); System.out.println(bg);
divide() 除法
BigDicimal.divide(数字);
只能与BigDicimal类型相加也可以和long类型相加。
可能会抛出异常ArithmeticException,精度太高可能会一直除下去无限循环。
可以在调用BigDicimal.divide()方法时添加精度。如果有无限循环小数,就会保留分子的精度。
BigDicimal.divide(数字,BigDecimal.ROUND_CEILING);
BigDicimal bg = new BigDicimal("1.2435680987"); BigDicimalr bg1 = new BigDicimal("10"); bg = bg.divide(bg1); System.out.println(bg);
40. 日期类
40.1 Date
Date:精确到毫秒,代表特定的瞬间。
SimpleDateFormat: 格式和解析日期的类。允许将进行格式化。
(日期 —> 文本)(文本 —> 日期)和规范化。
构造器
Date类中有三个构造器
无参构造器:
- 返回的格式时国外的格式,通常需要对格式进行转换。
- 使用SimpleDateFormat类中的format()方法进行格式转换。
- 获取当前的系统时间。
- 在使用SimpleDateFormat类中的format()方法进行格式转换时,需要获取字符串的格式和传入SimpleDateFormat类构造器的格式一致,否则会抛出异常:ParseException.
//获取当前系统时间 Date date = new Date(); System.out.println(date); //格式转换 SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E"); String time = sdf.format(date); System.out.println(time);
毫秒数构造器
//通过毫秒数得到时间 Date date = new Date(毫秒数);
字符串构造器
- 可以将一个格式化的String转换成对应的Date
- 得到的Date仍然在输出时还是国外的形式。
String time = "Wed Mar 02 19:43:55 CST 2022"; Date date = new Date(time); //格式转换 SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E"); String times = sdf.format(date); System.out.println(times);
40.2 Calendar类
Calendar类是一个抽象类,它为特定瞬间与一组诸如YEAR,MONTH,
DAR_OF_MONTH,HOUR等日历字段之间的转换提供了一些方法,并为操作日历字段提供了一些方法。
Calendar的构造器是private,可以通过getInstance()获取。
Calendar提供了大量方法和字段提供调用。
返回月份时按照0开始编号。
Calendar cale = Calendar.getInstance(); //获取日历对象的某个日历字段 System.out.println("年:" + cale.get(Calendar.YEAR)); //返回月份时,是 按照0编号的,需要返回值加一 System.out.println("月:" + cale.get(Calendar.MONTH)); System.out.println("日:" + cale.get(Calendar.DAY_OF_MONTH)); //返回以12进制小时数 System.out.println("小时:" + cale.get(Calendar.HOUR)); System.out.println("分钟:" + cale.get(Calendar.MINUTE)); System.out.println("秒:" + cale.get(Calendar.SECOND)); //返回以24进制小时数 System.out.println("小时:" + cale.get(Calendar.HOUR_OF_DAY));
缺点:
- 可变性:像日期和时间这样的类应该是不可变的。
- 偏移性:date中的年份从1900年开始,月份都是从零开始。
- 格式化: 格式化只对Date有用,Calendar则不行。
- Date和Calender都不是线程安全的,不能处理闰秒。(每隔两天,多出1s)
40.3 LocalDate,LocalTime,LocalDateTime
LocalDate返回日期,可以获取日期字段。
LocalTime返回时间,可以获取时间字段。
LocalDateTime返回日期时间,可以获取日期和时间字段。
这些时间类是在JDK8中加入的。
- 使用plus方法测试增加时间的某个日期。
LocalDateTime ldt = LocalDateTime.now(); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分钟ss秒"); //获取指定天数后的对象 LocalDateTime ldt2 = ldt.plusDays(875); //转换格式 String format = dtf.format(ldt2); System.out.println(format);
- 使用minus方法测试查看减少时间的某个日期
LocalDateTime ldt = LocalDateTime.now(); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分钟ss秒"); //获取指定天数前的对象 LocalDateTime ldt2 = ldt.minusDays(875); //转换格式 String format = dtf.format(ldt2); System.out.println(format);
//获取日期和时间字段。 LocalDateTime ldt = LocalDateTime.now(); //获取日期字段。 LocalDate ld = LocalDate.now(); //获取时间字段。 LocalTime lt = LocalTime.now(); System.out.println("年:" + ldt.getYear()); System.out.println("月(英文):" + ldt.getMonth()); System.out.println("月(数字):" + ldt.getMonthValue()); System.out.println("日:" + ldt.getDayOfMonth()); System.out.println("时:" + ldt.getHour()); System.out.println("分:" + ldt.getMinute()); System.out.println("秒:" + ldt.getSecond());
可以使用DateTimeFormatter对象的ofPattern方法来进行格式化。类似于SimpleDateFormat。
LocalDateTime ldt = LocalDateTime.now(); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分钟ss秒"); String format = dtf.format(ldt); System.out.println(format);
时间戳
类似于Date,提供了一系列和Date类转换的方式。
Instant ----> Date
Instant instant = Instant.new(); Date date = Date.from(instant);
Date —> Instant
Date date = new Date(); Instant instant = date.toInstant();
41. 集合
保存数据可以使用数组也可以使用集合。
数组:
- 长度开始时必须指定,而且一旦指定,不能更改。
- 保存的必须为同一类型的元素。
- 使用数组进行增加元素比较麻烦。(创建新的数组,然后将原本数组复制到新的数组)
集合:
- 可以动态保存任意多个对象,使用比较方便。
- 提供了一系列方便的操作对象方法:add,remove,set,get。
- 集合主要是两组(单列集合,双列集合)
- Collection接口有两个重要的子接口List,Set,他们的实现子类都是单列集合。
- Map接口的实现子类是双列集合,存放的键值对形式。
41.1 集合继承图
集合主要是两组(单列集合,双列集合)
Collection接口有两个重要的子接口List,Set,他们的实现子类都是单列集合。
Map接口的实现子类是双列集合,存放的键值对形式。
41.2 Collection接口
Collection接口的实现子类可以存放多个元素,每个元素可以是Object。
有些Collection的实现类,可以存放重复的元素,有些不可以。
有些Collection的实现类,是有序的(list),有些不是有序的(set)。
Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现。
接口中的方法:
方法 | 解释 | |
---|---|---|
add() | 添加单个元素(其中元素为对象) | 放置基本数据类型自动装箱 |
remove(索引\值) | 删除指定元素 | |
contains(值) | 查找元素是否存在 | |
size() | 获取元素个数 | |
isEmpty() | 判断是否为空 | |
clear() | 清空 | |
addAll(集合) | 添加多个元素 | 可以添加实现了Collection接口的集合 |
containsAll(集合) | 查找多个元素是否都存在 | 可以添加实现了Collection接口的集合 |
removeAll(集合) | 删除多个元素 | 可以添加实现了Collection接口的集合 |
Collection遍历元素方式
Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
==所有实现了Collection接口的集合都有一个iterator()方法,==可以返回一个实现了Iterator接口的对象,即可以返回一个迭代器。
Iterator仅仅用于遍历集合,Iterator本身并不存放对象。
Iterator细节:
- 使用迭代器得到下一个元素之前必须使用迭代器的hasNext方法判断是否有下一个元素。
- 如果没有使用hasNext方法判断,调用Next方法且已经没有下一个元素会抛出异常:NoSuchElementException。
- 可以使用iterator.Next()将指向下移并且得到当前指向的元素。
- 当退出while循环后,这是iterator迭代器还是指向最后的元素,不能再次调用Next,否则会指向超出集合长度,抛出异常:NoSuchElementException。
- 如果希望再次遍历,可以重新得到迭代器,不能使用原有迭代器:
ArrayList arrayList = new ArrayList(); //第一次构造迭代器 Iterator iterator = arrayList.iterator(); //重新构造迭代器 iterator = arrayList.iterator();
ArrayList arrayList = new ArrayList(); arrayList.add("元素"); //其中有自动装箱 //得到迭代器 Iterator iterator = arrayList.iterator(); //使用迭代器得到下一个元素之前必须使用迭代器的hasNext方法判断是否有下一个元素 //如果没有使用hasNext方法判断,调用Next方法且已经没有下一个元素会抛出异常:NoSuchElementException // while (iterator.hasNext()) { // Object next = iterator.next(); }
增强for
增强for循环,可以替代iterator迭代器,增强for就是简化版的iterator,本质一样们只能用于遍历集合或者数组。
增强for的底层也是使用了迭代器。
语法:for(元素类型 元素名 : 集合名或数组 ){ 访问元素 }
ArrayList arrayList = new ArrayList(); arrayList.add("元素"); //其中有自动装箱 for(Object ob : arrayList){ System.out.println(ob); }
普通for
也可以使用普通for循环进行遍历集合
普通for循环只能用作有顺序集合的遍历
ArrayList arrayList = new ArrayList(); arrayList.add("元素"); //其中有自动装箱 for(int i = 0;i < arrayList.size();i++){ System.out.println(arrayList.get(i)); }
41.3 List 接口
List接口是Collection接口的子接口。
- List集合中的元素有序(即添加顺序和取出顺序一致),且可重复。
- List集合中的每个元素都有其对应的顺序索引,即支持索引。
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
List常用方法:
add() 添加
add(索引,元素);
向指定索引处添加指定元素,如果不指定索引默认添加到最后。
List arrayList = new ArrayList(); arrayList.add("元素"); arrayList.add(0,"索引0元素");
addAll() 添加
add(索引,元素);
向指定索引处添加指定所有元素(集合),如果不指定索引默认添加到最后。
List arrayList = new ArrayList(); List arrayList1 = new ArrayList(); arrayList.addAll(arrayList1); arrayList.addAll(0,arrayList1);
get() 获得
get(索引);
返回指定索引位置的元素。
如果超出索引长度会抛出异常。
List arrayList = new ArrayList(); arrayList.get(0);
indexOf() 查找
indexOf(元素);
返回指定元素在当前集合中首次出现的位置索引。
List arrayList = new ArrayList(); arrayList.add("元素"); arrayList.indexOf("元素");
laseindexOf() 添加
laseindexOf()(元素);
返回指定元素在当前集合中最后出现的位置索引。
List arrayList = new ArrayList(); arrayList.add("元素"); arrayList.add("元素"); arrayList.laseindexOf("元素");
remove() 删除
remove(索引);
移除指定index位置的元素,并且返回此元素。
List arrayList = new ArrayList(); arrayList.add("元素"); String str = arrayList.remove(0);
set() 设置(替换)
set(索引,元素);
设置指定索引位置为指定元素,相当与替换。
List arrayList = new ArrayList(); arrayList.add("元素"); arrayList.set(0,"元素");
subList()
subList(开始索引,结束索引);
截取指定范围的集合元素返回一个新的集合,包含开始索引,不包含结束索引。
List arrayList = new ArrayList(); arrayList.add("元素0"); arrayList.add("元素1"); arrayList.add("元素2"); //获取第零个元素和第一个元素 List arrayList2 = arrayList.subList(0,2);
41.4 ArrayList
ArrayList可以加入Null,并且多个。
ArrayList由数组实现数据的存储。
ArrayList基本等同于Vector,除了ArrayList是线程不安全的(执行效率高)在多线程情况下,不建议使用ArrayList。多线程推荐使用Vector。(ArrayList没有使用synchronized修饰)
分析:
- ArrayList中维护了一个Object类型的数组elementData。
transient Object[] elementData; //transient表示短暂的,瞬间,保证该属性不会被序列化。
- 当创建对象时,如果使用的是无参构造器,则初始elementData的容量为0.(jdk为10)
- 当添加元素时,先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到合适的位置。
- 如果使用的是无参构造器,如果是第一次添加,需要扩容的话,扩容后的elementData为10,如果需要再次扩容的话,则扩容elementData为1.5倍。(第一次为10,第二次为15)
- 如果使用的是指定容量capacity的构造器,则初始elementData容量为caoacity。
- 如果使用的是只当容量capacity的构造器,如果需要扩容,则直接扩容elementData为1.5倍。
41.5 Vector
Vector底层也是一个对象数组,Protected Object[] elementData;
Vector的对象是有固定顺序且可以重复的。
Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized。
public synchronized E get(int index) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); return elementData(index); }
在开发中,需要线程同步安全时,考虑使用Vector。
底层结构 | 版本 | 同步,效率 | 扩容倍数 | |
---|---|---|---|---|
ArrayList | 可变数组 | jdk1.2 | 不安全,效率高 | 如果有参数构造为传入参数大小,扩容1.5倍;无参数默认是0,第一次10,第二次1.5倍 |
Vector | 可变数组 | jdk1.0 | 安全,效率低 | 如果是无参,默认10,满后按照2倍扩容,如果指定大小,则每次直接按照2倍扩容 |
41.6 LinkedList
LinkedList实现了双向链表和双端队列特点。
可以添加任意元素(元素可以重复),包括null。
线程不安全,没有实现同步。
- LinkedList底层维护了一个双向链表。
- LinkedList中维护了两个属性first和last分别指向首节点和尾节点。
- 每个节点(Node对象),里面又维护了prev,next,item三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双向链表。
- 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
LinkedListCRUD
add()
add(元素);
向指定LinkedList对象中添加元素。
LinkedList lkl = new LinkedList();
for (int i = 0; i < 10; i++) {
lkl.add(i);
}
remove()删除
remove(); remove(节点位置);
remove默认删除的是第一个节点,可以指定删除哪一个节点。
LinkedList lkl = new LinkedList(); for (int i = 0; i < 10; i++) { lkl.add(i); } lkl.remove(); lkl.remove(1); System.out.println(lkl);
set() 设置
set(节点位置,元素); (节点索引也是从零开始)
将指定节点位置的元素更换为指定元素。
LinkedList lkl = new LinkedList(); for (int i = 0; i < 10; i++) { lkl.add(i); } lkl.set(1,9999); System.out.println(lkl);
get() 获取
get(节点位置);
获取指定节点的位置。
LinkedList lkl = new LinkedList(); for (int i = 0; i < 10; i++) { lkl.add(i); } Object obj = lkl.get(1); System.out.println(obj);
底层结构 | 增删的效率 | 改查的效率 | |
---|---|---|---|
ArrayList | 可变数组 | 较低,数组扩容 | 较高 |
LinkedList | 双向链表 | 较高,通过链表追加 | 较低 |
- 如果改查的操作比较多,选择ArrayList。
- 如果我们该删的操作多,选择LinkedList。
- 一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList。
- 允许在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另一个模块是LinkedList。
41.7 Set接口
无序(添加和取出来的顺序不一致,但是取出来的顺序是固定的),所以没有索引。
如果添加的元素有重复只会存储一个。
不允许重复元素,所以最多包含一个null。
JDK API中set的实现类为:HashSet ,TreeSet。
Set和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection的接口一样。
set接口的遍历方式
同Collection的遍历遍历一样,因为Set接口是Collection接口的子接口:
- 可以使用迭代器
HashSet hashSet = new HashSet(); for (int i = 0; i < 10;i++) { hashSet.add(i); } Iterator iterator = hashSet.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); }
- 增强for
- 不能使用索引的方式来获取元素,因为set接口不支持索引,没有顺序。
41.8 HashSet
HashSet实现了Set接口。
HashSet实际上是HashMap。(只是将HashMap的value处放固定值)
HashSet不允许重复元素。可以存放null值,但是只能有一个null。
HashSet不保证元素是有序的,取决与hash后,再确定索引的结果。
不能有重复元素/对象,再前面Set接口使用已经讲过。
不能加入相同对象的真正含义:
- HashSet底层是HashMap,维护了一个table和双链表。
- 添加一个元素时,先得到hash值,会转成索引值。
- 找到保存数据表table,看这个索引位置是否已经存放有元素。
- 如果没有,直接加入,如果有调用equals比较,如果相同,就放弃添加,如果不同,则添加到最后。
- 再java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8)并且table的大小>=MIN_CAPACITY(默认是64),就会进行树化(红黑树)
- 可以重写hashCode方法和equlas方法来控制不能重复的范围。
相同元素的定义:
- 准备加入的和已经加入的节点是同一个对象。
- 准备加入的节点和已经加入的执行equals()方法。
- 重点是指定对象equals方法被重写后的机制
HashSet hashSet = new HashSet(); hashSet.add("字符串"); hashSet.add("字符串"); //同一个对象添加失败 hashSet.add(new Dog("doubao")); hashSet.add(new Dog("doubao")); //非同一个对象添加成功 hashSet.add(new String("字符串")); hashSet.add(new String("字符串"));//
HashSet的底层机制:
- HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是0.75 = 12.(并不是到达极限再扩容)
- 只要数组和链表中加入一个元素就算做加一,加到临界值会触发扩容。
- 如果table数组使用到了临界值12,就会扩容到16*2 = 32 ,新的临界值就是32 * 0.75 = 24,以此类推。
- 再java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8)并且table的大小>=MIN_CAPACITY(默认是64),就会进行树化(红黑树),否则仍然采用数组的扩容机制。
- 如果单条链的长度到达8且数组长度未到达64,则会将数组按照规则扩容,每次这条链多追加元素一便扩容一次。
add()
添加指定对象,返回一个boolean的值。
如果添加成功返回true,如果添加失败返回false。
remove()
remove(对象);
删除指定对象。
41.9 LinkedHashSet
LinkedHashSet 时HashSet的子类。实现了有顺序的存储。
LinkedHashSet底层是一个LinkedHashMap,是HashMap的子类,底层维护了一个数组+双向链表。
LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时用链表维护元素的次序,这使得元素看起来是以处插入顺序保存的。
LinkedHashSet不允许出现重复元素。
在LinkedHashSet中维护了一个hash表和双向链表(LinkedHashSet有head和tail)
每一个节点有before和after属性,这样可以形成双向链表。
在添加一个元素时。先求hash值,再求索引,确定该元素再hashtable的位置,然后将添加的元素加入到双向链表(如果已经存在,不添加)
这样的话,遍历时可以确保插入顺序和遍历顺序一致。
添加第一次时,直接将数组table扩容到16,存放的节点类型是LinkedHashMap$Entry。
底层维护着一个HashMap$Node[],存放的元素是LinkedHashMap$Entry类型。(多态数组)
41.10 TreeSet
可以使用TreeSet集合将添加元素按照指定顺序排列;
- 如果使用无参构造器创建TreeSet时,仍然时无顺序的。但是也会去除重复,使用添加元素实现的Compareable接口的compareTo去重。
- 如果使用无参构造器且传入的实现类并没有实现对应compareTo方法,则会在底层报错:ClassCastExcepotion。
- 使用提供的传入一个比较器(匿名内部类)时可以按照指定顺序排序。
TreeSet set = new TreeSet(new Comparator() { @Override public int compare(Object o1, Object o2) { //根据字符串大小排序 return ((String)o1).compareTo(((String)o2)); //根据字符串长度排序 //return ((String)o1).length - ((String)o2).length; } });
TreeSet的底层就是TreeMap。
构造器会将出传入的比较器传入到TreeMap对象。当调用add()方法时,会执行传入的比较器,然后根据返回值存放元素。
parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value);
41.11 Map接口
Map与Collection并列存在(其中并没有关系),用于保存具有映射关系的数据:Key-Value。
Map对象底层也维护着table和一个双链表Node。
Map中的key和value可以时任何引用类型的数据,会封装到HashMap$Node对象中。
Map中的key不允许重复。(原因和HashSet一样,HashSet也是key-value结构但是value是固定值)
Map中的value可以重复,可以为空和重复。
Map中的Key可以为null,value也可以为null,注意key为null,只能有一个。value为null,可以多个。如果有多个相同的key会采取替换机制。
常用String类作为Map的key,可以使用object类型作为Key。
key和value之间存在单项一对一关系,通过指定的key总能找到对应的value。但是value无法找到key。
Map存放数据的key-value,一对key-value是放在一个HashMap$Node中的,是因为Node实现了Entry接口。
key-value最后是HashMap$Node node = newNode(hash,key,value,null);
key-value为了方便遍历,还会创建EntrySet集合,该集合存放的元素的类型Entry,而一个Entry对象就有key,value,EntrySet<Entry<K,V>> entrySet;
EntrySet 中,定义的类型是Map.Entry,但是实际上存放的还是HashMap$Node,这是因为
static class Node<K,Y> implements Map.Entry<K,V>
利用接口的多态。当把HashMap$Node对象存放到EntrySet就方便遍历,因为Entry接口提供了重要方法:
getKey
和getValue
Entry中存储着指定数据在HashMap$Node中的地址,实际上数据还是在HashMap$Node中存储。
使用EntrySet结构为了访问方便。
put() 添加
put(“键”,“值”);
使用put()方法可以添加键值对到指定的Map对象。
Map map = new HashMap(); map.put("key","value");
get() 获得
get(“key”);
通过get()方法可以返回指定key的value值。
Map map = new HashMap(); map.put("key","value"); map.get("key");
remove()
remove(“key”);
根据键值对删除映射关系。
Map map = new HashMap(); map.put("key","value"); map.remove("key");
size()
size()
获取元素个数
Map map = new HashMap(); map.put("key","value"); int size = map.size();
isEmpty()
isEmpty();
判断个数是否为0
Map map = new HashMap();
map.put("key","value");
boolean i = map.isEmpty();
clear()
clear();
清除所有元素
Map map = new HashMap();
map.put("key","value");
map.clear();
containsKey()
containsKey(“key”);
查找键是否存在。
Map map = new HashMap();
map.put("key","value");
boolean i = map.containsKey("key");
map遍历方式
Map接口存放着key和value,分别放在keySet和values中。
一.增强for循环
通过增强for获得keySet中的key,可以调用get方法得到对应的值。
Map map = new HashMap();
map.put("key","value");
map.put("key1","value1");
map.put("key2","value2");
map.put("key3","value3");
map.put("key4","value4");
//取出所有key再通过Key去除对应的Value
Set keyset = map.keySet();
for (Object key : keyset){
System.out.println(map.get(key));
}
二.迭代器
存放key的是set类型的keySet集合,可以使用迭代器得到key再调用get方法获得对应key的value。
Map map = new HashMap();
map.put("key","value");
map.put("key1","value1");
map.put("key2","value2");
map.put("key3","value3");
map.put("key4","value4");
//取出所有key再通过Key去除对应的Value
Set keyset = map.keySet();
//获得set集合的迭代器
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
//获得keySet的key,调用Map的get方法获得值
Object next = iterator.next();
System.out.println(map.get(next));
}
三. 获得Values
存储value的是collection类型的values集合。可以直接使用values方法获得value的集合。
获得collection类型的values集合后可以使用迭代器,增强for循环。
不能使用普通for循环,不提供get方法。
Map map = new HashMap();
map.put("key","value");
map.put("key1","value1");
map.put("key2","value2");
map.put("key3","value3");
map.put("key4","value4");
//调用values方法获得value的集合
Collection values = map.values();
//获取其中元素可以使用迭代器,增强for循环
四. EntrySet
可以使用map中维护的Entry集合的方法获取。
- 调用entrySet获得EntrySet对象,增强for后得到的是HashMap$Node类型的对象,但是实现了Map.Entry接口,也可以向上转型后使用getKey和getValue方法。
- 获得Entry集合后,因为Entry也是Set类型的也可以使用迭代器。使用迭代器返回的是HashMap$Node类型但是实现了Map.Entry接口,也可以向上转型后使用getKey和getValue方法。
Map map = new HashMap(); map.put("key","value"); map.put("key1","value1"); map.put("key2","value2"); map.put("key3","value3"); map.put("key4","value4"); //获得entryset Set entryset = map.entrySet(); for(Object entry : entryset){ //entry实现了Map.Entry接口 Map.Entry m = (Map.Entry) entry; System.out.println(m.getKey() + "-" + m.getValue()); }
Map map = new HashMap(); map.put("key","value"); map.put("key1","value1"); map.put("key2","value2"); map.put("key3","value3"); map.put("key4","value4"); //获得entryset Set entryset = map.entrySet(); Iterator iterator = entryset.iterator(); while (iterator.hasNext()) { Map.Entry m = (Map.Entry) iterator.next(); System.out.println(m.getKey() + "-" + m.getValue()); }
41.12 HashMap
Map接口的常用实现类:HashMap ,Hashtable和Properties。
HashMap是Map接口使用频率最高的实现类。
HashMap是以key-val对的方式存储的。
key不能重复,但是值可以重复,允许使用null键和null值。
如果添加相同的key,则会覆盖原来的key-val,等同于修改(key不会替换,val会替换)
与HashSet一样,不保证映射顺序,因为底层是以hash表的方式来存储的。
HashMap没有实现同步,因此是线程不安全的。方法上没有做同步互斥。(synchronized)
- (k,v)是一个Node实现了Map.Entry<K,V>。
- jdk7.0的hashmap底层实现[数组+链表],jdk8.0底层[数组+链表+红黑树]
扩容机制:
- HashMap底层维护了Node类型的数组table,默认为null。
- 当创建对象时,将加载因子(loadfactor)初始化为0.75。
- 当添加key-val时,通过key的哈希值得到在table上的索引,然后判断索引处是否有元素,如果没有元素直接添加,如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等直接替换val;如果不相等需要判断树结构还是链表结构,做出相应的处理,如果添加时发现容量不够用,则需要扩容。
- 第一次添加,则需要扩容table容量为16,临界值为(threshold)为12.
- 以后再次扩容,则需要扩容的容量为原来的2倍。临界值为原来的两倍,即24,以此类推。
- 在java8中如果一条链表的元素个数超过TREEIFY_THRESHOLD(默认为8),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进项树化(红黑树)。如果小于会先扩容再树化。
41.13 Hashtable
存放的元素时键值对:即k-y
hashtable的键和值不能为null。 否则还会抛出异常:NullPointerException
hashTable使用方法基本上和HashMap一样。
扩容机制:
(oldCaoacity <<1)+1
版本 | 线程安全 | 效率 | 允许null | |
---|---|---|---|---|
HashMap | 1.2 | 不安全 | 高 | 可以 |
Hashtable | 1.0 | 安全 | 较低 | 不可以 |
41.14 Properties
Properties类继承自Hashtable类并且实现Map接口,也是使用一种键值对的形式保存数据。
继承Hashtable所以key和value不能为Null。
存放是无顺序的。
Properties还可以用于从xxx.Properties文件中,加载数据到Prorerties类对象。
常用方法:
put() 添加修改
put(“key”,“vale”);
可以通过put方法向指定Properties对象中添加键值对,如果有相同的键则会替换对应的值。
Properties properties = new Properties(); properties.put("key","value");
remover()
remover(“key”);
可以通过remover方法向指定Properties对象中删除对应键值对。如果删除成功会返回对应的值,如果没有找到会返回null。
Properties properties = new Properties(); properties.put("key","value"); Object s = properties.remove("key"); System.out.println(s);
get() 查
get(“key”);
可以通过get方法向指定Properties对象中查找对应键值对。如果查找成功会返回对应的值,如果没有找到会返回null。
Properties properties = new Properties(); properties.put("key","value"); Object s = properties.get("key"); System.out.println(s);
41.15 TreeMap
TreeMap使用默认构造器是没有排序的存储。
调用TreeMap类传入比较器的构造器会按照指定比较器来对添加的元素的key进行排序。(找到适当的位置差插入)
TreeMap treeMap = new TreeMap(new Comparator() { @Override public int compare(Object o1, Object o2) { //根据字符串大小排序 return ((String)o1).compareTo(((String)o2)); //根据字符串长度排序 //return ((String)o1).length - ((String)o2).length; } });
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
- 第一次添加元素时,直接将k-y封装到Entry对象中,放入root;
- 第二次添加时候才使用比较器,将加入的key找到适当的位置存储。
41.16 集合选择
在开发中,选择集合实现类主要取决于业务操作特点,然后跟根据集合实现类特性进行选择:
先判断存储的类型(一组对象[单列]或一组键值对)
- 一组对象:Collection接口:
- 允许重复: list接口
- 增删多:LinkedList (底层维护了一个双向链表)
- 改查多:ArrayList (底层维护Object类型的可变数组)
- 线程安全: Vector
- 不允许重复:set接口:
- 无序: HashSet (底层是HashMap,维护了一个哈希表)
- 排序: TreeSet
- 插入和取出顺序一致:LinkedHashSet (维护数组+双向链表)
- 一组键值对: Map接口
- 键无顺序:HashMap (底层是哈希表:数组 + 链表 + 红黑树)
- 键排序:TreeMap
- 键插入和键取出LinkedHashMap
- 读取文件: Properties
- 线程安全: Hashtable
42. Collections工具类
Collections是一个操作Set,List和Map等集合的工具类
Collections中提供了一些列静态的和方法对集合元素进行排序,查找,修改等操作。
42.1 排序操作:
排序操作均为static方法。
- reverse(List): 反转List中元素的顺序。
- shuffle(List):对List集合元素进行随机排序。
- sort(List):根据元素的自然顺序对只当List集合按照升序排序。
- sort(List,Comparator);根据指定的Conmparator产生的顺序对List集合元素进行排序。
- swap(List,int,int); 将指定List集合中的i处元素和j处元素进行交换。
List li = new ArrayList(); li.add(1); li.add(3); li.add(2); System.out.println(li); Collections.reverse(li); System.out.println(li);
42.2 查找,替换
- Object max(Collection); 根据元素的自然顺序,返回给定结合中最大的元素。
- Object max(Collection,Comparator);根据Comparator指定的顺序,返回给定集合中的最大元素。
- Object min(Collection); 根据元素的自然顺序,返回给定结合中最小的元素。
- Object min(Collection,Comparator);根据Comparator指定的顺序,返回给定集合中的最小元素。
- int frequency(Collection,Object); 返回指定集合中指定元素的出现次数。
- void copy(List dest,List src):将src中的内容赋值到dest中。
- boolean replaceAll(List list,Object oldVal,Object newVal);使用新值替换List对象的所有旧值。
43. 泛型
泛型又称参数化类型,是jdk5.0中出现的新特性,解决数据类型的安全性问题。
在类声明或者实例化是只要指定需要的具体的类型即可。该类型会在定义对象时指定,在编译期间确定。
ArrayList<String> strings = new ArrayList<String>();
java泛型可以保证程序在编译时没有发生警告,运行时就不会产ClassCastException异常,同时代码更加简洁,健壮。
泛型的作用:可以在类声明时通过一个<>表示类中某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型。
class doubao<E>{ E id; //E 表示数据类型,该数据类型时在定义对象时指定,在编译期间会确定E的类型。 String name; public E getId(){ return id; } }
泛型 在编译时,检查添加元素的类型,提高了安全性。减少了类型转换的次数,提高了效率。不再提示编译警告。
43.1 泛型使用:
泛型语法:
interface 接口{} 和class 类 <K,V>{}
- 其中T,K,V不代表值,而是表示类型.
- 任意字母都可以,常用T表示,是Type的缩写.
泛型实例化:
在类名后指定参数类型的值(类型):
ArrayList<String> strings = new ArrayList<String>(); Iterator<Customer> iterator = customers.iterator();
细节:
- interface List{}, public class HashSet{}等
- T,E只能是引用类型.
- 在泛型指定具体类型后,可以传入该类型或者其子类类型.
- 泛型的使用形式和简写形式:
List<Integer> strings = new List<Integer>(); //简写形式 编译器会进行类型推断 List<Integer> strings = new List<>();
如果不指定具体的泛型类型时,默认是object类型.
List strings = new List(); //不指定具体的泛型类型时,默认是object类型. List<Object> strings = new List<>();
43.2 自定义泛型类
基础语法;
class 类名<T,R…>{ 成员 }
细节:
普通成员可以使用泛型(属性,方法).
class Genericclass<T,R,M>{ String name; R r; //属性使用泛型 M m; T t; public Genericclass(String name, R r, M m, T t) { this.name = name; this.r = r; this.m = m; this.t = t; } public R getR() { //方法使用泛型 return r; } }
使用泛型的数组,不能初始化。
//数组在创建的时候不能初始化,只能定义 T[] ts; // T[] te = new T[3] 无法初始化数组。因为不能确定创建空间的类型以及大小。
泛型标识符可以有多个。
静态方法中和静态属性不能使用类的泛型。
//静态方法不能使用类的泛型,因为静态方法可能在类实例化之前就被调用 public static void fun(){ }
泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)
如果创建对象时,没有指定类型,默认为Object类型。
43.3 自定义泛型接口
基础语法;
interface 类名<T,R…>{ 成员 }
interface GenericInt<U,R>{ R get(U u); default R method(U u){ return null; } } class Genclass implements GenericInt<String,Integer>{ @Override public Integer get(String s) { return null; } @Override public String setU(String s) { return null; } @Override public Integer method(String s) { return null; } }
细节:
- 接口中,静态成员也不能使用泛型。(接口中的属性都是静态属性)
- 泛型接口的类型,在继承接口或者实现接口时确定。
- 没有指定类型,默认为Object。
43.4 自定义泛型方法
基本语法:
修饰符 <T,R…> 返回类型 方法名(参数列表){}
public void run(){}; //使用泛型的普通方法 public <T,R> void fly(T t,R r){} //泛型方法 //调用方法: Car car = new Car(); car.fly(100,""); //当调用方法时,传入参数,编译器会确定类型
细节:
- 泛型方法可以定义在普通类中,也可以定义在泛型类中。
- 当泛型方法被调用时,类型会确定。
- public void 方法名(E e){} 修饰符后没有<T,R,…> 该方法不是泛型方法,而是使用了泛型。
- 泛型方法可以使用类声明的泛型,也可以使用自己声明的泛型。
43.5 泛型的继承和通配符
泛型不具备继承性
<?> : 支持任意类型函数。<? extends A> :支持A类以及A类的子类,规定泛型的上限。 <? super A>: 支持A类一级A类的父类,不限于直接父类,规定了泛型的下限。//List<父类> strings = new List<子类>(); 不被允许
//该方法允许传入一个泛型列表类,且其泛型可以是任意
public void PrintGeneric(List<?> l){}
//该方法允许传入一个泛型列表类,但其类型必须是A类及其子类
public void PrintGeneric(List<? extends A> l){}
//该方法允许传入一个泛型列表类,但其类型必须是A类及其父类
public void PrintGeneric(List<? super A> l){}
44. JUnit
一个类有功能代码需要测试,就需要写入到一个main方法中。
如果有多个功能代码需要测试就需要来回注释,解开注释,切换很麻烦。
如果可以直接运行一个方法们就会方便很多,并且可以给出相关信息。
45. java绘图
java绘图体系
java中坐标系,坐标远点位于左上角,以像素为单位。在java坐标系中,第一个是x坐标,表示当前位置为水平方向。距离坐标原点x个像素,第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。
符合计算机图形学知识体系。
像素是一个密度单位,并不是长度单位。
java中画图过程:
- 定义一个画板类,继承Jpanel类。
class Mypanel extends JPanel{ @Override public void paint(Graphics g) { super.paint(g); //调用父类方法完成初始化。 } }
- 重写父类的paint方法。在paint方法中调用绘制图像的方法。
- 将主方法所在类继承窗口框架类 JFram。
- 在主方法创建画板对象。
- 实例化继承窗口框架类对象,将画板添加至窗口框架类对象。
- 设置窗口参数。
Component类
Component类提供了两个和绘图相关的最重要的方法;
- paint(Graphics g)绘制组件的外观
- repaint()刷新组件的外观。
paint方法
paint(Graphics g)绘制组件的外观。
以下情况paint()方法会再次被调用。
- 当组件第一次在屏幕显示的时候,程序会自动的调用paint()方法来绘制组件。
- 窗口最小化,再最大化。
- 窗口的大小发生变化。
- repaint方法被调用。
例子:
public class draw extends JFrame{
private Mypanel mp = null;
public static void main(String[] args) {
new draw();
}
public draw(){
//初始化面板
mp = new Mypanel();
//将面板放入窗口框架中
this.add(mp);
//设置窗口大小
this.setSize(400,300);
//当点击关闭窗口时退出程序
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true); //可以显示
}
}
class Mypanel extends JPanel{
@Override
public void paint(Graphics g) {
super.paint(g); //调用父类方法完成初始化。
//调用方法绘制图像
g.drawOval(10,10,100,100);
}
}
Graphics类
Graphics类中提供了各种各样绘制图形的方法。
画直线:drawLine(int x1,int y1,int x2,int y2);
画矩形边框: drawRect(int x1,int y1,int width,int height);
画椭圆边框: drawOval(int x1,int y1,int width,int height);
填充矩形: fillRect(int x,int y,int width,int height);
填充矩椭圆: fillOval(int x,int y,int width,int height);
画图片:drawimage(imge img,int x,int y,…);
Image img = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/doubao.jpg")); g.drawImage(img,10,10,400,500,this);
画字符串: drawString(String str,int x,int y); //这里设置的位置是字符串的左下角。
设置画笔的字体 setFont(Font font);
g.setFont(new Font("字体",颜色,大小));
设置画笔的颜色: setColor(Color c);
46. java事件处理机制
java种事件处理采取的是"委派事件模型"。当事件发生时,产生事件的对象,会把此类 “信息” 传递给 “事件的监听者处理” ,这里的 “信息” 实际上就是java.aet.event 事件类库里某个类所创建的对象,把它称为事件的对象。
事件处理机制分为:事件源,事件,事件监听器。
- 事件源:事件源是一个产生事件的对象,比如按钮,窗口。
- 事件:事件就是承载事件源状态改变的对象,比如当键盘事件,鼠标事件,窗口事件等等。会生成一个事件对象,该对象保存这当前事件很多信息,比如KeyEvent对象有被按下键的Code值。java.awt.event包和javax.swing.event包种定义了各种事件类型。
- 事件监听器接口:
- 当事件源产生一个事件,可以传送给事件监听者处理。
- 事件监听者实际上就是一个类,给类实现了某个事件监听器接口。
- 事件监听器接口有多种,不同的事件监听器接口可以监听不同的事件,一个类可以实现多个接口。
- 这些接口在java.awt.event包和javax.swing.event包中定义。
47. 多线程基础
线程相关概念:
- 程序(program):是为了完成特定的任务,用某种语言编写的一组指令的集合。
- 进程:进程就是运行种的程序,启动一个进程,操作系统就会为该进程分配内存空间。进程是程序的一次执行过程,或者是正在运行的一个程序,是动态的过程:它有自身的产生,存在和消亡。
- 线程:线程是由进程创建的,是进程的一个实体。一个进程可以拥有多个线程。
- 单线程:同一时刻,只允许执行一个线程。
- 多线程:同一时刻是可执行多个线程。
- 并发:同一时刻,多个任务交替执行,造成一种”貌似同时“的错觉,单核cpu实现的多任务就是并发。
- 并行:同一时刻,多个任务同时执行,多核cpu可以实现并行。并发和并行可以同时存在。
47.1 创建线程
- 继承Thread类,重写run方法。
- 实现Runnable接口,重写run方法。
run方法是实现了Runnable接口的run方法。
继承thread类
当主线程执行子线程时主线程不会阻塞
public class Thread01 { public static void main(String[] args) { Cat cat = new Cat(); cat.start(); } } //1.当一个类继承了Thread类,该类就可以当作线程使用 class Cat extends Thread{ @Override public void run() { while (true) { System.out.println("线程正在执行"); //线程休眠 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
实现Runnable接口
- 创建类实现Runnable接口,实现run方法.
- 创建对应类的对象.
- 创建Thread类的对象,将实现Runnable接口类的对象传入Thread的构造方法.
- 使用Thread类对象调用start()方法创建新的线程.
public class Thread01 { public static void main(String[] args) throws InterruptedException { Cat2 ca = new Cat2(); Thread thread = new Thread(ca); thread.start(); } } class Cat2 implements Runnable{ @Override public void run() { System.out.println("次要线程"); } } }
将实现Runnable的类的对象传入Thread类的构造方法,是使用了静态代理模式.
47.2 调用线程
- 创建线程后实例化对象,再调用对应对象的start方法进行使用线程.
ThreadClass tc = new ThreadClass(); tc.start();
- 将实例化的对象传入Thread构造方法中,调用Tread类对象的start方法进行使用进程.
Cat2 ca = new Cat2(); Thread thread = new Thread(ca); thread.start();
start()方法调用开启一个新的线程,和主线程轮转运行.
run()方法调用,run()方法只是一个普通的方法,不开启新的线程,还是主线程调用run()中的代码.
start()方法调用底层start0()方法后.该线程并不会立即执行,只是将该线程变成可运行状态,具体什么事件执行,取决于cpu由cpu统一调度.
- start0()是本地方法,是JVM调用,底层是C/C++实现,真正实现多线程的效果是start0(),而不是run();
47.3 线程终止
当线程完成任务时自动退出.
可以通过使用变量来控制run方法退出的方式停止线程,即通知方式.
public class Thread03 {
public static void main(String[] args) {
Thsda thsda = new Thsda();
thsda.start();
thsda.setLoop(false);
}
}
class Thsda extends Thread{
//定义私有变量控制线程的for循环
private boolean loop = true;
@Override
public void run() {
while (loop){
System.out.println("线程运行中");
}
}
//私有属性的set方法,可以控制通知变量.
public void setLoop(boolean loop) {
this.loop = loop;
}
}
47.4 线程常用方法
SetName() //设置线程名称
getName() //获得该线程的名称
start() //使该线程开始执行
run() //调用线程对象run方法
setPriority() //更改线程的优先级
getPriority() //获取线程的优先级
sleep() //在指定的毫秒数内让正在执行的线程休眠.静态方法
interrupt() //中断线程,并不是结束线程,抛出InterruptedExcepotion异常.
yield:线程的礼让.让出cpu,让其他线程执行,但礼让的事件不确定,所以也不一定礼让的事件不确定,所以也不一定礼让成功.
join: 线程的插队,插队的线程一旦插队成功,则肯定限制运行插入线程的所有的任务.(再被插队的线程中调用插队线程的join方法)
//主线程中 Thead1 th = new Thead1(); th.start(); //主线程和th分线程交替执行 th.join() //将th分线程插入到主线程前,先执行完分线程再执行主线程。
用户线程和守护线程
- 用户线程:也叫工作线程,当线程的任务执行玩,或者通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。 (垃圾回收机制)
//主线程中
Thead1 th = new Thead1();
//设置指定线程为守护线程,在启动相应的线程
th.setDeamon(true);
th.start();
47.5. 线程的生命周期
JDK中使用Thread.State枚举表示了线程的几种状态。
- New: 尚未启动的线程处于此状态。
- Runnable: 启动或者正在Java虚拟机中执行的线程处于此状态
- Teminated: 线程终止。
- Time_Waiting: 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
- Warting: 正在等待另一个线程执行特定动作的线程处于此状态。
- Blocked: 被阻塞等待监视器锁定的线程处于此状态。
47.6. 线程同步机制
在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何任何时刻,最多有一个线程访问,以保证数据的完整性。
线程同步:即当有一个线程对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
同步方法:Synchronized
-
同步代码块:
synchronized (对象){ //得到对象的锁,才能操作同步代码 //需要被同步的代码 }
-
Synchronized 还可以放到方法声明中,表示整个方法为同步方法:
public synchronized void m(String name){ //需要被同步的代码 }
互斥锁
java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只有一个线程访问该对象。
关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
同步的局限性:导致程序的执行效率要降低。
同步方法(非静态的) 的锁可以是this,也可以是其他对象(要求是同一个对象)
同步方法(静态的) 的锁为当前类本身,不是this。
public start void m(String name){ synchronized (当前类名.class){ } }
同步代码块的锁可以是this也可以是其他对象。
同步方法如果没有使用static修饰,默认锁对象为this。
如果方法使用static修饰,默认锁对象:当前类.class
47.7. 线程的死锁
多个线程都占用了对方的锁资源,但是都不肯相让,导致了死锁,在编程是一定要避免死锁的发生。
47.8 释放锁
下面的操作会释放锁:
- 当前线程的同步方法,同步代码块结束。
- 当前线程在同步代码块,同步方法中遇到break,return。
- 当前线程在同步代码块,同步方法中出现了未处理的Error和Excepotion,导致异常结束,
- 当前线程在同步代码块,同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
下面的操作不会释放锁:
- 线程执行同步代码块或者同步方法时,程序调用Thread.sleep(),Thread.yield()方法暂停当前线程的执行,不会释放锁。
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起。
48. IO流
文件:文件就是保存数据的地方。
文件流: 文件在程序中是以流的形式来操作的。
- 流:数据在数据源(文件)和程序(内存)之间经历的路径。
- 输入流:数据从数据源(文件)到程序(内存)的路径。
- 输出流:数据从程序(内存)到数据源(文件)的路径。
- File实现了Serializable和Comparable接口可以串行化和排序。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7BpF4Saj-1650011950867)(./img/io接口图.png)]
File创建文件对象相关构造器
File中的路径中不同系统中路径分隔符不同。
构造器:
new File(String pathname) //根据路径构建一个File对象。
使用createNewFile()方法可以创建对应文件。
String filePath = "D:\\writ\\news1.txt"; File file = new File(filePath); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } System.out.println("文件创建成功");
new File(File parent,String child) //根据父目录文件 + 子路径构建
String parentFile = "D:\\writ\\"; String fileName = "new2.txt"; File file = new File(parentFile,fileName); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } System.out.println("文件创建成功");
new File(String parent,String child) //根据父目录 + 子路径构建
String parentFile = "D:\\writ\\"; String filePath = "new3.txt"; File file = new File(parentFile,filePath); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } System.out.println("文件创建成功");
常用文件的操作
getName: 获取文件的名字
getAbsolutePath: 获取文件的据对路径
getParent: 获取文件的父级目录
Iength: 获取文件的大小(字节)
exists: 是否存在文件
isFile: 是否是一个文件
isDiectory: 是否是一个路径;
mkdir:创建一级目录。
创建单级目录,返回一个boolean值
String parentFile = "D:\\writ\\newFile"; File file = new File(parentFile); if (file.mkdir()) { System.out.println("成功"); }else{ System.out.println("失败"); }
mkdirs:创建多级目录
返回多级目录,返回一个boolean值
String parentFile = "D:\\writ\\newFile\\jbz\\cql"; File file = new File(parentFile); if (file.mkdirs()) { System.out.println("成功"); }else{ System.out.println("失败"); }
delete:删除空目录或者文件
在java编程中目录被当作特殊的文件。
IO流原理及流的分类
-
Java IO流原理:
- IO是Input/Output的缩写,IO技术是非常实用的技术,用于处理数据传输。如读写文件,网络通讯。
- Jave程序中,对于数据的输入输出操作以流(stream)的方式进行。
- java.io包下提供了各种流类和接口,用以获取不同种类的数据,并通过方法输入或者输出数据。
- 输入input:读取外部数据(磁盘,光盘等存储设备的数据)到程序(内存)中。
- 输出output: 将程序(内存)数据输出到磁盘,光盘等存储设备中。
-
流的分类:
- 按照操作数据的单位不同分为: 字节流(8bit,对二进制文件如声音文件比较好),字符流(按字符对文本文件操作比较好)
- 按照数据流的流向不同分为: 输入流,输出流
- 按照流的角色的不同分为:节点流,处理流/包装流
抽象基类 字节流 字符流 输入流 InputStream Reader 输出流 OutputStream Writer - java的io流共涉及40多个类,实际上非常规则,都是从如上四个抽象基类中派生的。
- 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。
InputStream 字节输入流
inputStream抽象类是所有类字节输入流的超类。
inputStream常用子类:
- FileInputStream: 文件输入流
- BufferedInputStream :处理字节输入流
- ObjectInputStream: 对象字节输入流
OutputStream 字节输出流
OutputStream 抽象类是所有类字节输出流的超类。
OutputStream 常用子类:
- FileOutputStream : 文件输出流
- BufferedOutputStream : 处理字节输出流
- ObjectOutputStream : 对象字节输出流
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MTvY4Nlf-1650011950867)(./img/Inputstream父类图.png)]
FileInputStream 文件字节输入流
构造器:
FileInputStream f = new FileInputStream(文件路径);
方法:
read();
- 对指定文件读入单个字节。
- 如果读取完毕返回-1,正常读取返回char的码值。
read(byte[] b)
传入一个字符数组,对指定文件读入指定字符数组的长度存储在传入的字符数组中。
如果读取完毕返回-1,未到结尾返回读取的个数;
这个返回的数组只是存储读取到的字符,如果到达尽头没有读取到数组长度的字符,则之前读取过的字符不会刷新还是维持之前保存的字符。
可以结合new String(字节数组,开始,结束)构造器得到字符。
//----------------------------read()--------------- int readData; String parentFile = "D:\\writ\\jbz.txt"; FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(parentFile); while ((readData = fileInputStream.read()) != -1){ System.out.print((char)readData); } } catch (IOException e) { e.printStackTrace(); } finally { //关闭输入流 fileInputStream.close(); } //----------------------------read(byte[] buf)--------------- byte[] buf = new byte[8]; int readLen; String parentFile = "D:\\writ\\jbz.txt"; FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(parentFile); //正常读取返回读取到的字节数,已经到达结束未读取到字节返回-1 //如果读取到的长度不到指定字节数组的长度则再下一次读取返回-1,此次读取只返回读取的字节长度。 while ((readLen = fileInputStream.read(buf)) != -1){ //利用返回的长度使用String构造器读取字符串。 System.out.print(new String(buf,0,readLen)); } } catch (IOException e) { e.printStackTrace(); } finally { //关闭输入流 fileInputStream.close(); }
FileOutputStream 文件字节输出流
构造器:
FileOutputStream file = new FileOutputStream (文件路径);
FileOutputStream file = new FileOutputStream (文件路径,是否以添加形式); //当是否以添加形式为true时,写入的字节将添加到文件的末尾,而不是替换。
方法:
write( byte ); 将指定字节写入指定文件中
write( str.getBytes() );
write( str.getBytes() ,开始,结尾);
FileOutputStream fil = null;
String parentFile = "D:\\writ\\jbz.txt";
try {
//设置将字符添加到文件末尾
fil = new FileOutputStream(parentFile,true);
String str = "ddddd";
fil.write(str.getBytes());
fil.write('s');
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
FileReader 文件字符输入流
FileReader 和 FileWrite是字符流,即按照字符来操作io。
构造器:
FileReader(文件路径);
方法:
read()每次读取单个字符,返回该字符,如果到文件末尾返回-1;
read(char[]): 批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1.
返回的char数组重复使用的时候不会清空而是只覆盖获得数组长度的位置。
相关API:
new String(char[]):将char[]转换为String;
new String(char[],off,len):将char[]的指定部分转换成String。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lltkvT9P-1650011950868)(./img/read类.png)]
FileWrite 文件字符输出流
FileReader 和 FileWrite是字符流,即按照字符来操作io。
构造器:
FileWrite(文件路径); //覆盖模式,相当于流的指针在首端。
FileWrite(文件路径,true); //追加模式,相当于流的指针在尾端。
方法:
write(int); 写入单个字符。
write(char[]); 写入指定数组。
write(char[],off,len) 写入指定数组的指定部分。
write(String); 写入整个字符串
write(String,off,len) 写入指定字符串的指定部分。
相关API:
toCharArray 将指定String转换成char[];
注意:FileWriter使用后,必须要关闭(close)或者刷新(flush),否则写入不到指定的文件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gzI0iMt8-1650011950869)(./img//filrwrite类.png)]
FileReader fr = null;
char[] str = new char[100];
int le = 0;
String filp = "D:\\writ\\jbz.txt";
try {
fr = new FileReader(filp);
while ((le = fr.read(str)) != -1){
System.out.print(new String(str,0,le));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
节点流和处理流区别
节点流可以从一个特定的数据源读写数据。(FileRead,FileWriter)
处理流(包装流)是链接已经存在的流(处理流或节点流)之上,为程序提供更为强大的读写功能。(BufferedReader,BufferedWriter,BufferedOutputStream,BufferedInputStream)
节点流和处理流的区别和联系:
- 节点流是底层流,低级流,可以直接和数据源相接。
- 处理流包装节点流,即可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出。
- 处理流(包装流),对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连。
处理 流功能:
- 性能的提高:主要以增加缓冲的方式来提高输入输出的效率。
- 操作的便捷:处理流可以提供一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便。
BufferedReader 字符输入处理流
BufferedReader 和 BufferedWrite 属于字符流,是按照字符来读取数据。关闭时,只需要关闭外层流既可。
不要使用字符流来读取二进制文件。
构造器:
BufferedReader(reader流)
方法:
readLine(); 按行返回字符串,如果已经到达流的结尾返回null
String line = ""; String filePath = "D:\\writ\\Bufferedtest.txt"; BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath)); while ((line = bufferedReader.readLine()) != null){ System.out.println(line); } bufferedReader.close();
- 只需要关闭处理流(外层),就可以在底层代码中关闭节点流。
BufferedWriter 字符输出处理流
BufferedReader 和 BufferedWrite 属于字符流,是按照字符来读取数据。关闭时,只需要关闭外层流既可。
不要使用字符流来读取二进制文件。
构造器:
BufferedWriter(Writer流);
BufferedWriter( Writer流(文件路径,true) );
方法:
writer( String ); //插入指定字符串,不会换行.
newLine(); //插入一个换行,与系统相关的换行
String line = "开始的开始我们都是孩子"; String filePath = "D:\\writ\\Bufferedtest.txt"; BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath)); bufferedWriter.write(line); bufferedWriter.close();
BufferedInputStream 字节输入处理流
BufferedInputStream是字节流,在创建BufferedInputStream时,会创建一个内部缓冲区数组。
构造器:
BufferedInputStream( InputStream流 );
BufferedInputStream( InputStream流 ,size);
BufferedOutputStream 字节输出处理流
BufferedOutputStream是字节流,在创建BufferedOutputStream 时,会创建一个内部缓冲区数组。
构造器:
BufferedOutputStream(OutputStream流);
BufferedOutputStream(OutputStream流(路径,true));
ObjectOutputStream
能够将基本数据类型或者对象进行序列化。使用修饰者模式。
序列化:保存数据时,保存数据的值和数据类型。
反序列化: 将保存的数据回复值和数据类型。
序列化后,保存的文件格式,不以保存文件后缀保存,以自身的格式来保存。
需要让某个对象支持序列化机制,则必须让其类时可序列化的,为了让某个类时可序列化的,该类必须实现如下两个接口之一:
- Serializable //标记接口,没有任何方法
- Externalizable //该接口有方法需要实现
方法:
- writeInt(100); //保存int类型
- writeBoolean(true); //保存boolean类型
- writeDouble(1.2); //保存double
- writeUTF(“你好”); //保存字符串
- writeChar(‘s’); //保存char
- oos.writeObject(对象); //保存实现接口的对象
public static void main(String[] args) throws IOException { String filePath = "D:\\writ\\wht.txt"; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath)); oos.writeInt(100); //自动进行装箱操作 oos.writeBoolean(true); //自动进行装箱操作 oos.writeDouble(1.2); //自动进行装箱操作 oos.close(); }
ObjectInputStream
能够将基本数据类型或者对象进行反序列化。使用修饰者模式。
序列化:保存数据时,保存数据的值和数据类型。
反序列化: 将保存的数据回复值和数据类型。
需要让某个对象支持序列化机制,则必须让其类时可序列化的,为了让某个类时可序列化的,该类必须实现如下两个接口之一:
- Serializable //标记接口,没有任何方法
- Externalizable //该接口有方法需要实现
方法:
readInt(); //保存int类型
readBoolean(); //保存boolean类型
readDouble(); //保存double
readUTF(); //保存字符串
readChar(); //保存char
读取的顺序需要和序列化的顺序一致。
反序列化对象,对应的类应该被定义。
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath)); System.out.println(ois.readInt()); System.out.println(ois.readBoolean()); System.out.println(ois.readDouble()); System.out.println(ois.readUTF()); System.out.println(ois.readChar());
注意细节:
读写顺序需要一致.
要求实现序列化或者反序列化对象,需要实现Serializable
序列化的类中添加SerialVersionUID,为了提高版本的兼容性.
private static final long serialVersionUID = 1L; //序列化的版本号,实现兼容性.
序列化对象时,默认将里面的所有属性都进行序列化但是除了static和transient修饰的成员.
序列化对象时,要求里面的属性的类型也需要实现对应的序列化接口了.
序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现序列化.
标准输入输出流
in 编译类型: InputStream 运行类型: BufferedInputStream
out 编译类型: PrintStream 运行类型: PrintStream
标准输入输出流 | 类型 | 默认设备 |
---|---|---|
System.in | InputSteam | 键盘 |
System.out | PrintStream | 显示器 |
Systeam.out.println();
Scanner san = new Scanner(System.in);
转换流定义
字符流的默认编码是UTF-8,如果文件使用的编码不是默认的UTF-8则使用字符流读取会出现乱码现象。
转换流InputStreamReader 和 OutputStreamWriter可以实现字节流向字符流的转换并且实现指定编码方式。
InputStreamReader是Reader的子类,可以将InputStream(字节流)包装称为Reader(字符流),并且指定编码方式;
OutputStreamWriter是Writer的子类,可以将 OutputStream(字节流)包装为Writer(字符流),并且指定编码方式;
当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流。
可以在使用时指定编码格式: utf-8,gbk,gb2312,iso8859-1
InputStreamReader 转换输入流
构造器:
InputStreamReader(inputStream流 , 编码); 传入一个inputStream.
public static void main(String[] args) throws IOException {
String filePath = "D:\\writ\\wht.txt";
String str = "";
//创建一个字节流,使用转换流转换为字符流,再将字符流包装为处理流
BufferedReader bfr = new BufferedReader(new InputStreamReader(new FileInputStream(filePath),"gbk"));
while ((str = bfr.readLine()) !=null){
System.out.println(str);
}
bfr.close();
}
OutputStreamWriter 转换输出流
构造器:
OutputStreamWriter(OutputStream流,编码); 传入一个OutputStream.
可以用该种方式创建指定编码方式的文件。
public static void main(String[] args) throws IOException {
String filePath = "D:\\writ\\wht.txt";
String str = "歌谣的歌谣,藏着童话的影子";
//创建一个字节流使用转换流转换为字符流,再将该字符流包装为处理流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath,true),"gbk"));
bw.newLine();
bw.write(str);
bw.close();
System.out.println("插入成功");
}
打印流定义
打印流没有输入流只有输出流。
在默认情况下输出数据的位置是显示器。
PrintStream
PrintWriter
PrintStream 字节打印流
构造器:
PrintStream(文件路径);
PrintStream( InputStream流 );
方法:
System.setOut(new PrintStream(文件路径)); //设置输出的位置
public static void main(String[] args) throws IOException { String filePath = "D:\\writ\\wht.txt"; //设置打印位置 System.setOut(new PrintStream(filePath)); System.out.println("最后的最后"); }
PrintWriter 字符打印流
构造器:
PrintWriter( Writer流 )
方法:
print() //向指定位置输出字符
close() //关闭打印流。
如果不关闭打印流,不会刷新不会打印进去。
public static void main(String[] args) throws IOException { String filePath = "D:\\writ\\wht.txt"; PrintWriter pw = new PrintWriter(new FileWriter(filePath)); pw.println("开始的开始"); pw.close(); }
Properties类
专门用于读写配置文件的集合类。配置文件的格式”
键=值
键=值
键值对不需要有空格,值不需要用引号引起来,默认类型是String。
方法:
- load() : 加载配置文件的键值对到Properties对象。
- list() : 将数据显示到指定设备。
- getProperty(key) : 根据键获取值。
- setProperty(key,value): 设置键值对到Properties对象。如果没有就是创建,如果有就是替换。
- store( InputStream流,注释):将Properties中的键值对存储到配置文件,在idea中,保存信息到配置文件,如果含有中文,会存储为unicode码。
//获取配置文件的值
public static void main(String[] args) throws IOException {
String filePath = "src\\myProperties.properties";
Properties ps = new Properties();
ps.load(new FileReader(filePath));
ps.list(System.out);
String user = ps.getProperty("user");
System.out.println(user);
}
//创建配置文件
public static void main(String[] args) throws IOException {
String filePath = "src\\myProperties2.properties";
Properties pr = new Properties();
pr.setProperty("user","cql");
pr.setProperty("name","nnn");
pr.setProperty("pwd","10086");
pr.store(new FileOutputStream(filePath),null);
System.out.println("配置成功");
}
ByteArrayOutputStream
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while ((readLean = bufferedInputStream.read(by)) != -1){
bos.write(by,0,readLean);
}
byte[] array = bos.toByteArray();
49.网络编程
ip地 址:
概念:用于唯一表示网络中的每台计算机/主机。
查看ip地址: ipconfig
ip地址的表示形式:点分十进制。(每个十进制的范围0~255)
IPv4使用四个字节,IPv6使用十六个字节
IP地址的组成 = 网络地址 + 主机地址
IPv6是互联网工程任务组设计的用于替代IPv4的下一代IP协议,其地址数量号称可以为全世界任何一粒沙子编上一个地址。
因为IPv4的最大问题在于网络地址资源有限,严重制约了互联网的应用和发展,IPv6的使用,不仅能解决网络资源数量的问题,而且也解决了多种接入设备连入互联网的障碍。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VFVR7lvY-1650011950870)(./img/ipv4.png)]
特殊: 127.0.0.1
域名
概念:将ip地址映射为域名。方面记忆,解决ip的记忆困难。
端口号
概念:用于标识计算器上某个特定的网络程序。
表示形式:使用整数形式,范围0~65535(2个字节)
0 ~ 1024已经被占用,比如ssh 22,ftp 21 , smtp 25,http 80
网络通信协议
TCP/IP中文译名为传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议是Internet最基本的协议,Internet国际互联网络的基础,简单的说,就是网络层的ip协议和传输层的TCP协议组成的。
TCP协议:传输控制协议
- 使用TCP协议前,需要建立TCP链接,形成传输数据通道。
- 传输前,采用三次握手方式,是可靠的。
- TCP协议进行通信的两个应用进程:客户端,服务器
- 在链接中可以进行大量数据的传输。
- 传输完毕,需要释放已经建立的链接,效率低。
UDP协议:
-
将数据,源,目的封装成数据包,不需要建立链接。
-
因为是无需连接的,所以是不可靠的。
-
每个数据报的大小限制在64k内。
-
发送数据结束后无需释放资源(因为不是面向链接的),速度快。
InetAddress类
InetAddress类实现了Serializable接口所以可以实现串行化。
获取InetAddress类对象:
//获取当前主机的InetAddress类 InetAddress localhost = InetAddress.getLocalHost(); //获取指定域名的InetAddress类 InetAddress localhost = InetAddress.getByName("网址") //获取指定主机名的InetAddress类 InetAddress localhost = InetAddress.getByName("主机名")
方法:
//通过 InetAddress对象获得ip地址 localhost.getHostAddress(); //通过 InetAddress对象获得域名 localhost.getHostName() //输出本机的ip地址和本机名 System.out.println(localhost);
Socket
- 套接字(Socket)开发网络应用程序被广泛采用,以至于称为事实上的标准。
- 通信的两端都要有Socket,这是两台机器间通信的端点。
- 网络通信其实就是socket间的通信。
- socket允许程序把网络链接当成一个流,数据在两个socket间通过IO传输。
- 一般主动发起通信的应用程序属于客户端,等待通信请求的为服务端。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P0upQoqx-1650011950871)(./img/socket图解.png)]
TCP网络通信编程
当客户端连接到服务端后,实际上也是通过一个端口和服务端进行通讯的,这个端口是TCP/IP来分配的,是不确定的,是随机的。
TCP字节流编程
TCP字节流编程步骤:
服务器:
- 设置监听端口获得ServerSocket对象。
ServerSocket serverSocket = new ServerSocket(监听端口号);
- 获得socket对象
Socket socket = serverSocket.accept();
//如果监听到数据会执行,如果没有阻塞。- 通过使用socket对象获得输入流,将数据通道的数据取出。
InputStream inputStream = socket.getInputStream();
inputStream.read()
- 通过使用socket对象获得输出流,像数据通道写入数据。
OutputStream outputStream = socket.getOutputStream();
outputStream.write(字符)
- 设置写入结束标记。
socket.shutdownOutput();
//如果不设置结束标记Socket对象会一直等待- 关闭输入输出流,socket对象,ServerSocket对象。
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();
//服务器端 public static void main(String[] args) throws IOException { //建立端口监听 ServerSocket serverSocket = new ServerSocket(9999); System.out.println("---------------服务器监听中……"); Socket socket = serverSocket.accept(); System.out.println("----------------服务器客户端通道建立"); //使用cocket对象向数据通道读取数据 InputStream inputStream = socket.getInputStream(); int readLen = 0; byte[] by = new byte[1024]; while ((readLen = inputStream.read(by)) != -1){ String str = new String(by,0,readLen); System.out.println("服务器得到数据:" + str); } //使用cocket对象向数据通道写入数据 OutputStream outputStream = socket.getOutputStream(); outputStream.write("$$$$服务器向客户端返回的数据".getBytes()); socket.shutdownOutput(); System.out.println("-----------------客户端返回数据"); //关闭流和socket outputStream.close(); inputStream.close(); socket.close(); serverSocket.close(); }
客户端:
- 连接指定InetAddress的服务器,设置端口。获得Socket对象
Socket socket = new Socket(InetAddress对象, 端口);
- 通过使用socket对象获得输出流,像数据通道写入数据。
OutputStream outputStream = socket.getOutputStream();
outputStream.write(字符)
- 设置写入结束标记。
socket.shutdownOutput();
//如果不设置结束标记Socket对象会一直等待- 通过使用socket对象获得输入流,将数据通道的数据取出。
InputStream inputStream = socket.getInputStream();
inputStream.read()
- 关闭输入输出流,socket对象。
outputStream.close();
inputStream.close();
socket.close();
//客户端 public static void main(String[] args) throws IOException { //连接指定服务器的指定端口 Socket socket = new Socket(InetAddress.getLocalHost(),9999); //使用socket对象向数据通道中写入数据(字节流) OutputStream outputStream = socket.getOutputStream(); outputStream.write("¥¥¥¥客户端发送的数据".getBytes()); socket.shutdownOutput(); System.out.println("--------------客户端向服务器发送数据"); //使用socket对象向数据通道中读取数据(字符流) InputStream inputStream = socket.getInputStream(); int readLen = 0; byte[] by = new byte[1024]; while ((readLen = inputStream.read(by)) != -1){ String str = new String(by,0,readLen) System.out.println("服务器返回的数据:" + str); } System.out.println("-------------------客户端返回数据"); //关闭流也socket对象 inputStream.close(); outputStream.close(); bufferedReader.close(); socket.close(); }
TCP字符流编程
TCP字符流编程步骤:
服务器:
- 设置监听端口获得ServerSocket对象。
ServerSocket serverSocket = new ServerSocket(监听端口号);
- 获得socket对象
Socket socket = serverSocket.accept();
- 通过使用socket对象获得输入流,将数据通道的数据取出,使用转换流和处理流得到相应的字符流。
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//使用转换流和处理流bufferedReader.read()
- 通过使用socket对象获得输出流,像数据通道写入数据,使用转换流和处理流得到相应的字符流。
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
//使用转换流和处理流bufferedWriter.write(字符)
- 设置写入结束标记,刷新流。
bufferedWriter.newLine();
//如果不设置结束标记Socket对象会一直等待bufferedWriter.flush();
//如果不设置刷新则不会写入数据- 关闭输入输出流,socket对象,ServerSocket对象。
bufferedReader.close();
bufferedWriter.close();
socket.close();
serverSocket.close();
//服务器端 public static void main(String[] args) throws IOException { //建立端口监听 ServerSocket serverSocket = new ServerSocket(9999); System.out.println("---------------服务器监听中……"); Socket socket = serverSocket.accept(); System.out.println("----------------服务器客户端通道建立"); //使用cocket对象向数据通道读取数据 InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String str = bufferedReader.readLine(); System.out.println("服务器得到数据:" + str); //使用cocket对象向数据通道写入数据 OutputStream outputStream = socket.getOutputStream(); BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); bufferedWriter.write("$$$$服务器向客户端返回的数据"); bufferedWriter.newLine(); bufferedWriter.flush(); System.out.println("-----------------客户端返回数据"); //关闭流和socket bufferedReader.close(); bufferedWriter.close(); socket.close(); serverSocket.close(); }
客户端:
- 连接指定InetAddress的服务器,设置端口。获得Socket对象
Socket socket = new Socket(InetAddress对象, 端口);
- 通过使用socket对象获得输出流,像数据通道写入数据,使用转换流和处理流得到相应的字符流。
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
//使用转换流和处理流bufferedWriter.write(字符)
- 设置写入结束标记,刷新流。
bufferedWriter.newLine();
//如果不设置结束标记Socket对象会一直等待bufferedWriter.flush();
//如果不设置刷新则不会写入数据- 通过使用socket对象获得输入流,将数据通道的数据取出,使用转换流和处理流得到相应的字符流。
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//使用转换流和处理流bufferedReader.read()
- 关闭输入输出流,socket对象。
bufferedReader.close();
bufferedWriter.close();
socket.close();
//客户端 public static void main(String[] args) throws IOException { //连接指定服务器的指定端口 Socket socket = new Socket(InetAddress.getLocalHost(),9999); //使用socket对象向数据通道中写入数据(字符流) OutputStream outputStream = socket.getOutputStream(); BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); bufferedWriter.write("¥¥¥¥客户端发送的数据"); //设置写入数据结束标记 bufferedWriter.newLine(); //刷新数据,如果不刷新不会将指定数据写入通道 bufferedWriter.flush(); System.out.println("--------------客户端向服务器发送数据"); //使用socket对象向数据通道中读取数据(字符流) InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String str = bufferedReader.readLine(); System.out.println("服务器返回的数据:" + str); System.out.println("-------------------客户端返回数据"); //关闭流与socket对象 bufferedWriter.close(); bufferedReader.close(); socket.close(); }
netstat指令
- netstat - an 可以查到当前主机网络情况,包括端口监听情况和网络连接情况。
- netstat - an | more 可以分页显示。
- 要求在dos控制台下执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZhquaHuG-1650011950872)(./img/netstat.png)]
- LISTENING表示某个端口在监听。
- 如果有一个外部程序(客户端)连接到该端口,就会显示一天连接信息。
UDP网络通信编程
- 类DatagramSocket和DatagramPacket 实现了基于UDP协议网络程序。
- UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以到达。
- DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的ip地址和端口号以及接收端的ip地址和端口号。
- UDP协议中每个数据报都给出了完整的地址信息,因此无需建立发送方和接收方的来连接。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r3QmkmUX-1650011950872)(./img/UDO图解.png)]
说明:
- 没有明确的服务端和客户端,演变成数据的发送端和接收端。
- 接收数据和发送数据是通过DatagramSocket对象完成。
- 将数据封装到DatagramPacket对象(装包)。
- 当接收到DatagramPacket对象,需要进行拆包,取出数据。
- DatagramSocket可以指定在那个端口接收数据。
- 接收端和服务端都可以设定使用的接口。
构造器:
DatagramSocket(端口号);
DatagramPacket(数据, 数据长度,接收端InetAddress对象,端口号); //用于发送数据
DatagramPacket(数据, 数据长度); //用于接收数据
UDP编程步骤:
- 创建DatagramSocket对象,在指定端口等待接收数据
DatagramSocket socket = new DatagramSocket(9998);
- 将需要发送的数据封装为DatagramPacket对象。
byte[] data = "发送端封装的数据".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length,InetAddress.getLocalHost(),9999);
//DatagramPacket(数据,数据长度,IP地址,端口);- 发送数据。
socket.send(packet);
- 构造接收数据的DatagramPacket对象。
byte[] datas = new byte[64*1024];
DatagramPacket repacket = new DatagramPacket(datas, datas.length);
//DatagramPacket(数据,数据长度);- 接收数据。
socket.receive(repacket);
- 将接收的数据报拆包。
int length = repacket.getLength();
byte[] redata = repacket.getData();
String str = new String(redata,0,length);
- 关闭资源
socket.close();
public static void main(String[] args) throws IOException {
//1.创建DatagramSocket对象准备返回和接收数据
//准备在指定端口接收数据
DatagramSocket socket = new DatagramSocket(9998);
//2.将需要发送的数据封装到DatagramPacket
byte[] data = "发送端封装的数据".getBytes();
//DatagramPacket(数据,数据长度,IP地址,端口);
DatagramPacket packet = new DatagramPacket(data, data.length,InetAddress.getLocalHost(),9999);
//4.发送数据
socket.send(packet);
//----------------------------------
//5.构造接收datagramPacket对象
byte[] datas = new byte[64*1024];
DatagramPacket repacket = new DatagramPacket(datas, datas.length);
//6.接收数据
socket.receive(repacket);
System.out.println("接收到数据……");
//7.拆包
int length = repacket.getLength();
byte[] redata = repacket.getData();
String str = new String(redata,0,length);
System.out.println(str);
//8.关闭资源
socket.close();
System.out.println("B端退出……");
}
先接收端
- 创建DatagramSocket对象,在指定端口等待接收数据
DatagramSocket socket = new DatagramSocket(9998);
- 构造接收数据的DatagramPacket对象。
byte[] datas = new byte[64*1024];
DatagramPacket repacket = new DatagramPacket(datas, data.length);
//DatagramPacket(数据,数据长度);- 接收数据。
socket.receive(repacket);
- 将接收的数据报拆包。
int length = repacket.getLength();
byte[] redata = repacket.getData();
String str = new String(redata,0,length);
- 将需要发送的数据封装为DatagramPacket对象。
byte[] data = "发送端封装的数据".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length,InetAddress.getLocalHost(),9999);
//DatagramPacket(数据,数据长度,IP地址,端口);- 发送数据。
socket.send(packet);
- 关闭资源
socket.close();
public static void main(String[] args) throws IOException {
//1.创建一个DatagramSocket对象,准备在指定端口接收数据,
DatagramSocket socket = new DatagramSocket(9999);
//2.构建一个DatagramPacket对象,准备接收数据.
byte[] buf = new byte[64 * 1024];
DatagramPacket packet = new DatagramPacket(buf,buf.length);
//3.调用接收方法。将通过网络传输的DatagramPacket对象填充到packet对象中。
//当有数据报发送到本机指定端口时,会接收数据。如果没有数据发送到指定端口,就会阻塞等待。
System.out.println("正在9999端口等待数据到来……");
socket.receive(packet);
System.out.println("接收到数据进行拆包……");
//4.将packet进行拆包,取出数据,并显示
int length = packet.getLength(); //实际接收到的数据长度
byte[] data = packet.getData();//接收到数据
String str = new String(data,0,length);
System.out.println(str);
//--------------------------
//5.创建datagrampacketu对象封装数据。
byte[] rebuf = "重新发送的数据".getBytes();
DatagramPacket packets = new DatagramPacket(rebuf, rebuf.length, InetAddress.getLocalHost(),9998);
//6.发送数据
socket.send(packets);
//7.关闭资源
socket.close();
System.out.println("A端退出……");
}
50. 项目开发流程
需求分析 - 设计阶段 - 实现阶段 - 测试阶段 - 实施阶段 - 维护阶段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CvG8aKkx-1650011950873)(./img/项目开发流程图.png)]
51. 反射
通过外部文件配置,在不修改源码情况下,来控制程序,也符合设计模式ocp(开闭原则:不修改源码,扩容功能)
优点:
- 可以动态的创建和使用对象(是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
缺点:
- 使用反射基本是解释执行,对执行速度有影响。
反射机制(java reflection):
反射机制允许程序在执行期借助于reflectionAPI取得任何类的内部信息(比如成员变量,构造器,成员方法等),并能操作对象的属性以及方法。反射在设计模式和框架底层都会用到。
加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个CLass对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以称之为:反射。
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时得到任意一个类的所育有的成员变量的方法。
- 在运行时调用任意一个对象的成员变量和方法。
- 生成动态代理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pS6uZrZV-1650011950874)(./img/反射机制.png)]
反射机制相关类
java.lang.Class 代表一个类,Class对象表示某个类加载后在堆中的对象。
java.lang.reflect.Method 代表类的方法
java.lang.reflect.Field 代表类的成员变量
java.lang.reflect.Constructor 代表类的构造方法
反射操作对象
使用Properties类操作配置文件,取出类的路径和方法名,属性名
Properties pr = new Properties(); pr.load(new FileInputStream("src\\re.properties")); String classname = pr.getProperty("ClassName"); String methodname = pr.getProperty("MethodName"); String fieldname = pr.getProperty("FieldName");
- 使用类的路径名,类名,构造出相应类的Class对象。
Class cls = Class.forName(classname);
- 通过Class类的对象加载类的实例
Object o = cls.newInstance();
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//构造Properties类获取配置文件中的属性
Properties pr = new Properties();
pr.load(new FileInputStream("src\\re.properties"));
String classname = pr.getProperty("Username");
//加载类,返回Class类型
Class cls = Class.forName(classname);
//通过cls得到加载类的实例
Object o = cls.newInstance();
System.out.println("o的运行类型为=" + o.getClass());
}
----------------------------------------------------------------------------
Username=com.qn.resd.DouBao
method=cry
反射操作方法
使用Properties类操作配置文件,取出类的路径和方法名,属性名
Properties pr = new Properties(); pr.load(new FileInputStream("src\\re.properties")); String classname = pr.getProperty("ClassName"); String methodname = pr.getProperty("MethodName"); String fieldname = pr.getProperty("FieldName");
- 使用类的路径名,类名,构造出相应类的Class对象。
Class cls = Class.forName(classname);
- 通过Class类的对象加载类的实例
Object object = cls.newInstance();
- 通过Class类的对象加载对应方法对象
Method cryMethod = cls.getMethod(cry);
- 通过Method的对象调用方法,传入方法对应的实例对象
cryMethod.invoke(object);
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//构造Properties类获取配置文件中的属性
Properties pr = new Properties();
pr.load(new FileInputStream("src\\re.properties"));
String classname = pr.getProperty("Username");
//加载类,返回Class类型
Class cls = Class.forName(classname);
//通过cls得到加载类的实例
Object o = cls.newInstance();
System.out.println("o的运行类型为=" + o.getClass());
//获得对应方法的对象
Method cryMethod = cls.getMethod(cry);
//调用方法
cryMethod.invoke(object);
}
----------------------------------------------------------------------------
Username=com.qn.resd.DouBao
method=cry
反射操作属性
无法访问私有属性。
使用Properties类操作配置文件,取出类的路径和方法名,属性名
Properties pr = new Properties(); pr.load(new FileInputStream("src\\re.properties")); String classname = pr.getProperty("ClassName"); String methodname = pr.getProperty("MethodName"); String fieldname = pr.getProperty("FieldName");
- 使用类的路径名,类名,构造出相应类的Class对象。
Class cls = Class.forName(classname);
- 通过Class类的对象加载类的实例
Object object = cls.newInstance();
- 通过Class类的对象加载对应属性对象
Field fieldname = cls.getField("fieldname");
- 通过Field的对象调用方法,传入方法对应的实例对象
fieldname.get(object);
- 通过Field对象更改属性值
fieldname.get(object,"值");
- 遍历所有的属性
Field[] fieldname = cls.getFields();
for(Field f : filelds){ f.getName()}
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//构造Properties类获取配置文件中的属性
Properties pr = new Properties();
pr.load(new FileInputStream("src\\re.properties"));
String classname = pr.getProperty("Username");
//加载类,返回Class类型
Class cls = Class.forName(classname);
//通过cls得到加载类的实例
Object o = cls.newInstance();
System.out.println("o的运行类型为=" + o.getClass());
//通过Class类的对象加载对应属性对象
Field fieldname = cls.getField("fieldname");
//通过Method的对象调用方法,传入方法对应的实例对象
fieldname.get(object);
}
----------------------------------------------------------------------------
Username=com.qn.resd.DouBao
method=cry
反射机制优化
Method和Field,Constructor对象都有setAccessible()方法。
setAccessible作用是启动和禁用访问安全检查的开关。
参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率,参数值未false则表示对反射执行访问检查。(但是效果并不明显)
Class类
Class类继承Object类。
Class类对象不是new出来的,而是系统创建的。
对于每个类的Class对象,在内存中只有一份,因为类只加载一次。
每个类的实例都会记的自己是哪个Class实例所生成。
通过Class可以完整的得到一个类的完整的结构,通过一系列API。
Class对象是存放在堆中的。
类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据。
如下类型有Class对象
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface :接口
- 数组
- enum : 枚举
- annottation : 注解
- 基本数据类型
- void
获取Class类对象
前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法fotName()获取,可能抛出ClassNotFoundExcepotion
- 多用于配置文件
Class cls = Class.forName("java.long.Cat")
前提:已知具体的类,通过类名.class获取,该方式最为安全可靠,程序性能最高。
- 多用于参数传递
Class cls = Cat.class
前提:一直某个类的实例,调用该实例的getClass()方法获取Class对象。也是获得运行类型的方法。
Class cls = 对象.getClass();
通过类加载器获得类的Class的对象
Class cls = classLoader.loadClass(classAllPath)
基本数据类型(int,double,char,boolean,long,float,byte,short)可以通过 基本数据类型.class 得到对应的Class类对象。
Class cls = 基本数据类型.class
基本数据类型对应的包装类,可以通过 包装类.TYPE得到对应的Class类对象
Class cls = 包装类.TYPE
5和6得到的Class类相同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zeNv1zOj-1650011950875)(./img/class类图.png)]
常用方法
方法名 | 功能 |
---|---|
static Class forName(String name) | 返回指定类名的class对象 |
getName() | 获取全类名(类名+包名) |
getSimpleName() | 获取简单类名(类名) |
getFields() | 返回所有public修饰的属性,包含本类以及父类的 |
getDeclaredFields() | 返回本类中的所有属性 |
getMethods() | 获取所有public修饰的方法,包含本类以及父类 |
getDeclaredMethods() | 获取本类中的所有方法。 |
getConstructors() | 返回一个包含本类public修饰Constructor对象的数组 |
getDeclaredConstructors() | 获取本类中的所有构造器 |
getPackage() | 以package得到类的包名 |
Class getSuperClass() | 返回当前Class对象的父类Class对象 |
Class [] getInterfaces() | 返回当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回此Class所表示实体的超类的Class |
getAnnontations() | 以Annotation[] 形式返回注释信息 |
Field类
常用方法:
- getModifiers():以int形式返回修饰符
- 默认修饰符为 0;public 为 1;private为2;protected为4
- static 为8;final为 16
- 多个修饰符返回相加的值
- getType()以Class形式返回类型
- getName() 返回属性名
Method类
常用方法:
- getModifiers()以int形式返回修饰符
- 默认修饰符为 0;public 为 1;private为2;protected为4
- static 为8;final为 16
- 多个修饰符返回相加的值
- getRetrunType() : 以Class形式获取返回类型
- getName() : 返回方法名
- getParameterType() : 以Class[]返回参数类型数组
Constructor类
常用方法:
- getModifiers()以int形式返回修饰符
- 默认修饰符为 0;public 为 1;private为2;protected为4
- static 为8;final为 16
- 多个修饰符返回相加的值
- getName() : 返回构造器名(全类名)
- getParameterTypes (): 以Class[]返回参数类型数组
类加载过程
反射机制是java动态语言的关键,也就是通过反射实现类的动态加载。
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强。
- 动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性。
类加载时机:
- 当创建对象时(new) --------------静态加载
- 当子类被加载时,父类加载 --------------静态加载
- 调用类中的静态成员时 --------------静态加载
- 通过反射 -------动态加载
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pFuHqcIz-1650011950875)(./img/类加载.png)]
1. 类加载
JVM在该阶段的主要目的是将字节码从不同的数据源(可能是class文件,也可能是jar包,甚至网络)转化二进制字节流加载到内存中,并生成一个代表类的java.lang.Class对象。
2. 连接阶段-验证
- 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- 包括:文件格式验证(是否以obcafebabe开头),元数据验证,字节码验证和符号引用验证。
- 可以考虑使用——Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
3.连接阶段-准备
- JVM会在该阶段对静态变量,匹配内存并默认初始化(对应数据类型的默认初始值,如0,0L,null,false)。这些变量所使用的内存都将在方法区中进行分配。
- 只有静态变量才会分配内存,默认初始化。
- static final的常量,它和静态变量不同,因为一旦赋值就不变,所以会直接赋值。
3.连接阶段-解析
虚拟机将常量池中的符号引用替换为直接引用的过程。
4. 初始化
- 到初始化阶段,才是真正开始执行类中定义的java程序代码。此阶段是执行()方法的过程。
- ()方法是由编译器按语句在源文件中出现的顺序,一次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
- 虚拟机会保证一个类()方法在多线程环境中被正确的加锁,同步,如果多个线程同时去初始化一个类,那么只有一个线程回去执行这个类的()方法,其他线程都会被阻塞等待,直到活动线程执行()方法完成。
通过反射创建对象
方式一:调用类中的public修饰的无参构造器
方式二:调用类中的指定构造器
相关方法:
Class类:
- newInstance() : 调用类中的无参构造器,获取对应的对象。
- getConstructor(Class class)根据参数列表,获取对应的public构造器对象
- getDecalaredConstructor(Class class):根据参数列表,获取对应的所有构造器对象。
Constructor类相关方法。
- setAccessible //爆破,使用反射可以访问private的构造器/方法/属性
- newInstance(Object obj):调用构造器
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
String filePath = "com.qn.xc.User";
//1.获取到指定类的Class对象
Class<?> aClass = Class.forName(filePath);
//2.通过public的无参构造器创建实例
Object o = aClass.newInstance();
//3.通过public的有参构造器创建实例
//先得到对应的构造器,再创建实例
Constructor<?> constructor = aClass.getConstructor(String.class);
Object o1 = constructor.newInstance("实参");
System.out.println(o1);
//4.通过非public的有参构造器创建实例
Constructor<?> constructor1 = aClass.getDeclaredConstructor(int.class,String.class);
constructor1.setAccessible(true); //爆破,使用反射可以访问private的构造器/方法/属性
Object o2 = constructor1.newInstance(11, "形参");
System.out.println(o2);
}
通过反射访问类的属性
根据属性名获取Field对象 Field f = class对象.getDeclaredField(属性名)
爆破:field.setAccessible(true);
操作属性:
- field.set(o,值);
- field.get(o); //表示对象
注意:如果是静态属性,则set和get中的参数o可以为:null。
在方法中如果有返回值,一律使用object类型接收。
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
String filePath = "com.qn.xc.User";
//1.获取到指定类的Class对象
Class<?> uclass = Class.forName(filePath);
Object o = uclass.newInstance();
//2.获取指定类的指定属性
Field fieldName = uclass.getDeclaredField("fieldName");
//3.如果是私有属性可以使用爆破
fieldName.setAccessible(true);
//4.获得指定对象的指定属性
Object o1 = fieldName.get(o); //静态属性.get(null)
//5.将指定对象的指定属性设置为指定值
fieldName.set(o,22); //静态属性.set(null,22)
System.out.println(o1);
}
通过反射访问类的方法
根据方法名和参数列表获取Method方法对象:
- Method m = class.getDeclaredMethod(方法名,XX.class);
获取对象: Object o = class.newInstance();
爆破:m.setAccessible(true)
访问: Object returnValue = m.invoke(o,实参列表);
如果是静态方法,则invoke的参数o可以写成null。
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
String filePath = "com.qn.xc.User";
//1.获取到指定类的Class对象
Class<?> uclass = Class.forName(filePath);
Object o = uclass.newInstance();
//2.调用普通的public方法。
Method dance1 = uclass.getMethod("dance1",String.class);
dance1.invoke(o,"实参");
//3.调用私有方法
Method dance2 = uclass.getDeclaredMethod("dance2", String.class);
dance2.setAccessible(true);
dance2.invoke(o,"实参");
//4.调用静态方法
Method dance3 = uclass.getDeclaredMethod("dance3", String.class);
dance3.setAccessible(true);
dance3.invoke(null,"形参");
}
52. JDBC和连接池
JDBC为访问不同的数据库提供的同一的接口,为了使使用者屏蔽细节问题。
使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-afVgCsfe-1650011950876)(./img/jdbc图解.png)]
- JDBC使java提供的一套用于操作数据库接口API,java程序员只需要面向这套接口变成即可,不同的数据库厂商,需要针对这套接口们提供不同实现。
JDBC程序编写步骤
- 前置工作:
- 将mysql.jar 拷贝到该目录下,点击add to project 加入到项目中
- 注册驱动 – 加载Driver类
- Driver driver = new Driver();
- 获取连接 – 得到Connection
- String url = “jdbc:mysql://localhost:3306/db01”; 设置连接了协议,连接地址,连接数据库
- Properties properties = new Properties();
- properties.setProperty(“user”,“root”); 设置用户名
- properties.setProperty(“password”,“fsc”); 设置密码
- Connection connect = driver.connect(url, properties); //建立连接
- 执行增删改查 – 发送sql给mysql执行
- String sql = “sql语句”; //设置要执行的sql语句
- Statement statement = connect.createStatement(); //新建执行sql语句的statement对象
- int i = statement.executeUpdate(sql); //执行sql语句得到返回值。如果返回值大于0则是影响的行数
- 释放资源 – 关闭相关连接
- statement.close(); //释放操作对象连接
- connect.close(); //释放数据库连接
public static void main(String[] args) throws SQLException {
//前置工作
//将mysql.jar 拷贝到该目录下,点解add to project 加入到项目中
//1.注册驱动
Driver driver = new Driver();
//2.得到连接
/*
* 1. jdbc:mysql:// 规定好的协议,通过jdbc的方式连接mysql
* 2. localhost 主机,数据库所在的IP地址
* 3. 3306 数据库所占用的端口
* 4. db01 数据库名字
* 5. musql的连接本质就是socket连接
* */
String url = "jdbc:mysql://localhost:3306/db01";
Properties properties = new Properties();
//将用户名和密码放入properties中
properties.setProperty("user","root");
properties.setProperty("password","fsc");
//连接指定数据库
Connection connect = driver.connect(url, properties);
//3.执行增删改查
String sql = "insert into lzy values(4,'豆包',590,'rz')";
Statement statement = connect.createStatement();
int i = statement.executeUpdate(sql);//返回的是操作影响的行数
System.out.println(i >0?"成功":"失败");
//4.释放资源
statement.close();
connect.close();
}
连接数据库:
使用DriverManager获得与数据库的连接对象
- getConnection(url,name,password); //输入数据库的url,用户名,密码,获得连接对象。
使用Connection对象获得对应数据库的执行对象
setAutoCommit(false) //关闭事务的自动提交
commit() //手动提交事务
rollback() //手动回滚事务
createrStatement() //创建Statement对象
preparedStement(sql) //创建预处理对象
方式一
使用Driver对象连接到数据库
public void connect01() throws SQLException { Driver driver = new Driver(); //创建driver对象 String url = "jdbc:mysql://localhost:3306/db01"; //将 用户名和密码放入到Properties 对象 Properties properties = new Properties(); //说明 user 和 password 是规定好,后面的值根据实际情况写 properties.setProperty("user", "root");// 用户 properties.setProperty("password", "fsc"); //密码 Connection connect = driver.connect(url, properties); System.out.println(connect); }
方式二:
使用反射机制动态加载Driver类,使用反射加载Driver类 , 动态加载,更加的灵活,减少依赖性
public void connect02() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException { //使用反射加载Driver类 , 动态加载,更加的灵活,减少依赖性 Class<?> aClass = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver)aClass.newInstance(); String url = "jdbc:mysql://localhost:3306/db01"; //将 用户名和密码放入到Properties 对象 Properties properties = new Properties(); //说明 user 和 password 是规定好,后面的值根据实际情况写 properties.setProperty("user", "root");// 用户 properties.setProperty("password", "fsc"); //密码 Connection connect = driver.connect(url, properties); System.out.println("方式2=" + connect); }
方式三
使用DriverManager 替代 driver 进行统一管理
public void connect03() throws IllegalAccessException, InstantiationException, ClassNotFoundException, SQLException { //使用反射加载Driver Class<?> aClass = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver) aClass.newInstance(); //创建url 和 user 和 password String url = "jdbc:mysql://localhost:3306/db01"; String user = "root"; String password = "fsc"; DriverManager.registerDriver(driver);//注册Driver驱动 Connection connection = DriverManager.getConnection(url, user, password); System.out.println("第三种方式=" + connection); }
方式四
使用Class.forName 自动完成注册驱动,简化代码
当动态加载Driver类时,会执行静态代码块:
static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); }
public void connect04() throws ClassNotFoundException, SQLException { //使用反射加载了 Driver类 //在加载 Driver类时,完成注册 Class.forName("com.mysql.jdbc.Driver"); //创建url 和 user 和 password String url = "jdbc:mysql://localhost:3306/db01"; String user = "root"; String password = "fsc"; Connection connection = DriverManager.getConnection(url, user, password); System.out.println("第4种方式~ " + connection); }
mysql驱动在5.1.6之后无需 Class.forName(“com.mysql.jdbc.Driver”);从jdk1.5
之后使用了jdbc4
,不需要显示调用 Class.forName()注册驱动,而是自动调用驱动jar包下META-INF\services\java.sql.Driver 文本中的类名称去注册。
方式五
在方式4的基础上改进,增加配置文件,让连接mysql更加灵活
public void connect05() throws IOException, ClassNotFoundException, SQLException { //通过Properties对象获取配置文件的信息 Properties properties = new Properties(); properties.load(new FileInputStream("src\\mysql.properties")); //获取相关的值 String user = properties.getProperty("user"); String password = properties.getProperty("password"); String driver = properties.getProperty("driver"); String url = properties.getProperty("url"); Class.forName(driver);//建议写上 Connection connection = DriverManager.getConnection(url, user, password); System.out.println("方式5 " + connection); }
ResultSet 结果集
表示数据库结果集的数据表,通常通过执行数据看查询语句生成。
ResultSet对象保持一个光标指向当前的数据行,最初光标位于第一行之前,next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环遍历结果集。
默认ResultSet对象不可以更新,并且只有一个向前移动的光标,只能从第一行到最后一行迭代一次,可以生成更新不敏感的结果集。
- ResultSet对象.next() //使光标向下移动一行,如果没有下一行返回false
- ResultSet对象.previous() //使光标向上移动一行,如果没有下一行返回false
- ResultSet对象.getXxx(索引 || 列名) //获得当前行指定索引或者列名的值
- ResultSet对象.getObject(索引 || 列名)
public static void main(String[] args) throws SQLException, ClassNotFoundException { String url = "jdbc:mysql://localhost:3306/db01"; String user = "root"; String password = "fsc"; Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection(url, user, password); System.out.println(connection); Statement statement = connection.createStatement(); String sql = "select * from lzy"; //ResultSet 是接口 ResultSet resultSet = statement.executeQuery(sql); System.out.println(resultSet); while(resultSet.next()){ //resultSet.next()如果下一行没有数据则返回false int id = resultSet.getInt(1); String name = resultSet.getString(2); int tal = resultSet.getInt(3); String address = resultSet.getString(4); System.out.println(id + "\t" + name + "\t" + tal + "\t" + address); } resultSet.close(); statement.close(); connection.close(); }
Statement
Statement对象,用于执行静态sql语句并且返回生成的结果对象。
在连接建立后,需要对数据库进行访问,执行命名或者是sql语句,可以通过:
- Statement [存在sql注入]
- PreparedStatement [预处理]
- CallableStatement [存储过程]
Statement对象执行sql语句存在sql注入的风险。
sql注入是利用某些系统没有对用户输入的数据进行充分的检查,而是用户输入数据种植呼入非法的sql语句段或者命令,恶意攻击数据库。
//输入的用户名: 1' or //输入密码: or '1'='1 select * from user where name = '用户名' and pwd = '密码' //sql注入后 '1' or ' and pwd = 'or '1'='1' //肯定成立
要防止sql主语只需要使用PreparedStatement(从Startement扩展而来)取代Statement就要可以。
- executeUpdate(sql) //执行修改,添加,删除语句,返回影响的行数
- executeQuery(sql) //执行查询语句,返回影响的行数。
- execute(sql) //执行任意sql,返回布尔值
PreparedStatement
PreparedStatement执行的sql语句中的参数使用问号(?)来表示,调用PreparedStatement对象的setXXX()方法来设置这些参数。 setXXX()方法有两个参数,第一个参数要设置sql语句中的参数的索引(从1开始),第二个是设置sql语句中参数的值。
String sql = "select count(*) from 表名 where username=? and password =?"
调用executeQuery(),返回ResultSet结果集对象(查询)。
调用executeUpdate(),执行更新,包括增加,删除,修改,返回一个int类型,如果大于0返回影响的行数。
预处理:
- 不再使用 + 拼接sql语句,减少语法错误。
- 有效的解决了sql注入问题。
- 大大减少了便于次数,效率提高。
主要方法:
- executeUpdate(sql) //执行修改,添加,删除语句,返回影响的行数
- executeQuery(sql) //执行查询语句,返回影响的行数。
- execute(sql) //执行任意sql,返回布尔值
- setXxx(占位符索引,占位符的值) //设置对应占位符的值,解决sql注入
- setObject(占位符索引,占位符的值)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-odqTmijK-1650011950877)(./img/prepared.png)]
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/db01";
String user = "root";
String password = "fsc";
Connection connection = DriverManager.getConnection(url,user,password);
//sql语句的?相当于占位符
String sql = "select * from lzy where name=? and tal=?";
//preparedStatement是实现了PreparedStatement接口的实现类的对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"lzy");
preparedStatement.setInt(2,12121);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
int tal = resultSet.getInt(3);
String address = resultSet.getString(4);
System.out.println(id + "\t" + name + "\t" + tal + "\t" + address);
}
resultSet.close();
preparedStatement.close();
connection.close();
}
jdbcAPI总结
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MMkjFpx4-1650011950878)(./img/jdbcAPI.png)]
JDBCUtile
package com.doubao.jdbcUtils;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/**
* 这是一个工具类,完成mysql数据库的的链接和关闭资源
*/
public class JDBCUtils {
//连接数据库的使用
private static String user;
private static String password;
private static String url;
private static String driver;
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\musqljdbc.properties"));
url = properties.getProperty("url");
password = properties.getProperty("password");
user = properties.getProperty("name");
driver = properties.getProperty("driver");
Class.forName(driver);
} catch (IOException | ClassNotFoundException e) {
//将编译异常转换为运行异常。
//调用者可以选择捕获异常也可以选择默认处理异常
throw new RuntimeException(e);
}
}
//获得数据库连接
public static Connection getConnection(){
try {
return DriverManager.getConnection(url,user,password);
} catch (SQLException e) {
//将编译异常转换为运行异常
throw new RuntimeException(e);
}
}
/**
*
* @param set 结果集
* @param statement statement或者statementMarager
* @param connection 连接对象
* 如果需要关闭资源就传入响应对象,否则传入null
*/
public static void close(ResultSet set, Statement statement,Connection connection) {
try {
if(set !=null ){
set.close();
}
if(statement != null){
statement.close();
}
if(connection != null){
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
----------------------properties文件---------------------
url=jdbc:mysql://localhost:3306/db01
name=root
password=fsc
driver=com.mysql.jdbc.Driver
事务
基本介绍
- JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务,每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
- JDBC程序为了让多个sql语句作为一个整体执行,需要使用事务。
- 调用Connection的setAutoCommit(false)可以取消自动提交事务。
- 在所有的sql语句都成功执行后,调用commit();方法提交事务。
- 在其中某个操作失败或出现异常时,调用rollback()方法回滚事务。
public static void main(String[] args) throws SQLException {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCUtils.getConnection();
//去除连接的自动提交事务
connection.setAutoCommit(false);
String sql = "select * from lzy";
preparedStatement =connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
int tal = resultSet.getInt(3);
String like = resultSet.getString(4);
System.out.println(id + "\t" + name + "\t" + tal + "\t" + like);
}
//如果没有出现异常手动提交事务
connection.commit();
} catch (SQLException throwables) {
//如果出现异常回滚事务
connection.rollback();
}
JDBCUtils.close(resultSet,preparedStatement,connection);
}
批处理
- 当需要成批插入或者更新记录时,可以采用java的批量更新机制,这一机制允许多条语句一次性提交给数据库进行批量处理,通常情况下,批量处理比单独提交处理更有效率。(批量处理可以减少发送sql语句的网络开销)
- JDBC的批处理语句包括下面的方法:
- addBatch(): 添加需要处理的批处理的sql语句或者参数。
- executeBatch():执行需要批量处理的sql语句或者参数。
- clearBatch(): 清空批处理包的语句。
- JDBC连接mysql时,如果要使用批处理功能,在url中添加批处理功能参数
?rewriteBatchedStatements=true
- 批处理往往和PreparedStatement一起搭配使用,可以减少编译次数,减少运行次数,运行效率大大提升。
public static void main(String[] args) {
try {
Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin1 values(?,?)";
PreparedStatement prepareStatement = connection.prepareStatement(sql);
System.out.println("开始执行");
for(int i = 0;i<5000;i++){
prepareStatement.setInt(1,i);
prepareStatement.setString(2,"lzy");
prepareStatement.addBatch();
//如果加入例如1000跳数据就开始执行
if((i + 1) %1000 == 0){
prepareStatement.executeBatch();
prepareStatement.clearBatch();
}
}
JDBCUtils.close(null,prepareStatement,connection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
--------------------------------------------------
url=jdbc:mysql://localhost:3306/db01?rewriteBatchedStatements=true
name=root
password=fsc
driver=com.mysql.jdbc.Driver
数据库连接池
- 传统的JDBC数据库了使用DriverManager来获取,每次向数据库建立连接的时候都将Connextion加载到内存中,再验证ip地址,用户名和密码,需要数据库连接的时候,就向数据库请求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。
- 每一次数据库连接,使用完成后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄露,最终将导致重启数据库。
- 传统获取连接的方式,不能控制创建的来连接数量,如果连接过多,也可能倒是内存泄露,mysql崩溃。
- 解决传统开发中的数据库来连接问题,可以采用数据库连接池技术(connextion pool)
数据库连接池机制:
- 预先在缓冲池中放入一定数量的来连接,当需要建立数据库连接时,只需要从”缓冲池“中取出来一个,使用完毕之后再放回去。
- 数据库连接池负责分配,管理和释放数据库来凝结,它允许应该程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 当应用程序向连接池请求的连接数超过最大的连接数量时,和这些请求被加到等待队列中。
- 从连接池取出连接
- 使用连接操作mysql数据库
- 连接放回连接池(该连接可以重复使用)
- 在数据库连接池技术中close方法不是断掉连接,只是将连接放回连接池
数据库连接池种类
- jdbc的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供。
- C3P0数据库连接池,速度相对较慢,稳定性好。
- DBCP数据库连接池,速度相对C3P0较快,但是不稳定。
- Proxool数据库连接池,有监控连接池状态的功能,稳定性较C3P0差一些。
- BoneCP 数据库连接池,速度快。
- Druid(德鲁伊)是阿里提供的数据库连接池集成C3P0,DBCP,Proxool优点于一身的数据库连接池。
c3p0
//需要引进jar包
public static void main(String[] args) throws IOException, PropertyVetoException, SQLException {
//1. 创建一个数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//2. 通过配置文件mysql.properties 获取相关连接的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\musqljdbc.properties"));
//读取相关的属性值
String user = properties.getPoperty("name");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
//给数据源 comboPooledDataSource 设置相关的参数
//注意:连接管理是由 comboPooledDataSource 来管理
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
//设置初始化连接数
comboPooledDataSource.setInitialPoolSize(10);
//最大连接数
comboPooledDataSource.setMaxPoolSize(50);
//测试连接池的效率, 测试对mysql 5000次操作
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = comboPooledDataSource.getConnection(); //这个方法就是从 DataSource 接口实现的
//System.out.println("连接OK");
connection.close();
}
long end = System.currentTimeMillis();
//c3p0 5000连接mysql 耗时=391
System.out.println("c3p0 5000连接mysql 耗时=" + (end - start));
}
//将c3p0提供的c3p0.config.xml
//该文件指定了连接数据库和连接池的相关参数
//方式1: 相关参数,在程序中指定user, url , password等
public static void main(String[] args) throws SQLException {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("hello");
Connection connection = comboPooledDataSource.getConnection();
System.out.println(connection);
connection.close();
}
---------------------------------c3p0.config.xml
<c3p0-config>
<!-- 数据源的名称-->
<named-config name="hello">
<!-- 驱动类 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- url-->
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/db01</property>
<!-- 用户名 -->
<property name="user">root</property>
<!-- 密码 -->
<property name="password">fsc</property>
<!-- 每次增长的连接数-->
<property name="acquireIncrement">5</property>
<!-- 初始的连接数 -->
<property name="initialPoolSize">10</property>
<!-- 最小连接数 -->
<property name="minPoolSize">5</property>
<!-- 最大连接数 -->
<property name="maxPoolSize">10</property>
<!-- 可连接的最多的命令对象数 -->
<property name="maxStatements">5</property>
<!-- 每个连接对象可连接的最多的命令对象数 -->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
Druid(德鲁伊)
public static void main(String[] args) throws Exception {
//加入druid的jar包,加入项目
//加入配置文件
//创建Propertise对象
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
//创建一个指定参数数据源
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//获取数据源连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
------------------------druid.properties
#key=value
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db01?rewriteBatchedStatements=true
#url=jdbc:mysql://localhost:3306/db01
username=root
password=fsc
#initial connection Size
initialSize=10
#min idle connecton size
minIdle=5
#max active connection size
maxActive=20
#max wait time (5000 mil seconds)
maxWait=5000
JDBCUtilsByDruid
package com.doubao.jdbcUtils;
//使用druid连接池技术
//需要添加druid配置文件,引入druid的jar包
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JDBCUtilsByDruid {
private static DataSource ds;
//静态代码块
static{
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//在数据库连接池技术中close方法不是断掉连接,只是将连接放回连接池
/**
*
* @param set 结果集
* @param statement statement或者statementMarager
* @param connection 连接对象
* 如果需要关闭资源就传入响应对象,否则传入null
* 并不是关闭数据库连接而是将连接放回连接池
*/
public static void close(ResultSet set, Statement statement, Connection connection) {
try {
if (set != null) {
set.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
----------------------------------druid.properties配置文件
#key=value
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db01?rewriteBatchedStatements=true
#url=jdbc:mysql://localhost:3306/db01
username=root
password=fsc
#initial connection Size
initialSize=10
#min idle connecton size
minIdle=5
#max active connection size
maxActive=20
#max wait time (5000 mil seconds)
maxWait=5000
Apache — DBUtils
基本介绍:
commons-dbutils 是Apache组织提供的一个开源的JDBC工具类库,他是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量。
DbUtils
- QueryRunner类:该类封装了SQL执行,是线程安全的,可以实现增,删,改,查,批处理等操作。
- ResultSetHandler接口:该接口用于处理Java.sql.ResultSet,将数据按照要求转换为另一种形式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1WzRYfbg-1650011950879)(./img/handler类.png)]
得到指定数据库的连接
Connection connection = JDBCUtilsByDruid.getConnection();
引入DBUtils相关的jar包,加入到本项目中.
创建对应表的javaBean类。
创建QueryRunner 对象:
QueryRunner queryRunner = new QueryRunner();
执行相关的方法,返回ArrayList结果集
QueryRunner对象.query(连接对象,sql语句,new BeanListHandler<>(javaBean对象.class),sql语句中的占位符所代表的值)
String sql = "select * from lzy";
List<lzyBean> list = queryRunner.query(connection, sql, new BeanListHandler<>(lzyBean.class));
关闭连接
JDBCUtilsByDruid.close(null,null,connection);
查询多列对象
query(连接对象,sql,new BeanListHandler<>(javaBean对象.class),1)
new BeanListHandler<>(javaBean对象.class)查询多行多列的返回值,返回的是一个javaBean对象组成的ArrayList数组。
- query()得到resultset,将resultset中的数据添加到相应的javaBean中,再将javaBean对象放入ArrayList数组中。
- new BeanListHandler<>(javaBean对象.class),底层使用反射机制,获取javaBean中的属性,将然后进行封装。
- 传入的sql语句中可以使用
?
当作占位符,再query()方法最后传入相应的值,可以传递多个值。sql语句查询到的会传值到javaBean对象,没有查询赋值为null。- 底层中得到的Resultset和PreparedStatement的对象会在query中关闭。
public static void main(String[] args) throws SQLException {
//1.得到连接
Connection connection = JDBCUtilsByDruid.getConnection();
//2.引入DBUtils相关的jar包,加入到本项目中
//3.创建QueryRunner 对象
QueryRunner queryRunner = new QueryRunner();
//4. 执行相关的方法,返回ArrayList结果集
String sql = "select * from lzy";
// List<lzyBean> list = queryRunner.query(连接对象, sql语句, new BeanListHandler<>(javaBean类.class),占位符对应的值);
List<lzyBean> list = queryRunner.query(connection, sql, new BeanListHandler<>(lzyBean.class));
for(lzyBean lzy : list){
System.out.println(lzy);
}
//关闭连接
JDBCUtilsByDruid.close(null,null,connection);
}
查询单行对象
query(连接对象,sql,new BeanHandler<>(javaBean对象.class),1)
使用new BeanHandler<>(javaBean对象.class)查询到单行返回值,只返回一个对应的javaBean对象。
如果没有查询到返回null。
public static void main(String[] args) throws Exception{
//获得数据库连接
Connection connection = JDBCUtilsByDruid.getConnection();
//创建QueryRunner对象
QueryRunner queryRunner = new QueryRunner();
String sql = "select * from lzy where id=?";
//执行单列查询,返回对应列的javaBean类
lzyBean lzyBean = queryRunner.query(connection, sql, new BeanHandler<>(lzyBean.class), 1);
System.out.println(lzyBean);
JDBCUtilsByDruid.close(null,null,connection);
}
查询单行单列对象
query(连接对象,sql,new ScalarHandler(),1)
new ScalarHandler()查询单行单列的一个返回值,返回的是一个object对象。
public static void main(String[] args) throws Exception{
//获得数据库连接
Connection connection = JDBCUtilsByDruid.getConnection();
//创建QueryRunner对象
QueryRunner queryRunner = new QueryRunner();
String sql = "select name from lzy where id=?";
//执行单行单列查询,返回对应列的javaBean类
//返回的是一个值
Object query = queryRunner.query(connection, sql, new ScalarHandler(), 1);
System.out.println(query);
JDBCUtilsByDruid.close(null,null,connection);
}
dml操作
QueryRunner对象.update(connection,sql语句,sql语句中的占位符代表值……)用来执行dml操作,返回的是影响的行数。
public static void main(String[] args) throws Exception {
//获得数据库连接
Connection connection = JDBCUtilsByDruid.getConnection();
//创建QueryRunner对象
QueryRunner queryRunner = new QueryRunner();
String sql = "update lzy set name =? where id = ?";
//执行修改操作,返回影响到的行数
int affectedRow = queryRunner.update(connection,sql,"name",1);
JDBCUtilsByDruid.close(null, null, connection);
}
DAO 数据访问对象(data access object)
apache-dbutils + druid 简化了JDBC开发,但是还有不足:
- SQL语句是固定的,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查。
- 对于select操作,如果有返回值,返回类型不能固定,需要使用泛型。
- 将来的表很多业务需求复杂,不看可能只靠一个Java类来实现。
通用类被称为BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作。
在BasicDao的基础上,实现一张表对应一个Dao,更好的完成功能。
批处理
- 当需要成批插入或者更新记录时,可以采用java的批量更新机制,这一机制允许多条语句一次性提交给数据库进行批量处理,通常情况下,批量处理比单独提交处理更有效率。(批量处理可以减少发送sql语句的网络开销)
- JDBC的批处理语句包括下面的方法:
- addBatch(): 添加需要处理的批处理的sql语句或者参数。
- executeBatch():执行需要批量处理的sql语句或者参数。
- clearBatch(): 清空批处理包的语句。
- JDBC连接mysql时,如果要使用批处理功能,在url中添加批处理功能参数
?rewriteBatchedStatements=true
- 批处理往往和PreparedStatement一起搭配使用,可以减少编译次数,减少运行次数,运行效率大大提升。
public static void main(String[] args) {
try {
Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin1 values(?,?)";
PreparedStatement prepareStatement = connection.prepareStatement(sql);
System.out.println("开始执行");
for(int i = 0;i<5000;i++){
prepareStatement.setInt(1,i);
prepareStatement.setString(2,"lzy");
prepareStatement.addBatch();
//如果加入例如1000跳数据就开始执行
if((i + 1) %1000 == 0){
prepareStatement.executeBatch();
prepareStatement.clearBatch();
}
}
JDBCUtils.close(null,prepareStatement,connection);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
--------------------------------------------------
url=jdbc:mysql://localhost:3306/db01?rewriteBatchedStatements=true
name=root
password=fsc
driver=com.mysql.jdbc.Driver
数据库连接池
- 传统的JDBC数据库了使用DriverManager来获取,每次向数据库建立连接的时候都将Connextion加载到内存中,再验证ip地址,用户名和密码,需要数据库连接的时候,就向数据库请求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。
- 每一次数据库连接,使用完成后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄露,最终将导致重启数据库。
- 传统获取连接的方式,不能控制创建的来连接数量,如果连接过多,也可能倒是内存泄露,mysql崩溃。
- 解决传统开发中的数据库来连接问题,可以采用数据库连接池技术(connextion pool)
数据库连接池机制:
- 预先在缓冲池中放入一定数量的来连接,当需要建立数据库连接时,只需要从”缓冲池“中取出来一个,使用完毕之后再放回去。
- 数据库连接池负责分配,管理和释放数据库来凝结,它允许应该程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 当应用程序向连接池请求的连接数超过最大的连接数量时,和这些请求被加到等待队列中。
- 从连接池取出连接
- 使用连接操作mysql数据库
- 连接放回连接池(该连接可以重复使用)
- 在数据库连接池技术中close方法不是断掉连接,只是将连接放回连接池
数据库连接池种类
- jdbc的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供。
- C3P0数据库连接池,速度相对较慢,稳定性好。
- DBCP数据库连接池,速度相对C3P0较快,但是不稳定。
- Proxool数据库连接池,有监控连接池状态的功能,稳定性较C3P0差一些。
- BoneCP 数据库连接池,速度快。
- Druid(德鲁伊)是阿里提供的数据库连接池集成C3P0,DBCP,Proxool优点于一身的数据库连接池。
c3p0
//需要引进jar包
public static void main(String[] args) throws IOException, PropertyVetoException, SQLException {
//1. 创建一个数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//2. 通过配置文件mysql.properties 获取相关连接的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\musqljdbc.properties"));
//读取相关的属性值
String user = properties.getPoperty("name");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
//给数据源 comboPooledDataSource 设置相关的参数
//注意:连接管理是由 comboPooledDataSource 来管理
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
//设置初始化连接数
comboPooledDataSource.setInitialPoolSize(10);
//最大连接数
comboPooledDataSource.setMaxPoolSize(50);
//测试连接池的效率, 测试对mysql 5000次操作
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = comboPooledDataSource.getConnection(); //这个方法就是从 DataSource 接口实现的
//System.out.println("连接OK");
connection.close();
}
long end = System.currentTimeMillis();
//c3p0 5000连接mysql 耗时=391
System.out.println("c3p0 5000连接mysql 耗时=" + (end - start));
}
//将c3p0提供的c3p0.config.xml
//该文件指定了连接数据库和连接池的相关参数
//方式1: 相关参数,在程序中指定user, url , password等
public static void main(String[] args) throws SQLException {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("hello");
Connection connection = comboPooledDataSource.getConnection();
System.out.println(connection);
connection.close();
}
---------------------------------c3p0.config.xml
<c3p0-config>
<!-- 数据源的名称-->
<named-config name="hello">
<!-- 驱动类 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- url-->
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/db01</property>
<!-- 用户名 -->
<property name="user">root</property>
<!-- 密码 -->
<property name="password">fsc</property>
<!-- 每次增长的连接数-->
<property name="acquireIncrement">5</property>
<!-- 初始的连接数 -->
<property name="initialPoolSize">10</property>
<!-- 最小连接数 -->
<property name="minPoolSize">5</property>
<!-- 最大连接数 -->
<property name="maxPoolSize">10</property>
<!-- 可连接的最多的命令对象数 -->
<property name="maxStatements">5</property>
<!-- 每个连接对象可连接的最多的命令对象数 -->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
Druid(德鲁伊)
public static void main(String[] args) throws Exception {
//加入druid的jar包,加入项目
//加入配置文件
//创建Propertise对象
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
//创建一个指定参数数据源
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//获取数据源连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
------------------------druid.properties
#key=value
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db01?rewriteBatchedStatements=true
#url=jdbc:mysql://localhost:3306/db01
username=root
password=fsc
#initial connection Size
initialSize=10
#min idle connecton size
minIdle=5
#max active connection size
maxActive=20
#max wait time (5000 mil seconds)
maxWait=5000
JDBCUtilsByDruid
package com.doubao.jdbcUtils;
//使用druid连接池技术
//需要添加druid配置文件,引入druid的jar包
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JDBCUtilsByDruid {
private static DataSource ds;
//静态代码块
static{
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//在数据库连接池技术中close方法不是断掉连接,只是将连接放回连接池
/**
*
* @param set 结果集
* @param statement statement或者statementMarager
* @param connection 连接对象
* 如果需要关闭资源就传入响应对象,否则传入null
* 并不是关闭数据库连接而是将连接放回连接池
*/
public static void close(ResultSet set, Statement statement, Connection connection) {
try {
if (set != null) {
set.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
----------------------------------druid.properties配置文件
#key=value
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db01?rewriteBatchedStatements=true
#url=jdbc:mysql://localhost:3306/db01
username=root
password=fsc
#initial connection Size
initialSize=10
#min idle connecton size
minIdle=5
#max active connection size
maxActive=20
#max wait time (5000 mil seconds)
maxWait=5000
Apache — DBUtils
基本介绍:
commons-dbutils 是Apache组织提供的一个开源的JDBC工具类库,他是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量。
DbUtils
- QueryRunner类:该类封装了SQL执行,是线程安全的,可以实现增,删,改,查,批处理等操作。
- ResultSetHandler接口:该接口用于处理Java.sql.ResultSet,将数据按照要求转换为另一种形式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fHGbSC91-1650011950880)(./img/handler类.png)]
得到指定数据库的连接
Connection connection = JDBCUtilsByDruid.getConnection();
引入DBUtils相关的jar包,加入到本项目中.
创建对应表的javaBean类。
创建QueryRunner 对象:
QueryRunner queryRunner = new QueryRunner();
执行相关的方法,返回ArrayList结果集
QueryRunner对象.query(连接对象,sql语句,new BeanListHandler<>(javaBean对象.class),sql语句中的占位符所代表的值)
String sql = "select * from lzy";
List<lzyBean> list = queryRunner.query(connection, sql, new BeanListHandler<>(lzyBean.class));
关闭连接
JDBCUtilsByDruid.close(null,null,connection);
查询多列对象
query(连接对象,sql,new BeanListHandler<>(javaBean对象.class),1)
new BeanListHandler<>(javaBean对象.class)查询多行多列的返回值,返回的是一个javaBean对象组成的ArrayList数组。
- query()得到resultset,将resultset中的数据添加到相应的javaBean中,再将javaBean对象放入ArrayList数组中。
- new BeanListHandler<>(javaBean对象.class),底层使用反射机制,获取javaBean中的属性,将然后进行封装。
- 传入的sql语句中可以使用
?
当作占位符,再query()方法最后传入相应的值,可以传递多个值。sql语句查询到的会传值到javaBean对象,没有查询赋值为null。- 底层中得到的Resultset和PreparedStatement的对象会在query中关闭。
public static void main(String[] args) throws SQLException {
//1.得到连接
Connection connection = JDBCUtilsByDruid.getConnection();
//2.引入DBUtils相关的jar包,加入到本项目中
//3.创建QueryRunner 对象
QueryRunner queryRunner = new QueryRunner();
//4. 执行相关的方法,返回ArrayList结果集
String sql = "select * from lzy";
// List<lzyBean> list = queryRunner.query(连接对象, sql语句, new BeanListHandler<>(javaBean类.class),占位符对应的值);
List<lzyBean> list = queryRunner.query(connection, sql, new BeanListHandler<>(lzyBean.class));
for(lzyBean lzy : list){
System.out.println(lzy);
}
//关闭连接
JDBCUtilsByDruid.close(null,null,connection);
}
查询单行对象
query(连接对象,sql,new BeanHandler<>(javaBean对象.class),1)
使用new BeanHandler<>(javaBean对象.class)查询到单行返回值,只返回一个对应的javaBean对象。
如果没有查询到返回null。
public static void main(String[] args) throws Exception{
//获得数据库连接
Connection connection = JDBCUtilsByDruid.getConnection();
//创建QueryRunner对象
QueryRunner queryRunner = new QueryRunner();
String sql = "select * from lzy where id=?";
//执行单列查询,返回对应列的javaBean类
lzyBean lzyBean = queryRunner.query(connection, sql, new BeanHandler<>(lzyBean.class), 1);
System.out.println(lzyBean);
JDBCUtilsByDruid.close(null,null,connection);
}
查询单行单列对象
query(连接对象,sql,new ScalarHandler(),1)
new ScalarHandler()查询单行单列的一个返回值,返回的是一个object对象。
public static void main(String[] args) throws Exception{
//获得数据库连接
Connection connection = JDBCUtilsByDruid.getConnection();
//创建QueryRunner对象
QueryRunner queryRunner = new QueryRunner();
String sql = "select name from lzy where id=?";
//执行单行单列查询,返回对应列的javaBean类
//返回的是一个值
Object query = queryRunner.query(connection, sql, new ScalarHandler(), 1);
System.out.println(query);
JDBCUtilsByDruid.close(null,null,connection);
}
dml操作
QueryRunner对象.update(connection,sql语句,sql语句中的占位符代表值……)用来执行dml操作,返回的是影响的行数。
public static void main(String[] args) throws Exception {
//获得数据库连接
Connection connection = JDBCUtilsByDruid.getConnection();
//创建QueryRunner对象
QueryRunner queryRunner = new QueryRunner();
String sql = "update lzy set name =? where id = ?";
//执行修改操作,返回影响到的行数
int affectedRow = queryRunner.update(connection,sql,"name",1);
JDBCUtilsByDruid.close(null, null, connection);
}
DAO 数据访问对象(data access object)
apache-dbutils + druid 简化了JDBC开发,但是还有不足:
- SQL语句是固定的,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查。
- 对于select操作,如果有返回值,返回类型不能固定,需要使用泛型。
- 将来的表很多业务需求复杂,不看可能只靠一个Java类来实现。
通用类被称为BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作。
在BasicDao的基础上,实现一张表对应一个Dao,更好的完成功能。
二. 模式
单例模式
单例设计模式,就是采取一定的方法保证在整个软件系统中,对某个类只能 存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
单例模式:
- 饿汉式
- 饿汉式可能创建对象却不使用。可能造成资源浪费
构造器私有化 ---- 防止new创建对象
类的内部创建对象
向外暴露一个静态的公共方法,返回对象
代码实现
- 懒汉式
- 构造器私有化 -----防止new创建对象
- 类的内部创建对象
- 向外暴露一个静态的公共方法
- 代码实现
总结:
二者最主要的区别就是创建对象的时机不同,饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建。
饿汉式不存在线程安全问题,懒汉式存在线程安全问题。
懒汉式存在浪费资源的可能。
模板设计模式
使用抽象类:
abstract class Work{ public void do(){ job(); 其他代码/共用代码; } abstract void job(); }