面向对象
(1)两个基本概念:类、对象
(2) 三大特性:封装、继承、多态
简单性(自动内存管理机制,不易造成内存溢出,简化流程处理,语义清晰)
跨平台(操作系统,服务器,数据库)
健壮性
吸收了C/C++语言的优点,但去掉了其影响程序健壮性的部分(如指针、内存的申请与释放等),提供了一个相对安全的内存管理和访问机制
先编译,在解释
将源文件编译成字节码文件(平台中立文件.class),再将字节码文件进行解释执行
同一个源文件可以定义多个类
编译后,每个类都会产生独立的 .class文件
一个类中,只有一个主方法,每个类都可以有自己的主方法
public修饰的类称为公开类,要求类名必须与文件名称完全相同,包括大小写
一个源文件中,只有一个公开的类
提高了代码的阅读性;调试程序的重要方法
注意注释的内容不参与编译,也就是说编译好的字节码文件不包含注释掉的信息
//单行注释
/*多行注释*/
生成外部文档:javadoc -d . HelloWorld.java
/**文档注释*/
注释不参与编译
功能 | 快捷方式 |
---|---|
增加或取消单行注释 | ctrl+/ |
增加或取消多行注释 | ctrl+shift+/ |
文档注释 | 输入/**+enter |
eclipse的快捷键
shift + alt + r :把某个变量名或单词全部替换掉
idea的快捷键
ctrl + alt + l 整理代码格式
ctrl + alt + v 自动补全前面的代码或者加.var
Ctrl + F 在当前文件进行文本查找
Ctrl + R 在当前文件进行文本替换
Ctrl + Z 撤销
Ctrl + Shift + Z 取消撤销
Ctrl + O 选择可重写的方法
Ctrl + / 注释光标所在行代码,会根据当前不同文件类型使用不同的注释符号
Ctrl + Shift + / 代码块注释
Alt + Enter IntelliJ IDEA 根据光标所在问题,提供快速修复选择,光标放在的位置不同提示的结果也不同
Alt + Insert 代码自动生成,如生成对象的 set / get 方法,构造函数,toString() 等
Ctrl + Alt + L 格式化代码,可以对当前文件和整个包目录使用
Ctrl + Alt + O 优化导入的类,可以对当前文件和整个包目录使用
Ctrl + Shift + F 根据输入内容查找整个项目 或 指定目录内文件
Ctrl + Shift + R 根据输入内容替换对应内容,范围为整个项目 或 指定目录内文件
Ctrl + Shift + Enter 自动结束代码,行末自动添加分号
可以由:字母,数字,_,$组成,但不能以数字开头
不能与关键字,保留字重名
不能包含空格
约定俗成:
望文生义,见名知义
类名由一个或多个单词组成,每个单词首字母大写(大驼峰)
函数名,变量名由一个或多个单词组成,首单词首字母小写,拼接词首字母大写(小驼峰)
包名全小写,只可以使用特殊字符".“并且不以”."开头或结尾
常量全大写,多个单词之间有下划线_分隔
程序运行过程中我们经常需要用一个东西来临时计算过程中的数据,这个东西就是变量。
在程序运行的过程中,变量的值可以再一定范围内变化
1.光定义,不赋值
格式:数据类型 变量名;
int num;//光定义一个int类型的变量num,但是还没有进行赋值
2.定义并赋值
格式:数据类型 变量名 = 初始化值;
int num = 1024;
1.变量有其作用范围,它的作用访问是定义他的那行代码所在的大括号内。
2.在同一个大括号中定义的变量名不能重复。
3.变量在使用之前,必须先初始化(赋值)。
4.定义long类型的变量时,需要在整数的后面加L(大小写均可,建议大写)。因为整数默认是int类型,加L相当于告诉计算机这个整数很特殊是long类型的。定义float类型的变量时,需要在小数的后面加F(大小写均可,建议大写)。因为浮点数的默认类型是double, 加F相当于告诉计算机这个小数很特殊是float类型的
整形
类型 | 字节 | 取值范围(二进制) | 取值范围(十进制) |
---|---|---|---|
byte | 1字节 | -2 ^ 7~2 ^7-1 | -128~127 |
short | 2字节 | -2^15 ~2 ^15-1 | -32768~32767 |
int | 4字节 | -2^31~2 ^31-1 | |
long | 8字节 | -2^63 ~2 ^63-1 |
Java中的默认类型:整数类型是int 、浮点类型是double
浮点型
类型 | 字节 | 取值范围 | 取值范围 |
---|---|---|---|
float | 4字节 | ||
double | 8字节 |
long类型:建议数据后加L表示。
float类型:数据后必须加F或者f表示。
基本数据类型(8种):byte、short、int、long、float、double、char、boolean
引用数据类型:类、数组、接口,枚举,注解
byte中最大值为127最小值为-128,整数类型不能是小数。
float类型后面数值必须跟f同理long类型后面必须跟l因为在java中默认的整数为int,小数为double
算术表达式中包含不同的基本数据类型的值的时候,整个算术表达式的类型会自动进行提升。
提升规则:
byte类型,short类型和char类型将被提升到int类型,不管是否有其他类型参与运算。
boolean类型不能与其它数据类型运算
当把任何基本数据类型的值和字符串(String)进行连接运算时(+),基本数据类型的值将自动转化为字符串(String)类型。
整个表达式的类型自动提升到与表达式中最高等级的操作数相同的类型
等级顺序:byte,short,char --> int --> long --> float --> double-->string
public static void main(String[] args){
byte b1=1;
byte b2=2;
byte b3=1 + 2;
byte b4=b1 + b2;
System.out.println(b3);
System.out.println(b4);
}
分析:b3 = 1 + 2 ,1和 2 是常量,为固定不变的数据,在编译的时候(编译器javac),已经确定了1+2 的结果并没有超过byte类型的取值范围,可以赋值给变量b3 ,因此b3=1 + 2是正确的。
反之,b4 = b2 + b3,b2 和 b3 是变量,变量的值是可能变化的,在编译的时候,编译器javac不确定b2+b3的结果是什么,因此会将结果以int类型进行处理,所以int类型不能赋值给byte类型,因此编译失败
自动类型转换:低级别–>高级别
强制类型转换:高级别–>低级别
将取值范围小的类型自动提升为取值范围大的类型
数据绝对安全的类型转换是可以自动转换的直接书写即可
double num = 1024;// int -> double 安全
long l = 1024;//int -> long 安全
double num2 = 3.14F;//float -> double
强制类型转换:高级别–>低级别
将取值范围大的类型强制转换成取值范围小的类型
转换后可能导致出现问题(数据溢出,丢失数据精度)的类型转换叫做强制类型转换。因为可能出现问题必须给计算机一个特殊标识,格式如下:
目标数据类型 变量名 = (目标数据类型)值或者变量;
其实就是常见的数学运行符,不过有些地方有点不同
符号 | 作用 | 不同之处 |
---|---|---|
+ | 加 | 单纯使用数字参与运算的时候和数学中的+没有不同 |
- | 减 | 无 |
* | 乘 | 无 |
/ | 除 | 整数相除只能得到整数,如果想得到小数必须使用小数参与计算 例如: int num = 10/3; //num的值为3而不会是3.333 |
% | 取余 | 获取的是两个数据做整除后得到的余数 int num = 10%3; |
对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分
如果字符参与了运算:
char类型参与算术运算,会把字符转换成对应的数字(参照 ASCII 码表)然后参与运算。
char c = 'A';
System.out.println(c+0); //65
如果字符串参与了+运算:
当“+”操作中出现字符串时,这个”+”是会进行字符串的拼接,而不会进行数据的计算
System.out.println("dyk"+123+"666"); //dyk123666
判断下面代码的执行结果
System.out.println('a'+10+"bc"+'d'+10);//107bcd10
Java中,整数使用以上运算符,无论怎么计算,也不会得到小数
public static void main(String[] args) {
int i = 123456;
System.out.println(i/1000*1000);//计算结果是123000
}
可以用来修改变量的值。赋值运行符左边写要被修改值的变量名,右边写参与计算的值,这个值可以是具体的值也可以是变量。
符号 | 功能 | 举例 |
---|---|---|
= | 赋值 | a=10,将10赋值给变量a |
+= | 加后赋值 | a+=10; //相当于a =(a的数据类型) (a + 10); |
-= | 减后赋值 | a-=b;//相当于a =(a的数据类型) (a - b); |
*= | 乘后赋值 | a*=b;//相当于a =(a的数据类型) (a * b); |
/= | 除后赋值 | a/=b;//相当于a =(a的数据类型) (a / b); |
%= | 取余后赋值 | a%=b;//相当于a =(a的数据类型) (a % b); |
public static void main(String[] args){
short s = 1;
s+=1;
System.out.println(s);
}
分析: s += 1 逻辑上看作是s = s + 1 计算结果被提升为int类型,再向short类型赋值时发生错误,因为不能将取值范围大的类型赋值到取值范围小的类型。但是,s=s+1进行两次运算,+= 是一个运算符,只运算一次,并带有强制转换的特点,也就是说s += 1 就是s = (short)(s + 1),因此程序没有问题编译通过,运行结果是2。
因此,这里要说一点的是,赋值运算符默认进行了强制类型转换
符号 | 作用 | 说明 |
---|---|---|
& | 逻辑与 | 理解成并且的意思,左右两边都要为真才为真 |
| | 逻辑或 | 理解成或者的意思,左右两边只要有一个为真就为真 |
^ | 逻辑异或 | 左右两边不同结果才为真 |
! | 逻辑非 | 取反 |
&& | 短路与 | 作用和&相同,但是有短路效果 |
|| | 短路或 | 作用和|相同,但是有短路效果 |
短路效果:左边表达式的结果确定之后,如果整个表达式的结果已经被确定了,就不会去执行右边的表达式
& 不管前面是不是false都会执行后面
&& 如果前面是false就不会再执行后面的
逻辑与&,无论左边真假,右边都要执行。
短路与&&,如果左边为真,右边执行;如果左边为假,右边不执行。
逻辑或|,无论左边真假,右边都要执行。
短路或||,如果左边为假,右边执行;如果左边为真,右边不执行
public static void main(String[] args) {
int x = 3;
int y = 4;
System.out.println((x++ > 4) & (y++ > 5)); // 两个表达都会运算
System.out.println(x); // 4
System.out.println(y); // 5
System.out.println((x++ > 4) && (y++ > 5)); // 左边结果为false,右边不参与运算
System.out.println(x); // 4
System.out.println(y); // 4
}
符号 | 作用 | 举例 |
---|---|---|
++ | 自增 | a++;//相当于a = a+1; |
– | 自减 | a–;//相当于a = a - 1; |
注意:
++和–可以写在变量的前面也可以写在变量的后面,单独写的时候写前面写后面都没有区别.区别就在和其它运算符一起写.
++写在变量前的时候先自增然后再参与其他运算
++写在变量后面的时候先参与其它运算后自增
++和-- 既可以放在变量的后边,也可以放在变量的前边。
单独使用的时候, ++和-- 无论是放在变量的前边还是后边,结果是一样的。
参与操作的时候,如果放在变量的后边,先拿变量参与操作,后拿变量做++或者--。
参与操作的时候,如果放在变量的前边,先拿变量做++或者--,后拿变量参与操作
int a = 5;
int c = 10;
int b = 20;
int d = ++a+b+++c+++(a+b);
System.out.println(d);//63
布尔表达式?表达式1:表达式2;
先判断布尔表达式的结果,如果结果为true三元表达式的结果就是表达式1的值,否则就是表达式2的值。因为肯定会有一个结果。所以我们在使用的时候需要把这个结果使用(用变量保存,打印等)起来。不然就没有意义。
在程序中我们需用去判断不同的情况做不同的处理,这个时候就需要使用到流程控制语句来处理
if(判断表达式1){
语句体1;
}
if(判断表达式1){
语句体1;
}else if(判断表达式2){
语句体2;
}
if(判断表达式1){
语句体1;
}else{
语句体n+1;
}
if(判断表达式1){
语句体1;
}else if(判断表达式2){
语句体2;
}else if(判断表达式3){
语句体3;
}
if(判断表达式1){
语句体1;
}else if(判断表达式2){
语句体2;
}else if(判断表达式3){
语句体3;
}else if(判断表达式4){
语句体4;
}else{
语句体n+1;
}
条件表达式必须是布尔表达式(关系表达式或逻辑表达式)、布尔变量
语句块只有一条执行语句时,一对{}可以省略,但建议保留
if-else语句结构,根据需要可以嵌套使用
当if-else结构是“多选一”时,最后的else是可选的,根据需要可以省略
当多个条件是“互斥”关系时,条件判断语句及执行语句间顺序无所谓
当多个条件是“包含”关系时,“小上大下 / 子上父下”
int a=10;
int b=20;
if(a>5){
System.out.print(1);
}else if(b>10){
System.out.print(2);
}else{
System.out.print(3);
}
System.out.print(4);
//结果14
switch (表达式) {
case 值1:
语句体1;
break;
case 值2:
语句体2;
break;
case 值3:
语句体3;
break;
...
default:
语句体n+1;
break; // 最后一个break语句可以省略,但是推荐不要省略
}
switch后面小括号当中只能是下列数据类型:
基本数据类型:byte/short/char/int
引用数据类型:String字符串、enum枚举
①switch可以没有default,但是一般都会加上
②case语句后面可以不加break.但是如果不加break就可能会出现case穿透问题.匹配哪一个case就从哪一个位置向下执行,直到遇到了break或者整个switch结束为止;
从上到下依次看表达式的结果和哪个case后面的值相同,相同就执行那个case后面的语句体,碰到break就结束switch.
如果没有符合要求的case则执行default后面的语句体.
switch(表达式)中表达式的值必须是下述几种类型之一:byte,short,char,int,枚举 (jdk 5.0),String (jdk 7.0);
case子句中的值必须是常量,不能是变量名或不确定的表达式值;
同一个switch语句,所有case子句中的常量值互不相同;
break语句用来在执行完一个case分支后使程序跳出switch语句块;如
果没有break,程序会顺序执行到switch结尾
default子句是可任选的。同时,位置也是灵活的。当没有匹配的case时,执行default
if的表达式的布尔表达式,可以进行更复杂条件的判断(例如:值在某个范围内,多个条件同时符合等)而switch的表达式的数据类型只能适合做有限个数的等值判断。所以如果是有限个数的等值判断的话switch有的时候会更方便。其他情况下if会更适合。
如果判断的具体数值不多,而且符合byte、short 、char、int、String、枚举等几种类型。虽然两个语句都可以使用,建议使用swtich语句。
因为效率稍高。
其他情况:对区间判断,对结果为boolean类型判断,使用if,if的使用范围更广。也就是说,使用switch-case的,都可以改写为if-else。反之不成立
当我们需要多次执行完全重复的操作或者是多次执行有规律的操作时,我们就可以用循环语句。
在java中有三种循环语句,分别是for循环,while循环,do…while循环。
while(布尔表达式){
循环体;
}
①看布尔表达式的结果
如果为false循环结束
如果为true,则执行循环体.
②循环体执行完后继续执行①以此类推
do{
循环体;
}while(布尔表达式);
注意:while小括号后面必须有个分号。
①执行循环体
②然后看布尔表达式的结果
如果为true则继续执行①循环体
如果判断表达式的结果为false则循环结束
以此类推
for(初始化语句;布尔表达式(条件判断);步进语句){
循环体;
}
①先执行初始化语句,
②然后看布尔表达式的结果,
如果为false 循环结束,
如果为true 执行循环体.
③循环体执行完后执行步进语句.然后继续执行②判断布尔表达式的结果(PS:注意,不是初始化语句)
然后以此类推.
for循环和while循环先判断条件是否成立,然后决定是否执行循环体(先判断后执行)
do...while循环先执行一次循环体,然后判断条件是否成立,是否继续执行循环体(先执行后判断)
for循环和while的区别 条件控制语句所控制的自增变量,因为归属for循环的语法结构中,在for循环结束后,就不能再次被访问到了
条件控制语句所控制的自增变量,对于while循环来说不归属其语法结构中,在while循环结束后,该变 量还可以继续使用
死循环(无限循环)的三种格式
1. for(;;){}
2. while(true){}
3. do {} while(true);
跳转控制语句(break)
跳出循环,结束循环
跳转控制语句(continue)
跳过本次循环,继续下次循环
注意: continue只能在循环中进行使用!break可以在循环和switch里面使用
循环控制语句主要有两个:break,continue
在循环过程中,碰到break 整个循环 就直接结束了
注意:break只能出现在循环中或者switch中
如果在循环过程中碰到了continue,则 跳过本次循环 , 继续下次循环
我们可以使用数组来保存同一个数据类型的多个数据
①数组的长度一旦确定就不能改变
②一个数组中元素的数据类型都是一样的
数组声明且为数组元素分配空间与赋值的操作分开进行
数据类型[] 数组名 = new 数据类型[长度];
例如:
int[] arr = new int[10];//动态初始化了一个长度为10的数组,数组元素为int类型
在定义数组的同时就为数组元素分配空间并赋值。
标准格式:
数据类型[] 数组名 = new 数据类型[] {元素值1,元素值2,元素值3,...};
例如:
int[] arr = new int[]{1,2,3,4,5};
省略格式(推荐使用):
数据类型[] 数组名 = {元素值1,元素值2,元素值3,...};
int[] arr = {1,2,3,4,5};
静态初始化和动态初始化都可以实现对数组的初始化,那么我们在需要创建数组的时候应该如何选择呢?
如果数组元素都确定好了,并且个数有限的情况下我们可以使用静态初始化,因为更方便。如果只能确定数组的长度,数组的元素值需要后面再赋值的话可以使用动态初始
数组名[索引值]
索引值就是一个int数字,代表数组当中元素的编号。
索引值从0开始,一直到“数组的长度-1”为止。例如长度为5的数组他的索引范围为0到4
在java中我们可以非常方便的获取到数组的长度,格式如下:
数组名.length
例如:
int[] arr = {1,2,3,4};
//输出数组长度
System.out.println(arr.length);
我们在操作数组的时候很多时候需要对数组的每个元素挨个操作一遍,这种情况下我们就可以对数组进行遍历操作
int[] arr={1,2,3,4,5,7};
//遍历输出数组元素
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
创建数组,赋值3个元素,数组的索引就是0,1,2,没有3索引,因此我们不能访问数组中不存在的索引,程序运行后,将会抛出 ArrayIndexOutOfBoundsException 数组越界异常
访问到了数组中的不存在的脚标时发生
public static void main(String[] args) {
int[] arr = {1,2,3};
System.out.println(arr[3]);
}
arr引用没有指向实体,却在操作实体中的元素时。
int[] arr = null;
System.out.println(arr[0]);
二维数组,本质就是在一维数组的基础上,每一个元素又由一个新的数组组成
数据类型[][] 数组名字 = new 数据类型[长度][长度];
int[][] array = new int[2][3];
数据类型[][] 数组名字 = new 数据类型[长度][];
int[][] array = new int[3][];
数据类型[][] 数组名字 = new 数据类型[][]{{元素1},{元素2}...};
数据类型[][] 数组名字 = {{元素1},{元素2}...};
每一行的第一个元素和最后一个元素都是 1
从第三行开始, 对于非第一个元素和最后一个元素的元素的值都是等于正上方的值和正上方左边的值的和
public static void main(String[] args) {
int[][] arr=new int[10][];
for (int i = 0; i < arr.length; i++) {
arr[i]=new int[i+1];
arr[i][0]=1;
arr[i][i]=1;
if(i>1){
for (int j = 1; j < arr[i].length-1; j++) {
arr[i][j]=arr[i-1][j]+arr[i-1][j-1];
}
}
}
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j <arr[i].length ; j++) {
System.out.print(arr[i][j]+" ");
}
System.out.println();
}
}
冒泡排序算法的原理如下:
比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
如果有n个元素,一共需要比较n-1次,第一轮需要比较n-1个每轮比较的个数也每次减1
public static void main(String[] args) {
// 定义一个元素顺序杂乱的数组
int[] arr = { 4, 8, 3, 5, 2, 7};
// 外层循环表示比较轮数
for (int i = 0; i < arr.length-1; i++) {
// 内层循环对比相邻两个数
for (int j = 0; j < arr.length-i-1; j++) {
// 定义一个临时变量
int temp=0;
// 进行元素位置交换
if(arr[j]>arr[j+1]) {
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
方法一
public static void main(String[] args) {
int[] arr=new int[6];
boolean[] ifexist=new boolean[31];
Random random=new Random();
int count=0;
int i=0;
while(true){
int i1 = random.nextInt(30-1+1) + 1;
if(!ifexist[i1]){
arr[i++]=i1;
count++;
ifexist[i1]=true;
}
if(count==6){
break;
}
}
for (int j = 0; j < arr.length; j++) {
System.out.print(arr[j]+" ");
}
}
方法二
public static void main(String[] args) {
int[] arr=new int[6];
Random random = new Random();
for(int i=0;i<arr.length;i++){
arr[i]=random.nextInt(30)+1;
for(int j=0;j<i;j++){
if(arr[i]==arr[j]){
i--;
break;
}
}
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
}
boolean equals(int[] a,int[] b) | 判断两个数组是否相等。 |
String toString(int[] a) | 输出数组信息。 |
void fill(int[] a,int val) | 将指定值填充到数组之中。 |
void sort(int[] a) | 对数组进行排序。 |
nt binarySearch(int[] a,int key) | 对排序后的数组进行二分法检索指定的值。 |
我们在编程过程中有的时候会遇到一些固定套路的代码,我们可以把这些代码封装成方法,这样我们后面去使用的时候就会更加的方便。并且代码也会更简洁,代码复用性更高
修饰符 返回值类型 方法名(参数类型1 参数名1,参数类型2 参数名2,....){
方法体;
return 返回值;
}
修饰符: 目前阶段使用public static 后期学完权限修饰符和static等修饰符后可以灵活使用
参数: 执行方法需要的数据
返回值: 方法执行完后得到的结果
方法体:方法中的代码
返回值类型:返回值执行完后结果的数据类型,如果没有结果就写成void
参数为变量是值的传递,改变形参的值,实参不受影响
参数为数组,传递的是地址(引用传递),形参改变,实参也会改变
基础数据类型作为方法的参数时,改变形参的值,实参不会改变。
引用数据类型作为方法的参数时,改变形参的值,实参值会改变
基本数据类型的参数,形式参数的改变,不影响实际参数
每个方法在栈内存中,都会有独立的栈空间,方法运行结束后就会弹栈消失
对于引用类型的参数,形式参数的改变,影响实际参数的值
引用数据类型的传参,传入的是地址值,内存中会造成两个引用指向同一个内存的效果,所以即使方法 弹栈,堆内存中的数据也已经是改变后的结果
1.返回方法的返回值
2. 结束方法(reutrn执行后整个方法执行结束)
很多个方法可以拥有相同的方法名,但传的参数列表不同
1.方法的名称必须相同
2.列表参数必须不同(参数列表不同:1. 参数类型不同 2.参数个数不同 3.参数顺序不同)
3.方法的返回类型可以相同,也可以不同
仅仅返回类型不同,不足以称作方法的重载
方法重载指同一个类中定义的多个方法之间的关系,满足下列条件的多个方法相互构成重载
多个方法在同一个类中
多个方法具有相同的方法名
多个方法的参数不相同,类型不同或者数量不同
重载仅对应方法的定义,与方法的调用无关,调用方式参照标准格式 重载仅针对同一个类中方法的
名称与参数进行识别,与返回值无关,换句话说不能通过返回值来判定两 个方法是否相互构成重载
与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别
可以列出来简单判断
public class Demo{
public int test(int a,int b){} // int,int
public void test(int a,int b){}//错
public void test(int a,int b,int c){} //int,int,int
public void test(int x,int y){}//错
public void test(int a,double b){}//int,double
public void test(double a,int b){}//double,int
public void test(double a,double b){}//double,double
}
1.一个方法能不能有两个返回值?
答案:不能直接返回,可以把结果放入数组中返回
2.没有返回值的方法中能不能写return?
答案:可以,可以用来结束方法。但是return的后面不能跟数据;
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("a="+a);
System.out.println("b="+b);
change(a,b);
System.out.println("方法调用后a="+a);
System.out.println("方法调用后b="+b);
}
public static void change(int a,int b){
int temp = a;//int temp = 10;
a = b;//a = 20;
b = temp;// b = 10;
}
传进去的形参,只是值的传递,作用域只在当前方法中,方法执行完就消失了,与外面定义的ab无关
a=10
b=20
方法调用后a=10
方法调用后b=20
public static void main(String[] args) {
int[] arr = {1,2};
System.out.println("arr[0]="+arr[0]);
System.out.println("arr[1]="+arr[1]);
change(arr);
System.out.println("方法调用后arr[0]="+arr[0]);
System.out.println("方法调用后arr[1]="+arr[1]);
}
public static void change(int[] arr){
int temp = arr[0];
arr[0] = arr[1];
arr[1] = temp;
}
数组是引用类型其实传进去的是内存中的地址和外面定义的地址指向堆中同一个地方
arr[0]=1
arr[1]=2
方法调用后arr[0]=2
方法调用后arr[1]=1
问题:java中方法调用的时候,形参的改变会不会影响实参?
答:如果方法的参数是基本数据类型,形参的改变不会影响实参。如果方法的参数是引用数据类型,形参的改变会影响实参
形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参
public static void main(String[] args) {
int[] arr = {1,2};
System.out.println("arr[0]="+arr[0]);
System.out.println("arr[1]="+arr[1]);
change(arr);
System.out.println("方法调用后arr[0]="+arr[0]);
System.out.println("方法调用后arr[1]="+arr[1]);
}
public static void change(int[] arr){
arr = new int[2];
arr[0] = 2;
arr[1] = 1;
}
可以画内存图理解下
arr[0]=1
arr[1]=2
方法调用后arr[0]=1
方法调用后arr[1]=2
JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参
//JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a ,String[] books);
//JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String…books);
说明:
类:对一类事物共同点的 描述
对象:对象是某类事物的一个 个体
类仅仅只是描述,我们要指挥事物指挥的肯定是某类事物中的一个个体。我们去指挥或者使用事物的时候肯定是使用其中的具体的个体也就是对象。
1.类是一组属性相同,方法相同的对象的集合,对象是类的具体化
2.对象具有类所有的特征,类拥有的,对象就拥有
3.类与对象是相对的
类:类是对现实生活中一类具有共同属性和行为的事物的抽象
对象:是能够看得到摸的着的真实存在的实体 简单理解:类是对事物的一种描述,对象则为具体存
在的事物
类名 对象名 = new 类名();
Phone phone = new Phone();
Student stu = new Student();
对象名.属性
对象名.方法()
Phone phone = new Phone();
//设置phone的brand属性
phone.brand = "华为";
//打印phone的brand属性
System.out.println(phone.brand);
Phone phone = new Phone();
//使用phone的sendMessage方法
phone.sendMessage();
构造方法的定义格式就是在普通成员方法定义格式的基础上加上了两个强制的要求。
要求:
①没有返回值类型,连void都不能写
②方法名和类名必须相同
③不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值
1.没有返回值,并且方法名与类名相同
2.所有的类都有一个默认的构造方法,用户一旦自己声明构造方法时,原本默认的构造方法失效,构造方法允许的存在多个
public class Phone {
// 成员变量
String brand; // 品牌
double price; // 价格
String color; // 颜色
public Phone(){
//这是一个无参构造
}
public Phone(String color,double price,String brand){
//这是一个有参构造
this.color = color;
this.price = price;
this.brand = brand;
}
}
用来实例化对象,功能:主要是完成对象数据的初始化
构造方法的创建
如果没有定义构造方法,系统将给出一个默认的无参数构造方法 如果定义了构造方法,系统将不再提供默认的构造 方法
构造方法的重载
如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法
推荐的使用方式
无论是否使用,都手工书写无参数构造方法
重要功能
可以使用带参构造,为成员变量进行初始化
如果一个类中没有写构造方法,编译器会默认送我们一个无参构造方法,但是如果自己写了,编译器就不会送了。这种情况下建议自己再加一个无参构造
Java语言中,每个类都至少有一个构造器
默认构造器的修饰符与所属类的修饰符一致
一旦显式定义了构造器,则系统不再提供默认构造器
一个类可以创建多个重载的构造器
父类的构造器不可被子类继承
封装其实就相当于把不需要用户了解细节(隐私或者的特别复杂的细节)包装(隐藏)起来,只对外提供公共访问方式
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想
我们可以使用private来修饰成员变量,提供对应的set/get方法提供刚刚的访问方式
private是一个修饰符,可以用来修饰成员(成员变量,成员方法)
被private修饰的成员,只能在本类进行访问,针对private修饰的成员变量,如果需要被其他类 使用,提供相 应的操作
提供“get变量名()”方法,用于获取成员变量的值,方法用public修饰
提供“set变量名(参数)”方法,用于设置成员变量的值,方法用public修饰
封装概述 是面向对象三大特征之一(封装,继承,多态) 是面向对象编程语言对客观世界的模
拟,客观世界 里成员变量都是隐藏在对象内部的,外界是无法直接操作的
封装原则 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实
现对隐藏 信息的操作和访问 成员变量private,提供对应的getXxx()/setXxx()方法
封装好处 通过方法来控制成员变量的操作,提高了代码的安全性 把代码用方法进行封装,提高了代码的复用性
隐藏一个类中不需要对外提供的实现细节;
使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,
限制对属性的不合理操作;
便于修改,增强代码的可维护性;
public class Phone{
private String brand;
public void setBrand(String brand){
this.brand = brand;
}
public String getBrand(){
return brand;
}
public Phone(String brand){
this.brand = brand;
}
}
this可以用来在局部变量和成员变量重名的时候区分他们,加了this的就是成员变量。
注意:我们只能在一个类的成员方法或者构造方法中去使用this。
this修饰的变量用于指代成员变量,其主要作用是(区分局部变量和成员变量的重名问题)
方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
this代表当前调用方法的引用,哪个对象调用的方法,this就代表哪一个对象
1.this调用本类中的属性,也就是类中的成员变量
2.this调用本类中的方法
3.this调用本类中的构造方法,调用是放在首行。
方法的形参如果与成员变量同名,不带this修饰的变量指的是形参,而不是成员变量
方法的形参没有与成员变量同名,不带this修饰的变量指的是成员变量
在方法体外,类体内声明的变量称为成员变量。
在方法体内部声明的变量称为局部变量。
成员变量
注意:二者在初始化值方面的异同:
同:都有生命周期
异:局部变量除形参外,均需显式初始化。
类中位置不同:
成员变量(类中方法外)
局部变量(方法内部或方法声明上)
内存中位置不同:
成员变量(堆内存)
局部变量(栈内存)
生命周期不同:
成员变量(随着对象的存在而存在,随着对象的消失而消失)
局部变量(随着方法的调用而 存在,醉着方法的调用完毕而消失)
初始化值不同:
成员变量(有默认初始化值)
局部变量(没有默认初始化值,必须先定义,赋值才能使用)
成员变量 | 局部变量 | |
---|---|---|
声明的位置 | 直接声明在类中 | 方法形参或内部、代码块内、构造器内等 |
修饰符 | private、public、static、final等 | 不能用权限修饰符修饰,可以用final修饰 |
初始化值 | 有默认初始化值 | 没有默认初始化值,必须显式赋值,方可使用 |
内存加载位置 | 堆空间 或 静态域内 | 栈空间 |
继承可以理解为就是让两个类(事物)产生从属关系,有了从属关系子类就肯定会具有父类的特征(父类中的非私有成员),这样我们用类去描述一些事物的时候就可以更方便
超类,父类都是同一个概念就是叫法不同
派生类,子类都是同一个概念就是叫法不同
继承就是子类继承父类的特征和行为,使得子类具有父类的相同属性
继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义以及 追加属性和方法
实现继承的格式 继承通过extends实现
格式:
class 子类 extends 父类 { }
public class Dog extends Animal {
//Dog就成为了Animal的一个子类
}
public class Fu {
public void show() {
System.out.println("show方法被调用");
}
}
public class Zi extends Fu {
public void method() {
System.out.println("method方法被调用");
}
}
public class Demo {
public static void main(String[] args) {
//创建对象,调用方法
Fu f = new Fu();
f.show();
Zi z = new Zi();
z.method();
z.show();
}
}
}
继承鼓励类的重用
继承可以多层继承
一个类只能继承一个父类,java只支持单继承
父类中private,default修饰的不能被继承
构造方法不能被继承,只能调用
继承的出现减少了代码冗余,提高了代码的复用性。
继承的出现,更有利于功能的扩展。
继承的出现让类与类之间产生了关系,提供了多态的前提。
继承的好处
继承可以让类与类之间产生关系,子父类关系,产生子父类后,子类则可以使用父类中非私有的成员。
继承好处 提高了代码的复用性(多个类相同的成员可以放到同一个类中) 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
继承弊端
继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削 弱了子类的独立性,违背了`高内聚,低耦合`原则
继承的应用场景: 使用继承,需要考虑类与类之间是否存在is..a的关系,不能盲目使用继承 is..a的关系:谁是谁的一种,例如:老师和学生是人的一种,
那人就是父类,学生和老师就是子类
在子类方法中访问一个变量,采用的是就近原则。
1. 子类局部范围找
2. 子类成员范围找
3. 父类成员范围找
4. 如果都没有就报错(不考虑父亲的父亲…)
构造方法不会继承给子类
子类的构造中必须调用父类的构造并且要求在第一行。
子类的构造默认都会在第一行调用父类的无参构造,所以当父类没有无参构造的时候子类构造中会报错。解决方案是给父类加上无参构造或者在子类构造中显示的调用父类的有参构造。
注意:子类中所有的构造方法默认都会访问父类中无参的构造方法
子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化,
原因在于,每一个子类构造方法的第一条语句默认都是:super()
问题:如果父类中没有无参构造方法,只有带参构造方法,该怎么办呢?
1. 通过使用super关键字去显示的调用父类的带参构造方法
2. 在父类中自己提供一个无参构造方法
1.通过子类对象访问一个方法
3. 子类成员范围找
4. 父类成员范围找
5. 如果都没有就报错(不考虑父亲的父亲…)
Java中类只支持单继承,不支持多继承(接口支持多继承)
错误范例:class A extends B, C { }
Java中类支持多层继承
问:继承的时候构造方法为什么不会被继承?
答: 假设会被继承,这个方法名和子类的类名就肯定不相同,所以不能算是构造方法.所以假设不成立
问:子类的构造方法会默认调用父类的无参构造super().为什么?
答:因为父类中可能有成员变量,并且这个成员变量要继承给子类使用,但是所有的变量在使用之前必须先赋值.而父类的成员变量只能用父类的构造进行默认的初始化。
问:在子类的构造方法中,能不能把父类的构造放到第二行?
答:不能,因为要保证成员变量先初始化了.如果放到第二行,有可能在第一行还没初始化的时候就使用的父类的成员变量
1.super关键字来访问父类成员
2.super只能出现在子类的方法和构造方法中
3.super调用构造方法时,只能是第一句
4.super不能访问父类的privae成员
尤其当子父类出现同名成员时,可以用super表明调用的是父类中的成员
super的追溯不仅限于直接父类
super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识
子类中所有的构造器默认都会访问父类中空参数的构造器
当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)或者super(参数列表)语句指定调用本类或者父类中相应的
构造器。同时,只能”二选一”,且必须放在构造器的首行
如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错
this:代表本类对象的引用
super:代表父类存储空间的标识(可以理解为父类对象引用)
this和super的使用分别
成员变量:
this.成员变量 - 访问本类成员变量
super.成员变量 - 访问父类成员变量
成员方法:
this.成员方法 - 访问本类成员方法
super.成员方法 - 访问父类成员方法
构造方法:
this(…) - 访问本类构造方法
super(…) - 访问父类构造方法
区别点 | this | super |
---|---|---|
访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 直接访问父类中的属性 |
调用方法 | 访问本类中的方法,如果本类没有此方法则从父类中继续查找 | 直接访问父类中的方法 |
调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
当子类拥有父类继承下来的某个功能(成员方法),但是在子类中对这个方法的具体实现和父类不同。这个时候我们在子类中定义了一个和父类方法相同的方法(包括返回值类型,方法名,参数列表) ,这就叫做方法重写
子类出现了和父类中一模一样的方法声明(方法名一样,参数列表也必须一样)
方法重写的应用场景
通过使用super关键字去显示的调用父类的带参构造方法
在父类中自己提供一个无参构造方法
当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了 父类的功能,又定义了子类特有的内容
1. 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
2. 子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
3. 子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
4. 子类不能重写父类中声明为private权限的方法
5. 子类方法抛出的异常不能大于父类被重写方法的异常
3、Override注解
用来检测当前的方法,是否是重写的方法,起到【校验】的作用
我们在重写方法的时候方法的权限修饰符其实可以和父类不同**(一般都相同)。但是子类中方法的权限不能比父类低。(权限修饰符 : private < 默认(什么都不写) < protected < public)**
我们在重写方法的时候方法的返回值类型其实可以不同** (一般都相同)** 。但是要求子类中方法的返回值类型必须是父类方法返回值类型的子类。
我们可以用 @Override 注解来校验是不是方法重写。
私有的方法不能被重写,因为私有的不会被继承
私有方法不能被重写(父类私有成员子类是不能继承的)
子类方法访问权限不能更低(public >protected> 默认 > 私有)
发生在父子关系中,子类覆盖父类方法时,这种方法就叫做重写,要求方法名,参数,都必须一
样,返回值可以类型相同或者是子类,子类权限的修饰符不能比父类小
注意:
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为
static的(不是重写)。因为static方法是属于类的,子类无法覆盖父类的方法
答:
方法重载:在同一个类中,方法名相同,参数列表不同,和返回值无关
方法重写:在子父类中,子类有一个和父类方法名相同,参数列表相同,返回值类型也相同的方法。这个就叫方法的重写
不是私有的无法继承
同一个数据类型的不同对象对同一种行为会有多种不同的实现
要有继承或实现关系 要有方法的重写 要有父类引用指向子类对象
Animal a = new Dog();
Animal b = new Cat();
解读:编译期间会去看左边(父类),看父类有没有这个成员方法。如果没有则直接报错,如果有则编译通
过,不报错。运行期间,实际执行代码,看的是右边(子类),看子类中有没有重写该方法,如果有则执行子类
中的该方法,如果没有则运行父类中的该方法。
成员访问特点
成员变量
编译看父类,运行看父类
或者编译看左边,运行也看左边
成员方法
编译看父类,运行看子类
或者编译看左边,运行看右边(因为方法有重写)
好处
提高程序的扩展性。定义方法时候,使用父类型作为参数,在使用的时候,使用具体的子类型参与操作
弊端
不能使用子类的特有成员
向上转型
父类引用指向子类对象就是向上转型
向下转型
格式:子类型 对象名 = (子类型)父类引用;
向上转型就是子类转父类。因为是绝对安全的所以会自动进行转换。
例如:
Animal a = new Dog();
向上转型就是父类转子类。因为不是绝对安全的所以必须使用强转的方式来进行转换。
例如:
Animal a = new Dog();
Dog d = (Dog)a;
注意:必须是这个子类的对象才可以转换成该子类,否则会出异常
在向下转型的时候为了保证安全我们可以使用instanceof进行类型判断。判断一个对象是否某个类的对象。如果是的话我们再把它转换成该类型,这样会更加安全。
对象 instanceof 类名/接口名
示例:
//判断对象是否是某个类的对象,如果是结果为true,如果不是结果为false
Animal a = new Dog();
if(a instanceof Dog){
//说明a是Dog这个类的对象,我们可以把他强转成Dog类型
Dog d = (Dog)a;
}
对于class的权限修饰只可以用public和default(缺省)
包就是文件夹,用来管理类字节码class文件的
package 包名; (多级包用.分开)
例如:package com.blb.demo;
位置:必须写在源文件的第一行
采用域名倒置的规则:www.baidu.com.cn ->> cn.com.baidu.xxx
自动生成目录结构
带包编译:javac –d . 源文件名称.java
例如:javac -d . HelloWorld.java
带包运行:java 包名+类名
例如:java com.blb.demo.HelloWorld (包名+类名又称全限定名)
使用不同包下的类时,使用的时候要写类的全路径,写起来太麻烦了
为了简化带包的操作,Java就提供了导包的功能
导包的格式
格式:import 包名;
范例:import java.util.Scanner;
static是一个修饰符,被其修饰的成员就属于类了。会被类的所有对象所共享
static的概念 static关键字是静态的意思,可以修饰【成员方法】,【成员变量】
static修饰的特点
被类的所有对象共享,这也是我们判断是否使用静态关键字的条件
可以通过类名调用当然,也可以通过对象名调用【推荐使用类名调用】
1.static关键字用来声明独立于对象的静态变量
2.静态变量也被称作为类变量(归类所有),局部变量不能被声明为static变量
3.static关键字用来声明独立于对象的静态方法,静态方法不能使用非静态变量(非静态变量是属于对象的)
在静态方法中不能访问非静态变量(动态变量)和非静态方法
类所有的方法和属性,它的对象一定会有
无论是成员变量,还是成员方法。用static修饰后我们即可以用对象名来调用也可以用类名来调用。一般都使用类名称进行调用。
静态变量:类名.静态变量名
静态方法:类名.静态方法名(参数)
随着类的加载而加载
优先于对象存在
修饰的成员,被所有对象所共享
访问权限允许时,可不创建对象,直接被类调用
非静态的成员方法
能访问静态的成员变量
能访问非静态的成员变量
能访问静态的成员方法
能访问非静态的成员方法
静态的成员方法
能访问静态的成员变量
能访问静态的成员方法
总结成一句话就是:
静态成员方法只能访问静态成员
1.静态方法中不能直接使用非静态的成员
2.静态方法中不能使用this
静态变量全局唯一,为所有的对象共用,修改它的值,其他对象使用该变量时,值也会改变
非静态变量,每个对象持有一份,是独立的,修改对象的值不会影响其他对象的该值。
因为被static修饰的东西就属于类了,所以可以使用类名来调用
因为被static修饰的东西就属于类了,类的加载优先于对象,所以在静态方法中,不能使用非静态的成员(PS:非静态的成员是属于对象),类出现的时候对象还不存在,所以不能使用还不存在的东西
因为被static修饰的东西就属于类了,而this代表的某个对象,类加载的时候对象还没创建,所以在静态方法中不能使用this,因为对象都没有出现呢
①如果需要共享数据,可以使用static
②如果想要方便调用某些成员,可以使用static修饰(PS:因为就可以直接用类名来调用,不需要创建对象,例如工具类如math类)
final可以修饰类,成员方法,局部变量,成员变量
1.修饰变量时,定义时必须赋值,被修饰的变量不可变,一旦赋了储值就不能重新赋值
可以修饰局部变量,被final修饰的局部变量就变成了常量,赋值之后就**不能被修改**
可以修饰成员变量,被final修饰的成员变量就变成了常量,一旦赋值之后就**不能被修改**.并且必须初始化。有两种初始化方式,一种是直接赋值,
另外一种是要在类的所有的构造方法中对其进行赋值
2.修饰方法 该方法不能被子类重写但是可以被重载
3.修饰类,该类不能被继承,比如math,string类
fianl关键字的作用 final代表最终的意思,可以修饰成员方法,成员变量,类 final修饰类、方法、变量的效果 fianl修饰类:该类不能被继承(不能有子类,但是可以有父类)
final修饰方法:该方法不能被重写
final修饰变量:表明该变量是一个常量,不能再次赋值
fianl修饰基本数据类型变量
final 修饰指的是基本类型的数据值不能发生改变
final修饰引用数据类型变量
final 修饰指的是引用类型的地址值不能发生改变,但是地址里面的内容是可以发生改变的
当我们在做子类共性功能抽取时,有些方法在父类中并没有具体的体现,这个时候就需要抽象类了!
在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类!
在成员方法的返回值类型前加abstract修饰,然后去掉方法的大括号,加上一个分号
public abstract void eat();
在class关键字的前面加上 abstract 修饰。
public abstract class Animal{
}
不能用abstract修饰变量、代码块、构造器;
不能用abstract修饰私有方法、静态方法、final的方法、final的类。
抽象类和抽象方法必须使用 abstract 关键字修饰
public abstract class 类名 {}
public abstract void eat();
抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
抽象类不能实例化
抽象类如何实例化呢?参照多态的方式,通过子类对象实例化,这叫抽象类多态
抽象类的子类
要么重写抽象类中的所有抽象方法
要么是抽象类
成员变量
既可以是变量 也可以是常量
构造方法
空参构造 有参构造
成员方法
抽象方法 普通方法
接口就是一种公共的规范标准,只要符合规范标准,大家都可以通用。
使用interface来定义接口
public interface 接口名{
//定义抽象方法
void method();
}
在要实现接口的类名后面加上implements 接口名。如果要实现多个接口,多个接口名用逗号分开。我们在理解的时候可以把接口理解成是一个特殊的父类
public class 类名 implements 接口名{}
接口用关键字interface修饰
类实现接口用implements表示
public class 类名 implements 接口名 {}
接口如何实例化呢?参照多态的方式,通过实现类对象实例化,这叫接口多态。
多态的形式:具体类多态,抽象类多态,接口多态。
接口的子类
要么重写接口中的所有抽象方法
要么子类也是抽象类
Java中的接口更多的体现在对行为的抽象
成员变量
只能是常量 默认修饰符:public static final
构造方法
没有,因为接口主要是扩展功能的,而没有具体存在
成员方法
只能是抽象方法
默认修饰符:public abstract
类与类的关系
继承关系,只能单继承,但是可以多层继承
类与接口的关系
实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
接口与接口的关系
继承关系,可以单继承,也可以多继承
抽象类
变量,常量;有构造方法;有抽象方法,也有非抽象方法
接口
常量;无构造方法,抽象方法
类与类
继承,单继承
类与接口
实现,可以单实现,也可以多实现
接口与接口
继承,单继承,多继承
抽象类
对类抽象,包括属性、行为
接口
对行为抽象,主要是行为
1、类名作为方法的形参
方法的形参是类名,其实需要的是该类的对象
实际传递的是该对象的【地址值】
2、类名作为方法的返回值
方法的返回值是类名,其实返回的是该类的对象
实际传递的,也是该对象的【地址值】
抽象类作为形参和返回值
方法的形参是抽象类名,其实需要的是该抽象类的子类对象
方法的返回值是抽象类名,其实返回的是该抽象类的子类对象
方法的形参是接口名,其实需要的是该接口的实现类对象
方法的返回值是接口名,其实返回的是该接口的实现类对象
多个对象在堆内存中,都有不同的内存划分,成员变量存储在各自的内存区域中,成员方法多个对象共用的 一份
当多个对象的引用指向同一个内存空间(变量所记录的地址值是一样的) 只要有任何一个对象修改了内存中的数据,
随后,无论使用哪一个对象进行数据获取,都是修改后的数据。
在jdk7版本中接口中只能有常量和抽象方法。
我们接口中定义不了成员变量。因为定义的成员变量默认都会修饰为:public static final
我们在接口中定义的方法默认修饰符为public abstract
public interface InterfaceA {
//常量
int NUM =10;//等价于public static final int NUM =10;
//抽象方法
void method();//等价于 public abstract void method();
}
在jdk8中允许我们在接口中定义默认方法。默认方法允许有方法体。
默认方法可以选择不进行重写
使用defaut关键字进行修饰
public interface InterfaceA {
//默认方法
default void method(){
}
}
如果两个接口中有相同的默认方法。一个类同时实现了这两个接口,必须要重写该方法
因为如果不重写那么java也不知道执行那个默认方法
如果在接口升级的时候某些方法并不想让所有的实现类进行重写,可以使用默认方法来定义
例如集合的接口,继承和实现关系非常复杂,如果想给接口新加功能,那么所有的实现了接口的方法都要实现该方法,那都要重写,有了默认方法有方法体,可以不用一定要重写
在jdk8中允许我们在接口中定义静态方法。静态方法允许有方法体。
静态方法不能被重写
使用static关键字进行修饰
public interface InterfaceA {
//静态方法
static void staticMethod(){
}
}
public class Demo {
public static void main(String[] args) {
//使用接口名.方法名(参数) 来调用
InterfaceA.staticMethod();
}
}
如果在接口升级的时候需要给接口增加一些工具方法。不想让实现类去重写该方法,可以使用静态方法来定义
使用private进行修饰。
public interface InterfaceA {
private static void privateStaticMethod(){
}
private void privateMethod(){
}
}
对默认方法或者是静态方法中重复的代码进行抽取,提高代码复用度
在方法中直接写一对大括号即可
public class Demo
{
public void test(){
int a=10;
//下面是一个局部代码块
{
int b=20;
}
}
}
如果需要控制局部变量的生命周期,想让其使用完后尽快销毁,可以把局部变量定义在局部代码块
在类中方法外直接写一对大括号
public class studnet
{
int age;
//下面是一个构造代码块
{
age=0;
}
}
构造代码块会在调用构造方法时候执行,并且会在构造方法的代码会写之前执行
用来抽取构造方法中的重复代码,提高代码的复用性
在类中方法外部直接写一对大括号即可,在括号前使用static修饰
public class studnet
{
static int age;
//下面是一个构造代码块
static{
age=0;
}
}
在类被加载的时候会执行,同一个类在程序运行过程中会被加载一次,且只会执行一次
用来给类中的静态成员进行初始化
非静态代码块:没有static修饰的代码块
静态代码块:用static 修饰的代码块
在一个类中定义一个类。举例:在一个类A的内部定义一个类B,类B就被称为内部类
/* 格式:
class 外部类名{
修饰符 class 内部类名{ }
}
*/
public class Outer {
public class Inner
{
}
}
内部类可以直接访问外部类的成员,包括私有
外部类要访问内部类的成员,必须创建对象
/*内部类访问特点:
内部类可以直接访问外部类的成员,包括私有
外部类要访问内部类的成员,必须创建对象
*/
public class Outer {
private int num = 10;
public class Inner {
public void show() {
System.out.println(num);
}
}
public void method()
{
Inner i = new Inner();
i.show();
}
}
在类中方法,跟成员变量是一个位置
外界创建成员内部类格式
格式:
外部类名.内部类名 对象名 = 外部类对象.内部类对象;
举例:Outer.Inner oi = new Outer().new Inner();
成员内部类的推荐使用方案
将一个类,设计为内部类的目的,大多数都是不想让外界去访问,所以内部类的定义应该私有化,私有 化之后,再提供一个可以让外界调用的方法,方法内部创建内部类对象并调用
class Outer
{
private int num = 10;
private class Inner {
public void show() {
System.out.println(num);
}
}
public void method() {
Inner i = new Inner();
i.show();
}
}
public class InnerDemo {
public static void main(String[] args) {
//Outer.Inner oi = new Outer().new Inner();
//oi.show();
Outer o = new Outer();
o.method();
}
}
public class Outer {
private int s = 111;
public class Inner {
private int s = 222;
public void mb(int s) {
System.out.println(s); // 局部变量s
System.out.println(this.s); // 内部类对象的属性s
System.out.println(Outer.this.s); // 外部类对象属性s
}
}
public static void main(String args[]) {
Outer a = new Outer();
Outer.Inner b = a.new Inner();
b.mb(333);
}
}
1.如果成员类中可以直接使用外部类的成员,即使这个成员是私有的
2.如果外部类的成员和内部类的成员重名了,可以使用外部类名.this.成员名来表示外部类的成员
3.成员内部类中类不能定义静态的成员(非静态中不能使用静态)
一个类不会单独使用,需要和另外的(外部类)一起使用才有意义,并且会用到外部类中的私有成员的时候可以把该类定义为一个成员内部类
局部内部类是在方法中定义的类
局部内部类方式方式
局部内部类,外界是无法直接使用,
需要在方法内部创建对象并使用 该类可以直接访问外部类的成员,也可以访问方法内的局部变量
class Outer {
private int num = 10;
public void method() {
int num2 = 20;
class Inner {
public void show() {
System.out.println(num);
System.out.println(num2);
}
}
Inner i = new Inner();
i.show();
}
}
public class OuterDemo {
public static void main(String[] args)
{
Outer o = new Outer();
o.method();
}
}
内部类可以使用外部的局部变量,但是要求这些变量必须是事实常量
匿名内部类本质是一个对象,它是某个(接口)的子类(实现类对象)
匿名内部类的前提
存在一个类或者接口,这里的类可以是具体类也可以是抽象类
匿名内部类的格式 格式:new 类名 ( ) { 重写方法 } new 接口名 ( ) { 重写方法 }
举例:
new Inter(){
@Override
public void method()
{}
}
匿名内部类的本质
本质:是一个继承了该类或者实现了该接口的子类匿名对象
匿名内部类的细节
匿名内部类可以通过多态的形式接受
Inter i = new Inter(){
@Override
public void method()
{
}
}
interface Inter{
void method();
}
class Test{
public static void main(String[] args){
new Inter(){
@Override
public void method(){
System.out.println("我是匿名内部类");
}
}.method(); // 直接调用方法
}
}
匿名内部类必须继承父类或实现接口
匿名内部类只能有一个对象
匿名内部类对象只能使用多态形式引用
如果要创建一个类或者接口的子类,但这个子类只会使用一次,没必要单独创建一个类,就可以使用匿名内部类的形式
异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”(开发过程中的语法错误和逻辑错误不是异常)
都是Exception类及其子类 必须显示处理,否则程序就会发生错误,无法通过编译
都是RuntimeException类及其子类 无需显示处理,也可以和编译时异常一样处理这个throws格式是跟在方法的括号后面的
编译时异常必须要进行处理,两种处理方案:try…catch …或者 throws,如果采用 throws 这种方案, 将来谁调用谁处理
运行时异常可以不处理,出现问题后,需要我们回来修改代码
ClassCastException
ArrayIndexOutOfBoundsException
NullPointerException
ArithmeticException
NumberFormatException
InputMismatchException
java.io.IOExeption
java.lang.ClassNotFoundException
java.lang.InterruptedException
java.io.FileNotFoundException
java.sql.SQLException
如果程序出现了问题,我们没有做任何处理,最终JVM 会做默认的处理,处理方式有如下两个步骤: 把异常的名称,错误原因及异常出现的位置等信息输出在了控制台 程序停止执行
try
{
可能出现异常的代码;
}
catch(异常类名 变量名)
{ 异常的处理代码;
}
程序从 try 里面的代码开始执行 出现异常,就会直接跳转到对应的 catch 里面去执行 执行完毕之后,程序还可以继续往下执行
一旦try中的异常对象匹配到某一个catch时,进入catch中进行异常处理,一旦处理完成,就跳出当前的try-catch结构继续执行其后的代码
catch中的异常类型如果没有满足子父类关系,则谁声明在上,谁声明在下无所谓
catch中的异常类如果满足子父类关系,则要求子类一定声明在父类的上面,否则报错
try-catch 真正的将异常处理了
throws的方式只是将异常抛出给方法的调用者,并没有真正的将异常处理掉
捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理。
不论在try代码块中是否发生了异常事件,catch语句是否执行,catch语句是否有异常,catch语句中是否有return,finally块中的语句都会被执行。
finally语句和catch语句是任选的
如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
一般地,用户自定义异常类都是RuntimeException的子类。
自定义异常类通常需要编写几个重载的构造器。
自定义异常需要提供serialVersionUID
自定义的异常通过throw抛出。
自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字判断异常类型。
public class ScoreException extends Exception {
public ScoreException() {}
public ScoreException(String message) {
super(message);
}
}
public class Teacher {
public void checkScore(int score) throws ScoreException {
if(score<0 || score>100) {
throw new ScoreException("你给的分数有误,分数应该在0-100之间");
}
else {
System.out.println("成绩正常");
}
}
}
public class Demo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入分数:");
int score = sc.nextInt();
Teacher t = new Teacher();
try {
t.checkScore(score);
} catch (ScoreException e) {
e.printStackTrace();
}
}
}
Random产生随机数
使用步骤:
1、Math类概述 Math 包含执行基本数字运算的方法
2、Math中方法的调用方式 Math类中无构造方法,但内部的方法都是静态的,则可以通过 类名.进行调用
返回参数的绝对值,参数传入一个整数
import java.lang.Math.*;
public class math {
public static void main(String[] args) {
int a=-10;
System.out.println(Math.abs(a));//10
}
}
向上取整,向下取整,返回一个小数值等于一个整数
import java.lang.Math.*;
public class math {
public static void main(String[] args) {
Double a=12.23;
System.out.println(Math.ceil(a)); //13.0 向上取,返回的是double类型
System.out.println(Math.floor(a)); //12.0 向下取,返回的是double类型
}
}
四舍五入,返回一个整数
import java.lang.Math.*;
public class math {
public static void main(String[] args) {
Double a=12.23;
System.out.println(Math.round(a)); //12 返回一个整数
}
}
返回a的b次幂,参数和返回值都是小数
import java.lang.Math.*;
public class math {
public static void main(String[] args) {
System.out.println(Math.pow(2.0,3.0)); //8.0
}
}
返回值为double 产生一个[0.0,1.0)随机数
import java.lang.Math.*;
public class math {
public static void main(String[] args) {
System.out.println(Math.random());//产生一个[0,1)随机数
}
}
BigInteger bi = new BigInteger("12433241123");
BigDecimal bd = new BigDecimal("12435.351");
BigDecimal bd2 = new BigDecimal("11");
System.out.println(bi);
//System.out.println(bd.divide(bd2));//必须要指定方式否则报错
System.out.println(bd.divide(bd2, BigDecimal.ROUND_HALF_UP));
System.out.println(bd.divide(bd2, 15, BigDecimal.ROUND_HALF_UP));
public class SystemDemo {
public static void main(String[] args) {
// 获取开始的时间节点
long start = System.currentTimeMillis();
for (int i = 1; i <= 10000; i++) {
System.out.println(i);
}
// 获取代码运行结束后的时间节点
long end = System.currentTimeMillis();
System.out.println("共耗时:" + (end -start) + "毫秒");
}
}
String javaVersion = System.getProperty("java.version");
System.out.println("java的version:" + javaVersion);
String javaHome = System.getProperty("java.home");
System.out.println("java的home:" + javaHome);
String osName = System.getProperty("os.name");
System.out.println("os的name:" + osName);
String osVersion = System.getProperty("os.version");
System.out.println("os的version:" + osVersion);
String userName = System.getProperty("user.name");
System.out.println("user的name:" + userName);
String userHome = System.getProperty("user.home");
System.out.println("user的home:" + userHome);
String userDir = System.getProperty("user.dir");
System.out.println("user的dir:" + userDir);
Object类概述 Object 是类层次结构的根,每个类都可以将 Object 作为超类。所有类都直接或者间接的继承自该类, 换句话说,该类所具备的方法,所有类都会有一份
equals方法的作用 用于对象之间的比较,返回true和false的结果
举例:s1.equals(s2); s1和s2是两个对象 重写equals方法的场景
不希望比较对象的地址值,想要结合对象属性进行比较的时候。
方法名称 | 类型 | 描述 |
---|---|---|
public Object() | 构造 | 构造器 |
public boolean equals(Object obj) | 普通 | 对象比较 |
public int hashCode() | 普通 | 取得Hash码 |
public String toString() | 普通 | 对象打印时调用 |
取得对象信息,返回该对象的字符串表示。Object的toString方法返回值的是类的全类名和对象hash值的拼接。
内部实现如下:
基本类型比较值:只要两个变量的值相等,即为true。
引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,==才返回true
用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错
所有类都继承了Object,也就获得了equals()方法。还可以重写
只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象
特例:当用equals()方法进行比较时,对类File、String、Date及包装类(Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对象;(原因:在这些类中重写了Object类的equals()方法。)
1 == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
2 equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的,
而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点。
3 具体要看自定义类里有没有重写Object的equals方法来判断。
4 通常情况下,重写equals方法,会比较类中的相应属性是否都相等。
Date now=new Date();
System.out.println(“now=”+now); 相当于
System.out.println(“now=”+now.toString());
s1=“hello”;
System.out.println(s1);//相当于System.out.println(s1.toString());
int a=10;
System.out.println(“a=”+a);
char[] arr = new char[] { 'a', 'b', 'c' };
System.out.println(arr);//
int[] arr1 = new int[] { 1, 2, 3 };
System.out.println(arr1);//
double[] arr2 = new double[] { 1.1, 2.2, 3.3 };
System.out.println(arr2);//
结果
abc
[I@24d46ca6
[D@4517d9a3
String是一个final类,代表不可变的字符序列
字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。
String对象的字符内容是存储在一个字符数组value[]中的
String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例。
也就是说,Java 程序 中所有的双引号字符串,都是 String 类的对象。
String 类在 java.lang 包下,所以使用的时候不需要导包
字符串不可变,它们的值在创建后不能被更改 虽然 String 的值是不可变的,
但是它们可以被共享 字符串效果上相当于字符数组( char[] ),但是底层原理是字节数组( byte[] )
int length():返回字符串的长度: return value.length
char charAt(int index): 返回某索引处的字符return value[index]
boolean isEmpty():判断是否是空字符串:return value.length == 0
String toLowerCase():使用默认语言环境,将 String 中的所有字符转换为小写
String toUpperCase():使用默认语言环境,将 String 中的所有字符转换为大写
String trim():返回字符串的副本,忽略前导空白和尾部空白
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写
String concat(String str):将指定字符串连接到此字符串的结尾。 等价于用“+”
int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回一个新的字符串,它是此字符串的从
beginIndex开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex) :返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
注:indexOf和lastIndexOf方法如果未找到都是返回-1
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。
String replaceAll(String regex, String replacement) : 使 用 给 定 的replacement 替换此字符串所有匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement) : 使 用 给 定 的replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。
boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
//public String():创建一个空白字符串对象,不含有任何内容
String s1 = new String();
System.out.println("s1:" + s1);
//public String(char[] chs):根据字符数组的内容,来创建字符串对象
char[] chs = {'a', 'b', 'c'};
String s2 = new String(chs);
System.out.println("s2:" + s2);
//public String(byte[] bys):根据字节数组的内容,来创建字符串对象
byte[] bys = {97, 98, 99};
String s3 = new String(bys);
System.out.println("s3:" + s3);
//String s = “abc”; 直接赋值的方式创建字符串对象,内容就是abc
String s4 = "abc";
System.out.println("s4:" + s4); } }
public boolean equals(String s) 比较两个字符串内容是否相同、区分大小写
public int length() 返回字符串的长度
public char charAt(int index) 返回索引处的char值
public class StringDemo02 {
public static void main(String[] args) {
//构造方法的方式得到对象
char[] chs = {'a', 'b', 'c'};
String s1 = new String(chs);
String s2 = new String(chs);
//直接赋值的方式得到对象
String s3 = "abc";
String s4 = "abc";
//比较字符串对象地址是否相同
ystem.out.println(s1 == s2); //new出来的地址值不一样 false
System.out.println(s1 == s3); //fasle
System.out.println(s3 == s4);//直接赋值的是在常量池,内容一样地址也一样 true
System.out.println("--------");
//比较字符串内容是否相同
System.out.println(s1.equals(s2)); //内容都相同
System.out.println(s1.equals(s3));
System.out.println(s3.equals(s4));
}
}
public class teststring {
public static void main(String[] args) {
String str="dyk666";
for(int i=0;i<str.length();i++)
{
System.out.println(str.charAt(i));
}
}
}
结论
常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
只要其中有一个是变量,结果就在堆中
如果拼接的结果调用intern()方法,返回值就在常量池中
public class StringTest {
String str = new String("good");
char[] ch = { 't', 'e', 's', 't' };
public void change(String str, char ch[]) {
str = "test ok";
ch[0] = 'b';
}
public static void main(String[] args) {
StringTest ex = new StringTest();
ex.change(ex.str, ex.ch);
System.out.print(ex.str + " and ");//
System.out.println(ex.ch);
}
}
good and best
形参里的str一开始传递的是地址一开始也指向good,由于string是final修饰的不可变,形参的str指向了test ok,并不影响成员变量的str,数组不是不可变的,形参ch和成员变量ch指向堆中同一空间,就会被修改
StringBuilder 是一个可变的字符串类,我们可以把它看成是一个容器,这里的可变指的是 StringBuilder 对象中的 内容是可变的
stringBuilder类和String类的区别
String类:内容是不可变的
StringBuilder类:内容是可变的
import java.util.ArrayList;
import java.util.List;
public class ArrayList01 {
public static void main(String[] args) {
StringBuilder sb=new StringBuilder("hello");
System.out.println(sb);//hellow
StringBuilder sb1= sb.append("world");
System.out.println(sb1==sb);//true append返回对象本身
sb.reverse();
System.out.println(sb);//dlrowwolleh
}
}
Integer包装类的public static int parseInt(String s):可以将由“数字”字符组成的字符串转换为整型
类似地,使用java.lang包中的Byte、Short、Long、Float、Double类调相应的类方法可以将由“数字”字符组成的字符串,转化为相应的基本数据类型
int a=Integer.parseInt("123");
调用String类的public String valueOf(int n)可将int型转换为字符串
相应的valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(double d)、valueOf(boolean b)可由参数的相应类型到字符串的转换
String 类的构造器:String(char[]) 和 String(char[],int offset,intlength) 分别用字符数组中的全部字符和部分字符创建字符串对象
public char[] toCharArray():将字符串中的全部字符存放在一个字符数组中的方法。
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):提供了将指定索引范围内的字符串存放到数组中的方法
String(byte[]):通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
String(byte[],int offset,int length) :用指定的字节数组的一部分,即从数组起始位置offset开始取length个字节构造一个字符串对象
public byte[] getBytes() :使用平台的默认字符集将此 String 编码为byte 序列,并将结果存储到一个新的 byte 数组中。
public byte[] getBytes(String charsetName) :使用指定的字符集将此 String 编码到 byte 序列,并将结果存储到新的 byte 数组。
StringBuffer类不同于String,其对象必须使用构造器生成。有三个构造器
如果知道要频繁添加,建议使用构造器二,以免扩容消耗时间
以构造器三,创建的底层数组初始长度为 str.Length()+16
默认扩容的大小为当前长度乘以2并加2再把原来的内容复制到新的数组里面
StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样,就是加了synchronized修饰
StringBuilder转换为String
public String toString():通过 toString() 就可以实现把 StringBuilder 转换为 String
String转换为StringBuilder
public StringBuilder(String s):通过构造方法就可以实现把 String 转换为 StringBuilder
StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接
StringBuffer delete(int start,int end):删除指定位置的内容
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
StringBuffer insert(int offset, xxx):在指定位置插入xxx
StringBuffer reverse() :把当前字符序列逆转
String(JDK1.0):不可变字符序列
StringBuffer(JDK1.0):可变字符序列、效率低、线程安全
StringBuilder(JDK 5.0):可变字符序列、效率高、线程不安全
注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值
String str = null;
StringBuffer sb = new StringBuffer();
sb.append(str);
System.out.println(sb.length());//4
System.out.println(sb);//null
StringBuffer sb1 = new StringBuffer(str);//报错
System.out.println(sb1);//报错
sb.append(str)的时候把null当字符串添加进去的;
基本类型包装类的作用 将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能方法操作该数据 常用的操作之一:用于基本数据类型与字符串之间的转换
方式一:直接在数字后加一个空字符串
方式二:通过String类静态方法valueOf()
public class IntegerDemo {
public static void main(String[] args) {
//int --- String
int number = 100;
//方式1
String s1 = number + "";
System.out.println(s1);
//方式2
//public static String valueOf(int i)
String s2 = String.valueOf(number);
System.out.println(s2);
System.out.println("--------");
}
}
方式一:先将字符串数字转成Integer,再调用valueOf()方法
方式二:通过Integer静态方法parseInt()进行转换
public class IntegerDemo {
public static void main(String[] args) {
//String --- int
String s = "100";
//方式1:String --- Integer --- int
Integer i = Integer.valueOf(s);
//public int intValue()
int x = i.intValue();
System.out.println(x);
//方式2
//public static int parseInt(String s)
int y = Integer.parseInt(s);
System.out.println(y);
}
}
自动装箱
把基本数据类型转换为对应的包装类类型
自动拆箱
把包装类类型转换为对应的基本数据类型
Integer i = 100; // 自动装箱
i += 200;
// i = i + 200; i + 200 自动拆箱;i = i + 200; 是自动装箱
Date date = new Date();
System.out.println(date);
System.out.println(System.currentTimeMillis());
System.out.println(date.getTime());
Date date1 = new Date(date.getTime());
System.out.println(date1.getTime());
System.out.println(date1.toString());
Tue Sep 28 10:17:27 CST 2021
1632795447584
1632795447521
1632795447521
Tue Sep 28 10:17:27 CST 2021
java.sql.Date转为java.util.Date
java.sql.Date date=new java.sql.Date();
java.util.Date d=new java.util.Date (date.getTime());
或者直接利用多态
java.util.Date d=new java.sql.Date(System.currentTimeMillis());
java.util.Date转为java.sql.Date
java.util.Date utilDate=new Date();
java.sql.Date sqlDate=new java.sql.Date(utilDate.getTime());
java.util.Date utilDate=new Date();
java.sql.Date sqlDate=new java.sql.Date(utilDate.getTime());
java.sql.Time sTime=new java.sql.Time(utilDate.getTime());
java.sql.Timestamp stp=new java.sql.Timestamp(utilDate.getTime());
SimpleDateFormat类概述 SimpleDateFormat是一个具体的类,用于以区域设置敏感的方式格式化和解析日期。 我们重点学习日期格式化和解析
SimpleDateFormat类的常用方法
格式化(从Date到String) public final String format(Date date):将日期格式化成日期/时间字符串
解析(从String到Date) public Date parse(String source):从给定字符串的开始解析文本以生成日期
public class SimpleDateFormatDemo {
public static void main(String[] args) throws ParseException {
//格式化:从 Date 到 String
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
String s = sdf.format(d);
System.out.println(s);
System.out.println("--------");
//从 String 到 Date
String ss = "2048-08-09 11:11:11";
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date dd = sdf2.parse(ss);
System.out.println(dd);
}
}
概述 Calendar 为特定瞬间与一组日历字段之间的转换提供了一些方法,
并为操作日历字段提供了一些方法 Calendar 提供了一个类方法 getInstance 用于获取这种类型的一般有用的对象。 该方法返回一个Calendar 对象。 其日历字段已使用当前日期和时间初始化:
Calendar rightNow =Calendar.getInstance();
获取Calendar实例的方法
使用Calendar.getInstance()方法
调用它的子类GregorianCalendar的构造器。
Calendar类常用方法
一个Calendar的实例是系统时间的抽象表示,通过get(int field)方法来取得想要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY 、MINUTE、SECOND
注意:
获取月份时:一月是0,二月是1,以此类推,12月是11
获取星期时:周日是1,周二是2 , 。。。。周六是7
public class CalendarDemo {
public static void main(String[] args) {
//获取日历类对象
Calendar c = Calendar.getInstance();
//public int get(int field):返回给定日历字段的值
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH) + 1;
int date = c.get(Calendar.DATE);
System.out.println(year + "年" + month + "月" + date + "日");
//public abstract void add(int field, int amount):根据日历的规则,将指定的时 间量添加或减去给定的日历字段
//需求1:3年前的今天
c.add(Calendar.YEAR,-3);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH) + 1;
date = c.get(Calendar.DATE);
System.out.println(year + "年" + month + "月" + date + "日");
//需求2:10年后的10天前 //
c.add(Calendar.YEAR,10);
c.add(Calendar.DATE,-10);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH) + 1;
date = c.get(Calendar.DATE);
System.out.println(year + "年" + month + "月" + date + "日");
//public final void set(int year,int month,int date):设置当前日历的年月日
c.set(2050,10,10);
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH) + 1;
date = c.get(Calendar.DATE);
System.out.println(year + "年" + month + "月" + date + "日");
}
}
LocalDate、LocalTime、LocalDateTime 类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息
//now():获取当前的日期、时间、日期+时间
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate);//2021-09-28
System.out.println(localTime);//10:36:20.438
System.out.println(localDateTime);//2021-09-28T10:36:20.438
//of():设置指定的年、月、日、时、分、秒。没有偏移量
LocalDateTime localDateTime1 = LocalDateTime.of(2020, 10, 6, 13, 23, 43);
System.out.println(localDateTime1);//2020-10-06T13:23:43
LocalDateTime localDateTime = LocalDateTime.now();
//getXxx():获取相关的属性
System.out.println(localDateTime.getDayOfMonth());//28
System.out.println(localDateTime.getDayOfWeek());//TUESDAY
System.out.println(localDateTime.getMonth());//SEPTEMBER
System.out.println(localDateTime.getMonthValue());//9
System.out.println(localDateTime.getMinute());//39
//体现不可变性
//withXxx():设置相关的属性
LocalDate localDate1 = localDate.withDayOfMonth(22);
System.out.println(localDate);
System.out.println(localDate1);
LocalDateTime localDateTime2 = localDateTime.withHour(4);
System.out.println(localDateTime);
System.out.println(localDateTime2);
//不可变性
LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
System.out.println(localDateTime);
System.out.println(localDateTime3);
LocalDateTime localDateTime4 = localDateTime.minusDays(6);
System.out.println(localDateTime);
System.out.println(localDateTime4);
// 2021-09-28
// 2021-09-22
// 2021-09-28T10:41:53.102
// 2021-09-28T04:41:53.102
// 2021-09-28T10:41:53.102
// 2021-12-28T10:41:53.102
// 2021-09-28T10:41:53.102
// 2021-09-22T10:41:53.102
Instant:时间线上的一个瞬时点。 这可能被用来记录应用程序中的事件时间戳
//now():获取本初子午线对应的标准时间
Instant instant = Instant.now();
System.out.println(instant);//2019-02-18T07:29:41.719Z
//添加时间的偏移量
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime);//2019-02-18T15:32:50.611+08:00
//toEpochMilli():获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数 ---> Date类的getTime()
long milli = instant.toEpochMilli();
System.out.println(milli);
//ofEpochMilli():通过给定的毫秒数,获取Instant实例 -->Date(long millis)
Instant instant1 = Instant.ofEpochMilli(1550475314878L);
System.out.println(instant1);
java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:
// 方式一:预定义的标准格式。如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//格式化:日期-->字符串
LocalDateTime localDateTime = LocalDateTime.now();
String str1 = formatter.format(localDateTime);
System.out.println(localDateTime);
System.out.println(str1);//2019-02-18T15:42:18.797
//解析:字符串 -->日期
TemporalAccessor parse = formatter.parse("2019-02-18T15:42:18.797");
System.out.println(parse);
// 方式二:
// 本地化相关的格式。如:ofLocalizedDateTime()
// FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
//格式化
String str2 = formatter1.format(localDateTime);
System.out.println(str2);//2019年2月18日 下午03时47分16秒
// 本地化相关的格式。如:ofLocalizedDate()
// FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
//格式化
String str3 = formatter2.format(LocalDate.now());
System.out.println(str3);//2019-2-18
// 重点: 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//格式化
String str4 = formatter3.format(LocalDateTime.now());
System.out.println(str4);//2019-02-18 03:52:09
//解析
TemporalAccessor accessor = formatter3.parse("2019-02-18 03:52:09");
System.out.println(accessor);
在Java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题
Java实现对象排序的方式有两种:
Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。
实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即通过 compareTo(Object obj) 方法的返回值来比较大小。如果当前对象this大于形参对象obj,则返回正整数,如果当前对象this小于形参对象obj,则返回负整数,如果当前对象this等于形参对象obj,则返回零。
实现Comparable接口的对象列表(和数组)可以通过 Collections.sort 或Arrays.sort进行自动排序。实现此接口的对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器
class Goods implements Comparable {
private String name;
private double price;
//按照价格,比较商品的大小
@Override
public int compareTo(Object o) {
if(o instanceof Goods) {
Goods other = (Goods) o;
if (this.price > other.price) {
return 1;
} else if (this.price < other.price) {
return -1;
}
return 0;
}
throw new RuntimeException("输入的数据类型不一致");
}
//构造器、getter、setter、toString()方法略
}
Goods[] all = new Goods[4];
all[0] = new Goods("War and Peace", 100);
all[1] = new Goods("Childhood", 80);
all[2] = new Goods("Scarlet and Black", 140);
all[3] = new Goods("Notre Dame de Paris", 120);
Arrays.sort(all, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Goods g1 = (Goods) o1;
Goods g2 = (Goods) o2;
return g1.getName().compareTo(g2.getName());
}
});
System.out.println(Arrays.toString(all));
进程:是正在运行的程序
是系统进行资源分配和调用的独立单位
每一个进程都有它自己的内存空间和系统资源
线程:是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
创建线程有两种方法,
实现多线程方式一:继承Thread类
实现多线程方式二:实现Runnable接口
但是一般都会选择实现Runnable接口,相比继承Thread类,实现Runnable接口的好处
避免了Java单继承的局限性
适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
实现步骤
定义一个类MyThread继承Thread类
在MyThread类中重写run()方法,将此线程的操作写在run()中
创建MyThread类的对象
启动线程,调用start方法
public class Mythread extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++)
{
System.out.println(i);
}
}
}
public class threadtest
{
public static void main(String[] args) {
Mythread t1 = new Mythread();
Mythread t2=new Mythread();
//void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
t1.start();
t2.start();
}
}
实现步骤
定义一个类MyRunnable实现Runnable接口
在MyRunnable类中重写run()方法
创建MyRunnable类的对象
创建Thread类的对象,把MyRunnable对象作为构造方法的参数
启动线程
public class Mythread implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++)
{
System.out.println(i);
}
}
}
public class threadtest
{
public static void main(String[] args) {
Mythread mythread = new Mythread();
Thread t1=new Thread(mythread);
Thread t2=new Thread(mythread);
t1.start();
t2.start();
}
}
区别
void start(): 启动线程,并执行对象的run()方法
run(): 线程在被调度时执行的操作
String getName(): 返回线程的名称
void setName(String name):设置该线程名称
static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
static void yield():线程让步
join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完
低优先级的线程也可以获得执行
static void sleep(long millis):(指定时间:毫秒)
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
抛出InterruptedException异常
stop(): 强制线程生命期结束,不推荐使用
boolean isAlive():返回boolean,判断线程是否还活着
方法名 | 说明 |
---|---|
void run() | 在线程开启后,此方法将被调用执行 |
void start() | 使此线程开始执行,Java虚拟机会调用run方法() |
为什么要重写run()方法?
因为run()是用来封装被线程执行的代码
run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法
方法名 | 说明 |
---|---|
void setName(String name) | 将此线程的名称更改为等于参数name |
String getName() | 返回此线程的名称 |
static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
Thread currentThread()是静态方法可以直接调用
除了用setName(String name) 设置线程名称还可以直接创建的时候就给名字Thread(String name)
public class Mythread extends Thread{
public Mythread()
{
}
public Mythread(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<100;i++)
{
System.out.println(getName()+" : "+i);
}
}
}
public class threadtest
{
public static void main(String[] args) {
// Mythread t1 = new Mythread();
// Mythread t2=new Mythread();
//void setName(String name):将此线程的名称更改为等于参数 name
// t1.setName("dyk");
// t2.setName("cb");
Mythread t1 = new Mythread("dyk");
Mythread t2=new Mythread("lfw");
//void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
t1.start();
t2.start();
//static Thread currentThread() 返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName());
}
}
注意如果要在创建时就起名字必须手动给出带参构造方法
如果是实现Runnable
Thread构造方法
方法名 | 说明 |
---|---|
Thread(Runnable target) | 分配一个新的Thread对象 |
Thread(Runnable target, String name) | 分配一个新的Thread对象 |
public class Mythread implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++)
{
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
public class threadtest
{
public static void main(String[] args) {
Mythread mythread = new Mythread();
Thread t1=new Thread(mythread,"dyk");
Thread t2=new Thread(mythread,"lfw");
t1.start();
t2.start();
}
}
注意因为实现Runnable没有继承thread类所以不能直接使用getName方法,所以要调用currentThread()这个静态方法,再调用getName
两种调度方式
分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
Java使用的是抢占式调度模型
随机性
假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
注意优先级高的不一定优先执行,只是抢到cpu资源的可能性大一些,也就是优先级低的也有可能在优先级高的前面执行
java里面优先级的范围是1到10,默认的优先级是5
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
方法名 | 说明 |
---|---|
final int getPriority() | 返回此线程的优先级 |
final void setPriority(intnewPriority) | 更改此线程的优先级 线程默认优先级是5;线程优级的范围是:1-10 |
public class threadtest
{
public static void main(String[] args) {
Mythread mythread = new Mythread();
Thread t1=new Thread(mythread,"dyk");
Thread t2=new Thread(mythread,"lfw");
System.out.println(t1.getPriority()); //5
System.out.println(t2.getPriority());//5
//public final void setPriority(int newPriority):更改此线程的优先级
// tp1.setPriority(10000); //IllegalArgumentException
//设置正确的优先级
t1.setPriority(10);
t2.setPriority(1);
System.out.println(t1.getPriority()); //10
System.out.println(t2.getPriority()); //1
t1.start();
t2.start();
System.out.println(Thread.MAX_PRIORITY); //10
System.out.println(Thread.MIN_PRIORITY); //1
System.out.println(Thread.NORM_PRIORITY); //5
}
}
相关方法
方法名 | 说明 |
---|---|
static void sleep(longmillis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
void join() | 等待这个线程死亡,其他线程才能执行 |
void setDaemon(booleanon) | 将此线程标记为守护线程,当运行的线程都是守护线时,Java虚拟机将退出 |
public class Mythread implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++)
{
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" : "+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注意单位是毫秒而不是秒
join方法:当一个线程调用了join方法,这个线程就会先被执行,它执行结束后才可以去执行其余的线程
join方法可以用这个形象的例子来理解,例如我现在有三个线程,名字分别为康熙,八阿哥,和四阿哥,他们3争夺皇位,但是康熙是他们爸爸啊,所以,只有康熙驾崩了,八阿哥和四阿哥才能争夺皇位,所以就给康熙添加join方法,只有等康熙这个线程结束了,八阿哥和四阿哥这两个线程才会开始
public class Mythread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
public class threadtest
{
public static void main(String[] args) {
Mythread mythread = new Mythread();
Thread t1=new Thread(mythread,"康熙");
Thread t2=new Thread(mythread,"四阿哥");
Thread t3=new Thread(mythread,"八阿哥");
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t3.start();
}
}
注意join方法只有,先start启动后在添加才有效果
关于这个方法也举一个形象的例子,这里有三个线程,刘备(主线程),关羽,张飞,把关羽和张飞设置为守护线程,当刘备死了,也就是主进程结束了,那么关羽和张飞也会马上死亡,因为当年他们桃园三结义的时候,说了不求同年同月同日生,但求同年同月同日死。
public class Mythread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
public class threadtest
{
public static void main(String[] args) {
Mythread mythread = new Mythread();
Thread t1=new Thread(mythread,"关羽");
Thread t2=new Thread(mythread,"张飞");
//设置主线程为刘备
Thread.currentThread().setName("刘备");
//设置守护线程
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
for(int i=0; i<10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
join方法是先启动在调用join方法,设置守护进程是先设置,在启动
Java中的线程分为两类:一种是守护线程,一种是用户线程
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
守护线程是用来服务用户线程的,通过在start()方法前调用
thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
Java垃圾回收就是一个典型的守护线程。
若JVM中都是守护线程,当前JVM将退出
买票的例子
这里采用的是实现Runnable方式,其实tickets不定义为static也是可以的,但是如果是基础thread方式那必须定义为static因为是new了3个对象而,实现Runnable只new了一次
public class sellticks implements Runnable{
private static int tickets=100;
@Override
public void run() {
while (true){
if(tickets>0){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
tickets--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class sellsticks_test {
public static void main(String[] args) {
sellticks st = new sellticks();
Thread th1 = new Thread(st,"窗口一");
Thread th2 = new Thread(st,"窗口二");
Thread th3 = new Thread(st,"窗口三");
th1.start();
th2.start();
th3.start();
}
}
卖票出现了问题
相同的票出现了多次
出现了负数的票
问题产生原因
线程执行的随机性导致的
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
安全问题出现的条件
是多线程环境
有共享数据
有多条语句操作共享数据
把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
Java提供了同步代码块的方式来解决
同步代码块格式:
synchronized(任意对象) {
多条语句操作共享数据的代码
}
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全
一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
public class sellticks implements Runnable{
private static int tickets=100;
private Object obj=new Object();
@Override
public void run() {
while (true){
synchronized (obj){
if(tickets>0){
try {
Thread.sleep(200);
System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
tickets--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class sellsticks_test {
public static void main(String[] args) {
sellticks st = new sellticks();
Thread th1 = new Thread(st,"窗口一");
Thread th2 = new Thread(st,"窗口二");
Thread th3 = new Thread(st,"窗口三");
th1.start();
th2.start();
th3.start();
}
}
(1)明确哪些代码是多线程运行的代码
(2)明确多个线程是否有共享数据
(3)明确多线程运行代码中是否有多条语句操作共享数据
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
即所有操作共享数据的这些语句都要放在同步范围中
范围太小:没锁住所有有安全问题的代码
范围太大:没发挥多线程的功能。
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。应尽量避免使用suspend()和resume()来控制线程
同步方法的格式
同步方法:就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
同步方法的锁对象是什么呢?
this
静态同步方法
同步静态方法:就是把synchronized关键字加到静态方法上
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}
同步静态方法的锁对象是什么呢?
类名.class
public class SellTicket implements Runnable {
private static int tickets = 100;
private int x = 0;
@Override
public void run() {
while (true) {
sellTicket();
}
}
// 同步方法
// private synchronized void sellTicket() {
// if (tickets > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName() + "正在出售第" +
tickets + "张票");
// tickets--;
// }
// }
// 静态同步方法
private static synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" +
tickets + "张票");
tickets--;
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
StringBuffer
线程安全,可变的字符序列
从版本JDK 5开始,被StringBuilder 替代。 通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步
Vector
从Java 2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector被同步。 如果不需要线程安全的实现,建议使用ArrayList代替Vector
Hashtable
该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键或者值从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Hashtable被同步。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable
其中vector和hashtable也不怎么用了可以用collections里面的方法获得同步的集合
方法名 | 说明 |
---|---|
synchronizedList(List list) | 返回由指定列表支持的同步(线程安全)列表 |
synchronizedMap(Map |
返回由指定map支持的同步(线程安全)映射。 |
synchronizedSet(Set s) | 返回由指定集合支持的同步(线程安全)集。 |
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
方法名 | 说明 |
---|---|
ReentrantLock() | 创建一个ReentrantLock的实例 |
加锁解锁方法
方法名 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class sellticks implements Runnable{
private static int tickets=100;
private Lock lock=new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();
if(tickets>0) {
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}
}
public class sellsticks_test {
public static void main(String[] args) {
sellticks st = new sellticks();
Thread th1 = new Thread(st,"窗口一");
Thread th2 = new Thread(st,"窗口二");
Thread th3 = new Thread(st,"窗口三");
th1.start();
th2.start();
th3.start();
}
}
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待阻塞并释放同步监视器,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明
在当前线程中调用方法: 对象名.wait()
使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify (或notifyAll) 为止。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
调用此方法后,当前线程将释放对象监控权 ,然后进入等待
在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。
在当前线程中调用方法: 对象名.notify()
功能:唤醒等待该对象监控权的一个/所有线程。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
与使用Runnable相比, Callable功能更强大些
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果
实现Callable接口,可以不带泛型,如果不带泛型,那么call方式的返回值就是object类型
如果带着泛型,那么call的返回值就是泛型对应的类型
可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
FutrueTask是Futrue接口的唯一的实现类
FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown() :关闭连接池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
类的对象只有有限个,确定的
当需要定义一组常量时,强烈建议使用枚举类
若枚举只有一个对象, 则可以作为一种单例模式的实现方式。
JDK1.5之前需要自定义枚举类
JDK 1.5 新增的 enum 关键字用于定义枚举类
枚举类对象的属性不应允许被改动, 所以应该使用 private final 修饰
枚举类的使用 private final 修饰的属性应该在构造器中为其赋值
若枚举类显式的定义了带参数的构造器, 则在列出枚举值时也必须对应的传入参数
class Season{
private final String SEASONNAME;//季节的名称
private final String SEASONDESC;//季节的描述
private Season(String seasonName,String seasonDesc){
this.SEASONNAME = seasonName;
this.SEASONDESC = seasonDesc;
}
public static final Season SPRING = new Season("春天", "春暖花开");
public static final Season SUMMER = new Season("夏天", "夏日炎炎");
public static final Season AUTUMN = new Season("秋天", "秋高气爽");
public static final Season WINTER = new Season("冬天", "白雪皑皑");
}
使用 enum 定义的枚举类默认继承了 java.lang.Enum类,因此不能再继承其他类
枚举类的构造器只能使用 private 权限修饰符
枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾)。列出的实例系统会自动添加 public static final 修饰
必须在枚举类的第一行声明枚举类对象
JDK 1.5 中可以在 switch 表达式中使用Enum定义的枚举类的对象作为表达式, case 子句可以直接使用枚举值的名字, 无需添加枚举类作为限定
package ENUM;
public enum SeasonEnum {
SPRING("春天","春风又绿江南岸"),
SUMMER("夏天","映日荷花别样红"),
AUTUMN("秋天","秋水共长天一色"),
WINTER("冬天","窗含西岭千秋雪");
private final String seasonName;
private final String seasonDesc;
private SeasonEnum(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
和普通 Java 类一样,枚举类可以实现一个或多个接口
若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可
若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式, 则可以让每个枚举值分别来实现该方法
public enum SeasonEnum implements Runnable{
SPRING("春天","春风又绿江南岸"),
SUMMER("夏天","映日荷花别样红"),
AUTUMN("秋天","秋水共长天一色"),
WINTER("冬天","窗含西岭千秋雪");
;
private final String seasonName;
private final String seasonDesc;
private SeasonEnum(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public void run() {
System.out.println("直接在枚举类重写run方法");
}
}
package ENUM;
public enum SeasonEnum implements Runnable{
SPRING("春天","春风又绿江南岸"){
@Override
public void run() {
System.out.println("春天的run方法");
}
},
SUMMER("夏天","映日荷花别样红"){
@Override
public void run() {
System.out.println("夏天的run方法");
}
},
AUTUMN("秋天","秋水共长天一色"){
@Override
public void run() {
System.out.println("秋天的run方法");
}
},
WINTER("冬天","窗含西岭千秋雪"){
@Override
public void run() {
System.out.println("冬天的run方法");
}
};
;
private final String seasonName;
private final String seasonDesc;
private SeasonEnum(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
使用 Annotation 时要在其前面增加 @ 符号, 并把该 Annotation 当成一个修饰符使用。用于修饰它支持的程序元素
@author 标明开发该类模块的作者,多个作者之间使用,分割
@version 标明该类模块的版本
@see 参考转向,也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明,如果没有参数就不能写
@return 对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写
其中
@param @return 和 @exception 这三个标记都是只用于方法的。
@param的格式要求:@param 形参名 形参类型 形参说明
@return 的格式要求:@return 返回值类型 返回值说明
@exception的格式要求:@exception 异常类型 异常说明
@param和@exception可以并列多个
@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings: 抑制编译器警告
package com.annotation.javadoc;
public class AnnotationTest{
public static void main(String[] args) {
@SuppressWarnings("unused")
int a = 10;
}
@Deprecated
public void print(){
System.out.println("过时的方法");
}
@Override
public String toString() {
return "重写的toString方法()";
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyAnnotation{
String value() default "dyk";
}
JDK 中的元注解JDK 的元 Annotation 用于修饰其他 Annotation 定义
JDK5.0提供了4个标准的meta-annotation类型,分别是:
@Retention: 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 的生命周期, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用
@Rentention 时必须为该 value 成员变量指定值:
默认方式是CLASS
@Target: 用于修饰 Annotation 定义, 用于指定被修饰的 Annotation 能用于修饰哪些程序元素。 @Target 也包含一个名为 value 的成员变量
值 | 作用 |
---|---|
CONSTRUCTOR | 用于描述构造器 |
FIELD | 用于描述域 |
LOCAL_VARIABLE | 用于描述局部变量 |
METHOD | 用于描述方法 |
PACKAGE | 用于描述包 |
PARAMETER | 用于描述参数 |
TYPE | 用于描述类,接口(包括注解类型)或enum声明 |
用于指定被该元 Annotation 修饰的 Annotation 类将被javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的
定义为Documented的注解必须设置Retention值为RUNTIME
Inherited 修饰的 Annotation, 则其子类将自动具有该注解。
比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解
Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。此外,反射也得到了加强,在Java8中能够得到方法参数的名称。这会简化标注在方法参数上的注解
JDK1.8之后,关于元注解@Target的参数类型ElementType枚举值多了两个:TYPE_PARAMETER,TYPE_USE。
在Java 8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用在任何地方。
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。
@MyAnnotation
public class AnnotationTest<U> {
@MyAnnotation
private String name;
public static void main(String[] args) {
AnnotationTest<@MyAnnotation String> t = null;
int a = (@MyAnnotation int) 2L;
@MyAnnotation
int b = 10;
}
public static <@MyAnnotation T> void method(T t) {
}
public static void test(@MyAnnotation String arg) throws @MyAnnotation Exception {
}
}
@Target(ElementType.TYPE_USE)
@interface MyAnnotation {
}
List:是一个有序集合,可以放重复的数据
Set:是一个无序集合,不允许放重复的数据
Map:是一个无序集合,集合中包含一个键对象,一个值对象,键对象不允许重复,值对象可以重
是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素 JDK 不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现
迭代器的介绍 迭代器,集合的专用遍历方式 Iterator iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到 迭代器是通过集合的iterator()方法得到的,所以我们说它是依赖于集合而存在的
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo {
public static void main(String[] args) { //创建集合对象
Collection<String> c = new ArrayList<>();
//添加元素
c.add("hello");
c.add("world");
c.add("java");
c.add("javaee");
//Iterator iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
Iterator<String> it = c.iterator();
//用while循环改进元素的判断和获取
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
}
}
List集合概述 有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置。用户可以通过整数索引访问元 素,并搜索列表中的元素
与Set集合不同,列表通常允许重复的元素
List集合特点
有索引
可以存储重复元素
元素存取有序
//迭代器方式
Iterator<Student> it = list.iterator();
while (it.hasNext()) {
Student s = it.next();
System.out.println(s.getName() + "," + s.getAge());
}
// System.out.println("--------");
//for循环方式
for (int i = 0; i < list.size(); i++) {
Student s = list.get(i);
System.out.println(s.getName() + "," + s.getAge());
}
}
出现的原因
迭代器遍历的过程中,通过集合对象修改了集合中的元素,造成了迭代器获取元素中判断预期修改值和实际 修改值不一致,则会出现:ConcurrentModificationException
解决的方案
用for循环遍历,然后用集合对象做对应的操作即可
public class ListDemo {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
添加元素
list.add("hello");
list.add("world");
list.add("java");
//遍历集合,得到每一个元素,看有没有"world"这个元素,如果有,我就添加一 个"javaee"元素,请写代码实现
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
list.add("javaee"); //会报错,ConcurrentModificationException
}
}
for(
int i = 0; i<list.size();i++)
{
String s = list.get(i);
if (s.equals("world")) {
list.add("javaee");
}
}
//输出集合对象
System.out.println(list);
}
}
通过List集合的listIterator()方法得到,所以说它是List集合特有的迭代器 用于允许程序员沿任一方向遍历的列表迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
//获取列表迭代器
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
String s = lit.next();
if (s.equals("world")) {
lit.add("javaee");
}
}
System.out.println(list);
for(元素数据类型 变量名 : 数组/集合对象名) { 循环体; }
ArrayList集合 底层是数组结构实现,查询快、增删慢
LinkedList集合 底层是链表结构实现,查询慢、增删
什么是集合
提供一种存储空间可变的存储模型,存储的数据容量可以发生改变 ArrayList集合的特点
底层是数组实现的,长度可以变化
泛型的使用 用于约束集合中存储元素的数据类型
public boolean add(E e) 将指定的元素追加到此集合的末尾
public void add(int index,E element) 在此集合中的指定位置插入指定的元素
import java.util.ArrayList;
public class Arraylist02 {
public static void main(String[] args) {
//public boolean add(E e) 将指定的元素追加到此集合的末尾
ArrayList<String> list=new ArrayList<>();
list.add("hello");
list.add("java");
list.add("javase");
System.out.println(list); //[hello, java, javase]
//public void add(int index,E element) 在此集合中的指定位置插入指定的元素
list.add(1,"dyk");
System.out.println(list);//[hello, dyk, java, javase]
list.add(5,"hehe");//java.lang.IndexOutOfBoundsException
}
}
public boolean remove(Object o) 删除指定的元素,返回删除是否成功
public E remove(int index) 删除指定索引处的元素,返回被删除的元素
import java.util.ArrayList;
public class Arraylist02 {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>();
list.add("hello");
list.add("java");
list.add("javase");
System.out.println(list); //[hello, java, javase]
//public boolean remove(Object o) 删除指定的元素,返回删除是否成功
System.out.println(list.remove("javase"));//true
System.out.println(list); //[hello, java]
System.out.println(list.remove("python"));//false 不会报错,但也不会删除,因为不存在
//public E remove(int index) 删除指定索引处的元素,返回被删除的元素
System.out.println(list.remove(0)); //hello
System.out.println(list.remove(5)); //报错 java.lang.IndexOutOfBoundsException
}
}
public E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
import java.util.ArrayList;
public class Arraylist02 {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>();
list.add("hello");
list.add("java");
list.add("javase");
System.out.println(list); //[hello, java, javase]
//public E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
list.set(0,"c++");
System.out.println(list); //[c++, java, javase]
list.set(5,"c++");//报错 java.lang.IndexOutOfBoundsException
}
}
public int size() 返回集合中的元素的个数
import java.util.ArrayList;
public class Arraylist02 {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>();
list.add("hello");
list.add("java");
list.add("javase");
System.out.println(list); //[hello, java, javase]
//public int size() 返回集合中的元素的个数
System.out.println(list.size()); //3
}
}
import java.util.ArrayList;
public class Arraylist02 {
public static void main(String[] args) {
ArrayList<String> list=new ArrayList<>();
list.add("hello");
list.add("java");
list.add("javase");
for(int i=0;i<list.size();i++)
{
String s = list.get(i);
System.out.println(s);
}
}
}
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(123);//elementData[0] = new Integer(123);
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
后续的添加和扩容操作与jdk 7 无异
小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存
import java.util.Iterator;
import java.util.LinkedList;
import java.util.zip.CheckedOutputStream;
public class listtest {
public static void main(String[] args) {
LinkedList<String> lk=new LinkedList<>();
lk.add("hello");
lk.add("world");
lk.add("java");
System.out.println("-------------");
//public E getFirst() 返回此列表中的第一个元素
String first = lk.getFirst();
//public E getLast() 返回此列表中的最后一个元素
String last = lk.getLast();
System.out.println(first);
System.out.println(last);
//public void addFirst(E e) 在该列表开头插入指定的元素
lk.addFirst("firstadd");
//public void addLast(E e) 将指定的元素追加到此列表的末尾
lk.addLast("lastadd");
Iterator<String> it = lk.iterator();
while (it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("---------");
//public E removeFirst() 从此列表中删除并返回第一个元素
lk.removeFirst();
//public E removeLast() 从此列表中删除并返回最后一个元素
lk.removeLast();
Iterator<String> it2 = lk.iterator();
while (it2.hasNext()){
String s = it2.next();
System.out.println(s);
}
}
}
Set接口是Collection的子接口,set接口没有提供额外的方法
Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set 集合中,则添加操作失败。
Set 判断两个对象是否相同不是使用 == 运算符,而是根据 两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等
元素存取无序
没有索引、只能通过迭代器或增强for循环遍历
不能存储重复元素
无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的
不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个
public class SetDemo {
public static void main(String[] args) {
//创建集合对象
Set<String> set = new HashSet<String>();
//添加元素
set.add("hello");
set.add("world");
set.add("java");
//不包含重复元素的集合
set.add("world");
//遍历
for(String s : set)
{
System.out.println(s);
}
}
}
是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
如何获取哈希值
Object类中的public int hashCode():返回对象的哈希码值
哈希值的特点
同一个对象多次调用hashCode()方法返回的哈希值是相同的 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同 获取哈希值的代码
1.根据对象的哈希值计算存储位置
如果当前位置没有元素则直接存入
如果当前位置有元素存在,则进入第二步
2.当前元素的元素和已经存在的元素比较哈希值
如果哈希值不同,则将当前元素进行存储
如果哈希值相同,则进入第三步
3.通过equals()方法比较两个元素的内容
如果内容不相同,则将当前元素进行存储
如果内容相同,则不存储当前元素
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数组中,指向原来的元素。
jdk 8 :原来的元素在数组中,指向元素a
总结:七上八下
HashSet底层:数组+链表的结构
public class Student {
private String name;
private int age;
public Student() { }
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) :
student.name == null; }
@Override public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
不能保证元素的排列顺序
HashSet 不是线程安全的
集合元素可以是 null
两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等
对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“相等的对象必须具有相等的散列码”
底层也是数组,初始容量为16,当如果使用率超过0.75,(16*0.75=12)就会扩大容量为原来的2倍。(16扩容为32,依次为64,128…等)
HashSet底层就是利用HashMap来完成的,每次添加其实就是调用的HashMap的put方法只是value是个固定值罢了
private transient HashMap<E,object> map;
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>(); //HashSet底层就是利用HashMap来完成的
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
LinkedHashSet 是 HashSet 的子类
哈希表和链表实现的Set接口,具有可预测的迭代次序 由链表保证元素有序,也就是说元素的存储和取出顺序是一致的
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的
LinkedHashSet插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能
由哈希表保证元素唯一,也就是说没有重复的元素
TreeSet集合概述
元素有序,可以按照一定的规则进行排序,具体排序方式取决于构造方法 TreeSet():根据其元素的自然排序进行排序 TreeSet(Comparator comparator) :根据指定的比较器进行排序 没有带索引的方法,所以不能使用普通for循环遍历 由于是Set集合,所以不包含重复元素的集合
TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态。
TreeSet底层使用红黑树结构存储数据
用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student() { }
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Student s)
{ // return 0;
// return 1;
// return -1;
//按照年龄从小到大排序
int num = this.age - s.age;
// 按照年龄从大到小排序 int num = s.age - this.age;
//年龄相同时,按照姓名的字母顺序排序
//如果不写这个,当年龄相同时再在treeset集合中添加元素时就添加不进去
int num2 = num==0?this.name.compareTo(s.name):num;
return num2;
}
}
用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
public class TreeSetDemo {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student> ()
{
@Override
public int compare(Student s1, Student s2) {
//this.age - s.age
//s1,s2
int num = s1.getAge() - s2.getAge();
int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
return num2;
}
});
//创建学生对象 S
tudent s1 = new Student("xishi", 29);
Student s2 = new Student("wangzhaojun", 28);
Student s3 = new Student("diaochan", 30);
Student s4 = new Student("yangyuhuan", 33);
Student s5 = new Student("linqingxia",33);
Student s6 = new Student("linqingxia",33);
//把学生添加到集合
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
//遍历集合
for (Student s : ts) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
public static List duplicateList(List list) {
HashSet set = new HashSet();
set.addAll(list);
return new ArrayList(set);
}
public static void main(String[] args) {
List list = new ArrayList();
list.add(new Integer(1));
list.add(new Integer(2));
list.add(new Integer(2));
list.add(new Integer(4));
list.add(new Integer(4));
List list2 = duplicateList(list);
for (Object integer : list2) {
System.out.println(integer);
}
}
public static void main(String[] args) {
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");
set.add(p1);
set.add(p2);
p1.name = "CC";
set.remove(p1);
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
set.add(new Person(1001,"CC"));
System.out.println(set);//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
set.add(new Person(1001,"AA"));//[Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
System.out.println(set);
}
泛型概述 是JDK5中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型 它的本质
是参数化类型,也就是说所操作的数据类型被指定为一个参数。一提到参数,最熟悉的就是定义方 法时有形参,然后
调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具 体的类型参数化,然后在使
用/调用时传入具体的类型。这种参数类型可以用在类、方法和接口中,分别被称 为泛型类、泛型方法、泛型接口
泛型定义格式 <类型>:指定一种类型的格式。这里的类型可以看成是形参 <类型1,类型2…>:指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是形参 将来具体调用时候给定的类型可以看成是实参,并且实参的类型只能是引用数据类型
把运行时期的问题提前到了编译期间
避免了强制类型转换
修饰符 class 类名<类型> { }
public class Generic<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
修饰符 <类型> 返回值类型 方法名(类型 变量名) { }
public class Generic {
public <T> void show(T t) {
System.out.println(t);
}
}
修饰符 interface 接口名<类型> { }
泛型接口
public interface Generic<T> {
void show(T t);
}
泛型接口实现类
public class GenericImpl<T> implements Generic<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
作用
为了表示各种泛型List的父类,可以使用类型通配符
类型通配符的分类 类型通配符:>
List>:表示元素类型未知的List,它的元素可以匹配任何的类型
这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中 类型通
配符上限: extends 类型> List extends Number>:它表示的类型是Number或者其子类型
类型通配符下限: super 类型> List super Number>:它表示的类型是Number或者其父类型
public class GenericDemo {
public static void main(String[] args) {
//类型通配符:>
List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Number>();
List<?> list3 = new ArrayList<Integer>();
System.out.println("--------");
//类型通配符上限: extends 类型>
// List extends Number> list4 = new ArrayList
//报错不能超过Number
List<? extends Number> list5 = new ArrayList<Number>();
List<? extends Number> list6 = new ArrayList<Integer>();
System.out.println("--------");
//类型通配符下限: super 类型>
List<? super Number> list7 = new ArrayList<Object>();
List<? super Number> list8 = new ArrayList<Number>();
// List super Number> list9 = new ArrayList();
//报错不能小于Number
}
}
可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了 可变参数定义格式
修饰符 返回值类型 方法名(数据类型… 变量名) { }
可变参数的注意事项 这里的变量其实是一个数组 如果一个方法有多个参数,包含可变参数,可变参数要放在最后
public static List asList(T… a):返回由指定数组支持的固定大小的列表
返回的集合不能做增删操作,可以做修改操作
List接口中有一个静态方法: public static List of(E… elements):返回包含任意数量元素的不可变列表
返回的集合不能做增删改操作 Set接口中有一个静态方法:
public static Set of(E… elements) :返回一个包含任意数量元素的不可变集合
在给元素的时候,不能给重复的元素
返回的集合不能做增删操作,没有修改的方法
public class ArgsDemo02 {
public static void main(String[] args) {
//public static List asList(T... a):返回由指定数组支持的固定大小的列 表
List<String> list = Arrays.asList("hello", "world", "java");
list.add("javaee"); //UnsupportedOperationException
list.remove("world"); //UnsupportedOperationException
list.set(1,"javaee");
System.out.println(list);
//public static List of(E... elements):返回包含任意数量元素的不可变列 表
List<String> list = List.of("hello", "world", "java", "world");
list.add("javaee");//UnsupportedOperationException
list.remove("java");//UnsupportedOperationException
list.set(1,"javaee");//UnsupportedOperationException
System.out.println(list);
//public static Set of(E... elements) :返回一个包含任意数量元素的不可 变集合
Set<String> set = Set.of("hello", "world", "java","world"); //IllegalArgumentException
Set<String> set = Set.of("hello", "world", "java");
set.add("javaee");//UnsupportedOperationException
set.remove("world");//UnsupportedOperationException
System.out.println(set);
}
}
interface Map
Map与Collection并列存在。用于保存具有映射关系的数据:key-value
Map 中的 key 和 value 都可以是任何引用类型的数据
Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法
key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value
Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是 Map 接口使用频率最高的实现类
键值对映射关系
一个键对应一个值
键不能重复,值可以重复
元素存取无序
public class MapDemo01 {
public static void main(String[] args) {
//创建集合对象
Map<String,String> map = new HashMap<String,String>();
//V put(K key, V value) 将指定的值与该映射中的指定键相关联
map.put("itheima001","林青霞");
map.put("itheima002","张曼玉");
map.put("itheima003","王祖贤");
map.put("itheima003","柳岩");
//输出集合对象
System.out.println(map);
}
}
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapTest {
public static void main(String[] args) {
HashMap<String, String> hm = new HashMap<>();
//V put(K key,V value) 添加元素
hm.put("郭靖", "黄蓉");
hm.put("杨过", "小龙女");
hm.put("张无忌", "赵敏");
//V remove(Object key) 根据键删除键值对元素
// System.out.println(hm.remove("杨过"));//小龙女返回键值对应的value
//void clear() 移除所有的键值对元素
// hm.clear();
//boolean containsKey(Object key) 判断集合是否包含指定的键
System.out.println(hm.containsKey("杨过"));//true;
System.out.println(hm.containsKey("郭襄"));//false;
//boolean containsValue(Object value) 判断集合是否包含指定的值
System.out.println(hm.containsValue("小龙女"));//true
System.out.println(hm.containsValue("尹志平"));//false
// boolean isEmpty() 判断集合是否为空
System.out.println(hm.isEmpty());//false
//int size() 集合的长度,也就是集合中键值对的个数
System.out.println(hm.size());//3
//V get(Object key) 根据键获取值
System.out.println(hm.get("杨过"));//小龙女
//Set keySet() 获取所有键的集合
Set<String> set = hm.keySet();
for (String s : set) {
System.out.println(s + " " + hm.get(s));
}
System.out.println("--------");
//Collection values() 获取所有值的集合
Collection<String> values = hm.values();
for (String s : values) {
System.out.println(s);
}
System.out.println("----------");
//Set> entrySet() 获取所有键值对对象的集合
Set<Map.Entry<String, String>> entries = hm.entrySet();
for (Map.Entry<String, String> hs:entries){
System.out.println(hs.getKey());
System.out.println(hs.getValue());
}
}
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class qiantao {
public static void main(String[] args) {
ArrayList<HashMap<String,String>> arr = new ArrayList<>();
HashMap<String, String> mp = new HashMap<>();
mp.put("杨过","小龙女");
mp.put("郭靖","黄蓉");
arr.add(mp);
HashMap<String, String> mp1 = new HashMap<>();
mp.put("孙策","大乔");
mp.put("周瑜","小乔");
arr.add(mp1);
HashMap<String, String> mp2 = new HashMap<>();
mp.put("曾小贤","胡一菲");
mp.put("吕子乔","陈美嘉");
arr.add(mp2);
//遍历1
for (HashMap<String, String> hm:arr){
Set<String> set = hm.keySet();
for (String s:set){
System.out.println(s+" "+hm.get(s));
}
}
System.out.println("---------");
//遍历2
for (HashMap<String, String> hm:arr){
Set<Map.Entry<String, String>> entries = hm.entrySet();
for (Map.Entry<String, String> m:entries){
System.out.println(m.getKey()+" "+m.getValue());
}
}
}
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class qiantao2 {
public static void main(String[] args) {
HashMap<String, ArrayList<String>> hm = new HashMap<>();
ArrayList<String> arr = new ArrayList<>();
arr.add("刘备");
arr.add("关羽");
arr.add("张飞");
hm.put("蜀国",arr);
ArrayList<String> arr1 = new ArrayList<>();
arr1.add("曹操");
arr1.add("张辽");
arr1.add("许褚");
hm.put("魏国",arr1);
ArrayList<String> arr2 = new ArrayList<>();
arr2.add("孙权");
arr2.add("孙策");
arr2.add("周瑜");
hm.put("吴国",arr2);
// 遍历1
Set<String> key = hm.keySet();
for (String s:key){
System.out.println(s);
ArrayList<String> lis = hm.get(s);
for (String ss:lis){
System.out.println(" "+ss);
}
}
System.out.println("-------------");
//遍历2
Set<Map.Entry<String, ArrayList<String>>> entries = hm.entrySet();
for (Map.Entry<String, ArrayList<String>> mm:entries){
System.out.println(mm.getKey());
ArrayList<String> value = mm.getValue();
for (String s:value){
System.out.println(" "+s);
}
}
}
}
统计字符串中每个字符出现的次
import java.util.HashMap;
import java.util.Scanner;
import java.util.Set;
public class Map {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
String s=sc.next();
HashMap<Character, Integer> hm = new HashMap<>();
for (int i=0;i<s.length();i++){
char c=s.charAt(i);
Integer num= hm.get(c);
if(num==null){
hm.put(c,1);
}
else{
num++;
hm.put(c,num);
}
}
Set<Character> ss = hm.keySet();
for (Character cc:ss){
System.out.println(cc+" :"+hm.get(cc));
}
}
}
HashMap的底层:数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk 8)
以jdk7为例说明:
HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table。…可能已经执行过多次put…
map.put(key1,value1):
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来
装填因子设置为1:空间利用率得到了很大的满足,很容易碰撞,产生链表,导致查询效率低
装填因子设置为0.5: 碰撞的概率低,扩容,产生链表的几率低,查询效率高,空间利用率低
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class collections01 {
public static void main(String[] args) {
//创建ArrayList集合对象
ArrayList<student> arr = new ArrayList<>();
//创建学生对象
student s1 = new student(16, "tom");
student s2 = new student(23, "jack");
student s3 = new student(8, "mike");
student s4 = new student(18, "mali");
student s5 = new student(23, "jby");
//把学生添加到集合
arr.add(s1);
arr.add(s2);
arr.add(s3);
arr.add(s4);
arr.add(s5);
//使用Collections对ArrayList集合排序
// sort•(List list, Comparator super T> c)
Collections.sort(arr, new Comparator<student>() {
@Override
public int compare(student s1, student s2) {
int num=s2.getAge()-s1.getAge();//按年龄降序排列
//int num=s1.getAge()-s2.getAge();//按年龄升序排列
int num1=num==0?s1.getName().compareTo(s2.getName()):num;//如果年龄相同就按名字升序排序
//int num1=num==0?s2.getName().compareTo(s2.getName()):num;//如果年龄相同按名字降序排列
return num1;
}
});
//遍历集合
for(student s:arr){
System.out.println(s.getName()+" "+s.getAge());
}
}
}
模拟斗地主案例-普通版本
通过程序实现斗地主过程中的洗牌,发牌和看牌
import java.util.ArrayList;
import java.util.Collections;
public class collections_ddz {
public static void main(String[] args) {
//创建一个牌盒,也就是定义一个集合对象,用ArrayList集合实现
ArrayList<String> arr = new ArrayList<>();
//定义花色数组
String [] colors={"♦","♣","♥","♠"};
//定义点数数组
String [] nums={"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
for (int i=0;i<colors.length;i++){
for(int j=0;j<nums.length;j++){
arr.add((colors[i]+nums[j]));
}
}
arr.add("小王");
arr.add("大王");
//创建3个打牌人的集合存储手中的牌和底牌集合
ArrayList<String> p1 = new ArrayList<>();
ArrayList<String> p2 = new ArrayList<>();
ArrayList<String> p3 = new ArrayList<>();
ArrayList<String> dp = new ArrayList<>();
//洗牌,也就是把牌打撒,用Collections的shuffle()方法实现
Collections.shuffle(arr);
System.out.println(arr.size());
//模拟发牌的过程
for (int i=0;i<arr.size();i++){
String poker=arr.get(i);
if(i>=arr.size()-3){
dp.add(poker);
}
else if(i%3==0){
p1.add(poker);
}
else if(i%3==1){
p2.add(poker);
}
else if(i%3==2){
p3.add(poker);
}
}
//看牌
System.out.print("p1的牌是:");
for (int i=0;i<p1.size();i++){
System.out.print(p1.get(i)+" ");
}
System.out.println();
System.out.print("p2的牌是:");
for (int i=0;i<p2.size();i++){
System.out.print(p2.get(i)+" ");
}
System.out.println();
System.out.print("p3的牌是:");
for (int i=0;i<p3.size();i++){
System.out.print(p3.get(i)+" ");
}
System.out.println();
System.out.print("底牌是:");
for (int i=0;i<dp.size();i++){
System.out.print(dp.get(i)+" ");
}
}
}
模拟斗地主案例-升级版本
通过程序实现斗地主过程中的洗牌,发牌和看牌。要求:对牌进行排序
import java.util.*;
public class shengjidoudizhu {
public static void main(String[] args) {
//创建HashMap,键是编号,值是牌
HashMap<Integer, String> hm = new HashMap<>();
//创建一个牌盒,也就是定义一个集合对象,用ArrayList集合实现
ArrayList<Integer> arr = new ArrayList<>();
//定义花色数组
String [] colors={"♦","♣","♥","♠"};
//定义点数数组
String [] nums={"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
int index=0;
//从0开始往HashMap里面存储编号,并存储对应的牌。同时往ArrayList里面存储编号
for(int j=0;j<nums.length;j++){
for (int i=0;i<colors.length;i++){
hm.put(index,(colors[i]+nums[j]));
arr.add(index);
index++;
}
}
hm.put(index,"小王");
arr.add(index);
index++;
hm.put(index,"大王");
arr.add(index);
//洗牌(洗的是编号),用Collections的shuffle()方法实现
Collections.shuffle(arr);
TreeSet<Integer> p1 = new TreeSet<>();
TreeSet<Integer> p2 = new TreeSet<>();
TreeSet<Integer> p3 = new TreeSet<>();
TreeSet<Integer> dp = new TreeSet<>();
System.out.println(arr.size());
//发牌(发的也是编号,为了保证编号是排序的,创建TreeSet集合接收)
for (int i=0;i<arr.size();i++){
int poker=arr.get(i);
if(i>=arr.size()-3){
dp.add(poker);
}
else if(i%3==0){
p1.add(poker);
}
else if(i%3==1){
p2.add(poker);
}
else if(i%3==2){
p3.add(poker);
}
}
//调用看牌方法
lookpoker("p1",p1,hm);
lookpoker("p2",p2,hm);
lookpoker("p3",p3,hm);
lookpoker("底牌",dp,hm);
}
//定义方法看牌(遍历TreeSet集合,获取编号,到HashMap集合找对应的牌)
public static void lookpoker(String name,TreeSet<Integer> set,HashMap<Integer,String> hm){
System.out.print(name+"的牌是:");
for (Integer i:set){
String s = hm.get(i);
System.out.print(s+" ");
}
System.out.println();
}
}
File类概述和构造方法
File类介绍 它是文件和目录路径名的抽象表示
文件和目录是可以通过File封装成对象的
对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。
它可以是存在的,也可以 是不存在的。将来是要通过具体的操作把这个路径的内
容转换为具体存在的
public class FileDemo01 {
public static void main(String[] args)
{
//File(String pathname):通过将给定的路径名字符串转换为抽象路径名来创建新的 File 实例。
File f1 = new File("E:\\itcast\\java.txt");
System.out.println(f1);
//File(String parent, String child):从父路径名字符串和子路径名字符串创建新的 File实例。
File f2 = new File("E:\\itcast","java.txt");
System.out.println(f2);
//File(File parent, String child):从父抽象路径名和子路径名字符串创建新的 File 实例。
File f3 = new File("E:\\itcast");
File f4 = new File(f3,"java.txt");
System.out.println(f4);
}
}
import jdk.swing.interop.SwingInterOpUtils;
import java.io.File;
import java.io.IOException;
public class IO01 {
public static void main(String[] args) throws IOException {
//public boolean createNewFile()
// 当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件并返回true;
//当文件已经存在时返回false
//需求1:我要在D:\\iotest目录下创建一个文件java.txt
File file = new File("D:\\iotest\\java.txt");
System.out.println(file.createNewFile());//不存在则创建成功返回true;
System.out.println(file.createNewFile());//存在则返回false;
System.out.println("------------");
//public boolean mkdir() 创建由此抽象路径名命名的目录
//需求2:我要在D:\\iotest目录下创建一个目录JavaSE
File file1 = new File("D:\\iotest\\javaSE");
System.out.println(file1.mkdir());//不存在则创建成功返回true;
System.out.println("------------");
//需求3:我要在D:\\iotest目录下创建一个多级目录JavaWEB\\HTML
//public boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录
File file2 = new File("D:\\iotest\\javaWEB\\HTML");
System.out.println(file2.mkdirs());
File file3 = new File("D:\\iotest\\javase.txt");
System.out.println(file3.mkdir());//会返回true并创建javase.txt的文件夹
// 创建文件还是需要用createNewFile()不然会生成.txt的文件夹
}
}
import java.io.File;
import java.io.IOException;
import java.sql.SQLOutput;
public class IO02 {
public static void main(String[] args) throws IOException {
File file = new File("D:\\iotest\\javaEE.txt");
// file.createNewFile();
// public boolean isDirectory():测试此抽象路径名表示的File是否为目录
// public boolean isFile():测试此抽象路径名表示的File是否为文件
// public boolean exists():测试此抽象路径名表示的File是否存在
System.out.println(file.isDirectory());//false
System.out.println(file.isFile());//true
System.out.println(file.exists());//true;
System.out.println("--------------");
// public String getAbsolutePath():返回此抽象路径名的绝对路径名字符串
// public String getPath():将此抽象路径名转换为路径名字符串
// public String getName():返回由此抽象路径名表示的文件或目录的名称
System.out.println(file.getAbsoluteFile());
System.out.println(file.getPath());
System.out.println(file.getName());
System.out.println("--------------");
// public String[] list():返回此抽象路径名表示的目录中的文件和目录的名称字符串数组
// public File[] listFiles():返回此抽象路径名表示的目录中的文件和目录的File对象数组
File file1 = new File("D:\\iotest");
String[] list = file1.list();
for (String s:list){
System.out.println(s);
}
System.out.println("--------------");
File[] files = file1.listFiles();
for (File f:files){
if(f.isFile()){
System.out.println(f);
System.out.println(f.getName());
}
}
}
}
public class FileDemo03 {
public static void main(String[] args) throws IOException { // File f1 = new File("E:\\itcast\\java.txt");
//需求1:在当前模块目录下创建java.txt文件
File f1 = new File("myFile\\java.txt"); System.out.println(f1.createNewFile());
//需求2:删除当前模块目录下的java.txt文件
System.out.println(f1.delete());
System.out.println("--------");
//需求3:在当前模块目录下创建itcast目录
File f2 = new File("myFile\\itcast"); //
System.out.println(f2.mkdir());
//需求4:删除当前模块目录下的itcast目录
System.out.println(f2.delete());
System.out.println("--------");
//需求5:在当前模块下创建一个目录itcast,然后在该目录下创建一个文件java.txt
File f3 = new File("myFile\\itcast"); //
System.out.println(f3.mkdir());
File f4 = new File("myFile\\itcast\\java.txt"); //
System.out.println(f4.createNewFile());
//需求6:删除当前模块下的目录itcast
System.out.println(f4.delete());
System.out.println(f3.delete());
}
}
绝对路径和相对路径的区别 绝对路径:完整的路径名,不需要任何其他信息就可以定位它所表示的文件。例如:E:\itcast\java.txt
相对路径:必须使用取自其他路径名的信息进行解释。例如:myFile\java.txt
如果文件夹里面有文件则无法删除该文件
递归遍历目录
import java.io.File;
public class IO03 {
public static void main(String[] args) {
File srcfile = new File("D:\\iotest");
getallpaths(srcfile);
}
public static void getallpaths(File srcfile){
File[] files = srcfile.listFiles();
if(files!=null){
for (File f:files){
if (f.isDirectory()){
//递归调用
getallpaths(f);
}
else{
System.out.println(f.getAbsolutePath());
}
}
}
}
}
IO流介绍
IO:输入/输出(Input/Output)
流:是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输称为流,流的本质是数据传输
IO流就是用来处理设备间数据传输问题的。常见的应用:文件复制;文件上传;文件下载
按照数据的流向
输入流:读数据
输出流:写数据
按照数据类型来分
字节流
字节输入流
字节输出流
字符流
字符输入流
字符输出流
如果操作的是纯文本文件,优先使用字符流
如果操作的是图片、视频、音频等二进制文件。优先使用字节流
如果不确定文件类型,优先使用字节流。字节流是万能的流
InputStream:这个抽象类是表示字节输入流的所有类的超类
OutputStream:这个抽象类是表示字节输出流的所有类的超类
子类名特点:子类名称都是以其父类名作为子类名的后缀
字节输出流 FileOutputStream(String name):创建文件输出流以指定的名称写入文件
使用字节输出流写数据的步骤
创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输
出流对象指向文件)
调用字节输出流对象的写数据方法
释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)
import java.io.FileOutputStream;
import java.io.IOException;
public class IO04 {
public static void main(String[] args) throws IOException {
//FileOutputStream(String name):创建文件输出流以指定的名称写入文件
FileOutputStream fos = new FileOutputStream("D:\\iotest\\dyk.txt");
/*
做了三件事情:
A:调用系统功能创建了文件
B:创建了字节输出流对象
C:让字节输出流对象指向创建好的文件
*/
fos.write(97);//将指定字节流写入文件输出流
fos.close();//关闭字节流释放资源
}
}
import java.io.FileOutputStream;
import java.io.IOException;
public class IO04 {
public static void main(String[] args) throws IOException {
//FileOutputStream(String name):创建文件输出流以指定的名称写入文件
FileOutputStream fos = new FileOutputStream("D:\\iotest\\dyk.txt");
/*
做了三件事情:
A:调用系统功能创建了文件
B:创建了字节输出流对象
C:让字节输出流对象指向创建好的文件
*/
//void write(int b) 将指定的字节写入此文件输出流 一次写一个字节数据
// fos.write(97);
//fos.write(97);//将指定字节流写入文件输出流
// byte [] bys={97,98,99,100};
// fos.write(bys);
String str="abcdef";
//fos.write(str.getBytes());//abcdef
//void write(byte[] b, int off, int len)
//将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流 一 次写一个字节数组的部分数据
fos.write(str.getBytes(),0,4);//abcd
fos.close();//关闭字节流释放资源
}
}
字节流写数据的两个小问题
字节流写数据如何实现换行
windows:\r\n
linux:\n
mac:\r
public FileOutputStream(String name,boolean append)
创建文件输出流以指定的名称写入文件。如果第二个参数为true ,则字节将写入文件的末尾而不是开头
字节流写数据加异常处理
try{
可能出现异常的代码;
}catch(异常类名 变量名){
异常的处理代码;
}finally{
执行所有清除操作;
}
finally特点 被finally控制的语句一定会执行,除非JVM退出
FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream ,该文件由文 件系统中的路径名name命名
字节输入流读取数据的步骤
创建字节输入流对象
调用字节输入流对象的读数据方法
释放资源
字节流读数据(一次读一个字节数据)
字节输入流
FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream ,该文件由文 件系统中的路径名name命名
import com.sun.jdi.PathSearchingVirtualMachine;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class IO05 {
public static void main(String[] args) throws IOException {
//创建字节输入流对象
// FileInputStream(String name)
FileInputStream fis = new FileInputStream("D:\\iotest\\dyk.txt");
int by;
/*
fis.read():读数据
by=fis.read():把读取到的数据赋值给by
by != -1:判断读取到的数据是否是-1
*/
while ((by=fis.read())!=-1){
System.out.print((char)by);
}
fis.close();
}
}
字节流复制文本文件
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IO05 {
public static void main(String[] args) throws IOException {
//创建字节输入流对象
// FileInputStream(String name)
FileInputStream fis = new FileInputStream("D:\\iotest\\dyk.txt");
FileOutputStream fos = new FileOutputStream("D:\\iotest\\dyk2.txt");
int by;
/*
fis.read():读数据
by=fis.read():把读取到的数据赋值给by
by != -1:判断读取到的数据是否是-1
*/
while ((by = fis.read()) != -1) {
fos.write(by);
}
fis.close();
fos.close();
}
}
一次读一个字节数组的方法 public int read(byte[] b):从输入流读取最多b.length个字节的数据
返回的是读入缓冲区的总字节数,也就是实际的读取字节个数
import java.io.FileInputStream;
import java.io.IOException;
public class IO06 {
public static void main(String[] args) throws IOException {
//创建字节输入流对象
FileInputStream fis = new FileInputStream("D:\\iotest\\dyk.txt");
int len ;
byte [] bys=new byte[1024];
while ((len=fis.read(bys))!=-1){
System.out.println(new String(bys,0,len));//1024及其整数倍
}
fis.close();
}
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IO06 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("D:\\iotest\\dyk3.txt");
FileInputStream fis=new FileInputStream("D:\\iotest\\dyk.txt");
int len;
byte[] bys=new byte[1024];
while((len=fis.read(bys))!=-1){
fos.write(bys,0,len);
}
fis.close();
fos.close();
}
}
lBufferOutputStream:该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写 入字节,而不必为写入的每个字节导致底层系统的调用
lBufferedInputStream:创建BufferedInputStream将创建一个内部缓冲区数组。 当从流中读取或跳过 字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节
import java.io.*;
public class IO07 {
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\iotest\\dyk.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\iotest\\dyk4.txt"));
int len;
byte [] bys=new byte[1024];
while ((len=bis.read(bys))!=-1){
bos.write(bys,0,len);
}
bos.close();
bis.close();
}
}
字符流的介绍
由于字节流操作中文不是特别的方便,所以Java就提供字符流
字符流 = 字节流 + 编码表
中文的字节存储方式
用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接 成中文,如何识别是中文的呢?
汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数
public class StringDemo {
public static void main(String[] args) throws UnsupportedEncodingException {
//定义一个字符串
String s = "中国";
//byte[] bys = s.getBytes(); //[-28, -72, -83, -27, -101, -67]
//byte[] bys = s.getBytes("UTF-8"); //[-28, -72, -83, -27, -101, -67]
byte[] bys = s.getBytes("GBK"); //[-42, -48, -71, -6] System.out.println(Arrays.toString(bys));
//String ss = new String(bys);
//String ss = new String(bys,"UTF-8");
String ss = new String(bys,"GBK");
System.out.println(ss);
}
}
字符流中和编码解码问题相关的两个类 InputStreamReader:是从字节流到字符流的桥梁
它读取字节,并使用指定的编码将其解码为字符
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集 OutputStreamWriter:是从字符流到字节流的桥梁
是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
import jdk.swing.interop.SwingInterOpUtils;
import java.io.*;
public class IO08 {
public static void main(String[] args) throws IOException {
OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("D:\\iotest\\osw.txt"),"GBK");
// InputStreamReader isr=new InputStreamReader(new FileInputStream("D:\\iotest\\osw.txt"),"GBK");
//void write(int c):写一个字符
osw.write(97);
//void writ(char[] cbuf):写入一个字符数组
char [] chs={'a','b','c','d'};
osw.write(chs);
//void write(char[] cbuf, int off, int len):写入字符数组的一部分
osw.write(chs,0,2);
//void write(String str):写一个字符串
osw.write("中国");
//void write(String str, int off, int len):写一个字符串的一部分
String sss="心好累感觉不会再爱了";
osw.write(sss,0,3);
//flush() 刷新流,之后还可以继续写数据
osw.flush();
//close() 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据
osw.close();
}
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
public class IO09 {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\iotest\\dyk4.txt"));
int ch;
//int read():一次读一个字符数据
while ((ch = isr.read()) != -1) {
System.out.print((char) ch);
}
//int read(char[] cbuf):一次读一个字符数组数据
// char[] chs=new char[1024];
// int len;
// while((len=isr.read(chs))!=-1){
// System.out.println(new String(chs,0,len));
// }
isr.close();
}
}
字符流复制Java文件
import java.io.*;
public class IO09 {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("D:\\iotest\\dyk4.txt"));
OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("D:\\iotest\\new.txt"));
// int ch;
//int read():一次读一个字符数据
// while ((ch = isr.read()) != -1) {
// osw.write(ch);
// }
//int read(char[] cbuf):一次读一个字符数组数据
char[] chs=new char[1024];
int len;
while((len=isr.read(chs))!=-1){
osw.write(chs,0,len);
}
isr.close();
osw.close();
}
}
FileReader(String fileName) 创建一个新的 FileReader ,给定要读取的文件的名称路径 继承于InputStreamReader
FileWriter(String fileName) 构造一个给定文件名的FileWriter对象。
FileWriter(String fileName, boolean append) 构造一个FileWriter对象,给出一个带有布尔值的文件名,表示是否附加写入的数据。
继承于OutputStreamWriter
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class IO10 {
public static void main(String[] args) throws IOException {
FileWriter fw=new FileWriter("D:\\iotest\\new1.txt");
FileReader fr=new FileReader("D:\\iotest\\new.txt");
// int ch;
// while ((ch=fr.read())!=-1){
// fw.write(ch);
// }
int len;
char[] chs=new char[1024];
while ((len=fr.read(chs))!=-1){
fw.write(chs,0,len);
}
fw.close();
}
}
字符缓冲流介绍 BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可 以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓 冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
import java.io.*;
public class IO11 {
public static void main(String[] args) throws IOException {
//BufferedWriter(Writer out)
BufferedReader bf=new BufferedReader(new FileReader("D:\\iotest\\dyk4.txt"));
//BufferedReader(Reader in)
BufferedWriter bw=new BufferedWriter(new FileWriter("D:\\iotest\\dyk5.txt"));
//一次读取一个字符数据
// int ch;
// while((ch=bf.read())!=-1){
// System.out.print((char)ch);
// }
int len;
char[] chs=new char[1024];
while((len=bf.read(chs))!=-1){
// System.out.println(new String(chs,0,len));
bw.write(chs,0,len);
}
bf.close();
bw.close();
}
}
import java.io.*;
public class IO12 {
public static void main(String[] args) throws IOException {
//根据数据源创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader("D:\\iotest\\dyk4.txt"));
//根据目的地创建字符缓冲输出流对象
BufferedWriter bw=new BufferedWriter(new FileWriter("D:\\iotest\\new3.txt"));
//读写数据,复制文件
// 使用字符缓冲流特有功能实现
String line;
while((line=br.readLine())!=null){
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
br.close();
bw.close();
}
}
Properties作为Map集合的使用
Properties介绍
是一个Map体系的集合类
Properties可以保存到流中或从流中加载
属性列表中的每个键及其对应的值都是一个字符串
import java.util.Properties;
import java.util.Set;
public class properties01 {
public static void main(String[] args) {
Properties pro = new Properties();
//普通方法
pro.put("01", "张三");
pro.put("02", "李四");
pro.put("03", "王五");
Set<Object> objects = pro.keySet();
for (Object s : objects) {
System.out.println((String) s + pro.get(s));
}
System.out.println("----------------");
Properties pro1 = new Properties();
//Object setProperty(String key, String value):设置集合的键和值,都是
//String类型,底层调用Hashtable方法put
pro1.setProperty("001", "张三");
pro1.setProperty("002", "李四");
pro1.setProperty("003", "王五");
/*
Object setProperty(String key, String value)
{
return put(key, value);
}
Object put(Object key, Object value)
{
return map.put(key, value);
}
*/
//String getProperty(String key):使用此属性列表中指定的键搜索属性
//Set stringPropertyNames():从该属性列表中返回一个不可修改的键集,其中
//键及其对应的值是字符串
Set<String> strings = pro1.stringPropertyNames();
for (String s : strings) {
System.out.println(s + " " + pro1.getProperty(s));
}
}
}
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
public class properties02 {
public static void main(String[] args) throws IOException {
//把集合中的数据保存到文件
Properties pro = new Properties();
pro.setProperty("001", "张三");
pro.setProperty("002", "李四");
pro.setProperty("003", "王五");
FileWriter fw=new FileWriter("D:\\iotest\\123.txt");
//void store(Writer writer, String comments)
//将此属性列表(键和元素对)写入此 Properties表中,以适合使用
//load(Reader)方法的格式写入输出字符流
pro.store(fw,null);
fw.close();
//把文件中的数据加载到集合
// Properties pro1=new Properties();
// FileReader fr=new FileReader("D:\\iotest\\123.txt");
//void load(Reader reader) 从输入字符流读取属性列表(键和元素对)
// pro1.load(fr);
// fr.close();
// System.out.println(pro1);
}
}
多线程
进程:是正在运行的程序
是系统进行资源分配和调用的独立单位
每一个进程都有它自己的内存空间和系统资源
线程:是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
public class xiancheng01 extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println(i);
}
}
}
public class xiancheng_test01 {
public static void main(String[] args) {
xiancheng01 xc1 = new xiancheng01();
xiancheng01 xc2 = new xiancheng01();
//xc1.run();
//xc2.run();//直接调用,相当于普通方法的调用
xc1.start();
xc2.start();
}
}
两个小问题
为什么要重写run()方法?
因为run()是用来封装被线程执行的代码
run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法
有两种方法可以实现线程命名
1.采用无参构造函数创建对象,然后利用setName方法
2,直接使用带参构造方法创建对象
public class MyThread extends Thread {
public MyThread() {}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
//void setName(String name):将此线程的名称更改为等于参数 name
my1.setName("高铁");
my2.setName("飞机");
//Thread(String name)
MyThread my1 = new MyThread("高铁");
MyThread my2 = new MyThread("飞机");
my1.start();
my2.start();
//static Thread currentThread() 返回对当前正在执行的线程对象的引用
//此方法是一个静态方法,直接用类名调用
System.out.println(Thread.currentThread().getName());
}
}
线程调度
两种调度方式
分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一
个,优先级高的线程获取的 CPU 时间片相对多一些
Java使用的是抢占式调度模型
随机性
假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也
就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一
定的
sleep演示:
static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数
public class ThreadSleep extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep ts1 = new ThreadSleep();
ThreadSleep ts2 = new ThreadSleep();
ThreadSleep ts3 = new ThreadSleep();
ts1.setName("曹操");
ts2.setName("刘备");
ts3.setName("孙权");
ts1.start();
ts2.start();
ts3.start();
}
}
Join演示:
public class ThreadJoin extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
}
//void join() 等待这个线程死亡其他的线程才能执行
public class ThreadJoinDemo {
public static void main(String[] args) {
ThreadJoin tj1 = new ThreadJoin();
ThreadJoin tj2 = new ThreadJoin();
ThreadJoin tj3 = new ThreadJoin();
tj1.setName("康熙");
tj2.setName("四阿哥");
tj3.setName("八阿哥");
tj1.start();
try {
tj1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
tj2.start();
tj3.start();
}
}
//void setDaemon(boolean on)
//将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
Daemon演示:
public class ThreadDaemon extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
td1.setName("关羽");
td2.setName("张飞");
//设置主线程为刘备
Thread.currentThread().setName("刘备");
//设置守护线程
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
for(int i=0; i<10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class myrunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<100;i++){
//因为没有继承thread所以没法直接使用getName方法
System.out.println(Thread.currentThread().getName()+" :"+i);
}
}
}
public class myrunnabletest {
public static void main(String[] args) {
myrunnable my = new myrunnable();
//Thread(Runnable target) 分配一个新的Thread对象
// Thread th1=new Thread(my);
// Thread th2=new Thread(my);
// th1.setName("张三");
// th2.setName("李四");
//Thread(Runnable target, String name) 分配一个新的Thread对象
Thread th1=new Thread(my,"张三");
Thread th2=new Thread(my,"李四");
th1.start();
th2.start();
}
}
多线程的实现方案有两种
继承Thread类
实现Runnable接口
相比继承Thread类,实现Runnable接口的好处
避免了Java单继承的局限性
适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现
了面向对象的设计思想
网络编程
IP地址
要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数
据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识
端口
网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区
分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序
了。也就是应用程序的标识
协议
通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定
的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则
被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守
才能完成数据交换。常见的协议有UDP协议和TCP协议
DOS常用命令:
ipconfig:查看本机IP地址
ping IP地址:检查网络是否连通
特殊IP地址:
127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
import java.net.InetAddress;
import java.net.UnknownHostException;
public class l1 {
public static void main(String[] args) throws UnknownHostException {
InetAddress address = InetAddress.getByName("127.0.0.1");
//public String getHostAddress():返回文本显示中的IP地址字符串
String hostAddress = address.getHostAddress();
//public String getHostName():获取此IP地址的主机名
String hostName = address.getHostName();
System.out.println(hostAddress+" "+hostName);
}
}
端口和协议
端口
设备上应用程序的唯一标识
端口号
用两个字节表示的整数,它的取值范围是0-65535。其中,0~1023之间的端口号用于一些知名的网络服
务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会
导致当前程序启动失败
协议
计算机网络中,连接和通信的规则被称为网络通信协议
UDP协议
用户数据报协议(User Datagram Protocol)
UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台
计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在
收到数据时,也不会向发送端反馈是否收到数据。
由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太
大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在
传输重要数据时不建议使用UDP协议
TCP协议
传输控制协议 (Transmission Control Protocol)
TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数
据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由
客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”
三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
第一次握手,客户端向服务器端发出连接请求,等待服务器确认
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
第三次握手,客户端再次向服务器端发送确认信息,确认连接
完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,
TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等
创建发送端的Socket对象(DatagramSocket)
创建数据,并把数据打包
调用DatagramSocket对象的方法发送数据
关闭发送端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UDPsendTest {
public static void main(String[] args) throws IOException {
/*
UDP发送数据:
数据来自于键盘录入,直到输入的数据是886,发送数据结束
*/
//创建发送端的Socket对象(DatagramSocket)
// DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口
DatagramSocket ds = new DatagramSocket();
Scanner sc = new Scanner(System.in);
try {
while (true) {
String message = sc.nextLine();
//输入的数据是886,发送数据结束
if ("886".equals(message)) {
break;
}
byte[] bys = message.getBytes();
//创建数据,并把数据打包
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
//构造一个数据包,发送长度为 length的数据包到指定主机上的指定端口号。
DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("127.0.0.1"), 10086);
//调用DatagramSocket对象的方法发送数据
//void send(DatagramPacket p) 从此套接字发送数据报包
ds.send(dp);
}
} finally {
System.out.println("发送结束");
//关闭发送端
//void close() 关闭此数据报套接字
ds.close();
}
}
}
接收数据的步骤
创建接收端的Socket对象(DatagramSocket)
创建一个数据包,用于接收数据
调用DatagramSocket对象的方法接收数据
解析数据包,并把数据在控制台显示
关闭接收端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPrecieveTset {
public static void main(String[] args) throws IOException {
//创建接收端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket(10086);
/*
UDP接收数据:
因为接收端不知道发送端什么时候停止发送,故采用死循环接收
*/
while (true){
byte[] bys = new byte[1024];
//创建一个数据包,用于接收数据
DatagramPacket dp = new DatagramPacket(bys, bys.length);
//调用DatagramSocket对象的方法接收数据
ds.receive(dp);
System.out.println("接收的数据是:"+new String(dp.getData(),0,dp.getLength()));
}
}
}
Java中的TCP通信
Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过
Socket产生IO流来进行网络通信。
Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
Lambda表达式
启动一个线程,在控制台输出一句话:多线程程序启动了,三种方式
public class lambda01 implements Runnable{
@Override
public void run() {
System.out.println("多线程启动了1");
}
public static void main(String[] args) {
//方法一传实现了runable的接口的对象
lambda01 lambda01 = new lambda01();
Thread thread = new Thread(lambda01);
thread.start();
//方法二采用匿名内部类
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程启动了2");
}
});
thread1.start();
//方法三采用lambda表达式
Thread thread2=new Thread(()->{
System.out.println("多线程启动了3");
});
thread2.start();
}
}
格式:
(形式参数) -> {代码块}
形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
->:由英文中画线和大于符号组成,固定写法。代表指向动作
代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
组成Lambda表达式的三要素:
形式参数,箭头,代码块
类加载
类加载的描述
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始
化这三个步骤来对类进行初始化。如果不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把
这三个步骤统称为类加载或者类初始化
类的加载
就是指将class文件读入内存,并为之创建一个 java.lang.Class 对象
任何类被使用时,系统都会为之建立一个 java.lang.Class 对象
类的连接
验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
准备阶段:负责为类的类变量分配内存,并设置默认初始化值
解析阶段:将类的二进制数据中的符号引用替换为直接引用
类的初始化
在该阶段,主要就是对类变量进行初始化
类的初始化步骤
假如类还未被加载和连接,则程序先加载并连接该类
假如该类的直接父类还未被初始化,则先初始化其直接父类
假如类中有初始化语句,则系统依次执行这些初始化语句
注意:在执行第2个步骤的时候,系统对直接父类的初始化步骤也遵循初始化步骤1-3
类的初始化时机
创建类的实例
调用类的类方法
访问类或者接口的类变量,或者为该类变量赋值
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
初始化某个类的子类
直接使用java.exe命令来运行某个主类
类加载器
负责将.class文件加载到内存中,并为之生成对应的 java.lang.Class 对象。虽然我们不用过分关心类加载机
制,但是了解这个机制我们就能更好的理解程序的运行!
JVM的类加载机制
全盘负责:就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载
器负责载入,除非显示使用另外一个类加载器来载入
父类委托:就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器
无法加载该类时才尝试从自己的类路径中加载该类
缓存机制:保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜
索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成
Class对象,存储到缓存区
Java中的内置类加载器
Bootstrap class loader:它是虚拟机的内置类加载器,通常表示为null ,并且没有父null
Platform class loader:平台类加载器可以看到所有平台类 ,平台类包括由平台类加载器或其祖先定义的Java
SE平台API,其实现类和JDK特定的运行时类
System class loader:它也被称为应用程序类加载器 ,与平台类加载器不同。 系统类加载器通常用于定义应
用程序类路径,模块路径和JDK特定工具上的类
类加载器的继承关系:System的父加载器为Platform,而Platform的父加载器为Bootstrap
public class ClassLoaderDemo {
public static void main(String[] args) {
//static ClassLoader getSystemClassLoader():返回用于委派的系统类加载器
ClassLoader c = ClassLoader.getSystemClassLoader();
System.out.println(c); //AppClassLoader
//ClassLoader getParent():返回父类加载器进行委派
ClassLoader c2 = c.getParent();
System.out.println(c2); //PlatformClassLoader
ClassLoader c3 = c2.getParent();
System.out.println(c3); //null
}
}
反射的概述
获取Class类对象的三种方式
类名.class属性
对象名.getClass()方法
public class fs01 {
public static void main(String[] args) throws ClassNotFoundException {
//使用类的Class属性来获取该类的Class对象
Class<student> studentClass = student.class;
System.out.println(studentClass);
Class<student> studentClass2 = student.class;
System.out.println(studentClass2==studentClass);
System.out.println(studentClass2);
//调用对象的getclass()方法,返回该对象所属类对应的Class对象
student student = new student();
Class<? extends student> aClass = student.getClass();
System.out.println(studentClass==aClass);
//使用Class类中的静态方法forname(string classname)
Class<?> student1 = Class.forName("student");
System.out.println(student1==studentClass);
}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class reflect02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取Class对象
Class<?> reflect = Class.forName("student");
//Constructor>[] getConstructors() 返回一个包含 Constructor对象的数组,
//Constructor对象反映了由该 Class对象表示的类的所有公共构造函数
// Constructor>[] cons = c.getConstructors();
//Constructor>[] getDeclaredConstructors() 返回反映由该 Class对象表示的类
//声明的所有构造函数的 Constructor对象的数组
Constructor<?>[] cons= reflect.getDeclaredConstructors();
for (Constructor c:cons){
System.out.println(c);
}
//运行结果
//public student()
//public student(int,int,java.lang.String,java.lang.String)
//Constructor getConstructor(Class>... parameterTypes)
//返回一个Constructor对象,该对象反映由该 Class对象表示的类的指定公共构造函数
//Constructor getDeclaredConstructor(Class>... parameterTypes)
//返回一个 Constructor对象,该对象反映由此 Class对象表示的类或接口的指定构造函数
//参数:你要获取的构造方法的参数的个数和数据类型对应的字节码文件对象
Constructor<?> constructor = reflect.getConstructor();
//Constructor提供了一个类的单个构造函数的信息和访问权限
//T newInstance(Object... initargs) 使用由此 Constructor对象表示的构造函数,
//使用指定的初始化参数来创建和初始化构造函数的声明类的新实例
Object o = constructor.newInstance();
System.out.println(o);
Constructor<?> constructor1 = reflect.getConstructor(int.class, int.class, String.class, String.class);
Object o1 = constructor1.newInstance(12, 1, "张三", "男");
System.out.println(o1);
Constructor<?> constructor2 = reflect.getDeclaredConstructor(String.class);
//暴力反射 可以使用私有的方法
//public void setAccessible(boolean flag):值为true,取消访问检查
constructor2.setAccessible(true);
Object o2 = constructor2.newInstance("李四");
System.out.println(o2);
}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class reflect03 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取Class对象
Class<?> stuclass = Class.forName("student");
Field[] declaredFields = stuclass.getDeclaredFields();
//Field[] getFields() 返回一个包含 Field对象的数组, Field对象反映由该 Class对象表示的类或接口的所有可访问的公共字段
//Field[] getDeclaredFields() 返回一个 Field对象的数组,反映了由该 Class对象表示的类或接口声明的所有字段
for (Field f:declaredFields){
System.out.println(f);
}
//遍历结果
//private int student.age
//private int student.id
//private java.lang.String student.name
//private java.lang.String student.sex
//Field getField(String name) 返回一个 Field对象,该对象反映由该 Class对象表示的类或接口的指定公共成员字段
//Field getDeclaredField(String name) 返回一个 Field对象,该对象反映由该Class对象表示的类或接口的指定声明字段
//获取无参构造方法创建对象
Constructor<?> con = stuclass.getDeclaredConstructor();
Object obj = con.newInstance();
Field agefield = stuclass.getDeclaredField("age");
agefield.setAccessible(true);
agefield.set(obj,12);
Field idfield = stuclass.getDeclaredField("id");
idfield.setAccessible(true);
idfield.set(obj,1);
Field namefield = stuclass.getDeclaredField("name");
namefield.setAccessible(true);
namefield.set(obj,"张三");
System.out.println(obj);
}
}