java
一. 关键字,标识符,常量
1. 学习了计算机基础知识(软件/硬件)
2. java语言的特点:
面向对象语言的一种
跨平台
开源
3. 了解了java平台版本以及历史.
4. 开发一个Java应用程序
jre:Java程序运行环境,也包括jvm以及java所需的核心类库.
jvm:java虚拟机
jdk:java开发工具包,包含jre以及开发工具的包.
三者之间的关系: jvm<jre<jdk
5. 了解了什么是关键字:
关键字是指被Java语言赋予特定含义的词.
class :表示创建一个类(Java语言最基本的单元)
public:权限修饰符的一种
static:静态修饰符号
void:和java的方法有关
6. 什么是标识符:
针对包,类,接口,方法,变量,常量等起名字的字符序列
标识符的组成:
26个英文字母(大小写)
数字字符
$符号
_下划线符号
.严格区分大小写的
标识符的注意事项:
1.数字字符不能作为标识符的开头位置
2.不能是Java中的关键字
3.Java严格区分大小写,我们在起名字的时候:见名知意
4.满足条件:满足组成的一种即可!不能是非法字符
7. 包,类,接口,方法,变量,常量,书写规则
包: (目录/文件夹) 都是字母小写 或者可以有"_"
类: 如果是一个单词 首字母大写 其余小写 例: class Student{}
如果是多个单词组成 每个单词首字母都大写 其余小写 例: class DataTypeDemo{}
接口: 与 类 相同
方法: 如果是一个单词 字母全部小写 例: main ()
int price = 10;
如果是多个单词 第一个单词全部小写 从第二个单词开始每个单词的首字母大写其余小写
例: checkUserName
int appleprice = 10;
变量: 与 方法 相同
常量: 如果是单个单词 字母全部大写 例:HELLO
如果是多个单词 每个单词字母都大写 单词与单词之间用"_"隔开
8. 什么是常量:
在程序执行过,其值不发生改变的量 分为: 字面值常量 自定义常量
1.字面值常量:
字符串常量:使用双引号包裹起来的内容:称为"字符串常量" "HelloWorld"
字符常量: 使用单引号包裹起来的内容 'a' 'A'
整数常量: 100 56
小数常量: 2.15 0.64
布尔常量: true false
空常量: null
2.自定义常量(面向对象 关键字 final)
进制也属于常量
二. 变量,运算符
1. 了解了更简单的方法: 8421码 可以快速的进行二进制到十进制,十进制到二进制的转换.
2. 有符号位的数据表示法
计算机底层对数据的计算: 是通过补码进行运算的
正数的 原码 反码 补码 相同
负数的最高符号位为1
负数的反码是在原码的基础上,最高符号位不变,数值位按位取反.
负数的补码是在反码的基础上,最高符号位不变,数值位末尾+1.
3. 变量 在程序执行过程中,其值发生改变的量
变量的三要素
数据类型
变量名: 满足标识符规则
初始化值: 满足的范围即可
格式: 数据类型 变量名 = 初始化值
4. 数据类型 :在Java中,数据类型分为两大类型:
A)基本数据类型:四类八种 (研究的都是基本类型)
整数类型:默认int
字节类型 byte 占1个字节(8个比特位) 取值范围:-128~127
短整型 short 占2个字节
整数默认类型 int 整数默认类型 占4个字节
长整型 long 占8个字节
注意事项:必须在long的初始化值的末尾加L或者l
浮点类型:默认类型double
单精度 float 单精度 占4个字节
注意事项:
float f = 12.56F ; //后面加上F或者f
双进度 double 双进度 占8个字节
字符类型 char 占2个字节
初始化值:单引号括起来的单个内容
布尔类型 boolean 不会参与类型转换:仅仅表示真,假 占1个字节
要么是true/要么false
int a = 10 ;
int b =20 ;
比较a与b是否相等:获取到boolean类型的结果
B)引用数据类型:后面说
数组,类,接口
5. 定义变量的注意事项:
在java语言(强类型语言:语法结构很严谨)中,同一个变量不能重复定义
(javascript语言:弱类型语言:可以去重复定义变量)
一行就写一个变量即可! 一行结束之后分号;(代码规范风格)
一行也可以定义多个变量;
变量要么直接初始化,要么先定义,但是必须在使用之前对其进行初始化
变量要进行运算,必须要确定数据类型的一致
在Java中有一个隐式类型转换: byte,short,char三者之间不进行相互转换,
参与运算, 优先转换为int类型,long,float-double类型
6. 显示转换与隐式转换
显示转换(强制转换) : 大的数据类型----->小的数据类型
格式: 目标数据类型 变量名 = (目标数据类型)初始化值; 会有损精度!
隐式转换:(隐式类型提升) byte,char,short三者之间不转换,一旦参与运算,优先提升为int...
超出了 byte类型 的范围为-128~127
先算出int 类型的二进制(4字节) 然后化为byte(1字节) 类型的二进制
最高符号为0,原码 ,反码,补码都相同
最高符号为1 数值位-1 求反码 数值位按位取反求原码
7. Java中的运算符号:
(1) 算术运算符 + - * / %
扩展算术运算符 ++ --
第一种:
++或者--单独使用
无论++或者--在数据前面还是数据后:都是对当前数据本身自增1或者自减1
第二种:
++或者--参与运算使用
如果++或者--在数据的前面:需要先进行自增1或者自减1,然后再参与运算!
如果++或者--在数据的后面:先进行运算,然后再进行自增1或者自减1
(2) 赋值运算符 =
扩展的赋值运算符:将符号右边的数据和左边的数据相加,然后再赋值给左边的这个变量
+=,-=,*=,/=,%=
+=举例:
int a = 10 ;
a += 20 ;
类似于 a = a + 20 ;
举例:
short s = 1;
1)s = s + 1 ;
2)s+=1 ;
以上代码1),2)哪一句会编译失败?为什么?哪一句编译成功?
1)编译失败
byte,short,char三者不转换,一旦参与运算,先提升为int类型,然后再参与运算!
2)编译成功:
s+= 1 ; 类似于s = s + 1 ;
扩展的赋值运算符中:
特点:隐藏了强转类型转换
等价于 s = (short)(s+1) ;
8. 关系(比较)运算符
=,<,<=,>=,>,==
无论我们的表达式是简单还是复杂的,最终比较运算符的结果不是true,就是false
注意: ==不能写成=
数学表达式:
3 <=x <=5 x>=3 && x<=5 Java语言(逻辑符号)
9. 逻辑运算符
基本的逻辑运算符号:
逻辑单与: & 并列关系(满足全部条件) 有false,则false
逻辑单或: | 或的关系:满足一个条件即可 有true,则true
逻辑异或: ^ 相同则为false,不同则为true
逻辑非: ! 非true,则false;非false则true 偶数个非是他本身
扩展的逻辑运算符:
逻辑双与: && 左边有false,右边则不执行
逻辑双或: || 左边有true,右边则不执行
10. 位运算符
基本的位运算符号:
位与: & 有0则0
位或: | 有1则1
位异或: ^ 相同则为0 不同则为1
特点: 一个数据被另一个数据位异或两次,其值是他本身!
位运算之位移符号:
<<: 左移
将数据的补码进行左移动,右边不够的补0;将最高符位丢弃掉
>>: 右移
将数据的补码进行右移动;如果最高符号位为1,则左边补1;
最高符号位为0,则左边补0;
>>>: 无符号右移
无论最高符号位是1还是0,左边始终补0
左移的特点: <<:将左边的数据乘以2的移动次幂
右移的特点: >>:将左边的数据除以2的移动次幂
11. 三元(三目)运算符
int temp = (x > y )? x: y ;
int result = (temp > z)? temp : z ;
或者
int max2 = (x>y)?((x>z)?x:z):((y>z)?y:z) ;
三. 语句
1. 键盘录入
使用步骤
(1). 导包: 在java语言中:只要不是java.lang包下的类都需要导入!
位置:在class上面
import java.util.Scanner;
(2). 固定格式: 创建键盘录入对象(文本扫描器对象)
Scanner 对象名 = new Scanner(System.in) ;
(3). 开始录入数据 :使用int类型举例
int 变量名 = 对象名.nextInt();
import java.util.Scanner;
class LiXin6{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.println("请输入第一个值");
int a = sc.nextInt();
System.out.println("请输入第二个值");
int b = sc.nextInt();
System.out.println("请输入第三个值");
int c = sc.nextInt();
int max2 = (a>b)?((a>c)?a:c):((b>c)?b:c);
System.out.println("最大值为:"+max2);
}
}
public String nextLine():正式用法:
import java.util.Scanner;
class ScannerDemo2{
public static void main(String[] args){
//创建键盘录入对象
Scanner sc = new Scanner(System.in) ;
//提示并录入数据
System.out.println("请输入一个字符串数据:") ;
String line = sc.nextLine() ;
System.out.println("您输入的字符串是:"+line) ; //"helloworld"
}
}
如果同时录入两个int类型,
同时录入两个String类型
或者 先录入String,在录入int
先录入int,在录入String:哪一个会有问题?
(4). 键盘录入的细节:
如果先录入int,再录入String会出现,会漏掉第二个数据String
原因就是 输入回车符号-->才能录入数据 ;回车符号---> (换行)
方案1:在录入字符串之前,新创建一个键盘录入对象
//先录入int,在录入String字符串
Scanner sc = new Scanner(System.in) ;
System.out.println("请输入第一个数据:") ;
int a = sc.nextInt() ;
//创建一个新的键盘录入
Scanner sc2 = new Scanner(System.in) ;
System.out.println("请输入第二个数据:") ;
String b = sc2.nextLine() ;
方案2:Scanner类提供了这个功能:
public String next():录入一个字符串 (非正式用法)
System.out.println("请输入第二个数据:") ;
String b = sc.next() ;
2. if 语句
格式一:
if(表达式){
语句;
}
格式二:
if(表达式){
语句1;
}else{
语句2;
}
if 语句的嵌套:
if(表达式1){
if(表达式2){
语句1;
}else{
语句2;
}
}else{
if(表达式3){
语句3;
}else{
语句4;
}
}
if 语句格式三 :
if(表达式1){
语句1;
}else if(表达式2){
语句2;
...
...
...
}else{
语句n;
}
3. switch语句
switch语句格式:
switch(表达式){
case 值1:
语句1;
break ;
case 值2:
语句2;
break ;
...
...
default:
语句n;
break ;
}
switch语句使用的注意事项:
(1). case语句后面的值只能是常量,不能是变量(Java是一个强类型语言的:语法结构非常严谨)
(2). 书写switch语句的时候,case语句必须存在break语句,结束switch语句的! 如果没有书写break语句,会造成"case穿透!"现象
(3). switch语句的结束条件
a)遇见break结束
b)程序默认执行末尾结束
4. for 语句 (明确循环次数使用)
for的格式:
for(初始化语句;条件表达式;控制体语句;){
循环体语句;
}
for 语句的嵌套:
for(初始化语句;条件表达式;控制提语句){
for(初始化语句;条件表达式;控制体语句){
}
}
5. while 语句 (不明确循环次数使用)
while语句的格式:
初始化语句;
while(条件表达式);
循环体语句;
控制体语句;
使用的场景不同
6. 死循环 break可跳出死循环
死循环的格式有两种:
1. for(;;){
循环体语句;
}
2. while(true){
循环体语句;
}
7. do while 循环语句
do while循环语句的格式:
do{
循环体语句;
控制体语句;
}
while(条件表达式);
8. for while 和 do while的区别
1.格式的不同
2.for循环节省内存空间:
for循环结束,变量随着被释放掉,节省内存空间;(不能访问这个变量了.)
while循环结束,依然可以访问这个变量,比较消耗内存空间...
3.dowhile:优先循环体语句,即使条件不成立,循环体至少执行一次
开发中:
优先for,其次while,再次do-while
9. 跳转控制语句有三个关键字 :
break: 结束中断,结束循环(不能单独使用) 在switch和循环中使用
continue: 继续, 在循环中使用 ,结束当前循环,立即进入下一次循环
return:很少单独使用,结合有具体返回值类型的方法使用! 结束方法的;
看程序:写结果
for(int i = 1; i <= 10; i++) { //i=1,i=2,i=3,i=4,i=5,i=6....i=9
if (i % 3 == 0) {
//在此处填写代码
}
System.out.println("java基础班");
//1)2),3),4)
}
在控制台输出2次“java基础班” break;结束中断
在控制台输出7次“java基础班” continue:结束当前循环,立即进入下一次循环
在控制台输出13次“java基础班” System.out.println("java基础班");
四. 方法
1. 方法:就是使用{}代码块包起来,并且起一个名字(见名知意)
Java中定义方法的格式:
(1).有具体返回值类型的方法的定义
固定格式
public static 返回值类型 方法名(小驼峰命名法)(参数类型1 变量名1,参数类型2 变量名2....){
int a,int b,intc
...
return 结果;
}
定义两个数据之和的功能时候,
两个明确
1)明确返回值类型:int
2)明确参数类型以及参数个数
int类型 2个参数
public static int sum(int a,int b){//形式参数
int result = a + b;//30+20
return result ;
}
}
例: class Text {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入第一个值");
int a = sc.nextInt();
System.out.println("请输入第二个值");
int b = sc.nextInt();
System.out.println("请输入第三个值");
int c = sc.nextInt();
int max = sum(a, b, c);
System.out.println(max);
}
public static int sum(int a, int b, int c) {
int max = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);
return max;
}
}
2. 方法使用中的注意事项
(1). 方法和方法是平级关系,不能进行嵌套: 在一个方法中定义另一个方法不允许
(2). 在Java中,定义方法的时候形式参数必须携带数据类型! (Java是一个强类型语言)
(3). 调用方法的时候,传递的实际参数不需要在携带数据类型了
(4). 定义方法的时候: 有{括号的地方不能有分号;
没有具体返回值的类型的方法的定义:
没有具体返回值类型的方法调用
1)单独调用: 只能单独调用
2)输出调用:
3)赋值调用:
//void v = printStar() ; //FunctionDemo.java:28: 错误: 非法的表达式开始
//输出调用
//System.out.println(printStar()) ; 不行
//单独调用
//printStar(m,n) ;
3. 方法重载 :
方法重载(overload) :方法名相同,参数列表不同,与返回值无关!
参数列表不同:
1)参数个数不同
2)参数类型不同
五. 数组
1. 数组 : 就是存储多个数据的容器,必须保证容器内数据类型的一致.
2.数组的定义格式:
动态初始化 :
给定了数组的长度,系统默认对元素进行初始化!
数据类型 数组名称[] = new 数据类型[数组长度];
数组类型 []数组名称 = new 数据类型[数组长度];
例: int[] arr ;定义一个int类型的数组arr
int[] arr = new int[3] ;
int arr[];定义了一个int类型的arr数组
int arr[] = new int[3] ;
length : 获取数组长度.
静态初始化 :
我们给定的具体的元素,数组长度由系统确定
数据类型 数组名称[] = new 数据类型{元素 1, 元素 2, ....};
数据类型 []数组名称 = new 数据类型{元素 1, 元素 2,....};
可简写为 : 数据类型 数组名称[] = {元素 1, 元素 2,.... };
数据类型 []数组名称 = {元素 1, 元素 2,....};
3. 遍历
printArray2(arr) ; //调用方法
public static void printArray2(int[] arr){
System.out.print("[") ;
//遍历数组
for(int x = 0 ; x < arr.length ; x ++){
//判断:如果当前x :取到最大索引值 arr.length-1
if(x==arr.length-1){
System.out.println(arr[x]+"]") ;
}else{
//不是最后一个索引值,中间的元素 arr[x]+", " 不换行
System.out.print(arr[x]+", ") ;
}
}
}
// 栈:存储都是局部变量(在方法定义中或者方法声明上)
// 堆:new出来的东西,创建对象 (里面存储:"成员变量")
// 方法区:有哪些方法--分别存储在哪个类中---xxx.class()
4. 数组的基本应用 :
求最值问题:
class Text {
public static void main(String[]args) {
int[] arr = {69,13,56,87,24} ;
//调用方法
int result = getMax(arr);
System.out.println("数组中的最大值是:" + result);
}
public static int getMax(int[] arr){
//假设思想:
int max = arr[0] ;
for(int x = 1 ; x < arr.length ; x ++){
//判断
if(arr[x] > max){
max = arr[x] ;
}
}
return max ;
}
}
5. 冒泡排序:
//冒泡:两两比较,较大的值往后放,第一次比较完毕,最大值出现在最大索引处;总共比较的次数:数组长度-1次
class Text {
public static void main(String[] args){
int arr[] = {12,24,6,35,21};
for (int x = 0;x < arr.length-1;x++){
for (int y = 0;y < arr.length-1-x;y++){
if (arr[y] > arr[y+1]){
int a = arr[y];
arr[y] = arr[y+1];
arr[y+1] = a;
}
}
}
sum (arr);
}
public static void sum(int arr[]){
System.out.print("[");
for (int x = 0; x < arr.length;x++){
if (x == arr.length-1){
System.out.print(arr[x]+"]");
}else{
System.out.print(arr[x]+",");
}
}
}
}
6. 选择排序
// 选择排序: 依次比较:使用0角标对应的元素依次后面角标对应的元素进行对比,小的往前放,
// 第一次比较完毕,最小值出现在最小索引处...依次这样比较..
public class Text5 {
public static void main(String[] args) {
int [] arr = {15,26,21,56,18};
for(int x = 0 ; x < arr.length-1; x ++){
for(int y = x + 1; y< arr.length ; y ++){
if(arr[y] < arr[x]){
int temp = arr[x] ;
arr[x] = arr[y] ;
arr[y] = temp ;
}
}
}
sum(arr);
}
public static void sum (int [] arr){
System.out.print("[");
for (int x = 0; x <arr.length;x++){
if (x == arr.length-1){
System.out.print(arr[x]+"]");
}else {
System.out.print(arr[x]+", ");
}
}
}
}
7. 方法的形式参数问题 :
形式参数的改变不能影响实际参数
8. 类与对象的关系 :
类 : 能够描述现实世界真实事物的一组属性和行为的集合!
事物 : 现实真是存在的事物;
创建对象 : 格式:类名 对象名 = new 类名();
面向对象 :
面向对象的思想特点:
1)更符号我们生活中是思想行为习惯
2)让复杂的事情简单化
3)我们从执行者变成了指挥者
三大特点 : 封装,继承,多态
9. 成员变量和局部变量的区别 :
1)在程序中的书写位置不同
局部变量:
方法定义中或者方法声明上
成员变量:
在类中,成员方法外定义的变量
2)在内存中
局部变量:
在栈内存中
成员变量:
在堆内存中
3)生命周期不同
局部变量:
随着方法调用而存在,随着方法调用完毕而消失!
成员变量:
随着对象的创建而存在,随着对象创建完毕之后,不会立即消失,
需要等待GC(垃圾回收器空闲时候回收掉!)
GC算法----->标记算法
标记---清除
4)初始化不同
局部变量:
可以先定义,但是必须在使用之前必须赋值,否则:可能尚未初始化变量
成员变量:
可以不初始化,它存在系统默认初始化!(根据类型判定)
如果一个方法的形式参数是引用类型 是具体类,那么调用该方法时,实际参数如何传递
//学生类
class Student{
//有一个成员方法:学习的方法
public void study(){
System.out.println("Good Good Study ,Day Day Up!!") ;
}
}
//定义一个StudentDemo类
class StudentDemo{
//有一个method成员方法
public void method(Student s){ //方法的形式参数是Student类型
//调用method 方法的时候:实际参数需要传递的是当前类的具体对象
//Student s = new Student();
s.study() ; //对象名.方法() ; //stu.study() ;
}
}
//测试类
class StudentTest{
public static void main(String[] args){
//在测试类中访问StudentDemo类中的method方法?
//创建StudentDemo类对象
StudentDemo sd = new StudentDemo() ;
//sd.method(s) ;//找不到符号
//先去创建学生对象
Student stu = new Student() ;
sd.method(stu) ;
}
}
10. 匿名对象
没有名字的对象
格式:
new 类名() ;
匿名对象有一个特点:可以作为参数进行传递
在开发中,匿名对象使用一次即可!
(因为没有栈内存变量指向堆内存地址,直接是在堆内存开辟空间,使用完毕,立即被回收!)
class NoNameObjectDemo{
public static void main(String[] args){
//之前的写法
//访问StudentDemo类中的method 方法
//创建StudentDemo类对象
StudentDemo sd = new StudentDemo() ;
//创建一个具体的学生对象
Student s = new Student() ;
sd.method(s) ;
System.out.println("------------------------------") ;
//方式2
StudentDemo sd2 = new StudentDemo() ;
sd2.method(new Student()) ;
System.out.println("------------------------------") ;
//链式编程
//一步走
new StudentDemo().method(new Student()) ;
}
}
11. 封装
目的 : 保证数据的安全性
12. private关键字的特点
1)可以修饰成员变量,也可以修饰成员方法,但是都只能在本类访问,外界类不能够访问
2)这些被私有修饰的成员变量,或者成员方法,可以间接通过公共方法来访问!
13. this
this:解决局部变量隐藏了成员变量
格式 : this.成员变量名 = 局部变量;
this:就是代表当前类的对象的地址值引用!
this.变量名 :访问的本类的成员变量
this.方法名():访问的是本类的成员方法
this() ; 访问本类无参构造方法
this(String xx):访问本类的有参构造方法
14. 构造方法
1)构造方法名和类名一致
2)没有具体的返回值类型
3)连void都没有
构造方法的目的:为了给类的成员的一些数据进行初始化
无参构造方法 :
private String brand ;
private int price ;//价格
private String color ;
public Phone(){
System.out.println("这是phone类的无参构造方法...") ;
}
....
....
Phone p = new Phone();//
//setXXX()赋值
p.setBrand("锤子手机") ;
p.setPrice(1299) ;
p.setColor("黑色") ;
System.out.println("品牌:"+p.getBrand()+",价格:"+p.getPrice()+",颜色:"+p.getColor()) ;
System.out.println(p) ;
有参构造方法 :
public Student(String name){
//this.name = name ;
System.out.println("这是Student类带String类型的有参构造方法...") ;
}
.... (set xxx,get xxx)
....
Phone p2 = new Phone("锤子手机",1299,"黑色") ;
System.out.println("品牌:"+p2.getBrand()+",价格:"+p2.getPrice()+",颜色:"+p2.getColor()) ;
}
一个类的成员:
1)成员变量
2)成员方法
3)构造方法
15. 静态static关键字的特点:
1)随着类的加载而加载
2)优先于对象存在: 它不能this共存 (this:代表当期类对象的地址值引用)
对象还没有new的时候,当前被static修饰的成员就已经内存了
3)被静态修饰的 可以被多个对象共享:有共享共用的意思
4)被静态修饰的变量,方法----->静态变量或者静态方法
我们所说的成员变量和成员方法:都指的是非静态
静态的成员的访问方式:类名.变量
类名.方法名()
关于static关键字的使用注意事项:
1)非静态的方法既可以访问静态变量,也可以访问非静态的变量
既可以调用静态方法,也可以调用非静态方法
2)静态的方法:只能访问静态变量,
只能调用静态方法
简单记:静态只能访问静态
16. 代码块
在java中用{}包起来的内容,称为代码块.
分类 :
局部代码块 : 在方法定义中使用,作用:限定局部变量的生命周期.
构造代码块 : 在类的成员位置(类中,方法外),使用使用{}包裹起来
作用:给类中的一些成员进行数据初始化
特点:每次在执行构造方法之前,如果存在构造代码块,先执行构造代码块中的内容!
静态代码块 : 在类的成员位置,直接使用 static{}. 静态代码块就加载一次!
作用:也可以通过static代码块,对一些操作(后期IO流创建文件/JDBC)
特点:随着类的加载而加载,优先于对象存在!
// 优先级:
// 静态代码块(只执行一次) > 构造代码块 > 构造方法
17. 继承
将多个类的共性内容抽取到一个独立的类中,然后这多个类和独立的这个类产生一种关系 : 继承关系
关键字 : extends
书写格式 :class Fu{}
class Zi extends Fu{}
继承的好处:
1)提高代码的维护性
2)提高代码的复用性
3)让类和类之间产生的关系,是"多态的前提条件"
继承的特点 :
1)类与类之间的关系,继承关系,只支持单继承
2)不支持多继承,但是可以支持多层继承
继承中使用的注意事项 :
1)子类继承父类 :可以继承父类的非私有的成员,私有的成员外界不能访问的,只能在本类中访问, 但是可以通过公共方法间接访问.
2)构造方法是不能被继承的,但是子类可以间接通过 super 关键字访问父类的构造方法.
一个类的组成 :
成员变量
构造方法
成员方法
继承中,每一个成员变量的关系问题
成员变量 :
a)子类继承父类,如果子类中的成员变量名称和父类的成员变量名称不一致,分别访问即可!
b)子类继承父类,如果子类的成员变量名称和父类的成员变量名称一致:如何访问呢? (重点)
1)首先在子类的局部位置找,是否存在局部变量名称,如果有,就使用
2)如果没有,就在子类的成员位置找,是否存在这个变量,如果存在,就使用
3)如果在子类的成员位置中没有找到,直接在父类的成员位置中找,如果有,就是使用!
4)如果父类的成员位置都没有,就没有这个变量,报错!
// 遵循一个原则:就近原则!
继承中构造方法的访问 :
a)子类继承父类,子类的所有的构造方法都会默认的访问父类的无参方法.
子类的所有构造方法的第一句话:默认隐藏了super() ;
因为子类中肯能会使用到父类的数据,所以在继承关系中,
得先让父类初始化---->构造方法 : 分层初始化!(先父类无参构造方法,在执行子类的构造方法)
super:代表的父类对象的空间表示(父类对象的地址值引用!)
b)如果父类中的无参构造方法没有,子类会怎么样?
子类的所有的构造都会报错! (因为子类所有构造方法默认父类的无参构造方法!)
如何解决呢?
方式1:手动给出父类的无参构造方法(推荐)
方式2:在子类的构造方法中的第一句话:通过super(xxx),间接的访问父类的有参构造方法
方式3:只要子类的所有构造方法中一个能够让父类初始化即可!
在子类的有参构造方法中:this():---->访问本类的无参构造方法,然后再子类的无参构造方法中
间接访问父类的有参构造方法super(xxx) ;
如何正确使用继承关系(extends)
如果一个A类是B类的一种,或者B类是A类的一种,这个时候就可以使用继承关系
继承关系:现实世界事物中本质体现的是一种 "is a"的关系
18. 总结:this和super 的区别
this:代表的当前类对象的地址值引用
super:代表的父类对象的地址值引用(代表父类的空间标识)
访问成员变量
this.变量名; 访问的本类中的成员变量
super.变量名; 访问的是父类的成员变量
访问构造方法:
this() ; 访问本类的无参构造方法
super() ;访问的父类的无参构造方法
this(xxx);访问的本类的有参构造方法
super(xxx);访问的父类的有参构造方法
成员方法:
this.方法名();访问的是本类的成员方法
super.方法名() ;访问的是父类的成员方法
19.方法重载,方法重写
//如果子类出现了和父类一模一样的方法声明,叫做方法重写!(Override) —方法复写(覆盖)
//方法重写和方法重载的区别?
方法重载:Overload
在一个类中,提供n多个功能,这些功能,方法名相同,参数列表不同,与返回值无关(目的:提高某个功能的扩展性)
参数列表不同:
1)类型不同
2)个数不同
3)考虑参数类型的顺序
public static void open(int a,double d){}
public static void open(double a,int b){}
构造方法也可以重载!
重载的目的:为了提高功能的扩展性:同一个方法可以接收很多类型的参数
方法重写:Override
在继承关系中,子类出现了父类一模一样的方法声明,重写的目的:子类有自己的功能,需要将父类的该功能覆盖掉!
重写的目的:为了沿用父类的功能,并且还需要使用子类的功能(具体的子类才具备具体的动作...)
举例:
动物:
都具备吃和睡的功能
猫和狗都继承自动物类,他们吃的不一样的
猫和狗就需要讲吃和睡的功能覆盖掉...
猫具体吃鱼
狗具体吃肉
20.关键子 : final
final(状态修饰符):最终的,无法更改的
关于final关键字的特点:
1)可以修饰类,该类不能被继承!
2)可以修饰符成员方法,成员方法不能重写! (根据具体的题意要求!)
3)可以修饰的变量,这个变量此时是一个常量! (自定义常量)
final修饰基本数据类型和引用类型的区别?
final修饰基本数据类型: 基本数据类型的对应的数据值不能在被赋值了,只能赋值一次!
final修饰引用类型:引用数据类型对应的地址值不能被改变
final Student s = new Student() ;//修饰实例变量s,s的堆内存地址值永远是固定值!
s = new Student() ;//重新开辟空间(报错)
六.多态
1.多态 : 一个事物在不同时刻不同形态.
多态的前提条件:
1)必须存在继承关系 (extends)
2)必须存在方法重写
子类需要覆盖父类的功能
Animal
eat():"动物都需要吃饭..."
Cat
eat() "猫吃鱼"
Dog
eat() "狗吃骨头"
3)必须有父类引用指向子类对象
class Fu{}
class Zi extends Fu{
//存在重写
}
格式: Fu fu = new Zi() ;
2. 多态成员的访问特点
Fu f = new Zi() ;
成员变量:编译看左(看Fu类是否存在变量,存在,编译不会报错!)
运行看左(使用Fu类的东西)
成员方法:(一般没有明确是静态方法的都是----->非静态)
编译看左(看Fu类是否存在这个方法,存在,编译不会报错!)
运行看右(存在方法重写,所以最终子类的功能将父类的该功能进行覆盖!)
静态的方法:
编译看左看Fu类是否存在这个静态方法,存在,编译不会报错!),
运行看左(静态方法:子类出现了父类一模一样的静态方法,算不上重写,跟类相关的-->类成员)
静态功能推荐的方式:类名.访问
构造方法:
即使多态的情况进行测试,多态的前提条件---->继承关系
当前执行子类的构造方法之前,需要让父类的先进行初始化,然后子类进行初始化(分层初始化!)
3. 多态的好处
1)提高代码的复用性:由继承保证
2)提高了代码的扩展性:由多态保证 (重点)
Fu fu = new Zi() ; 父类引用可以指向子类对象
4. 多态的弊端:
不能访问子类的特有功能 (Fu f = new Zi())
f.方法名() ;报错了. 父类中没有子类特有功能!
如何解决多态的弊端?
方案1: (不推荐)
具体的子类创建具体的子类对象 Zi z = new Zi() ;
z.成员方法名() ;
本身Fu f = new Zi() ;已经在堆内存中开辟空间了
Zi z = new Zi() ;在堆内存中又开辟空间,从内存角度考虑,这种比较消耗内存空间,不太好!
方案2:(推荐使用:"向下转型")
多态的第三个前提条件:父类 引用指向子类对象 :"向上转型 "Fu f = new Zi() ;
能不能将父类的引用转换成子类的引用? 好处:不需要在堆内存开辟空间
可以------>"向下转型"
Zi z = (Zi)f ; 还原成子类型
强转类型转换: 目标类型 变量名 =(目标类型)初始化值;
基本类型 : int num = 65 ;
//num--->char类型
char ch = (char)num ; ====>'A'
5. 多态的向上转型和向下转型
多态的向上转型:多态的第三个前提条件--->父类引用指向子类对象
格式:Fu fu = new Zi() ;
多态的弊端:不能访问子类的特有功能
可以使用向下转型:
将父类的强转强转转换为子类引用
Zi zi = (Zi)fu;
多态的方式:使用向下转型时,可能出现异常?
要使用向下转型,前提必须有父类引用指向子类对象 Fu f = new Zi() ;
遵循向下转型的格式:
Zi z = (Zi)f; 必须心里清楚堆内存存储的类型....
向下转型使用不当,就出现java.lang.ClassCastException:类转换异常: (属于运行时期异常)
当前堆内存中的实例不是该类型时,就会出现问题!
6. 抽象类
什么是抽象类----->现实世界事物中,某个事物是比较概括性(人/水果/动物),描述为抽象事物,
只有具体的工人/苹果/猫或者狗,才具备具体的功能;
将某个事物中的一些功能仅仅给出声明即可,没有方法体----->抽象方法---->此时这个类必须为抽象类!
举例:
动物都需要吃和睡
只要看到具体的动物类:猫类/狗类,才具备吃和睡的功能
将动物类中的吃和睡给出一个声明:加入一个关键字 abstract:抽象方法
动物类----->抽象类
抽象的关键字:Java语言 :abstract关键字 (抽象的含义)
抽象方法的格式;
权限修饰符(一般情况都是public) abstract 返回值类型 方法名(形式参数列表) ;
抽象类的格式:
abstract class 类名{}
7. 抽象类的特点
1)有抽象方法的类一定是抽象类
2)抽象类中不一定只有抽象方法 ,还可以非抽象方法(有方法体)
3)抽象类不能实例化---->意思:不能创建对象
如何实例化呢:通过具体的子类进行实例化(进行对象的创建), 抽象类多态 Fu fu = new Zi() ;Fu类型 抽象类型
4)抽象类的子类有两种情况:
a)目前来说:如果抽象类的子类都是抽象类---毫无意义 因为子类也不能new ,除非再有具体的子类
b)抽象类的子类具体类---才能new :抽象多态的形式 Fu fu = new Zi() ;
抽象类的核心宗旨:就是强制子类必须完成的事情(要将父类中的所有的抽象方法必须重写,否则报错!)
抽象类成员特点:
成员变量
既可以定义变量,也可以常量:被final修饰
成员方法
既可以定义为抽象方法,也可以定义为非抽象方法
如果定义为抽象方法:关键字abstract(显示给出)
构造方法
存在无参构造/有参构造方法---->目的:分层初始化
一个类中没有抽象方法,那么将这个类定义为抽象类的意义何在?
意义:为了不让它直接实例化!
如何实例化:
情况1)直接就有具体的子类
情况2)间接的有具体的子类
8.接口
接口---->它的本质就是体现一个现实世界事物所具有的的额外的扩展功能!
定义格式: interface 接口名{} ------>接口名遵循"标识符规则"---->大驼峰命名法
接口中的方法:不能有方法体,隐藏public abstract关键字,只能是抽象方法,不能有方法体
9. 接口的特点
1)不能实例化(不能创建对象)
2)如何实例化
接口实例化: 通过接口的子实现类(一定是具体类)进行实例化----接口多态(使用时最多的!)
子实现类和接口的关系: implements 实现关系
接口的子实现类如果是抽象类----->肯定要存在抽象类的具体的子类,否则都不能实例化!
接口的子实现类的命名规则:
开发中--->在接口名的后面+Impl:子实现类
interface Inter{}
class InterImpl implements Inter{
}
接口的成员特点:
1)接口中的成员方法:只能是抽象方法,默认的修饰符:public abstract(可以省略不写)
2)接口没有构造方法
3)接口的成员变量只能是常量:
存在默认修饰符:public static final (可以省略不写)
类与类之间的关系: extends 继承关系
只支持单继承,不支持多继承,但是可以多层继承
类和接口的关系: implements关系:实现关系
一个类继承另一个类的同时,可以实现多个接口,中间逗号给隔开
接口与接口之间: extends:继承关系
不仅支持单继承,也可以多继承,中间使用逗号隔开
10.形式参数问题的研究:引用类型
如果方法的形式参数是类 ,调用方法的时候,如何传递实际参数?
具体类,调用该方法,实际参数需要传递当前具体类的对象
抽象类,调用该方法实际参数需要传递的抽象类的子类对象 (抽象类多态)
接口 调用该方式,实际参数需要传递的是当前接口的子实现类对象(接口多态)
例题:
//方法的形式参数是类 调用方法的时候,如何传递实际参数?
class Student{
public void study(){
System.out.println("学习JavaEE...");
}
}
class StudentDemo{
public void method(Student s){
s.study() ;
}
}
//测试类
public class StudentTest {
public static void main(String[] args) {
//需要访问StudentDemo中的method 方法?
//创建StudentDemo类对象
StudentDemo sd = new StudentDemo() ;
//创建一个具体的学生对象
Student student = new Student() ;
sd.method(student);
System.out.println("-----------------------");
//或者匿名对象
new StudentDemo().method(new Student());
}
}
//方法的形式参数是一个抽象类
abstract class Person{
//定义一个抽象方法
public abstract void work() ;
}
//PersonDemo类
class PersonDemo{
public void show(Person person){ //形式参数是一个抽象类的情况: Person p = new Programmer();
person.work(); //p.work();
}
}
//定义Person抽象类的子类
class Programmer extends Person{
@Override
public void work() {
System.out.println("程序员不断的去学习...");
}
}
//测试类
public class PersonTest {
public static void main(String[] args) {
//需求:要访问PersonDemo类中的show方法?实际参数如何传递?
//创建PersonDemo类对象
PersonDemo pd = new PersonDemo() ;
// Person p = new Person() ; //抽象类不能实例化! 需要借助子类来进行实例化(存在具体的子类)
//抽象类多态
Person p = new Programmer() ;
pd.show(p);
System.out.println("-----------------------------");
//匿名对象
new PersonDemo().show(new Programmer());
}
}
//方法的形式参数是一个接口类
interface Love{
void love() ;
}
//LoveDemo类
class LoveDemo { //Love love = new LovePerson() p
public void function(Love l){//方法的形式参数是接口类型,必须传递接口的子实现类对象
l.love();//love.love() ;
}
}
//定义一个接口的子实现类
class LovePerson implements Love{
@Override
public void love() {
System.out.println("爱生活,爱Java,爱高圆圆...");
}
}
//测试类
public class LoveTest {
public static void main(String[] args) {
//需要访问LoveDemo类中的function方法
//创建LoveDemo类对象
LoveDemo ld = new LoveDemo() ;
//Love love = new Love() ; //接口不能实例化
//接口多态
Love love = new LovePerson() ;
ld.function(love) ;
System.out.println("------------------------");
//匿名对象
new LoveDemo().function(new LovePerson());
}
}
如果一个方法的返回值是引用类型,最终方法结束,如何返回?
具体类 :方法返回的就是当前具体类对象!
抽象类 :需要返回的是抽象类的子类对象
接口 :需要返回的是该接口的子实现类对象
例题:
//方法的返回值是一个具体类
class Student{
public void study(){
System.out.println("Good Good Study,Day Day Up!");
}
}
//StudentDemo类
class StudentDemo {
public Student show() { //方法的返回值是引用类型---具体类 :需要返回的当前类的具体对象
/* Student student = new Student() ;
//???
return student ;*/
//匿名对象
return new Student();
}
}
//测试类
public class StudentTest {
public static void main(String[] args) {
//调用StudentDemo类中的show方法?
//创建StudentDemo类对象
StudentDemo sd = new StudentDemo() ;
Student s = sd.show(); //Student s = new Student(); sd.show()---->返回的具体的学生对象
s.study();
System.out.println("-----------------------");
//匿名对象
Student student = new StudentDemo().show();
student.study();
}
}
//方法的返回值是一个抽象类
abstract class Person{
public abstract void work() ;
}
//PersonDemo类
class PersonDemo{
public Person method(){ //返回值类型是一个引用类型---抽象类的情况,需要返回的是抽象类的子类对象
//return ?
// Person p = new Person() ;//抽象类不能实例化
//抽象类多态
// Person p = new Worker() ;
//return p ;
//一步走
//匿名对象
return new Worker() ;
}
}
//定义一个子类 继承Person类
class Worker extends Person{
@Override
public void work() {
System.out.println("工人不断的去工作...");
}
}
//测试类
public class PersonTest {
public static void main(String[] args) {
//需要调用PersonDemo类中的method 方法?
PersonDemo pd = new PersonDemo() ;
Person person = pd.method(); // method方法其完成了一件事件: Person person = new Worker() ;
person.work() ;
System.out.println("---------------------------------");
//匿名对象
Person p = new PersonDemo().method();
p.work() ;
}
}
//方法的返回值是一个接口
interface Mary{
public abstract void mary() ;
}
//MaryDemo类
class MaryDemo{
public Mary function(){//返回值类型是接口类型,
//return ?
// Mary mary = new Mary() ; //接口不能实例化
//需要提供接口的子实现类对象
//接口多态
// Mary mary = new You() ;
// return mary ;
//一步走
return new You() ;
}
}
//定义一个类
class You implements Mary{
@Override
public void mary() {
System.out.println("结婚了,很开心...");
}
}
//测试类
public class LoveTest {
public static void main(String[] args) {
//调用MaryDemo类中的function方法?
MaryDemo maryDemo = new MaryDemo() ;
Mary mary = maryDemo.function();
mary.mary();
System.out.println("----------------------");
//匿名对象
Mary m = new MaryDemo().function();
m.mary();
}
}
11.关键词 : package
包的真实含义:
以后要代码分层...
将包进行划分
开发中,写包名的时候(字母都是小写,多级包) :公司域名反写
开发中:
先按照模块划分,在按照功能划分
用户模块
注册功能
登录功能
用户激活功能(使用邮件激活技术,手机短信验证,微信二维码)
用户退出
商品模块
订单模块
购物车模块
支付模块
12.权限修饰符
默认修饰符
私有修饰符:private
受保护的 :protected
公共的,公开的:public
修饰的权限从小到:private,默认,protected,public
七. 内部类
1. 内部类
在一个类中可以定义另一个类:
在类A 中定义了类B,将类B就称为类A的内部类,类A就是外部类!
成员内部类:
在一个类的成员位置中定义了另一个类
内部类可以访问外部类的成员,包括私有!
外部类如何直接访问内部类的成员方法?
格式:
外部类名.内部类名 对象名 = 外部类对象.内部类对象;
成员内部类的修饰符:
在成员内部类上面---加入private修饰:为了数据的安全性,它的访问---就要外部类的公共访问间接访问...
如果当前成员内部类是静态的, 里面的方法无论是静态的还是非静态的,都只能访问外部类的静态成员,包括私有!
如何直接访问静态成员内部类的成员呢?
将静态的成员内部类看成是外部类的静态成员访问
直接访问方式
外部类名.内部类名 对象名 = new 外部类名.内部类名() ;
2. 局部类部类
关于局部内部类,它的书写位置,在外部类的成员方法中的定义的类
局部内部类可以访问外部类的成员变量包括私有!
在外部类的局部位置,访问内部类的成员方法,创建当前局部内部类对象来访问!
3. 局部内部类访问局部变量的时候,此时局部变量应该注意什么?
(JDK7/JDK8),为什么要加入final关键字呢?
如何此时Java环境是Jdk7,局部内部类访问局部变量时,此时该变量必须显示加入final修饰
目前环境是JDK8环境,做了什么优化?
通过反编译查看
class Outer2$1Inner2{
final int val$num; num已经加入了final修饰
final Outer2 this$0;
public void show(){
System.out.println(val$num);
}
Outer2$1Inner2(){
this.this$0 = this$0;
val$num = I.this;
super();
}
}
原因:
局部变量的生命周期是随着方法调用而存在,随着方法调用结束而消失
而当前外部类对象调用method 方法的时候,此时num进入栈内存,在局部位置创建了局部内部类对象
而局部内部类对象调用它的成员方法访问该变量,方法method方法结束之后,内部类对象不会立即消失,
它里面的成员方法在访问局部变量,局部变量必须变成常量,常驻内存,否则如果当前变量消失了,局部内部类的成员依然在访问
就会出现冲突! 所以 jdk7 收到必须加入final修饰,jdk8通过jvm已经做了优化了,无需手动加入final修饰
4. 匿名内部类
没有名字的内部类 一般在我们局部位置使用!
格式:
匿名内部类它是内部类的一种简化格式
new 类名(可以是抽象类,也可以具体类)或者是接口名(){
重写功能
} ;
匿名内部类的本质:
继承了该类或者是实现了该接口的子类对象
关于匿名内部类在开发中的使用:
// 方法的形式参数如果是一个抽象类,那么实际参数可以需要接口的子类对象
1)将子类定义出来 继承自抽象类
2)直接使用抽象类的匿名内部类
// 方法的形式参数如果是一个接口,实际需要传递的接口子实现类对象
方式1:将接口的子实现类定义出来
方式2;使用接口的匿名内部类
//看程序,写结果
要求:需要在控制台分别打印30,20,10
考点:
外部类直接访问非静态的成员内部类的格式
外部类的成员变量的方法方式,(在成员内部类的成员方法中)
成员变量,局部变量名称都一样(就近原则)
外部类和内部类没有继承关系
class Outer{
int num = 10 ;
//成员内部类
class Inner{
int num = 20 ;
public void show(){
int num = 30;
//补全代码
System.out.println(num);
System.out.println(this.num);//this限定 :this.变量名:当前类的成员变量
// System.out.println(new Outer().num) ; //方式1:new Outer().num
System.out.println(Outer.this.num) ; //方式2:外部类的this限定
}
}
}
public class OuterTest {
public static void main(String[] args) {
Outer.Inner oi = new Outer().new Inner() ;
oi.show();
}
}
// 方法的返回值是一个抽象类?
// 需要返回的是当前抽象类的子类对象
// 方法的返回值是接口类型,需要返回的当前接口的子实现类对象
5. java.lang.Object:是类结构层次的根类(超类—>父类),所有的类都默认继承自Object子类(派生类)
使用JDK提供的API文档学习常用类中的常用功能
API:Application Programming Interface:应用程序接口开发文档
Object功能类的getClass()方法
public final Class getClass():表示正在运行的类 (就是字节码文件对象)
Class----->反射的时候去使用
Class类:
功能:
public String getName(): 获取当前类的全限定名称(包名.类名)
Object类中的getClass()/finalize()/hashCode()以及以后常用类
功能中如果native关键字:本地方法,非java语言底层实现另外一个功能
public int hashCode():(了解)获取对象的一个哈希码值 (本质不是地址值,可以把它理解为地址值)----跟哈希表有关系(HashMap)
一般情况:不同的对象获取的哈希码值是不同的 ,(但是中文字符,可能内容不一样,但是哈希码值不同!)
底层是通过哈希表算出来的.
6. 获取一个类的字节码文件对象有几种方式? 三种
第一种:通过Object类的getClass()--->Class :正在运行的java类: class 包名.类名
第二种:任意Java类型的class属性----获取当前类的字节码文件对象Class
第三种方式:Class里面forName("类的全限定名称(包名.类名)") ; (使用最多)
7. Object的public String toString()
返回对象的字符串表示形式。结果应该是一个简明扼要的表达,容易让人阅读。
建议所有子类覆盖此方法。
描述一个对象:是由很多属性(成员变量组成),应该看到的具体的属性描述
要么手动方式(不推荐)
也可以直接快捷键----重写toString即可
大部分的常用类或者后面的集合都会重写Object类的toString()
8. Object类的equals方法
public boolean equals(Object obj)
判断当前obj对象是否和当前对象想等
面试题:
equals和==的区别?
==: 连接的基本数据类型: 比较的是数据值否相同
==: 连接的是引用类型: 比较的是地址值是否相同
equals方法:如果使用Object默认的:底层用==,默认比较的还是两个对象的地址值是否相同,
Student s1 = new Student("文章",35) ;
Student s2 = new Student("文章",35) ;
s1和s2虽然地址值不同,他们的成员的内容相同,认为他是同一个人,但是如何让s1.equals(s2)为true:针对equals来说比较的是 成员信息内容是否相同;
重写Object的equals方法同时还需要重写hashCode内容相同,还需要比较哈希码值相同
alt+ins--->hashcode+equals方法
重写之后,就比较的是成员信息的内容是否相同!
克隆方法:
protected Object clone() throws CloneNotSupportedException:创建对象并返回该对象的副本
这个方法会抛出一个异常,throws:表示的是可能出现异常,针对调用者必须进行处理
要使用clone方法,当前某个对象所在的类必须实现"标记接口"Cloneable(没有字段(成员变量),也没有成员方法)
实现这个接口,那么就可以使用Object的clone()方法
9. Scanner
Scanner类:文本扫描器 java.util.Scaner ;
构造方法:
public Scanner(InputStream source) :创建一个文本扫描器
形式参数是一个抽象类--->
它通过System类里面的public static final InputStream in
System类中
public final static InputStream in = null;
本地方法(非Java语言实现)---> private static native void setIn0(InputStream in);
底层方法一定会创建系统资源---->读取用户输入的 字符(整数,字符串...)
Scanner类提供判断功能: 防止出现输入的类型和结果类型不匹配!
public boolean hasNextXXX():判断下一个录入的是否为指定的XXX类型
XXX nextXXX() 获取功能
举例:
public boolean hasNextInt()
int nextInt()
如果先录入int,在录入String---->nextLine()---- 录入的字符串数据被漏掉
解决方案;
1)直接使用next()---->String
2)在使用nextLine()之前,在创建Scanner对象即可
统一先用String----->全部接收 ----->后期可以通过Integer的特有功能将整数---->String
前提条件:String---->数字字符串 "1","2","3"
举例:
本身:int
录入5个学生的语文,数学,英语成绩,按照总分从高到底排序(可以TreeSet集合进行排序)
语文成绩,数学成绩,英语成绩---->nextLine()---->String
"98" "78" "60"
String--- 基本类型的包装类类型Integer--->int
八. String
1. String构造方法
java.lang.String:代表的字符串:
字符串是一个常量,一旦被赋值了,其值(地址值)不能被更改
推荐的使用方式:
String 变量名 = "xxxx" ;//xxxx代表 的当前String的实例
String类常用的功能:
获取功能:
int length():获取字符串长
面试题:
在数组中有没有length方法,在String类中有没有length方法,在集合中有没有length方法
数组中没有length方法,length属性
int[] arr = new int[3] ;
arr.length;
String类中有length()
集合中没有length(),----->size()获取元素数
构造方法:
(1). public String(): //空参构造:空字符序列
例: String s = new String() ;
System.out.println("s:"+s); //String类重写了Object的toString(),
System.out.println(s.length());
(2). public String(byte[] bytes)://将一个字节数组构造成一个字符串,使用平台默认的字符集(utf-8:一个中文对应三个字节) 解码
例: byte[] bytes = {97,98,99,100,101} ;
String s2 = new String(bytes) ;
System.out.println(s2);
编码和解码---保证字符集统一
编码:将一个能看懂的字符串---->字节 "今天老地方见" utf-8
解码:将看不懂的字节---->字符串 "今天老地方见" gbk
(3). public String(byte[] bytes,字符集):使用指定的字符集,将字节数组构造成一个字符串
(4). public String(byte[] bytes,int offset,int length):将指定的部分字节数组转换成字符串
参数1:字节数组对象,参数2:指定的角标值 参数3:指定长度
例: String s3 = new String(bytes,2,2) ;
System.out.println(s3);
System.out.println(s3.length());
(5). public String(char[] value):将字符数组构造成一字符串
例: char[] chs = {'我','爱','高','圆','圆'} ;
String s4 = new String(chs) ;
System.out.println(s4);
System.out.println(s4.length());
(6). public String(char[] value,int offset,int count):将部分字符数组转换成字符串
例: String s5 = new String(chs,1,4) ;
System.out.println(s5);
System.out.println(s5.length());
(7). public String(String original):构造一个字符串,参数为字符串常量
例: String s6 = new String("hello") ; //创建字符串对象,常量值:hello
System.out.println(s6);
String s7 = "hello" ; //推荐的方式
System.out.println(s7)
//面试题:
String s1 = "hello" ;
String s2 = new String("hello") ;
在内存中分别创建了几个对象?
第一个创建了一个对象,直接在常量池创建,开辟常量池空间
第二个:创建了两个对象,一个堆内存中开辟空间,一个指向常量池(不推荐)
/**
* String类型重写了Object的equals方法
* Object 的equals
*
* public boolean equals(Object obj) {
* return (this == obj); //默认比较的地址值
* }
* class String{
* private final char value[]; 属性 value 字符数组
* public boolean equals(Object anObject) { Object anObject = new String() ;
* if (this == anObject) { //判断当前字符串对象和传递进来S2对比
* return true; //判断地址值相同
* }
* if (anObject instanceof String) { //判断传进来的s2是否为String类型的实例
* String anotherString = (String)anObject; // 向下转型 String类型
* int n = value.length; //获取了字符数组长度 this.value.length----> int n = 5
* if (n == anotherString.value.length) { // if(s1的长度 5 == s2.value.length 5)
* char v1[] = value; //char v1[] = this.value; 将s1---->v1字符数组
* char v1[] = {'h','e','l','l','o'}
* char v2[] = anotherString.value; //char v2[] =s2.value; 将s2----->v2字符数组
* char v2[] = {'h','e','l','l','o'}
* int i = 0; //统计变量
* while (n-- != 0) { // i=0
* if (v1[i] != v2[i]) // v1[0] != v2[0] ---->'h' v1[1] != v2[1]
* return false;
* i++; /i=1
* }
* return true; //true
* }
* }
* return false;
* }
*
* }
*/
2. String类的常用的转换功能: (重点)
(1). byte[] getBytes() : 将字符串转换成字节数组 (编码)
如果方法为空参,使用平台默认的编码集进行编码(utf-8:一个中文对应三个字节)
例: String str = "中国" ;
byte[] bytes = str.getBytes();//默认utf-8
System.out.println(Arrays.toString(bytes));//[-28, -72, -83, -27, -101, -67]
(2). byte[] getBytes(String charset): 使用指定的字符集进行编码
解码的过程:将看不懂的字节数----->String
(3). String(byte[] bytes):使用默认字符集进行解码
例: String strResult = new String(bytes) ;// //使用平台默认解码集进行解码: utf-8
System.out.println(strResult);
(4). String(byte[] bytes,指定字符集)编码和解码必须要保证字符集统一
字符集:
gbk :一个中文两个字节(中国中文编码表)
gb2312:gbk升级版(含义有一个中文字符:中国的中文编码表)
iso-8859-1:拉丁文码表
utf-8:任何的浏览器--->都支持 utf-8格式 (后期统一个)
unicode:国际编码表
JS:日本国际 电脑系统 一个字符集
Arrays
静态功能:
(5). public static String toString(int/byte/float/double...[] a):将任意类型的数组---->String
(6). public char[] toCharArray(): 将字符串转换成字符数组
例: //定义一个字符串
String s2 = "helloworldJavaEE" ;
// public char[] toCharArray()
char[] chs = s2.toCharArray();
//遍历字符数组
for(int x = 0 ; x < chs.length; x ++){
System.out.println(chs[x]);
}
(7). public String toString(): 返回自己本身---"当前字符串的内容"
例: //System.out.println(s2.toString());
(8). public String toUpperCase(): 将字符串转换成大写
(9). public String toLowerCase(): 将字符串转换成小写java
例: System.out.println(s2.toUpperCase());
System.out.println(s2.toLowerCase());
3. String类型的判断功能
public boolean equals(Object anObject)://比较两个字符的内容是否相同 (区分大小写)
public boolean equalsIgnoreCase(String anotherString)://比较两个字符串是否相同(不区分大小写)
public boolean startsWith(String prefix)://判断字符串是否以指定的内容开头
public boolean endsWith(String suffix)://判断字符串是否以指定的内容结尾
需求:在某个时间点(今天下午18:00 将某个目录下的所有的以.java文件结尾删除)
Java中定时器类:Timer---->定时任务TimerTask(抽象类)
表示文件或者文件夹的抽象路径形式:File类
递归删除 (定义方法删除)
boolean isEmpty() 判断字符串是否为空 ://若为空,则返回true;否则返回false
String s = "" ;// 空字符串 ,存在String对象 ""
String s = null ; 空值 (空对象) null:引用类型的默认值
例:
String s1 = "helloJavaEE" ;
String s2 = "hellojavaee" ;
//比较两个字符的内容是否相同 (区分大小写)
System.out.println("equals:"+s1.equals(s2));
//比较两个字符的内容是否相同 (不区分大小写)
System.out.println("equalsIgnoreCase():"+s1.equalsIgnoreCase(s2));
/*
* public boolean startsWith(String prefix):判断字符串是否以指定的内容开头
* public boolean endsWith(String suffix):判断字符串是否以指定的内容结尾
* boolean isEmpty() 判断字符串是否为空 :若为空,则返回true;否则返回false
*/
System.out.println("startsWith():"+s1.startsWith("hel"));
System.out.println("startsWith():"+s1.startsWith("ak47"));
System.out.println("endsWith():"+s1.endsWith("EE"));
s1 = "" ; //length()---->长度0 (空字符序列)
System.out.println(s1.isEmpty());
4. String类的获取功能:(重点)
(1). int length(): //获取字符串长度
(2). public char charAt(int index); //获取指定索引处的字符
例: String str = "helloworldJavaEE" ;
System.out.println("charAt():"+str.charAt(4));
(3). public String concat(String str): //将指定的字符串和当前字符串进行拼接,获取一个新的字符串
例: System.out.println("concat:"+str.concat("R").concat("Go"));
(4). public int indexOf(int ch): //返回指定字符第一次出现的索引值
(5). public int lastIndexOf(int ch): //返回值指定字符最后一次出现的索引值
例: System.out.println("indexOf():"+str.indexOf("o"));
System.out.println("lastIndexOf():"+str.lastIndexOf("o"));
(6). public String[] split(String regex): //拆分功能:通过指定的格式将字符串---拆分字符串数组
例: String str2 = "JavaEE-Python-Go-R-C-C#-PHP" ;
String[] strArray = str2.split("-");
for(int x = 0 ; x < strArray.length ; x ++){
System.out.print(strArray[x]+"\t");
}
(7). public String substring(int beginIndex) ://从指定位置开始默认截取到末尾角标从0开始
(8). public String substring(int beginIndex,int endIndex)://从指定位置开始,截取到位置结束(包前不包右)
//只能取到endIndex-1处
例: String str = "helloworldJavaEE" ;
System.out.println("subString():"+str.substring(5));
System.out.println("subString():"+str.substring(5,9))
(9). public static String valueOf(boolean/int/long/float/double/char...Object b)//万能方法,将任意类型转换String //类型
5. 字符串其他功能:
(1). public String replace(char target,char replacement)://替换功将指定的内容使用target字符进行替换
例: String s = "helloworld" ;
System.out.println("replace():"+s.replace('l','k'));
(2). public String replaceAll(String regex, String replacement) ://将指定的和参数1正则表达式匹配的字符串 使用 //replacement进行替换
参数1:
[0-9] --->如果字符是数字字符
参数2: "*"替换掉
(3). public String trim()://去除字符串两端的空格
例: String s3 = " hello " ;
System.out.println("s3:"+s3.trim()+"----");
重点:
(4). public int compareTo(String anotherString):按照字典顺序比较,返回值是int.
String s1 = "hello" ;
String s2 = "hel" ;
String s3 = "abc" ;
s1.compareTo(s2); //2
s1.compareTo(s3) ;//7
/*
String类型---内置一个属性: char[] value
//1)字符串底层---->字符数组 s1和s2--->转换字符串数组 获取长度 5 和 3
2)通过Math的min方法(5,3) --->获取最小值 int lim = 3 ;
3) 创建两个数组对象 char[] c1 = {'h','e','l','l','o'}
char[] c2 = {'h','e','l'} ;
4)判断 定义统计变量 k = 0
while(k < lim){
if(c1[k] != c2[k]){
return 对应的字符值进行相减
return 'h' - 'a' = 104 - 97 = 7
}
k ++ ; //统计变量++
}
return 字符串的长度相减(字符数组长度相减)
*/
6. int 和 String 类型之间如何转换
//int---->String
//integer类型的静态功能toString
public static String toString(int i)
//String--->int
//Integer的静态功能
public static int parseInt(String s)
//String ---->Integer ---->int
String s = "100" ;
Integer i = new Integer(s) ;
int num = i.intValue() ;
7. StringBuffer 的构造方法以及功能
StringBuffer :字符串缓冲区 ---->类似于String,但是不一样 (可变的字符序列)
线程安全------>线程----(多线程中说)
线程依赖于进程存在!进程,能够被系统资源调用的独立单位
一个进程可以有多个线程,每一个线程----->"执行单元"(任务)
线程安全---->同步的----->执行效率低
举例:
银行类的网站/医疗网站
ATM机取钱---->插卡--->输入密码---->查询余额---->取钱
StringBuilder:和StringBuffer具有相互兼容的API,它是线程不安全的类---->不同步----->执行效率高
举例:
论坛网站
博客...
单线程程序中:jvm在进行编译的时候 使用StringBuilder去替换StringBuffer
//StringBuffer的构造方法:
public StringBuffer() : 空参构造,创建一个空字符序列的字符串缓冲去 (推荐)
public StringBuffer(int capacity): 构造一个字符串缓冲区对象,指定容量大小
public StringBuffer(String str): 指定字符序列,长度加上初始容量16(总容量)
//获取功能:
public int length():获取字符数(长度)
public int capacity():获取字符串缓冲区容量
//StringBuffer常用功能
StringBuffer的反转功能:reverse()
StringBuffer的追加功能: append(xxx)
StringBuffer的删除工能:deleteCharAt(int index)、delete(int start,int end)
StringBuffer的截取功能: subString(int bengin,int end)
StringBuffer的截取功能: subString(int bengin)
StringBuffer的插入功能: insert(int offset,String str)
8. StringBuffer,StringBuilder的区别
共同点:
两个都都是字符串缓冲区,支持可变的字符序列!
不同点:
StringBuffer:线程安全的类---->同步的(多线程:同步锁:源码几乎所有的方法都是同步方法 synchronized)
执行效率低
StringBuilder:线程不安全的类---->不同步----->执行效率高
单线程程序中,只考虑执行效率不考虑安全,所以StringBuilder类用作StringBuffer的简易替换
多线程环境中,要考虑安全问题只能使用StringBuffer,
线程安全的类:StringBuffer,Vector(List接口的子类)
9. StringBuffer和数组的区别
共同点:都是容器,都可以存储任意类型的元素 , 可以存储基本类型,也可以存储引用类型
数组---->同一种类型的元素 长度是固定的
如果需求中长度是不断变化的,那么数组用不了,考虑:StringBuffer/集合
StringBuffer---->存储不同类型的元素 append(int/char/double/Object/foat...) 长度是可变的
借助StringBuffer的功能:reverse()/append()追加----->转换成String形式体现
10. StringBuffer,StringBuilder和String的区别
String:字符串是一个常量,一旦被赋值,其值不能更改/作为形式参数属于特殊的引用类型,形式参数的改变不会实际参数
StringBuffer:可变的字符序列,线程安全的类----同步的----->执行效率低(线程角度)
StringBuilder:可变的字符序列.和StringBuffer具有相互兼容的api,单线程程序中(只考虑执行效率,不考虑安全问题)会使用StringBuilder替代StringBuffer
作为方法的形式参数,形参的改变会直接影响实际参数
11. String类的遍历
//将字符串的每一个字符分别输出! charAt(int index)
String str = "helloJavaEE" ;
//循环改进: 利用String类的length():获取字符串长度 + charAt(int index)
for(int x = 0 ;x < str.length() ; x ++){
System.out.println(str.charAt(x));//charAt(0)
}
//使用String类的转换功能
//String---->字符数组toCharArray()--->char[]
char[] chs = str.toCharArray();
for(int x = 0 ; x < chs.length ; x ++){
char ch = chs[x] ;
System.out.println(ch);
}
13. String类的字符串反转,判断字符串是否为对称字符串(reverse)
public class StringTest4 {
public static void main(String[] args) {
//创建键盘录入对象
Scanner sc = new Scanner(System.in) ;
//提示并录入数据
System.out.println("请您输入一个字符串:");
String line = sc.nextLine() ;
//方式2:使用StringBuffer的功能改进
boolean flag2 = compare2(line) ;
System.out.println(flag2);
private static boolean compare2(String line) {
/*
StringBuffer sb = new StringBuffer(line) ;
String str = sb.reverse().toString();
return str.equals(line) ;
*/
return new StringBuffer(line).reverse().toString().equals(line) ;
}
14. String类的equals方法表示什么意思
String类底层已经针对Object的equals和hashCode方法进行了重写
hashCode():String对象的哈希码值是否相同,哈希码值相同的不一定内容就相同,所以它需要覆盖equals方法
比较的字符串内容是否相同.
String s1 = "hello" ;
String s2 = new String("hello") ;
s1 ==s2---> false
s1.equals(s2) ---->true
15. StringBuffer和String如何转换
//String--->StringBuffer
String s = "hello" ;
//使用StringBuffer的有参构造方法
StringBuffer sb = new StringBuffer(s)
//或者是StringBuffer的追加功能
StringBuffer sb = new StringBuffer() ;
sb.append(s) ;
//StringBuffer--->String
//String类的构造方法 String(StringBuffer)
StringBuffer buffe = new StringBuffer("world") ;
String str = new String(buffer) ;
//第二种方式:StringBuffer的toString方法
九. Integer
1. 基本类型对应的包装类类型
整数类型 引用类型(默认值都是null)
byte Byte
short Short
int Integer
long Long
*
浮点类型
float Float
double Double
字符类型
char Character
布尔类型
boolean Boolean
2. 通过Integer得到int类型的取值范围
public static String toBinaryString(int i)://将整数---->二进制 的字符串
public static String toOctalString(int i)://将整数---->八进制的字符串
public static String toHexString(int i)://将整数---->十六进制数据
public static final int MAX_VALUE:int的最大值
public static final int MIN_VALUE:int的最小值
System.out.println(Integer.toBinaryString(100)); // 1100100
System.out.println(Integer.toOctalString(100)); // 144
System.out.println(Integer.toHexString(100)); //64
System.out.println(Integer.MIN_VALUE);//-2的31次方
System.out.println(Integer.MAX_VALUE);//2的31次方-1
3. Integer的构造方法
Integer(int value):可以将int类型保证为Integer类型
Integer(String s) throws NumberForamtException: 抛出一个数字格式化异常
注:当前字符串如果不是能够解析的整数的,就会出现数字格式化异常,s必须为 数字字符串
String s = "50" ;
Integer integer2 = new Integer(s) ;
System.out.println(integer2);
4.自动拆装箱
基本类型---> 对应的包装类类型 (装箱)
对应的包装类型---->基本类型 (拆箱)
方式:int---->Integer---->String //Integer作为桥梁
int i = 50 ;
Integer ii = new Integer(i) ;
String str2 = ii.toString();
System.out.println(str2);//50
方式:String ---->Integer---->int
String s = "100" ;
Integer integer = new Integer(s) ;
int result2 = integer.intValue();
System.out.println(result2);//100
> Integer的内部缓存区:IntegerCache
> low =-128 high=127
直接赋值的形式----》执行的底层Integer.valueOf(int i){}
> Integer i = 128 ; //new Integer(128)
> Integer i2 = 128 ; //new Integer(128)
> System.out.println(i == i2) ;false
十. Charcater : char类型的包装类类型
1. 构造方法
public Character(char value)
//创建字符类对象
//Character character = new Character('a') ;
Character character = new Character((char)(97)) ;
System.out.println(character); ----------a
2.主要功能
public static boolean isUpperCase(char ch)://判断当前字符是否大写字母字符
public static boolean isLowerCAse(char ch)://是否为小写字母字符
public static boolean isDigit(char ch)://是否为数字字符
3.例题
//定义三个统计变量
int bigCount = 0 ;
int smallCount = 0 ;
int numberCount = 0;
//创建键盘录入对象
Scanner sc = new Scanner(System.in) ;
//提示并录入数据
System.out.println("请您输入一个数据:");
String line = sc.nextLine() ;
//转换成字符数组
char[] chs = line.toCharArray();
for (int i = 0; i <chs.length ; i++) {
char ch = chs[i] ;
//直接判断
if(Character.isDigit(ch)){
numberCount ++ ;
}else if(Character.isUpperCase(ch)){
bigCount ++ ;
}else if(Character.isLowerCase(ch)){
smallCount ++;
}
}
十一. 日历类
1. Data
public Date()://当前系统时间格式
public Date(long date):参数为 时间毫秒值---->Date对象 (1970年1月1日...)
//创建日期类对象
Date date = new Date() ;
System.out.println(date);
long time = 60*60 ;
Date date2 = new Date(time) ;
System.out.println(date2);
2. Date和String类型如何转换
//java.util.Date---->String: 格式化操作
//1)创建Date对象
Date date = new Date() ;
//2)创建SimpleDateFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ;
//3)格式化的操作
String textDate = sdf.foramt(date) ; //使用textDate:日期文本字符串
//String:日期文本字符串----->java.util.Date :解析操作
//1)日期文本字符串
String source = "2021-7-29" ; //格式
//2)创建SimpleDateFormat对象
SimplDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd") ; //模式必须和String的格式一致,否则解析出错
//3)解析操作
Date date2 = sdf2.parse(source) ; //使用Date
3. 静态功能,返回值是它自己本身 public static Calendar getInstance()
public int get(int field)://根据给定日历字段----获取日历字段的值(系统的日历)
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR) ;
int month = calendar.get(Calendar.MONTH) ;
int date = calendar.get(Calendar.DATE) ;
public abstract void add(int field,int amount)://给指定的日历字段,添加或者减去时间偏移量
参数1:日历字段
参数2:偏移量
int year = calendar.add(Calendar.YEAR,-3);
十二. Random(伪随机数生成器)
1.构造方法
public Random(): 产生一个随机生成器对象,通过成员方法随机数每次没不一样的(推荐)
public Random(long seed) :参数为long类型的值(随机数流:种子),每次通过成员方法获取随机数产生的随机数相同的
2. 获取随机数的成员方法
public int nextInt():获取的值的范围是int类型的取值范围(-2的31次方到2的31次方-1)
public int nextInt(int n):获取的0-n之间的数据 (不包含n)
产生随机数:
Math类的random方法
public static double random();
Random类:也能够去使用
无参构造方法 + 成员方法
public Random():+ public int nextInt(int n)
3. java.lang.Math :针对数学运算的工具类,提供了很多方法
public static int abs(int a)://绝对值方法
public static double ceil(double a)://向上取整
public static double floor(double a)://向下取整
public static int max(int a,int b)://获取最大值
public static int min(int a,int b)://获取最小值
public static double pow(double a,double b)://a的b次幂
public static double random():[0.0,1.0)://随机数
public static long round(double a)://四舍五入
public static double sqrt(double a)://开平方根
Math类中的功能都是静态的,里面构造方法私有了!
一般情况:工具类中构造方法都是会私有化(自定义的工具类),提供对外静态的公共访问方法
(Java设计模式:单例模式)
JDK5的静态导入特性,必须方法静态的(导入到方法的级别)
Math类的功能都是静态的,就可以使用静态导入
import static 包名.类名.方法名;
前提不能和其他方法名重名;
4. BigDecimal
小数要进行精确计算-还可以计算的同时保留小数点后的有效位数
Java提供的类: BigDecimal
构造方法:
public BigDecimal(String value):数字字符串
成员方法:
public BigDecimal add(BigDecimal augend)//加
public BigDecimal subtract(BigDecimal subtrahend)//减
public BigDecimal multiply(BigDecimal multiplicand)//乘
public BigDecimal divide(BigDecimal divisor)//除
public BigDecimal divide(BigDecimal divisor,int scale,int roundingMode)
参数1:商
参数2:小数点后保留的有效位数
参数3:舍入模式 :四舍五入
5. ObjectArrayDemo 对象数组:能够存储对象的数组.
例:
需求:
使用数组存储5个学生(姓名,年龄,性别),然后将数组进行遍历,获取出来每一个学生的信息!
分析:
1)创建一个学生类
name,age,gender/sex
2) 数组存储5个学生
数组的定义格式:
数据类型[] 数组名称 = new 数据类型[长度] ; 学生对象数组
数据类型:Student类型 Student[] students = new Student[5] ;
3)创建5个学生对象:s1,s2,s3,s4,s5
4)students[0] =s1 ; 给数组中的元素进行赋值
students[1] = s2;
....
5)遍历学生数组,获取学生信息
现在5个学生,以后学生的不断的增加或减少,用数组合适吗? 数组不适合针对长度可变的需求,所以Java提供---
集合框架去使用!
public class Student {
private String name ;
private int age ;
private String gender ;
public Student() {
}
public Student(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
'}';
}
}
public class ObjectArrayDemo {
public static void main(String[] args) {
//创建学生数组
// 数据类型[] 数组名称 = new 数据类型[长度] ; 学生对象数组
Student[] students = new Student[5] ;
//创建5个学生
Student s1 = new Student("文章",35,"男") ;
Student s2 = new Student("高圆圆",42,"女") ;
Student s3 = new Student("刘亦菲",33,"女") ;
Student s4 = new Student("马蓉",30,"女") ;
Student s5 = new Student("马保国",65,"男") ;
//给数组中的元素赋值
students[0] = s1 ;
students[1] = s2 ;
students[2] = s3 ;
students[3] = s4 ;
students[4] = s5 ;
//遍历学生数组
for(int x = 0 ; x < students.length ; x ++){
//System.out.println(students[x]);
//就需要同getXXX()方法获取成员信息
Student s = students[x] ;
System.out.println(s.getName()+"---"+s.getAge()+"---"+s.getGender());
}
}
}
十三. Collection
Collection:集合层次的根接口
一些集合允许元素重复(List),一些集合不允许元素重复(Set)
一些集合有序(存储和取出一致)(List),一些集合无序(存储和取出不一致)(Set),
JDK不提供此接口的任何直接实现:它提供了更具体的子接口的实现,如Set和List
Collection
List:
最具体的子实现类ArrayList,LinkedList,Vector
基本功能:
添加
boolean add(Object e)://添加元素 E(Element)
删除:
void clear() // 暴力删除(将集合的素有元素全部干掉)
boolean remove(Object o)://从集合中删除指定的元素
获取集合的元素数 :int size()
判断功能:boolean isEmpty()://判断集合是否为空,为空元素,则返回true
boolean contains(Object o)://判断集合中是否包含指定元素,包含则返回true
1. Collection的高级功能
boolean addAll(Collection c)://添加一个集合中的所有元素
boolean containsAll(Collection c)://包含一个集合中的所有元素
boolean removeAll(Collection c)://删除集合中的所有元素, (删除一个算删除,还是删除所有)
boolean retainAll(Collection c)://A集合对B集合求交集, boolean的返回值是什么意思,交集的元素是保存在A中还是B中
//Collection最基本的遍历功能,不属于集合的专有遍历
Object[] toArray():将集合转换成了对象数组
2. Collection的迭代器:集合的专有遍历方式
Iterator iterator():返回值类型接口类型,需要返回的子实现类对象
Iterator接口:
boolean hasNext():判断迭代器中是否存在下一个元素
Object next(): 获取下一个可以遍历的元素
给Collection中存储String类型,遍历出来
public class CollectionTest {
public static void main(String[] args) {
//创建集合对象
Collection c = new ArrayList() ; //List接口的子实现类 (重复元素)
//添加元素
c.add("hello") ;
c.add("world") ;
c.add("javaee") ;
// System.out.println(c);
//获取Collection的迭代器Iterator iterator()
Iterator it = c.iterator();
//如果现在明确存储了3个元素,以后这些数据可能数据库获取的一个列表集合数据,一般while循环
while(it.hasNext()){//判断迭代器中有下一个元素
//才获取
Object obj = it.next();// Object obj = new String("hello") ...
// System.out.println(obj+"----"+obj.length());
//获取的元素同时,还要获取当前存储元素的长度 ---->String类型 length()
String str = (String) obj;//向下转型
System.out.println(str+"----"+str.length());
}
}
}
3.泛型<E/T>
集合类型<引用数据类型> 集合对象名 = new 子实现类<引用数据类型>() ;
泛型的好处:
1)将运行时期异常提前了编译时期
2)避免了强制类型转换
3)提高了程序安全性
public class GenericDemo {
public static void main(String[] args) {
//创建Collection集合对象
Collection<String> c = new ArrayList<String>() ; //new XXX<数据类型>: jdk7以后泛型推断
c.add("hello") ;
c.add("高圆圆") ;
c.add("你好吗") ;
// c.add(100) ;
//获取迭代器Iteratr<E>是集合中存储的泛型是一致的
Iterator<String> it = c.iterator();
while(it.hasNext()){
//获取String字符串的同时,还要获取长度
String str = it.next();
System.out.println(str+"---"+str.length());
}
}
}
4. Collection的面试题
集合和数组的区别
1)长度区别
数组:长度固定
集合:长度可变
2)存储数据类型的区别
数组:
可以存储基本数据类型,也可以存储引用数据类型
int[] arr = {100,200,50,"hello"} ;不行的
集合:前提条件:集合中加入泛型<> 也是在模拟数组的特点:
只能存储引用类型 Collection<E> :泛型<引用类型>
3)存储元素的区别:
数组:
存储的元素必须为同一种数据类型
举例:
水杯中加入水
集合:如果没有加入泛型 :就出任意类型的元素(必须引用类型)
举例:
水杯加入水,可乐,加入威士忌
十四. List
1. List 集合的特点
1.有序(存储元素和取出元素一致)
2.允许元素重复
具备Collection相关的功能
Object [] toArray()
Iterator iterator()
2.特有功能
void add(int index,Object element)://在指定的索引处插 入元素
Object get(int index)://获取指定位置处的元素 + int size():一种新的集合遍历方式
Object remove(int index)://删除指定位置处的元素
Object set(int index,E element)://修改指定位置处的元素(替换)
ListIterator<E> listIterator():列表迭代器
ListIterator接口:
void add(E e)有添加
remove():有删除
3. List集合的遍历方式
Object[] toArray()
Iterator iterator()
Object get(int index):获取指定位置处的元素 + int size():一种新的集合遍历方式
ListIterator<E> listIterator():列表迭代器 :List集合专有遍历方式
public class ListTest {
public static void main(String[] args) {
//创建List集合对象
List<String> list = new ArrayList<>() ;
//添加元素
list.add("hello");
list.add("world");
list.add("java");
list.add("android");
//size()+get(int index)集合
for(int x = 0 ; x < list.size() ; x ++){
String s = list.get(x);
System.out.println(s+"---"+s.length());
}
System.out.println("-----------------------------------------------");
//List集合存储Student对象并进行遍历(学生:姓名,年龄)
List<Student> stuList = new ArrayList<>() ;//JDK7以后 泛型推断:自动的和前面的泛型类型一致!
//创建3个学生
Student s1 = new Student("张佳宁",31) ;
Student s2 = new Student("迪丽热巴",29) ;
Student s3 = new Student("张俊杰",20) ;
//添加到列表中
stuList.add(s1) ;
stuList.add(s2) ;
stuList.add(s3) ;
//遍历
//方式1:Object[] toArray()
Object[] objs = stuList.toArray();
for(int x = 0 ; x < objs.length ; x ++){
Student s = (Student) objs[x];
System.out.println(s.getName()+"----"+s.getAge());
}
System.out.println("--------------------------------------");
//Collection的迭代器
Iterator<Student> it = stuList.iterator();
while(it.hasNext()){
Student s = it.next() ;
System.out.println(s.getName()+"---"+s.getAge());
// System.out.println((it.next().getName()+"---"+(it.next().getAge())));
// next()只能使用一次,不能多次使用 //错误的用法
}
System.out.println("-------------------------------------");
//方式3:size()+get(int index)
for(int x = 0 ; x < stuList.size() ; x ++){
Student s = stuList.get(x);
System.out.println(s.getName()+"----"+s.getAge());
}
System.out.println("-----------------------------------------");
//正向遍历:ListIterator<E> listIterator():列表迭代器 :List集合专有遍历方式
ListIterator<Student> lit = stuList.listIterator();
/**
* ListIterator extends Iterator{}
*
* class ArrayList{
* List具体ArrayList子实现类重写了Iterator listiterator(){
*
* return new ListItr(0) ;
* }
*
* private class ListItr extends Itr implements Iterator{
*
* //具备hasNext()
* //next()
* }
*
* }
*/
while(lit.hasNext()){
Student student = lit.next();
System.out.println(student.getName()+"---"+student.getAge());
}
System.out.println("-----------------------------------------");
//逆向遍历:前提:必须有正向遍历
//ListIterator<E> listIterator()
//ListIterator:特有功能:
//boolean hasPrevious():是否有上一个元素可以迭代
//Object previous():获取上一个元素
while(lit.hasPrevious()){
Student student = lit.previous();
System.out.println(student.getName()+"---"+student.getAge());
}
}
}
4. LIst如何去重
(1).存储字符串类型
方式一:新建空集合思想
//新建一个空的集合List
List<String> newList = new ArrayList<>() ;
//遍历以前的集合
for(String s :list){
//使用新集合判断,不包含这个元素,说明该元素没有重复,就可以添加
if(!newList.contains(s)){
newList.add(s) ;
}
}
//遍历新的集合
for(String s:newList){
System.out.println(s);
}
方式二:利用选择排序的思想去完成
//利用选择排序的思想完成
for(int x = 0 ; x < list.size()-1 ; x ++){
for(int y = x +1 ; y < list.size() ; y++){
//如果后面的元素和前面的元素相同
if(list.get(y).equals(list.get(x))){
//通过集合remove掉
list.remove(y) ; // public Object remove(int index)
//角标--
y -- ;
}
}
}
for(String s:list){
System.out.println(s);
}
(2). 存储自定义对象
方式1:新建集合思想
contains(Object)方法依赖于Object的equals方法,所以集合存储的类型所在的类必须重写equals方法,否则默认使用
方式2:使用选择排序思想 将List存储的重复的学生对象进行重写!
//方式1:创建新的一个新集合
List<Student> newList = new ArrayList<>() ;
//遍历以前的集合获取每一个学生对象
for(Student s:list){
//如果当前newList不包含这个学生添加到新集合中
if(!newList.contains(s)){
newList.add(s) ;
}
}
//遍历新集合
for(Student student:newList){
System.out.println(student.getName()+"----"+student.getAge());
}
5. List的并发修改异常java.util.ConcurrentModificationException
集合在使用迭代器会经常出现的问题:并发修改异常,
当集合的元素正在被迭代器进行遍历,那么集合对象是不能够对元素进行增加或者删除 (一个线程正在遍历,一个线程在修改元素)
解决方案:
1)要么就是迭代器去遍历集合的元素,迭代器去添加元素 :列表迭代器才具备添加的动作
2)要么集合遍历,集合添加
public class ListTest2 {
public static void main(String[] args) {
//创建List集合对象
List<String> list = new ArrayList<>() ;
//给添加元素
list.add("hello") ;
list.add("world") ;
list.add("javaee") ;
//使用迭代器遍历
//Iterator iterator()
/* Iterator<String> it = list.iterator(); //"hello","world","javaee"
while(it.hasNext()){
String s = it.next() ;//"hello","world","javaee"
//判断
if("world".equals(s)){
list.add("javaEE") ;//集合对象添加的元素,迭代器不知道
}
}
System.out.println(list);*/
//解决方案1: 1)要么就是迭代器去遍历集合的元素,迭代器去添加元素 :列表迭代器才具备添加的动作
//ListIterator
//void add(E e)
/*ListIterator<String> lit = list.listIterator();
while(lit.hasNext()){
//获取
String s = lit.next();
//判断是否存在"world"元素
if("world".equals(s)){
//列表迭代器添加
lit.add("javaEE");
}
}*/
//方案2:要么集合遍历,集合添加
//size()+get(int index)
for(int x = 0 ; x < list.size(); x ++){
String s = list.get(x);
if("world".equals(s)){//将常量放前面,防止出现NullPointerException
list.add("javaEE");
}
}
System.out.println(list);
}
}
十五. Vector
1. Vector集合特有功能:
添加
public void addElement(Object obj):在vector对象的末尾添加元素 ------> 一直使用的add(Object e)
删除
public boolean removeElement(Object obj):删除元素
获取功能
public Object elementAt(int index):获取指定位置的元素---->类似于 public Object get(int index)
public Enumeration<E> elements() :Vector集合的专有遍历方式---->类似于 Iterator literator()
接口
boolean hasMoreElements():判断是否有更多的元素可以迭代
Object nextElement() 获取元素
2. Vector集合遍历方式
public Enumeration elements() //Vector集合的专有遍历方式
public class VectorDemo {
public static void main(String[] args) {
//创建Vector集合对象
Vector<String> v = new Vector<>() ;
v.addElement("hello");
v.addElement("world");
v.addElement("SpringBoot");
v.addElement("SpringCloud") ;
//遍历:特有功能
Enumeration<String> en = v.elements(); //相当于Iterator
while(en.hasMoreElements()){
String s = en.nextElement();
System.out.println(s+"---"+s.length());
}
System.out.println("----------------------------------");
for(String s: v){
System.out.println(s+"----"+s.length());
}
}
}
十六. 增强for循环
1. 增强for循环
替代集合中迭代器去遍历集合使用的(优先在集合中使用)
格式:
for(存储的引用数据类型 变量名: 集合/数组对象){
//集合使用居多,数组一般都是使用普通for
使用变量名即可
}
注意事项:
当前集合对象不能为空 null :foreach语句:增强for它本身就是获取迭代器了,就会出现空指针异常
public class ForeachDemo {
public static void main(String[] args) {
//数组
int[] arr = {11,22,33,44,55} ;
for(int x = 0 ; x < arr.length ; x ++){
System.out.println(arr[x]);
}
System.out.println("--------------------------------");
/*
for(存储的引用数据类型 变量名: 集合/数组对象){
* 使用变量名即可
* }
*/
//对于数组来说:使用普通for
/* for(int a:arr){
System.out.println(a);
}*/
//创建List集合
List<String> list = new ArrayList<>() ;
list.add("hello") ;
list.add("world") ;
list.add("javaee") ;
/* for(String s:list){//替换迭代器使用
//如果存在world元素,添加一个android元素
//System.out.println(s);
if("world".equals(s)){
list.add("android") ;//出现并发修改异常
}
}
System.out.println(list);
*/
list = null ;
if(list!=null){
for(String s:list){//获取迭代器
System.out.println(s+"---"+s.length());
}
}else{
System.out.println("当前集合对象为null了");
}
}
}
2. 面试题
需求:
使用增强for遍历List,存储三个学生,遍历后获取学生信息(姓名和年龄)
public class ForeachTest {
public static void main(String[] args) {
//创建List
List<Student> list = new ArrayList<>() ;
//创建三个学生
Student s1 = new Student("张佳宁",29) ;
Student s2 = new Student("邓超",40) ;
Student s3 = new Student("黄海波",30) ;
list.add(s1) ;
list.add(s2) ;
list.add(s3) ;
//方式5:增强for循环
for (Student s: list) {
// System.out.println(s.getName()+"---"+s.getAge());
System.out.println(s); //对象名称:直接使用重写后的toString()
}
}
}
3. 插入排序
核心思想:使用1角标对应的元素进行和0角标比较
如果前面元素大,向右移动,确定角标1对应的元素的位置,再次使用2角标对应的元素依次和1和0都元素比较
依次这样比较
public class InsertSortTest {
public static void main(String[] args) {
//定义一个Integer数组: Integer实现的自然排序:元素能够按照升序默认排序
Integer[] arr = {34,8,64,51,32,21} ;
System.out.println("排序前:");
printArr(arr);
//定义一个功能
insertSort(arr) ;
System.out.println("排序后:");
printArr(arr);
}
//插入排序
private static void insertSort(Integer[] arr) {
//定义一个变量j
int j ; //j记录当前角标的变化
//定义变量 : p:表示一个比较次数 p=1,2,3,4,5 (每一移动的元素的位置)
for(int p = 1 ; p < arr.length ; p ++ ){ //比较次数 p=2
//定义临时变量temp
Integer temp = arr[p] ; //temp = 8; temp = 64
//开始比较
for(j = p ; j>0 && temp.compareTo(arr[j-1])<0; j-- ){ // j= 1 ; 1>0&& 8 < 34 j-- : j= 0
//j=2 ; 2>0 && 64 < 32
//数据移动
arr[j] = arr[j-1] ;
}
//确定temp的位置:8的位置 64的位置:p=2
arr[j] = temp ; // 没有移动
}
}
public static void printArr(Integer[] arr){
System.out.print("[");
for(int x = 0 ; x < arr.length ; x ++){
if(x == arr.length -1){
System.out.println(arr[x] +"]");
}else{
System.out.print(arr[x]+", ");
}
}
}
}
十七. LinkedListDemo
1. LinkedList集合特点:
线程不安全的类,执行效率高链接列表结构,查询慢,增删快
2.特有功能:
public void addFirst(Object e):在列表开头插入元素
public void addLast(Object e):将元素追加到列表的末尾
public Object getFirst():获取列表的第一个元素
public Object getLast():获取列表的最后一个元素
public Object removeFirst(): 删除列表的第一个元素,并获取第一个元素
public Object removeLast():删除列表的最后一个元素,并获取最后一个元素
十八. HashSet
1. HashSet
Set集合:无序(存储和取出不一致),能够保证元素唯一
HashSet:底层数据结构是一个哈希表(桶结构)
线程不安全的类---->不同步---->执行效率高
JDK8以后;提供了juc(并发包:java.util.concurrent):
ConcurrentHashMap<K,V>和HashMap的区别 ?
String类型:String类型本身已经重写了hashCode()和equals,如果hashCode和equals()都相同,
那么认为同一个元素,存储以前的值
2. 如果现在存储是自定义对象,如何保证元素唯一?HashSet
Student s1 = new Student("高圆圆"42) ;
Student s2 = new Student("高圆圆"42) ;
HashSet集合依赖于add方法---->HashMap的put方法
首先要比较元素的哈希码值相同----->hash()就相同
还要比较成员信息是否相同,对应存储自定的类必须要重写Object的equals方法
Student的这个类,必须手动给出hashCode()和equals
Hashset集合不能保证顺序迭代恒久不变!
应用场景:
在一些需求中,如果没有明确要求元素重复,那就可以使用hashSet,保证元素唯一!
类型:String,Integer,Long,....
例题:
public class Student {
private String name ;//姓名
private int age ;//年龄
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name.equals(student.name);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + age;
return result;
}
}
public class HashSetDemo2 {
public static void main(String[] args) {
//创建HashSet集合对象
HashSet<Student> hs1 = new HashSet<>() ;
Student s1 = new Student("宋江",35) ;
Student s2 = new Student("宋江",35) ;
Student s3 = new Student("武松",30) ;
Student s4 = new Student("宋江",30) ;
Student s5 = new Student("武松",30) ;
Student s6 = new Student("卢俊义",28) ;
Student s7 = new Student("卢俊义",28) ;
System.out.println("-------------------------------");
//System.out.println(s1.hashCode());
//System.out.println(s2.hashCode());
//添加集合中
hs1.add(s1) ;
hs1.add(s2) ;
hs1.add(s3) ;
hs1.add(s4) ;
hs1.add(s5) ;
hs1.add(s6) ;
hs1.add(s7) ;
//遍历
for(Student s : hs1){
System.out.println(s.getName()+"---"+s.getAge());
}
}
}
3. ArrayList的嵌套
ArrayList<ArrayList<Studet>>
ArrayList<Studet>代表一个java基础班,同时三个班级,都是可以三个ArrayList<Studet>来表示
ArrayList<ArrayList<Studet>> ---> 三个ArrayList<Studet> 每一个ArrayList<Student>存储三个学生
将大集合进行遍历!
public class Student {
private String name ;//姓名
private int age ;//年龄
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class ArrayListIncludeArrayListTest {
public static void main(String[] args) {
//需要创建一个大的集合
ArrayList<ArrayList<Student>> bigArray = new ArrayList<>() ;
//第一个子集合ArrayList<Student>
ArrayList<Student> firArray = new ArrayList<>() ;
Student s1 = new Student("高圆圆",42) ;
Student s2 = new Student("文章",35) ;
Student s3 = new Student("王宝强",30) ;
firArray.add(s1) ;
firArray.add(s2) ;
firArray.add(s3) ;
//将第一个子集合添加到大集合中
bigArray.add(firArray) ;
//第二个子集合ArrayList<Student>
ArrayList<Student> secArray = new ArrayList<>() ;
Student s4 = new Student("张三",42) ;
Student s5 = new Student("王五",35) ;
Student s6 = new Student("李四",30) ;
secArray.add(s4) ;
secArray.add(s5) ;
secArray.add(s6) ;
//将第二个子集合添加到大集合中
bigArray.add(secArray) ;
//第三个子集合ArrayList<Student>
ArrayList<Student> thirArray = new ArrayList<>() ;
Student s7 = new Student("盲僧",42) ;
Student s8 = new Student("亚索",35) ;
Student s9 = new Student("提莫",30) ;
thirArray.add(s7) ;
thirArray.add(s8) ;
thirArray.add(s9) ;
//将第三个集合添加到集合中
bigArray.add(thirArray) ;
//ArrayList<ArrayList<Student>>遍历大集合
for(ArrayList<Student> array:bigArray){
for(Student s: array){
System.out.println(s.getName()+"\t"+s.getAge());
}
}
}
}
4. Treeset
TreeSet集合 : 无序性,元素唯一
底层依赖于TreeMap集合, 红黑树结构(也称为 "自平衡的二叉树结构"),可以实现Map的自然排序以及比较器排序取决于使用的构造方法
构造方法:
public TreeSet():构造一个空的树,实现元素自然排序 (取决于存储的元素类型能否实现Comparable接口)
自然排序 ----->执行的TreeSet无参构造方法,而且前提条件当前存储类型必须实现Comparable接口
TreeSet能够实现两种排序:
自然排序/比较器排序,取决于构造方法
自然排序:TreeSet<E>(),E类型必须实现Comparable接口,实现自然排序(实现的compareTo(T t))
比较器排序:
public TreeSet(Comparator<? super E> comparator)
Comparator是一个接口类型
1)自定义一个类实现Comparator接口,重写compare方法
2)使用接口的匿名内部类(推荐)
使用TreeSet存储Student类型,遍历元素(使用比较器排序完成)
排序条件:
主要条件:按照学生的年龄从小到大排序
5. 自然排序
例题:
使用TreeSet存储Student类型,遍历元素
排序条件:
主要条件:按照学生的年龄从小到大排序,自己分析次要条件
//实现自然排序 实现一个接口Comparable
public class Student implements Comparable<Student>{
private String name ;//姓名
private int age ;//年龄
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//排序的代码
@Override
public int compareTo(Student s) { //后面需要和学生对象对比
//主要条件:就是按照年龄从小到大排序
//定义一个变量
//年龄int类型
/* int num = this.age - s.age ;
// int num = s.age - this.age ;//从大到小
//次要条件:年龄相同,还要比较姓名的内容是否相同
int num2 = (num==0)?(this.name.compareTo(s.name)):num; //字符串的字典顺序比较
//gaoyuanyuan
//jacky
return num2;*/
// 第二种情景 主要条件:按照学生姓名的长度:从小到大进行排序
//主要条件:按照学生姓名的长度:从小到大进行排序
int num = this.name.length() - s.name.length() ;
//如果长度相同,还比较内容是否一样 "hello" ,"hel"
int num2 = (num==0)?(this.name.compareTo(s.name)):num ;
//如果长度相同,内容一样
//按照学生的年龄从小到大比
int num3 = (num2==0)? (this.age - s.age) :num2 ;
return num3 ;
}
}
public class TreeSetDemo2 {
public static void main(String[] args) {
//创建TreeSet,无参构造方法
TreeSet<Student> ts = new TreeSet<>() ;
//创建几个学生对象
Student s1 = new Student("gaoyuanyuan",42) ;
Student s2 = new Student("gaoyuanyuan",42) ;
Student s3 = new Student("jacky",40) ;
Student s4 = new Student("rose",40) ;
Student s5 = new Student("tomcat",35) ;
Student s6 = new Student("jeffry",35) ;
Student s7 = new Student("liushishi",54) ;
Student s8 = new Student("liudehua",60) ;
//添加
ts.add(s1) ; //因为当前集合存储的自定义对象,元素要实现自然排序必须所在的类实现Compareable接口
ts.add(s2) ;
ts.add(s3) ;
ts.add(s4) ;
ts.add(s5) ;
ts.add(s6) ;
ts.add(s7) ;
ts.add(s8) ;
//遍历
for(Student s : ts){
System.out.println(s.getName()+"---"+s.getAge());
}
}
}
6. 比较器排序
例题2:
//比较器排序
public class Student {
private String name ;//姓名
private int age ;//年龄
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name.equals(student.name);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + age;
return result;
}
}
//方式1:接口子实现类对象
public class MyComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
//主要条件:按照学生的年龄从小到大排序
//s1---->就是刚才自然排序里面this
//s2---->就是刚才自然排序里面s
int num = s1.getAge() - s2.getAge() ;
//如果年龄相同,比较姓名是否一样
int num2 = (num==0)? (s1.getName().compareTo(s2.getName())): num ;
return num2;
}
}
//方式2:接口的匿名内部类
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//主要条件:按照学生的年龄从小到大排序
//s1---->就是刚才自然排序里面this
//s2---->就是刚才自然排序里面s
int num = s1.getAge() - s2.getAge() ;
//如果年龄相同,比较姓名是否一样
int num2 = (num==0)? (s1.getName().compareTo(s2.getName())): num ;
return num2;
}
}) ;
//创建几个学生对象
Student s1 = new Student("gaoyuanyuan",42) ;
Student s2 = new Student("gaoyuanyuan",42) ;
Student s3 = new Student("jacky",40) ;
Student s4 = new Student("rose",40) ;
Student s5 = new Student("tomcat",35) ;
Student s6 = new Student("jeffry",35) ;
Student s7 = new Student("liushishi",54) ;
Student s8 = new Student("liudehua",60) ;
ts.add(s1) ;
ts.add(s2) ;
ts.add(s3) ;
ts.add(s4) ;
ts.add(s5) ;
ts.add(s6) ;
ts.add(s7) ;
ts.add(s8) ;
for (Student s:ts) {
System.out.println(s.getName()+"---"+s.getAge());
}
}
}
7. 泛型
泛型高级通配符(了解)
<?> :任意Java类型,包括Object
<? super E> : 向上限定:E类型以及E父类
<? extends E>: 向下限定:E以及它的子类
//创建Collection
Collection<?> c1 = new ArrayList<Object>() ;
Collection<?> c2 = new ArrayList<Animal>() ;
Collection<?> c3 = new ArrayList<Cat>() ;
Collection<?> c4 = new ArrayList<Dog>() ;
System.out.println("------------------------------");
Collection<? super Cat> c5 = new ArrayList<Cat>() ;//最基本的一致
Collection<? super Cat> c6 = new ArrayList<Animal>() ;
Collection<? super Cat> c7 = new ArrayList<Object>() ;
System.out.println("------------------------------");
Collection<? extends Object> c8 = new ArrayList<Object>() ;
Collection<? extends Object> c9 = new ArrayList<Animal>() ;
Collection<? extends Object> c10 = new ArrayList<Cat>() ;
Collection<? extends Animal> c11 = new ArrayList<Animal>() ;
Collection<? extends Animal> c12 = new ArrayList<Cat>() ;
Collection<? extends Animal> c13 = new ArrayList<Dog>() ;
Collection<? extends Animal> c14 = new ArrayList<Object>() ;
十九. Map
1. Map
Map集合:键映射到值的对象,Map集合可以多个值,但键必须唯一!
特点:
每个元素成对存在,由键和值两部分组成,通过键可以找到对应的值
键(key值)不可重复,值(value)可以重复,一个value值可以和很多key值形成对应关系,每个建最多只能映射到一个值
两个 Map 接口的重要实现类:HashMap类、LinkedHashMap 类
2. Map子类(HashMap HashTable TreeMap LinkedHashMap )
1.HashMap
使用 HashMap定义的Map集合是无序存放的
如果发现重复的 key会将新的数据替换掉已有的数据
使用 HashMap子类保存数据时,key或 value可以保存为null
HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap
2.HashTable
Hashtable和 HashMap很相似,不同之处是 Hashtable线程是安全的,key不允许设置为 null
3.TreeMap
可以排序的Map集合,按集合中的key排序,key不允许重复
最终保存在Map中的数据是经过排序的数据,按其key排序
4.LinkedHashMap
LinkedHashMap 是HashMap的一个子类,保存了记录的插入顺序,因此在遍历的时候会比HashMap效率要低。
不过也有例外情况,当HashMap容量很大,实际存储的数据较少时,遍历起来可能会比LinkedHashMap要慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关
一般情况下,在Map中插入、删除和定位元素,HashMap 是最好的选择。如果需要元素输出的顺序和输入的相同,就需要选择LinkedHashMap了
3. Map集合的功能
Map集合的功能:
1)V put(K key,V value):添加键值对元素
注意事项:
如果key是第一次添加,那么返回的结果为null
如果key是否重复添加,第二次添加,返回的上一次添加的键对应的值
2)V remove(Object key):删除指定的键,返回被删除键对应的值
3)void clear()
4)boolean containsKey(Object key) :是否包含指定的键 (使用居多)
5)boolean containsValue(Object value):是否包含指定的值
4. HashMap 和 HashTable 的区别
共同点:底层数据结构是桶结构(基于哈希表实现)
不同点:
1)HashMap是线程不安全的类,多线程下会造成并发冲突,但单线程下运行效率较高
HashTable是线程安全的类,很多方法都是用synchronized修饰,但同时因为加锁导致并发效率低下,单线程环境效率也十分低
2)插入null:HashMap允许有一个键为null,允许多个值为null
HashTable不允许键或值为null
3)容量:HashMap底层数组长度必须为2的幂,这样做是为了hash准备,默认为16
HashTable底层数组长度可以为任意值,这就造成了hash算法散射不均匀,容易造成hash冲突,默认为11
4)Hash映射:HashMap的hash算法通过非常规设计,将底层table长度设计为2的幂,使用位与运算代替取模运算,减少运算消耗
HashTable的hash算法首先使得hash值小于整型数最大值,再通过取模进行散射运算
5. Map 和 Collection集合的区别
Map和Collection集合的区别:
Collection:只能存储一种类型 Collection<E> 简单记"光棍"
Map集合:可以两种类型的,键的类型,值的类型 Map<K,V> 简单记"夫妻对"
遍历方式不同
Collection:就通过5种方式(List)
Map:两种方式:
方式1:获取所有的K的集合(键的集合)
通过键获取值
方式2: 获取所有的键值对对象Map.Entry<K,V> ("结婚证")
通过键值对对象获取所有的键("结婚证男方")
通过键值对对象获取所有的值("结婚证女方")
有内在联系:
TreeSet集合---->Collection---->间接的使用到了TreeMap集合的put方法
HashSet阶------>Collection---->间接使用到了HashMap的put方法
public class MapDemo {
public static void main(String[] args) {
//创建Map集合:接口
//默认用的是HashMap集合 TreeMap(根据元素排序)
Map<String,String> map = new HashMap<String,String>() ;
System.out.println(map);
//添加功能
// String result = map.put("文章", "马伊琍");
// System.out.println(result);
map.put("文章", "马伊琍");
map.put("王宝强","马蓉") ;
map.put("杨过","小龙女") ;
map.put("郭靖","黄蓉") ;
// String result2 = map.put("文章", "姚笛");
// System.out.println(result2);
map.put("文章", "姚笛");
System.out.println("---------------------------------");
// System.out.println(map.remove("杨过"));
// map.clear();
System.out.println(map.containsKey("周杰伦")) ;
System.out.println(map.containsKey("王宝强")) ;
System.out.println(map.containsValue("郭蓉")) ;
System.out.println(map.containsValue("小龙女")) ;
System.out.println(map);
}
}
6. 遍历
高级功能:
Map遍历功能
方式1:
Set<K> keySet() :获取当前Map集合中的所有的键的集合 (将所有的丈夫集中起来,找对应的妻子)
+
V get(Object key):通过键获取值
方式2:
获取所有的结婚证 (键值对对象)
Set<Map.Entry<K,V>> entrySet()
通过键值对象 获取键 /获取值(通过结婚证找男方/女方)
K getKey()
V getValue()
public class MapDemo2 {
public static void main(String[] args) {
//创建Map集合对象
Map<String,String> map = new HashMap<>() ;
//添加元素
map.put("令狐冲","东方不败") ;
map.put("杨过","小龙女") ;
map.put("陈玄风","梅超风") ;
map.put("郭靖","黄蓉") ;
// Set<K> keySet() :获取当前Map集合中的所有的键的集合
Set<String> keySet = map.keySet(); //推荐第一种方式
//增强for遍历
for(String key: keySet){
//获取所有的键的元素
// V get(Object key):通过键获取值
String value = map.get(key);
System.out.println(key+"="+value);
}
System.out.println("--------------------------------------");
//方式2:
//Set<Map.Entry<K,V>> entrySet()
Set<Map.Entry<String, String>> entry = map.entrySet();
//增强for:遍历键值对对象获取到
for(Map.Entry<String, String> en: entry){
//获取键和值
//K getKey()
// V getValue()
String key = en.getKey();
String value = en.getValue();
System.out.println(key+"="+value);
}
}
}
7. HashMap
HashMap<Student,String>: HashMap<String,Student>
Key: Student类型(姓名和年龄):自定义类型
Value: String(爱好)
Map集合针对键有效:不能迭代顺序恒久不变
HashMap的put方法依赖于hashCode()和equals方法,键的类型必须重写Object类的hashCode和equals方法,保证键唯一!
//public class Student implements Comparable<Student>{
public class Student{
private String name ;
private int age ;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
/*@Override
public int compareTo(Student s) {
//主要条件:学生的年龄从小到大排序
int num = this.age - s.age ;
//如果年龄相同,要比较姓名的内容是否相同
int num2 = (num==0)? (this.name.compareTo(s.name)):num ;
return num2;
}*/
}
public class HashMapDemo {
public static void main(String[] args) {
//创建Map集合对象
HashMap<Student,String> map = new HashMap<>() ;
//创建几个学生对象
Student s1 = new Student("文章",35) ;
Student s2 = new Student("文章",35) ;
Student s3 = new Student("文章",37) ;
Student s4 = new Student("潘玮柏",40) ;
Student s5 = new Student("赵又廷",39) ;
Student s6 = new Student("蔡徐坤",38) ;
Student s7 = new Student("蔡徐坤",38) ;
Student s8 = new Student("肖战",30) ;
map.put(s1,"足球") ;
map.put(s2,"篮球") ;
map.put(s3,"足球") ;
map.put(s4,"吸毒") ;
map.put(s5,"高圆圆") ;
map.put(s6,"乒乓球") ;
map.put(s7,"篮球") ;
map.put(s8,"演戏") ;
//遍历
Set<Student> students = map.keySet();
for(Student key :students){
//通过键获取值
String hobit = map.get(key);
System.out.println(key.getName()+"---"+key.getAge()+"---"+hobit);
}
}
}
8. TreeMap
TreeMap:红黑树结构---针对Map的键按照条件排序---键属于自定义的情况
TreeMap<Integer,String>
TreeMap<Student,String>
存储学生类型(姓名,年龄) ,value:描述"朝代"
键必须唯一而且排序的主要条件:按照学生的年龄从小到大排序
TreeMap的构造方法
public TreeMap():针对键进行自然排序
public TreeMap(Comparator<? super K> comparator):针对键按照比较器进行排序
public class TreeMapDemo {
public static void main(String[] args) {
//创建TreeMap集合对象
//TreeMap<Student,String> tm = new TreeMap<>() ;
//无参构造方法:自然排序 :前提条件:键 的类型必须实现Comparable
//比较器排序:匿名内部类
TreeMap<Student,String> tm = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
//主要条件:学生的年龄从大到小排序
int num = s2.getAge() - s1.getAge() ;
//如果年龄相同,要比较姓名的内容是否相同
int num2 = (num==0)? (s1.getName().compareTo(s2.getName())):num ;
return num2;
}
}) ;
//创建学生对象
Student s1 = new Student("唐伯虎",38) ;
Student s2 = new Student("唐伯虎",38) ;
Student s3 = new Student("秋香",30) ;
Student s4 = new Student("祝枝山",40) ;
Student s5 = new Student("祝枝山",45) ;
Student s6 = new Student("文征明",39) ;
Student s7 = new Student("石榴姐",20) ;
Student s8 = new Student("东香",18) ;
Student s9 = new Student("徐香",18) ;
tm.put(s1,"明朝") ;
tm.put(s2,"宋代") ;
tm.put(s3,"清朝") ;
tm.put(s4,"明朝") ;
tm.put(s5,"现代") ;
tm.put(s6,"唐朝") ;
tm.put(s7,"宋代") ;
tm.put(s8,"明朝") ;
tm.put(s9,"现代") ;
Set<Student> students = tm.keySet();
for(Student key :students){
String value = tm.get(key);
System.out.println(key.getName()+"---"+key.getAge()+"---"+value);
}
}
}
二十. Collection
1. 针对集合操作工具类
Collections:针对集合操作工具类
提供静态功能:
1)public static <T extends Comparable<? super T>> void sort(List<T> list):
//按照自然升序排序(针对List集合排序)
2)public static <T> void sort(List<T> list,Comparator<? super T> c):
//按照比较器排序针对List集合
3)public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T>:
//获取当前自然顺序中List的最大值
4)public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T>:
//最小值
5)public static void reverse(List<?> list):
//对List集合顺序反转
6)public static void shuffle(List<?> list):
//随机置换
public class CollectionsDemo1 {
public static void main(String[] args) {
//创建List集合
List<Integer> list = new ArrayList<>() ;
//添加元素
list.add(10) ;
list.add(50) ;
list.add(15) ;
list.add(25) ;
list.add(5) ;
list.add(12) ;
System.out.println(list);
System.out.println("---------------------------------");
//public static <T extends Comparable<? super T>> void sort(List<T> list):
Collections.sort(list);
System.out.println(list);
System.out.println("----------------------------------");
//public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T>
Integer max = Collections.max(list);
System.out.println(max);
System.out.println("----------------------------------");
System.out.println(Collections.min(list));
System.out.println("-----------------------------------");
Collections.reverse(list);//反转
System.out.println(list);
System.out.println("-----------------------------------");
// public static void shuffle(List<?> list):随机置换
Collections.shuffle(list);
System.out.println(list);
}
}
2. 自定义类型元素
1)public static <T extends Comparable<? super T>> void sort(List<T> list):按照自然升序排序(针对List集合排序)
2)public static <T> void sort(List<T> list,Comparator<? super T> c):按照比较器排序针对List集合
List<Student>:针对List进行自然升序排序,
主要条件:按照学生的年龄从小到大排
次要条件:年龄相同,比较姓名内容是否相同!
//public class Student implements Comparable<Student> {
public class Student {
private String name ;
private int age ;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
/* @Override
public int compareTo(Student s) {
//主要条件:按照学生的年龄从小到大排序
int num = this.age - s.age ;
//如果年龄相同,姓名比(字典顺序)
int num2 = (num==0)?(this.name.compareTo(s.name)):num ;
return num2;
}*/
}
public class CollectionsTest {
public static void main(String[] args) {
//创建List集合对象
List<Student> list = new ArrayList<>() ;
//创建几个学生对象
Student s1 = new Student("gaogaoyuan",42) ;
Student s2 = new Student("gaogaoyuan",40) ;
Student s3 = new Student("liushishi",42) ;
Student s4 = new Student("wanglihong",45) ;
Student s5 = new Student("wenzhang",38) ;
Student s6 = new Student("huazi",35) ;
Student s7 = new Student("huazi",32) ;
Student s8 = new Student("zhangjunjie",20) ;
//添加
list.add(s1) ;
list.add(s2) ;
list.add(s3) ;
list.add(s4) ;
list.add(s5) ;
list.add(s6) ;
list.add(s7) ;
list.add(s8) ;
//排序
//Collections.sort(list); //自然升序排序:针对集合当前存储的类型必须实现Comparable
//使用比较器排序:针对List集合
//public static <T> void sort(List<T> list,Comparator<? super T> c):按照比较器排序针对List集合
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num = s1.getAge() - s2.getAge() ;
int num2 = (num==0)?(s1.getName().compareTo(s2.getName())):num ;
return num2;
}
});
for(Student s:list){
System.out.println(s.getName()+"---"+s.getAge());
}
}
}
3. 斗地主
/*
模拟斗地主,保证牌有序
*/
public class PokerTest2 {
public static void main(String[] args) {
//1)牌盒
//创建一个牌盒Map:HashMap<Integer,String> key:编号 value:牌
HashMap<Integer,String> hm = new HashMap<>();
//创建一个ArrayList集合:存储编号
ArrayList<Integer> arrayList = new ArrayList<>() ;
//2)装牌
//创建点数数组
String[] numbers = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"} ;
//创建花色数组
String[] colors = {"♥","♠","♣","♦"} ;
//拼接
//定义牌的编号:0开始
int index = 0 ;
for(String number:numbers){
for(String color:colors){
String porker = number.concat(color);
//将编号以及牌都添加HashMap集合
hm.put(index,porker) ;
//单独给ArrayList存储编号
arrayList.add(index) ;
index ++ ;
}
}
//给HashMap集合添加小王,大王,给ArrayList添加小王,大王的编号
hm.put(index,"小王") ;
arrayList.add(index) ;
index ++ ;
hm.put(index,"大王") ;
arrayList.add(index) ;
// System.out.println(arrayList);
//3)洗牌:随机置换 ArrayList<Integer> 洗的是编号
Collections.shuffle(arrayList);
// System.out.println(arrayList);
//4)发牌
/*
为了保证牌有序,发也是编号
* 三个人都分别是 TreeSet<Integer>集合
* 创建一个集合:diPai
* 判断:
* 如果角标>=牌盒整个size()-3 底牌
* 如果角标 %3 == 0 第一个人的
* 如果角标 %3 == 1 第二个人的
* %3 == 2 第三个人的
*/
TreeSet<Integer> player1 = new TreeSet<>() ;
TreeSet<Integer> player2 = new TreeSet<>() ;
TreeSet<Integer> player3 = new TreeSet<>() ;
//底牌
TreeSet<Integer> diPai = new TreeSet<>() ;
for(int x = 0 ;x < arrayList.size() ; x ++){
//0开始
if(x >= arrayList.size()-3){
diPai.add(arrayList.get(x)) ;
}else if(x % 3 == 0 ){
player1.add(arrayList.get(x)) ;
}else if(x % 3 == 1){
player2.add(arrayList.get(x)) ;
}else if(x % 3 ==2){
player3.add(arrayList.get(x)) ;
}
}
//看牌://看牌:每一个人都可以看牌,还可以看底牌,所以看牌封装一个功能
lookPoker("张俊杰",player1,hm);
lookPoker("高圆圆",player2,hm);
lookPoker("赵又廷",player3,hm);
lookPoker("底牌",diPai,hm);
}
public static void lookPoker(String name,TreeSet<Integer> ts,HashMap<Integer,String> hm){
//玩家1的牌是:xxx...
//玩家2的牌是:xxx....
System.out.print(name+"的牌是:");
//遍历TreeSet集合,获取每一个编号
for(Integer key: ts){
//获取到每一个编号---在HashMap集合中属于key(键) 编号
String poker = hm.get(key); //在大Map集合中通过键获取值
System.out.print(poker+" ");
}
System.out.println();
}
}
二十一. 异常
1. 异常
1.Throwable:包含所有的错误以及异常! 它是一个超类(父类)
error , Exception
1)error:非常严重问题 (跟代码没有太大有关系)
OOM Out Of Memory:内存溢出 (严重问题)
举例:
手机移动端 (旅游app)
下拉刷新的速度比图片还快(一个activity:界面 一次性刷新很多图片)
2)Exception:异常
编译时期异常和运行时期异常(RuntimeException):程序在运行过程中出现问题(代码书写不严谨)
只要不是RuntimeException的子类都是属于编译时期异常
error----> 在生活中 "地震了,不可抗力的因素"
Exception:异常
编译时期异常: 在程序,运行前需要检查的! 在生活中 "长途旅行之前,检查你的车胎情况"...
运行时期异常:在程序.程序代码逻辑问题(代码不严谨) 在生活中 "no zuo no die"
2. 异常的两种处理方式
标准格式:try...catch...finally
变形格式
1)try{
//可能出现问题的代码
}catch(异常类名 变量名){
//处理异常
}
2)try{
//可能出现问题的代码
}catch(异常类名 变量名){
//处理异常1
}catch(异常类名 变量名){
//处理异常2
}
注意:trt...catch...catch...catch...不能将大的异常放在最前面
//多线程:jdk5以后:Lock:接口 (锁:可重复入的互斥锁)
3)try{
//可能出现问题的代码
}finally{
//释放资源(系统资源)
}
4)try{(jdk7以后提供的)
//可能出现问题的代码
}catch(异常类名1 | 异常类名2 | 异常类名3 ...变量名){ //异常类名必须为同级别
处理异常
}
throws:抛出
例:
public class ExceptionDemo1 {
public static void main(String[] args) {
//定义两个变量
/* int a = 10 ;
int b = 0 ;
System.out.println(a/b); //jvm在内存中会创建当前异常类的对象 new ArithmeticException()*/
//使用try...catch进行捕获异常
try{
//可能出现问题代码
int a = 10 ;
int b = 0 ; //直接获取到的,以后可能值---->通过一些方法获取到的值
System.out.println(a/b);
System.out.println("over");
}catch(ArithmeticException e){ //捕获异常:可以使用大的Exception,但是捕获:具体异常具体捕获
System.out.println("除数不能为0");
}
//....
}
}
//使用try...catch去处理多个异常
public class ExceptionDemo2 {
public static void main(String[] args) {
//method1() ;
//method2() ;
//method3() ;
method4() ;
}
/*
jdk7以后提供的 try{
可能出现问题的代码
}catch(异常类名1 | 异常类名2 | 异常类名3 ...变量名){ //异常类名必须为同级别
处理异常
}
*/
private static void method4() {
try {
int a = 10;
int b = 0;
int[] arr = {4, 5, 6};
System.out.println(a / b);
System.out.println(arr[0]);
System.out.println(arr[3]);
} catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {//包含了所有 编译时期/运行时期异常
System.out.println("程序出问题了...");
}
}
//使用trt...catch...catch...catch...不能将大的异常放在最前面
private static void method3() {
try {
int a = 10;
int b = 1; //可能为0
int[] arr = {4, 5, 6};
arr = null; //空值
System.out.println(a / b);
System.out.println(arr[0]);
System.out.println(arr[3]);
} catch (Exception e) {//包含了所有 编译时期/运行时期异常
System.out.println("程序出问题了...");
}
/* }catch (ArrayIndexOutOfBoundsException e){
System.out.println("访问了数组中不存在的索引");
}catch(ArithmeticException e){// 通过jvm创建当前异常类对象,如果当前e类型匹配就会catch语句
System.out.println("除数不能为0");
}*/
}
//针对多个异常进统一处理,try...catch...catch
private static void method2() {
try{
int a = 10;
int b = 1 ; //可能为0
int[] arr = {4,5,6} ;
arr = null ; //空值
System.out.println(a/b);
System.out.println(arr[0]);
System.out.println(arr[3]);
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("访问了数组中不存在的索引");
}catch(ArithmeticException e){// 通过jvm创建当前异常类对象,如果当前e类型匹配就会catch语句
System.out.println("除数不能为0");
}catch (Exception e){
System.out.println("程序出问题了...");
}
}
//分别针对可能出现问题的代码,进行try...catch(不推荐)
private static void method1() {
try{
int a = 10;
int b = 0 ;
System.out.println(a/b);
}catch (ArithmeticException e){
System.out.println("除数不能为0");
}
//创建一个数组
try{
int[] arr = {4,5,6} ;
System.out.println(arr[0]);
System.out.println(arr[3]);
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("访问了数组中不存在索引");
}
}
}
3. finally,finalize的区别
finally:是处理的一种标准格式
try...catch...finally
finally语句中存储的内容都是释放资源的代码 ---->即使代码出问题了,finally语句也一定会执行
特例:当执行finally语句,jvm退出了
资源对象.close() ;
流对象就需要调用close()
JDBC的连接对象Connection需要close()
...
finalize是一个方法,Object类中的一个方法,该方法调用通过垃圾回收器线程----垃圾回收器GC
当垃圾回收启动时候,寻找内存中没有更多引用的对象,调用finalize()进行内存清楚并释放资源
4. 编译时期异常和运行时期异常
编译时期异常和运行时期异常的区别?
RuntimeException:运行时期异常
很多的子类:NullPointerException,ClassCastException,ArrayIndexOutOfBoundsException....
运行时期异常:
一般程序员逻辑结构不严谨导致的问题,调用者可以进行显示处理(try...catch.../throws)
也可以不进行显示处理,通过逻辑语句进行处理!
编译时期异常:调用者必须显示处理,不处理,编译通过不了,程序运行不了
如果在当前方法中已经去捕获了try...catch...,调用者无序进行处理,
但是如果在方法中抛出异常的,调用者必须处理(捕获/抛出throws)
开发中: 尽量优先使用try...catch异常,其次再是throws
public class ExceptionDemo3 {
public static void main(String[] args) throws Exception {
method1() ;//调用者
/* try {
method2() ;
} catch (ParseException e) {
System.out.println("解析出问题了...");
}*/
method2();
}
//编译时期异常
private static void method2() throws ParseException,ClassNotFoundException,Exception {
//String日期文本--->java.util.Date日期对象(解析)
//捕获异常.调用者不会进行处理
/* try{
String s = "2021-8-3" ;
//创建SimpleDateFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ;
Date dat = sdf.parse(s) ; //parse方法本身就有一个异常,throws ParseException
}catch (ParseException e){
System.out.println("解析出问题了...");
}*/
//在当前方法中使用throws
String s = "2021-8-3" ;
//创建SimpleDateFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd ") ;
Date dat = sdf.parse(s) ;
}
//运行时期异常
private static void method1() {
//显示处理了 (能使用异常处理方式:捕获/抛出throws,就是用他)
/* try{
int a = 10 ;
int b = 0 ;
System.out.println(a/b);
}catch (Exception e){
System.out.println("除数为0了...");
}*/
//并没有做显示处理
int a = 10 ;
int b = 0 ;
if(b!=0){
System.out.println(a/b);
}else{
System.out.println("除数为0了...");
}
}
}
5. 注意
1)有的时候没有办法去抛出,继承关系中,如果子类继承父类,
父类的该方法没有异常,子类重写该方法的时候,只能try...catch
2)子类继承父类,如果父类的该方法本身抛出异常了,那么子类重写该方法的时候,要么跟父类的方法的异常类名一致,要么是该异常的类子类!
public class ExceptionDemo4 {
public static void main(String[] args) {
}
}
class Father{
public void show(){}
public void method() throws ArrayIndexOutOfBoundsException{}
}
class Son extends Father{
/* @Override
public void method() throws Exception { //要么跟他父类的方法异常类名一致,要么是异常类名的子类
}*/
@Override
public void method() throws ArrayIndexOutOfBoundsException {//要么跟他父类的方法异常类名一致,要么是异常类名的子类
}
public void show() {
//将String--->Date
try{
String source = "2021-8-3" ;
//创建SimpleDateFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ;
//解析
Date date = sdf.parse(source) ;
System.out.println(date);
}catch (ParseException e){
System.out.println("解析出问题了...");
}
}
}
6. Throw 和 Throws
面试题
throws和throw的区别?
共同点:都是抛出
用法不同:
1)使用位置不同
throws:
a)将异常抛出在方法声明上
b)在方法名的后面可以跟多个异常类名,中间逗号隔开!
throw
a)在方法的语句体中某个逻辑语句中
b)它后面只能跟异常对象,而不是类名
2)调用者是否处理不同
throws: 调用者必须进行显示处理(try...catch/throws),否则报错
throw: 调用者无须显示处理,一般情况都是在逻辑语句进行处理
3)出现异常是否肯定性
throws:在方法上的,执行某个方法的代码中,可能有问题(表示出现异常的一种可能性)
throw:执行某段代码一定会执行这个异常(表示出现异常的一种肯定性)
4)
throws---->将具体的处理交给jvm---通过jvm吧异常信息打印控制台上
显示的底层源码而且会显示当前错误消息字符串
throw---->程序 中某段代码有问题:只是打印异常类名(jvm处理)
public class ExceptionDemo5 {
public static void main(String[] args) throws ParseException {
// try {
// method();
// } catch (ParseException e) {
// System.out.println("解析问题了...");
// }
method();
// method2();
}
//throw
private static void method2() throws ArithmeticException {
int a = 10 ;
int b = 0 ;
//逻辑语句
if(b!=0){
System.out.println(a/b);
}else{
//抛出一个对象
//throw 匿名对象 new XXXException() ;
throw new ArithmeticException() ;
}
}
//throws
private static void method() throws ParseException {
//将String--->Date
String source = "2021-8-3" ;
//创建SimpleDateFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd ") ;
//解析
Date date = sdf.parse(source) ;
System.out.println(date);
}
}
7. 异常处理方法
不管使用throws/try...catch...finally:
都是要通过jvm调用Throwable里面的功能完成日志(错误信息)打印
try{
//可能出现问题的代码 //首先加载这些代码 (语法校验)
}catch(异常类名 变量名){ //如果存在问题,那么jvm在内存中创建异常的实例,实例是否为catch语句中类型的实例
//类似于 a instanceOf 引用类型:如果是就会执行catch语句
异常处理 //要么手动处理/要么通过jvm进行处理(打印错误日志信息)
}finally{
释放资源...
}
Exception----->Throwable
1)public String getMessage()获取详细消息字符串 同义的方法:public String getLocalizedMessage()
2)public String toString():打印出当前异常信息的简短描述:
内存中异常对象的类名:(全限定名称:包名.类名) 换行
“:”(一个冒号和一个空格) 换行
public String getMessage():消息字符串
3)public void printStackTrace():跟踪堆栈:包含toString以及底层原码(某行代码出现问题)以及本类中的代码问题
都是体现方法上(将当前跟方法相关的底层方法全部跟踪一遍)
finally:不能单独使用,它是结合try...catch....finally:异常的标准格式
finally用法:
特点:
释放相关的资源,代码一定执行的
除非在执行finally,jvm退出了!
public class ExceptionDemo6 {
public static void main(String[] args) {
try{
String source = "2021-8-3" ;
//创建SimpleDateFormat对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd ") ;
//解析
Date date = sdf.parse(source) ;
System.out.println(date);
}catch (ParseException e){
//使用Throwabl里面的功能
//String result = e.getMessage();//获取消息字符串
// System.out.println(result);
// String result = e.toString();
// System.out.println(result);
e.printStackTrace();
System.exit(0);
}finally{
//finally语句代码是一定执行的!
//释放资源
System.out.println("需要释放当前系统资源"); //jdbc( Connection连接对象/执行对象Statement)
//IO流(创建流对象,流对象得释放----系统资源)
//框架里面:Mybatis(JDBC的封装): 执行对象SqlSession
}
//快捷键:针对try---catch---finally: alt+ctrl+t--->try...catch:捕获
}
}
8. 面试题
如果在某个方法中捕获异常,但是该方法有返回值类型,如果在catch语句出现return语句,finally代码还会执行吗?
如果会执行,在return前还是在后
答:finally会执行,但是现在这个代码,在catch语句已经形成返回路径,它会记录最终返回就是30;finally是去释放资源用的,很少牵扯业务代码;都会执行的,除非jvm退出!
public class Test {
public static void main(String[] args) {
int num = getNum(10) ;// i=10
System.out.println(num);
}
private static int getNum(int i) {
try{
i = 20 ; //i = 20 ;
System.out.println(i/0); //除数为0
}catch (ArithmeticException e){
i = 30 ; //i =30
return i ; // return i = return 30 :已经在catch语句形成返回的路径 返回结果就是30
}finally { //finally代码一定会执行,除非jvm退出了
i = 40 ; // i = 40
}
return i; //30
}
}
二十二. 线程
1.线程
线程是依赖于进程的
进程:
能够调用的系统资源的独立单位!
理解:计算机---->打开任务管理器---->客户端软件---->应用进程
计算机开启启动 ---->服务进程(后台进程)
现在的计算机----"多进程计算机" 意义?
主要为了提高CPU的使用率,
在玩游戏的同时,还可以听音乐----->开启游戏的进程,音乐软件的进程 是同时的吗?
不是同时,在一点点(时间片),在两个进程进行高效切换!
线程 :
属于 程序中执行的最小单元(进程中的一条任务线)
一个进程有多个线程组成,多个线程----->线程组(ThreadGroup)
一个线程看成是某个任务,线程的执行具有随机性(多个线程并发执行)以及原子性
多线程的意义?
多线程的特点:具有随机性
多个线程在抢占CPU的执行权
举例:
1v3 打篮球,只能3个人抢占篮球的几率大,并不一定这个3个人就一直能够抢占篮球
有可能1个人抢占的篮球几率大(线程的执行具有随机性)
检验多线程安全问题的标准:
1)是否是多线程环境
2)是否存在共享数据 (必须要有共享数据)
3)是否存在多条语句对共享数据的操作
面试题:
jvm是多线程吗?
是多线程:
至少有两条线程
用户线程main,以及创建对象的时候,当对象使用完毕,需要被垃圾回收器回收;
jvm针对没有更多引用对象,开启一条垃圾回收线程!
java语言能够开启多线程?
开启线程---->开启进程----Java语言不能够开启进程---借助于底层语言C语言开启进程
封装成本地方法 ----->jdk提供类:Thread 里面封装好的方法
开启线程:start()
2. Thread 类(线程的实现方式一)
线程模拟线程环境
开启两条线程
创建线程的实现 方式1:
1)将一个类声明为Thread的子类
2)这个子类应该重写Thread类的run方法
3)然后可以分配并启动子类的实例。
start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,
run()只是一个普通方法,不会出现线程的执行具有随机性(不会互相抢占cpu执行权)
只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新的线程
不会出现两个线程并发执行
public class ThreadDemo {
public static void main(String[] args) {
//3)创建Thread类的子类对象
MyThread my1 = new MyThread() ;//第一个线程对象
MyThread my2 = new MyThread() ; //第二个线程对象
//4)启动
//my1.run();
//my2.run();
/* my1.start();
my1.start();
my1只是代表一个线程对象
my1将start方法调用两次---->IllegalThreadStateException:非法线程状态异常
start()原码: 校验当前线程状态:如果线程已经启动了,就不能再启动
*/
my1.start();//start():有jvm调用底层run方法,出现并发执行
my2.start();
}
}
//线程类
public class MyThread extends Thread {
//重写Thread类的方法
@Override
public void run() {
//run方法里面:一般情况耗时的操作
for(int x = 0 ; x < 200 ; x ++){
System.out.println(x);
}
}
}
3. Thread类的方法
Thread类的构造方法:
Thread(String name):创建线程类对象,设置名称
Thread类的成员方法
1)public final String getName():获取线程名称
2)public final void setName(String name):设置线程名称
3)线程的优先级
a)Thread类中静态常量字段(成员变量field)
b)public static final int MAX_PRIORITY 10 最大优先级
c)public static final int MIN_PRIORITY 1 最小优先级
d)public static final int NORM_PRIORITY 5 默认优先级
e)public final void setPriority(int newPriority):设置线程的优先级
f)public final int getPriority():获取优先级
优先级越大的:抢占CPU的执行权越大
优先级小的:抢占CPU的执行权越小
默认优先级:随机性大一些
public class ThreadDemo2 {
public static void main(String[] args) {//用户线程
//创建MyThread2类的对象
MyThread2 t1 = new MyThread2() ;
MyThread2 t2 = new MyThread2() ;
MyThread2 t3 = new MyThread2() ;
// public final void setName(String name):设置线程名称
t1.setName("洪学佳") ;
t2.setName("张俊杰") ;
t3.setName("高圆圆");
t1.setPriority(10); //最大优先级
t2.setPriority(1);//最小优先级
int num1 = t1.getPriority();
int num2 = t2.getPriority();
int num3 = t3.getPriority();
System.out.println(num1+"---"+num2+"---"+num3);
//启动线程
t1.start();
t2.start();
t3.start();
}
}
public class MyThread2 extends Thread {
//t1,t2
@Override
public void run() {
for(int x = 0 ; x < 100 ; x ++){
//获取线程名称
System.out.println(this.getName()+":"+x);
}
}
}
4. 方法
1)public final void join() throws InterruptedException ://等待该线程终止!
2)public static void yield(): //暂停当前正在执行的线程,执行对方线程
3)public final void setDaemon(boolean on) //守护线程
5. join
public final void join() throws InterruptedException:等待该线程终止!
底层依赖于线程安全的方法join(0):永远等待(当前执行完毕结束!)
又依赖于wait(long time)
考虑安全问题:
三个线程---优先级 都是默认(5)
都去设置join(): 三个线程谁先抢占到谁先执行
public class ThreadJoinDemo {
public static void main(String[] args) {
//创建三个线程
JoinThread jt1 = new JoinThread() ;
JoinThread jt2 = new JoinThread() ;
JoinThread jt3 = new JoinThread() ;
//设置线程名称
jt1.setName("李渊") ;
jt2.setName("李世民") ;
jt3.setName("李元霸") ;
//启动线程
jt1.start();
//jt1调用join
try {
jt1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
jt3.start();
}
}
public class JoinThread extends Thread{
@Override
public void run() {
for(int x =0 ; x < 100 ; x ++){
System.out.println(getName()+":"+x);
}
}
}
6. yield
public static void yield()://暂停当前正在执行的线程,执行对方线程
public class ThreadYieldDemo {
public static void main(String[] args) {
//创建两条线程对象
YieldThread yt1 = new YieldThread() ;
YieldThread yt2 = new YieldThread() ;
//设置名称
yt1.setName("高圆圆") ;
yt2.setName("赵又廷") ;
//启动线程
yt1.start();
yt2.start();
}
}
public class YieldThread extends Thread{
//yt1/yt2
@Override
public void run() {
for(int x = 0 ; x <100 ; x ++){
System.out.println(getName()+":"+x);
Thread.yield(); //暂停当前线程,执行对方线程
}
}
}
7.setdaemon
public final void setDaemon(boolean on)
参数为true,表示标记当前线程为守护线程,当正在运行的线程如果都是守护线程,则jvm自动退出
这个方法必须在启动线程之前调用(start()之前)
举例:
玩游戏:坦克大战
这个两个坦克----守护线程
如果运行的线程都是守护线程,jvm退出,运行的线程不会立即停止掉!
public class ThreadDaemonDemo {
public static void main(String[] args) {
//创建两个线程
ThreadDaemon td1 = new ThreadDaemon() ;
ThreadDaemon td2 = new ThreadDaemon() ;
//设置名称
td1.setName("张飞");
td2.setName("关羽");
//设置为守护线程
td1.setDaemon(true) ;
td2.setDaemon(true) ;
//启动线程
td1.start();
td2.start();
//public static Thread currentThread():获取正在运行的线程执行对象的引用
Thread.currentThread().setName("刘备");
//提供for循环:
for(int x = 0 ; x < 5 ; x ++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}
8. 电影院售票
电影院有三个窗口,共同出售100张票,使用多线程创建方式1来进行实现!
分析
1)自定义类SellTicket extends Thread
tickets:票 = 100张;
2)重写run方法
耗时的操作 :模拟一致有票,使用while(true)
3)在main用户(主线程)创建三个 sellTicket 对象
4)分别设置线程名称:窗口1,窗口2,窗口3
5)分别启动线程
方式1 多线程的创建方式存在弊端 (会出现重票)
1)它是一个继承关系, 具有"局限性"
2)不能够体现资源共享的概念----- 因为Thread类使用到的静态代理 (设计模式)
st1,st2,st3 :三个栈内存变量
分别需要new对象
线程的创建方式 第二种方式优于第一种
public class SellTicketTest {
public static void main(String[] args) {
//创建三个窗口
SellTicket st1 = new SellTicket() ;
SellTicket st2 = new SellTicket() ;
SellTicket st3 = new SellTicket() ;
//设置线程名称
st1.setName("窗口1");
st2.setName("窗口2");
st3.setName("窗口3");
//启动线程
st1.start() ;
st2.start() ;
st3.start() ;
}
}
public class SellTicket extends Thread {
//需要保证票被共用:使用static修饰
public static int tickets = 100 ;
//重写run方法
//st1,st2,st3
@Override
public void run() {
//模拟一直有票
while(true){
//模拟网络延迟 :睡眠的过程
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(tickets>0){ //100>0
System.out.println(getName()+"正在出售第"+(tickets--)+"张票");
}
/**
* 出现同票的原因:存在: 线程的执行 原子性操作 (++,-- :简单,最直接的操作)/延迟性操作
* st1(窗口1)---->ticket 记录100 正在第100张票
* st3(窗口3)---->窗口1准备执行ticket--(100-1=99),抢占到执行权了,正在出售100张票
*/
}
}
}
9.Runnable(线程的实现方式二)
1)自定义类实现Runnable接口
2)重写Runnable接口的run方法
3)在main用户线程中
可以分配类的实例(创建类的实例)
4)创建当前类对象,然后创建Thread类对象,将当前类对象作为参数来传递
当前类---->"资源共享类"
Thread(Runnable target, String name)
5)分别启动线程即可!
public class ThreadDemo {
public static void main(String[] args) {
//可以分配类的实例(创建类的实例)
MyRunnable my = new MyRunnable() ; //资源类:被多线程共享//具体类new 具体类
//创建两个线程类对象
Thread t1 = new Thread(my,"张俊杰") ;
Thread t2 = new Thread(my,"高圆圆") ;
//分别启动线程
t1.start();
t2.start();
}
}
//资源类
public class MyRunnable implements Runnable {
@Override
public void run() {
//耗时的操作
for(int x = 0 ; x < 100 ; x ++){
//public static Thread currentThread()
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}
第二种实现方式:
静态代理
特点:真实角色和代理角色必须实现同一个接口
真实角色:专注于自己的功能
代理角色:完成对真实角色功能的"增强"
/*
结婚:
结婚这个这个情况
真实角色:You 你自己
代理角色:WeddingCompany 婚庆公司
*/
public class ThreadDemo {
public static void main(String[] args) {
//接口多态
//Mary mary = new You() ;
You mary = new You() ;
mary.mary();
System.out.println("----------------------");
//静态代理:通过婚庆公司帮助自己You来完成结婚
//真实角色
You you2 = new You() ; // MyRunnable
WeddingCompany wc = new WeddingCompany(you2) ;// Thread类对象
wc.mary();
}
}
//定义一个接口的接口
interface Mary{
void mary() ;//结婚
}
//自己:真实角色
class You implements Mary{
@Override
public void mary() {
System.out.println("结婚了,很开心...");
}
}
//代理角色:婚庆公司 在你结婚之前,它可以给你布置婚礼线程, 结婚之后,开开心心吃席
class WeddingCompany implements Mary{
//将真实角色作为参数传递
private You you ;
public WeddingCompany(You you){
this.you = you ;
}
@Override
public void mary() {
System.out.println("给你布置婚礼现场...");
you.mary(); //只要专注于自己的事情!
System.out.println("婚礼线程布置完毕,吃席...");
}
}
10. synchronized
1.同步代码块
synchronized(锁对象){
多条语句对共享数据的操作
}
锁对象:必须为多个线程的同一把锁;锁对象可以是任意Java的类对象
面试题:
wait()方法/notify()方法 ---- 也可以称为"同步" --->等待唤醒机制
线程等待/线程唤醒 这些方法为什么不定义在Thread类中呢,而是定义Object类中呢?
它和锁对象有关系,而锁对象:可以任何的java类对象,-----Object(顶层父类)
syncronized(锁对象){
锁对象.wait()
//锁对象.notify()
}
电影院卖票---100张票,三个窗口同时出售,
第二种方式进行实现,更能体现"资源共享"
模拟真实场景:网络延迟,加入了睡眠操作
1)可能出现一张票被卖多次(同票)
2)可能出现负票(-1)
存在安全问题:
窗口1正在出售第23张
窗口3睡醒了之后,抢占到了 出售第22张... 如果出现第23张
出现负票: 线程的执行具有随机性, ---加入延迟!
窗口1正在出售第1张票,延迟睡眠150秒 t3醒来之后
窗口3正在出售第0张票, --
t2也在某一刻同时醒来并执行语句---- 窗口2正在出售第-1张票
检验多线程安全问题的标准;
1)是否是多线程环境 是 不能更改,使用多线程实现
2)是否存在共享数据 是 (资源类的数据: tickets 票) 必须要有共享数据
3)是否存在多条语句对共享数据的操作 是 解决点
解决----Java提供同步机制:同步代码块 将多条对共享数据包裹起来
synchronized(锁对象){
将多条对共享数据包裹起来
}
锁对象:必须要多个线程使用的同一个锁对象,而不是分别自己的锁对象!
(生活中:火车上上厕所---> 当一个人进去之后,门一关,其他人进不来..)
public class SellTicket implements Runnable {
//成员变量;100张票
public static int tickests = 100 ;
//创建一个锁对象:
//public Object obj = new Object() ;
//创建Demo类对
Demo d = new Demo() ;
//t1,t2,t3
@Override
public void run() {
//模拟一直票
while(true){
//t1先抢占到CPU执行权
//t1,t2,t3在抢占CPU执行权
//同步代码块
synchronized (d){ //t1进来之后,别的t2,t3线程进不来的
//t3进来,t1,t2进不来
if(tickests>0){//100>0
try {
Thread.sleep(100); //单位为毫秒数
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickests--)+"张票");
}
}//t1出来,t3出来---回到同步代码块之前,继续抢
//锁的释放,同步结束之后就会释放锁(同步锁)
}
}
}
class Demo{}
public class SellTicketTest {
public static void main(String[] args) {
//创建资源类对象SellTicket
SellTicket st = new SellTicket() ;
//创建三个线程类对象
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//分别启动线程
t1.start();
t2.start();
t3.start();
}
}
public class SellTicketTest {
public static void main(String[] args) {
//创建资源类对象SellTicket
SellTicket st = new SellTicket() ;
//创建三个线程类对象
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//分别启动线程
t1.start();
t2.start();
t3.start();
}
}
public class SellTicket implements Runnable {
//成员变量;100张票
public static int tickests = 100 ;
//创建一个锁对象:
public Object obj = new Object() ;
//t1,t2,t3
@Override
public void run() {
//模拟一直票
while(true){
//t1,t2,t3
//解决方案:
//将多条语句对共享数据的操作包裹起来
//synchronized (new Object()){ //锁对象 :三个线程分别使用自己的锁
//必须为是同一个锁对象
synchronized (obj){
//模拟网络延迟
//判断
if(tickests>0){//100>0
//t1先进来,睡眠150毫秒,t1已经睡完了,执行下面的操作
//t3先进来,睡眠150毫秒,t3醒来之后
//t2最后抢占到,醒来之后
try {
Thread.sleep(100); //单位为毫秒数
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出窗口信息
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickests--)+"张票");
}
}
/**
* 出现同票: 线程的原子性操作(++,--:最简单的操作)
* 原子性:记录原始数据值,然后再对数据自增或者自减
* t1执行的操作, 窗口1正在出售第 23张票 ,当准备--的时候,t3将t1里面tickets--还没执行的时候,就已经打印了
* 窗口3正在出售第23张票
*
* t2进来之后,tickets-- 动作完成了 22张
* 窗口2正在出售第22张票
*
* 出现负票: 线程的执行具有随机性, ---加入延迟!
*
* 窗口1正在出售第1张票,延迟睡眠150秒 t3醒来之后
* 窗口3正在出售第0张票, --
* t2也在某一刻同时醒来并执行语句---- 窗口2正在出售第-1张票
*
*/
}
}
}
2. 同步方法
什么是同步方法? 如果一个方法的方法体的第一句话就是同步代码块
可以将synchronized关键字提取到方法声明上,跟在权限修饰符的后面
权限修饰符 synchronized 返回值类型 方法名(形式列表){ //非静态的同步方法
业务逻辑...
}
锁作用于方法:
锁对象是什么? 默认都是非静态的同步锁的是this:当前类对象的地址值引用
静态的同步方法:锁对象,跟类相关: 当前类的字节码文件对象: 类名.class
public class SellTicket implements Runnable {
//定义100张票
public static int tickets = 100 ;
//定义一个统计变量
int x = 0 ;
public Object obj = new Object() ;//锁对象obj
@Override
public void run() {
while(true){
if(x % 2 ==0){ //
// synchronized (obj){
// synchronized (this){ //跟下面的非静态同步方法锁对象一致
synchronized (SellTicket.class){ //跟下面的静态的同步方法锁对象一致
if(tickets>0){
//睡眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}
}else{
sellTicket() ; //同步方法
}
x ++ ; //x=1
}
}
//调用了一个卖票 方法
// sellTicket() ;
//同步方法
// public synchronized void sellTicket() {//sellTicket 的锁对象是什么? 默认都是非静态的同步锁的是this:当前类对象的地址值引用
public static synchronized void sellTicket() { //静态的同步方法:锁对象,跟类相关: 当前类的字节码文件对象: 类名.class
if(tickets>0){
//睡眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
//创建资源类对象
SellTicket st = new SellTicket() ;
//创建线程类对象 将st作为参数传递
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//启动线程
t1.start();
t2.start();
t3.start();
}
}
11. 优化
1. 死锁
线程安全问题:可以通过同步方法或者是同步代码块去解决,但是执行过程中就可能出现死锁问题
死锁问题:
(使用同步机制解决线程安全) 线程和线程之间出现了互相等待的情况!
解决方案:
多个线程之间的通信:必须使用的是一个资源类对象,而不能是每一个线程在使用自己的资源类对象!
使用生成者和消费者模式思想去解决,前提条件:生成者线程和消费者线程 必须操作的同一个资源类对象!
//死循环
//锁对象
public class MyMonitor {
//提供两把锁对象
public static final Object objA = new Object() ;
public static final Object objB = new Object() ;
}
//资源类
public class DieLock implements Runnable {
private boolean flag ;//标记值
public DieLock(boolean flag){
this.flag = flag ;
}
@Override
public void run() {
//判断标记值
//t1 ---->DieLock(true)
//t2 ---->DieLock(false)
if(flag){
//t1
synchronized (MyMonitor.objA){
System.out.println("if ObjeA");//"if objA"
synchronized (MyMonitor.objB){
System.out.println("if objB");// "if objB"
}
}
}else{
//t2
synchronized (MyMonitor.objB){
System.out.println("else ObjB"); //else objB
synchronized (MyMonitor.objA){
System.out.println("else objA"); //"else objA"
}
}
}
/**
* t2线程先抢到了
* else ObjB ---->等待ObjA锁释放
* if ObjeA ---->等待ObjB锁释放
*
* t1线程先抢占到了
* if ObjeA
* else ObjB
*/
}
}
2. 优化1
/**
* @author Kuke
* @date 2021/8/5
* 使用生成者和消费者思想模式---解决线程死锁问题
* 1)StuffBun包子类属性
* 包含包子的名称name
* 包子的大小type
* 2)生产者资源类 SetBun 产生包子
* 3)消费者资源类 GetBun 使用包子
* 4)ThreadDemo:main 用户线程
* 按照上面的方式:模拟生产者产生数据,消费者使用数据出现问题 null---null
* 生产资源类中和消费者资源类中所操作的包子对象不是同一个对象!
* 可以将包子通过生产资源类或者消费者资源类 通过构造方法传递
* 优化1:
* 加入while循环,模拟包子一直生产和一直消费!
* 出现问题:数据紊乱:加入同步代码块给每一个资源类中都加入解决! 将多条语句对共享数据的操作包起来!
*/
public class ThreadDemo {
public static void main(String[] args) {
//创建一个包子对象
StuffBun sbu = new StuffBun() ; //同一个对象
//创建生产资源类对象
SetBun sb = new SetBun(sbu) ;
//消费者资源类对象
GetBun gb = new GetBun(sbu) ;
//创建线程了对象
Thread t1 = new Thread(sb) ;//生产者资源类所在的生产者线程
Thread t2 = new Thread(gb) ;//消费者资源类所在的消费者线程
t1.start();
t2.start();
}
}
//包子类
public class StuffBun {
//成员变量不私有化
String name ;//包子的类型(肉包子,菜包子)
String bunType ;//大包子/小包子
}
//生产者资源类(做包子)
public class SetBun implements Runnable {
//声明这个包子类
private StuffBun stu ;
public SetBun(StuffBun stu){
this.stu = stu ;
}
//定义一个统计变量
int x = 0 ;
@Override
public void run() {
//产生包子
/* StuffBun stu = new StuffBun() ;
stu.name = "肉包子" ;
stu.bunType = "大类型";*/
//不断的产生数据
while(true){
synchronized (stu){
if(x % 2 == 0){//t1
stu.name = "肉包子" ;
stu.bunType = "大包子";
}else{
stu.name = "菜包子" ;
stu.bunType = "小包子" ;
}
}
x ++ ;
}
}
}
//消费者资源类(吃包子)
public class GetBun implements Runnable {
//声明包子类的变量stb
private StuffBun stb ;
public GetBun( StuffBun stb){
this.stb = stb ;
}
@Override
public void run() {
//模拟要使用数据
// StuffBun stb = new StuffBun() ;
//不断使用数据
while(true){
synchronized (stb){
System.out.println(stb.name+"---"+stb.bunType);
}
}
}
}
3. 优化2 wait ()+notify()
使用生成者和消费者思想模式---解决线程死锁问题
1)StuffBun包子类属性
包含包子的名称name
包子的大小type
2)生产者资源类 SetBun 产生包子
3)消费者资源类 GetBun 使用包子
4)ThreadDemo:main 用户线程
优化2:
想出现依次打印
肉包子---大包子
菜包子---小包子
...
wait()+notify()--->实现同步机制(并且同时信号法:将死锁问题解决!)
//包子类
public class StuffBun {
//成员变量不私有化
String name ;//包子的类型(肉包子,菜包子)
String bunType ;//大包子/小包子
//定义标记:表示是否存在包子数据
boolean flag ; //默认false,没有数据
}
//消费者资源类
public class GetBun implements Runnable {
//声明包子类的变量stb
private StuffBun stu ;
public GetBun( StuffBun stu){
this.stu = stu ;
}
@Override
public void run() {
//模拟要使用数据
// StuffBun stb = new StuffBun() ;
//不断使用数据
while(true){
synchronized (stu){
//如果当前消费资源类中存在包子数据,先等待消费使用完毕数据
if(!stu.flag){
//等待使用完毕数据
try {
stu.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(stu.name+"---"+stu.bunType);
//改变信号值
//如果包子消费完毕
stu.flag = false ;
//唤醒对方线程(生产者资源类,别等了,产生数据)
stu.notify();
}
}
}
}
//生成者资源类
public class SetBun implements Runnable {
//声明这个包子类
private StuffBun stu ;
public SetBun(StuffBun stu){
this.stu = stu ;
}
//定义一个统计变量
int x = 0 ;
@Override
public void run() {
//产生包子
/* StuffBun stu = new StuffBun() ;
stu.name = "肉包子" ;
stu.bunType = "大类型";*/
//不断的产生数据
while(true){
synchronized (stu){
//如果当前生产者没有语句,需要等待生成产生数据
if(stu.flag){
//锁对象调用wait发那个发
try {
stu.wait();//释放锁对象...
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(x % 2 == 0){//t1
stu.name = "肉包子" ;
stu.bunType = "大包子";
}else{
stu.name = "菜包子" ;
stu.bunType = "小包子" ;
}
//如果现在有数据了
//改变信号
stu.flag = true ;//有数据类
//通知(唤醒)消费者线程,赶紧使用数据
stu.notify(); //唤醒对方线程
}
x ++ ;
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
//创建一个包子对象
StuffBun sbu = new StuffBun() ; //同一个对象
//创建生产资源类对象
SetBun sb = new SetBun(sbu) ;
//消费者资源类对象
GetBun gb = new GetBun(sbu) ;
//创建线程了对象
Thread t1 = new Thread(sb) ;//生产者资源类所在的生产者线程
Thread t2 = new Thread(gb) ;//消费者资源类所在的消费者线程
t1.start();
t2.start()
}
}
//包子类
// 所有属性私有修饰
public class StuffBun {
//成员变量不私有化
private String name ;//包子的类型(肉包子,菜包子)
private String bunType ;//大包子/小包子
//定义标记:表示是否存在包子数据
private boolean flag ; //默认false,没有数据
//提供给包子数据进行赋值的方法
public synchronized void set(String name,String bunType){ //锁对象是this:非静态的同步方法
//如果当前生产者没有语句,需要等待生成产生数据
if(this.flag){
//锁对象调用wait发那个发
try {
this.wait();//释放锁对象...
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//赋值
this.name = name ;
this.bunType = bunType ;
//如果现在有数据了
//改变信号
this.flag = true ;//有数据类
//通知(唤醒)消费者线程,赶紧使用数据
this.notify(); //唤醒对方线程
}
//提供方法:获取包子的数据
public synchronized void get(){ //非静态的同步方法:锁对象 this
//如果当前消费资源类中存在包子数据,先等待消费使用完毕数据
if(!this.flag){
//等待使用完毕数据
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.name+"---"+this.bunType);
//改变信号值
//如果包子消费完毕
this.flag = false ;
//唤醒对方线程(生产者资源类,别等了,产生数据)
this.notify();
}
}
// 生成者资源类
public class SetBun implements Runnable {
//声明这个包子类
private StuffBun stu ;
public SetBun(StuffBun stu){
this.stu = stu ;
}
//定义一个统计变量
int x = 0 ;
@Override
public void run() {
//产生包子
/* StuffBun stu = new StuffBun() ;
stu.name = "肉包子" ;
stu.bunType = "大类型";*/
//不断的产生数据
while(true){
if(x % 2 == 0){//t1
//stu.name = "肉包子" ;
//stu.bunType = "大包子";
stu.set("肉包子","大包子");
}else{
//stu.name = "菜包子" ;
//stu.bunType = "小包子" ;
stu.set("菜包子","小包子");
}
x ++ ;
}
}
}
//消费者资源类
public class GetBun implements Runnable {
//声明包子类的变量stb
private StuffBun stu ;
public GetBun( StuffBun stu){
this.stu = stu ;
}
@Override
public void run() {
//模拟要使用数据
// StuffBun stb = new StuffBun() ;
//不断使用数据
while(true){
stu.get();//获取包子数据
}
}
}
//测试类
public class ThreadDemo {
public static void main(String[] args) {
//创建一个包子对象
StuffBun sbu = new StuffBun() ; //同一个对象
//创建生产资源类对象
SetBun sb = new SetBun(sbu) ;
//消费者资源类对象
GetBun gb = new GetBun(sbu) ;
//创建线程了对象
Thread t1 = new Thread(sb) ;//生产者资源类所在的生产者线程
Thread t2 = new Thread(gb) ;//消费者资源类所在的消费者线程
t1.start();
t2.start();
}
}
12.Lock锁
JDK5以后提供java.util.current.locks.Lock :提供比syncrhonized方法(/同步代码块)更具体的锁定操作
多个线程并发访问,抢占共享资源数据,通过lock实现多个线程对某个共享资源进行独占访问,不会安全问题!
Lock是一个接口
void lock()获取锁
void unlock() 试图释放锁
提供跟具体的子实现类:
ReentrantLock
//资源类---需要被多个线程进行共享
public class SellTicket implements Runnable {
//定义100张票
private static int tickets = 100 ;
//创建一个锁对象
Lock lock = new ReentrantLock() ;
@Override
public void run() {
//模拟一只有票
while(true){
//通过锁对象--->获取锁
lock.lock();
//try...catch...finaly:捕获异常
//使用try..finally
try{
//判断
if(tickets>0){
//睡眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}else{
break ;
}
}finally {
//释放锁
lock.unlock();
}
}
}
}
public class LockDemo {
public static void main(String[] args) {
//创建共享资源类对象
SellTicket st = new SellTicket() ;
//创建三个线程类对象
Thread t1 = new Thread(st,"窗口1") ;
Thread t2 = new Thread(st,"窗口2") ;
Thread t3 = new Thread(st,"窗口3") ;
//启动线程
t1.start();
t2.start();
t3.start();
}
}
13.线程组(了解)
线程组 ThreadGroup
线程组代表一组线程。 此外,线程组还可以包括其他线程组
Thread:----> public final ThreadGroup getThreadGroup() {//获取线程组
return group;
}
//给线程设置默认的线程组名称
//public Thread(ThreadGroup group, Runnable target) {
ThreadGroup:
public final String getName() {:获取默认的线程组名称
return name;
}
构造方法;
public ThreadGroup(String name) {}
线程组: 将线程可以都添加一组中,方便管理,
线程启动完毕之后,线程终止之后,不会将这个线程对象在内存中重复利用
任务队列--------一个任务称为"一个线程" , Quene
public class MyThread implements Runnable {
@Override
public void run() {
for(int x = 0 ;x < 100 ; x ++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}
public class ThreadGroupDemo {
public static void main(String[] args) { //jvm调用main方法
// method1();
method2() ;
}
//设置一个新的线程组名称
private static void method2() {
//创建一个线程组对象--同时设置线程组名称
ThreadGroup tg = new ThreadGroup("myMain") ;
//创建两条线程对象
MyThread my = new MyThread() ;
Thread t1 = new Thread(tg,my) ;
Thread t2 = new Thread(tg,my) ;
//获取线程组对象并同时线程组名称
String name1 = t1.getThreadGroup().getName();
String name2 = t2.getThreadGroup().getName();
System.out.println(name1+"---"+name2);
}
private static void method1() {
//创建两个线程
MyThread my = new MyThread() ;
Thread t1 = new Thread(my) ;
Thread t2 = new Thread(my) ;
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
String name1 = tg1.getName();
String name2 = tg2.getName();
System.out.println(name1+"---"+name2); //默认线程组名称都是main
}
}
14.线程池(重点)
线程池 属于 "池"化技术 ------>相似 数据库连接池(dbcp,c3po,druid(为监控而生))
特点:
在内存中创建一个固定可重用的线程数,当前线程执行完毕终止了,不会被回收掉,再次回到线程池中,等待下一次利用!
弊端: 维护成本大
ExecutorService---接口
通过 工厂类:
Exceutors
创建一个固定的可重用的线程数,返回值就线程池对象
public static ExecutorService newFixedThreadPool(int nThreads)
ExecutorService
提交队列任务(多个线程并发执行)
<T> Future<T> submit(Callable<T> task)
提交值返回任务以执行,并返回代表任务待处理结果的Future。
Future<?> submit(Runnable task)
submit的返回值:异步计算的结果,如果不做计算,无须返回结果!
public class ThreadPoolDemo {
public static void main(String[] args) {
//通过工厂类创建线程池对象
ExecutorService threadPool = Executors.newFixedThreadPool(2);
//提交异步方法
//MyRunnable:打印x的值0-99之间的数据,不需要返回结果
// threadPool.submit(new MyRunnable()) ;
// threadPool.submit(new MyRunnable()) ;
// <T> Future<T> submit(Callable<T> task)
//Callable:提交异步计算---需要重写Callable的call来计算结果;如果没有结果,直接在call无须返回
threadPool.submit(new MyCallable()) ;
threadPool.submit(new MyCallable()) ;
//void shutdown()关闭线程池
threadPool.shutdown();
}
}
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
for(int x = 0 ; x < 100 ; x++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
return null;
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int x = 0 ; x < 100 ; x ++){
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}
15.设计原则
设计原则:
开闭原则:
对现有代码修改关闭,对扩展代码开放
举例:
项目开发完毕,进行更新,不能够修改现有代码,在现有代码的基础上提供扩展!
接口分离原则
一个接口中定义一个功能,接口和接口之间独立的,不能相互影响
实际开发中:
某个接口中,可能将相关的功能都定义在这一个接口中
按模块划分:
用户模块 UserDao 接口
login()/register()/logout()/checkUserName()
商品模块
ProductDao 接口
Product findById(Integer id) ;
List<Product> findAll() ;
void update(Product product);
订单模块
OrderDao 接口
List<Order> findPage(int pageSize,int currentPage);
里氏替换原则:任何父类出现的地方都可以子类替代!
class Father{
public void show(){
// ...
Class<Son> clazz = Son.class ;
//反射方式---->字节码文件对象就调用method----->将所有的成员方法---->Method
}
}
class Son extends Father{
public void method(){
}
}
23种设计模式都需要遵循 原则"低耦合,高内聚"
设计模式是一种思想,前人总结出来,不是技术!
创建型:对象的创建 (使用最多)
结构型:整个结构的组成
代理模式
静态代理
代理角色
真实角色
行为型:具备功能性的
16. 工厂方法:
创建型设计模式:
简单工厂:----称为 静态工厂方法模式
优点:利用多态创建子类对象,能够灵活去创建对象(提供的静态功能)
弊端:代码量大,一旦有新的类型增加,工厂类的静态功能就需要改动...
1工厂方法模式
1)抽象类:Animal 动物类 (基类)
2)提供一些子类,完成方法重写:eat(){} sleep(){}
3)提供一个接口 :
Factory
public Animal createAnimal() ;
DogFactory /CatFactory具体的子实现类实现对象的创建
优点:具体的动物创建,交给类工厂类来实现 (面向接口编程),便于功能维护(代码维护)
弊端:代码量非常大
如果有新的类中增加,那么还必须提供对应具体的工厂类创建当前类实例!
/*
* 工厂接口:
* 具体的对象的创建工作----交给工厂接口(抽象类)的子实现类完成!
*/
public interface Factory {
public Animal createAnimal() ;
}
//抽象的动物类
public abstract class Animal {
public abstract void eat() ;
public abstract void sleep() ;
}
/*
* 猫的工厂类
*/
public class CatFactory implements Factory{
@Override
public Animal createAnimal() {
return new Cat();
}
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void sleep() {
System.out.println("猫趴着睡觉...");
}
}
//狗的工厂类
public class DogFactory implements Factory{
@Override
public Animal createAnimal() { //接口多态中---->抽象类多态
return new Dog();
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头...");
}
@Override
public void sleep() {
System.out.println("狗躺着睡觉...");
}
}
public class FactoryPattern {
public static void main(String[] args) {
//没有工厂方法之前:
//使用抽象类多态
Animal a = new Dog() ;
a.eat();
a.sleep();
System.out.println("-------------------------------");
a = new Cat() ;
a.eat();
a.sleep();
//使用工厂方式模式来操作
System.out.println("-------------------------------------");
Factory factory = new DogFactory() ;
Animal animal = factory.createAnimal();
animal.eat();
animal.sleep();
System.out.println("---------------------------------------");
factory = new CatFactory() ;
Animal a2 = factory.createAnimal();
a2.eat();
a2.sleep();
}
}
二十三. 单列模式
1.单列模式:
单例模式:
始终在内存中创建一个实例
分为两种:
饿汉式:永远不会出现问题的单例模式(最简单的一种单例)
懒汉式:可能出现问题的一种单例模式
2.饿汉式
饿汉式:
1)构造方法私有:保证外界不能直接创建当前类对象
2)在当前类的成员位置:创建当前类实例
3)提供一个静态的功能,返回值就是当前类本身(需要当前类的实例)
public class Student {
//成员变量
// public static Student s = new Student() ; //当前类实例
private static Student s = new Student() ; //当前类实例 (静态实例变量)
private Student(){}
//静态功能
//返回是当前类本身
public static Student getStudentInstance(){
return s;
}
}
public class Single_Pattern_01 {
public static void main(String[] args) {
// Student s1 = new Student() ;
//Student s2 = new Student() ;
// System.out.println(s1==s2);
// System.out.println(s1.getClass()==s2.getClass());
//class com.qf.single_pattern.Student
//调用公共方法
Student s1 = Student.getStudentInstance();
Student s2 = Student.getStudentInstance();
Student s3 = Student.getStudentInstance();
Student s4 = Student.getStudentInstance();
System.out.println(s1==s2);
System.out.println(s1==s3);
System.out.println(s1==s4);
// Student.s = null ; // 赋值null,堆内存不开辟空间(外界可以更改当前类地址值)
//当前这个成员变量的实例s应该被私有修饰(保证安全性)
}
}
3. 懒汉式
懒汉式:可能出现问题的一种单例模式
1)构造方法私有化
2)在当前成员变量的位置声明变量:数据类型就是当期类 (私有的,静态的)
3)提供静态功能,返回值还是当前类本身
判断如果当前没有给当前类型变量为null,直接new 当期类的实例
如果不为null,就返回当前类的变量!
延迟加载或者懒加载----出现安全问题
// 面试中,问的最多就是懒汉式----->如何解决线程安全问题: 想到同步方法解决
public class Worker {
//成员变量位置声明变量w
private static Worker w;//默认值null
//构造方法私有化
private Worker() {} //外界不能直接new 对象
//提供静态功能,返回值是当前类本身
//w1,w2,w3 互相抢占getWorkerIntance的内容---会造成安全问题
/*
public static Worker getWorkerIntance(){
//w1
//先判断w变量为null
if(w == null){ //当为null
w = new Worker() ; //如果当前w变量为null,创建一个新的对象,返回
return w;
}
return w ;
}
*/
//改进:同步代码块---->静态的同步方法
public synchronized static Worker getWorkerIntance(){ //锁对象:Worker.class
// synchronized (w){
//w1
//先判断w变量为null
if(w == null){ //当为null
w = new Worker() ; //如果当前w变量为null,创建一个新的对象,返回
return w;
}
return w ;
//}
}
}
public class Single_pattern_02 {
public static void main(String[] args) {
//使用Worker第一次调用getInstance方法
Worker w1 = Worker.getWorkerIntance(); //第一个用户操作这个方法----属于一个线程
//第二次
Worker w2 = Worker.getWorkerIntance(); //第二个用户操作---->属于一个线程
System.out.println(w1==w2);
Worker w3 = Worker.getWorkerIntance(); //第三个用户操作--->属于一个线程
System.out.println(w1==w3);
}
}
二十四. File
1. File
File 文件和目录(文件夹)路径名的抽象表示。
2. 构造方法
File(String pathname) :参数就是指定的路径/如果没有指定路径(默认是在当前项目下)
通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
File(File parent, String child)
从父抽象路径名和子路径名字符串创建新的 File实例。
File(String parent, String child):参数1:父目录地址 参数2:具体的子文件地址
3. 成员方法
成员方法:
创建/删除/重名
public boolean createNewFile() throws IOException ://表示创建文件 :如果不存在,则创建
public boolean mkdir()://创建文件夹,如果不存在,则创建;否则就返回false
public boolean mkdirs()://创建多个文件,如果父目录不存在,则创建
public boolean delete()://删除文件或者文件夹(如果删除文件夹,文件夹必须为空目录)
public boolean renameTo(File dest)://重命名
参数传递的修改的File对象
/*需求:
将D:\EE_2106\day25\code路径下logo.jpg---->改名为 D:\EE_2106\day25\code路径下 mv.jpg
如果两个地址一样:只是改名
如果地址不一样,剪切并改名!
*/
public class FileDemo {
public static void main(String[] args) throws IOException {
//表示:E盘下的demo文件夹中的a.txt文件
//File(String pathname) 方式1 (推荐)
// File file = new File("e://demo//a.txt") ;只是表示这个路径,如果创建a.txt文件,系统找不到指定路径
File file = new File("D:\\EE_2106\\day25\\code\\a.txt"); //绝对路径
File file2 = new File("aaa.txt");//没有带路径,就默认在当前项目下(相对路径)
File file3 = new File("D:\\EE_2106\\day25\\code\\demo") ;
File file4 = new File("aaa\\bbb\\ccc\\ddd") ;
//File(File parent, String child) 方式2
/* File file2 = new File("E://demo") ;
File file3 = new File(file2,"a.txt") ;
//File(String parent, String child):方式3
File file4 = new File("E://demo","a.txt") ;*/
// public boolean createNewFile()
System.out.println(file.createNewFile());
System.out.println(file2.createNewFile());
System.out.println(file3.mkdir());
System.out.println(file4.mkdirs());
System.out.println(file3.delete());
System.out.println(file.delete());
System.out.println("------------------------");
//D:\EE_2106\day25\code路径下logo.jpg :描述下这个地址File
// File srcFile = new File("D:\\EE_2106\\day25\\code\\logo.jpg") ;
File srcFile = new File("D:\\EE_2106\\day25\\code\\mv.jpg") ;
File destFile = new File("高圆圆.jpg") ;//当前项目路径下了
//public boolean renameTo(File dest)
System.out.println(srcFile.renameTo(destFile)) ;
}
}
4. 判断功能
public boolean canRead()//是否可读
public boolean canWrite()//是否可写
public boolean exists()://是否存在
public boolean isFile()://是否是文件
public boolean isDirectory()://是否是文件夹
public boolean isHidden()://是否隐藏
5. 高级获取功能
高级获取功能:
public long length()
public String getName()://获取抽象路径 名所表示的文件或者目录的名称
public File[] listFiles()://获取某个目录下的所有的文件以及文件夹的File数组
public String[] list()://获取某个抽象路径名所表示的文件以及目录的字符串数组
需求:获取
D盘下的所有的文件夹以及文件的名称....
public class FileDemo2 {
public static void main(String[] args) {
//创建File对象,描述当前项目下的aaa.txt文件
File file = new File("aaa.txt") ;
System.out.println(file.canRead());
System.out.println(file.canWrite());
System.out.println(file.exists());
System.out.println(file.isDirectory());//false
System.out.println(file.isFile());
System.out.println(file.isHidden());
System.out.println(file.length());
System.out.println(file.getName());
System.out.println("------------------------");
// public File[] listFiles():获取某个目录下的所有的文件以及文件夹的File数组
//描述D盘
File file2 = new File("d://") ;
File[] fileArray = file2.listFiles();
//防止空指针异常
if(fileArray!=null){
for(File f :fileArray){
System.out.println(f.getName());
}
}
System.out.println("----------------------------------");
//public String[] list():获取某个抽象路径名所表示的文件以及目录的字符串数组
String[] strArray = file2.list();
if(strArray!=null){
for(String s:strArray){
System.out.println(s);
}
}
}
}
6. 获取D盘下所有的以 .jpg 结尾的文件
分析:
1)描述下D盘
2) public File[] listFiles():获取D盘下的所有的文件以及文件夹的File数组
2.1)对获取到的数组进行非判断
如果不为null,再去判断
2.2)判断File是一个文件
2.3)判断:文件必须以.jpg结尾
String类 endsWith(".jpg")
提供了另一个重载功能:
public File[] listFiles(FilenameFilter filter)
String[] list(FilenameFilter filter)
参数为:文件名称过滤器FilenameFilter:接口
成员方法:
boolean accept(File dir,String name):测试指定文件是否包含在文件列表中
返回如果true,将文件添加到文件列表中
1)描述下D盘
2)public File[] listFiles(FilenameFilter filenamefilter):
获取D盘下的File数组的时候,就已经指定文件进行过滤...
public class FileTest {
public static void main(String[] args) {
//1)描述D盘
File file = new File("D://") ;
//2)获取盘符下的所有的文件以及文件夹的File数组
File[] fileArray = file.listFiles();//这一步并没有直接获取到要的.jpg文件
//后面一些判断
if(fileArray!=null){
for(File f:fileArray){
//判断f是文件
if(f.isFile()){
//是文件
//判断它的名称是否以.jpg结尾
if(f.getName().endsWith(".jpg")){
System.out.println(f.getName());
}
}
}
}
System.out.println("-------------------------------------------------------");
//描述下D盘
File srcFile = new File("D://") ;
//获取当前D盘下的所有文件以及文件夹File数组,并进行文件名过滤
//public File[] listFiles(FilenameFilter filter)
File[] files = srcFile.listFiles(new FilenameFilter() {//接口的匿名内部类
@Override
public boolean accept(File dir, String name) {
//返回true:表示将指定文件添加列表文件中
//描述文件所表示抽象路径File
File file = new File(dir, name);
//两个条件:file是文件并且file的文件名称以".jpg结尾"
return file.isFile() && file.getName().endsWith(".jpg");
}
});
if(files!=null){
for(File f :files){
System.out.println(f.getName());
}
}
}
}
二十五. 递归
1. 方法递归
方法递归:方法调用方法本身的一种现象,并非是方法嵌套方法
前提条件
1)必须有一个成员方法
2)必须有方法递归的出口条件(结束条件),如果没有出口条件,就是死递归...
3)还存在一定的规律
注意事项:构造方法不存在递归
伪代码:
public void show(int n){ //50 调用
if(n<0){
System.out.println(n) ;
}
n -- ;//49
show(n) ;
}
Math.max(Math.max(10,30),50) ;嵌套
举例: 1
求5的阶乘!
public class DiGuiDemo {
/* public DiGuiDemo(){
//DiGuiDemo() ;//构造方法不能使用递归
}
*/
public static void main(String[] args) {
System.out.println("5的阶乘是:"+jieCheng(5));
}
//递归:需要考虑(一定要有规律)
private static int jieCheng(int i) {
if(i==1){
return 1 ;
}else{
return i * jieCheng(i-1) ;
}
}
}
举例: 2
需求:使用递归方式去删除D盘某个demo文件夹中有很多个目录,
每个目录中可能还有目录,删除这些所有目录中的
带.java文件的文件!
分析:
1)描述下D盘下demo文件夹 File srcFile = new File("d://demo")
2)调用递归删除方法
//递归删除多级目录中的java文件
public void deleteSrcFile(File srcFile){
//获取srcFile的所有的文件夹以及文件的File数组
//非空判断
//判断如果是文件夹
回到到2)继续调用递归删除
否则,是文件
判断是否以.java文件
最终删除文件即可!
public class DiGuiTest {
public static void main(String[] args) {
//描述D盘的demo文件夹
File srcFloder = new File("d://demo") ;
//调用递归删除的方法
deleteFloder(srcFloder) ;
}
public static void deleteFloder(File srcFloder) {
//获取srcFloder下的所有的文件以及文件的File数组
File[] fileArray = srcFloder.listFiles();
if(fileArray!=null){
for(File file:fileArray){
//获取到每一个file对象
//如果file是文件夹,继续回到deleteFloder(srcFloder) ; 删除文件夹
if(file.isDirectory()){
deleteFloder(file) ;
}else{
//判断是文件,必须以.java结尾 文件
if(file.getName().endsWith(".java")){
//删除的同时----获取名称
System.out.println(file.getName()+"------------"+file.delete());
}
}
}
//删除文件夹
System.out.println(srcFloder.getName()+"----"+srcFloder.delete());
}
}
}
二十六. IO
1. IO流的分类
按流的方向:
输入和输出流
按类型分:
字节和字符流:字节先出现,后面在有字符流
再次流的方向划分
字节输入流:InputStream:表示输入字节流的所有类的超类(父类)
高效的字节输入流(字节缓冲输入流):
BufferedInputStream
合并输入流:SequenceInputStream :只能操作源文件
可以将多个文件复制到一个文件中...
字节输出流:OutputStream:表示字节输出流的所有类的超类
高效字节输出流 (字节缓冲输出流)
BufferedOutputStream
字符输入流:Reader表示输入字符流的抽象类
字符输出流:Writer表示输出字符流的抽象类
字节/字符流都很多子类:
XXXInputStream
XXXOutputStream
XXXReader
XXXWriter
字节输出流:OutputStream抽象类
子类进行实例化FileOutputStream:将指定的内容写到文件中
实现步骤:
1)创建文件输出流对象 :FileOutputStream(String name) :推荐:可以指定参数地址 FileOutputStream(File file)
2)写数据
public void write(int b) throws IOException 写一个字节
public void write(byte[] bytes) throws IOException 写一个字节数组
public void write(byte[] bytes,int off,int len) throws IOException:写一部分字节数组
3)关闭资源
public class FileOutputStreamDemo {
public static void main(String[] args) throws IOException {
//创建文件输出流对象
//指向某个盘符下的文件中(或者当前项目下)
FileOutputStream fos = new FileOutputStream("my.txt") ;//文件需要被创建当前项目下
//2)写数据
/* fos.write(97);
fos.write(98);
fos.write(99);*/
//写一个字节数组
/* byte[] bytes = {97,98,99,100,101,102} ;
// fos.write(bytes);
fos.write(bytes,2,2);*/
for(int x = 0 ; x < 10 ; x ++){
fos.write(("hello"+x).getBytes());
//windows操作系统 "\r\n"代表换换行
fos.write("\r\n".getBytes());
}
//3)关闭资源
fos.close(); //释放fos流对象所指向的my.txt的系统资源
}
}
// 在字节输出流中加入异常处理(捕获异常)
public class FileOutputStreamDemo2 {
public static void main(String[] args) {
// method1() ;
method2() ;//
}
//标准方式:try...catch...finally
//统一处理
private static void method2() {
FileOutputStream fos = null ;
//alt+ctrl+t--->
try {
fos = new FileOutputStream("fos2.txt") ;
//写数据
fos.write("hello,我来了".getBytes());
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放资源
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//分别try...catch...(不用,这种方式阅读性很差)
private static void method1() {
//在当前项目下输出fos.txt
//创建字节输出流对象
FileOutputStream fos = null ;
try {
fos = new FileOutputStream("fos.txt") ;
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//写数据
try {
fos.write("hello,IO".getBytes()) ;
} catch (IOException e) {
e.printStackTrace();
}
//关闭资源
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 字节输入流 InputStream
字节输入流:InputStream
子类:FileInputStream
实现步骤:
需要读取当前项目下的fis.txt文件
1)创建文件字节输入流对象,指向 fis.txt
public FileInputStream(String name) throws FileNotFoundException
2)读内容
read(byte[] bytes):一次读取一个字节数组
read():一次读取一个字节
3)释放资源
读取文件的时候,一次读取一个字节,存在中文乱码, 在最终输出的结果 (char)by (场景:将某一个文件内容打印在控制台上了)
"abc" 英文 读一个字节 a--->97 b --98
针对中文字符: 现在idea环境: 编码格式:utf-8格式: 一个中文对应三个字节 ,和前面的英文拼接出现问题,只要中文都会乱码
才出现了字符流(加入编码和解码的格式)
public class FileInputStreamDemo {
public static void main(String[] args) {
//创建一个字节输入流对象
FileInputStream fis = null ;
try {
// fis = new FileInputStream("fis.txt") ;
fis = new FileInputStream("DiGuiTest.java") ; //读取当前项目下的DiGuiTest.java
//读取内容
/* //第一次读取
int by = fis.read(); //字节数
System.out.println(by);
System.out.println((char)by); //获取的字符
//第二次读取
by = fis.read();
System.out.println((char)by);
//第三次读取
by = fis.read() ;
System.out.println((char)by);
//第四次读取
by = fis.read() ;
System.out.println(by);//字节数
System.out.println((char)by);*/
//使用循环优化: 结束条件:获取的字节数为-1
//当前不知道循环多少次:while循环
//将判断,赋值一块去使用 (模板代码)
int by = 0 ; //字节数为0
while((by=fis.read())!=-1) {
System.out.print((char)by);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放资源
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
//public int read(byte[] b)
// throws IOException一次读取一个字节数组 ,返回值值的是每次读取的实际字节数
// 需求将:
// fis2.txt读取出来,并展示在控制台上
public class FileInputStreamDemo2 {
public static void main(String[] args) {
//创建字节输入流对象
FileInputStream fis = null ;
try {
fis = new FileInputStream("fis2.txt") ;
//一次读取一个字节数组
//创建一个字节数组
//第一次读取
/* byte[] bytes = new byte[5] ;
int len = fis.read(bytes); //读取的实际字节数
System.out.println(len);
//获取的是当前的读取的实际内容
//byte[]---->String?
// String(byte[] bytes)
// String(byte[] bytes,int off,int len)
// System.out.println(new String(bytes)); //应该描述:每一次从0这个位置读取实际长度
System.out.println(new String(bytes,0,len));
System.out.println("-------------------------");
//第二次读取:
len = fis.read(bytes) ;
System.out.println(len);
// System.out.println(new String(bytes));
System.out.println(new String(bytes,0,len));
System.out.println("-------------------------");
//第三次
len = fis.read(bytes) ;
System.out.println(len);
// System.out.println(new String(bytes));
System.out.println(new String(bytes,0,len));
System.out.println("-------------------------");
//第四次
len = fis.read(bytes) ;
System.out.println(len);
// System.out.println(new String(bytes));
System.out.println(new String(bytes,0,len));
//第五次
len = fis.read(bytes) ;
System.out.println(len);
// System.out.println(new String(bytes));
System.out.println(new String(bytes,0,len));*/
//模板
//创建一个数组:长度:1024或者1024的整数倍
byte[] buffer = new byte[1024] ; //长度虽然1024个长度
int len = 0 ;
while((len=fis.read(buffer))!=-1){
//每次获取的从0开始获取实际字节长度
System.out.println(new String(buffer,0,len)) ;
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3. 字节流读写复制(一次读取一个字节数组/一个字节)
/*读写复制操作:
* 一次读取一个字节数组
*
* 需求:
* 将当前项目下的DiGuiTest.java 的内容 复制到D盘下的Copy.java文件中
*
* 分析:
* 源文件: 当前项目下 "DiGuiTest.java"
* 封装源文件:FileInputStraem(String pathname)
* 一次读取一个字节
*
* 目的地文件: D://Copy.java
* 封装目的地文件:FileOutputStream(String pathname)
* 一次写一个字节
*/
public class CopyFileDemo {
public static void main(String[] args) {
long start = System.currentTimeMillis() ;//时间毫秒值
// method("DiGuiTest.java","D://Copy.java") ;
method2("DiGuiTest.java","D://Copy.java") ;
long end = System.currentTimeMillis() ;
System.out.println("共耗时:"+(end-start)+"毫秒");
}
private static void method2(String srcFile, String destFile) {
FileInputStream fis = null ;
FileOutputStream fos = null ;
try {
// 封装源文件:FileInputStraem(String pathname)
//字节输入流
fis = new FileInputStream(srcFile) ;
//字节输出流
// 封装目的地文件:FileOutputStream(String pathname)
fos = new FileOutputStream(destFile) ;
//读写复制操作
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=fis.read(bytes))!=-1){
//赋值
//fos流对象中写
//带上len的使用
fos.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fos.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 一次读取一个字节:读写复制
* @param srcFile 源文件
* @param destFile 目的地文件
*/
private static void method(String srcFile, String destFile) {
FileInputStream fis = null ;
FileOutputStream fos = null ;
try {
// 封装源文件:FileInputStraem(String pathname)
//字节输入流
fis = new FileInputStream(srcFile) ;
//字节输出流
// 封装目的地文件:FileOutputStream(String pathname)
fos = new FileOutputStream(destFile) ;
//读写复制操作
//一次读取一个字节
int by = 0 ;
while((by=fis.read())!=-1){
//没有读完,继续复制 :写一个字节
fos.write(by);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fos.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.字节缓冲流
1. 缓冲流的特点
缓冲流的特点:提供缓冲区大写:1024的8倍 还是通过底层流提高读速度(FileInputStream)
BufferedInputStream
BufferedOutputStream(OutputStream out) 推荐使用这个:默认缓冲区大小 足够大了 8kb
创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
BufferedOutputStream(OutputStream out, int size)
public class BufferedOutputStreamDemo {
public static void main(String[] args) throws IOException {
//BufferedOutputStream(OutputStream out) //参数父类:抽象类
//创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt")) ;
/**
* 源码:
* public BufferedOutputStream(OutputStream out) {
* this(out, 8192);
* }
*
*
* public BufferedOutputStream(OutputStream out, int size){
*super(out);
*if (size <= 0) {
*throw new IllegalArgumentException("Buffer size <= 0");
*}
*buf = new byte[size]; //创建了字节数组 byte[] buf = new byte[8192]
*}
*/
//写数据
//写一个字节数组
bos.write("hello.bufferedOutputStream".getBytes());
//释放资源
bos.close();
}
}
2. 字节缓冲输入流
字节缓冲输入流
构造方法
BufferedInputStream(InputStream in) :默认缓冲区大写 (足够大了)
提供一个指定的缓冲区大小
BufferedInputStream(InputStream in, int size)
public class BufferedInputStreamDemo {
public static void main(String[] args) throws IOException {
//创建一个字节缓冲输入流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bos.txt")) ;
/**
* class BufferedInputStream{
*
* private static int DEFAULT_BUFFER_SIZE = 8192; //8kb
*
* public BufferedInputStream(InputStream in) {
* this(in, DEFAULT_BUFFER_SIZE);
* }
*
* public BufferedInputStream(InputStream in, int size) {
* super(in);
* if (size <= 0) {
* throw new IllegalArgumentException("Buffer size <= 0");
* }
* buf = new byte[size]; //底层还是一个字节数组
* }
*
* }
*/
//读内容
//一次读取一个字节
/* int by = 0 ;
while((by=bis.read())!=-1){
//打印控制台上
System.out.print((char)by);
}*/
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=bis.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
}
}
3. 基本字节流 和 字节缓冲流
/*
* 读取D://a.mp4
* 将这个文件内容复制到 当前项目下的copy.mp4中
*
* 基本的字节流一次读取一字节
* 共耗时89021毫秒
* 基本的字节流一次读取一字节数组
* 共耗时116毫秒
* 字节缓冲流一次读取一个字节
* 共耗时348毫秒
* 字节缓冲流一次读取一个字节数组
* 共耗时47毫秒
*
*/
public class CopyMp4 {
public static void main(String[] args) {
//起始时间
long start = System.currentTimeMillis() ;
// copyMp4("D://a.mp4","copy.mp4") ;
//copyMp4_2("D://a.mp4","copy.mp4") ;
//copyMp4_3("D://a.mp4","copy.mp4") ;
copyMp4_4("D://a.mp4","copy.mp4") ;
//结束时间
long end = System.currentTimeMillis() ;
System.out.println("共耗时"+(end-start)+"毫秒");
}
//缓冲流一次读取一个字节数组
private static void copyMp4_4(String srcFile, String destFile) {
BufferedInputStream bis = null ;
BufferedOutputStream bos = null ;
try {
bis = new BufferedInputStream(new FileInputStream(srcFile)) ;
bos = new BufferedOutputStream(new FileOutputStream(destFile)) ;
//读写操作
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=bis.read(bytes))!=-1){
bos.write(bytes,0,len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
bos.close();
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//缓冲流一次读取一个字节
private static void copyMp4_3(String srcFile, String destFile) {
BufferedInputStream bis = null ;
BufferedOutputStream bos = null ;
try {
bis = new BufferedInputStream(new FileInputStream(srcFile)) ;
bos = new BufferedOutputStream(new FileOutputStream(destFile)) ;
//读写操作
int by = 0 ;
while((by=bis.read())!=-1){
bos.write(by);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
bos.close();
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//基本的字节流一次读取一个字节数组
public static void copyMp4_2(String srcFile,String destFile){
//封装源文件和目的地文件
FileInputStream fis = null ;
FileOutputStream fos = null ;
try {
fis = new FileInputStream(srcFile) ;
fos = new FileOutputStream(destFile) ;
//读写复制
byte[] bytes = new byte[1024] ;
int len = 0 ;//实际字节数
while((len=fis.read(bytes))!=-1){
fos.write(bytes,0,len);
//强制输出流将缓冲的这字节数写出来
fos.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fos.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//基本的字节流一次读取一个字节
public static void copyMp4(String srcFile, String destFile) {
//封装源文件和目的地文件
FileInputStream fis = null ;
FileOutputStream fos = null ;
try {
fis = new FileInputStream(srcFile) ;
fos = new FileOutputStream(destFile) ;
//读写复制
int by = 0 ;
while((by=fis.read())!=-1){
fos.write(by);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fos.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
5. 字符流
字符流的出现是在字节流的后面,可以解决中文乱码问题
Writer:抽象类
提供子类:字符转换输出流: 字节输出流通向字符输出流的桥梁!
构造方法
OutputStreamWriter(OutputStream out) ://使用平台默认编码集写入数据
OutputStreamWriter(OutputStream out, String charsetName) ://使用指定的字符集进行编码
public class WriterDemo {
public static void main(String[] args) throws Exception {
//创建字符缓冲输出流对象
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt")) ;//使用平台的默认编码集(utf-8)
//写入数据
/**
* write(String str)
* write(int ch):写入字符: 字符--->对应的ASCII码表
* write(char[] ch)
* write(char[] ch,int off,int len)
* write(String str,int off,int len)
*/
osw.write("hello,字符流我来了");
osw.write(97);
char[] chs = {'A','B','C','D','E'} ;
osw.write(chs);
osw.write(chs,2,2);
//使用flush():刷新字符输出流
osw.flush();
//关闭资源,释放相关的资源
osw.close(); //关闭之前,flush
//osw.write(98);
}
}
Reader:抽象类
具体的子类:字符转换输入流 InputStreamReader
构造方法:
InputStreamReader(InputStream in) ://使用平台默认解码集进行读
InputStreamReader(InputStream in,String charset) ://使用指定的解码集进行读
public class ReaderDemo {
public static void main(String[] args) throws Exception {
//创建字符转换输入流对象
//InputStreamReader(InputStream in,String charset) :使用指定的解码集进行读
// InputStreamReader isr = new InputStreamReader(new FileInputStream("osw.txt"),"UTF-8") ;
//InputStreamReader(InputStream in)
InputStreamReader isr = new InputStreamReader(new FileInputStream("osw.txt")) ;//平台默认的解码集进行读取
//读一个字符数组
char[] chs = new char[1024] ;
//实际字符数
int len = 0 ;
while((len=isr.read(chs))!=-1){
//打印控制台上:每次获取的实际长度
System.out.println(new String(chs,0,len));
}
//释放资源
isr.close();
}
}
Reader/Writer子类:转换流
InputStreamReader(InputStream in)
OutputStreamWriter(OutputStream out)
他们不能直接去操作文件,jdk提供了这两种类型的便捷类,可以直接操作文件
FileReader(File file)
FileReader(String pathname)
FileWriter(File file)
FileWriter(String filename)
这两个类:使用的平台的默认编码和解码 (utf-8)
//需求:
// 当前项目下的ReaderDemo.java 复制到 D://Demo.java
// 针对文本文件:优先采用字符流
public class CopyFile {
public static void main(String[] args) throws Exception {
//1)封装源文件:
FileReader fileReader = new FileReader("ReaderDemo.java") ;
//2)封装目文件
FileWriter fileWriter = new FileWriter("D://Demo.java") ;
//3)一次读取一个字符
/*int by = 0 ; //实际字符数
while((by=fileReader.read())!=-1){
fileWriter.write(by) ;
fileWriter.flush() ;
}*/
//一次读取一个字符数组
char[] charArray = new char[1024] ;
int len = 0 ;
while((len=fileReader.read(charArray))!=-1){
//写入实际字符数
fileWriter.write(charArray,0,len);
fileWriter.flush();
}
//关闭
fileWriter.close();
fileReader.close();
}
}
1. 字符缓冲输出流
BufferedWriter:
字符缓冲输出流
BufferedWriter(Writer out) :创建默认的缓冲区大小: 默认大小:defaultcharbuffersize:8192(默认值足够大)
BufferedWriter(Writer out, int sz) :指定的大小
public void newLine() throws IOException://写入行的分隔符号
BufferedReaer/BufferedWriter :读写复制操作
一次读取一个字符
一次读取一个字符数组
//特有功能:
利用BufferedReader的readLine()读取一行
利用BufferedWriter写一行,
public class BufferedWriterDemo {
public static void main(String[] args) throws IOException {
//输出bw.txt文件,并给里面写内容,而且去实现换行效果 write("\r\n")----现在可以使用newLine
//创建字符缓冲输出流对象
//BufferedWriter(Writer out) :
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
//写入数据
//写字符串/写字符
bw.write("hello");
//public void newLine()
bw.newLine();
bw.write("world");
bw.newLine();
bw.write("javaEE");
bw.newLine();
//刷新流
bw.flush();
//释放资源
bw.close();
}
}
2. 字符缓冲输入流
BufferedReader ---- 键盘录入数据 Scanner(InputSteram in) String nextLine()
BufferedReader(Reader in)
Reader
InputStreamReader(InputStream in):转换流
如果直接进行读的操作(直接操作文件)FileReader(String pathName)
创建使用默认大小的输入缓冲区的缓冲字符输入流。
BufferedReader(Reader in, int size) :指定缓冲区大小
特有功能:public String readLine() :一次读取一行内容
public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
//创建字符缓冲输入流对象 :使用键盘录入数据!(流的方式)
//InputStreamReader(InputStream in):
//InputStream in = System.in ;//标准输入流
// BufferedReader(Reader in)
// Reader reader = new InputStreamReader(in) ;
// BufferedReader br = new BufferedReader(reader) ;
//一步走
/* BufferedReader br = new BufferedReader(new InputStreamReader(System.in)) ;
System.out.println("请您输入一个字符串数据:");
//public String readLine()
String line = br.readLine(); //阻塞式方法
System.out.println("您输入的数据是:"+line);
System.out.println("------------------------------------------");*/
//使用BufferedReader读取bw.txt的文件内容
//readLine:读取一行内容
BufferedReader br2 = new BufferedReader(new FileReader("bw.txt")) ;
/* //第一次读取
String str1 = br2.readLine();
System.out.println(str1);
System.out.println("-----------------");
//第二次读取
str1 = br2.readLine() ;
System.out.println(str1);
System.out.println("-----------------");
//第三次读取
str1 = br2.readLine() ;
System.out.println(str1);
System.out.println("-----------------");
//第四次读取
str1 = br2.readLine() ;
System.out.println(str1); //null 已经读完*/
//定义变量
String line2 = null ;
while((line2=br2.readLine())!=null){
System.out.println(line2);
}
}
}public class BufferedReaderDemo {
public static void main(String[] args) throws IOException {
//创建字符缓冲输入流对象 :使用键盘录入数据!(流的方式)
//InputStreamReader(InputStream in):
//InputStream in = System.in ;//标准输入流
// BufferedReader(Reader in)
// Reader reader = new InputStreamReader(in) ;
// BufferedReader br = new BufferedReader(reader) ;
//一步走
/* BufferedReader br = new BufferedReader(new InputStreamReader(System.in)) ;
System.out.println("请您输入一个字符串数据:");
//public String readLine()
String line = br.readLine(); //阻塞式方法
System.out.println("您输入的数据是:"+line);
System.out.println("------------------------------------------");*/
//使用BufferedReader读取bw.txt的文件内容
//readLine:读取一行内容
BufferedReader br2 = new BufferedReader(new FileReader("bw.txt")) ;
/* //第一次读取
String str1 = br2.readLine();
System.out.println(str1);
System.out.println("-----------------");
//第二次读取
str1 = br2.readLine() ;
System.out.println(str1);
System.out.println("-----------------");
//第三次读取
str1 = br2.readLine() ;
System.out.println(str1);
System.out.println("-----------------");
//第四次读取
str1 = br2.readLine() ;
System.out.println(str1); //null 已经读完*/
//定义变量
String line2 = null ;
while((line2=br2.readLine())!=null){
System.out.println(line2);
}
}
}
3. BufferedReaer / BufferedWriter 读写复制操作
BufferedReaer/BufferedWriter :读写复制操作
一次读取一个字符
一次读取一个字符数组
特有功能:
利用BufferedReader的readLine()读取一行
利用BufferedWriter写一行,然后换行
将当前项目下的 ReaderDemo.java 复制到当前项目下:copy.java
一个文本文件读写复制:
阻塞流 (传统的IO流)
当一个线程如果操作的是读的动作, read(byte[] byte/char[] ..)/readLine():都属于阻塞式方法
另一个线程如果操作的是写的动作, 读的线程如果开始读,这边写的线程才能开始进行写的复制操作!
基本的字节流:一次读取一个字节/一次读取一个字节数组
字节缓冲流:一次读取一个字节/一次读取一个字节数组
字符转换流:InputStreamReader(InputStream in)
OutputStreamWriter(OutputStream out)
一次读取一个字符/一次读取一个字符数组
转换流的便捷类
FileReader(String name)
FileWriter(String writer)
一次读取一个字符/一次读取一个字符数组
BufferedReader/BufferedWriter
一次读取一个字符/一次读取一个字符数组
特有功能:
一次读取一行内容
public class CopyFile {
public static void main(String[] args) {
BufferedReader br = null ;
BufferedWriter bw = null ;
try {
//封装源文件:前项目下的 ReaderDemo.java
br = new BufferedReader(new FileReader("ReaderDemo.java")) ;
bw = new BufferedWriter(new FileWriter("copy.java")) ;
//使用特有功能读取一行内容
String line = null ;
while((line=br.readLine())!=null){
//读取一行,bw写一行并换行,然后刷新
bw.write(line);
bw.newLine();
bw.flush();;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
bw.close();
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
6. 字节流的逻辑串联
SequenceInputStream:
字节流的逻辑串联!
可以将两个或者是两个以上的文件进行读的操作 ,只能操作源文件
构造方法:
public SequenceInputStream(InputStream s1,InputStream s2)
参数1和参数2:分别要读取的字节输入流对象
之前:
当前项目的a.txt---->当前项目b.txt
现在:合并流
当前项目a.java+b.java---->当前项目下的c.java文件中
将当前项目下的BufferedWriterDemo.java+copy.java文件---->复制到当前项目下b.java文件中
public class CopyMulFile {
public static void main(String[] args) throws Exception {
//创建两个字节输入流对象
InputStream is = new FileInputStream("BufferedWriterDemo.java") ;
InputStream is2 = new FileInputStream("copy.java") ;
//public SequenceInputStream(InputStream s1,InputStream s2)
//创建字节合并流
SequenceInputStream sis = new SequenceInputStream(is,is2) ; //SequenceInputStream extends InputStream
//封装目的地文件:
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.java")) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len = sis.read(bytes))!=-1){
bos.write(bytes,0,len);
}
//释放资源
bos.close();
sis.close();
}
}
例题:
/*
*public SequenceInputStream(Enumeration<? extends InputStream> e) :将两个以上的文件进行读取
* Vector<InputStream>
*
* add(添加多个字节输入流对象)
*
*
* BufferedWriterDemo.java
* ReaderDemo.java
* CopyMp4.java
* 复制到D://hello.java文件中
*/
public class CopyFileDemo2 {
public static void main(String[] args) throws Exception {
//public SequenceInputStream(Enumeration<? extends InputStream> e)
//需要三个字节输入流对象
InputStream is1 = new FileInputStream("BufferedWriterDemo.java") ;
InputStream is2 = new FileInputStream("ReaderDemo.java") ;
InputStream is3 = new FileInputStream("CopyMp4.java") ;
//创建一个Vector集合
Vector<InputStream> vector = new Vector<>() ;
//添加流对象
vector.add(is1) ;
vector.add(is2) ;
vector.add(is3) ;
//public Enumeration<E> elements() ---- >类似于Collection的Iterator迭代器
Enumeration<InputStream> enumeration = vector.elements();
//创建合并流对象 封装源文件
SequenceInputStream sis = new SequenceInputStream(enumeration);
//创建字节缓冲输出流对象
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("d://hello.java")) ;
//一次读取一个字节
int by = 0 ;
while((by=sis.read())!=-1){
bos.write(by);
bos.flush();
}
bos.close();
sis.close();
}
}
7.序列化 与 反序列化
1. 序列化 反 序列化
//序列化:ObjectOutputStream
将某个实体对象写入到流对象中---->将一个对象变成 流数据
构造方法
protected ObjectOutputStream()
protected ObjectOutputStream(OutputStream out) :将某个对象通过底层的基本字节输出进行写的动作
public final void writeObject(Object obj)
throws IOException
需求:将Person p = new Person("高圆圆",42) ;---->变成流对象 进行传输
//反序列化:ObjectInputStream
将流数据----还原成 "对象"
构造方法
public ObjectInputStream(InputStream in)
public final Object readObject()
throws IOException, ClassNotFoundException
例子:
public class Person implements Serializable {
//产生一个固定的序列化版本Id
private static final long serialVersionUID = 8988325735017562383L; //常量值
String name ; //姓名
private transient int age ; //年龄 //transient :想让某个字段不参与序列化:这个字段(成员变量就使用transient)
public Person(){}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class ObjectStreamDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// myWrite() ;//写
myRead() ;//读
}
//反序列化
private static void myRead() throws IOException, ClassNotFoundException {
//创建反序列化流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("oos.txt")) ;
//读
//public final Object readObject()
Object object = ois.readObject();
//关闭流
ois.close();
System.out.println(object);//toString()
//java.io.StreamCorruptedException: invalid stream header: EFBFBDEF
//当从对象流读取的控制信息违反内部一致性检查时抛出。
}
//序列化
//将Person p = new Person("高圆圆",42) ;---->变成流对象 进行传输
private static void myWrite() throws IOException {
//创建Person对象
Person p = new Person("高圆圆",42) ;
//protected ObjectOutputStream(OutputStream out):创建一个序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("oos.txt")) ;
//将p对象写入到oos流对象中
//public final void writeObject(Object obj)throws IOException
oos.writeObject(p);
//释放资源
oos.close();
}
}
2.异常
NotSerializableException://未实现序列化接口的异常
一个类在进行序列化的时候,(将类的对象写入序列化流中),标记接口Serializable 它有个特点
为当前这个Person进行编码(为类以及类的成员----->序列化化版本ID(签名):SerialversonUID)
java.io.InvalidClassException: com.qf.serailizable_05.Person; local class incompatible:
stream classdesc serialVersionUID = 2588921225545403990,
local class serialVersionUID = -862808041229244683
在进行序列化的时候:当前的 serialVersionUID:序列化版本id号 (假设100): 内存中生成一个id值
在进行反序列化的时候:将流--->对象 :
Person类的字段信息更改了(类的签名信息就会被更改),那么 直接进行反序列化 产生serialVersionUID=(假设200)
序列化版本id保证,
在idea中生成一个固定的序列版本id号 (一般都针对实体类)
一个实体类:
1)当前类必须为具体类 class 类名{}
2)必须存在私有字段(私有成员变量)
3)必须提供公共的setXXX()/getXXX()
4)如果当前类需要在网络中进行传输(分布式系统中)必须implements Serializable
idea-
file--->setting---->editor---->Inspections----->java---->序列化serializion issue
--->serializable class 打上对钩即可!
8. properties
Propeties // 键和值都是字符串类型:可能需要在src(类所在的路径:类路径)作为一个配置文件 xxx.properties
Properties extends Hashtable<K,V> ,它没有泛型,Key和Value都是String
表示一组持久的属性。 Properties可以保存到流中或从流中加载。
属性列表中的每个键及其对应的值都是一个字符串。
1)可以使用Map的功能
put(K ,V)
遍历:
keySet()通用
2)有自己特有功能添加元素和遍历
public Object setProperty(String key,String value):// 给属性列表中添加属性描述(key和value)
public Set<String> stringPropertyNames():// 获取属性列表中的所有的键
public String getProperty(String key):// 在属性列表中通过键获取值
public class PropertiesDemo {
public static void main(String[] args) {
//Properties() :空参构造
Properties properties = new Properties() ;
// System.out.println(properties);
//利用Map集合的功能去添加元素
properties.put("高圆圆","赵又廷") ;
properties.put("文章","姚笛") ;
properties.put("文章","马伊琍") ;
properties.put("王宝强","马蓉") ;
// System.out.println(properties);
//遍历
Set<Object> keySet = properties.keySet();
for(Object key :keySet){
//通过key获取value
Object value = properties.get(key);
System.out.println(key+"---"+value);
}
System.out.println("---------------------------------------");
/**
* 推荐Properties作为集合类 的遍历方式
*
* public Object setProperty(String key,String value):给属性列表中添加属性描述(key和value)
* public Set<String> stringPropertyNames():获取属性列表中的所有的键
* public String getProperty(String key):在属性列表中通过键获取值
*112`1`1231
*/
//创建一个空的属性列表
Properties prop = new Properties() ;
prop.setProperty("张三","30") ;
prop.setProperty("李四","40") ;
prop.setProperty("王五","35") ;
prop.setProperty("赵六","45") ;
//遍历:
Set<String> set = prop.stringPropertyNames();//获取所有键
for(String key:set){
String value = prop.getProperty(key);
System.out.println(key+"---"+value);
}
}
}
9. 网络编程
1.特点
网络编程------>Socket编程
特点:
发送端/客户端
接收端/服务器端 这两端必须存在Socket对象
2.网络编程三要素
网络编程的三要素:
举例:
我要找到高圆圆 说一句话
ip:
知道高圆圆的ip地址
port 0-65535 (0-1024属于保留端口)
知道高圆圆的端口号
规定一种协议
说一句话: i love you 她听不懂英文
"我爱你"
协议:
udp协议
1)不需要建立连接通道
2)属于一种不可靠连接
3)发送文件大小限制的
4)执行效率高 (不同步的)
TCP/Ip协议
1)建立连接通道
2)属于安全连接(可靠连接)
3)发送文件(使用基本字节流),相对udp协议来说没有限制
4)执行效率低(同步的)
InetAddress:互联网ip地址
获取InetAddress:ip地址对象
public static InetAddress getByName(String host):参数:主机名称(计算机电脑名称)
public String getHostAddress():获取ip地址字符串形式
public class NetDemo {
public static void main(String[] args) throws UnknownHostException {
//如何获取自己电脑上的ip地址----》String形式!
//10.12.156.107
InetAddress inetAddress = InetAddress.getByName("DESKTOP-Q62EUJH");
String ip = inetAddress.getHostAddress();
System.out.println(ip);
//10.12.156.107
}
}
/*
* Udp接收端
* 1)创建Socket对象
* 2)创建一个接收容器:数据报包:DatagramPacket
* 3)接收
* 4)解析容器的的实际数据大小
* 5)展示发送端发送的数据
*/
public class UdpReceive {
public static void main(String[] args) throws IOException {
//)创建Socket对象
//public DatagramSocket(int port)
DatagramSocket ds = new DatagramSocket(12306) ;
//2)创建一个接收容器:数据报包:DatagramPacket 实际数据没有这么大
//public DatagramPacket(byte[] buf, int length)
//自定义字节缓冲区
byte[] bytes = new byte[1024] ;
int lentgth = bytes.length ;
DatagramPacket dp = new DatagramPacket(bytes,lentgth);
//3)public void receive(DatagramPacket p)
ds.receive(dp);
//4)解析实际数据
//byte[] getData() 获取实际字节数
//返回数据缓冲区。
//int getLength() 获取实际长度
byte[] bytes2 = dp.getData();
int length2 = dp.getLength();
String receiverStr = new String(bytes2,0,length2) ;
//获取ip地址
//InetAddress getAddress()
//InetAddress
//public String getHostAddress():
String ip = dp.getAddress().getHostAddress();
//展示数据
System.out.println("data from "+ip+" ,content is :"+receiverStr);
}
}
/*
* Udp编程的发送端:
* 1)创建Socket对象
* 2)发送内容 :内容数据一种数据报文(报包)DatagramPacket
* 3)释放资源
*/
public class UdpSend {
public static void main(String[] args) throws IOException {
//1)创建Socket对象
//DatagramSocket:发送和接收数据报数据包的套接字。
//public DatagramSocket() throws SocketException
DatagramSocket ds = new DatagramSocket() ;
//2)创建一个数据报包对象DatagramPacket
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
//参数1:当前数据的字节数组
//参数2:当前数据的长度
//参数3:InetAddress:ip地址对象
//参数4:绑定的端口号
byte[] bytes = "hello,马三奇".getBytes() ;
int length = bytes.length ;
//public static InetAddress getByName(String host)
InetAddress inetAddress = InetAddress.getByName("10.12.156.107");
int port = 12306 ;
DatagramPacket dp = new DatagramPacket(bytes,length,inetAddress,port) ;
//3)发送数据报包
//public void send(DatagramPacket p)
ds.send(dp);
//4)释放资源
ds.close();
}
}
3. 接收端 发送端 不断的接收和录入数据(UDP)
/*
* 接收端端不断的接收数据
* 接收端一般不关闭
*
* 接收端只能开启一次,多次就出现 BindException:绑定一次
* Address already in use: Cannot bind:端口号被占用!
*/
public class UdpReceive {
public static void main(String[] args) {
//创建接收端的Socket
try {
DatagramSocket ds = new DatagramSocket(6666) ;
while(true){
//创建一个接收容器
byte[] bytes = new byte[1024] ;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length) ;
//接收
ds.receive(dp);
//解析实际数据
String receiveStr = new String(dp.getData(), 0, dp.getLength());
//获取ip
String ip = dp.getAddress().getHostAddress();
//展示数据
System.out.println("data from "+ip+"data is--->"+receiveStr);
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//接收不关闭
//接收socket对象一一直开着
}
}
}
/*
* 发送端不断键盘录入数据,接收端不断接收数据,然后展示内容
*/
public class UdpSend {
public static void main(String[] args) {
DatagramSocket ds = null ;
try {
//创建发送端的Socket
ds = new DatagramSocket() ;
//键盘录入数据
//IO流的方式
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)) ;
//使用readLine()读取一行内容
String line = null ;
while((line=br.readLine())!=null){ //readLine():阻塞式方法
//自定义结束条件
if("886".equals(line)){
break ;
}
//创建数据报包
DatagramPacket dp = new DatagramPacket(line.getBytes(),
line.getBytes().length,
InetAddress.getByName("10.12.156.107"),6666);
//发送数据报包
ds.send(dp);
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放资源
ds.close();
}
}
}
4. TCP服务器端的实现步骤:
/*
* TCP特点:需要建立连接通道(就是以一种字节流的方式写入,读取)
* 什么时候建立连接(服务器端如果监听到端口,客户端就立即和服务器端建立连接!)
*
* TCP客户端写数据
* 1)创建TCP客户端的Socket对象
* 2)写数据
* 3)释放资源
*/
public class ScoketDemo {
public static void main(String[] args) throws IOException {
//1)创建TCP客户端的Socket对象
// public Socket(String host, int port)throws UnknownHostException, IOException
//参数1:主机名称/或者ip地址字符串形式
//参数2:指定的端口(0-65535(不包含0-1024))
Socket s = new Socket("10.12.156.107",8888) ;
//2)写数据
//public OutputStream getOutputStream()throws IOException
OutputStream out = s.getOutputStream();//获取通道内的输出流对象
out.write("hello,TCP".getBytes());
//读取服务器端反馈的数据
//3)释放资源
s.close();
}
}
/*
TCP服务器端的实现步骤:
1)创建服务器端的Socket对象
public ServerSocket(int port)throws IOException
2)监听客户端连接
public Socket accept()throws IOException 返回值就是当前监听到的客户端的Socket对象
3)获取通道内的输入流
public InputStream getInputStream() throws IOException
4)读取数据:一次读取一个字节数组
5)展示数据 而且获取ip地址
//public InetAddress getInetAddress()
*/
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 1)创建服务器端的Socket对象
//public ServerSocket(int port)throws IOException
ServerSocket ss = new ServerSocket(8888) ;
System.out.println("服务器正在等待连接...");
//2)监听客户端连接
// public Socket accept()throws IOException 返回值就是当前监听到的客户端的Socket对象
Socket socket = ss.accept(); //阻塞式方法
//3)取通道内的输入流
//public InputStream getInputStream() throws IOException
InputStream inputStream = socket.getInputStream();
//4)读取数据:一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = inputStream.read(bytes);
//获取内容
String clientStr = new String(bytes,0,len) ;
//再去反馈数据
//5)获取ip
String ip = socket.getInetAddress().getHostAddress();
//展示数据
System.out.println("data from "+ip+" content is--->"+clientStr);
//关闭
ss.close();
}
}
5. TCP 三次握手
TCP三次握手
1. 第一次握手: 客户端首先连接服务器端(发送信号SYN(Synchronize Sequence Number))
2. 第二次握手: 服务器端发送客户端应答(表示已经确认收到了)ACK(Acknowledge character)+SYN
3. 第三次握手: 客户端再次发送确认字符以及同步序列编号,告诉服务器端已经收到了
三次模式: 最为安全的!
两端Socket---建立连接
通过通道内的输入输出流 传输数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wj6yWYtQ-1628934136355)(E:\qianfeng\day27\resource\images\01_TCP三次握手原理.png)]
//客户端
public class ScoketDemo {
public static void main(String[] args) throws IOException {
//1)创建TCP客户端的Socket对象
// public Socket(String host, int port)throws UnknownHostException, IOException
//参数1:主机名称/或者ip地址字符串形式
//参数2:指定的端口(0-65535(不包含0-1024))
Socket s = new Socket("10.12.156.107",8888) ;
//2)写数据
//public OutputStream getOutputStream()throws IOException
OutputStream out = s.getOutputStream();//获取通道内的输出流对象
out.write("hello,TCP".getBytes());
//读取服务器端反馈的数据
//获取通道内的输入流
InputStream in = s.getInputStream();
//读
byte[] bytes = new byte[1024] ;
int len = in.read(bytes);
String fkMsg = new String(bytes,0,len) ;
System.out.println("fkMsg:"+fkMsg);
//3)释放资源
s.close();
}
}
//服务端
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 1)创建服务器端的Socket对象
//public ServerSocket(int port)throws IOException
ServerSocket ss = new ServerSocket(8888) ;
System.out.println("服务器正在等待连接...");
//2)监听客户端连接
// public Socket accept()throws IOException 返回值就是当前监听到的客户端的Socket对象
Socket socket = ss.accept(); //阻塞式方法
//3)取通道内的输入流
//public InputStream getInputStream() throws IOException
InputStream inputStream = socket.getInputStream();
//4)读取数据:一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = inputStream.read(bytes);
//获取内容
String clientStr = new String(bytes,0,len) ;
//服务器给客户端再去反馈数据,
//获取通道内的输出流
OutputStream outputStream = socket.getOutputStream();
//写入到通道内的流对象中
outputStream.write("ok,数据已经收到".getBytes());
outputStream.flush();
//5)获取ip
String ip = socket.getInetAddress().getHostAddress();
//展示数据
System.out.println("data from "+ip+" content is--->"+clientStr);
//关闭
ss.close();
}
}
6. TCP客户端不断键盘录入数据,服务器端不断接收数据,然后展示数据
/*
* 1)TCP客户端不断键盘录入数据,服务器端不断接收数据,然后展示数据
*
*
* 2)TCP客户端文本文件(XXX.java文件)----->服务器端将文件进行读写复制,输出到当前项目的Copy.java文件中
* BuferedReader
* 3)TCP客户端文本文件(XXX.jpg文件)----->服务器端将文件进行读写复制,输出到当前项目的Copy.jpg文件中
* BufferedInputStream
*
*/
public class ClientDemo {
public static void main(String[] args) {
//创建客户端的Socket
Socket socket = null ;
try {
socket = new Socket("10.12.156.107",10010) ;
//创建BufferedReader
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)) ;
//录入一行
//读取一行
String line = null ;
while((line=br.readLine())!=null){//null只是作为 一个文件是否读完
if("over".equals(line)){
break ;
}
//获取通道内的输出流(字节流OutputStream)
//封装通道内字节输出流
//分步走
//OutputStream outputStream = socket.getOutputStream(); //字节流---->字符流
//创建字符缓冲输出流
// BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream)) ;
//一步走
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())) ;
//给封装的通道内的流对象bw写一行
bw.write(line);
bw.newLine();
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//释放资源
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* 服务器端不断读取数据并展示
*/
public class ServerDemo {
public static void main(String[] args) {
//创建服务端的Socket,一直开启
ServerSocket ss = null ;
try {
ss = new ServerSocket(10010) ;
//ArrayList<Socket>:ArrayList存储一个列表:都是多个客户端 对象
while(true){
System.out.println("服务器正在等待连接");
Socket socket = ss.accept();
//不断监听,不断展示数据
//获取通道内的输入流(InputStream)
//封装通道内的输入流对象
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())) ;
//一次读取一行
String line = null ;
while((line=br.readLine())!=null){ //阻塞式方法
// //展示数据
System.out.println(line);
}
//复制完毕之后,给客户端反馈
}
} catch (IOException e) {
e.printStackTrace();
}
//服务器端不关闭
}
}
7.服务器端复制Test.java到当前项目下的Copy.java文件中
/*
* 客户端的一个文本文件,服务器端进行复制到指定的某个文件中 ,复制完毕了,服务器端需要给客户端反馈!(反馈的消息,客户端能不能获取到)
*
* 加入反馈操作:出现客户端和服务器端互相等待的情况--->但是文件已经复制完毕!
*
* 针对服务器端:不知道客户端是否还需要从通道内的输出流对象中写入数据(此时文件读写复制结束条件:只是null)
* 文件读完毕的条件是null,但是TCP通过流的方式 要进行结束; 服务器端不知道客户端是否还需要写入数据,客户端等待着服务器反馈的数据!
*
* 解决方案:
* 1)自定义结束条件
* 在客户端读完文件中,通知一下服务器端
* 写入一行内容("886/over"),服务器端只要读取到886或者over
*
*
* 2)可以使用客户端Socket的一个方法 标记(通知服务器端,客户端已经没有数据输出了)
* public void shutdownOutput()
* throws IOException
*/
public class ClientTest {
public static void main(String[] args) throws IOException {
//将当前项目下的Test.java 客户端将文件写入到 服务器端,服务器端进行复制
//创建客户端Socket对象
Socket socket = new Socket("10.12.156.107",10086) ;
//创建BufferedReader流对象,去操作当前项目下的Test.java文件
BufferedReader br = new BufferedReader(new FileReader("Test.java")) ;
//获取通道内字节输出流,写数据----封装通道内的字节流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())) ;
//一次读取一行,然后给通道内的流对象中写入过去
String line = null ;
while((line=br.readLine())!=null){//阻塞式方法
bw.write(line);
bw.newLine();
bw.flush();
}
//方案1 :自定义结束条件:客户端通道内的流中已经没有数据了
/*bw.write("over");
bw.newLine();
bw.flush();*/
//方案2:使用socket对象的方法
socket.shutdownOutput(); //禁用套接字流输出,通知服务器端 客户端已经不会在写入数据了
//客户端读取反馈
//获取通道内字节输入流对象
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024] ;
int len = inputStream.read(bytes);//阻塞方法
String fkMsg = new String(bytes,0,len) ;
System.out.println("客户端到读取的反馈数据是:"+fkMsg);
//关闭
br.close();
socket.close();
}
}
/*
* 服务器端复制Test.java到当前项目下的Copy.java文件中
*/
public class ServerTest {
public static void main(String[] args) throws IOException {
//创建服务器端Socket
ServerSocket ss = new ServerSocket(10086) ;
Socket socket = ss.accept();
//创建BufferedReader去读取通道内写入过来的数据,封装通道内的流对象
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())) ;
//创建字符缓冲输出流,写入数据,输出到当前项目下的Copy.java文件中
BufferedWriter bw = new BufferedWriter(new FileWriter("copy.java")) ;
//一次读取一行
String line = null ;
while((line=br.readLine())!=null){//阻塞方法 :可能客户端的文件已经null了,但是服务器端不知道!
/* if("over".equals(line)){
break ;
}*/
bw.write(line);
bw.newLine();
bw.flush();
}
//加入反馈操作
//继续通道内输出流对象
OutputStream outputStream = socket.getOutputStream();
outputStream.write("数据已经收到了,已经复制完毕".getBytes());
outputStream.flush();
//释放资源
bw.close();
ss.close();
}
}
8. 客户端的图片文件,服务器端将图片进行复制,并反馈给客户端
/*
* 服务器端将图片进行复制,并反馈给客户端
* 将图片文件写入到当前项目下的高圆圆.jpg
*/
public class ServlerImgDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(12306) ;
//监听
Socket socket = ss.accept() ;
//读取:封装通道内的字节输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()) ;
//输出
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("高圆圆.jpg")) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=bis.read(bytes))!=-1){ //阻塞式方法
bos.write(bytes,0,len);
bos.flush();
}
//加入反馈
OutputStream outputStream = socket.getOutputStream();
outputStream.write("图片文件复制完毕".getBytes());
outputStream.flush();
//释放资源
bos.close();
ss.close();
}
}
/*
* 客户端的图片文件,服务器端将图片进行复制,并反馈给客户端
*/
public class ClientImgDemo {
public static void main(String[] args) throws IOException {
//创建客户端的Socket
Socket socket = new Socket("10.12.156.107",12306) ;
//创建BuferedInputStream 读取图片文件 :d://mm.jpg
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("d://mm.jpg")) ;
//写入到通道内流中同时 封装通道内的流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()) ;
//一次读取一个字节数组
byte[] buffer = new byte[1024] ;
int len = 0 ;
while((len=bis.read(buffer))!=-1){
//写
bos.write(buffer,0,len);
bos.flush(); //强制刷新,将缓冲的字节数全部写出
}
//通知服务器端,通道内输出流对象没有数据了
socket.shutdownOutput();
//读取反馈
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024] ;
int length = inputStream.read(bytes);
System.out.println("反馈内容为:"+new String(bytes,0,length));
//关闭资源
bis.close();
socket.close();
}
}
10. 反射
1.什么是反射
什么是反射?
能够获取当前某个类的字节码文件对象Class,那么就可以获取当前类的构造器并且创建当前类实例,
还可以获取当前类的成员变量并去赋值,或者获取当前类的成员方法并去调用!
如何获取一个类的字节码文件对象?
Object类的getClass()获取 获取当前某个类的实例(正在运行的类)
任意Java类型的class属性
Class类的静态功能
public static Class<?> forName(String className)
public class Person {
//成员变量
private String name ;//姓名 私有
public int age ; //年龄 默认
String address ; //地址 默认修饰符
//无参构造方法:公共的
public Person(){}
//提供带两个参数的构造方法,默认修饰符
Person(String name,int age){
this.name = name ;
this.age = age ;
}
//提供三个参数的构造方法,私有的
private Person(String name,int age,String addrss){
this.name = name ;
this.age = age ;
this.address = addrss ;
}
//提供一些成员方法(非静态)
public void show(){
System.out.println("show Person");
}
private String functioin(String str){
return str ;
}
void method(String message,int num){
System.out.println(message+num);
}
public int method2(){
return 100 ;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
//获取Person类的字节码文件对象:Class
Person p = new Person() ;
Class c1 = p.getClass();
System.out.println(c1);//class 全限定名称
Person p2 = new Person() ;
Class c2 = p2.getClass() ;
System.out.println(c2);
System.out.println(c1==c2);
System.out.println(p==p2);//两个对象
System.out.println("----------------------------");
Class c3 = Person.class ;
System.out.println(c3);
System.out.println(c1==c3);
System.out.println("-----------------------------");
//知道某个一类的全限定名称了,也可以获取当前类的字节码文件对象
// Class c4 = Class.forName("Person"); com.qf.reflect_03.Person //参数字符串:全限定名称
Class c4 = Class.forName("com.qf.reflect_03.Person"); //com.qf.reflect_03.Person //参数字符串:全限定名称
System.out.println(c4);
System.out.println(c1==c4);
//第二种方式和第三种使用 推荐第三种 因为参数为String------>就可以存储在配置文件中
}
}
2. 创建对象
/*
* 之前的写法:
* 创建一个Person类对象 :通过无参构造方法创建当前类的实例
* Person p = new Person() ;
*
* Person p = new Person("高圆圆",20) ;
* p
* 执行toString
*
* 现在需要使用反射(底层)如何创建p对象的!
*/
public class ReflectDemo {
public static void main(String[] args) throws
ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//之前的写法
Person p = new Person() ;
System.out.println(p);
System.out.println("-----------------------------------------");
//使用反射的方式创建当前类实例
//1)获取Person类的字节码文件对象
Class personClass = Class.forName("com.qf.reflect_03.Person") ;//class com.qf.reflect_03.Person
//2)获取构造器对象
//Class类
//public Constructor<?>[] getConstructors():获取当前字节码文件对象中(正在运行的这个类)里面所有的公共的构造方法所在的对象
//public Constructor<?>[] getDeclaredConstructors():获取所有的构造器对象Constructor,返回构造器对象数组 包含私有化构造/默认的
// Constructor[] constructors = personClass.getConstructors();
/* Constructor[] constructors = personClass.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);//public com.qf.reflect_03.Person()
//private com.qf.reflect_03.Person(java.lang.String,int,java.lang.String)
//com.qf.reflect_03.Person(java.lang.String,int)
//public com.qf.reflect_03.Person()
}*/
//获取某一个构造器对象Constructor
//public Constructor<T> getConstructor(Class<?>... parameterTypes) :// ... :jdk5以后 可变参数 (参数数量未知)
//获取指定的公共的单个的构造器对象 参数:需要书写的是当前参数类型的字节码文件对象
Constructor constructor = personClass.getConstructor();//java.lang.String
//通过构造器对象创建当前类的实例
//public T newInstance(Object... initargs): 参数为最终赋值的实际参数 (可变参数:实际参数未知)
Object obj = constructor.newInstance();
System.out.println(obj); //p
}
}
3. 给对象赋值
/*
* 之前的写法
* Person p = new Person("高圆圆",20) ;
* p
* 执行toString
*
*
* Person p = new Person("高圆圆",20,"西安") ;报错
* // private Person(String name,int age,String addrss)
*/
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
//之前的写法
//Person p = new Person("高圆圆",20) ; //默认修饰符只能同一个包下访问
//反射创建p对象
//1)获取类的字节码文件对象
Class clazz = Class.forName("com.qf.reflect_03.Person") ;
//2)获取构造Constructor(构造方法所在对象)
//Person(String name,int age){
获取指定的构造函数对象 ,返回值都是单个构造函数
//public Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) :参数都是参数类型的字节码文件对象
// 公共的构造函数对象:public Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
//通过构造器创建当前类的实例
//getDeclaredConstructor
//java.lang.IllegalAccessException: Class com.qf.reflect_04.ReflectDemo2 非法访问异常:因为当前构造函数式默认修饰符
// can not access a member of class com.qf.reflect_03.Person with modifiers ""
//取消Java语言访问检查(反射中使用到)
//public void setAccessible(boolean flag)参数为true,取消Java语言访问检查
constructor.setAccessible(true);
Object obj = constructor.newInstance("高圆圆", 20);
System.out.println(obj);//Person{name='高圆圆', age=20, address='null'}
}
}
4. 获取构造函数
/*
* 通过反射获取Person类的字节码文件对象并获取当前构造函数(带三个参数的构造函数)创建当前类实例
*
* private Person(String name,int age,String addrss)
*/
public class ReflectDemo3 {
public static void main(String[] args) throws Exception {
//获取Person类的字节码文件对象
Class clazz = Class.forName("com.qf.reflect_03.Person");
//获取构造器对象
//public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
Constructor con = clazz.getDeclaredConstructor(String.class,int.class,String.class) ;
//取消Java语言访问检查
con.setAccessible(true);
//通过构造器对象创建当前类实例
Object obj = con.newInstance("高圆圆", 20, "西安市");
System.out.println(obj);//Person{name='高圆圆', age=20, address='西安市'}
}
}
5. 获取成员变量
public class ReflectDemo {
public static void main(String[] args) throws Exception {
//没有使用反射之前
Person p = new Person() ;
System.out.println(p);
p.age = 20 ;//对象名.属性;
System.out.println(p);
System.out.println("-----------------------------");
//反射的方式
//1)获取Person类的字节码文件
Class clazz = Class.forName("com.qf.reflect_03.Person") ;
//方式1:直接获取当前类的实例
//public T newInstance()
Object obj = clazz.newInstance();
System.out.println(obj);
//方式2:通过获取构造函数对象,创建当前类实例
/* Constructor constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
System.out.println(obj);*/
//2)Class类
//获取所有的公共的成员变量Field
//public Field[] getFields()throws SecurityException
//public Field[] getDeclaredFields():所有的字段所在的Field对象
// 这包括公共,受保护,默认(包)访问和私有字段,但不包括继承的字段。
// Field[] fields = clazz.getFields() ;
/*Field[] fields = clazz.getDeclaredFields() ;
for (Field field : fields) {
System.out.println(field);*/
//public int com.qf.reflect_03.Person.age
//private java.lang.String com.qf.reflect_03.Person.name
// public int com.qf.reflect_03.Person.age
// java.lang.String com.qf.reflect_03.Person.address
// }
//现在访问name private String name ;
//public Field getDeclaredField(String name):获取指定的单个的字段所在的Field对象
//参数为"当前字段名称"
Field nameField = clazz.getDeclaredField("name"); //默认:null
//Field提供方法
//public void set(Object obj,Object value):给绑定在当前对象上的字段进行赋值
//参数1:创建当前类的实例
//参数2:value 赋值的实际参数
//取消Java语言访问检查
nameField.setAccessible(true);
//赋值
nameField.set(obj,"高圆圆");
System.out.println(obj);
System.out.println("-----------------------------------------------");
//public int age ;
//获取当前age所在Field对象
//public Field getField(String name):参数为字段名称
Field ageField = clazz.getField("age");
//直接赋值
ageField.set(obj,20);
System.out.println(obj);
System.out.println("-----------------------------------------");
// String address ; //地址 默认修饰符
//获取Field对象
Field addressField = clazz.getDeclaredField("address");
//取消Java语言访问检查
addressField.setAccessible(true);
//赋值
addressField.set(obj,"西安市");
System.out.println(obj);
}
}
6. 获取成员方法
public class ReflectDemo {
public static void main(String[] args) throws Exception{
//没有使用反射之前
Person p = new Person() ;
p.show();
System.out.println("--------------------------");
//直接反射方式获取
//1)获取当前Person字节码文件对象
Class clazz = Class.forName("com.qf.reflect_03.Person") ;
Object obj =clazz.newInstance() ;//当前类实例
//2)获取所有的成员方法:公共的(包含他父类的所有的公共的方法)
//public Method[] getMethods()
//public 方法[] getDeclaredMethods():通过此表示类对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。
// Method[] methods = clazz.getMethods();
/* Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}*/
//获取指定的方法 调用public void show()
// public Method getMethod(String name,Class<?>... parameterTypes)
//参数1:方法名
//参数2:当前这个方法所携带的参数类型的Class
Method showMethod = clazz.getMethod("show");
//Method:底层调用方法
//public Object invoke(Object obj,Object... args):将方法的实际参数绑定在当前类的实例上
//参数1:就是当前类的实例
//参数2:可变参数, 方法传递实际参数 若无参,则实际不用传递
//返回值:如果当前方法本身有返回值,则返回Object
showMethod.invoke(obj) ;
System.out.println("--------------------------------------");
//private String functioin(String str){
// return str ;
// }
//调用Person类中function
//获取function方法的Method对象
//public Method getDeclaredMetho(String name,Class<?> ..parameterTypes).
Method functonMethod = clazz.getDeclaredMethod("functioin", String.class);
//取消Java语言访问检查:私有方法
functonMethod.setAccessible(true);
Object returnObj = functonMethod.invoke(obj, "hello,洪学佳,别睡了...");
System.out.println(returnObj);
// void method(String message,int num){
// System.out.println(message+num);
// }
//
// public int method2(){
// return 100 ;
// }
}
}
7. 反射的应用
/*
* 现在有一个ArrayList<Integer>,里面有一些元素,
* 如何给ArrayList添加String类型的元素呢?
*/
public class ReflectTest {
public static void main(String[] rgs) throws Exception {
//有一个ArrayList集合对象
ArrayList<Integer> arrayList = new ArrayList<>() ;
arrayList.add(100) ;
arrayList.add(50) ;
arrayList.add(200) ;
System.out.println(arrayList);
// arrayList.add("hello") ;
//1)获取当前集合实例所在的字节码文件对象
//通过Object的getClass()获取当前实例所在的类的字节码对象
Class clazz = arrayList.getClass();
//System.out.println(obj);
//System.out.println(clazz);//class java.util.ArrayList
//获取当前类中 add方法所在的Method
//public boolean add(E e) ://参数就是任意Java类型 (Element)
Method addMethod = clazz.getMethod("add", Object.class);
//调用方法
addMethod.invoke(arrayList, "hello");
addMethod.invoke(arrayList, "world");
addMethod.invoke(arrayList, "javaEE");
System.out.println(arrayList);
}
}
8. 反射的应用2
/*
需求;
现在有一个学生类以及工人类,两类都有一个love方法
ReflectTest2在测试类中进行测试
1)起初,创建的一个学生对象,调用love方法
2)代码更改,创建一个工人类对象,调用love方法
Java设计原则:
开闭原则:对修改关闭,对扩展开放 (在现有代码进程上,想办法进行扩展...)
如果提供配置文件,以后只需要修改配置文件中内容,而不更改当前的代码!
*/
//学生类
public class Student {
public void love(){
System.out.println("爱学习,爱Java,爱高圆圆...");
}
}
//工人类
public class Worker {
public void love(){
System.out.println("爱生活,爱drink...,爱足球");
}
}
//测试类
public class ReflectTest2 {
public static void main(String[] args) throws Exception {
//1)现在学生类对象
/* Student s1 = new Student() ;
s1.love();*/
//需求改变:创建工人类对象
/*Worker worker = new Worker() ;
worker.love();*/
//优化:提供src(类路径下提供配置文件)myClass.properties
//1)读取src下面的myClass.properties
//获取资源文件所在的输入流对象
InputStream inputStream = ReflectTest2.class
.getClassLoader().
getResourceAsStream("myClass.properties");
//创建属性集合列表:空的
Properties prop = new Properties() ;
//将指定的配置文件所在的输入流加载属性列表中
prop.load(inputStream);
System.out.println(prop);
//可以通过可以key获取value
String className = prop.getProperty("className") ; //当前类的全限定名称
String methodName = prop.getProperty("methodName") ;
//通过反射创建当前类的字节码文件对象
Class clazz = Class.forName(className) ;
//创建当前类的实例 (解耦)
Object obj = clazz.newInstance() ;
//通过clazz字节码文件对象获取当前成员方法所在的Method类对象
Method method = clazz.getMethod(methodName) ;
method.invoke(obj) ;
}
}
MYSQL
一. DDL语句:数据库定义语句
库/表的增删查改
二. 查询当前mysql下所有的数据库
mysql8.0 自带的 跟5.5自带的不一样
mysql > show databases;
+--------------------+
| Database |
+--------------------+
| information_schema | mysql默认的一些配置
| mysql | mysql库里面包含user表用户表 (权限用户)
| performance_schema | mysql性能相关的库
| sakila | 提供其他三个库 sakila sys world 提供了一些例库 练习sql语句
| sys |
| world |
| zy | 自定义的库(开发者自定义)
+--------------------+
7 rows in set (0.00 sec)
1.查看创建数据库的默认的字符集(了解)
show create database 库名;
mysql> show create database mydb_01;
+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| Database | Create Database |
+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| mydb_01 | CREATE DATABASE `mydb_01` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */ |
+----------+-----------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
2. 修改数据库的字符集(了解)
alter database 库名 (default)可以省略 character set=字符集;
mysql> alter database mydb_01 default character set gbk ;
Query OK, 1 row affected (0.01 sec)
mysql> show create database mydb_01;
+----------+----------------------------------------------------------------------------------------------------+
| Database | Create Database |
+----------+----------------------------------------------------------------------------------------------------+
| mydb_01 | CREATE DATABASE `mydb_01` /*!40100 DEFAULT CHARACTER SET gbk */ /*!80016 DEFAULT ENCRYPTION='N' */ |
+----------+----------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql>
三. 创建数据库
create database 库名 ;
create database if not exists 库名;
方式一 :
create database 库名 ;
mysql> create database mydb_01;
Query OK, 1 row affected (0.02 sec)
方式二 :
create database if not exists 库名;
mysql> create database if not exists mydb_02;
Query OK, 1 row affected (0.02 sec)
四. 删除数据库
drop database 库名;
drop database if exists 库名;
mysql> drop database mydb_02;
Query OK, 0 rows affected (0.02 sec)
mysql> drop database if exists mydb_03;
Query OK, 0 rows affected (0.01 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mydb_01 |
| mysql |
| performance_schema |
| sakila |
| sys |
| world |
| zy |
+--------------------+
五. 模糊查询 mysql 服务中所有的带character字符集的全局变量
show variables like ‘%character%’ ;
mysql> show variables like '%character%' ;
+--------------------------+---------------------------------------------------------+
| Variable_name | Value |
+--------------------------+---------------------------------------------------------+
| character_set_client | gbk |
| character_set_connection | gbk |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | gbk |
| character_set_server | utf8mb4 |
| character_set_system | utf8mb3 |
| character_sets_dir | C:\Program Files\MySQL\MySQL Server 8.0\share\charsets\ |
+--------------------------+---------------------------------------------------------+
8 rows in set, 1 warning (0.01 sec)
如果出现中文乱码:
character_set_client 客户端 utf8
character_set_results 结果集 utf8
改动字符集:临时解决中文乱码
mysql> set character_set_client=utf8;
Query OK, 0 rows affected, 1 warning (0.01 sec)
mysql> set character_set_results=utf8;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> show variables like '%character%' ;
+--------------------------+---------------------------------------------------------+
| Variable_name | Value |
+--------------------------+---------------------------------------------------------+
| character_set_client | utf8mb3 |
| character_set_connection | gbk |
| character_set_database | ut
f8mb4 |
| character_set_filesystem | binary |
| character_set_results | utf8mb3 |
| character_set_server | utf8mb4 |
| character_set_system | utf8mb3 |
| character_sets_dir | C:\Program Files\MySQL\MySQL Server 8.0\share\charsets\ |
+--------------------------+---------------------------------------------------------+
8 rows in set, 1 warning (0.00 sec)
六. 创建表
create table 表明(
列名1 字段类型1,
列名2 字段类型2,
...
列名n 字段类型n
) ;
数据库常用的字段类型
int------>默认int(11) 取的是当前实际长度 (推荐)
id int,---编号 1
int(长度):指定长度
id int(3) 1---->001
varchar(字符长度):字符串类型数据 '' "" 不写 引号 ,习惯去使用''或者双引号
double(值1,值2):小数类型
举例
double(3,2) ----小数类型,3位数,小数点后保留2位
123.56
日期类型
date 仅仅表示日期,不表示时分秒
"2021-8-12"
datatime 不仅表示日期具体的时间
"2021-8-12 17:31:00"
timestap:时间戳
给表中插入数据, 显示当前插入/修改/删除/查询数据那一刻时间 (具体到秒"2021-8-12 18:00:01")
注意事项:就是给那一个库中创建表 使用哪个库(选择数据库名)
use 库名;
mysql> use mydb_01;
Database changed
mysql>
mysql> create table student(
-> id int,
-> name varchar(20),
-> gender varchar(10),
-> address varchar(50),
-> age int
-> )
-> ;
Query OK, 0 rows affected (0.05 sec)
七. 查询当前数据库中有哪些表
show tables;
mysql> show tables;
+-------------------+
| Tables_in_mydb_01 |
+-------------------+
| student |
+-------------------+
1 row in set (0.01 sec)
八. 查询当前表的结构
desc student;
mysql> desc student;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id | int | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
| gender | varchar(10) | YES | | NULL | |
| address | varchar(50) | YES | | NULL | |
| age | int | YES | | NULL | |
+---------+-------------+------+-----+---------+-------+
5 rows in set (0.01 sec)
九. 修改表
1. 给表添加一列 :
alter table 表名 add 字段名称 字段类型;
mysql> alter table student add email varchar(50) ;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc student;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id | int | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
| gender | varchar(10) | YES | | NULL | |
| address | varchar(50) | YES | | NULL | |
| age | int | YES | | NULL | |
| email | varchar(50) | YES | | NULL | |
+---------+-------------+------+-----+---------+-------+
6 rows in set (0.01 sec)
2. 修改表中的字段类型
alter table 表名 modify 字段名称 更改后的字段类型;
mysql> alter table student modify address varchar(30) ;
Query OK, 0 rows affected (0.07 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc student;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id | int | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
| gender | varchar(10) | YES | | NULL | |
| address | varchar(30) | YES | | NULL | |
| age | int | YES | | NULL | |
| email | varchar(50) | YES | | NULL | |
+---------+-------------+------+-----+---------+-------+
6 rows in set (0.00 sec)
3. 修改表中的字段名称
alter table 表名 change 旧列名 新列名 字段类型;
mysql> alter table student change gender sex varchar(10) ;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc student;
+---------+-------------+------+-----+---------+-------+
句:数据库定义语句
库/表的增删查改
## 二. 查询当前mysql下所有的数据库
mysql8.0 自带的 跟5.5自带的不一样
> mysql > show databases;
```sql
+--------------------+
| Database |
+--------------------+
| information_schema | mysql默认的一些配置
| mysql | mysql库里面包含user表用户表 (权限用户)
| performance_schema | mysql性能相关的库
| sakila | 提供其他三个库 sakila sys world 提供了一些例库 练习sql语句
| sys |
| world |
| zy | 自定义的库(开发者自定义)
+--------------------+
7 rows in set (0.00 sec)
1.查看创建数据库的默认的字符集(了解)
show create database 库名;
mysql> show create database mydb_01;
+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| Database | Create Database |
+----------+-----------------------------------------------------------------------------------------------------------------------------------+
| mydb_01 | CREATE DATABASE `mydb_01` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */ |
+----------+-----------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
2. 修改数据库的字符集(了解)
alter database 库名 (default)可以省略 character set=字符集;
mysql> alter database mydb_01 default character set gbk ;
Query OK, 1 row affected (0.01 sec)
mysql> show create database mydb_01;
+----------+----------------------------------------------------------------------------------------------------+
| Database | Create Database |
+----------+----------------------------------------------------------------------------------------------------+
| mydb_01 | CREATE DATABASE `mydb_01` /*!40100 DEFAULT CHARACTER SET gbk */ /*!80016 DEFAULT ENCRYPTION='N' */ |
+----------+----------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql>
三. 创建数据库
create database 库名 ;
create database if not exists 库名;
方式一 :
create database 库名 ;
mysql> create database mydb_01;
Query OK, 1 row affected (0.02 sec)
方式二 :
create database if not exists 库名;
mysql> create database if not exists mydb_02;
Query OK, 1 row affected (0.02 sec)
四. 删除数据库
drop database 库名;
drop database if exists 库名;
mysql> drop database mydb_02;
Query OK, 0 rows affected (0.02 sec)
mysql> drop database if exists mydb_03;
Query OK, 0 rows affected (0.01 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mydb_01 |
| mysql |
| performance_schema |
| sakila |
| sys |
| world |
| zy |
+--------------------+
五. 模糊查询 mysql 服务中所有的带character字符集的全局变量
show variables like ‘%character%’ ;
mysql> show variables like '%character%' ;
+--------------------------+---------------------------------------------------------+
| Variable_name | Value |
+--------------------------+---------------------------------------------------------+
| character_set_client | gbk |
| character_set_connection | gbk |
| character_set_database | utf8mb4 |
| character_set_filesystem | binary |
| character_set_results | gbk |
| character_set_server | utf8mb4 |
| character_set_system | utf8mb3 |
| character_sets_dir | C:\Program Files\MySQL\MySQL Server 8.0\share\charsets\ |
+--------------------------+---------------------------------------------------------+
8 rows in set, 1 warning (0.01 sec)
如果出现中文乱码:
character_set_client 客户端 utf8
character_set_results 结果集 utf8
改动字符集:临时解决中文乱码
mysql> set character_set_client=utf8;
Query OK, 0 rows affected, 1 warning (0.01 sec)
mysql> set character_set_results=utf8;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> show variables like '%character%' ;
+--------------------------+---------------------------------------------------------+
| Variable_name | Value |
+--------------------------+---------------------------------------------------------+
| character_set_client | utf8mb3 |
| character_set_connection | gbk |
| character_set_database | ut
f8mb4 |
| character_set_filesystem | binary |
| character_set_results | utf8mb3 |
| character_set_server | utf8mb4 |
| character_set_system | utf8mb3 |
| character_sets_dir | C:\Program Files\MySQL\MySQL Server 8.0\share\charsets\ |
+--------------------------+---------------------------------------------------------+
8 rows in set, 1 warning (0.00 sec)
六. 创建表
create table 表明(
列名1 字段类型1,
列名2 字段类型2,
...
列名n 字段类型n
) ;
数据库常用的字段类型
int------>默认int(11) 取的是当前实际长度 (推荐)
id int,---编号 1
int(长度):指定长度
id int(3) 1---->001
varchar(字符长度):字符串类型数据 '' "" 不写 引号 ,习惯去使用''或者双引号
double(值1,值2):小数类型
举例
double(3,2) ----小数类型,3位数,小数点后保留2位
123.56
日期类型
date 仅仅表示日期,不表示时分秒
"2021-8-12"
datatime 不仅表示日期具体的时间
"2021-8-12 17:31:00"
timestap:时间戳
给表中插入数据, 显示当前插入/修改/删除/查询数据那一刻时间 (具体到秒"2021-8-12 18:00:01")
注意事项:就是给那一个库中创建表 使用哪个库(选择数据库名)
use 库名;
mysql> use mydb_01;
Database changed
mysql>
mysql> create table student(
-> id int,
-> name varchar(20),
-> gender varchar(10),
-> address varchar(50),
-> age int
-> )
-> ;
Query OK, 0 rows affected (0.05 sec)
七. 查询当前数据库中有哪些表
show tables;
mysql> show tables;
+-------------------+
| Tables_in_mydb_01 |
+-------------------+
| student |
+-------------------+
1 row in set (0.01 sec)
八. 查询当前表的结构
desc student;
mysql> desc student;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id | int | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
| gender | varchar(10) | YES | | NULL | |
| address | varchar(50) | YES | | NULL | |
| age | int | YES | | NULL | |
+---------+-------------+------+-----+---------+-------+
5 rows in set (0.01 sec)
九. 修改表
1. 给表添加一列 :
alter table 表名 add 字段名称 字段类型;
mysql> alter table student add email varchar(50) ;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc student;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id | int | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
| gender | varchar(10) | YES | | NULL | |
| address | varchar(50) | YES | | NULL | |
| age | int | YES | | NULL | |
| email | varchar(50) | YES | | NULL | |
+---------+-------------+------+-----+---------+-------+
6 rows in set (0.01 sec)
2. 修改表中的字段类型
alter table 表名 modify 字段名称 更改后的字段类型;
mysql> alter table student modify address varchar(30) ;
Query OK, 0 rows affected (0.07 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc student;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id | int | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
| gender | varchar(10) | YES | | NULL | |
| address | varchar(30) | YES | | NULL | |
| age | int | YES | | NULL | |
| email | varchar(50) | YES | | NULL | |
+---------+-------------+------+-----+---------+-------+
6 rows in set (0.00 sec)
3. 修改表中的字段名称
alter table 表名 change 旧列名 新列名 字段类型;
mysql> alter table student change gender sex varchar(10) ;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc student;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Ex