一、Java的发展史
Java是一门面向对象的编程语言 ,1994年推出该语言
Java之父:James Gosling
Java的鼻祖公司: Sun公司
Java 在2009年时被Oracle公司收购,先隶属于Oracle公司的产品
Java是开源、免费的产品
Java的开发工具: JDK (Java Development Kit)
JDK版本从 JDK1.0开始 到现在的JDK12.0 ,我们学习JDK8.0
编写Java程序,需要 安装JDK ,需要编写程序的工具( 可以使用记事本编写,也可以使用eclipse 或 IDEA工具) 。
二、安装Java工具
1、安装jdk JDK8.0
2、配置环境变量 :
我的电脑 -》 右键“属性”-》 高级系统设置 -》环境变量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0db15sB7-1604826438981)(C:\Users\wuyafeng\AppData\Roaming\Typora\typora-user-images\1600137686806.png)]
a、新建环境变量 JAVA_HOME
变量值: 安装的jdk的路径 例如C:\Program Files\Java\jdk1.8.0_144
b、在path变量中 新建 环境变量
%JAVA_HOME%\bin
如果是win7系统 ,也是在path新增 %JAVA_HOME%\bin; 注意这句话写在path的最前面
如果是win10 也需要将代码 移到最上面
C:\Users\wuyafeng\AppData\Roaming\Typora\typora-user-images\1600137883185.png
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mcIBNeYY-1604826438984)(C:\Users\wuyafeng\AppData\Roaming\Typora\typora-user-images\1600138002134.png)]
测试环境变量是否配置成功:
win+r : 调出命令行 输入cmd
再输入 java命令 、 javac命令
三、Java的语法
1、关键字
定义: 在程序中 用于赋予特殊意义的单词称为关键字 ,Java中有50个关键字
关键字必须小写 ,
1、基本数据类型关键字
byte : 定义字节类型关键字
short :定义短整型关键字
int:定义整型关键字
long:定义长整型关键字
char:定义字符型关键字
boolean:定义布尔类型关键字
float:定义单精度浮点类型关键字
double:定义双精度浮点类型关键字
void : 定义无类型
2、修饰符关键字
abstract : 修饰抽象关键字
private :私有的
protected : 受保护的
public : 公共的
static : 静态的
final: 定义常量的关键字
native : 调用其他语言的关键字
volatile: 短暂的
transient: 瞬时状态(短暂)
synchronized : 线程同步的
3、面向对象关键字
class : 定义一个类
extends : 继承 父类
implements :实现接口关键字
interface : 定义接口关键字
new : 创建对象
super : 继承中的关键字
import : 导入包关键字
package : 定义包名
this : 表示 当前对象
instanceof : 判断类型的关键字
4、控制语句(流程控制)
if : 判断条件
else : 判断条件
switch : 判断条件
case : 条件判断的分支
while : 循环关键字
do : 循环的一部分
for: 循环关键字
break : 终止操作
continue : 终止后继续
return: 返回
default : 默认
5、异常关键字:
try : 试一试
catch: 捕获
finally : 异常最后执行的语句块
throws: 定义抛出的异常
throw: 抛出异常
保留字: goto 、const
字面值 : true 、false 、null
6.其他
assert:测试中的断言
strictfp:其他
enum:定义枚举
2、标识符
定义: 用于给java中的类,接口,方法,变量命名的单词称为标识符 ,标识符由如下规则
1、标识符由数字、字符、_ 或$ 组成
2、标识符不能以数字开头
3、标识符区分大小写 (例如 student 、 Student 不一样)
4、标识符不能是关键字
注意 标识符尽量 “见名知义” , 对类的标识符 必须 首字母大写, 后面的单词首字母大写
例如 定义类名的标识符: User 类,Student类型 , CardInfo类
准寻“驼峰命名法” StudentInfo People mapCase userInfoCase
abc | 正确 | $abc | 对 | p | 对 |
---|---|---|---|---|---|
abc1 | 正确 | _abc | 对 | 变量 | 语法满足但不建议 |
1abc | 错误(不能以数字开头) | _1 | 对 | userinfo | 对 |
true | 错误(不能是关键字) | _true | 对 | null | 错 |
3、变量 :
在程序中 其值可以改变的量称为变量, 变量程序中占有一定的内存大小 ,变量的内存在程序运行期间才会分配。
同样村组件内存大小,其值不可以改变的量称为 常量
定义变量的语法 :
数据类型 变量名(标识符) = 初始值;
或
数据类型 变量名;
或定义多个变量
数据类型 变量名1 , 变量名2;
4、数据类型
用于定义变量时存储数据的类型 ,数据类型分为两大类
1、 基础数据类型(简单数据类型)
数值型 : 整型
byte : 字节
short : 短整型
int : 整型
long : 长整型
浮点型
float : 单精度
double : 双精度
字符型: char
布尔型: Boolean
2、引用数据类型(复合数据类型)
数组、自定义类 、 接口 、集合 等
不同的数据类型 它们的内存大小不一样
数据类型 | 大小 | 范围 |
---|---|---|
byte | 1个字节 (8个位) byte n=1 | -128~127 -》 -2的7次方 ~ 2的7次方-1 |
short | 2个字节 (16个位) | -2的15次方 ~ 2的15次方-1 |
int | 4个字节(32位) | -2的31次方 ~ 2的31次方-1 |
long | 8个字节 (64位) | -2的63次方~ 2的63次方-1 |
float | 4个字节 | 单精度 |
double | 8个字节 | 双精度 |
char | 1个字符= 2个字节 | ‘’ |
boolean | 布尔类型 1个字节 |
byte n = 1 ,:表示该变量可存储的最大空间是 1字节
数据类型之间的转换
在Java中,必须相同数据类型的变量才可以相互计算,如果数据类型不同,必须先转成相同的
数据类型转换准寻一下规则:
数据类型从低到高排列
byte -》 short - 》 char -》 int - 》 long - 》 float - 》double
其中boolean 不参与数据类型转换
1、低类型转高类型 会自动转换
2、高类型转低类型 需要强制类型转换
public static void main(String[] args){
// 定义变量
int num1=100;
long n1 =100;
long num2 = num1 + n1;
int num3 = num1 + (int)n1; // 强转后相加
int num4 = (int)(num1 + n1); // 相加后强转 都可能丢失精度
// int 型不能 + long型 必须保证两者一致
// 等号右边会自动转成 高类型 long ,由于等号左边是 int ,两者不能画等号
System.out.println(num4);
byte b1 = (byte)num4;
System.out.println(b1);
// 200 >
short s =(short) (b1 + 1 ) ;
// Java中 将整数当做 int 类型
// 对于小数的转换
float f1 = 100.05F;
long s2 = (long)f1;
System.out.println(s2); // 丢失精度 100
double d1 = 100.05;
double d2 = f1+d1; // 小数与小数之间计算 会出现非常精确的小数 位
System.out.println(d2);
int i=1;
System.out.println(i/2);//0
System.out.println(i/2.0);//0.5 int/double 自动先转成double
/**
* 知识点总结
* 1、 变量的定义
* 2、数据类型的 转换 ( 高类型转低类型 是强转 ,低类型转高类型是 自动转换)
* 3、高类型转成低类型 可能会丢失精度
* 4、类型与类型相同才可以计算,其计算的结果也是相同数据类型
*/
}
一、运算符
在Java中用于程序计算的操作符统称为运算符,运算符分为如下几类
1、算术运算符
运算符 | 说明 | |
---|---|---|
+ | 加号两遍是数值,可以运算,如果一边存在字符串,则当做连接符 | a+b |
- | 两个数相减 , 减号也可以表示负数 | a-b -a |
* | 两个数相乘, 其中*不能省略 | a*b |
/ | 两个数相除,必须保证数据类型一致,其中除数不能为0 ,否则出现算术异常 | a*b |
% | 对一个数取余数 | a%b |
++ | 对一个数 自加1 | a++ 、++a |
– | 对一个数自减1 | a–、--a |
// 总结 ++ – 的优先级高于 + ,-,*,、,%
public static void main(String [] args){
// + - * / % 所有运算符在计算式必须保证 数据类型一致
int num1 = 100;
int num2 = 200;
int sum = num1 + num2;
int mul = num1-num2;
int num3 = -num2; // -200
System.out.println("两个数相加" + sum); // + 表示连接符
System.out.println("两个数相减" + mul);
System.out.println( "num2: " + num2+ " ,num3:" + num3 );
System.out.println("num1+num2="+ (num1+num2) );
int sum2 = num1*num2;
int sum3 = num1/num3;
System.out.println(sum3); // 控制台输出的快捷键 sout+ enter
// System.out.println(num1/sum3); //算术异常:ArithmeticException: / by zero
// 取模
System.out.println( 10%2);//0
System.out.println(1%5);// 1
System.out.println(2%5);
}
public static void main(String[] args) {
// 自加 自减 ++ -- 只能对整数进行
int i=1;
i++ ; // i = i + 1
System.out.println("i:" + i);
int j=1;
++j; // j = j + 1
System.out.println("j:" + j);
int a =1;
int sum = a++; // 先将a的值赋值给sum ,a再自加1
int sum2 = ++a; // 先将a自加1 ,再将自加后的结果赋值给sum2
System.out.println("sum:" + sum);
System.out.println("sum2:" + sum2 );
System.out.println("a:" +a); // 3
int sum3 = a++ + a++ ;
System.out.println("sum3:"+sum3);
System.out.println("a:"+a);
// -- 操作 与++操作类似
int b=1;
int s1= b--; // b = b -1 s1 的值为 先赋值再自减1 s1 = 1
int s2 = --b; // b = b -1 s2 的值为 先自减1 ,再赋值 s2 = -1
System.out.println("b:"+b);
System.out.println("s1:"+s1);
System.out.println("s2:"+s2);
// ++ -- 综合
int x=1;
int y=2;
int s3 = x++ + --x * (y-- + ++x);
System.out.println("x:"+x);// 2
System.out.println("y:"+y);// 1
System.out.println("s3:"+s3);// 5
}
2、比较运算符
用于比较两个表达式之间的 结果 , 结果返回true 、false
比较运算符不能单独作为 一行代码运行 ,必须接收结果或者输出
比较运算符 | 说明 | 案例 |
---|---|---|
> | 比较左边的数是否大于右边的数,如果大于返回true,否则返回false | a>b ,3>5 |
< | 比较左边的数是否小于右边的数,如果大于返回true,否则返回false | a<b |
>= | 比较左边的数是否大于等于右边的数,如果大于返回true, 否则返回false | 1>=2 |
<= | 比较左边的数是否小于等于右边的数,如果大于返回true ,否则返回false | 1<=2 |
== | 比较==两遍是否相等,如果相等则返回true ,否则返回false ,对于基本数据类型比较数值是否相等, 对于引用数据类型比较 它们的地址是否相等 (比较地址就是比较是否同一块内存) | 1==2 |
!= | 与== 相反, 比较左边和右边的数是否不相等 。如果不相等返回true ,如果相等返回false | 1!=2 |
注意: 不能连写 例如 1<3<5
public static void main(String[] args) {
// 比较运算符
int a =1;
int b =2;
// a>b; // 不能单独比较 必须接受结果或输出
System.out.println(a>b); //false
System.out.println(a<b); //true
System.out.println(a==b); //false
System.out.println(a!=b); //true
// 增加逻辑判断
// 如果 if 后面的条件成立, 则执行if里面的语句 ,如果不成立 则只需else语句
if(a>b){
System.out.println("a>b成立");
}else{
System.out.println("不成立");
}
}
3、赋值运算符
将表达式 的结果 赋值给一个变量,只能赋值给变量 不能赋值给常量
例如: a = 3
赋值运算符 | 说明 | 案例 |
---|---|---|
= | 将=右边的结果 赋值给左边的变量 | int a = 2 ,将2赋值给变量a |
+= | 计算后的赋值,将+=右边的结果赋值给左边的变量 | a+=2 等价于 a = a +2 |
-= | 计算后赋值,将-=右边的结果赋值给左边的变量 | a-=b 等价于 a = a-b |
*= | 同上, 将右边的结果赋值给左边的变量 | a*=b 等价于 a = a * b |
/= | 同上,将右边的结果赋值给左边的变量 | a /=b 等价于 a = a/b 其中b!=0 |
%= | 同上,将右边的结果赋值给左边的变量 | a %=b 等于 a= a%b 其中b!=0 |
// 赋值运算符 = += -= *= /= %=
int a=2;
a+=2;
System.out.println(a);// 4
a-=3;
System.out.println(a);// 1
a*=2;
System.out.println(a); // 2
a/=4;
System.out.println(a);// 0
a+=a-=3; // a+=(a-=3) -》 a=a +(a=a-3 )
System.out.println("a="+a);
int x=2;
x+=x-=x*=x++;
//x = x +(x = x -( x= x *(x++) ))
// x = 2 + ( x = 2 - (x = 2 * 2))
// x = 2 + ( 2 - 4)
// x = 0
System.out.println("x="+x);
//赋值运算符的优先级最低, 从右往左计算
int y=2;
y*=y+=y; // 也是从右往左计算
// y = y * (y = y + y);
// y = 2 * (2+2)
// y =8;
System.out.println("y="+y);
4、逻辑运算符
在Java中用于两个或两个以上的表达式 取逻辑判断的结果 ,通常需要使用逻辑运算符
逻辑运算符 | 说明 | 案例 |
---|---|---|
& | 逻辑与 ,左右两边可以连接表达式 ,两个表达式都成立时,整个结果为true | true&true、 false&true false&false 、 true&false |
| | 逻辑或,左右两边的表达式,任意一个成立,整个结果为true | true|true false|true true|false false|false |
! | 逻辑非 对表达式取反 | !false !true |
&& | 短路与 , 与&类似 ,短路 特点: 符号左边为false,右边不再运算 | true&&true true&&false … |
|| | 段落或, 与| 类似,段落忒点: 符号左边为true ,右边不再运算 | false||true false||false |
// 逻辑运算符
System.out.println(true & true); // true
System.out.println(true & false);// false
System.out.println(false & true); // false
System.out.println(false & false);// false
// true&true
System.out.println(1>0 & 3>1);
System.out.println(1>0 && 3>1);
// | 或
System.out.println(true | true); //true
System.out.println(true | false);// true
System.out.println(false | true); // true
System.out.println(false | false);// false
// || 短路或
System.out.println(true || false) ;// true
总结: &与&&的区别 |与||的区别?
回答 1、& 对于符号两边的表达式都会执行 && 符号左边为false,则右边不执行
| 对于符号两边的表达式都会执行, || 符号左边为true,则右边不执行
2、 & 、| 两边可以直接写数字, 按位计算 ,短路符号 不能直接运算数字
int a=1;
int b=2;
// System.out.println(a>b && b++>0); // 符号右边的不运算
// System.out.println("b:"+b);
System.out.println(a>b & b++>0); // 符号两边都运行
System.out.println("b:"+b);
// || 与 | 的区别
// System.out.println(a>=1 || a++<0); // a++ 不执行
// System.out.println("a:"+a);
System.out.println(a>=1 | a++<0 ); // a++ 会执行
System.out.println("再次输出a :" + a);
十进制转二进制
十进制 | 转换 | 二进制 |
---|---|---|
1 | 1*2的0次方 | 1 |
2 | 1*2的1次方 | 10 |
4 | 1*2的2次方 | 100 |
对于2的倍数 ,1*2的n次方 转成二进制位1后面n个0 | ||
16 | 1*2的4次方 | 10000 |
对于任意十进制转二进制,先查找比它小的且离它最近的2的倍数 ,然后差值再计算二进制 ,(使用拆分法)
例如 28 = 16+8+4 = 11100
34 = 32+2 = 100010
96 = 64+32 = 1100000
二进制转十进制
公式: 从个位开始,每个位上的数 乘以2的n次方 累加之和
100100 = 1*2的2次方+1 * 2的5次方 = 36
5、位运算符
移位运算符 | 说明 | 案例 |
---|---|---|
<< | 左移: 将一个数转成二进制之后,整体往左移动n位 ,扩大倍数 ,一个数向左移动n位,结果为这个数乘以2的n次方 | 3<<2 = 3*2的2次方 |
>> | 右移:将一个数转成二进制后,整体往右移动n位,缩小倍数,一个数向右移动n位,结果为这个数除以2的n次方(除不尽单独考虑) | 8>>2 = 8/2的2次方 |
> > > | 无符号的右移 ,不考虑符号位,将这个数整体王右移动n位。 | |
^n | 异或 | |
~n | 数值取反 |
//正整数的移位 << >> >>>
System.out.println(3<<2); // 12
System.out.println(7<<3); // 7*8=56
// 对于正数的无符号右移和 右移 结果一样
System.out.println(16>>2);// 4
System.out.println(18>>2);// 4
System.out.println(5^9);//12
System.out.println(3>>2);
System.out.println(3>>>2);
System.out.println(~5);
// 负数的移位
// 负数的左移位还是为负数
System.out.println(-4<<2); // -4*2的2次方 =
/**
* -4的原码: 1 0000... 00000100
* * -4的反码: 1 1111... 11111011
* * -4的补码: 1 1111... 11111100
* * 开始移位 2
* 1 1111... 11110000
* 最后结果 = 取反+1
* 1 0000... 00001111 + 1
* :
* 1 0000... 00010000 =-16
*/
// 补码 = 反码+1
//负数是对于补码 进行移位 -4/2 =-2
System.out.println(-4>>1);
// -16无符号右移2位
System.out.println(-16>>>2); //1073741820
// -16 的补码算出来
/**
* 原码 10000.. 0010000
* 反码 11111.. 1101111
* 补码 11111.. 1110000
* 00111.. 1111100 由于不考虑符号,移动后高位全部补0 变成正数
* 正数原码和补码一致 这个数即为所得
* 1073741820
*/
6、三目运算符
表达式 ? 结果1 : 结果2
当?前面成立时, 整个表达式输出 结果1 ,如果?前面不成立,则输出结果2
// 生成100以内的随机数
int n = (int)(Math.random()*100);
System.out.println("n:"+n);
System.out.println( n%2==0 ?"这个数是偶数":"这个数是奇数");
二、表达式、语句块
表达式 : 通过运算符 和 操作数组成的 元素 , 表达式不能单独写在程序中,需要接受结果或输出。
表达式中可能同时存在多个操作符 ,此时需要考虑操作符的优先级问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-30ivIRX8-1604826438986)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1.png)]
注意 : 这里的() 表示 方法的括号 ,点号表示小数点 ,[] 表示数组的下标
2-5 : 用于得到结果值
6-12:用于运算得到ture、false
13,14:是赋值 。赋值的优先级最低
语句块:
在一个方法中,可以使用{} 表示一个语句块 , 也可以放在方法的外面 ,类的里面,称为独立代码块
public static void main(String[] args) {
//定义一个方法中的代码块
{
// 局部变量只能使用在它所在的区域
int a=1;
a++;
System.out.println("这是一个方法里面的代码块 "+a);
}
// a++;
if(true){
System.out.println("这是一个if代码块");
}
}
一、关键字 break、continue 、return的区别
1、break : 用于在switch。。case中放置语句块穿透,
用于跳出循环
// 从1-100 遇到7的倍数 break
for(int i=1;i<100;i++){
// i==7 跳出循环
if(i%7 == 0 ){
break;
}
System.out.println(i);
}
2、continue: 跳出本次循环,继续下一次循环
for(int i=0;i<100;i++){
if(i%7 == 0 ){
continue; // 跳出本次循环,继续下一次循环
}
System.out.println("i---"+i);
}
3、return : 返回 本次方法
用法1 : 如果return放在循环中, 会跳出循环,且不会只想循环外面的语句 ,
用法2: 作为方法的返回值
用法3 : 无论方法是否有返回值 ,可以在条件判断的位置 直接 return ,
return和break在循环语句块是,break只是结束循环语句块,对于循环外面的代码会执行,而return是结束当前所在方法的剩下的语句块。
public static void main(String[] args) {
for(int i = 1;i<100;i++) {
if (i == 50) {
return;
}
System.out.println("i----"+i);
}
System.out.println("程序结束");
}
public void method1(){
// return 还可以在条件判断的位置 直接返回方法
int n = (int)(Math.random()*10);
if(n%2 ==0){
return ; // 方法的后面就不再运行
}
System.out.println("方法的其他代码块");
}
结论:只要执行return,那么它 后面的代码都不执行。
public int add(){
return 0;
}
return作为方法返回值的关键字 ,
二、嵌套循环 以及案例
嵌套循环: 在一个循环语句中,还包含了另一个循环。例如 在一个for循环中还有一个for循环 ,
它的总的循环次数 = 外循环的次数* 内循环的次数
语法:
for(){ // 外层循环
for(){ // 内层循环
}
}
执行顺序: 外层循环 循环一次 ,内层循环循环完整的一遍
* * * * *
打印直角三角形
*
* *
* * *
* * * *
* * * * *
外循环控制打印几行, 内循环控制打印即可*
*
* * *
* * * * *
* * * * * * *
* * * * * * * * *
* * * * * * *
* * * * *
* * *
*
思路 : 考虑一行打多少个空格 多少个*
一共5 行 空格的个数(5-i) *的个数 (2 * i - 1)
i=1 4 1
i=2 3 3
i=3 2 5
i=4 1 7
i=5 0 9
System.out.println("打印正三角形");
for(int i=1;i<=5;i++){
// 先打印空格
for(int k=0;k<5-i;k++){
System.out.print(" ");
}
// 再打印*
for(int j=0;j<2*i-1;j++){
System.out.print("* ");
}
// 换行
System.out.println();
}
九九乘法表
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
....
1*9=9 2*9=18 ...... 9*9=81
三、数组的概念以及数组案例
1、容器的概念
用于存储数据的一块内存称为容器,生活中有很多容器,例如 水杯,衣柜,书包,有一定的空间可以存放“东西”
存放在容器中的数据 称为“元素”
2、为什么会存在数组呢?
假如现在要存储全班同学的成绩 , 全班40人,按照定义变量的思维,需要定义40个double类型的变量,每次从40个变量中找某一个变量,操作很麻烦, Java中可以定义一个数据类型存放40个人的成绩 , 这个类型就是数组类型。
数组定义: 它是相同数据类型的有序集合
3、 数组特点
- 数组的长度固定(数组的长度一旦声明,就不能改变)
- 数组中存储的元素数据类型必须相同
- 数组的元素 通过下标访问,且下标默认从0 开始
- 数组类型属于引用数据类型, 数组的元素类型 既可以是基本数据类型,也可以是引用数据类型。
4、创建数组
方式一:
数组存储的数据类型 [] 数组名 = new 数组存储的数据类型[长度];
详解:
数组存储的数据类型 :创建数组容器中可以存储什么数据类型 (基本数据类型 ,引用数据类型)
[] : 表示数组
数组名: 给数组起给名字,遵循标识符规则
new : 创建数组的关键 字
[长度] : 数组的长度 , 这个长度定义后不可改变
例如
int [] arr = new int[3];
new出来的空间在堆内存中,数组是引用数据类型 ,存在内存地址
内存解析: 在堆内存中开辟一段连续的3个长度的int类型的内存空间 ,并由arr变量指向这块内存的地址 (换句话说 arr输出的就是 这个内存的地址)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g5UOqLth-1604826438988)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1600935311755.png)]
方式二:
数据类型 [] 数组名 = new 数据类型[]{元素1,元素2,元素3...}
这里的数组长度根据元素的个数自动分配大小
int [] arr = new int[]{90,88,78,92};
或者
int arr [] = new int[]{90,88,78,92}
方式三:
数据类型 [] 数组名 = {元素1,元素2,元素3...};
注意: 这里的元素类型必须满足 数组的元素数据类型
char [] arr = {'a','b','c'};
或者
char arr [] = {'a','b','c'};
5、数组的访问
数组的访问通过索引访问
索引(下标): 每一个数组的元素都有一个编号,这个编号从0开始 , 这个编号称为数组的索引,通过数据名[索引] 访问到数组的原始
例如: 访问数组的第二个元素: 数组名[1]
数组的长度: 数组的长度 声明已固定 ,访问数组的长度 : 数组名.length
数组的最大索引= 数组的长度 -1
数组元素的赋值 :通过索引可以给元素赋值 数组名[索引] = 值
将数据 赋值给 指定索引的 元素
一、动态数组
1、数组的定义
用于存储相同数据类型的一组连续的存储空间
2、数组的特点:
数组的长度一旦定义,则不可改变
访问数组的元素需要通过下标(索引)访问,下标从0开始
数组是引用数据内存,内存分布在堆内存中,数组的变量存储的内存地址
3、动态数组:
由于数组的长度定义后不能改变,所谓“动态数组”是可以增加数组的长度, 所以Java实现动态数组是改变数组变量指向不同的内存的地址。 本质并没有将数组的长度改变。
动态数组的本质: 将内存空间的改变, 以及指向数组内存的地址改变
操作1 : 给数组 添加新元素 ,可添加在最后面 ,也可添加到指定位置
/**
* 添加元素 (添加到末尾)
*/
public static int [] addEle(int [] array ,int num){
// 目标数组 添加的原始
//int [] array = {10,9,3,2,1};
// 1、创建临时数组的变量
int [] tempArray = new int[array.length+1];
//2、将目标数组的元素 copy到 临时数组的内存中
for(int i = 0 ;i<array.length;i++){
tempArray[i]= array[i];
}
// 3、将添加的元素放入临时数组中
tempArray[tempArray.length-1] = num;
// 4、将目标数组的地址 指向 临时数组的地址
array= tempArray; // 由于tempArray 是局部变量, 方法执行完内存自动回收 ,
// 如果不返回 没有地址指向tempArray的内存
// 如果返回并接收,说明这块内存仍然有用。
return array;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1bBpo12M-1604826438990)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1601352120889.png)]
/**
* 将元素num 添加到指定index的位置
* @param arr
* @param num
* @param index
* @return
*/
public static int [] addEle(int [] arr , int num ,int index){
// 1、创建临时数组的大小
int [] tempArray = new int[arr.length+1];
//2、遍历arr
for(int i = 0 ; i<=arr.length;i++){
// 如果i<index
if(i<index){
tempArray[i] = arr[i];
}else if(i==index){ // 2
tempArray[i] = num;
}else{ // i > index
// i=3 arr[i-1] 10 9 3 2 1 -> 10 9 5 3 0 0
// i =4 array[i-1] 2-> 10 9 5 3 2 0
// i=5 array[4] 1 -> 10 9 5 3 2 1
tempArray[i] = arr[i-1];
}
}
// 赋值
arr = tempArray;
return arr;
}
操作2: 删除元素 ,删除指定下标的元素
操作3、修改指定下标的原始
一、二维数组以及多维数组
1、二维数组定义:
在一维数组中定义每一个元素也是一个数组元素,这样的数组称为“二维数组”
多维数组就是在一维数组上再次定义二维数组或三维数组等。
一维数组定义 int [] array = { 1, 2 , 3}
//定义三个长度的二维数组,其数组的每一个元素是一个一维数组
int [][] arrays = {{} ,{} ,{}} ;
或者
int [][] arrays = new int [3][2]; // 左边的【】中表示二维数组的长度 ,其中2可以省略,3 不能省略
// 注意: 等号左边有几个【】 就表示几维
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ff581a7-1604826438992)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1.png)]
// 1、定义二维数组
int [][] array;
// 定义时给二维数组赋值 3个长度的二维数组 里面的一维数组的长度不一定相等
int [][] array2 = {{1,2,3},{4,5},{7,8}};
// 定义时只指定大小 不给定初始值
int [][] array3 = new int[3][]; // 等价 {{},{},{}}
// array3[0][0]=1; // 赋值时 空指针异常 ,因为里面的一维数组是空的
//定义一个3个长度的二维数组,里面的元素长度是2
int array4 [][] = new int[3][2];
//给元素赋值
array4[0][0] =1;
// 输出二维数组中的所有元素
for(int i=0;i<array4.length;i++){
// System.out.println(array4[i]);
for(int j=0;j<array4[i].length;j++){
System.out.println(array4[i][j]);
}
}
一、方法
方法的概念
将一个功能抽取出来,放在类中的大括号中, 形成一个独立的功能 , 当需要使用该功能时,则调用它, 这样可以增强代码的复用性(重复利用) ,并解决代码的冗余现象。
方法的语法:
[访问修饰符] 返回值类型 方法名( [参数类型 参数名1 , 参数类型 参数名2 …] ){
方法体
}
详解:
访问修饰符: 用于修饰这个方法的调用范文 目前默认 public static
返回值类型: 无返回值就写void 或者 方法执行后返回的结果的数据类型 ,方法执行完之后会将结果返回
方法名 : 给方法取名, 准寻标识符的规则
参数类型 、参数名: 它们是同时定义 ,方法在执行时未知的数据,需要在调用方法时进行传递值。 参数类型表示 这个参数的数据类型 ,参数名就是一个参数名称 这个参数名可以在方法体中使用。
方法体: 这个方法具有的功能(代码块)
定义一个方法
public static int add(int num1 , int num2){
return num1+num2;
}
根据方法的参数 不同,返回值不同,可以将方法分为四类:
1、无参无返回值方法
语法:
public static void 方法名(){
方法体
}
2、无参有返回值方法
语法:
public static 返回值类型 方法名 (){
方法体
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YahmxQA3-1604826438994)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1601006194152.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B5FmKzya-1604826438995)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1601006324810.png)]
结果在n中
3、有参无返回值方法
语法:
public static void 方法名(参数列表 ){
方法体
}
情况一、 当参数中是基本数据类型 ,将实参的值赋值给 形参
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rDOFQfyO-1604826439008)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1601013606427.png)]
public static void add(int num){
num++;
System.out.println("方法中 num 的值:"+num);
}
调用方法: num的改变不会改变n 的值 ,因为是两个独立的内存
int n =10;
System.out.println(n);
add(n);
System.out.println("方法外面 实参的值:"+n);
结果输出:
方法中 num 的值: 11
方法外面 实参的值:10
情况二: 当参数的数据类型是引用数据类型
例如 数组 、类
// 方法的比对 参数是数组
public static void add(int [] arr){
arr[0]++;
System.out.println("方法中 arr[0]="+arr[0]);
}
调用:
int [] array ={10,20};
add(array);// 会调用 参数是数组的add方法
System.out.println("方法外面的 arr[0]="+ array[0]);
结果:
方法中 arr[0]=11
方法外面的 arr[0]=11
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LS0VFr8u-1604826439012)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1601016479071.png)]
类的调用:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wrIWg6ua-1604826439016)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1601015593741.png)]
public static void addAge(Student student){
// 将学生的年龄 获取 自加
int age = student.age;
age++;
// 在重新赋值
student.age=age;
}
调用
Student student = new Student(); // age默认为 18
System.out.println("方法调用之前: "+student.age); // 18
addAge(student); // 传的地址
System.out.println("方法外面 学生的年龄:"+student.age);
结果输出: 方法调用之前: 18
方法外面 学生的年龄:19
/**
* 以上方法调用 的结论: 对于参数为引用数据类型,方法外面和方法内部公用同一块内存,
* 也就是说 参数再传递时,将引用类型的地址赋值给 方法的形参
* 对于基本数据类型 ,方法的参数将 值的副本赋值给形参,这样形参的改变
* 不会影响实参的值
* 原因: 引用数据类型在参数传递时 是地址 (JVM对于堆内存的大小不可控)
* 基本数据类型在参数传递时 是值
*
*/
情况三、 当参数是String类型时 ,String是引用数据类型 ,但是在参数传递时,是与基本类型一样
public static void main(String[] args) {
// 参数是字符串
String uname ="李泽文";
sayHello(uname);
System.out.println("我最后对"+uname+"sayHello");
}
public static void sayHello(String name){
name="祝一帆";
System.out.println("我对"+name+"sayHello");
}
结果:
我对祝一帆sayHello
我最后对李泽文sayHello
4、有参有返回值方法
语法:
public static 返回值类型 方法名(参数列表){
方法体
}
例如 :
public static String sayHello(String name){
name="祝一帆";
System.out.println("我对"+name+"sayHello");
return name ;
}
调用
public static void main(String[] args) {
// 参数是字符串
String uname ="李泽文";
// 将返回值接收 ,这是 uname 才被改变 ,如果不接受,则不改变
uname = sayHello(uname);
System.out.println("我最后对"+uname+"sayHello");
}
结果:
我对祝一帆sayHello
我最后对祝一帆sayHello
一、方法的调用以及方法参数传递
1、方法的定义:
访问修饰符 返回值类型 方法名 ([参数列表]){
方法体
}
如果方法体中需要一些未知的数据作为执行条件,那么这些数据可以作为参数。
如果方法需要返回数据,在定义有返回值的方法,且需要明确返回值类型
方法调用两种方式:
1、 对象名.方法名(参数)
2、直接写方法名调用 ,但必须是static修饰的
// 获取这个字符串的第一个字符
public static char method1(String str){
return str.charAt(0); //获取字符串的第一个字符
}
//方式一: 调用方法
对象名.method1()
//方式二: 调用静态方法 (static修饰的)
String s ="abc";
char c = method1(s)// s将赋值给 方法的形参 str ,那么方法执行时str有值
// c 就是方法调用后的返回值
1、方法的参数是基本数据类型 ,它传递的是 值
// 计算一个数的平方
public static int pow(int a){
a=a+2; // 6
return a*a; // 6*6 = 36
}
main:
int num=4;
int sum = pow(num); //num实参 num赋值给a
System.out.println(num); // 4
System.out.println(sum); // 36
2、方法的参数是引用数据类型,它传递的是地址 (String类型除外)
//参数 是数组类型
public static int pow(int [] arr){
arr[0]=arr[0]+2; //根据地址 改变数组的第一个元素值 arr[0]=4;
return arr[0]*arr[0]; // 4*4
}
调用
// 调用另一个pow
int [] array = {2,3};
int sum2 = pow(array) ; // array中存放数组的地址 ,将地址赋值给 arr形参
System.out.println(array[0]);// 4
System.out.println(sum2 ); // 16
方法的重载:
在一个类中,存在相同方法名的两个及以上的方法,且他们的参数列表不同(参数类型不同 ,参数个数不同,参数顺序不同),不考虑参数名,不考虑返回值类型
一个类中不能存在完全相同的方法,他们是重复的方法 JVM编译不通过
例如:
public int add(int a ,int b){
return a+b;
}
public int add(long a,long b){
return (int)(a+b);
}
//与前两个 不重载
public void add(int c,int d){
}
// 能与 方法1 ,2 构成重载
public void add(int a){
}
// 能与 方法1 ,2 构成重载
public void add(int a ,long b){
}
// 能与 方法1 ,2 构成重载
public void add(long a ,int b){
}
把握: 方法名是否相同,同时 参数列表是否不同 (参数个数不同 或者参数类型不同或者参数顺序不同)
在一个方法可以调用其他的方法, 它们的执行流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OgdpSYmo-1604826439020)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1601281371019.png)]
/**
* 程序入口
* @param args
*/
public static void main(String[] args) {
System.out.println("这里是main方法的开始");
method1();
System.out.println("这是main方法的结束");
}
// 在一个方法中可以调用另一个方法, 它们的执行顺序是这样的
public static void method1(){
System.out.println("这是method1 的开始语句块 ");
// 调用method2
method2();
System.out.println("这是method1的 结束语句块");
}
public static void method2(){
System.out.println("这是method2 的开始语句块 ");
// 调用method3
method3();
System.out.println("这是method2 的结束语句块");
}
public static void method3(){
System.out.println("这是method3的执行语句块");
}
二、方法的递归调用
1、定义
在一个方法中,运行方法自身调用,这样的调用称为方法的递归调用, 为了避免出现方法调用时出现死循环,递归调用中必须定义 方法结束调用的条件。
方法递归调用执行流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6rpFsEWO-1604826439022)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1601282957753.png)]
public static void m3(int i){
if(i==5){
return ;
}
System.out.println("这是m3方法开始i :"+ i);
i++;
m3(i);
System.out.println("m3方法的结束 i:"+ i );
}
public static void main(String[] args) {
m3(0); //
}
练习1 : 使用递归计算一个数的阶乘
n =5 = 5! = 5*4的阶乘
f(n) = n* f(n-1) =
(n-1)*f(n-1-1)
public static int f(int n ){
if(n==1){
return 1;
}
int s = n*f(n-1);
System.out.println("结束 --"+s);
return s;
}
// n =5 n=4 n=3 n=2 n=1 return 1
// s = 5*f(4) s=4* f(3) s= 3* f(2) s=2*f(1)
// s=5*24 s=4* 6 s=3* 2= 6 s=2
// s = 120
练习2 : 一组序列 1 ,1, 2,3, 5 , 8… n, 第12个数 为多少?
求第n数,
假设n=1 这是数 1
假设 n=2 这是数 1
假设n=3 这是数 第n-1个数 + 第n-2个数
n n-1 的数 + n-2的数
public static int f(int n){
// 退出的条件
if(n==1){
return 1;
}
if(n==2){
return 1;
}
int sum = f(n-1)+f(n-2);
return sum;
}
// 分析
int sum = f(12)
f(5)
假设 n=5
1 n==5 sum = f(4) + f(3) = 3+2
f(3)+ f(2) f(2)+f(1)
f(2)+f(1) 1+ 1
1 + 1 + 1
练习3: 1, 3, 5, 7, 9, 11 . 问前10个数的和
倒推 找出口
n=1 前1个数的和 1
n=2 前2个数的和
n=3 前3个数的和 =1+3+5 5
n 前n个数的和 = 前n-1个数的和 + 第n个数
如何表示第n个数: 2*n-1
public static int f(int n ){
if(n==1){
return 1
}
return f(n-1)+ (2*n-1);
}
// 分析:
f(5)
n==5 f(4)+9
f(3)+7
f(2)+5
f(1)+3
1
= 1+3+5+7+9
练习4 : 前5个阶乘之和 例如 5!+4!+3!+…1!
规律: 前5个数的阶乘之和 前4个数的阶乘之和 + 当前这个数的阶乘
//前n个数的阶乘之和
public int sum(int n){
if(n==1){
return 1;
}
return sum(n-1) +f(n); // 其中f(n)表示当前这个数的阶乘
}
public int sum(int n ){
int sum=0;
for(int i=1;i<=n;i++){
sum+= f(n);
}
}
一、流程控制
1、定义
在一个Java程序中,各条语句的执行对程序的结果有直接影响,也就是说 各个语句的执行顺序对程序的结果有直接影响。
在程序中 ,可能出现不同的执行顺序,必须 自上而下顺序执行,或者 条件判断的顺序或者循环执行的顺序。
2、分类
顺序执行
条件分支
循环执行
3、顺序执行
//顺序执行, 从上而下执行
public static void main(String [] args){
System.out.println(1);
System.out.println(2);
System.out.println(3);
}
4、条件分支
1、if条件分支
语法:
if(条件){
语句块
}
其他代码
解释: 如果条件成立 ,则执行语句块 ,如果条件不成立,则不执行语句块
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ehADNwzp-1604826439023)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1600825779613.png)]
// 生成一个100以内的随机数 判断它是否为偶数
int n = (int)( Math.random()*100);
if(n%2 == 0){
System.out.println("这是数是偶数");
}
System.out.println("程序结束");
2、if…else条件分支
语法:
if(条件){
语句块1
}else{
语句块2
}
解释: 如果条件成立, 则执行语句块1 ,如果条件不能力 ,则执行语句块2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uafRmdYw-1604826439025)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1600825751304.png)]
int n = (int)(Math.random()*100);
// n<50 需要买西瓜 >50 需要买葡萄
if(n<50){
System.out.println("买了一个大西瓜");
}else{
System.out.println("买了一串葡萄");
}
System.out.println("n->"+n);
System.out.println(" 嗯,程序猿的女朋友很高兴,至少买了水果");
3、if…else if … else 多条件分支
语法:
if(条件1){
语句块1
}else if(条件2){
语句块2
}else if(条件3){
语句块3
}
...
else{
语句块4
}
解释: 从条件1开始依次判断,如果条件1 成立,则执行语句块1 ,其他条件不再执行,如果条件2成立,则执行语句块2,其他条件不再执行。。。 依次类推如果条件都不成立,则执行else语句块。 ,最终只执行其中某一个语句块,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Byun5nNp-1604826439026)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1600825721795.png)]
// 随机生成90以内的年龄 整数
int n = (int)(Math.random()*90);
if(n<18){
System.out.println("未成年");
}else if( n<30){
System.out.println("青年");
}else if( n<50){
System.out.println("中年");
}else if( n<70){
System.out.println("老年");
}else{
System.out.println("晚年");
}
System.out.println("n->"+n);
嵌套条件判断
语法: 以上3种格式 ,可以同时使用,在一个if语句再次嵌套if 语句
// 接收控制台输入 , 判断 输入的数 是否能被3整除
// 如果能被3整除,输出这个数除以3的结果,并判断结果能被7整除
// 如果不能被3整除,判断是否为偶数
Scanner sc = new Scanner(System.in);
// 接收控制台输入的整数
int n = sc.nextInt();
if(n%3 == 0 ){
System.out.println("这个数能被3整除");
// 在if语句中继续判断, 就是嵌套条件判断 ,需要往后缩进
int result= n/3;
if(result%7 == 0){
System.out.println("这个结果能被7整除");
}else{
System.out.println("这个结果不能被7整除");
}
}else{
System.out.println("这个数不能被3整除");
if(n%2 ==0){
System.out.println("这个数能2整除");
}else{
System.out.println("这个数不能被2整除");
}
}
注意 : 嵌套条件时 为了增强代码的可读性,将条件语句块的分支 往后缩进 ,{}作为一个整体
条件语句块中如果只有一个输出语句, 可以省略{}
5、选择条件判断
语法:
switch(表达式){
case 常量值1:
语句块1;
break; // 语句块接收的标记
case 常量值2:
语句块2;
break;
...
default:
语句块3;
break;
}
注意: switch 的表达式判断 只能等值比较 ,其中case的常量值 类型位: 整数型(byte short int long ),字符型,字符串型,枚举型
byte n = (byte)(Math.random()*7+1);
switch (n){
case 1 :
System.out.println("星期一");
break;
case 2:
System.out.println("星期二");
break;
case 3:
System.out.println("星期三");
break;
case 4:
System.out.println("星期四");
break;
case 5:
System.out.println("星期五");
break;
case 6 :
System.out.println("星期六");
break;
default :
System.out.println("星期天");
break;
}
case穿透问题
在switch中,如果case后面不写break,将会出现穿透现象,也就是说不会执行下一个case的判断条件直接往后运行,直到遇到break,或整体switch结束
练习
* 1、使用switch制作一个简单的计算器:
* 让用户输入计算数字1和运算符 及计算数字2,然后程序帮他计算出结果。
*
二、循环
1、循环定义
在Java程序中,重复的执行某一段代码 这个过程称之为循环, 为了避免出现死循环,循环分为四部分
1、初始条件
2、循环的判断条件 ,条件为true ,则进入循环体
3、循环体
4、迭代变量
while循环
语法:
初始条件
while(判断条件){
循环体
迭代部分 (为了改变循环的判断条件)
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p3wNTeiP-1604826439029)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1600842052900.png)]
计算1到100的累计之和 1+2+3+4…+100=?
初始值 n= 1
条件: 100以内的 数可以一直累加(一直循环)
迭代: n++
//计算 1到100的累加之和
int sum=0;
int n=1;
while(n<=100){
sum+=n;
n++;
}
System.out.println("n:"+n);
System.out.println("sum:"+sum);
do…while循环
语法:
初始值1
do{
循环体2
迭代部分3
}while(返回boolean类型的表达式4);
执行顺序: 123-》423 -》423-》423 .。。4 直到条件4为false 则退出循环。
先执行初始值 1,循环体2 ,迭代3
再判断条件4是否成立,成立,继续执行2,3
再判断条件4是否成立,成立,继续执行2,3
…
判断条件4是否成立,不成立,退出
int i=0;
do{
System.out.println("i--"+i);
i++;
}while(i<10);
System.out.println("i===="+i);
/**
* 第一遍: 输出 0 i=1
* 第二遍: 判断 1<10 成立 输出1 i=2
* 第三遍: 判断 2<10 成立 输出2 i=3
* .。。
* 第九遍: 判断8<10 成立 输出8 i=9
* 第十遍:判断 9<10 成立 输出9 i=10
* 第十一遍: 判断 10<10 不成立。退出
*
*
*/
问题: while循环与do while循环的区别?
1、while循环先判断条件是否成立,再执行循环体,do while循环 先执行一遍循环体再判断条件。
break : 退出循环
for循环
for循环的升级版就是 foreach循环
for循环通常在明确循环次数的情况下使用, 它也分为四部分
语法:
for(初始值1 ; 循环判断条件2 ; 迭代部分3 ){
循环体4
}
或者
初始值
for( ; 循环判断条件 ; ){
循环体
迭代部分
}
// 他们的区别是 初始值定义在for的外边,就可以在循环外边使用
循环执行流程:
1243-》243-》243-》243-》。。。》2 为false 循环退出
例如 :循环输出5遍hello world
// i 表示计数器,从1开始 到5结束
for(int i=1 ;i<=5 ; i++){
System.out.println("hello world");
}
循环的执行顺序:
第一遍: i=1 1<5 成立 输出“hello world ” i++ i=2
第二遍 :2<=5 成立 输出“hello world” i=3
第三遍 : 3<=5 成立 输出”hello world“ i=4
第四遍: 4<=5成立 输出”hello world“ i=5
第五遍: 5<=5 成立 输出”hello world“ i=6
第六遍: 6<=5 不成立,退出
i=6
使用for循环计算1-的阶乘
// 使用for循环 计算 10的阶乘
for(int i=1;i<=10;i++){
sum*=i;
}
for循环部分可以省略
// 死循环
for(;;){
System.out.println("hello");
}
for(;true;){
System.out.println("hello world");
}
// 迭代部分的代码 可以 放入循环体中
int i=0;
for( ; i<10; ){
System.out.println("第"+i+"次输出");
i++;
}
一、关键字 break、continue 、return的区别
1、break : 用于在switch。。case中放置语句块穿透,
用于跳出循环
// 从1-100 遇到7的倍数 break
for(int i=1;i<100;i++){
// i==7 跳出循环
if(i%7 == 0 ){
break;
}
System.out.println(i);
}
2、continue: 跳出本次循环,继续下一次循环
for(int i=0;i<100;i++){
if(i%7 == 0 ){
continue; // 跳出本次循环,继续下一次循环
}
System.out.println("i---"+i);
}
3、return : 返回 本次方法
用法1 : 如果return放在循环中, 会跳出循环,且不会只想循环外面的语句 ,
用法2: 作为方法的返回值
用法3 : 无论方法是否有返回值 ,可以在条件判断的位置 直接 return ,
return和break在循环语句块是,break只是结束循环语句块,对于循环外面的代码会执行,而return是结束当前所在方法的剩下的语句块。
public static void main(String[] args) {
for(int i = 1;i<100;i++) {
if (i == 50) {
return;
}
System.out.println("i----"+i);
}
System.out.println("程序结束");
}
public void method1(){
// return 还可以在条件判断的位置 直接返回方法
int n = (int)(Math.random()*10);
if(n%2 ==0){
return ; // 方法的后面就不再运行
}
System.out.println("方法的其他代码块");
}
结论:只要执行return,那么它 后面的代码都不执行。
public int add(){
return 0;
}
return作为方法返回值的关键字 ,
二、嵌套循环 以及案例
嵌套循环: 在一个循环语句中,还包含了另一个循环。例如 在一个for循环中还有一个for循环 ,
它的总的循环次数 = 外循环的次数* 内循环的次数
语法:
for(){ // 外层循环
for(){ // 内层循环
}
}
执行顺序: 外层循环 循环一次 ,内层循环循环完整的一遍
* * * * *
打印直角三角形
*
* *
* * *
* * * *
* * * * *
外循环控制打印几行, 内循环控制打印即可*
*
* * *
* * * * *
* * * * * * *
* * * * * * * * *
* * * * * * *
* * * * *
* * *
*
思路 : 考虑一行打多少个空格 多少个*
一共5 行 空格的个数(5-i) *的个数 (2 * i - 1)
i=1 4 1
i=2 3 3
i=3 2 5
i=4 1 7
i=5 0 9
System.out.println("打印正三角形");
for(int i=1;i<=5;i++){
// 先打印空格
for(int k=0;k<5-i;k++){
System.out.print(" ");
}
// 再打印*
for(int j=0;j<2*i-1;j++){
System.out.print("* ");
}
// 换行
System.out.println();
}
九九乘法表
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
....
1*9=9 2*9=18 ...... 9*9=81
三、数组的概念以及数组案例
1、容器的概念
用于存储数据的一块内存称为容器,生活中有很多容器,例如 水杯,衣柜,书包,有一定的空间可以存放“东西”
存放在容器中的数据 称为“元素”
2、为什么会存在数组呢?
假如现在要存储全班同学的成绩 , 全班40人,按照定义变量的思维,需要定义40个double类型的变量,每次从40个变量中找某一个变量,操作很麻烦, Java中可以定义一个数据类型存放40个人的成绩 , 这个类型就是数组类型。
数组定义: 它是相同数据类型的有序集合
3、 数组特点
- 数组的长度固定(数组的长度一旦声明,就不能改变)
- 数组中存储的元素数据类型必须相同
- 数组的元素 通过下标访问,且下标默认从0 开始
- 数组类型属于引用数据类型, 数组的元素类型 既可以是基本数据类型,也可以是引用数据类型。
4、创建数组
方式一:
数组存储的数据类型 [] 数组名 = new 数组存储的数据类型[长度];
详解:
数组存储的数据类型 :创建数组容器中可以存储什么数据类型 (基本数据类型 ,引用数据类型)
[] : 表示数组
数组名: 给数组起给名字,遵循标识符规则
new : 创建数组的关键 字
[长度] : 数组的长度 , 这个长度定义后不可改变
例如
int [] arr = new int[3];
new出来的空间在堆内存中,数组是引用数据类型 ,存在内存地址
内存解析: 在堆内存中开辟一段连续的3个长度的int类型的内存空间 ,并由arr变量指向这块内存的地址 (换句话说 arr输出的就是 这个内存的地址)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cP3PlQny-1604826439031)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1600935311755.png)]
方式二:
数据类型 [] 数组名 = new 数据类型[]{元素1,元素2,元素3...}
这里的数组长度根据元素的个数自动分配大小
int [] arr = new int[]{90,88,78,92};
或者
int arr [] = new int[]{90,88,78,92}
方式三:
数据类型 [] 数组名 = {元素1,元素2,元素3...};
注意: 这里的元素类型必须满足 数组的元素数据类型
char [] arr = {'a','b','c'};
或者
char arr [] = {'a','b','c'};
5、数组的访问
数组的访问通过索引访问
索引(下标): 每一个数组的元素都有一个编号,这个编号从0开始 , 这个编号称为数组的索引,通过数据名[索引] 访问到数组的原始
例如: 访问数组的第二个元素: 数组名[1]
数组的长度: 数组的长度 声明已固定 ,访问数组的长度 : 数组名.length
数组的最大索引= 数组的长度 -1
数组元素的赋值 :通过索引可以给元素赋值 数组名[索引] = 值
将数据 赋值给 指定索引的 元素
一、Java的面向对象的特征
1、封装(隐藏)
对类中的成员属性进行隐藏(私有化),对类中的成员方法公共。
2、继承
一个类A可以继承另一个类B,这里类A就是类B的子类,类A可以继承类比的属性和方法,也可以定义自己的属性和方法
3、多态
为了适应需求的多种变化,类可以呈现多种形态,是代码更加通用
1、封装
为了提高类的隐蔽性,对类实现的细节隐藏,提供外部访问的接口即可,提高代码的可扩展性
生活中的封装: 例如笔记本 的内部结构统一封装了,一般人使用笔记本时不需要了解笔记本的结构,而是直接开机关机使用。
对代码的封装包括两层意思:
1、对类的成员属性的封装 :
将属性私有化(private),提供对属性的访问给属性添加公用的getter和setter方法
2、对代码的封装:
为了提高代码的复用性,尽量使用方法加参数传递对代码进行封装,并使该方法公有(public)
public class People {
private String pname;
private int age;
private String sex;
// 提供 getter 和 setter
public String getPname(){
return pname;
}
public void setPname(String pname){
this.pname=pname;
}
public int getAge(){
return age;
}
public void setAge(int age){
// 对成员属性的隐蔽性 可以防止随意对属性更改
if(age>100 || age<0){
System.out.println("赋值的年龄不合法");
return;
}
this.age = age;
}
public String getSex(){
return sex;
}
public void setSex(String sex){
this.sex= sex;
}
// 通常为了方便给属性赋值,会提供有参构造
public People(String pname ,int age,String sex){
this.pname = pname;
this.age = age;
this.sex = sex;
}
public People(){
}
}
对于boolean类型的属性,需要使用isXxx返回属性的值。
封装的优点:
1、良好的封装可以减少类的耦合性(类与类的关联)
2、对类中封装的代码可以自由修改,而不会影响其他类
3、最大程度提高类中属性的隐蔽性 和对属性的控制
2、访问修饰符的权限
用于修饰类,属性,方法的关键字都称为访问修饰符
- 1、public :公共的
可被同一个项目的所有类方法(项目可见性)
- 2、protected :受保护
可以被自身的类访问
可以被同包下的其他类访问
对于不同包的,存在父子关系的子类可以访问
- 3、默认的
可以被自身类访问
可以被同包下的其他类访问
- 4、private: 私有的
只能被自身类访问
访问修饰符 | 同一个类 | 同一包不同类(子类或非子类) | 不同包的子类 | 不同包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
默认 | √ | √ | × | × |
private | √ | × | × | × |
3、static关键字
static表示“静态” ,它可以修饰 属性,方法,代码块 , 在一个类中除了可以定义成员属性、成员方法和构造器以外,还可以定义静态的部分(静态属性,静态方法,静态代码块)
static 修饰属性:称为 静态属性或类的属性
static修饰方法:称为静态方法或类的方法
static修饰的语句块: 称为静态代码块
static修饰的组件不需要通过对象访问,而是直接通过类名访问,在类一加载时会给static修饰的属性和方法分配内存区,这个内存分布在 静态内存区中。后续所有对象操作的是同一个内存区
案例一:
public class Student {
// 成员属性
String name;
// 静态属性 通常static写在public的后面
static int age=20;
// 静态代码块
static{
System.out.println("这是静态代码块,在类一加载时,就会被执行,且只执行一次");
}
//成员方法 : 既可以访问 成员属性,也可以访问静态属性
public void getInfo(){
System.out.println("姓名:"+name +" 年龄:" +age);
}
// 静态方法 : 只能访问静态属性,不能访问成员属性(非静态属性
// 这是为什么? 由于成员属性的存在需要依赖对象 ,
// 静态属性和静态方法在创建对象之前就必须初始化并分配内存
public static void getStaticInfo(){
// System.out.println("姓名:"+name); 成员属性不能被访问
System.out.println("静态属性:"+age);
}
public Student(){
System.out.println("无参构造器");
}
// 构造代码块
{
System.out.println("构造代码块");
}
public static void main(String[] args) {
System.out.println("访问静态属性:"+Student.age);
// 访问静态方法
Student.getStaticInfo();
}
// 类的组件执行顺序
// 类编译成.class文件被JVM的类加载器加载-》
// 从上往下初始化static修饰的组件
// (静态属性,静态代码块,静态方法,其中静态方法调用才执行,静态属性和静态代码块直接分配内存)-》
// --》构造代码块-》执行构造器 -》初始化成员属性,成员方法
Student stu1 = new Student();
stu1.name="张三";
// 静态属性可以通过类名访问,也可以通过对象名访问
stu1.age=21;
System.out.println(stu1);
Student stu2 = new Student();
stu2.name="王麻子";
stu2.age=22;
System.out.println(stu2);
System.out.println(stu1.name);
System.out.println(stu1.age); // 22
System.out.println(stu2.name); //王麻子
System.out.println(stu2.age); // 22
System.out.println(Student.age);// 22
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4N49U82K-1604826439033)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/11.png)]
案例二: 静态的变量 在同一个内存中
public class People {
double height;
static int score;
static{
score++; // 1
}
public void setScore(){
score++; //81 86
}
public static void setScore2(){
score++;
}
public static void main(String[] args) {
People p1 = new People();
p1.score=80;//静态属性
p1.setScore();
People.score=85;
p1.height= 1.75;
People p2 = new People();
p2.setScore(); //86
p2.height= 1.80;
System.out.println(p1.score); // 86
System.out.println(p1.height); // 1.75
System.out.println(p2.score);//86
System.out.println(p2.height);// 1.80
}
}
案例三: 构造代码块和静态代码块 的执行顺序
package com.j2008.statics;
/**
* ClassName: UserInfo
* Description:
* date: 2020/10/8 11:53
*
* @author wuyafeng
* @version 1.0 softeem.com
*/
public class UserInfo {
// 关于静态的组件 从上往下执行
// 静态属性 需要先初始化 ,需要new一个对象
static UserInfo u = new UserInfo(); // 先执行构造代码块 在执行构造器
static{
System.out.println("这是静态代码块,只执行一次");
}
public UserInfo(){
System.out.println("这是无参构造器");
}
{
System.out.println("构造代码块");
}
public static void main(String[] args) {
// 结果
UserInfo u = new UserInfo();
}
}
结果:
构造代码块
这是无参构造器
这是静态代码块,只执行一次
构造代码块
这是无参构造器
4、继承
4.1、概念
当多个类中都存在相同的属性和行为时,可以将这些共有的属性和行为定义到一个新的类中,让其他类服用这个新类的属性和行为,这种关系就是继承关系
当满足 XXX是 一个XXX的时候,也是继承关系,例如 苹果是一种水果,其中水果就是父类,苹果就是子类, 水果有多种水果,而苹果只是水果的一种,所以苹果继承水果
其中 被继承的类是 父类(超类,基类),继承父类的类就是子类(新类,派生类)
4.2、继承的语法
先定义父类
public class 父类{
}
再定义子类
public class 子类 extends 父类{
}
2.1 子类继承父类,子类拥有父类的哪些属性和方法?
可访问: 子类拥有父类的共有的属性和方法, 同包下面的属性和方法,不同包下的受保护的也可以访问
不可访问: 其中子类不能继承 父类的私有的属性和方法、不同包的默认属性和方法 ,不能继承父类的构造器
2.2 子类继承父类,子类如何访问父类的属性和方法
属性: 子类通过super 关键字访问父类的属性 ,子类通过this关键字访问自己的属性
方法:子类通过super挂件自方法父类的方法, 子类通过this关键字访问自己的方法
注意: 这里的this和super可以省略,省略后子类通过“就近原则”访问属性和方法(子类中存在就访问子类的,子类中不存在,就访问父类的。)
super.属性
super.方法(参数)
构造器:子类通过super([参数]) 调用父类的构造器, 子类通过this([参数])调用 自己类的其他构造器,其中 super(参数[]) 必须写在子类构造器的第一行
通过在子类构造器手动调用父类的有参构造器给父类的属性赋值
public class Employee {
String ename="王麻子";//员工姓名
double sal=5000 ; // 员工薪资
public Employee(String ename,double sal){
this.ename = ename;
this.sal = sal;
}
}
public class Manager extends Employee{
// 奖金
private double comm;
public Manager(String ename ,double sal ,double comm){
// 如何覆盖父类的无参构造器 ,手动调用父类的有参构造
super(ename ,sal); // 只能写在第一句
this.comm = comm;
}
}
注意: 子类构造器中默认调用父类的无参构造器
2.3 Java中只能是单继承,一个类只能有一个直接父类的父类,可实现多层次继承
子类 -》 父类 -》 父类的父类
pupil -> Student-》 People
创建子类对象时,优先创建父类对象,再创子类对象, 执行顺序 最上层父类的构造器 -》 父类构造器 -》子类构造器。
扩展问题:当一个类中存在static元素时,它们的执行顺序是如何?
顺序: 最上层父类的静态块 - 》 父类的静态块-》 子类的静态块- 》最上层 父类的构造块和构造方法
-》父类的构造块和构造方法- 》 子类的构造块和构造方法
public class People {
static{
System.out.println("People的静态语句块");
}
public People(){
System.out.println("People类的无参构造器");
}
{
System.out.println("People的构造语句块");
}
}
public class Student extends People{
static{
System.out.println("Student 的静态语句块");
}
public Student(){
// 默认调用父类的构造器
System.out.println("Student的无参构造器");
}
{
System.out.println("Student的构造语句块");
}
}
public class Pupil extends Student {
static{
System.out.println("Pupil的静态语句块");
}
public Pupil(){
// 调用它父类的无参构造器
System.out.println("Pupil类的无参构造器");
}
{
System.out.println("pupil的构造器语句块");
}
}
public static void main(String[] args) {
//创建Pupil对象
Pupil pupil = new Pupil();
}
结果:
People的静态语句块
Student 的静态语句块
Pupil的静态语句块
People的构造语句块
People类的无参构造器
Student的构造语句块
Student的无参构造器
pupil的构造器语句块
Pupil类的无参构造器
4.3、重写
子类可以继承父类的方法,但是当父类的方法不能满足子类的需要时,子类可以重写父类的方法
重写的必要条件:
1、两个方法名必须相同,且存在不同类中(父子关系的类)
2、子类重写的父类的方法,其方法的参数和返回值必须完全一样,方法的具体实现可不一样
3、访问修饰符必须大于或等于父类的修饰符
注意: 子类的对象 调用父类方法时,如果子类重写了父类的方法,则执行子类的方法,没有重写执行父类的方法。
面试题:
说一下方法的重写与重载的区别?
1、重写存在于父子关系中的不同类,重载存在同类中
2、重写必须满足 方法名相同,且参数相同,返回值相同 ,重载满足方法名相同,参数不同,与返回值无关。
3、重写的访问修饰符必须 子类大于等于父类, 重载没有要求
5.多态
多态:继Java面向对象中封装,继承之后的第三个特征
生活中的多态: 同一个行为,例如跑,人是两条腿跑,动物是四条腿或两条腿跑,飞的行为不同是事物飞的方式也不同,飞机飞,小鸟飞,无人机飞都不一样,同一种行为对于不同的事物呈现的不同形态就是多态的表现。
5.1、 定义
同一种行为,具有多个不同的表现形式
5.2、实现多态的前提
- 基于继承关系或基于实现关系的
- 子类或实现类必须对方法进行重写(没有重写的方法 不具有多态行为)
- 父类的引用指向子类对象( 接口的引用指向实现类的对象)
5.3、多态的对象转型
1、子类对象转父类对象时,称为向上转型是默认转换,自动转型
Cat cat = new Cat();
// 猫类 可以是一只动物 an4的本质还是 猫对象
Animal an4 = cat; // 子类对象转成父类对象 ,是自动转型
cat.eat();
an4.eat();
2、父类的引用转成子类对象,称为向下转型,向下转型需要强转 , 为了避免转换错误,需要先判断数据类型是否匹配
// 创建一个动物类, 将动物类转成子类引用
Animal an5 = new Cat();
// an5.catchMouse(); // 动物类型对象不能访问 它子类特有的方法
if(an5 instanceof Cat) {
Cat cat2 = (Cat) an5;
cat2.eat();
cat2.catchMouse();
}
if(an5 instanceof Dog) {
Dog dog2 = (Dog)an5;
}else{
System.out.println("不能转成dog");
}
instanceof : 判断该对象是否属于 这个类型
为什么需要做类型换行呢? 有时候我们需要调用子类特用的方法时必须用子类的引用。所有多态对象下父类应用需要强转。
为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:
变量名 instanceof 数据类型如果变量属于该数据类型,返回true。
如果变量不属于该数据类型,返回false
5.4、在多态环境型,方法和属性的调用规则
属性: 当子类和父类中存在相同属性时 ,以 对象的左边引用类型为依据,所谓“看左边”
方法: 以当前new出来的对象为依据,如果方法重写了,就调用子类方法,如果方法没有重写 调用父类方法
静态方法: 无论方法是否有重写, 都已对象左边的引用类型为依据。
public class Student extends People {
int age =20;
@Override
public void showAge() {
this.age++;
System.out.println("年龄:"+this.age+" ----"+super.age);
}
// 重写的静态方法
public static void method1(){
System.out.println("method1------------ 重写后的 方法");
}
}
public static void main(String[] args) {
People p1 = new People();
System.out.println(p1.age);// 18
p1.showAge(); // 对象的本质people 调用people
People p2 = new Student();
System.out.println( p2.age);// 18
p2.showAge(); // 本质student ,且重写了 调Student
p2.sleep(); // 本质student ,没重写,调用people
Student p3 = (Student)p2;
System.out.println( p3.age); // 20
p3.showAge(); // 本质student ,且重写了 调Student
People p4 = p3;
System.out.println( p4.age); // 18 看左边
p4.showAge(); // 本质student ,且重写了 调Student
}
结论:
18
年龄:18
18
年龄:21 ----18
睡觉方法。。。。。
21
年龄:22 ----18
18
年龄:23 ----18
public static void main(String[] args) {
People p1 = new People();
p1.method1();
p1.method2();
People p2 = new Student();
p2.method1();
p2.method2();
Student p3 = (Student)p2;
p3.method1();
p3.method2();
}
结论:
method1------------
method2---------
method1------------
method2---------
method1------------ 重写后的 方法
method2---------
一、面向对象语言编程
Java是一门面向对象的编程语言(OOP),万物皆对象
面向对象初步认识,在大多数编程语言中根据解决问题的思维方式不同分为两种编程语言
1、面向过程编程
2、面向对象编程
面向过程 | 面向对象 | |
---|---|---|
区别 | 事物比较简单,可以使用线性思维解决,具体每一个实现步骤清晰可见 | 事物比较复杂使用简单的线性思维无法解决,存在对象与对象之间的引用 |
共同点 | 1、都是为了解决实际问题的一种方式 2、当解决复杂问题时,面向对象是从宏观角度把握问题的整体,面向过程是从微观角度实现具体细节,两者之间相辅相成 | |
以 每天下楼吃饭为例:
面向过程: 面向对象
1、下楼找餐厅 1、下楼找餐厅
2、看菜品,并熟悉掌握你吃的每一道菜的 2、我要开吃了 (不关注具体菜的细节)
来源,制作流程,烹饪手法等具体细节 3、吃完了
3、吃这道菜
二、Java的面向对象编程
Java作为面向对象的语言,关于面向对象语言的核心概念
1、类和对象
类: 一类事物的抽象的模板,在现实世界中 类就是任意一类事物 ,在程序中类就是一个描述这类事物的类文件。
对象: 在这一类事物中,具体的某一个个体就是对象 ,在程序中对象就是new出来的有内存空间
2、类和对象的关系
类和对象的关系: 类是抽象的而对象是具体的, 对象是由类创建的实例(new出来的)
类的组成(人类):
类名: 给某一类事物取个名字: People
静态的特征称为属性: 姓名,年龄,身高,体重 (定义变量的语法)
动态的行为称为方法: 吃饭,睡觉,打豆豆 (方法的定义依然满足之前所学)
类的实现:
在一个类文件(People)中,定义属性和方法
对象的实现
通过类名创建这个类的对象。
注意 类名不能直接访问 它里面的属性和方法的,必须由类的对象访问
package com.j2008.init;
/**
* ClassName: People
* Description:
* date: 2020/10/7 11:06
* 创建一个People类 ,并定义这个类的属性(静态的特征)
* 和 这个类的方法 (动态的行为)
*
* @author wuyafeng
* @version 1.0 softeem.com
*/
public class People {
// 定义姓名属性 数据类型 和属性名 = [初始值]
String name="张三";
// 定义性别属性
String sex="男";
// 定义身高属性
double height =1.75;
// 定义体重属性
double weight = 140;
/**
* 定义吃饭的行为(方法)
*/
public void eat(){
System.out.println("正在吃饭");
}
/**
* 定义睡觉方法
*/
public void sleep(){
System.out.println("正在睡觉");
}
/**
* 定义打豆豆方法
*/
public void playGame(){
System.out.println("正在打豆豆");
}
// 计算两个数相加
public int add(int a, int b){
return a+b;
}
}
public static void main(String[] args) {
// 不同通过People直接访问它 需要创建类的实例,也就是对象
// 创建对象的过程称为类的实例化
// 语法: 类名 对象名 = new 类名() ;
People people = new People();
// 这时候才可以通过对象名 访问这个对象具有的属性 和 方法
// 对象名.属性名
// 对象名.方法名([实参])
System.out.println("这个对象的属性name :"+ people.name);
System.out.println("这个对象的属性 sex :" + people.sex);
System.out.println("这个对象的属性 weight :"+ people.weight);
System.out.println("这个对象的属性height:"+ people.height);
// 调用对象的访问
people.eat();
people.sleep();
people.playGame();
int result = people.add(2,4);
System.out.println("这个对象还可以计算 结果:"+result);
}
在类中定义的属性,称为“成员属性” ,在类中定义的方法称为“成员方法”
3、面向对象的特征
面向对象的三大特征: 封装、继承、多态
封装: 将类中成员属性 私有化,并提供公有的访问属性的方法。 为了最大程度保护类中属性的隐蔽性(不被其他对象改变。)
继承: 用于定义类与类的关系的方式 ,一个类可以继承一个类。
多态: 在继承关系中,一个类的对象可能呈现不同的状态。
4、构造器(Construct):
定义: 在创建对象时被自动调用的特殊方法,也称为构造方法。 在一个类中除了包含属性,方法以外,还可以包含 构造器(构造方法)
每一个类都自带一个无参构造器,也可以在这个类中定义多个构造器,多个构造器之间称为“构造器重载”
语法:
访问修饰符 类名([参数列表]){
}
例如
public class Student{
//无参构造器
public Student (){
System.out.println("这是一个无参构造器");
}
}
构造器的作用:
1、用于创建对象自动调用 ,并可以给对象的属性赋初始值
public class Student{
String name;//对象的属性
//有参构造器
public Student (String name1){
name=name1;
}
//注意一个类中如果存在有参构造器,那么它的无参构造器被覆盖。
}
创建对象:
Student stu = new Student("张三");
//这里会自动调用有参构造器 并将“张三”赋值给name1,由于自动执行以上构造器,将name1的值赋值给name,这个name就是对象的属性
System.out.print(stu.name);//张三
类与类的关联关系 :
如果在一个类中引用另一个类,那么这两个类属于关联关系,
例如一个小明同学养了一条狗,如何通过面向对象的方式定义小明同学用于狗的关系
思路: 定义一个People类,其中name属性 ,
定义一个Dog类 包含dogName ,dogColor
将People类与Dog类关联关系 ,在People类中 创建Dog类的引用
public class People {
String name;
// 在People类中 建立 People对象与Dog对象的关系
Dog dog;
public People(String name){
this.name = name; // 将形参name 赋值给 当前对象的成员属性name
}
}
public class Dog {
// 由于以下属性 属于狗的特征,所以必须放在Dog类中,而不能放在People类
String dogName;
String dogColor;
/**
* 创建Dog对象时 初始化狗的基本属性
* @param dogName
* @param dogColor
*/
public Dog(String dogName ,String dogColor){
this.dogName = dogName;
this.dogColor = dogColor;
}
}
public static void main(String[] args) {
// 先创建一个小明同学
People people = new People("小明同学");
System.out.println("people.dog:"+people.dog);
// 再创建一个 Dog对象
Dog dog = new Dog("拉布拉多","灰白");
System.out.println("dog:"+dog);
、 //设置dog和people对象的关系
people.dog = dog;
System.out.println("people.dog:" +dog);
}
people.dog:null
dog:com.j2008.construct.Dog@4554617c
people.dog:com.j2008.construct.Dog@4554617c![]()
一、抽象类
1、定义
在已有类的基础上,由于特殊情况将该类设置为抽象的,这个类就是抽象类
语法:
public abstract class 类{
// 类的元素
}
什么情况下需要定义抽象类?
1、当这个类不需要创建具体的实例时,可将类定义为抽象的
2、当这个类中存在没有实现的方式时(没有方法体的方法),可以将这个类定义抽象的
2、抽象类的特点
2.1 抽象类 不能实例化(不能new) ,通常抽象被当作父类使用
2.2 抽象类中 可以有抽象方法( 没有方法体的方法) 也可以有普通方法
2.3 抽象类被当作父类时,它的子类必须重写父类的抽象方法
public abstract class Fruit {
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
// 水果的甜度 由于不知道是什么水果,所以说过的甜度未知,可以定义为抽象方法
// 抽象方法
public abstract void getSweet();
}
public class Apple extends Fruit {
// 子类重写(实现)父类的抽象方法
@Override
public void getSweet() {
System.out.println("这个水果有点甜");
}
}
public class Lemon extends Fruit {
@Override
public void getSweet() {
System.out.println("这个水果有点酸,想想都觉得酸");
}
}
public static void main(String[] args) {
// 抽象类不能实例化
// Fruit fruit = new Fruit();
// 创建子类对象
Apple apple = new Apple();
apple.setColor("红色");
apple.getSweet();
System.out.println(apple.getColor());
Lemon lemon = new Lemon();
lemon.setColor("黄色");
lemon.getSweet();
System.out.println(lemon.getColor());
}
}
面试题:
抽象方法和非抽象方法的区别
回答: 抽象方法没有方法体,需要使用abstract修饰, 只能写在抽象类中
非抽象方法就是普通方法(成员方法或静态方法) ,可以写在抽象类中或普通类中。
一、包装流
定义: 在原始字节流或字符流的基础性,为了提高读写效率进行再次处理的流, 称为包装流/处理流
1、缓存字节流 BufferedInputStream 、BufferedOutputStream
由于原始流在文件读写时 效率比较低(操作文件本身占用资源较多),可以通过创建缓冲区的方式提高读写效率, 将读取/写出的数据线放入缓冲区,到达一定数量后再次冲缓冲区读取/写出
mark(readLimit) 与 reset()用法
其中reset不能单独使用,必须mark(readLimit) ,readLimit表示标记后最多读取的上限,但是这里标记后读取的内容与BufferedInputStream的缓冲大小有关,比由上限决定,也就是说读取的内容超出上限可以继续重置到mark的位置。
public static void main(String[] args) throws IOException {
//创建缓冲流
InputStream is = new FileInputStream("d:/myfile.txt");
BufferedInputStream bis = new BufferedInputStream(is);
//是否支持mark 或 reset
System.out.println(bis.markSupported());
System.out.println((char)bis.read());//97
//重置
bis.mark(3); // pos标记往后退三个 最多可以读取字节上限
System.out.println("再次读取:"+(char)bis.read());
System.out.println("再次读取:"+(char)bis.read());
System.out.println("再次读取:"+(char)bis.read());
System.out.println("再次读取:"+(char)bis.read());
bis.reset(); // 这里 重置后 退回到3个以前的位置
// 重置后输出
int n =0;
while( (n = bis.read()) !=-1){
System.out.println("重置后;"+(char)n);
}
//关闭流
bis.close();
is.close();
}
2、缓存字符流 (BufferedReader 、BufferedWriter)
public static void main(String[] args) throws IOException {
// 缓冲字符流 可以一行一行读取 、写出
BufferedReader br = new BufferedReader(new FileReader("d:/小众网站.txt"));
//读一行
// System.out.println(br.readLine());
// System.out.println(br.readLine());
// System.out.println(br.readLine());
String s = null; //读的数据为空 则不需要读
while( (s = br.readLine()) !=null){
System.out.println(s);
}
br.close();
//缓冲写出流
FileOutputStream pw = new FileOutputStream("d:/abcd.txt");
//由于字节流不能直接放入 字符缓冲区,需要将它转成字符流 使用转换流并可以指定编码格式
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(pw));
bw.newLine();// 开启新一行(换行)
bw.write("这是测试转换流的方式");
bw.close();
}
3、打印流(输出流) PrintWriter 、PrintStream
public static void main(String[] args) throws FileNotFoundException {
// 打印流 ,提供一些打印输出方法
PrintWriter pw = new PrintWriter("d:/abcd.txt ");
pw.print(100);
pw.println('a');//换行打印
pw.print("hello");
pw.close();
//System.out 字节打印流 PrintStream
4、数据字节流DataInputStream、DataOutputStream
它们用于读入写出Java基本数据类型的数据到文件或其他设备端,它们也属于包装流
DataOutputStream 常用方法
- writerByte(byte):写一个字节到设备或文件
- writerChar(char):写一个字符到设备或文件
- writerInt(int):写一个4个字节的int到设备或文件
- writer(boolean):写一个boolean类型到设备或文件
- writerDouble(double):写一个double类型到设备或文件
- writerFloat(float):写一个float类型到设备或文件
- writerLong(long):写一个long类型到设备或文件
- writerShort(short):写一个short类型到设备或文件
- writerUTF(String):写一个字符串类型到设备或文件
DataInputStream: 读指定文件的数据,可以读数据类型
- int readInt() :读一个int类型
- short readShort():读一个short类型
- readByte():读一个字节类型
- read():读一个字节类型
- readDouble(): 读一个double类型
- readFloat():读一个float类型
- readChar():读一个字符类型
- readBoolean():读一个boolean类型
- readLong() :读一个long类型
public static void main(String[] args) throws IOException {
//创建数据写出流
DataOutputStream dos = new DataOutputStream(
new FileOutputStream("d:/data.txt"));
//写一个int类型 依次写出4个字节
dos.writeInt(100);
dos.writeBoolean(true);
//关闭
dos.close();
//读取文件 创建数据读入流 ,需要按写的顺序读进来
DataInputStream dis = new DataInputStream(
new FileInputStream("d:/data.txt"));
//读一个int类型 (依次读4个字节)
int num = dis.readInt();
System.out.println("读取的数据:"+ num);
System.out.println("读的数据:"+dis.readBoolean());
dis.close();
}
5、转换流
转换流是将字节流转成字符流的桥梁, 也可以在转换时指定编码格式。 InputStreamReader 和 OutputStreamWriter
public static void main(String[] args) throws IOException {
// 字节流转成字符流
InputStream is = new FileInputStream("d://小众网站.txt");
InputStreamReader isr = new InputStreamReader(is);
//缓冲流 读取数据
BufferedReader br = new BufferedReader(isr);
//读一行
String str =null;
while( (str= br.readLine()) !=null){
System.out.println(str);
}
//关闭流
br.close();
isr.close();
is.close();
}
public static void main(String[] args) throws IOException {
// 创建 字节转成字符的 写出流 FileOutputStream os =
FileOutputStream fos = new FileOutputStream("d://data.txt");
//指定编码 GBK 格式一个汉字占2个字节 UTF-8 格式一个汉字占3个字节
OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
//缓冲形式的
BufferedWriter bw = new BufferedWriter(osw);
bw.write("你好");
bw.newLine();
bw.write("我不好");
bw.close();
}
6、随机字节流
RandomAccessFile 是随机字节流,它是一个可读可写的流 ,在文件操作时指定该对象的模式(model)后,可以读数据或写数据
实现 DataInputStream和DataOutputStream类
构造器:
RandomAccessFile rm = new RandomAccessFile(File ,mode);
RandomAccessFile rm = new RandomAccessFile(String ,mode);
mode表示对象的模式
r: 表示该对象只能读 不能写
rw/rws/rwd :表示该 对象是可读可写的;
public static void main(String[] args) throws IOException {
//创建可读 的流
RandomAccessFile reader = new RandomAccessFile("d://data.txt","r");
//创建可读可写的 的流
RandomAccessFile writer = new RandomAccessFile("d://data-1.txt","rw");
// 读和写的过程和之前一样
byte [] b= new byte[10];
int len=0;
while( (len = reader.read(b)) !=-1){
writer.write(b , 0 , len);
}
System.out.println("复制成功");
//关闭流
writer.close();
reader.close();
}
skipByte 和 seek的区别
// 跳字节读取
RandomAccessFile raf = new RandomAccessFile("d:/data.txt","rw");
// 跳过2个字节
raf.skipBytes(2);
System.out.println((char)raf.readByte()); //3
System.out.println("当前偏移量:"+raf.getFilePointer());//3
// 又丢弃1个字节 从当前位置 往后偏移1位
raf.skipBytes(1);
System.out.println("修改后的偏移量"+raf.getFilePointer());//4
System.out.println("偏移后的读取数据:"+(char)raf.readByte()); //5
raf.close();
// seek用法
RandomAccessFile raf2 = new RandomAccessFile("d:/data.txt","rw");
// 设置当前读取的位置 ,从0开始计算 ,指定n ,就从n的下一个字节 读取
raf2.seek(2);
System.out.println("seek后的数据:"+(char)raf2.readByte());//3
raf2.seek(1); // 又从0开始 设置偏移量为1
System.out.println("修改后的偏移量"+raf.getFilePointer());//1
System.out.println("seek后的数据:"+(char)raf2.readByte())//2
raf2.close();
7、对象序列化流
对象流也称为序列化流,用于存储对象和读取对象的字节流,也是属于包装流
序列化和反序列化
将内存中的对象(Object,集合类等)保存到磁盘、网络介质、其他设置的过程,并在合适的时间能获取磁盘文件/网络的数据 ,这个过程就是对象的序列化和反序列化。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KZfuARfv-1604826439035)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/44.png)]
为什么需要序列化和反序列化呢?
在之前文件中存储的文本信息,这样不便于对数据的分类和操作,如果可以做到直接对对象的读和写这样可大大提高编程效率,并最大程度保证对象的完整性。
Java-IO中实现对象序列化的两种方式:
-
实现Serializable接口
-
实现Externalizable接口
Serializable接口
对象需要实现该接口,但是它没有任何需要实现的方法,只有一个用于标记该类可序列化的唯一标识。 任何类需要序列化都必须标记该变量
public class User implements Serializable {
// 对于能区分其他类的唯一表示
private static final long serialVersionUID = 1L;
private int uid;
private String name;
private String password;
// 有一部分属性不能序列化
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"uid=" + uid +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
//创建序列化的对象流 从内存到文件
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("d:/user.txt"));
User user= new User();
user.setUid(1001);
user.setName("admin");
user.setPassword("123456");
//序列化对象
oos.writeObject(user);
//关闭流
oos.close();
// 反序列化: 将文件中的数据 再读入到内存中 ,需要一个读的流 ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d://user.txt"));
// 反序列化尽量只读一次 (也可以读多次, 如何写出就如何读入)
Object obj = ois.readObject();
if(obj instanceof User){
User u = (User)obj;
System.out.println("反序列化的结果:"+u);
}
//关闭流
ois.close();
问题: 能否自定义序列化的属性 ,这里可以采用方式二,实现Externalizable,并重写两个方法 接口继承而来,在其基础上新增了两个未实现方法:readExternal(ObjectInputStream)和 writeExternal(ObjectOutputStreawm) ,自定义需要序列化的属性
public interface Externalizable extends java.io.Serializable
Externalizable接口
public class Student implements Externalizable {
private int id;
private String name;
private String sex;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
// 自定义可序列化的属性
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(this.id);
out.writeUTF(this.name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.id = in.readInt();
this.name = in.readUTF();
}
public Student(int id, String name, String sex) {
this.id = id;
this.name = name;
this.sex = sex;
}
public Student( ) {
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建序列化类
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("d:/stu.txt"));
//创建学生
List<Student> list = new ArrayList<>();
list.add(new Student(1001,"张飞","男"));
list.add(new Student(1002,"刘备","男"));
list.add(new Student(1003,"小乔","女"));
// 将集合序列化
oos.writeObject(list);
//关闭
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("d:/stu.txt"));
//读
Object obj = ois.readObject();
if(obj instanceof List){
List<Student> list2 = (List<Student>)obj;
for(Student s : list2){
System.out.println(s);
}
}
//关闭流
ois.close();
}
问题: 哪些属性不能实现序列化
1、类中的static修饰的属性不能序列化
2、类中属性被transient修饰的不能序列化 例如 transient private Integer age = null;
3、实现Externalizable接口的类的属性不能全部序列化,必须手动写可序列化的属性。
一、StringBuffer和StringBuilder类
1、StringBuffer 类
是一个字符串缓冲区的类,线程安全运行效率低,用户存储可变字符串
构造器:
StringBuffer sb = new StringBuffer(); // 创建空字符串的容器
StringBuffer sb = new StringBuffer(String);// 将字符串使用容器存储
StringBuffer sb = new StringBuufer(int);//声明指定容量的容器
常用方法:
1.1、append():追加字符串
1.2、delete(int start,int end):删除指定位置的字符
1.3、insert(int start ,String):插入到指定位置
1.4、reverse():反转字符
1.5、capacity():获取初始容量
1.6、ensureCapacity(int):设置最低所需容量
2、StringBuilder类
也是字符串缓冲区的类,它是线程不安全,且运行效率高的可变字符串缓冲类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uoSXUI2k-1604826439037)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/22.png)]
其StringBuilder的方法与StringBuffer几乎一样
3、面试题
1、StringBuffer、StringBuilder和String的区别
a、在运行速度上 : StringBuilder > StringBuffer > String
原因: String是字符串常量,而StringBuilder和StringBuffer是字符串变量,当需要改变字符串内容时,Stirng重新创建变量并赋值, 而StringBuilder和StringBuffer可直接改原有的值,所有效率高,
b、在线程安全上: StringBuffer > StringBuilder > String
原因: StringBuffer是线程安全的,而StringBuilder线程不安全,在StringBuffer上的很多方法增加同步关键字(synchronized),导致在多个线程运行时,保持数据的完整性和一致性,而StringBuilder的方法并没有同步 ,如果在多线程环境下为了确保数据安全,建议使用StringBuffer ,如果在单线程环境下,提高效率使用StringBuilder。
二、对象的克隆
1、为什么需要克隆?
对于基本数据类型,可以将值直接复制给另一个变量,这里两个变量相互独立,而引用数据类型(自定义类) 对于引用类型的赋值并没有产生新的个体,而是将两个变量的类型指向同一个对象。 (本质只有一个对象),如果想要赋值的对象与原始对象独立,则需要进行“对象克隆”
2、如何克隆
我们知道任意一个类都继承自Object类,其中Object类提供一个clone方法 用于克隆对象。
实现步骤:
a、 实现 接口 Cloneable
b、重写 clone方法(由于该方法是Object的 protectect修饰 不能直接访问)
3、浅克隆和深克隆
3.1、浅克隆
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
Java的对象克隆默认是浅克隆,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-34teUGRj-1604826439038)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E6%B5%85%E5%85%8B%E9%9A%86.png)]
3.2、深克隆
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yhdpxj4D-1604826439041)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E6%B7%B1%E5%85%8B%E9%9A%86%20-1603252064551.png)]
实现深克隆的方式:
1、需要将克隆对象的引用数据类型 也实现克隆
public class Student implements Cloneable {
private int id;
private String sname;
private int age;
//收货地址
private Address address;
//实现深克隆
@Override
protected Object clone() throws CloneNotSupportedException {
// return super.clone();
Student stu = (Student)super.clone();
// 获取学生的address
Address address = (Address) stu.getAddress().clone();
// 将address对象放入 新克隆的stu中
stu.setAddress(address);
return stu;
}
}
public class Address implements Cloneable{
//联系人
private String contectName;
//联系电话
private String contectPhone;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
测试:
public static void main(String[] args) throws CloneNotSupportedException {
// 创建学生对象
Student stu1 = new Student(1001,"马创",22);
Address address = new Address("马创的女朋友","18888888888");
// 将收货地址 与该学生对象关联
stu1.setAddress(address);
//克隆一个对象
Student stu2 = (Student)stu1.clone();
System.out.println(stu1.getId()+"---"+stu1.getSname());
System.out.println(stu2.getId()+"---"+stu2.getSname());
// 问题:是否会克隆新的address对象 还是说address的内存地址相同
System.out.println(stu1.getAddress());
System.out.println(stu2.getAddress());
// 对于克隆对象的引用数据类型,它默认不会创建引用数据类型的 新对象
// 这种方式称为“浅克隆”
// Java也可以实现深克隆
System.out.println(stu1.getAddress().getContectName());
System.out.println(stu2.getAddress().getContectName());
}
结果:
1001—马创
1001—马创
com.j2008.clones.Address@4554617c
com.j2008.clones.Address@74a14482
马创的女朋友
马创的女朋友
三、枚举类(enum)
1、枚举类型的诞生
在JDK5以前,定义常量需要使用public fiinal static… 单独定义,如果有一组常量,需要定义一组final修饰的类型。这样会很繁琐,JDK5以后推出枚举类型。 可以将一组常量定义一个自定义类,使用是通过该类型直接方法。
2、 枚举类型的语法
public enum 枚举类型名称{
值1,值2,值3...
}
访问时: 枚举类型名称.值1
用法1:
public enum Color {
RED(),YELLOW(),BLUE();
}
public static boolean isRedColor(Color color){
if(color.equals(Color.RED)){
return true;
}
return false;
}
public static void main(String[] args) {
System.out.println(isRedColor(Color.BLUE));
System.out.println(isRedColor(Color.RED));
}
用法2: 定义枚举类的属性 并赋值
public enum PROVINCES {
//枚举的值 在初始化枚举值时,通过构造器给它的属性赋值
HB("湖北",0),BJ("北京",1),HN("湖南",2),FJ("福建",3);
//枚举的属性
private String provinceName;
private int index;
//枚举的构造器
private PROVINCES(String provinceName,int index){
this.provinceName= provinceName;
this.index = index;
}
public String getProvinceName() {
return provinceName;
}
public void setProvinceName(String provinceName) {
this.provinceName = provinceName;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
//输出某一枚举的值 name和 index
System.out.println(PROVINCES.BJ.getProvinceName());
System.out.println(PROVINCES.BJ.getIndex());
//遍历所有的枚举的值
PROVINCES [] provs = PROVINCES.values();
for(PROVINCES pro : provs){
System.out.println(pro.getIndex()+"---"+pro.getProvinceName());
}
四、Math类
java.lang.Math类用于数学计算的工具类 ,它提供都是静态方法 ,不需要构造Math对象
常用方法:
Math.random():获取随机数
Math.abs() 获取绝对值
Math.ceil(): 向上取整
Math.floor() :向下取整
Math.rint():取接近它的整数 ,如果两个同样接近,往偶数靠近
Math.max(int,int):返回两个数的最大值
Math.min(int,int):返回两个数的最小值
Math.round():四舍五入整数
Math .sqrt():对一个数开平方
Math.pow(double,double),对一个数的几次幂
public static void main(String[] args) {
System.out.println(Math.PI);
System.out.println(Math.E);
System.out.println(" 一个数的绝对值:"+Math.abs(-100));//100
System.out.println(" 向上取整:"+Math.ceil(13.5)); // 14
System.out.println(" 向上取整(比它大的最近数):"+Math.ceil(-13.5));// -13
System.out.println("向下取整:"+ Math.floor(13.5));// 13
System.out.println("向下取整"+Math.floor(-20.2));// -21
//四舍五入
System.out.println(Math.round(23.4));// 23
System.out.println(Math.round(-23.5));//-23
System.out.println(Math.round(-24.5));//-24
System.out.println(Math.rint(23.4));// 23
System.out.println(Math.rint(23.5));//24 如果两个一样接近,取接近偶数
System.out.println(Math.rint(22.5));//22
System.out.println(Math.rint(0.6));//0
}
五、大数据类型BigDecimal
1、BigDecimal类
两个double类型的计算可能导致精度不准确,这里使用
java.math.*里面提供了BigDecimal类(提供高精度计算的方法)
public static void main(String[] args) {
double a= 1.200000;
double b= 1.35433;
double c = a+b;
System.out.println(c);
System.out.println(0.05+0.01);
System.out.println(1.0-0.42); // 会出现精度问题 计算不准确
// 使用BigDecimal ,先将类型转成字符串 (为了避免精度问题)
BigDecimal num1 = new BigDecimal("0.051");
BigDecimal num2 = new BigDecimal("0.012");
// 两个数相加
BigDecimal sum = num1.add(num2 ) ;
// 设置保留2位整数 四舍五入
sum =sum.setScale(2,BigDecimal.ROUND_HALF_UP);
System.out.println(sum);
// 减法
sum = num1.subtract(num2);
System.out.println(sum);
// 乘法
sum = num1.multiply(num2);
System.out.println(sum);
// 除法
sum = num1.divide(num2,2,BigDecimal.ROUND_HALF_UP);
System.out.println(sum);
}
2、NumberFormat类
java.text.NumberFormat类 :用于将数值格式转成指定格式并输出字符串形式的类 。
DecimalFormat类: 属于NumberFormat的子类。
// NumberFormat类是对数值类型的格式化类,其中 DecimalFormat是继承NumberFormat
// 获取数值的货币表现形式
NumberFormat nf = NumberFormat.getCurrencyInstance();
String s1 = nf.format(23424.2);
System.out.println(s1);
//获取数值的百分比
nf = NumberFormat.getPercentInstance();
s1= nf.format(0.654);
System.out.println(s1);
//根据指定的格式匹配百分比
nf = new DecimalFormat("##.##%");
s1=nf.format(0.654);
System.out.println(s1);
// 根据指定的模式匹配千分比
nf = new DecimalFormat("##.##\u2030");
s1 = nf.format(0.6543);
System.out.println(s1);
// 根据指定的模式 转成科学计数法
nf = new DecimalFormat("#.###E0");
s1 = nf.format(198200);
System.out.println(s1);
// 根据指定的模式 将字符串转成 指定格式数值型
String s2 ="25.3%";
nf = new DecimalFormat("##.##%");
Number dd = nf.parse(s2);
double num = (double)dd;
System.out.println("这个字符串转成double:"+num);
一、内部类(inner class)
1、定义
在一个类中,定义另一个类的代码结构,通常定义在类内部的类称为 “内部类” ,外面的类称为“外部类” , 在逻辑关系上 内部类与外部类是从属关系,比如 一个People类 存在收货地址类(收货人,收货联系方式)
2、分类
2.1、 普通内部类(inner class),一个类A中定义另一个类B,其中类B就是类A的内部类,也是类A的一部分
public class People {
private String pname="张三";
public void sayHello(){
System.out.println("Let us say Hello");
// 知识点1 :外部类的方法中,可以使用内部类的属性、方法
Address address = new Address();
address.addressName ="湖北武汉";
address.contentName="张某某";
address.showAddressInfo();
}
/**
* 定义普通内部类 收货地址
*/
class Address{
private String addressName;// 收货地址
private String contentName;// 联系人
public void showAddressInfo(){
System.out.println("联系人:"+contentName + "--收货地址:"+addressName);
// 内部类的方法 可以直接访问外部类的属性 (由于通常情况属性的访问必须通过对象才可以使用,而)
System.out.println("访问外部类的属性:"+pname);
}
}
}
注意两点
- 外部类的方法中,可以直接访问内部类的所有成员(包括私有)
- 内部类的方法中,也可以直接方法外部类的所有成员,当外部和内部的成员名相同时,就近原则访问成员,或者引入外部类的对象访问
2.2、 静态内部类(static inner class): 在普通内部类基础上,增加“static”关键字,与静态方法相似,满足静态的要求
public class People{
/**
* 2、定义静态内部类
* 卡信息
*/
static class Card{
private static String cardNo="4200018888000022";
private String cardName="身份证";
// 定义静态方法
public static void showCard(){
System.out.println("身份证号:"+ cardNo);
}
// 定义非静态方法
public void showCard2(){
System.out.println("cardName:"+cardName + "----"+ cardNo);
}
}
// 外部类的方法
public void method2(){
Card card = new Card();
// 对于静态方法可以直接类名.方法名
// 对于非静态方法,需要创建Card类的对象访问
card.showCard2();
}
}
使用:
// 2 创建静态内部类的对象
People.Card.showCard();
// 创建静态内部类的对象
People.Card card = new People.Card();
card.showCard2();
2.3、方法内部类: 在一个方法中定义的类,其中这个类只属于该方法,也只能在该方法中使用
/**
* 3、方法内部类 (将一个类定义在方法里面)
*/
public void method3(){
int score = 98;
// 在这里定义一个类
class MyClass{
String subject="Java";
public void getSubjectScore(){
//方法内部类中 也可以使用方法的属性
System.out.println(pname+"的"+subject+":"+score);
}
}
//调用方法里面的类
MyClass mycls = new MyClass();
mycls.getSubjectScore();
}
People people = new People();
// 3 调用方法
people.method3();
注意:内部类剩的class文件 命名 外部类$内部类名.class
2.4 匿名内部类: 定义一个没有类名,只有对方法的具体实现。通常它依赖于实现关系(接口)或继承关系(父类)
a、基于实现关系
public interface MyInterface {
// 学习
public void study();
// 工作
public void work();
}
// 创建一个匿名类(让接口的引用 指向匿名类的对象)
MyInterface person = new MyInterface() {
@Override
public void study() {
System.out.println("这个人也好好学习");
}
@Override
public void work() {
System.out.println("这个人也好好工作");
}
};
person.study();
person.work();
b、基于继承关系
public class MyClass {
public void service(){
System.out.println("提供服务的方法。");
}
}
// 父类 new 一个 匿名类,这个匿名类是它的子类
MyClass cls = new MyClass(){
@Override //匿名类重写父类的方法 service
public void service() {
System.out.println("这是子类的方法");
}
};
cls.service();
二、异常
1、异常的概述
异常定义: 在程序中,发生“不正常”的事件,导致程序无法正常运行,并使JVM中断,称为异常
生活中的异常: 早上起床上课,平时骑车20分钟可以到达教室,由于天气原因或者闹钟响了自动关闭,不能按时到达教室上课,迟到了,此时就属于异常现象 。
捕获异常: 当程序在运行时,发生了异常 ,为了让程序正常执行,需要对异常捕获(catch),称之为捕获异常
Java是面向对象的语言, 异常本身就是一个类(Exception),当发生异常时会创建异常对象,捕获的就是该对象。
System.out.println("请输入一个数字");
Scanner sc = new Scanner(System.in);
// 对可能发生的异常 进行处理
int num = sc.nextInt();
if(num%2==0){
System.out.println("这个数是偶数");
}
异常代码可能发生异常, 当用户输入非数字时, 导致程序抛出一个异常对象 :
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor(Scanner.java:864)
2、异常关键字 以及层次关系
a、try: 试一试 ,将可能发生的代码使用try包裹 ,try不能单独出现
b、catch : 捕获异常, 当发生指定的异常对象时,执行catch代码
System.out.println("请输入一个数字");
Scanner sc = new Scanner(System.in);
// 对可能发生的异常 进行处理
try {
int num = sc.nextInt(); // 发生异常后,try里面的代码不再执行
if (num % 2 == 0) {
System.out.println("这个数是偶数");
}
System.out.println("结束");
}catch(Exception ee){// 对应的异常类 来捕获对应的异常对象 ,不能确定异常类,可以使用父类Exception
System.out.println("你的输入不正确");
}
System.out.println("程序继续运行直到结束。。。。");
一个try + 多个catch
// 抛出的异常 不能被catch捕获,会发生什么?
try {
int[] num = {1, 2, 3};
System.out.println(num[1]); // 没有捕获该异常对象,JVM依然终止运行
System.out.println(10/0);
}catch(NullPointerException ee){
System.out.println("这是空指针异常");
}catch(ArrayIndexOutOfBoundsException ee){
System.out.println("数组下标越界异常");
}catch(Exception ee){
// 输出异常 堆栈消息 方便程序员排错(尽可能避免用户看见)
ee.printStackTrace();
System.out.println("系统繁忙!"+ee.getMessage());
}
System.out.println("程序结束");
c: finally : 异常之后的最终处理 (无法是否发生异常,程序都执行 )
try… finally 结构
try{
System.out.println("请输入两个数 ,计算两个数相除");
Scanner sc = new Scanner(System.in);
int num1 = sc.nextInt();
int num2 = sc.nextInt();
double s = num1/num2; // 可能出错
System.out.println(" try里面结束,结果:"+s);
}finally{
System.out.println("无论是否发生异常,都会执行这个语句块,一般用于资源回收");
}
try… catch…finally 结构
try {
System.out.println("请输入两个数 ,计算两个数相除");
Scanner sc = new Scanner(System.in);
int num1 = sc.nextInt();
int num2 = sc.nextInt();
double s = num1 / num2; // 可能出错
System.out.println(" try里面结束,结果:" + s);
}catch(ArithmeticException ee){
ee.printStackTrace();
System.out.println("除数不能为0 !!");
}catch(Exception ee){
ee.printStackTrace();
System.out.println("系统繁忙!!!");
}finally {
System.out.println("用于资源回收。");
}
3、捕获异常
try…catch…finally
4、抛出异常
/**
* 根据下标访问数组元素
* @param array
* @param index
* @return
*/
public static int getEleByIndex(int [] array , int index){
// 抛出异常: 可以在异常发生时 或发生之前 创建一个异常对象并抛出
// 手动抛出一个异常 throw new 异常类([异常消息]);
if(index <0 || index > array.length-1){
//抛出异常
throw new ArrayIndexOutOfBoundsException("你的下标越界了");
}
int n = array[index];
return n;
}
public static void main(String[] args) {
//数组
int [] array = {2,1,4,5};
int index=4;
// 定义方法访问下标的元素 此时会产生异常 并抛出给方法的调用者
try {
int num = getEleByIndex(array, index);
System.out.println("访问的元素:" + num);
}catch(ArrayIndexOutOfBoundsException ee){
System.out.println(ee.getMessage());
}
System.out.println("结束。。。");
}
5、异常分类
由于有些异常是不能直接抛出的 ,需要先声明才可以抛出,异常可以分为两大类:
1、 编译期异常(check 异常或者检查异常):在编译期间检查异常,如果没有处理异常,则编译出错。
//创建一个文件类的对象
File file = new File("d:/aaa.txt");
// 在写代码(编译之前)时 一定要处理的异常(try..catch 或者 throws),就是编译时异常
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
这里的IOException 就是 编译期异常,需要手动处理的
2、运行期异常(runtime 异常或者运行异常):在运行期间检查异常, 编译期可以不处理异常。
// 在运行期间抛出异常 不需要事先处理的 NullPointException是运行异常
String str=null;
System.out.println(str.length());
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yl29AENC-1604826439044)(D:\课程\J2008\笔记\assets\1.png)]
Exception中常用的异常类
- RuntimeException
- ArrayIndexOutOfBoundsException :数组下标越界异常
- NullPointerException:空指针异常
- ArithmeticException: 算术异常
- NumberFormatException :数字格式化异常
- ClassNotFoundException: 类没找到异常
- ClassCaseException: 类转换异常
- 检查异常(check Exception)
IOException :IO操作
FileNotFoundException: 文件未找到异常
SQLException:
EOFException:读写文件尾异常
DateFormatException:日期格式化异常
SocketException:SocketException
注意: 对于抛出检查异常,需要使用throws声明,对于抛出运行时异常,必须要使用throws声明
声明抛出异常语法:
声明抛出异常语法:
public ... 方法名([参数]) throws 异常类1,异常类2{
// 通过throw抛出 或 处理 检查异常
}
/**
* 声明抛出异常语法:
* public ... 方法名([参数]) throws 异常类1,异常类2{
*
* }
*/
//创建文件
public static void createFile() throws FileNotFoundException ,IOException {
File file = new File("d:/hello.txt");
if(file.exists()){
// 不能创建 ,需要提示用户 该文件存在
throw new FileNotFoundException("这个文件已存在,不能创建");
}else{
//创建
file.createNewFile();
}
}
面试题: 关于 finally 和 return的执行顺序问题?
回答: 当方法有返回值时,先执行fianlly,再return, 但是 finally的代码不会改变return结果
/**
* 方法有返回值 有 finally
* @param n
* @return
*/
public static int getNum(int n){
try{
if(n%2==0){
n++;
}else{
n--;
}
return n;
}catch(Exception ee){
System.out.println("catch--"+n);
return 0;
}finally {
// return 如果放在 try或catch中,不会受finally的改变
// 如果放在最下面,会受finally的改变
n++; // 5
System.out.println("fially----n:" + n); // 5
}
}
结果 返回
fially----n:5
4
6、自定义异常
1、为什么需要使用自定义异常
在Java中每一个异常类都表示特定的异常类型, 例如 NullPointerException表示空指针 ,ArithmeticException表示算术异常, 但是sun公司提供的API中不可能将实际项目中的业务问题全部定义为已知的异常类 ,这是需要程序员根据业务需求来定制异常类,例如 用户注册,可以定义用户注册异常(RegisterException),分数不能为负数也可以定制异常(ScoreExcecption)。
2、什么是自定义异常
在开发中根据自己的业务情况来定义异常类 , 灵活性较高,且方便易用。
3、如何实现自定义异常
a、定义编译期异常类,创建一个类继承 java.lang.Exception ;
b、定义运行期异常类,创建一个类继承java.lang.RuntimeException;
4、案例分析:自定义异常应用
要求: 模拟用户注册操作, 用户输入用户名 ,验证用户名是否存在,如果存在,则抛出一个异常消息 “亲,该用户已存在,不能注册” ,通过自定义异常提示消息
public class RegisterException extends Exception {
public RegisterException(){
}
public RegisterException(String message){
// 将message 赋值给父类的构造
super(message); // 将message赋值给父类的 属性,可通过getMessage()方法
}
}
public class TestRegister {
// 模拟已存在的用户
String [] users = {"袁魏巍","王麻子","王小花"};
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入你要注册的用户:");
String uname = sc.next();
TestRegister obj = new TestRegister();
try {
// 调用方法
obj.checkUserName(uname);
System.out.println("注册成功");
} catch (RegisterException e) {
System.out.println("注册失败");
System.out.println(e.getMessage());
}
}
/**
* 检查用户是否存在
* @param username
* @return true 表示通过
* 异常表示不通过
*/
public boolean checkUserName(String username) throws RegisterException{
// 使用foreach遍历
/**
* for(数据类型 变量名 : 数组名/集合名 ){
* 循环中的 变量名代表的就是数组的元素
* }
*/
for(String u : users){
// 判断u是否与 username相等 ,相等说明用户存在,需要抛出异常
if(u.equals(username)){
throw new RegisterException("亲,"+username+" 已存在,不能注册");
}
}
return true;
}
}
一、排序简介
排序算法大体可分为两种:
1、比较排序,时间复杂度O(nlogn) ~ O(n^2),主要有:冒泡排序,选择排序,插入排序,归并排序,堆排序,快速排序等。
2、非比较排序,时间复杂度可以达到O(n),主要有:计数排序,基数排序,桶排序等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LfAHvhZ0-1604826439046)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1940317-7caf7a8dec095a80-1562598759288.png)]
二、排序分类
1.冒泡排序法
算法思路:
1、比较相邻的元素。如果第一个比第二个大,就交换它们两个;
2、对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
3、针对所有的元素重复以上的步骤,除了最后一个;
4、重复步骤1~3,直到排序完成。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GnPs4cgS-1604826439048)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E5%86%92%E6%B3%A1.gif)]
代码:
public class Bubble
{
public static void main(String[] args) {
int array[] = {1,2,4,3,9,7,8,6};
for( int i = 0;i < array.length;i++ ){
for( int j = 0;j < array.length - 1;j++ ){
if( array[j] > array[j+1] ){
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
for( int i = 0 ; i < array.length ; i++ ){
System.out.print(array[i]+" ");
}
}
}
2.选择排序
算法思路:
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OEr6a7at-1604826439050)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E9%80%89%E6%8B%A9.gif)]
代码:
public class Select {
public static void main(String[] args) {
int minIndex = 0;
int temp = 0;
int array[] = {1,2,4,3,9,7,8,6};
for(int i = 0;i < array.length;i++){
minIndex = i; //先假设最开始的元素为最小的元素
for( int j = i + 1;j < array.length;j++ ){
if( array[j] < array[minIndex] ){ // 寻找最小的数
minIndex = j; // 将最小数的索引保存
}
}
temp = array[minIndex]; //将此轮的最小元素和最开始的元素交换
array[minIndex] = array[i];
array[i] = temp;
}
for( int i = 0;i < array.length;i++ ){
System.out.print(array[i]+" ");
}
}
}
3.插入排序
算法思路:
1、从第一个元素开始,该元素可以认为已经被排序;
2、取出下一个元素,在已经排序的元素序列中从后向前扫描;
3、如果该元素(已排序)大于新元素,将该元素移到下一位置;
4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
5、将新元素插入到该位置后;
6、重复步骤2~5。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FMAkyVSh-1604826439052)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E6%8F%92%E5%85%A5.gif)]
代码:
public class Insert {
public static void main(String[] args) {
int array[] = {1,2,4,3,9,7,8,6};
int index = 0;
int current = 0;
for (int i = 1; i < array.length; i++) {
index = i - 1; //左边的排是排好序的
current = array[i]; //表示当前取到的扑克牌
while (index >= 0 && array[index] > current) { //如果左边的排比取到的排大则右移
array[index + 1] = array[index];
index--;
}
array[index + 1] = current; //直到该手牌比抓到的牌小(或二者相等),将抓到的牌插入到该手牌右边
}
for( int i = 0 ; i < array.length ; i++ ){
System.out.print(array[i]+" ");
}
}
}
4.归并排序
算法思路:
该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
1、把长度为n的输入序列分成两个长度为n/2的子序列;
2、对这两个子序列分别采用归并排序;
3、将两个排序好的子序列合并成一个最终的排序序列。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dey6JhHk-1604826439054)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E5%BD%92%E5%B9%B6.gif)]
代码:
/*
k表示最终i和j比较之后最终需要放的位置
i和j用来表示当前需要考虑的元素
left表示最左边的元素
right表示最右边的元素
middle表示中间位置元素,放在第一个已经排好序的数组的最后一个位置
*/
public class Merging {
/*******************测试************************/
public static void main(String[] args) {
int[] nums = { 2, 7, 8, 3, 1, 6, 9, 0, 5, 4 , 9 , 19 ,12,16,14,12,22,33 };
mergeSort(nums , 0 , nums.length - 1 );
System.out.println(Arrays.toString(nums));
}
/********************算法************************/
/*
arr:要处理的数组
l:开始位置
r:结束位置
递归对arr[ l ... r ]范围的元素进行排序
*/
private static void mergeSort(int[] arr,int left,int right){
if( right - left <= 10 ){ //当数据很少的时候使用插入排序算法
ChaRuPaiXu.ChaRuPaiXuFa2( arr , left ,right);
return;
}
int middle = ( left + right ) / 2; //计算中点位置
mergeSort( arr , left , middle ); //不断地对数组的左半边进行对边分
mergeSort( arr , middle+1 , right ); //不断地对数组的右半边进行对半分
if( arr[middle] > arr[middle+1] )//当左边最大的元素都比右边最小的元素还小的时候就不用归并了
merge( arr , left , middle , right ); //最后将已经分好的数组进行归并
}
//将arr[ l... mid ]和arr[ mid ... r ]两部分进行归并
/*
|2, 7, 8, 3, 1 | 6, 9, 0, 5, 4|
*/
private static void merge(int[] arr, int left, int mid, int right) {
int arr1[] = new int[ right - left + 1 ]; //定义临时数组
for( int i = left ; i <= right ; i++ ) //将数组的元素全部复制到新建的临时数组中
arr1[ i - left ] = arr[ i ];
int i = left;
int j = mid + 1; //定义两个索引
for( int k = left;k <= right ; k++){
if( i > mid ) //如果左边都比较完了
{
arr[ k ] = arr1[ j - left ]; //直接将右边的元素都放进去
j++;
}
else if( j > right ){ //右边都比较完了
arr[ k ] = arr1 [i - left ]; //直接将左边的元素放进去
i++;
}
else if( arr1[ i-left ] < arr1[ j-left ] ){
arr[ k ] = arr1[ i - left];
i++;
}
else
{
arr[ k ] = arr1[ j - left];
j++;
}
}
}
}
5.快速排序
算法思路:
通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。
1、从数列中挑出一个元素,称为 “基准”(pivot);
2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-73rwOq2L-1604826439057)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E5%BF%AB%E9%80%9F.gif)]
代码:
public class Quick {
public static void main(String[] args){
int array[] = {1,2,4,3,9,7,8,6};
quickSort(array,0,array.length-1);
for( int i = 0 ; i < array.length ; i++ ){
System.out.print(array[i]+" ");
}
}
private static void quickSort(int[] arr,int l,int r){
if( l >= r ) return;
int p = partition(arr,l,r); //找到中间位置
quickSort(arr,l,p-1);
quickSort(arr,p+1,r);
}
private static int partition(int[] arr,int l,int r){
int v = arr[l]; //取出第一个元素
int j = l; //j表示小于第一个元素和大于第一个元素的分界点
for( int i = l + 1;i <= r;i++ ){
//将所有小于第一个元素的值的元素全部都放到它的左边
if( arr[i] < v ){ //如果当前元素小于v,则交换
swap(arr,i,j+1);
j++;
}
}
swap(arr,l,j); //将第一个元素和中间的元素进行交换
return j;
}
}
public static void quickSort(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;
j=high;
//temp就是基准位
temp = arr[low];
while (i<j) {
//先看右边,依次往左递减
while (temp<=arr[j]&&i<j) {
j--;
}
//再看左边,依次往右递增
while (temp>=arr[i]&&i<j) {
i++;
}
//如果满足条件则交换
if (i<j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换
arr[low] = arr[i];
arr[i] = temp;
//递归调用左半数组
quickSort(arr, low, j-1);
//递归调用右半数组
quickSort(arr, j+1, high);
}
public static void main(String[] args){
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
quickSort(arr, 0, arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
6. 堆排序
算法思路:
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ieL7bigd-1604826439058)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E5%A0%86.gif)]
最大堆要求节点的元素都要不小于其孩子,最小堆要求节点元素都不大于其左右孩子
那么处于最大堆的根节点的元素一定是这个堆中的最大值.
public class Heap {
public static void main(String[] args) {
int A[]={49,38,65,97,76,13,27,49,78,34,12,64,5,4,62,99,98,54,56,17,18,23,34,15,35,25,53,51};
HeapSort(A, A.length);
System.out.println(Arrays.toString(A));
}
public static void Swap(int A[], int i, int j)
{
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
public static void Heapify(int A[], int i, int size) // 从A[i]向下进行堆调整
{
int left_child = 2 * i + 1; // 左孩子索引
int right_child = 2 * i + 2; // 右孩子索引
int max = i; // 选出当前结点与其左右孩子三者之中的最大值
if (left_child < size && A[left_child] > A[max])
max = left_child;
if (right_child < size && A[right_child] > A[max])
max = right_child;
if (max != i)
{
Swap(A, i, max); // 把当前结点和它的最大(直接)子节点进行交换
Heapify(A, max, size); // 递归调用,继续从当前结点向下进行堆调整
}
}
public static int BuildHeap(int A[], int n) // 建堆,时间复杂度O(n)
{
int heap_size = n;
for (int i = heap_size / 2 - 1; i >= 0; i--) // 从每一个非叶结点开始向下进行堆调整
Heapify(A, i, heap_size);
return heap_size;
}
public static void HeapSort(int A[], int n)
{
int heap_size = BuildHeap(A, n); // 建立一个最大堆
while (heap_size > 1) // 堆(无序区)元素个数大于1,未完成排序
{
// 将堆顶元素与堆的最后一个元素互换,并从堆中去掉最后一个元素
// 此处交换操作很有可能把后面元素的稳定性打乱,所以堆排序是不稳定的排序算法
Swap(A, 0, --heap_size);
Heapify(A, 0, heap_size); // 从新的堆顶元素开始向下进行堆调整,时间复杂度O(logn)
}
}
}
7.希尔排序
算法思路:
1、选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
2、按增量序列个数k,对序列进行k 趟排序;
3、每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fBKmIFcr-1604826439060)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E5%B8%8C%E5%B0%94-1562598303986.gif)]
常用的h序列由Knuth提出,该序列从1开始,通过如下公式产生:
h = 3 * h +1
反过来程序需要反向计算h序列,应该使用
h = ( h - 1 ) / 3
代码:
public class Shell {
public static void main(String[] args) {
int array[] = {1,2,4,3,9,7,8,6};
int h = 0;
int length = array.length;
while( h <= length ){ //计算首次步长
h = 3 * h + 1;
}
while( h >= 1 ){
for( int i = h;i < length;i++ ){
int j = i - h; //左边的一个元素
int get = array[i]; //当前元素
while( j >= 0 && array[j] > get ){ //左边的比当前大,则左边的往右边挪动
array[j+h] = array[j];
j = j - h;
}
array[j + h] = get; //挪动完了之后把当前元素放进去
}
h = ( h - 1 ) / 3;
}
for( int i = 0 ; i < array.length ; i++ ){
System.out.print(array[i]+" ");
}
}
}
一、泛型
1、泛型定义
泛型(generics)是JDK5.0以后的特性,提供了编译期间安全监测机制,它是将数据类型参数化的一种方式。 例如:在对方法进行编写参数列表时,以前我们需要知道方法的参数类型 ,现在使用泛型机制可以将方法的参数类型也作为 “未知的类型” ,在调用该方法时传递该类型。
2、泛型的使用
2.1 泛型类(generic class)
它是一种具有一个或多个类型变量的类,(一个变量可以有多种类型)
语法
public class 类<T>{
// 类里面的数据类型 和 方法返回值,以及方法的参数都可以使用T
// <>里面可以是任意大写字母
}
public class People<T> {
private T name;
private T sex;
public T getName() {
return name;
}
public People(T name,T sex){
this.name= name;
this.sex = sex;
}
public People(){
}
}
// 创建没有指定泛型的对象 ,它默认是Object类型
People obj= new People();
obj.setName("李四");
System.out.println(obj);
System.out.println(((String)obj.getName()).length());
// 创建泛型类的对象
People<String> people = new People<String>("张三","男");
System.out.println(people);
System.out.println(people.getName().length());
定义泛型的字母
T : Type: 变量类型
K: Key : 任意键的类型
V: Value : 任意值的类型
E:ELement 用于定义集合的元素类型
2.2 泛型接口(generic interface)
在接口中定义泛型,使接口的方法可以使用该泛型,实现类实现该接口时需要指定接口的类型、
语法:
public interface Genarator<T> {
public T getValue();
public void setValue(T s);
}
public class StringGenarator implements Genarator<String> {
private String name;
@Override
public String getValue() {
return name;
}
@Override
public void setValue(String s) {
this.name=s;
}
public class StudentGenarator implements Genarator<Student> {
private Student stu;
@Override
public Student getValue() {
return stu;
}
@Override
public void setValue(Student s) {
this.stu = s;
}
}
泛型接口的好处:
让接口的方法的返回值或参数类型 也参数化 (泛型)
2.3 泛型方法
a、为什么会使用泛型方法
当一个类中 只有某个方法需要使用泛型,而不是类的全部方法使用泛型,这时可以将泛型定义的范围缩小,通常我们可以定义进行泛型方法。
b、定义泛型方法
语法:
public class 普通类{
public <T> T getValue(){
}
public <T> void setValue(T t){
}
}
public class Convert {
/**
* 转成字符串的方法
* @param <T> : 任意类型
* @return
*/
public <T> String convertString(T t){
return t.toString();
}
public <K,V> V converted(K k){
return (V)k;// 强转的前提 是k -v 有关系
}
泛型的好处:
1、 可以对类的数据类型 写通用类型,提高代码的复用性 和 可扩展性
2.4 泛型通配符
在定义泛型时除了可使用大写字母表示一种泛型类以外,还可以使用通配符表示泛型类型,如下三种表示方法
<?> :表示一种通用的泛型类,与相似 <? extends T> :表示 泛型类型是T的子类,或者是T <? super T> : 表示泛型类型是T的父类,或者是T 问题: <?> 与 的区别 T t = new T() // 语法满足
? t = new ?() // 语法不满足
是一种确定的类型 , 可以表示定义泛型类或泛型方法
<?> 是一种不确定的类型, 不能定义泛型类或泛型方法, 通常用于作为方法的形参public class Dept<T> {
// 第一个员工
private T first;
// 第二个员工
private T second;
public class Employee {
private String ename;
public String getEname() {
return ename;
}
public class Manager extends Employee {
// 通过经理对象 给经理赋值名称
public Manager(String ename){
super(ename);
}
// 使用不确定的泛型类型 <?>
/**
*
* 这里的部门的泛型可以是任意类型
*/
public void showInfo(Dept<?> dept){
System.out.println(dept.getFirst());
}
/**
* @param dept 的泛型可以是Employee 或者继承自Employee
* @param dept
*/
public void showInfo2(Dept<? extends Employee> dept){
System.out.println(dept.getFirst());
System.out.println(dept.getSecond());
}
/**
*
* @param dept 的泛型必须是 Manager 或者 Manager的父类
*/
public void showInfo3(Dept<? super Manager> dept){
System.out.println(dept.getFirst());
System.out.println(dept.getSecond());
}
public static void main(String[] args) {
TestDept obj = new TestDept();
//创建部门对象
Dept<String> dept = new Dept();
dept.setFirst("员工1");
dept.setSecond("员工2");
obj.showInfo(dept);
// 在部门中添加 员工对象
Dept<Employee> dept2 = new Dept();
dept2.setFirst(new Employee("小强"));
dept2.setSecond(new Employee("小花"));
//这里的dept2的泛型是 Employee
obj.showInfo2(dept2);
Dept<Manager> dept3 = new Dept();
dept3.setFirst(new Manager("张经理"));
dept3.setSecond(new Manager("王经理"));
//这里的dept3的泛型是 Manager
obj.showInfo2(dept3);
// 调用时 参数的泛型必须是 Manager 或Manager的父类
obj.showInfo3(dept3);
obj.showInfo3(dept2);
}
二、集合框架
1、为什么会有集合?
存储多个元素我们以前学过数组类型, 由于数组类型特点是 相同类型且长度固定 ,如果需要存储某一天的新闻数据,用数组不合理 ,无法确定当天数量。 Java中提供可变长度的存储多个元素的数据类型,还可以存储不同数据结构的数据。这样的类型 就是“集合类型”
数组和集合的区别?
a、数组的长度固定,集合的长度可自动扩容
b、数组的数据类型固定,集合可以存储任意类型 ,集合可以支持泛型
c、数组没有方法,而集合提供大量的方法
d、Java中提供一个动态数组 集合类型,或其他结合类型
2、集合的分布图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8e5uVfkU-1604826439064)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1591347967566.png)]
2.1 集合的顶级接口: Collection
Collection属于单列集合的根接口,它扩展的主要子接口包括 java.util.List 和 java.util.Set接口,
List接口特点存储有序 且 可重复的元素, 而Set接口特点存储无序且不可重复的元素,其中List下扩展常用的实现类包括 java.util.ArrayList 和java.util.LinkedList 和Vector , 其中Set接口下扩展的实现类包括 java.util.HashSet 和 java.util.TreeSet .
集合接口的常用方法:
-
public void add(E) : 把给定的元素添加到集合中
-
public void clear():清空集合中的所有元素
-
public boolean remove(E):删除集合中指定的元素,删除成功返回true
-
public boolean contains(E):判断该元素是否存在集合中
-
public boolean isEmpty():判断是否为空集合对象 null会报异常
-
public int size():获取几个元素的大小
-
publict Object toArray() : 将集合元素转成对象数组
public static void main(String[] args) {
//通过接口创建实现类 , 可指定存储的泛型
Collection<String> collection = new ArrayList<String>();
// 集合中指定了元素的类型 String
collection.add("hello"); // 默认添加到末尾
collection.add("hi");
collection.add("哈哈");
System.out.println("元素大小:"+ collection.size());
// 删除集合元素 (后面的原始往前 移动)
collection.remove("hi");
System.out.println("元素大小:"+collection.size());
System.out.println("第一个元素:"+((ArrayList<String>) collection).get(0));
System.out.println("第二个元素:"+((ArrayList<String>) collection).get(1));
// 判断元素是否存在
System.out.println("是否存在哈哈:"+collection.contains("哈哈"));
// 转成数组对象
Object [] objs = collection.toArray();
//遍历元素
for(Object obj : objs){
System.out.println("数组的元素:"+obj);
}
//清空元素 clear
collection.clear();
// 大小
System.out.println("清空后元素的大小(对象依然存在,只能内容为空)"
+collection.size());
// 判断对象中是否是空集合
System.out.println(collection.isEmpty());
}
Iterator 集合遍历接口
// 直接对集合元素遍历 泛型只能是包装类
Collection<Integer> scores = new ArrayList<>();
scores.add(90);
scores.add(88);
scores.add(92);
scores.add(91);
//遍历集合 使用 ,
// 再遍历集合时 不能一边遍历集合一边删除集合元素,这样会改变它的遍历的模式
Iterator<Integer> is = scores.iterator();
//判断是否有下一个元素 ,如果true ,则可以通过next获取值
while(is.hasNext()){
Integer score = is.next();
System.out.println(score);
}
-
2.2 ArrayList类
java.util.ArrayList是一个数组结构的集合,实现动态数组的功能,扩展所有Collection的方法
数组结构的本质: 线性结构的顺序结构,ArrayList中使用连续的内存空间存储, 访问时通过下标(元素所在的位置)访问
2.2 ArrayList类:
java.util.ArrayList是一个数组结构的集合,实现动态数组的功能,扩展所有Collection的方法
数组结构的本质: 线性结构的顺序结构,ArrayList中使用连续的内存空间存储, 访问时通过下标(元素所在的位置)访问
ArrayList的数据结构
分析一个类的时候,数据结构往往是它的灵魂所在,理解底层的数据结构其实就理解了该类的实现思路,具体的实现细节再具体分析。
ArrayList的数据结构是:
说明:底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。我们对ArrayList类的实例的所有的操作底层都是基于数组的。
源码分析参考:https://www.cnblogs.com/zhangyinhua/p/7687377.html#_lab2_0_1
public static void main(String[] args) {
// 通过ArrayList 创建集合对象
// 还可以存储自定义对象 默认容量是 10
ArrayList<String> list = new ArrayList<String>();
// 存储有序集合
list.add("aaa");
list.add("bbb");
list.add("ccc");
// 将元素插入到指定位置
list.add(1,"ddd");
//直接 遍历元素 get(Index) 通过下标访问元素
for(int i = 0 ;i<list.size();i++){
System.out.println("集合元素:"+ list.get(i));
}
// 设置集合的最小容量
list.ensureCapacity(20);
}
一、包的结构与功能介绍
Java是一门面向对象的语言,sun公司提供基于面向对象的帮助文档(API Application Program Interface) ,并针对不同的版本生成的API
API中根据不同的功能分如下包 (package)
- java.applet.* : java的小应用程序
- java.awt.* 和 java.swing.* : java的图形用户界面(开发单机版的小游戏)
- java.lang.* : java的语言包
- java.util.* : java的工具类包、集合框架包
- java.io.* : java文件读写包(Input 、Output)
- java.net.* : java的网络编程包(Socket机制相关,URL)
- java.sql./ javax.sql. : java的数据库操作
- java.lang.reflect.* 反射相关包
二、java的lang包
1、包装类
定义: Java的8个基本数据类型对应的 对象类型,称为它们的包装类
为什么需要包装类:
基本数据类型中不能提供方法, 不能与其他引用数据类型转换,此时包装类作为该基本数据类型的对象类型,不仅提供常用的方法,还可以与其他数据类型互相转换 和 “装箱”、“拆箱”
基本数据类型 | 包装类型 | 包装类的默认值 |
---|---|---|
byte | Byte | null |
short | Short | null |
int | Integer | null |
long | Long | null |
float | Float | null |
double | Double | null |
char | Character | |
boolean | Boolean |
问题1: 基本数据类型、包装类以及字符串的相互转换
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f5A6j9pC-1604826439068)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/2.png)]
public static void main(String[] args) {
// 1、byte 的包装类 Byte
// 创建包装类的对象
byte b=123;
Byte obj1 = new Byte(b);
//1、 包装类 转字符串 包装类对象.toString()
String s1 = obj1.toString();
//2、字符串转包装类 new 包装类(s) 或者 包装类.valueOf(s)
String s2="100";
Byte obj2 = new Byte(s2);
// 或者
Byte obj3 = Byte.valueOf(s2);
//3 获取包装类的数值,包装类转基本数据类型 Byte - > byte
// 包装类.valueOf(基本数据类型) 或者 byteValue()
byte b2 = obj2; // 包装类可以直接复制给基本数据类型 ,这个过程 “拆箱”过程
byte b3 = Byte.valueOf(obj2);
// 4、字符串转 基本类型 包装类.paseByte(s)
byte b4 = Byte.parseByte(s2);
byte b5=122;
String s5 = new Byte(b5).toString();
}
再以 Integer 举例
public static void main(String[] args) {
int n=250;
// 转包装类
Integer obj1 = new Integer(n);
//包装类转基本数据类型
int n3 = Integer.valueOf(obj1);
// 转字符串
String s1 = obj1.toString();
// 字符串再转成 Integer
Integer obj2 = Integer.parseInt(s1);
Integer obj3 = new Integer(s1);
// 字符串转int
int n2 = Integer.valueOf(s1);
// int 转 转字符串
String s3 = new Integer(n2).toString();
System.out.println("-------------Intger的常用方法------");
int num = Integer.bitCount(2); // 个位数 + 高位数的和
System.out.println(num);
// n1>n2 返回1 n1==n2 返回0 n1<n2 -1
//比较两个数是否相等
System.out.println(Integer.compare(100,200));
System.out.println(Integer.decode("123"));
//equals 比较两个数是否相等 对于基本数据类型的包装类比较他们的数值
Integer n1 = new Integer(90);
Integer n4 = new Integer(90);
System.out.println(n1.equals(n4));// 比较两个对象的 值
System.out.println(n1 == n4);// 比较 两个对象的地址
int n5 =100;
int n6 =100;
System.out.println(n5==n6);// 对于基本数据类型 == 比较的值
// 进制转换
System.out.println(Integer.toBinaryString(18));//转成二进制表示形式
System.out.println(Integer.toHexString(15));//转成16进制表示形式
System.out.println(Integer.toOctalString(10));//转成8进制表示
}
问题2: 数据类型的装箱和拆箱
装箱: 将基本数据类型自动转换成 它的包装类,可以使用包装类的方法和属性
// 自动装箱: 100自动转成 Integer
Integer num1 = 100;
拆箱: 将包装类型 自动转成 对应的基本数据类型。
// 自动拆箱: Integer 自动转成 int
int num2 = num1;
面试题:
public static void main(String[] args) {
// 包装类
// 自动装箱: 100自动转成 Integer
Integer num1 = 100;
// 自动拆箱: Integer 自动转成 int
int num2 = num1;
Integer n1 =100;
Integer n2 =100;
System.out.println(n1.equals(n2)); //true
System.out.println(n1 == n2);// 应该true 他们同时指向常量池100
Integer n3 = 150; // 等价于 Integer n3 = new Integer(150);
Integer n4 = 150; // 等价于 Integer n4 = new Integer(150);
System.out.println(n3.equals(n4));//true
System.out.println(n3 == n4);//false
Integer n6 = new Integer(100);// 一定会创建新对象
System.out.println(n6 == n1); // false
}
//结论
//对于 -128 <=Integer的值 <=127 之间(byte范围),
// 装箱时不会创建新对象 而是直接引用 常量池中的值
// 如果超出byte 的返回,则自动创建新对象,各自指向新对象的内存
2、Object类
Object类是lang包提供的 ,对于lang包的类不需要import,所以 Object类无处不在,你不需要自己创建
常用方法
a、getClass: 返回该对象的类型 任何类都有它的类型
b、equals : Java中所有的equals 方式都是重写Object的方法
原生的equals 比较的是 对象的地址 ,我们通常说的 equals比较两个对象的值是因为几乎所有的数据类型(包装类,String)都重写了equals 方法的
public boolean equals(Object obj) {
return (this == obj);
}
c、 hashCode() : 返回该都对象的hash值
// Object中的hashCode 本身没有实现 ,
/**
* 1、对于基本数据类型的包装类 其值就是其本身
* 2、对于String类型的HashCode ,也是String自己实现的,其算法目的尽可能减少hash冲突
* 3、对于自定义类,需要你自己重写HashCode ,如果不重写 就在程序运行期间 JVM根据内存地址
* 类自动分配。(原则: 根据每个有意义的属性值,计算各自的hashCode 相加等一系列操作得到)
*/
d:finalize() 资源回收调用该方法, 当对象地址不在被引用时,会被GC回收 并调用该方法
Object obj = null ;
c:toString() : 返回该对象的字符串表现形式 (通常会被子类重写)
wait():线程等待
notify():唤醒其中一个等待的线程
notifyAll:唤醒所有等待中的线程
对象的比较
public class Student {
private int id; //学生编号
private String sname;
private Integer age;
public void showInfo(){
System.out.println( sname +"---"+ age);
}
public Student(){
}
public Student(int id ,String sname ,int age){
this.id = id;
this.sname = sname;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}
// 判断类型 是否一致
if(obj instanceof Student){
// 强转
Student stu = (Student)obj;
// 开始比较 id 和 sname
if(this.id == stu.id && this.sname.equals(stu.sname)){
return true;
}
}
return false;
}
@Override
public int hashCode() {
return id;
}
}
public static void main(String[] args) {
// 创建对象 比较对象是否相等
// 比较内存相等 或 比较值(对象的属性)相等
Student stu1 = new Student(1001,"敖园",22);
Student stu2 = new Student(1001,"敖园",22);
System.out.println(stu1==stu2); // 比较两个对象的地址 (不相等) false
System.out.println(stu1.equals(stu2)); // true
// 由于equals本身没办法解决
// 两个对象因id 和name相等业务上是同一个对象的问题
// 所以需要重写 equals 和 hashcode 。
// 为什么要重写HashCode呢?
// 回答: 在JMV中如果HashCode不相等,一定不能认为是同一个对象
Student stu3 = stu1; // stu3 的地址于stu1的地址是同一个
}
总结: 对象之间的比较 ,通常是比较属性值,如果属性值相等,我们可以认为是同一个对象,
此时需要重写 equals 和hashcode方法。
为什么要重写HashCode呢?
回答: 在JMV中如果HashCode不相等,一定不能认为是同一个对象
3、System类
public static void main(String[] args) {
// System 属于系统类
// System.out; // 获取控制台的打印流
// 设置JVM运行时 系统参数
System.setProperty("encoding","UTF-8");
System.out.println("获取:"+System.getProperty("encoding"));
// 时间从 1970-01-01
System.out.println("获取当前系统的时间毫秒数:"+ System.currentTimeMillis());
System.exit(0); // 0 : 表示JVM正常退出 -1 表示非正常退出
}
4、字符串类
java.lang.String类,Java中所有的字符串都会创建该类的实例 , 它可以对字符串查找,检索,转变大小写,截取等一系列操作,并提供了大量的字符串操作方法。
String类的特点:
它是一个不可变字符串 ,它的值创建后不能被改变。
String的构造器
// 创建字符串对象
String s1="abc";
String s2 = new String("abc");
//通过字符数组构建
char [] chars = {'a','b','c'};
String s3 = new String(chars); // 或指定长度构建字符串
String s4 = new String(chars,0,2);
//或根据字节数组构建
byte [] byts = {97,98,99};
String s5 = new String(byts);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println(s4);
System.out.println(s5);
// 字符串是一个不可变的对象
// class类被JVM装载时,会分配一个存放字符串的常量池(String Pool)
// 在类加载时 先检查常量池中是否有“abc”常量,如果有,直接将ss1指向该常量
// 如果没有,则创建常量abc
// 创建2个对象
String ss1 = "abc";
// abc常量不能改变, 则再创建 abcd的常量,由ss1重新指向
ss1+="d";
// 创建3个对象
String ss2 ="abcd"; // abcd
String ss3 = "aaa"; // aaa
ss2 += ss3; // abcdaaa 重新创建abcdaaa并由ss2重新指向
String a1="abc";
String b1="abc"; // 两个地址同时指向一个常量 “abc”
System.out.println(a1==b1); // true
System.out.println(a1.equals(b1));
String c1=new String("abc");// 堆内存中 对abc包装后的地址
System.out.println(a1==c1); // false
System.out.println(a1.equals(c1));//true
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WaD6dykQ-1604826439069)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/33.png)]
字符串类常用方法
- 将此字符串与指定对象进行比较:
public boolean equals (Object anObject)
- 将此字符串与指定对象进行比较,忽略大小写:
public boolean equalsIgnoreCase (String anotherString)
举例:
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = "HELLO";
//boolean equese(Object obj):比较字符串的内容是否相同
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
System.out.println("-------------");
//boolean equalsIgnoreCose(String str):比较字符串的内容是否相同,忽略大小写
System.out.println(s1.equalsIgnoreCase(s2));
System.out.println(s1.equalsIgnoreCase(s3));
System.out.println("--------------");
}
4.1、获取功能的方法
- 返回字符串的长度:
public int length()
- 将指定的字符串连接到该字符串的末尾:
public String concat (String str)
- 返回指定索引处的char值:
public char charAt (int index)
- 返回指定字符串第一次出现在该字符串内的索引:
public int indexOf(String str)
- 返回一个子字符串,从beginIndex开始截取字符串到字符串结尾:
public String substring (int beginIndex)
- 返回一个子字符串,从beginIndex到endIndex截取字符串。含beginIndex,不含endIndex
public String substring (int beginIndex,int endIndex)
举例:
public static void main(String[] args) {
String s = "helloworld";
//length() :获取字符串的长度,其实也就是字符的个数
System.out.println(s.length());
System.out.println("---------");
//String concat (String str):将指定的字符串连接到该字符串的末尾
String s2 = s.concat("**hellow itheima");
System.out.println(s2);
//charAt(int index):获取指定索引处的字符串
System.out.println(s.charAt(0));
System.out.println(s.charAt(1));
System.out.println("-------");
//int indexOf(String str):获取str在字符串对象中第一次出现的索引,没有返回-1
System.out.println(s.indexOf("l"));
System.out.println(s.indexOf("owo"));
System.out.println(s.indexOf("ak"));
System.out.println("---------");
//String sbustring(int start):截取从start开始,到字符串结尾的字符串
System.out.println(s.substring(0));
System.out.println(s.substring(5));
System.out.println("----------");
//String substring(int start,int end):从start到end截取字符串,含start,不含end
System.out.println(s.substring(0,s.length()));
System.out.println(s.substring(3,8));
}
4.2、转换功能的方法
- 将字符串转换为新的字符数组:
public char[] toCharArray()
- 使用平台的默认字符集将该String编码转换为新的字节数组:
public byte[] getBytes()
- 将与targer匹配的字符串使用replacement字符串替换
public String rep;ace (CharSequence targer,CharSequence replacement)
举例:
public static void main(String[] args) {
String s = "helloworld";
//char[] toCharArray():把字符串转换为字符数组
char[] chs = s.toCharArray();
for(int x = 0 ; x < chs.length;x++){
System.out.println(chs[x]);
}
System.out.println("---------");
//byte[] getBytes():把字符串转换为字节数组
byte[] bytes = s.getBytes();
for(int x = 0;x < bytes.length; x++){
System.out.println(bytes[x]);
}
System.out.println("--------");
String str = "softeem";
String replace = str.replace("s","S");
System.out.println(replace);
}
4.3、分割功能的方法
将字符串按照给定的regex(规则)拆分为字符串数组:public String[] split(String regex)
举例:
public static void main(String[] args) {
String s = "aa|bb|cc";
String[] strArray = s.split("|");
for(int x = 0;x < strArray.length; x++){
System.out.println(strArray[x]);
}
}
Java的util包
一、日期和日历类
1、日期类 Date
在Java中用于表示日期的类 java.util.Date() ,用于获取日期和时间的对象, 不过这个类的一些方法以及过时(被日历类取代)
创建日期类
Date date = new Date();
Date date = new Date(long) ;
// 创建日期类的对象
Date date = new Date();
//获取当前时间 以 标准格式
System.out.println(date);
// 获取当前时间的毫秒数
System.out.println(date.getTime());
//通过毫秒数构建当前时区的 时间
Date date2 = new Date(100000);
System.out.println(date2);
// 获取 纪元时间 相隔本地时间8时区
System.out.println(date.toGMTString());
System.out.println(date.toLocaleString());
//测试时间的先后
System.out.println(date.after(date2));// true
System.out.println(date.before(date2));//false
System.out.println(date.compareTo(date2));//比较时间大小
// 时间转成字符串
System.out.println(date.toString());
2、日历类 Calendar
java.util.Calendar 是表示日历类, 它是一个单例模式 ,通过 getInstance()获取一个日历对象, 并获取日历的任意时间,日期。
常用方法
getInstance() : 获取日历对象
get() :获取指定的单位的日历数值 (年,月,日 等等)
set():设置指定单位的日历数值
add() :添加指定单位的日历数值
getTimeInMills() :获取日期的毫秒数
// 获取当前日历对象
Calendar cal = Calendar.getInstance();
// 日历的属性 (fields)
System.out.println(Calendar.DATE);
System.out.println(Calendar.YEAR);
System.out.println(Calendar.MONTH);
//获取当前日历的年份
System.out.println(cal.get(Calendar.YEAR));
// 获取月份 (返回 0-11)
System.out.println(cal.get(Calendar.MONTH));
// 获取日(1-31)
System.out.println(cal.get(Calendar.DATE));
// 获取日期
System.out.println("获取当前日历的日期:"+ cal.getTime());
// 添加日历 (指定的field 和 数量)
// 将当前日历添加2个月
cal.add(Calendar.MONTH ,2);
System.out.println("新的日期:"+cal.getTime());
cal.add(Calendar.DATE,-3);
System.out.println("新的日期:"+cal.getTime());
// 判断日期的前后 after before
// 题目 给定两个日期, 计算相差多少天
Calendar cal1 = Calendar.getInstance();
计算两个日历(日期)的相差天数/月数
// 重写构建一个时间
Calendar cal2 = Calendar.getInstance();
// 设置指定的时间
cal2.set(2020,10,20);
System.out.println("cal2---"+cal2.getTime());
// 原始方式 通过毫秒数计算
long n = cal1.getTimeInMillis()-cal2.getTimeInMillis();
System.out.println("n---->"+n);
long days = n/(1000*60*60*24);
System.out.println("days:"+days);
// java8 的方式
LocalDate date1 = LocalDate.of(2020,2,22);
LocalDate date2 = LocalDate.of(2020,3,22);
long days2 = date1.until(date2,ChronoUnit.DAYS);
System.out.println("两个时间相差的天数:"+ days2);
3、字符串与日期格式的转换
/**
* 字符串转日期
* @param str
*/
public static Date strToDate(String str) throws ParseException {
//使用格式化日期类 指定格式 :yyyy-MM-dd HH:mm:ss
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 指定格式的字符串 转成 日期
return sdf.parse(str);
}
/**
* 日期转指定格式的字符串
*/
public static String dateToString(Date date){
// 使用同样的方式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
return sdf.format(date);
}
// 调用转换方法
String s = "2020-10-22 10:10:10";
Date myDate= strToDate(s); // 注意 这里的格式需要匹配yyyy-MM-dd HH:mm:ss
System.out.println(myDate);
// 将日期转成字符串
System.out.println(dateToString(myDate));
二、正则表达式
1、正则表达式定义
正则表达式(Regular Expression)由字母和符号组成的具有特定意义的公式,用于匹配或检索符合条件的字符串。
例如 在网页上输入用户名,要求用户名必须由数字,字母,下划线,或者长度必须8-16位之间 像这样的满足条件的公式,都是编写的 正则表达式进行验证。
^[a-zA-Z0-9_]{8,16}$
解释: ^ :表示以指定的字符开头
$:以指定符合结尾
[ a-zA-Z]: 字符必须是这个a-z或 A-Z之间的一个字符
{8,16}:匹配前面的字符8次到16次之间
正则表达式不属于某一门编程语言,可以在很多语言中使用, 例如 Java ,Python 、JS、MySql
Java提供对正则表达式的支持,有如下类
a、java.util.regex.Pattern 正则表达式的编译器类
b、java.util.regex.Matcher 正则表达式的匹配器
c、java.lang.String 中的方法自动兼容 正则语法
1、 元字符
元字符是组成正则表达式的最基本的符号 ,通常代表一定意义
元字符 | 解释 |
---|---|
. | 匹配任意一个字符 |
\w | 匹配一个数字,字母,_ 或汉字 \W :对\w取反 |
\d | 匹配一个数字 \D:对\d取反 |
\s | 匹配一个空白字符 \S:对\s取反 |
\b | 匹配以什么字符开头 |
^ | 以指定的字符串开头 ,用于正则开始的标志位 |
$ | 以指定的字符串结尾,用于正则结束的标志位 |
- 匹配以abc开头的字符串
^abc 或者 \babc
2、匹配8位数的QQ号码
^\d\d\d\d\d\d\d\d$ 或 ^\d{8}$
3、匹配以1开头的手机号
^1\d\d\d\d\d\d\d\d\d\d$
String s ="abc";
System.out.println(s.matches("abc"));
System.out.println(s.matches("^abc"));
s="6";
System.out.println(s.matches("\\d"));
s="123456780";
System.out.println(s.matches("\\d{8}"));
区间段
//[x] :表示匹配一个字符
//[abc]:表示匹配a或b或c 一个字符
System.out.println("a".matches("[abc]"));
System.out.println("b".matches("[abc]"));
//[^abc]:匹配不是 a 或b 或c
System.out.println("b".matches("[^abc]"));
// [0-9]:匹配任意一个数字
System.out.println("4".matches("[0-9]"));
重复限定符
正则表达式中用于匹配重复次数的符号
重复限定符 | 意义 |
---|---|
* | 匹配前一个字符0次或多次 |
? | 匹配前一个字符0次或1次 |
+ | 匹配前一个字符1次或多次 |
{n} | 匹配前一个字符n次 |
{n,} | 匹配前一个字符至少n次 |
{n,m} | 匹配前一个字符n到m次(包含n次,m次) |
转义
如果要匹配的字符串中本身就包含小括号,那是不是冲突?应该怎么办?
针对这种情况,正则提供了转义的方式,也就是要把这些元字符、限定符或者关键字转义成普通的字符,做法很简 答,就是在要转义的字符前面加个斜杠,也就是\即可。 如:要匹配以(ab)开头:
或条件
回到我们刚才的手机号匹配,我们都知道:国内号码都来自三大网,它们都有属于自己的号段,比如联通有 130/131/132/155/156/185/186/145/176等号段,假如让我们匹配一个联通的号码,那按照我们目前所学到的正 则,应该无从下手的,因为这里包含了一些并列的条件,也就是“或”,那么在正则中是如何表示“或”的呢? 正则用符号 | 来表示或,也叫做分支条件,当满足正则里的分支条件的任何一种条件时,都会当成是匹配成 功。
那么我们就可以用或条件来处理这个问题
// 创建匹配格式的编译器
String phone ="13388880000";
Pattern pattern = Pattern.compile("1[356789][0-9]{9}");
// 根据编译器获取匹配器
Matcher matcher = pattern.matcher(phone);
System.out.println("是否匹配目标字符串:"+matcher.matches());
// 或者简写 匹配年龄 (0-100 01-09 | 10-99 |100)
boolean flag = Pattern.matches
("(0?[0-9])|([1-9][0-9])|(100)","10");
System.out.println(flag);
// 匹配一个特殊字符
// 匹配一个字符串中是否包含 .
String s2 ="adb";
System.out.println(s2.matches(".*b.*"));
// 因为.表示的所有字符 当匹配.时,需要转义
System.out.println(s2.matches(".*\\..*"));
// 对于特殊符号 需要转义 \\
// 匹配邮箱 包含 任意字符任意个数@域名.com
// .cn .com.cn .gov .net
String email="1@softeem.com";
System.out.println(email.matches("\\w+@\\w+(\\.[a-z]{2,3}){1,2}"));
// 匹配一级网址 http:// https://d
String url="http://www.baidu.com";
System.out.println(url.matches("https?://www\\.\\w+\\.[a-z]{2,3}"));
// 匹配生日格式 1900-13-01 ~ 2099-12-31 01 -31
String birthday="1998-13-10"; // yyyy-MM-dd
String regexp="((19)|(20))[0-9]{2}-((0[0-9])|(1[0-2]))-((0[1-9])|([1-2][0-9])|(3[0-1]))";
System.out.println(birthday.matches(regexp));
匹配汉字
// 匹配汉字
String uname ="张";
System.out.println(uname.matches("[\\u4E00-\\u9FFF]+"));
三、定时器类(Timer类)
1、Timer类
java.util.Timer类 是一个任务调度的定时器类,可以安排任务一次执行,或定期重复执行
创建Timer对象
Timer timer = new Timer();
Timer timer = new Timer(name);
常用方法
timer.schedule(TimerTask,2000); 2秒后执行一次任务
timer.schedule(TimerTask,2000,1000); 2秒后开始执行任务,每1s执行一次
2、TimerTask类
java.util.TimerTask类是由定时器执行的任务类,是一个抽象类。
import java.util.Timer;
import java.util.TimerTask;
/**
* ClassName: TestTimer
* Description:
* date: 2020/10/22 17:10
*
* @author wuyafeng
* @version 1.0 softeem.com
*/
public class TestTimer {
//统计打印次数
static int count=0;
// 创建一个定时器类
static Timer timer = new Timer("我的定时器");
public static void main(String[] args) {
// 开始定时一个任务
TestTimer obj = new TestTimer();
// 多少毫秒之和 执行一次任务
// timer.schedule(obj.new MyTask(),2000);
// 2秒之后开始执行,每隔1s执行一次
timer.schedule(obj.new MyTask(),2000,1000);
}
// 定义内部类时,可以直接使用外部类的属性 和它的对象
class MyTask extends TimerTask{
@Override
public void run() {
System.out.println("你好!!!");
count++;
if(count>=10){
System.out.println("停止");
//停止
timer.cancel();
}
}
}
}
一、集合框架
有关LinkedList的集合的,它是一个链表结构的集合
1、链表结构
1.1 单链表的结构
所谓单链表(Linked)在内存中不连续的一端内存空间, 链表的每一个元素是一个节点,每一个结点由数据元素和下一个结点的存储位置组成,链表结构与数组结构最大区别是链接结构的存储内存是不连续的,而数组结构的内存是连续的,链表结构不能与数组结构一样快速查找,
链表结构操作特点是 添加,删除元素效率高,查询效率低;
数组结构操作特点: 添加,删除效率低,查询效率高
链表结构的示意图
前驱: 该节点的上一个元素的地址
后继: 该节点的下一个元素的地址
链表结构中最后一个元素的”后继“为null
1.2 单链表的实现
链表实现添加元素:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IS4Rq6er-1604826439072)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E6%B7%BB%E5%8A%A0%E8%8A%82%E7%82%B9.png)]
/**
* 添加到最后元素
* @param obj
*/
public void addLast(Object obj){
//将节点添加到最后
//add(obj , this.size);
// 创建节点
// Node node = new Node(obj);
// // 找到最后一个元素的地址
// Node lastNode = this.header;
// for(int i = 0;i<this.size-1 ;i++){
// lastNode = lastNode.next;
// }
//
// lastNode.next=node;
// 找最后一个结点 (由于最后一个结点的next是null)
Node node = new Node(obj);
Node lastNode = this.header;
while(lastNode.next!=null){
lastNode = lastNode.next;
}
lastNode.next = node;
this.size++;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kr6iX16j-1604826439074)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E5%88%A0%E9%99%A4.png)]
/**
* 删除第一个节点
* @param index
* @return
*/
public void removeFirst(){
//删除第一个节点
if(this.size==0){
throw new IllegalArgumentException("没有需要删除的原始");
}
// 获取当前连接的“后继”
Node node = this.header.next;
// 并让后继作为头
this.header = node;
this.size--;
}
/**
* 删除最后节点
*/
public void removeLast(){
//删除是否存在数据
if(this.size==0){
throw new IllegalArgumentException("没有需要删除的原始");
}
// 找最后一个元素的前一个 地址 ,并将该地址的next 改为null
Node cur = this.header;
Node pre = this.header;
while(cur.next!=null){
pre = cur;
// 下一个变为当前
cur = cur.next;
}
// 最后一个元素 就是 当前
pre.next = null;
size--;
}
2、队列结构
队列结构(Queue): 在基于链表结构的基础上 ,实现的一种“先进先出”的结构, 常用操作 入队(put),出队(pop) ,设置队列的头结点 和 尾结点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ncbMBWJf-1604826439076)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E9%98%9F%E5%88%97%E7%BB%93%E6%9E%84.png)]
package com.j2008.dataStruct;
/**
* ClassName: MyQueue
* Description:
* date: 2020/10/26 16:41
*
* @author wuyafeng
* @version 1.0 softeem.com
*/
public class MyQueue<T> {
// 头结点
private Node front;
// 尾结点
private Node tail;
// 大小
private int size;
public MyQueue(){
// 头,尾为空
this.front= this.tail=null;
}
class Node{
private T obj;
private Node next;
public Node(T obj){
this.obj = obj;
}
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
/**
* 入队 : 将元素添加到队列的尾部
*/
public void put(T obj){
// 创建节点
Node node = new Node(obj);
// 如果元素为空 则头就尾,尾就是头
if(isEmpty()){
this.front = this.tail = node;
return ;
}
// 将新元素的地址 作为尾的next
this.tail.next=node;
//将新元素的结点 作为尾节点
this.tail = node;
this.size++;
}
/**
* 出队: 将元素从队列的头部移除 (保持与队列脱离关系)
* @return
*/
public T pop(){
if(isEmpty()){
throw new IllegalArgumentException("没有弹出的原始");
}
// 移除头部元素
Node popNode = this.front;
// 设置现在的头元素是下一个
this.front = popNode.next;
// 将弹出的元素next 设置null,与队列脱离关系
popNode.next=null;
this.size--;
// 如果没有元素了 则需要 设置头尾都是null
if(this.size<0){
this.front=this.tail=null;
}
return popNode.getObj();
}
/**
* 判断元素是否为空
* @return
*/
public boolean isEmpty(){
if(this.front==null && this.tail==null){
return true;
}
return false;
}
}
3、栈结构
栈(Stack)结构也是常用数据结构之一,它具有 “后进先出” 的特点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kk324c9a-1604826439078)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E6%A0%88.png)]
public class MyStack<T> {
// 定义一个数组 ,用于存储元素
private Object[] obj;
private int size;
public MyStack(){
obj = new Object[10];
size=0;
}
/**
* 入栈: 压入栈顶元素
* @param t
*/
public void push(T t){
expandCapacity(size+1);
obj[size]=t;
size++;
}
/**
* 返回栈顶元素:peek
*/
public T peek(){
if(size>0) {
return (T) obj[size - 1];
}
return null;
}
/**
* 出栈: 返回栈顶的元素,并删除该元素
* @return
*/
public T pop(){
T t = peek();
if(size>0) {
// 将最后一个元素 删除
obj[size - 1] = null;
size--;
}
return t;
}
/**
* 是否为空元素
* @return
*/
public boolean isEmpty(){
return size==0;
}
/**
* 扩容数组大小 : 扩容1.5倍
*/
public void expandCapacity(int size){
if(obj.length< size){
// 需要扩容
int length = size*3/2 + 1;
this.obj = Arrays.copyOf(this.obj,length);
}
}
}
一、LinkedList集合
java.util.LinkedList集合是java.util.List的实现类,实现List接口的所有方法(添加,删除,查找,判断是空等) ,它添加,删除元素较快,查询相对慢,但是查询头尾元素较快
LinkedList集合实现双向链表接口,实现从头元素到尾元素的链表和从尾到头元素的链表,目标为了增加元素的检索效率 ,如下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VrxYRsfO-1604826439079)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E5%8F%8C%E5%90%91.png)]
关于LinkedList实现大量操作头元素和尾元素的方法。 其中必须通过LinkedList的引用创建该对象
public void addFirst(E e) :将指定元素插入此列表的开头。
public void addLast(E e) :将指定元素添加到此列表的结尾。
public E getFirst() :返回此列表的第一个元素。
public E getLast() :返回此列表的后一个元素。
public E removeFirst() :移除并返回此列表的第一个元素。
public E removeLast() :移除并返回此列表的后一个元素。
public E pop() :从此列表所表示的堆栈处弹出一个元素。
public void push(E e) :将元素推入此列表所表示的堆栈。
public boolean isEmpty() :如果列表不包含元素,则返回true
二、 Set集合
java.util.Set 接口 继承自Collection接口,实现对元素的基本操作 ,与java.util.List区别于 Set集合存储无序,且唯一的元素,List存储有序,且可重复的元素
Set接口的实现类 HashSet 、 LinekedHashSet 、TreeSet
1、HashSet
HashSet集合依据元素的哈希值确定在内存中的存储位置, 所谓Hash值是内存中哈希表的唯一标志,通过哈希值可快速检索到元素所在的位置 , 所以它查询效率高 ,与HashSet类似结构的包括HashMap 等
创建一个HashSet时,就是创建一个HasMap( 关于HashMap结构后面讲)
什么是哈希表?
在Java1.8以前,哈希表的底层实现采用数组+链表结构,但是这样对于“Hash冲突” (两个对象生成的哈希值一样),即多个元素存储在一个“数据桶”中, 这样查找该元素时,依然效率低下, 为了解决由于哈希冲突导致的数据查询效率低下,JDK8以后将哈希表实现采用 数组+链表+红黑树结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EXCDQdDD-1604826439081)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/12.png)]
1.2.HashSet存储自定义对象类型
HashSet对于对象是否相同的依据,判断对象的hashCode值和equals是否相等,如果它们相等则判断元素一致,不能重复添加
public class People {
private int pid;
private String pname;
private int age;
public People(int pid, String pname, int age) {
this.pid = pid;
this.pname = pname;
this.age = age;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
return this.pid;
}
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}
if(obj instanceof People){
People p = (People) obj;
if(p.pid == this.pid && p.getPname().equals(p.getPname())){
return true;
}
}
return false;
}
}
//存储 对象类型
Set<People> sets = new HashSet<>();
People p = new People(1001,"关羽",100);
People p2 = new People(1002,"张飞",100);
People p3 = p2;
System.out.println("p的hashcode:"+p.hashCode());
sets.add(p);
// 检查是否为同一个地址
sets.add(p);
sets.add(p2);
sets.add(p3);
// 插入一个重新new的张飞对象 HashSet以 equals和hashcode的结果作为是否重复对象的依据
People p4 = new People(1002,"张飞",90);
sets.add(p4); // 会当做是重复的对象 ,不能添加成功。
System.out.println("sets的长度:"+sets.size());
for(People obj : sets){
System.out.println(obj.getPid()+"---"+obj.getPname()+"---"+obj.getAge());
}
1.3 LinkedHashSet
在HashSet中存储的数据是唯一且无序,如何保证数据的有序型,可通过扩展HashSet的子类完成,
java.util.LinkedHashSet ,它实现有序的Hash结构, 它的底层实现使用链表+哈希结构,
创建LinkedHashSet时,就是创建一个LinkedHashMap结构 ,linkeHashSet中如何保证顺序一致性
accessOrder = false; 按照插入的顺序存储 accessOrder = true: 按照访问的顺序存储。
// 创建LinkedHashSet对象
LinkedHashSet<String> set = new LinkedHashSet();
set.add("aaa");
set.add("bbb");
set.add("ccc");
set.add("ddd");
//遍历元素
for(String s : set){
System.out.println(s);
}
1.4 TreeSet
TreeSet实现对Set元素的排序功能, 也包含基础的Set集合功能。 存放在TreeSet中的元素时有序的,默认升序,也可以自定义排序规则。
两种方式实现自定义排序规则
1、对元素(自定义类)实现 java.lang.Comparable 接口,重写 compareTo方法
public class Fruit implements Comparable<Fruit>{
private int id;
private String name;
public int compareTo(Fruit o) {
// return this.id-o.id; 升序
return o.id - this.id;
// 正数: 前一个大于后一个
// 负数: 前一个小于后一个
}
}
// 实现自定义排序规则的方式一 : 对象实现Comparable接口 (java.lang)
// 重写compareTo 方法。
TreeSet<Fruit> fruitSet = new TreeSet<>();
Fruit f1 = new Fruit(100,"苹果");
Fruit f2 = new Fruit(101,"香蕉");
fruitSet.add(f1);
fruitSet.add(f2);
System.out.println(fruitSet.size());
for(Fruit f : fruitSet){
System.out.println(f.getId()+"---"+f.getName());
}
2、通过匿名内部类的方式 在创建TreeSet时,创建自定义排序规则 ,new Comparator的接口
// 自定义排序规则的方式二: 对treeSet实现匿名内部类 new Comparator(java.util)
TreeSet<Integer> scores = new TreeSet (new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1; //降序
}
});
// 添加元素
scores.add(80);
scores.add(87);
scores.add(90);
scores.add(78);
for(Integer score : scores){
System.out.println(score);
}
// 按照对象的某一属性降序
TreeSet<People> peopleSet = new TreeSet<>(new Comparator<People>() {
@Override
public int compare(People o1, People o2) {
return o2.getAge()- o1.getAge(); // 根据age降序排列
}
});
peopleSet.add(new People(1001,"张飞",100));
peopleSet.add(new People(1002,"刘备",102));
peopleSet.add(new People(1003,"关羽",108));
for(People p : peopleSet){
System.out.println(p.getPid()+"--"+p.getAge());
}
三、Map集合
java.util.Map集合用于存储key-value的数据结构 ,一个键对应一个值,其中键在集合中是唯一的, Value可以重复, 例如 学号与学生的关系,省份编号对应省份信息, 对于Map集合的常用实现类包括 HashMap 、LinkedHashMap、HashTable 、TreeMap 等 。
1、HashMap(重点)
java.util.HashMap 存储无序的,键值对数据,HashMap的实现原理在JDK1.8以前使用 链表+数组结构,1.8以后使用链表+数组+红黑树结构, 使用Hash表的存储方式其检索效果高
特点:
a、HashMap的key唯一,且无序,value不唯一
b、HashMap的key和value 都可以为null
c、对于相同key 元素,它的value会覆盖原始value
HashMap的常用方法
HashMap()
构造一个空的 HashMap ,默认初始容量(16)和默认负载系数(0.75)。
HashMap(int initialCapacity)
构造一个空的 HashMap具有指定的初始容量和默认负载因子(0.75)。
HashMap(int initialCapacity, float loadFactor)
构造一个空的 HashMap具有指定的初始容量和负载因子。
HashMap(Map<? extends K,? extends V> m)
构造一个新的 HashMap与指定的相同的映射 Map 。
a、put(K key,V value) : 存储key-value 到容器中
b、V get(K key): 根据key 获取对应的value
c、Set keySet(): 返回所有的key,Set集合
d、boolean containsKey(K key): 判断key是否存在
e、clear():清空容器的原始
f、boolean containsValue(V value):判断value是否存在
g、Collection values() : 返回所有的value集合
h、isEmpty(): 判断是否为空集合
i、remove(Object key) : 根据key删除这个key-value
j、size():返回元素的大小
k、Set<Map.Entry<Key,Value>> entrySet(): 返回容器的key-value的实体类的集合,方便遍历元素
HashMap的源码分析
// 创建HashMap对象
Map<String , Integer> cards = new HashMap();
//存储
cards.put("红桃",3);
cards.put("黑桃",3);
cards.put("方片",2);
cards.put("梅花",8);
cards.put("红桃",2); // 会覆盖原始的value
//获取指定key的value元素
System.out.println(cards.get("红桃"));
// 获取所有的key
Set<String> keys= cards.keySet();
//通过遍历所有的key 访问对应的value
for(String k : keys){
System.out.println(k+"-----"+cards.get(k));
}
// 判断key 是否存在, 判断value是否存在
System.out.println("是否有红桃:"+cards.containsKey("红桃"));
System.out.println("判断是否有value:"+cards.containsValue(2));
// 获取所有的value
Collection<Integer> values = cards.values();
Iterator its= values.iterator();
while(its.hasNext()){
System.out.println("values ----"+its.next());
}
//获取所有的元素
System.out.println(cards.size());
// 遍历map集合元素 entrySet
Set<Map.Entry<String, Integer>> entrys = cards.entrySet();
for(Map.Entry<String, Integer> en : entrys ){
System.out.println("entry遍历方式:"+en.getKey()+"-----"+en.getValue());
}
// remove
System.out.println("删除元素:"+cards.remove("红桃"));
//清空元素
cards.clear();
System.out.println("元素的大小:"+cards.size());
}
一、Java的I/O
1、什么是I/O?
在生活中,你需要将U盘的文件 ,拷贝到电脑(或者将电脑的文件拷贝到其他设备), 文件是通过数据流的方式依次到达另一个设备中, 文件的拷贝就是一个输入(Input)和输出(Output)的过程
Java中提供对应的API支持对文件的输入和输出 , java.io.*
2、什么流?
生活中 也存在流的概念,例如 管道中的流水,从管道的入口到达管道出口,一滴水可以从入口流到出口,可以将“水”比作 “字节数据或字符数据”,数据也可以从一端流到另一端。
输入(Input): Java中,以“应用程序(内存)”为中心,将磁盘文件(设备端)到达内存中的过程 称为 输入
输出(Output): 以“应用程序(内存)”为中心,将数据从内存到达磁盘文件(设备端)的过程称为 输出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Yel8csz-1604826439084)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/11.png)]
根据文件操作方式不同可以将IO分为三类
1、按照读写方向不同: 输入流(InputStream)和输出流(OutputStream)
2、按照数据类型不同: 字节流(Byte)和字符流(Char)
3、按照读写效率不同: 单一流和包装流(buffered等缓冲流)
关于流的分类Java提供 4个顶级抽象类 ,分布构建它们的子类
输入流 | 输出流 | |
---|---|---|
字节流 | InputStream(字节输入流) | OutputStream(字节输出流) |
字符流 | Reader(字符输入流) | Writer(字符的输出流) |
常见的流
1、文件字节输入流和文件字节输出流 : FileInputStream 和 FileOutputStream
2、文件字符输入流和文件字符输出流: FileReader 和 FileWriter
3、缓存字节输入流和 缓存字节输出流 BufferedInputStream 和 BufferedOutputStream
4、缓存字符输入流和缓冲字符输出流 BufferedReader 和BuffereWriter
5、数据输入流和数据输出流: DataInputStream 和 DataOutputStream
6、字节数组输入流 和 字节数组输出流 : ByteArrayInputStream 和 ByteArrayOutputStream
7、字符数组输入流 和 字符数组输出流: CharArrayReader 和 CharArrayWriter
8、转换流: (字节流转成字符流) InputStreamReader 和 OutputStreamWriter
9、对象流(序列化流): ObjectInputStream 和 ObjectOutputStream
10、随机访问流(这个流既可以读,也可以写):RandomAccessFIle
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XLRQLbRu-1604826439085)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/22.png)]
字节流
定义: 文件的输入输出以一个“字节”为单位,进行流处理
FileInputStream 和 FileOutputStream
读入: 将文件中的数据读到内存中
常用方法:
int read() : 一个字节的读取 ,返回字节 的asci码 ,对于汉字会分3次读取
int read(byte) : 按一个数组长度读取, 返回实际读取的字节长度, 数据存放在数组中
int read(byte , offset , len) : 读取流中指定长度的数据,并存放在指定位置,
available() :返回流中剩余的字节长度,如果已读完,则返回0
skip(long n ): 丢弃指定的字节长度,从下一个开始读取
`**available**()`
File file = new File("d:/aaa.txt");
FileInputStream fis = new FileInputStream(file);
//
byte [] b= new byte[10];
StringBuffer sb = new StringBuffer();
//每次读取的长度, b: 存放数据的数组
int len = 0;
while((len = fis.read(b)) !=-1){
sb.append( new String(b,0,len));
}
System.out.println(sb);
fis.close();
public static void read2() throws IOException {
// InputStream是抽象类
InputStream is = new FileInputStream("d:/aaa.txt");
//丢弃前两个字节
is.skip(2);
System.out.println((char)is.read());
System.out.println("还剩下多少个字节:"+ is.available());
// 将后面的字节继续使用字节数组读
byte [] b = new byte[10];
int len = is.read(b,1,4);
// 显示数组中读取的所有数据
System.out.println(Arrays.toString(b));
//将数组的内容转成字符串 对于空内容不会转换
System.out.println(new String(b));
is.close();
}
文件写出: 将内存的数据写出到磁盘中
构造方法:
new FileOutputStream(File/String ) : 构造文件对象的写出流, 默认覆盖写出
new FileOutputStream(File/String , append): 构造文件对象的写出流,
append:表示在原有文件上追加数据, false : 覆盖
常用方法:
void write(int) : 写出一个字节
void writer(byte []) :写出一个字节数组,这里需要指定数组的编码格式 “UTF-8”
void writer(byte[] , offerset,len) : 写出一个字节数组,指定数组的长度和下标。 从数组的下标开始写出,len表示写出长度
flush() :清空缓存,对于使用缓冲流时,将缓冲强制清空。
//将内存的数据写出到文件 如果文件不存在,会自动创建, 默认覆盖写入 true:追加
FileOutputStream fos = new FileOutputStream("d://aaa.txt" ,true);
String str="今天天气还不错";
fos.write(99);
//写出一个字符串 字符串可以转成字节数组 或字符数组
fos.write(str.getBytes("UTF-8"));
// 写出指定长度
fos.write(str.getBytes("UTF-8"),0,3); // 写出这个数组的前2个字节
// 清空缓存
fos.flush();
// 关闭流
fos.close();
System.out.println("写出成功");
文件复制:
将文件(图片,文本,视频)从一个目录复制到另一个目录, 其中数据长度不变,通过文件读写的方式完成复制
复制过程:从源文件读取数据,然后将数据再出到目标文件中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZtM1n7Yw-1604826439088)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/copy.png)]
/**
* 单个字节复制
* @param srcFile 源文件
* @param disFile 目标文件
*/
public static void copyFile(File srcFile, File disFile){
FileInputStream fis=null;
FileOutputStream fos =null;
try {
// 源文件输入流
fis = new FileInputStream(srcFile);
// 目标文件输出流
fos = new FileOutputStream(disFile);
int n=0;
while( (n =fis.read()) !=-1){
//将读到的n写出到 目标文件中
fos.write(n);
}
System.out.println("复制成功。。");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
//无论是否发生异常 都会关闭流
try {
fos.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 一个字节数组的赋值
* @param src 源地址
* @param disc 目标地址
*/
public static void copyFile(String src,String disc){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//创建 字节输入流
fis=new FileInputStream(src);
fos = new FileOutputStream(disc);
int len=0;
byte [] b = new byte[1024];
while( (len= fis.read(b)) !=-1){
// 写出 实际读取的长度 ,为了避免在最后一次写出时出现多余字节
fos.write(b,0,len);
}
System.out.println("复制成功");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
fos.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
练习一: 将d://mywork目录下的文件 赋值到 e://mywork
// 其中 目录下包含子目录 也按照相同的文件目录复制
// 思路: 1、先遍历文件夹,递归调用,
// 如果是文件先获取文件的父路径,如果不存在,就创建
// 如果存在路径就直接复制文件(调用刚才的赋值方法)
// 2、 创建父路径使用 mkdirs()
// 例如 d://mywork/aaa/1.txt
// 目标: e://mywork/aaa/1.txt 可以使用字符串替换 d:替换e:
// 先看这个路径是否存在,如果不存在 就创建
字符流
字符流用于读写存储字符的文件, 以一个字符为单位,一依次读取字符 文件, 常用类以 Reader或Writer为父类, 对文件的操作使用 java.io.FileReader 和java.io.FileWriter
读文件 :FileReader
常用方法:
new FileReader(path): 通过文件路径构建字符输入流
new FileReader(File):通过文件对象构建字符输入流
- int read() :读取一个字符 ,返回字符的int类型
- int read(char ):读取字符数组长度的数据 ,返回实际读取字符长度,数据存放在字符数组中
- int read(char offerset len):读取指定字符长度的数组,返回 实际读取字符的长度,数据存放在字符数组中
- mark(int) :标记流中当前位置 (读取到哪里了)
- markSupported():判断此流是否支持mark操作
- reset(): 重置流数据,(又可从头开始读取)
- skip(long) :丢弃指定长度字符
读字符文件
// 1、创建字符输入流
try {
FileReader reader = new FileReader("d:/myfile.txt");
// 丢弃字符
reader.skip(1);
//读一个字符
System.out.println((char)reader.read());
System.out.println((char)reader.read());
//读一个字符数组长度
char [] c = new char[10];
System.out.println("实际长度:"+reader.read(c));
System.out.println(new String(c));
//继续读
int len = reader.read(c,0,5);
System.out.println("字符数组:"+ Arrays.toString(c));
System.out.println("读指定长度字符个数:"+new String(c,0,len));
// 将字符流重置
// reader.reset();
// System.out.println("重置后继续读:"+ reader.read());
//System.out.println("是否支持标记字符:"+reader.markSupported());
//关闭流,后面就不能使用该对象
reader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
写文件: 将内存数据写出到文件中,在写出过程中可以 覆盖写出也可以追加写出,FileWriter类创建对象过程
new FileWriter(String ):指定写出文件地址
new FileWriter(String ,append) : 指定写出文件地址,设置是否追加写出,true表示追加,false表示覆盖
new FileWriter(File)指定写出文件对象
new FileWriter(File ,append);指向写出文件对象,设置是否可追加
常用方法:
writer(int) :写出一个字符
writer(String):写出一个字符串
writer(char [] ):写出一个字符数组
writer(char [] , offerset , len):写出一个指定长度的字符数组
flush() :刷新缓冲,
close():关闭缓冲
append© :将指定字符添加到此流中
// 1、创建文件写出流 FileWriter
try {
// 文件不存在,可自动创建,但是不会创建目录
File file = new File("d://myabc/aaa.txt");
//判断文件目录不存在, 先创建目录
if(!file.getParentFile().exists()){
//创建该目录
file.getParentFile().mkdirs();
}
FileWriter writer = new FileWriter("d://myabc/aaa.txt");
// 写一个字符的asci
writer.write(98);
//写字符串
writer.write("hello");
//写指定长度的字符串
writer.write("abcdef",0,3); //写abc
char [] c = {'L','O','L'};
//写字符数组
writer.write(c);
System.out.println("写出成功");
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
关闭和刷新:
对于带有缓冲 功能的写出流,需要先刷新缓冲区,才能将数据写出,如果不刷新则最后不能正常写出。写出流如果刷新后还可以继续写,而关闭了则不能继续写。
面试题 flush 和close的区别?
flush: 刷新缓冲 ,流可以继续使用
close: 先刷新缓冲器,然后再释放系统资源, 关闭后不能继续使用
try {
FileWriter writer = new FileWriter("1.txt");
writer.write("刷");
writer.flush();
writer.write("新");
writer.flush();
writer.write("关");
writer.close();
writer.write("闭"); // 这里抛出异常 , Stream closed
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
关于换行符
回车符 \r 和换行符 \n :
回车符:回到一行的开头(return)。
换行符:下一行(newline)。
系统中的换行:
Windows系统里,每行结尾是 回车+换行 ,即 \r\n ;
Unix系统里,每行结尾只有 换行 ,即 \n ;
Mac系统里,每行结尾是 回车 ,即 \r 。从 Mac OS X开始与Linux统一。
一、包装流
定义: 在原始字节流或字符流的基础性,为了提高读写效率进行再次处理的流, 称为包装流/处理流
1、缓存字节流 BufferedInputStream 、BufferedOutputStream
由于原始流在文件读写时 效率比较低(操作文件本身占用资源较多),可以通过创建缓冲区的方式提高读写效率, 将读取/写出的数据线放入缓冲区,到达一定数量后再次冲缓冲区读取/写出
mark(readLimit) 与 reset()用法
其中reset不能单独使用,必须mark(readLimit) ,readLimit表示标记后最多读取的上限,但是这里标记后读取的内容与BufferedInputStream的缓冲大小有关,比由上限决定,也就是说读取的内容超出上限可以继续重置到mark的位置。
public static void main(String[] args) throws IOException {
//创建缓冲流
InputStream is = new FileInputStream("d:/myfile.txt");
BufferedInputStream bis = new BufferedInputStream(is);
//是否支持mark 或 reset
System.out.println(bis.markSupported());
System.out.println((char)bis.read());//97
//重置
bis.mark(3); // pos标记往后退三个 最多可以读取字节上限
System.out.println("再次读取:"+(char)bis.read());
System.out.println("再次读取:"+(char)bis.read());
System.out.println("再次读取:"+(char)bis.read());
System.out.println("再次读取:"+(char)bis.read());
bis.reset(); // 这里 重置后 退回到3个以前的位置
// 重置后输出
int n =0;
while( (n = bis.read()) !=-1){
System.out.println("重置后;"+(char)n);
}
//关闭流
bis.close();
is.close();
}
2、缓存字符流 (BufferedReader 、BufferedWriter)
public static void main(String[] args) throws IOException {
// 缓冲字符流 可以一行一行读取 、写出
BufferedReader br = new BufferedReader(new FileReader("d:/小众网站.txt"));
//读一行
// System.out.println(br.readLine());
// System.out.println(br.readLine());
// System.out.println(br.readLine());
String s = null; //读的数据为空 则不需要读
while( (s = br.readLine()) !=null){
System.out.println(s);
}
br.close();
//缓冲写出流
FileOutputStream pw = new FileOutputStream("d:/abcd.txt");
//由于字节流不能直接放入 字符缓冲区,需要将它转成字符流 使用转换流并可以指定编码格式
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(pw));
bw.newLine();// 开启新一行(换行)
bw.write("这是测试转换流的方式");
bw.close();
}
3、打印流(输出流) PrintWriter 、PrintStream
public static void main(String[] args) throws FileNotFoundException {
// 打印流 ,提供一些打印输出方法
PrintWriter pw = new PrintWriter("d:/abcd.txt ");
pw.print(100);
pw.println('a');//换行打印
pw.print("hello");
pw.close();
//System.out 字节打印流 PrintStream
4、数据字节流DataInputStream、DataOutputStream
它们用于读入写出Java基本数据类型的数据到文件或其他设备端,它们也属于包装流
DataOutputStream 常用方法
- writerByte(byte):写一个字节到设备或文件
- writerChar(char):写一个字符到设备或文件
- writerInt(int):写一个4个字节的int到设备或文件
- writer(boolean):写一个boolean类型到设备或文件
- writerDouble(double):写一个double类型到设备或文件
- writerFloat(float):写一个float类型到设备或文件
- writerLong(long):写一个long类型到设备或文件
- writerShort(short):写一个short类型到设备或文件
- writerUTF(String):写一个字符串类型到设备或文件
DataInputStream: 读指定文件的数据,可以读数据类型
- int readInt() :读一个int类型
- short readShort():读一个short类型
- readByte():读一个字节类型
- read():读一个字节类型
- readDouble(): 读一个double类型
- readFloat():读一个float类型
- readChar():读一个字符类型
- readBoolean():读一个boolean类型
- readLong() :读一个long类型
public static void main(String[] args) throws IOException {
//创建数据写出流
DataOutputStream dos = new DataOutputStream(
new FileOutputStream("d:/data.txt"));
//写一个int类型 依次写出4个字节
dos.writeInt(100);
dos.writeBoolean(true);
//关闭
dos.close();
//读取文件 创建数据读入流 ,需要按写的顺序读进来
DataInputStream dis = new DataInputStream(
new FileInputStream("d:/data.txt"));
//读一个int类型 (依次读4个字节)
int num = dis.readInt();
System.out.println("读取的数据:"+ num);
System.out.println("读的数据:"+dis.readBoolean());
dis.close();
}
5、转换流
转换流是将字节流转成字符流的桥梁, 也可以在转换时指定编码格式。 InputStreamReader 和 OutputStreamWriter
public static void main(String[] args) throws IOException {
// 字节流转成字符流
InputStream is = new FileInputStream("d://小众网站.txt");
InputStreamReader isr = new InputStreamReader(is);
//缓冲流 读取数据
BufferedReader br = new BufferedReader(isr);
//读一行
String str =null;
while( (str= br.readLine()) !=null){
System.out.println(str);
}
//关闭流
br.close();
isr.close();
is.close();
}
public static void main(String[] args) throws IOException {
// 创建 字节转成字符的 写出流 FileOutputStream os =
FileOutputStream fos = new FileOutputStream("d://data.txt");
//指定编码 GBK 格式一个汉字占2个字节 UTF-8 格式一个汉字占3个字节
OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
//缓冲形式的
BufferedWriter bw = new BufferedWriter(osw);
bw.write("你好");
bw.newLine();
bw.write("我不好");
bw.close();
}
6、随机字节流
RandomAccessFile 是随机字节流,它是一个可读可写的流 ,在文件操作时指定该对象的模式(model)后,可以读数据或写数据
实现 DataInputStream和DataOutputStream类
构造器:
RandomAccessFile rm = new RandomAccessFile(File ,mode);
RandomAccessFile rm = new RandomAccessFile(String ,mode);
mode表示对象的模式
r: 表示该对象只能读 不能写
rw/rws/rwd :表示该 对象是可读可写的;
public static void main(String[] args) throws IOException {
//创建可读 的流
RandomAccessFile reader = new RandomAccessFile("d://data.txt","r");
//创建可读可写的 的流
RandomAccessFile writer = new RandomAccessFile("d://data-1.txt","rw");
// 读和写的过程和之前一样
byte [] b= new byte[10];
int len=0;
while( (len = reader.read(b)) !=-1){
writer.write(b , 0 , len);
}
System.out.println("复制成功");
//关闭流
writer.close();
reader.close();
}
skipByte 和 seek的区别
// 跳字节读取
RandomAccessFile raf = new RandomAccessFile("d:/data.txt","rw");
// 跳过2个字节
raf.skipBytes(2);
System.out.println((char)raf.readByte()); //3
System.out.println("当前偏移量:"+raf.getFilePointer());//3
// 又丢弃1个字节 从当前位置 往后偏移1位
raf.skipBytes(1);
System.out.println("修改后的偏移量"+raf.getFilePointer());//4
System.out.println("偏移后的读取数据:"+(char)raf.readByte()); //5
raf.close();
// seek用法
RandomAccessFile raf2 = new RandomAccessFile("d:/data.txt","rw");
// 设置当前读取的位置 ,从0开始计算 ,指定n ,就从n的下一个字节 读取
raf2.seek(2);
System.out.println("seek后的数据:"+(char)raf2.readByte());//3
raf2.seek(1); // 又从0开始 设置偏移量为1
System.out.println("修改后的偏移量"+raf.getFilePointer());//1
System.out.println("seek后的数据:"+(char)raf2.readByte())//2
raf2.close();
7、对象序列化流
对象流也称为序列化流,用于存储对象和读取对象的字节流,也是属于包装流
序列化和反序列化
将内存中的对象(Object,集合类等)保存到磁盘、网络介质、其他设置的过程,并在合适的时间能获取磁盘文件/网络的数据 ,这个过程就是对象的序列化和反序列化。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BnOd5IaL-1604826439089)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/44.png)]
为什么需要序列化和反序列化呢?
在之前文件中存储的文本信息,这样不便于对数据的分类和操作,如果可以做到直接对对象的读和写这样可大大提高编程效率,并最大程度保证对象的完整性。
Java-IO中实现对象序列化的两种方式:
-
实现Serializable接口
-
实现Externalizable接口
Serializable接口
对象需要实现该接口,但是它没有任何需要实现的方法,只有一个用于标记该类可序列化的唯一标识。 任何类需要序列化都必须标记该变量
public class User implements Serializable {
// 对于能区分其他类的唯一表示
private static final long serialVersionUID = 1L;
private int uid;
private String name;
private String password;
// 有一部分属性不能序列化
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"uid=" + uid +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
//创建序列化的对象流 从内存到文件
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("d:/user.txt"));
User user= new User();
user.setUid(1001);
user.setName("admin");
user.setPassword("123456");
//序列化对象
oos.writeObject(user);
//关闭流
oos.close();
// 反序列化: 将文件中的数据 再读入到内存中 ,需要一个读的流 ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d://user.txt"));
// 反序列化尽量只读一次 (也可以读多次, 如何写出就如何读入)
Object obj = ois.readObject();
if(obj instanceof User){
User u = (User)obj;
System.out.println("反序列化的结果:"+u);
}
//关闭流
ois.close();
问题: 能否自定义序列化的属性 ,这里可以采用方式二,实现Externalizable,并重写两个方法 接口继承而来,在其基础上新增了两个未实现方法:readExternal(ObjectInputStream)和 writeExternal(ObjectOutputStreawm) ,自定义需要序列化的属性
public interface Externalizable extends java.io.Serializable
Externalizable接口
public class Student implements Externalizable {
private int id;
private String name;
private String sex;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
// 自定义可序列化的属性
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(this.id);
out.writeUTF(this.name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.id = in.readInt();
this.name = in.readUTF();
}
public Student(int id, String name, String sex) {
this.id = id;
this.name = name;
this.sex = sex;
}
public Student( ) {
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建序列化类
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("d:/stu.txt"));
//创建学生
List<Student> list = new ArrayList<>();
list.add(new Student(1001,"张飞","男"));
list.add(new Student(1002,"刘备","男"));
list.add(new Student(1003,"小乔","女"));
// 将集合序列化
oos.writeObject(list);
//关闭
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("d:/stu.txt"));
//读
Object obj = ois.readObject();
if(obj instanceof List){
List<Student> list2 = (List<Student>)obj;
for(Student s : list2){
System.out.println(s);
}
}
//关闭流
ois.close();
}
问题: 哪些属性不能实现序列化
1、类中的static修饰的属性不能序列化
2、类中属性被transient修饰的不能序列化 例如 transient private Integer age = null;
3、实现Externalizable接口的类的属性不能全部序列化,必须手动写可序列化的属性。
一、文件的压缩流和解压流
1、为什么需要使用压缩文件
文件压缩使用场景: 在文件上传或下载中需要操作多个文件时,如果一个一个复制需要花较长时间,而且比较繁琐,javaAPI提供一种压缩/解压文件方式,可以将多个文件打包成一个文件(.zip)
包: java.util.zip
常用类: ZipEntry: 表示压缩文件中的每一个实体文件
ZipFile: 表示压缩文件对象
ZipOutputStream: 表示压缩文件输出流,用于将普通文件写出到zip文件中
ZipInputStream: 表示解压文件的输入流,用于读zip文件中的每一个实体ZipEntry
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PIO8va51-1604826439091)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/4.png)]
这里的abcd.txt就是一个ZipEntry
2、压缩文件步骤
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ka2i1uHc-1604826439093)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/%E5%8E%8B%E7%BC%A9.png)]
a、创建需要压缩文件的输入流(InputStream )
b、创建压缩包所在的路径,并指定压缩文件名,同时创建ZipOutputStream输出流
c、将文件对象 添加到 ZipOutputStream中的实体中(也可以指定压缩后的实体名称)
d、文件复制
e、关闭流
public static void main(String[] args) throws IOException {
// 1、创建文件对象
File file = new File("d:/小众网站.txt");
// 2、创建文件的输入流
FileInputStream fis = new FileInputStream(file);
// 3、创建文件压缩流(输出流)
ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream("d:/myfile.zip"));
// 给压缩包中添加文件,并可自定义文件名
zos.putNextEntry(new ZipEntry("小众网站.txt "));
// 给压缩包设置注释
zos.setComment("这是压缩包的注释。。。。");
// 文件复制
int len = 0;
byte [] b = new byte[1024];
while( (len = fis.read(b)) !=-1){
zos.write(b,0,len);
}
System.out.println("文件压缩成功");
zos.close();
fis.close();
}
压缩多个文件
/**
* 压缩一个文件夹 myfile
* @param args
*/
public static void main(String[] args) throws IOException {
//构建压缩包的输出流
ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream("d:/myfile.zip"));
File file=new File("d:/myfile");
File [] files = file.listFiles();
for(File f : files){
//构造每一个文件的输入流
FileInputStream fis = new FileInputStream(f);
putZipFile(fis, zos ,f.getName());
System.out.println(f.getName()+"文件压缩成功" );
}
//关闭压缩流
zos.flush();
zos.close();
}
/**
* 将文件放入压缩流中
* @param fis
* @param zos
* @param entryName
* @throws IOException
*/
public static void putZipFile(InputStream fis ,
ZipOutputStream zos,
String entryName) throws IOException {
// 给压缩包中添加文件,并可自定义文件名
zos.putNextEntry(new ZipEntry(entryName));
// 给压缩包设置注释
zos.setComment("这是压缩包的注释。。。。");
// 文件复制
int len = 0;
byte [] b = new byte[1024];
while( (len = fis.read(b)) !=-1){
zos.write(b,0,len);
}
System.out.println("文件压缩成功");
fis.close();
}
3、解压文件步骤
解压文件是将一个.zip文件的内容,复制到文件下,需要使用ZipInputStream
解压文件的关键点: 获取解压文件的每一个条目ZipEntry的输入流 ,将输入流写出到指定位置。
如何获取输入流: ZipFile对象 表示一个zip文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PbY7tIlj-1604826439094)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1604460262339.png)]
步骤:
a、根据文件路径 创建ZipInputStream
b、根据文件路径创建ZipFile对象
c、循环遍历每一天条目, 得到它的ZipEntry
d、获取ZipEntry的输入流
e、将文件复制到指定位置
public static void main(String[] args) throws IOException {
//1、创建ZipInputStream
ZipInputStream zis = new ZipInputStream(
new FileInputStream("d:/myfile.zip"));
// 2、创建ZipFile对象
ZipFile zipFile = new ZipFile("d:/myfile.zip");
// 3、获取zip中的实体
ZipEntry en = null;
while( (en= zis.getNextEntry())!=null){
System.out.println(en.getName()+"--"+en.isDirectory());
//4、获取每一个en的输入流 (关键)
InputStream is = zipFile.getInputStream(en);
copyFile(is ,"d:/my",en.getName());
}
}
/**
* 通过输入流 复制文件到指定的目录下
* @param is 输入流
* @param path 存放文件的路径
* @param fileName 文件名
*/
public static void copyFile(InputStream is , String path , String fileName) throws IOException {
File file = new File(path);
//判断目录是否存在, 不存在就 创建
if(!file.exists()){
file.mkdirs();
}
FileOutputStream fos = new FileOutputStream(path+File.separator+fileName);
int len = 0 ;
byte [] b = new byte[1024];
while( (len = is.read(b)) !=-1){
fos.write(b,0,len);
}
System.out.println("解压成功");
fos.close();
is.close();
}
二、Java的多线程
1、线程的基本概念
1.1 定义
引入线程: 打开计算中的任务管理器,有很多条目,每一条目对应一个应用程序,这个应用程序我们称之为 “进程” ,每一个进程都占用CPU资源和内存, 在这一个进程中 包含多个任务,他们可以“同时”运行, 这里的每一个任务称为”线程“
如果将Java的 应用程序比作一个进程,那么它包含的多个执行流程就是一个 线程。
生活中的多线程: 你现在正在玩游戏 ,你可以一边聊天(互喷),你也可以操控游戏,还可以问候队友。玩游戏就是一个进程,你的不同的操作对于游戏本身就是一个单线程,如果你可以同时操作,就是游戏可支持 多线程。
进程:进程是计算机中独立的应用程序,进程是动态,可运行的
线程:在进程中运行的单一任务,是进程的子程序
程序: 程序是数据描述和操作代码的集合,它是完成某个功能的代码,它是静态的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ZGu1uxP-1604826439095)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1604477167683.png)]
多线程: 一个进程中的多个子任务, 在多线程中会出现资源抢占问题, 在单核CPU下同一时间点某个进程下只能有一个线程运行。线程与线程之间会互抢资源
CPU资源分配
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vPWiSmVA-1604826439098)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1604477391246.png)]
电脑可以运行多个应用程序(多进程),在同一时间片,CPU只能执行某一个任务,由于时间片切换非常快,你根本不能察觉会出现“等待”的情况,如果电脑出现 “卡死” 你可以任务资源没有获取并正在等待中。
单线程运行流程:程序只有一条运行线路,从开始到结束保持一致
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mOigbwtR-1604826439102)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1604478443410.png)]
多线程:可以有多条结束任务,对于那一条先结束无法预知
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gaJ4mWic-1604826439104)(D:/%E8%AF%BE%E7%A8%8B/J2008/%E7%AC%94%E8%AE%B0/assets/1604478625751.png)]
如何创建多线程的程序呢?
方式一: 继承Thread类
a、 定义一个类继承Thread类,重写run方法
b、创建该类的对象, 并调用start方法
public class MyThread extends Thread {
@Override
public void run() {
for(int i=0;i<100;i++){
//获取当前线程名
System.out.println(this.getName()+"----"+i);
}
}
}
public static void main(String[] args) {
// 创建线程对象
MyThread my = new MyThread();
//开启线程
my.start();
for(int i = 0 ;i < 100 ;i ++){
System.out.println("主线程的 i-----"+i);
}
// 结论: 对于多线程之间它们的执行过程会存在资源抢占,谁先获得cpu资源,谁就执行
}
方式二:实现Runnable接口
a、创建一个类实现一个接口
public class MyThread2 implements Runnable {
@Override
public void run() {
for(int i = 0;i<100;i++){
//获取当前 线程的线程名
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
b、借助Thread类开启线程
public static void main(String[] args) {
// 由于 MyThread2 与线程无关联,需要借助线程类完成启动
// 创建线程需要执行的任务类
MyThread2 my = new MyThread2();
Thread th = new Thread(my,"线程A");
th.start();
//再启动一个
Thread th2 = new Thread(my,"线程B");
th2.start();
问题:以上两种创建线程的区别?
1、继承方式适用于没有直接父类 ,相对简单 ,是单一继承, 而接口的方式目标类既可以继承类还可以实现其他接口
2、Runnable实现方式适用于 资源共享,线程同步情况。
3、Runnable实现方式并不是线程类,而是实现线程的目标类(Target)
补充: 创建线程并非只有以上两种方式,还可以通过匿名内部的方式创建线程和 线程池的方式。
2、线程的生命周期
生命周期定义
线程从创建到销毁的整个过程,称为线程生命周期, 好比人的生命周期就是从出生到去世的整个过程中间会经历的过程包括 出生,长大,变老,离开 都是一个人要经历的。
生命周期的阶段
1、新生状态 : 程序创建该线程(实例化对象)
2、就绪状态(可运行状态) : 当线程对象调用start()方法后 ,可以抢占cpu资源,但不会立马运行run方法
3、运行状态: 当抢占到资源后,立马运行run方法
4、阻塞状态: 在运行过程中,线程遇到阻塞事件(线程休眠,wait ,IO操作,join操作等),变为阻塞状态
5、死亡状态: 线程运行完毕,或异常中断 ,此时CPU资源被释放
new FileOutputStream("d:/stu.txt"));
//创建学生
List<Student> list = new ArrayList<>();
list.add(new Student(1001,"张飞","男"));
list.add(new Student(1002,"刘备","男"));
list.add(new Student(1003,"小乔","女"));
// 将集合序列化
oos.writeObject(list);
//关闭
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("d:/stu.txt"));
//读
Object obj = ois.readObject();
if(obj instanceof List){
List<Student> list2 = (List<Student>)obj;
for(Student s : list2){
System.out.println(s);
}
}
//关闭流
ois.close();
}
问题: 哪些属性不能实现序列化
1、类中的static修饰的属性不能序列化
2、类中属性被transient修饰的不能序列化 例如 transient private Integer age = null;
3、实现Externalizable接口的类的属性不能全部序列化,必须手动写可序列化的属性。
一、文件的压缩流和解压流
## 1、为什么需要使用压缩文件
文件压缩使用场景: 在文件上传或下载中需要操作多个文件时,如果一个一个复制需要花较长时间,而且比较繁琐,javaAPI提供一种压缩/解压文件方式,可以将多个文件打包成一个文件(.zip)
包: java.util.zip
常用类: ZipEntry: 表示压缩文件中的每一个实体文件
ZipFile: 表示压缩文件对象
ZipOutputStream: 表示压缩文件输出流,用于将普通文件写出到zip文件中
ZipInputStream: 表示解压文件的输入流,用于读zip文件中的每一个实体ZipEntry
[外链图片转存中...(img-PIO8va51-1604826439091)]
这里的abcd.txt就是一个ZipEntry
## 2、压缩文件步骤
[外链图片转存中...(img-Ka2i1uHc-1604826439093)]
a、创建需要压缩文件的输入流(InputStream )
b、创建压缩包所在的路径,并指定压缩文件名,同时创建ZipOutputStream输出流
c、将文件对象 添加到 ZipOutputStream中的实体中(也可以指定压缩后的实体名称)
d、文件复制
e、关闭流
```java
public static void main(String[] args) throws IOException {
// 1、创建文件对象
File file = new File("d:/小众网站.txt");
// 2、创建文件的输入流
FileInputStream fis = new FileInputStream(file);
// 3、创建文件压缩流(输出流)
ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream("d:/myfile.zip"));
// 给压缩包中添加文件,并可自定义文件名
zos.putNextEntry(new ZipEntry("小众网站.txt "));
// 给压缩包设置注释
zos.setComment("这是压缩包的注释。。。。");
// 文件复制
int len = 0;
byte [] b = new byte[1024];
while( (len = fis.read(b)) !=-1){
zos.write(b,0,len);
}
System.out.println("文件压缩成功");
zos.close();
fis.close();
}
压缩多个文件
/**
* 压缩一个文件夹 myfile
* @param args
*/
public static void main(String[] args) throws IOException {
//构建压缩包的输出流
ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream("d:/myfile.zip"));
File file=new File("d:/myfile");
File [] files = file.listFiles();
for(File f : files){
//构造每一个文件的输入流
FileInputStream fis = new FileInputStream(f);
putZipFile(fis, zos ,f.getName());
System.out.println(f.getName()+"文件压缩成功" );
}
//关闭压缩流
zos.flush();
zos.close();
}
/**
* 将文件放入压缩流中
* @param fis
* @param zos
* @param entryName
* @throws IOException
*/
public static void putZipFile(InputStream fis ,
ZipOutputStream zos,
String entryName) throws IOException {
// 给压缩包中添加文件,并可自定义文件名
zos.putNextEntry(new ZipEntry(entryName));
// 给压缩包设置注释
zos.setComment("这是压缩包的注释。。。。");
// 文件复制
int len = 0;
byte [] b = new byte[1024];
while( (len = fis.read(b)) !=-1){
zos.write(b,0,len);
}
System.out.println("文件压缩成功");
fis.close();
}
3、解压文件步骤
解压文件是将一个.zip文件的内容,复制到文件下,需要使用ZipInputStream
解压文件的关键点: 获取解压文件的每一个条目ZipEntry的输入流 ,将输入流写出到指定位置。
如何获取输入流: ZipFile对象 表示一个zip文件
[外链图片转存中…(img-PbY7tIlj-1604826439094)]
步骤:
a、根据文件路径 创建ZipInputStream
b、根据文件路径创建ZipFile对象
c、循环遍历每一天条目, 得到它的ZipEntry
d、获取ZipEntry的输入流
e、将文件复制到指定位置
public static void main(String[] args) throws IOException {
//1、创建ZipInputStream
ZipInputStream zis = new ZipInputStream(
new FileInputStream("d:/myfile.zip"));
// 2、创建ZipFile对象
ZipFile zipFile = new ZipFile("d:/myfile.zip");
// 3、获取zip中的实体
ZipEntry en = null;
while( (en= zis.getNextEntry())!=null){
System.out.println(en.getName()+"--"+en.isDirectory());
//4、获取每一个en的输入流 (关键)
InputStream is = zipFile.getInputStream(en);
copyFile(is ,"d:/my",en.getName());
}
}
/**
* 通过输入流 复制文件到指定的目录下
* @param is 输入流
* @param path 存放文件的路径
* @param fileName 文件名
*/
public static void copyFile(InputStream is , String path , String fileName) throws IOException {
File file = new File(path);
//判断目录是否存在, 不存在就 创建
if(!file.exists()){
file.mkdirs();
}
FileOutputStream fos = new FileOutputStream(path+File.separator+fileName);
int len = 0 ;
byte [] b = new byte[1024];
while( (len = is.read(b)) !=-1){
fos.write(b,0,len);
}
System.out.println("解压成功");
fos.close();
is.close();
}
二、Java的多线程
1、线程的基本概念
1.1 定义
引入线程: 打开计算中的任务管理器,有很多条目,每一条目对应一个应用程序,这个应用程序我们称之为 “进程” ,每一个进程都占用CPU资源和内存, 在这一个进程中 包含多个任务,他们可以“同时”运行, 这里的每一个任务称为”线程“
如果将Java的 应用程序比作一个进程,那么它包含的多个执行流程就是一个 线程。
生活中的多线程: 你现在正在玩游戏 ,你可以一边聊天(互喷),你也可以操控游戏,还可以问候队友。玩游戏就是一个进程,你的不同的操作对于游戏本身就是一个单线程,如果你可以同时操作,就是游戏可支持 多线程。
进程:进程是计算机中独立的应用程序,进程是动态,可运行的
线程:在进程中运行的单一任务,是进程的子程序
程序: 程序是数据描述和操作代码的集合,它是完成某个功能的代码,它是静态的
[外链图片转存中…(img-9ZGu1uxP-1604826439095)]
多线程: 一个进程中的多个子任务, 在多线程中会出现资源抢占问题, 在单核CPU下同一时间点某个进程下只能有一个线程运行。线程与线程之间会互抢资源
CPU资源分配
[外链图片转存中…(img-vPWiSmVA-1604826439098)]
电脑可以运行多个应用程序(多进程),在同一时间片,CPU只能执行某一个任务,由于时间片切换非常快,你根本不能察觉会出现“等待”的情况,如果电脑出现 “卡死” 你可以任务资源没有获取并正在等待中。
单线程运行流程:程序只有一条运行线路,从开始到结束保持一致
[外链图片转存中…(img-mOigbwtR-1604826439102)]
多线程:可以有多条结束任务,对于那一条先结束无法预知
[外链图片转存中…(img-gaJ4mWic-1604826439104)]
如何创建多线程的程序呢?
方式一: 继承Thread类
a、 定义一个类继承Thread类,重写run方法
b、创建该类的对象, 并调用start方法
public class MyThread extends Thread {
@Override
public void run() {
for(int i=0;i<100;i++){
//获取当前线程名
System.out.println(this.getName()+"----"+i);
}
}
}
public static void main(String[] args) {
// 创建线程对象
MyThread my = new MyThread();
//开启线程
my.start();
for(int i = 0 ;i < 100 ;i ++){
System.out.println("主线程的 i-----"+i);
}
// 结论: 对于多线程之间它们的执行过程会存在资源抢占,谁先获得cpu资源,谁就执行
}
方式二:实现Runnable接口
a、创建一个类实现一个接口
public class MyThread2 implements Runnable {
@Override
public void run() {
for(int i = 0;i<100;i++){
//获取当前 线程的线程名
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
b、借助Thread类开启线程
public static void main(String[] args) {
// 由于 MyThread2 与线程无关联,需要借助线程类完成启动
// 创建线程需要执行的任务类
MyThread2 my = new MyThread2();
Thread th = new Thread(my,"线程A");
th.start();
//再启动一个
Thread th2 = new Thread(my,"线程B");
th2.start();
问题:以上两种创建线程的区别?
1、继承方式适用于没有直接父类 ,相对简单 ,是单一继承, 而接口的方式目标类既可以继承类还可以实现其他接口
2、Runnable实现方式适用于 资源共享,线程同步情况。
3、Runnable实现方式并不是线程类,而是实现线程的目标类(Target)
补充: 创建线程并非只有以上两种方式,还可以通过匿名内部的方式创建线程和 线程池的方式。
2、线程的生命周期
生命周期定义
线程从创建到销毁的整个过程,称为线程生命周期, 好比人的生命周期就是从出生到去世的整个过程中间会经历的过程包括 出生,长大,变老,离开 都是一个人要经历的。
生命周期的阶段
1、新生状态 : 程序创建该线程(实例化对象)
2、就绪状态(可运行状态) : 当线程对象调用start()方法后 ,可以抢占cpu资源,但不会立马运行run方法
3、运行状态: 当抢占到资源后,立马运行run方法
4、阻塞状态: 在运行过程中,线程遇到阻塞事件(线程休眠,wait ,IO操作,join操作等),变为阻塞状态
5、死亡状态: 线程运行完毕,或异常中断 ,此时CPU资源被释放