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. 方法的形式参数问题 :
形式参数的改变不能影响实际参数
7. 类与对象的关系 :
类 : 能够描述现实世界真实事物的一组属性和行为的集合!
事物 : 现实真是存在的事物;
创建对象 : 格式:类名 对象名 = new 类名();
面向对象 :
面向对象的思想特点:
1)更符号我们生活中是思想行为习惯
2)让复杂的事情简单化
3)我们从执行者变成了指挥者
三大特点 : 封装,继承,多态
8. 成员变量和局部变量的区别 :
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) ;
}
}
9. 匿名对象
没有名字的对象
格式:
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()) ;
}
}
10. 封装
目的 : 保证数据的安全性
11. private关键字的特点
1)可以修饰成员变量,也可以修饰成员方法,但是都只能在本类访问,外界类不能够访问
2)这些被私有修饰的成员变量,或者成员方法,可以间接通过公共方法来访问!
12. this
this:解决局部变量隐藏了成员变量
格式 : this.成员变量名 = 局部变量;
this:就是代表当前类的对象的地址值引用!
this.变量名 :访问的本类的成员变量
this.方法名():访问的是本类的成员方法
this() ; 访问本类无参构造方法
this(String xx):访问本类的有参构造方法
13. 构造方法
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)构造方法
14. 静态static关键字的特点:
1)随着类的加载而加载
2)优先于对象存在: 它不能this共存 (this:代表当期类对象的地址值引用)
对象还没有new的时候,当前被static修饰的成员就已经内存了
3)被静态修饰的 可以被多个对象共享:有共享共用的意思
4)被静态修饰的变量,方法----->静态变量或者静态方法
我们所说的成员变量和成员方法:都指的是非静态
静态的成员的访问方式:类名.变量
类名.方法名()
关于static关键字的使用注意事项:
1)非静态的方法既可以访问静态变量,也可以访问非静态的变量
既可以调用静态方法,也可以调用非静态方法
2)静态的方法:只能访问静态变量,
只能调用静态方法
简单记:静态只能访问静态
15. 代码块
在java中用{}包起来的内容,称为代码块.
分类 :
局部代码块 : 在方法定义中使用,作用:限定局部变量的生命周期.
构造代码块 : 在类的成员位置(类中,方法外),使用使用{}包裹起来
作用:给类中的一些成员进行数据初始化
特点:每次在执行构造方法之前,如果存在构造代码块,先执行构造代码块中的内容!
静态代码块 : 在类的成员位置,直接使用 static{}. 静态代码块就加载一次!
作用:也可以通过static代码块,对一些操作(后期IO流创建文件/JDBC)
特点:随着类的加载而加载,优先于对象存在!
// 优先级:
// 静态代码块(只执行一次) > 构造代码块 > 构造方法
16. 继承
将多个类的共性内容抽取到一个独立的类中,然后这多个类和独立的这个类产生一种关系 : 继承关系
关键字 : 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"的关系
17. 总结:this和super 的区别
this:代表的当前类对象的地址值引用
super:代表的父类对象的地址值引用(代表父类的空间标识)
访问成员变量
this.变量名; 访问的本类中的成员变量
super.变量名; 访问的是父类的成员变量
访问构造方法:
this() ; 访问本类的无参构造方法
super() ;访问的父类的无参构造方法
this(xxx);访问的本类的有参构造方法
super(xxx);访问的父类的有参构造方法
成员方法:
this.方法名();访问的是本类的成员方法
super.方法名() ;访问的是父类的成员方法
18.方法重载,方法重写
//如果子类出现了和父类一模一样的方法声明,叫做方法重写!(Override) —方法复写(覆盖)
//方法重写和方法重载的区别?
方法重载:Overload
在一个类中,提供n多个功能,这些功能,方法名相同,参数列表不同,与返回值无关(目的:提高某个功能的扩展性)
参数列表不同:
1)类型不同
2)个数不同
3)考虑参数类型的顺序
public static void open(int a,double d){}
public static void open(double a,int b){}
构造方法也可以重载!
重载的目的:为了提高功能的扩展性:同一个方法可以接收很多类型的参数
方法重写:Override
在继承关系中,子类出现了父类一模一样的方法声明,重写的目的:子类有自己的功能,需要将父类的该功能覆盖掉!
重写的目的:为了沿用父类的功能,并且还需要使用子类的功能(具体的子类才具备具体的动作...)
举例:
动物:
都具备吃和睡的功能
猫和狗都继承自动物类,他们吃的不一样的
猫和狗就需要讲吃和睡的功能覆盖掉...
猫具体吃鱼
狗具体吃肉
19.关键子 : 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. 编译时期异常和运行时期异常
编译时期异常和运行时期异常的区别?
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了...");
}
}
}
4. 注意
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("解析出问题了...");
}
}
}
5. 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);
}
}
6. 异常处理方法
不管使用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:捕获
}
}
7. 面试题
如果在某个方法中捕获异常,但是该方法有返回值类型,如果在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种设计模式都需要遵循 原则"低耦合,高内聚"
设计模式是一种思想,前人总结出来,不是技术!
创建型:对象的创建 (使用最多)
结构型:整个结构的组成
代理模式
静态代理
代理角色
真实角色
行为型:具备功能性的
创建型设计模式:
简单工厂:----称为 静态工厂方法模式
优点:利用多态创建子类对象,能够灵活去创建对象(提供的静态功能)
弊端:代码量大,一旦有新的类型增加,工厂类的静态功能就需要改动...