1、JAVASE基础:
-
1995年由sun公司推出,1996年正式上市。
-
Java之父:詹姆斯 高斯林
-
2009年被Oracle(甲骨文)收购,前身oak语言
-
软件开发两大架构:
-
B/S:浏览器服务器架构 淘宝、新浪、京东....
-
C/S:客户端服务器 QQ、微信、超市管理系统....
-
-
Java的领域:
-
JavaSE java标准版 桌面级应用开发 C/S
-
JavaEE Java企业版 B/S
-
JavaME Java微小版 Andriod IOS 结合嵌入式 ---- 几乎没人用
-
-
Java的版本号: Java1.0 --------- >Java17(?)
-
java1.0 ----- java1.2路程碑 分出 Java三大领域
-
java1.5时修改语法
-
java1.8开始新增语法
-
Java现在用的时Java8(JDK8)
-
-
JDK安装:
-
点击JDK,直接下一步安装,默认安装在C盘(推荐)
-
配置环境变量:
-
先配置JAVA_HOME:里面输入的jdk/bin
-
再去配置 Path(系统变量中的Path):%JAVA_HOME%(win7需要在最后加;(英文状态下))
-
-
检查环境变量是否配置成功Win+R进入DOS命令行,输入java和javac,出现一堆文字,配置成功
-
-
DOS命令行常用指令:
-
进入dos win+r
-
进入电脑中的某一个盘: 盘符名: C:
-
列举文件夹内容: dir
-
进入文件夹 cd 文件夹名
-
-
Java的第一个程序:
-
先创建一个java文件(.java),文件名首字母大写,不要使用中文
-
在文件中写代码
-
public class Test1{ public static void main(String[] args){ System.out.println("Hello World"); } }
-
进入dos命令行 先输入javac 文件名.java ----- 会出现一个.class文件 然后java 文件名(不带后缀名)
-
-
Java文件的运行机制:
-
JVM:Java虚拟机,它是java的核心和基础,想要运行Java文件必须右jvm
-
JRE:Java运行环境,运行Java文件的必要属性
-
JDK:Java开发工具包 ,想要编写Java代码必须安装jdk
-
JVM、JRE、JDK三者之间关系:
-
JRE:JVM+核心类库
-
JDK:JRE+Java开发工具
-
安装往jre之后才会有jvm
-
-
Java文件的运行流程:
-
-
Java语言的特点:
-
跨平台性:一处编写,处处可以运行。(可以在不同的电脑操作系统上运行)
-
-
面向对象性:Java是面向对象的语言,简称OOP 面向对象的三大特征:封装、继承、多态
-
多线程:Java支持多线程,允许一个程序可以有多个任务并发执行,效率高
-
-
安全性:提供了网络应用的类库
-
健壮性:Java是强类型语言,严格区分代码的大小写,并且严格区分每种数据类型,一句话结束之后必须有分号
-
-
Java语言的缺点:
-
运行速度比较慢
-
不适合图像、游戏、效果开发
-
-
Java开发工具(IDE):
-
eclipse----免费的开发工具
-
idea---收费的软件(免费试用一个月)
-
STS---eclipse定制版(Spring定制版)
-
MyEclipse-----几乎没人用
-
-
IDEA常用的快捷键:
-
ctrl+shift+F10 运行快捷键
-
整理代码格式: ctrl + alt + l
-
注释的快捷键:
-
单行注释: // 单行注释 ctrl+/ 单行注释一般用于一句代码结束之后
-
多行注释: /* 多行注释 */ ctrl+shift+/ 一般用于注释很多行代码或者需要写较多的注释
-
文档注释: / ** 内容 * / 标记代码作者、代码编写日期和时间、指明参数类型、方法返回值....
能够生成html文档,方便查阅。
-
生成方式:
-
指定字符集 -encoding utf-8 -charset utf-8
-
-
-
2、Java的语法:
/* 定义一个Java的类 class是类的关键字*/ /* class关键字后面跟的是类名 * 类名的规范: * 1.不能以数字开头,由字母、数字、_、$构成 * 2.类名不能用Java中的关键字 * 3.推荐使用英文,首字母必须大写(每个单词的首字母)--大驼峰命名法(每个单词的首字母大写) TestDemo--大驼峰 demoLike--小驼峰 * */ public class Test2 { /* Java语言的主方法或者main方法 * 主方法是程序的入口,程序执行的起始点 * 一个普通项目中至少要有一个主方法 * */ public static void main(String[] args) { /* Java的输出语句 * System.out.println(); 换行输出 --- 换行输出语句里面不输出任何东西可以表示换行 * System.out.print(); 不换行输出 --- 不换行输出语句中必须指定输出内容 * Java输出的内容: * 如果先输出数字 直接写数字 * 如果想要输入文本内容(单词、汉语、句子)---- 必须套上""变成字符串 * 可以输出一个计算表达式 * 字符串 + 内容 ----- 相当于做字符串拼接 得到的结果一定是字符串 * ; : 代表一个语句的结束 每写完一个语句后面必须加分号 * 两种特殊的转移字符: * \n : 换行 * \t :水平制表符(8个小空格)(相当于键盘上的Tab键) * \\ : 转义\ * 两个双引号连在一起是空字符串 * Java中严格区分双引号和单引号 * "" -- 字符串 * ''-- 字符 单引号中只能有一个字母或者字符 * 一个Java源文件中可以由多个类,只能有一个类带有public修饰(主类) * */ System.out.println(123); // 输出数字123 System.out.println("123"); // 输出字符串123 System.out.println(2 + 3); // 直接计算结果 System.out.println("2" + 3); // 字符串23 System.out.println("你\n好"); System.out.println("你\t好"); System.out.println("你\\好"); System.out.print(""); // 空字符 /* 字符串中的内容不会进行任何运算 */ System.out.println("2 + 3 + 2" + 1); System.out.println(2 + 3 + 2 + "" + 1); // 字符串71 System.out.println('1'); // 字符 } }
-
如何在控制太输入内容:
-
/* * 在空绘太输出语句:Scanner类 * 1. Scanner 自定义名 = new Scanner(System.in); --- 只需要定义一次 * 2. 写一个提示语句(选择写) * 3. 如果想要输入一个字符串: 自定义名.next(); * 如果想要输入一个整数: 自定义名.nextInt(); * 如果想要输入小数: 自定义名.nextDouble(); * */ Scanner input = new Scanner(System.in); System.out.print("请输入一句话"); /* 创建一个字符串的变量: * String 字符串名 = "内容"; * String 字符串名 = 自定义名.next(); * */ String str = input.next(); System.out.println(str); /*在控制台输入一个整数*/ System.out.print("请输入一个整数"); System.out.println(input.nextInt()); /*在控制台输入一个小数*/ System.out.print("请输入一个小数:"); System.out.println(input.nextDouble());
-
包名的要求:公司域名反写 zretc.com ----- com.zretc.xxx
3、数据类型:
-
变量:是一个数据存储空间的表示,是内存中的一块存储区域、一次可以多次改变变量中的值
-
作用:一次定义,多次使用,从而提高效率
-
变量的语法声明:
-
先声明,在赋值: 数据类型 变量名 ; 变量名 = 变量值;
-
边声明边赋值: 数据类型 变量名 = 变量值;
-
可以同时声明多个同类型的变量: 数据类型 变量1,变量2,...;
-
-
变量没有赋值的时候不能拿去使用
-
变量的命名要求:
-
不能以数字开头,由字母、数字、_、$组成
-
不能使用关键字
-
严格区分大小写
-
推荐采用小驼峰命名法,stuName
-
-
常量:一旦定义之后,值不允许改变
-
常量的声明语法: final 数据类型 常量名 = 常量值; 常量名每个字母都要大写
-
数据类型:确定常量或变量的类型,从而确定该变量或常量在内存中分配的大小
-
数据类型:基本数据类型、引用数据类型
-
引用数据类型:数组、类、接口、枚举、注解
-
String是类中的一种,String是应用数据类型
-
基本数据类型:
-
4类:整数型、浮点型、字符型、布尔型
-
8种:byte、short、int、long(整数型) float、double(浮点型)、char(字符型)、boolean(布尔型)
-
-
计算机常见存储单位:
-
bit位 最小的单位
-
字节(byte) 8个bit位构成一组叫做一字节 1字节 = 8bit位
-
K 1024字节 = 1K
-
M 1024K = 1M
-
G 1024M = 1G
-
二进制:只有0和1
-
八进制:0-7,以0开头
-
十六进制:0-F ,以0x开头
-
-
整数型:
-
byte 字节型 所占字节数:1字节 所占位:8位 取值范围:-128~127
-
short 短整形 所占字节:2字节 所占位:16位 取值范围:-32768~32767
-
int 整形 所占字节:4个字节 所占位:32位 取值范围:-2 ^31~2^31-1
-
long 长整型 所占字节:8字节 所占位:64位 取值范围:-2^63~2^63-1
-
注意:整数型默认类型为int类型,在定义long类型时,需要在值后面加l或者L
-
-
浮点型:(小数)
-
float 单精度浮点类型 所占字节:4字节 所占位:32位+?
-
double 双精度浮点类型 所占字节:8个字节 所占位: 64位+?
-
浮点型永远大于整型
-
注意:浮点型默认类型位double,如果想要表示flost类型的数,需要在数后面加F或f
-
-
浮点类型遇到的问题:精度缺失问题
-
/*处理精度缺失*/ double d1 = 0.1; double d2 = 0.2; System.out.println(d1 + d2); // 用BigDecimal类处理精度缺失问题 BigDecimal bd1 = new BigDecimal("0.1"); BigDecimal bd2 = new BigDecimal("0.2"); /*加法运算*/ System.out.println(bd1.add(bd2));
-
-
字符型:(''单引号表示)
-
char 字符型('') 所占字节:2字节 所占位数:16位 取值范围:0~65535(没有符号位)
-
Java中采用的UTF-8编码格式(支持中文)
-
字符型可以转换成整数 参照ASCII表转换
-
0:48 A:65 - Z:90 a:97 - z:122
-
一个汉字是一个字符,两个字节
-
转义字符:\t 制表符 \n 换行 \ \ 转义出\ \" 转换为"
-
-
布尔型(boolean):逻辑值
-
逻辑值 true(真) / false(假),没有0,1
-
占位:一个字节(了解)
-
不参加大小顺序比较,也不参加类型转换
-
默认值 false------直接写在类中的才有默认值,主方法中的变量没有默认值
-
-
基本数据类型的大小顺序:
-
小->大: byte < short / char < int < long < float < double
-
大->小:double > float > long > int > short / char > byte
-
-
基本数据类型的转换:
-
自动转换(隐式转换): 小类型给大类型赋值,自动转换 大类型 = 小类型
-
强制转换(显式转换): 大类型给小类型赋值,需要强制将大类型转换位小类型 小类型 = (小的数据类型)大类型
-
一定要注意short和char:大小相同,内部自动转换 char s1 = (short)10; 强制转换
-
-
-
基本数据类型存放在内存中栈 栈中存放的是基本数据类型和地址值
-
基本数据类型和字符串的堆栈图:
4、运算符
-
运算符分类:算术运算符、比较运算符、逻辑运算符、三目运算符(三元运算符)、赋值运算符、位移运算符(了解)
-
算术运算符:
-
+: 如果加号前后是两个数直接计算结果,加号前后如果出现字符串相当于字符串拼接
-
-:减法
-
*:乘法
-
/:除法 左右两边为整数的时候,结果一定是整数;左右两边如果有一个小数,结果一定为小数
-
%:求模(取余) 计算余数 3 % 2 = 1
-
++:自增
-
++a:前++ a先自加1,然后在计算其他结果
-
a++:后++ 先用a的原值算其他的,其他全部算完之后a在加1
-
-
--:自减
-
--a:a先自减1,然后在计算其他结果
-
a--: a先用原值计算其他的,算完其他之后a在自减1
-
-
-
比较运算符:比较大小,比较运算符的结果一定是boolean的
-
'>'
-
'<'
-
'>='
-
'<='
-
!= : 不等于 ,判断是否不等
-
== : 判断是否相等 判断栈中的值是否相等
-
// new对象的形式比较 String str1 = new String("hello") ; String str2 = new String("hello"); // 字符串判断是否相等时,不要使用== System.out.println(str1 == str2); // false
-
-
逻辑运算符:操作都是布尔类型的
-
与
-
短路与: && 当符号左面操作为假,符号右面操作都不执行 表达式1&&表达式2 一个假即为假
-
逻辑与:& 全部都得执行 一个假即为假
-
-
或
-
短路或:|| 表达式1||表达式2 表达式1为真,表达式2不执行 只要有一个真结果即为真
-
逻辑或:| 全部执行 只要有一个真结果即为真
-
-
非
-
!:相当取反 真变假,假变真
-
-
位移运算符(了解):(位移运算的最快的)
-
<< 左位移 数乘以2的n次幂
-
'>>' 右位移 数除以2的n次幂
-
'>>>' 无符号右移 数除以2的n次幂
-
^ : 异或 将数字转换位二进制数,比较每一位,不同的为1,相同的为0
-
&: 按位与 将数字转换二进制数,比较每一位,同1则1
-
| : 按位或 将数字转换二进制数,比较每一位,有1则1
-
-
-
三目运算符
-
表达式 boolean类型表达式?结果1:结果2
如果boolean类型表达式结果为true,则得到结果1,否则得到结果2
Scanner s = new Scanner(System.in);//获取从控制台获取输入内容的工具 System.out.println("请输入你要比较的两个整数:"); int a = s.nextInt();//从控制台 获取一个整数 赋值给a 15 25 int b = s.nextInt();//同上 赋值给b 17 17 String result1 = a+">"+b;//组装”a>b“的字符串 其中a、b都是变量 ”15>17“ "25>17" 赋值给result1 String result2 = a+"<"+b;//同上 ”25<17“ 赋值给result2 //如果a>b(15>17)返回true 则得到result1(”15>17“) //如果返回false 则得到result2 ("15<17") String result = a>b?result1:result2; System.out.println(result);
-
5、分支语句
-
if语句
-
if语句 简单理解为如果语句
-
语法规则
-
不使用else 多个if的条件语句的判断条件没有关联性(判断的变量不相同) (a<b b<c)
-
使用else 多个if的条件语句的判断条件有关联性(判断的变量相同)(no1 no2,money>10,money>50)
/*如果boolean表达式1成立(返回true),则运行第一个{}中的内容,否则如果boolean表达式2成立,运行第二个{},都不成立,运行第三个 if(boolean表达式1){ }else if(boolean表达式2){ }else{ } {}在只有1行语句时,可以省略,多行时必须写(推荐必须写) else 不是必须的 if else if else if可以写多个 */ Scanner s = new Scanner(System.in); System.out.println("请输入要比较的两个整数:"); int a = s.nextInt(); int b = s.nextInt(); if(a>b){ System.out.println(a+"比"+b+"大"); }else if(a<b) { System.out.println(a+"比"+b+"小"); }else{ System.out.println(a+"和"+b+"相等"); }
-
-
swtich语句
-
语法
-
switch(表达式) { case 常量表达式1: 语句1; case常量表达式2: 语句2; ...... case常量表达式n : 语句n; default: 语句n+1; }
-
switch的()里,写的是一个变量名,针对这个变量进行枚举(一个一个的进行举例,叫枚举),case里面的内容,是一个常量(数字或字符串,类型需要与switch中的变量保持一致)。
-
case后面,在代码最后一行,要写break;表示跳出当前的代码块({}圈起来的部分,叫代码块)
-
Scanner s = new Scanner(System.in); int no = s.nextInt(); switch (no){ case 1: System.out.println("夏令营"); break;//跳出 不再向下执行 case 2: System.out.println("ipad"); break; case 3: System.out.println("移动硬盘"); break; default: System.out.println("啥也不给"); }
6、循环语句
-
定义:
//循环语的作用就是反复执行一段代码(代码块{}),直到满足终止循环的条件为止。 //如果没有终止循环的条件,这段代码会一直反复执行,执行到死(死循环)
-
for循环
-
语法
int a = 1;//循环初始语句 for(;a<100;){//;;中间的a<100 是循环继续执行的条件 System.out.println(a); a++;//更新a的值,达到终止循环的条件 如果不对(不写),死循环 } System.out.println("----------下面写法由上面写法变化而来---------"); for(int b=1;b<100;b++){ System.out.println(b); } System.out.println("----------b++在循环体执行结束后才执行---------");
-
9x9乘法表
-
9行9列的*
for(int r=1;r<10;r++){//循环的嵌套 for (int c = 1; c < 10; c++) {//根据r执行9次 System.out.print(" * ");//根据c执行9次 因为c又根据r 执行9次 共执行81次 } System.out.println();//9次 根据r的变化执行 他属于外层循环的代码块 }
-
9行,第n行打印n颗星
for(int r=1;r<10;r++){//外层循环r的变化 代表行的变化 for (int c = 1; c <= r; c++) {//层存循环c的变化 代表的是每一行列的变化 System.out.print(" * "); } System.out.println(); }
-
9x9乘法表
//9x9乘法表 for(int r=1;r<10;r++){//外层循环r的变化 代表行的变化 for (int c = 1; c <= r; c++) {//层存循环c的变化 代表的是每一行列的变化 System.out.print(""+c+" x "+r+" = "+r*c+" \t"); } System.out.println(); }
-
-
break关键字:跳出当前的循环,从当前循环的下一行代码开始执行
如果是多层循环,只跳出当前的循环
for(int j=2;j<50;j++) { for (int i = 2; i <= j; i++) { if (j % i == 0) { System.out.println("能被" + j + "整除的最小的整数是" + i + ""); break;//多层循环嵌套的时候,只跳出当前循环 } } }
-
continue关键字:忽略循环中continue关键字下的所有代码,执行下一次循环(没有跳出循环)
for(int i=0;i<10;i++){ if(i%2==0){ //当i为偶数时 if条件成立 执行continue,忽略了循环本次下面的所有代码 执行下一次循环 continue; } System.out.println(i); }
-
-
while循环
-
语法
-
控制语句编写再循环体的最后一行
-
for和while,当能确定循环次数的时候使用for,不能确定时使用while
-
-
do while循环
-
语法
-
while 和 do while的区别
while是先判断条件是否成立,再执行
do while 无条件执行一次 再判断
-
-
-
代码规范
-
一行 要么以;结尾,要么以{结尾
-
以;结尾后,下一行首字母和上一行再一列
-
以{结尾后,下一行要进行缩进
-
语句块的括回},要和括出那一行的首字母的列对其
-
7、数组
-
定义:是一组相同数据类型的数据的集合;
-
声明:两种形式 int[] array1;
int array2[];
推荐使用第一种,避免和int类型的声明混淆。
-
初始化:两种方式。
-
静态初始化
int[] array3 = {1,3,4,5,7};//在初始化的时候 直接给数组元素赋值,数组的元素分别为1,3,4,5,7 //静态初始化时 在数组声明时完成(1行写完,不能分两行,否则使用new int[]{},相当于重新创建一个数组)
-
动态初始化
//对array1这个数据进行初始化 整数类型的数组 长度是5 int[] array1 = new int[5]; //数组的所有的元素 都是默认是0
-
-
数组的元素
//数组中的元素 采用数组名[索引值(下标)]来获取 //索引值编号 从0开始,最后一个元素 索引为数组的长度减1 System.out.println(array1[1]);//数组的第二个元素 System.out.println(array3.length);//数组的长度 长度为5 System.out.println(array3[array3.length-1]);//数组的最后一个元素
-
数组的遍历
for(int tmp:array){//增强型循环 只能在遍历时使用 System.out.println(tmp); } for(int i=0;i<array.length;i++){ System.out.println(array[i]); }
-
二维数组
如果把数组理解为数据的组(一维),那么二维数组就是数组的数组。
二维数组的长度,为一维数组的个数。
取二维数组中的元素,使用arrayi
-
int[][] array = new int[5][3];//创一个二维数组 5行3列 15个元素 int[][] array1 = {new int[3],new int[3],new int[3],new int[3],new int[3]}; int[][] array2 = {{1,2,3},{4,2,3},{1,2,6},{1,5,9},{1,22,33}}; int[][] array3 = { {0,0,0,0,0,0,0,0,0}, {0,0,1,0,2,2,3,2,0}, {0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0}, {1,1,1,1,1,0,0,1,1}, {1,1,1,1,1,0,0,1,1} }; int[][] array4 = {};// int[][] array5 = {{}};// int[][] array6 = {new int[3],{1,2}}; System.out.println(array4.length); System.out.println(array5.length); System.out.println(array.length); System.out.println(array6.length); System.out.println(array3.length); System.out.println(array3[1][6]);//3 System.out.println(array6[1][1]);//2
8、类和对象
-
面向过程式
想到哪就写到哪,约到问题,立刻就解决问题,修改代码。
这种写法缺少设计,代码的重用性低,整体的逻辑和可读性不强。
面向过程是具体化的,流程化的: 解决一个问题,你需要一步一步的分析,一步一步的实现。
-
面向对象式
面向对象是模块化的,将多个模块合并到一起。
易维护、易复用、易扩展,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护。
三个特点:封装、继承和多态。
-
抽象
将一些事物的共同特点归纳总结的过程,叫抽象。
-
类:
在抽象过程中,对共同特点的总结,形成了一个新的类型,给他起个名字,叫做类。它是概念上的定义。
类中包括属性和方法,属性是指数值类的特征,方法是行为类的特征(动作)。
-
对象:
万物皆对象,满足这个类型的特点,真实存在的实例,叫对象。
-
属性:
抽象出来的数值类特征。类中定义的属性,可以不初始化,系统会默认初始化,但也可以进行初始化。
no没有初始化,调用时no默认为0,age初始化为1,调用时就是1。
-
方法:
抽象出来的行为类特征。
对于一些复杂的代码逻辑,如果希望重复使用这些代码,并且做到“随时任意使用”,那么就可以将这些代码放在一个大括号“{}”当中,并且起一个名字。使用代码的时候,直接找到名字调用即可。
-
语法格式:
【访问权限修饰符】【修饰符】返回值数据类型 方法名 ( 形式参数列表 ) { 执行语句; return 返回值; }
-
方法的命名:方法名的第一个单词应以小写字母作为开头,后面的单词则使用驼峰命名法。sendMessge
-
访问权限修饰符及修饰符:都在后续学习中详细学习
-
参数列表:就是变量列表,用于存储调用方法时传递给方法的实际参数。 语法:方法名(数据类型1 参数名1,数据类型2 参数名2,数据类型3 参数名3....) 注意:参数的个数可以是0个、1个或者多个
int... a 表示可变参数,类型必须是int,个数可以发生变化。
-
return:用于结束方法以及返回方法指定类型的值。
-
返回值:该方法执行后的结果,该结果会返回给调用者。
-
方法体:方法体里面不能写方法,可以写一切符合java规范的代码。
public class TestMethod { //定义一个方法,这个方法能打印一首诗 //方法名 首字母小写 方法名后要跟() //方法名之前 要编写方法的返回类型 //当方法不需要返回结果时,我们使用void 表示无返回 void methodName(){ System.out.println("清明时节雨纷纷,路上行人欲断魂!");//方法的执行语句 return;//方法返回结束 void的时候,可以不写 } //定义一个方法 可以将一个数组 进行排序(从小到大) //()里,可以添加参数,在调用时传递进来,这个int[] array 起到占位的作用 //需要将排好序的结果返回,返回类型为int[] int[] sort(int[] data){ int tmp = 0; for(int i=0;i<data.length-1;i++){ for(int j=i+1;j<data.length;j++){ if(data[i]<data[j]){ tmp = data[i]; data[i] = data[j]; data[j] = tmp; } } } return data; } }
-
-
值传递:当一个变量,作为参数传递到一个方法中的时候,在方法的内部,只能修改这个变量的值,不能修改它的地址。
简单类型:在方法内部对其进行修改,就相当于修改地址,修改后外部的值不变。
引用类型:在方法内部如果对这个对象直接进行赋空值操作,就是对其地址进行修改,方法外部不变。如果对这个对象的属性进行修改,相当于修改它的值,方法外部跟着改变。、
数组类型:元素相当于值,对象本身是地址。
public class TestByValue { public static void main(String[] args) { int i = 5; modifyInt(i); System.out.println("方法外部i->"+i); Person p = new Person(); p.setAge(15); modifyPerson(p); System.out.println("方法外部p->"+p.getAge()); modifyPerson1(p); System.out.println("方法外部p->"+p); ArrayList list = new ArrayList(); list.add("1"); list.add("2"); modifyArrayList(list); System.out.println("方法外部list->"+list); modifyArrayList1(list); System.out.println("方法外部list->"+list); } public static ArrayList modifyArrayList1(ArrayList list){ list = null; System.out.println("方法内部list->"+list); return list; } public static ArrayList modifyArrayList(ArrayList list){ list.add("3"); System.out.println("方法内部list->"+list); return list; } public static Person modifyPerson1(Person p){ p = null; System.out.println("方法内部p->"+p); return p; } public static Person modifyPerson(Person p){ p.setAge(20); System.out.println("方法内部p->"+p.getAge()); return p; } public static int modifyInt(int i){ i++; System.out.println("方法内部i->"+i); return i; } }
-
对象的创建和使用
创建一个类的对象,通过对象去调用类中定义的属性和方法。属性和方法都可以多次调用,从而实现代码的重用(参考Random类,通过new Random(),多次调用nextInt(),每次调用都可以获取一个新的随机数,如果不能多次调用的话,每次使用都需要编写那几行代码)。
调用时,采用 对象名.方法名/属性名 的方式
一个类,可以创建无数个对象。
public class Student {//class 是类的意思 ,这样代码是声明一个类型叫Student //属性 在Student类中 定义的数值特征 叫属性 String name;//姓名 类的属性 不需要初始化,系统会默认初始化,能初始化 String sex;//姓名 默认null int age = 1;//年龄 初始化为1 int no;//学号 默认为0 //方法 定义的行为特征 void study(){//学习 方法中,可以写多行代码 System.out.println("听课"); System.out.println("敲代码"); System.out.println("完成作业"); } void show(){ System.out.println("我叫"+name+",性别"+sex+",今年芳龄"+age); } void sleep(){//睡觉 System.out.println("洗脸"); System.out.println("玩会手机"); System.out.println("手机砸脸上了,睡着了"); } }
public class TestStudent { public static void main(String[] args) { //创建一个对象 声明一个Student类型的变量叫s 是一个新学生(对象) Student yanghui = new Student(); yanghui.study();//调用这个对象的类中的方法 yanghui.name = "杨慧"; yanghui.sex = "女"; yanghui.age = 18; yanghui.no = 1; yanghui.show(); System.out.println("-------------------------------------"); //创建一个对象,可以多次调用类中的方法 实现代码的重用, Student ylx = new Student(); ylx.show(); ylx.study(); } }
-
全局变量和局部变量
全局变量:在类中定义的属性,在整个类的所有方法中都可以使用。使用之前可以不进行初始化,系统会默认初始化。
局部变量:在方法或者其它语句块中定义的变量,只在这个方法或语句块中有效。使用之前必须初始化,否则系统会报编译错误。
注:一个全局变量,一个局部变量可以重复定义。两个局部变量不能重复定义。
public class Var { int i;//全局变量,所有方法中都可以使用,不需要初始化,系统会默认初始化 void method1(){ i++; } void method2(){ i--; } void method3(){ int j = 5;//局部变量 只在method3中有效 int k; //System.out.println(k);//报错 局部变量在使用之前,必须初始化 int i = 5;//全局变量定义了i,局部可以重新定义,局部使用的变量为局部定义的 //int i = 6;//报错,局部变量 不能重复定义 } void method4(){ //j++;//报错,j没有被定义,因为method3中定义的j是局部变量 for(int x = 0;x<10;x++){ System.out.println(x); } //在method4中 虽然定义了两次x,但两个x的局部变量范围是循环内部 //他们之间没有冲突 for(int x=0;x<10;x++){ System.out.println(x); } //x++;//报错,x没有被定义 } }
-
构造方法
-
构造方法,是一种特殊的方法。调用构造方法的时候,可以完成创建对象。
-
构造方法没有返回类型(普通方法必须有返回类型,如果不需要返回值,需要编写void),方法名必须和类名完全一致。
-
编写一个类,系统会自动添加一个无参数的构造方法(不用写)。
-
当我们编写了构造方法,系统就不会再添加无参数构造方法了。
-
一个类中,可以定义多个构造方法(需要参数列表不同)
-
构造方法的作用:
在创建对象时,可以给属性进行初始化。
如果不需要对属性进行初始化,那就不需要编写,使用系统自动添加的构造方法。
如果编写了带参数的构造方法,同时将无参数构造方法加上。
-
-
匿名对象
创建对象时不需要命名(名字没用,一般在作为数组中的一个元素时使用)
-
继承
A类可以继承B类,B类叫父类,A类叫子类。
A类继承了B类之后,就继承了B类中所有的属性和方法(构造方法除外)。
Java中,采用的单继承,也就是说一个类只能继承一个类(只能一个亲爹)。
-
语法:
public class Father { String name; void eat(){ System.out.println(name+"在吃饭"); } void work(){ System.out.println(name+"在工作"); } }
public class Son extends Father{ //Son中定义了带参数的构造方法,默认的无参数构造方法就没有了 public Son(String name){ this.name = name; } }
public class TestExtends { public static void main(String[] args) { Father f = new Father(); f.name = "谢贤"; f.eat(); f.work(); System.out.println("-------------------------"); //Son中定义了带参数的构造方法,默认的无参数构造方法就没有了 //这种写法是错误的,子类继承父类 并不能继承父类构造方法 //Son s = new Son(); Son s = new Son("谢霆锋"); //Son继承了父类,就继承了父类中的属性和方法 s.eat(); s.work(); } }
-
当子类继承了父类,创建子类对象时,java虚拟机自动调用父类的无参数构造方法。
当父类没有无参数构造方法,子类的构造方法中,要使用super关键字,来通知虚拟机调用父类哪个带参数的构造方法,super必须出现在子类构造方法的第一行。一般这种情况,在父类中写个无参数构造方法。
-
java中允许多层继承。但单向继承,不能转圈。
-
-
封装
把对象的属性和操作结合为一个独立的整体,并尽可能隐藏对象的内部实现细节。
高内聚:就是类的内部数据操作细节自己完成,不允许外部干涉。 低耦合:是仅暴露少量的方法给外部使用,尽量方便外部调用。
所有的属性,都应该定义为私有属性。使用private关键字。
同时,提供相应public的setter和getter方法,对属性进行访问。public表示公开的,公共的。
public class Person { //类中所有的属性都应该私有 //同时,提供相应public的setter和getter方法,对属性进行访问 private String name; private int age; private String sex; //下面三个,叫setter方法 //原则是 返回类型为void,方法名以set开头,后面是属性名 public void setName(String name){ this.name = name; } public void setAge(int age){ this.age = age; } public void setSex(String sex){ this.sex = sex; } //下面这三个方法,叫getter方法 //返回类型为属性的类型,方法名以get开头,后面是属性名 public String getName(){ return name; } public int getAge(){ return age; } public String getSex(){ return sex; } } public class TestPerson { public static void main(String[] args) { Person p = new Person(); p.setAge(16);//p.age = 16; p.setName("花木兰");//p.name = "花木兰"; p.setSex("女"); //p.name,p.sex,p.age System.out.println(p.getName()+":"+p.getSex()+","+p.getAge()); } }
-
权限修饰符
public :公共权限 修饰类、属性、方法。可以被任意类访问 protected:受保护的权限 修饰属性、方法 可以被同包类访问,如果不是同包类,必须是该类的子类才可以访问。 default:同包权限 修饰类、属性、方法。只能被同包的类访问 private:私有权限 修饰属性、方法。 只能在本类中访问
一般情况下:属性都是private修饰,方法都是public修饰,需要子类用到的特殊(只想让子类访问,不想让其他类访问)属性和方法,使用protected。default永远都不用。
package com.chinasoft.javase.demo403; public class Emp { public String name;//公开的 所有类都能访问 protected String dept;//受保护的 不同包内的子类能访问 int age;//默认的 default 不能写 同包内能访问 private double salary;//私有的 只有自己能访问 public Emp() { } public Emp(String name, String dept, int age, double salary) { this.name = name; this.dept = dept; this.age = age; this.salary = salary; } public void show(){ System.out.println(name+","+dept+","+age+","+salary); } }
package com.chinasoft.javase.demo403.role; import com.chinasoft.javase.demo403.Emp;//由于Emp和Teacher不在一个包下 不能访问 必须import public class Teacher extends Emp{ public void display(){ System.out.println(name);//public 谁都能访问 System.out.println(dept);//protected 子类能访问 //System.out.println(age);//default 不在一个包下 不能访问 //System.out.println(salary);//private 私有的 除了自己谁也不能访问 } }
package com.chinasoft.javase.demo403.role; import com.chinasoft.javase.demo403.Emp; public class Programer { Emp emp = new Emp(); public void method(){ System.out.println(emp.name); //System.out.println(emp.dept);//protected 不是子类 不能访问 //System.out.println(emp.age);//不是同包 不能访问 //System.out.println(emp.salary);//private 不是自己 访问不了 } }
package com.chinasoft.javase.demo403; public class TestEmp { public static void main(String[] args) { Emp e = new Emp("张三","市场部",25,8000); e.show(); //TestEmp和Emp在一个包下 可以访问 System.out.println(e.name);//public System.out.println(e.dept);//protected System.out.println(e.age);//default 以上三种 同一包中都能访问 //System.out.println(e.salary);//报错 因为属性是私有的外部不能访问 } }
-
游离块和静态游离块
游离块,是在类中定义的一个不属于任何方法和构造方法的语句块。他会在创建对象前执行,它没有用。
静态游离块,它在类第一次加载时执行一次,一般用来给复杂的静态属性初始化。
public class StaticBlock { public static int i = 5; public static int[][] data; static{//静态游离块 只有在类第一次使用时执行一次,给静态属性赋值 System.out.println("执行了静态游离块"); data = new int[3][3];//给复杂属性赋值 } {//游离块 每次创建对象时执行一次,在构造方法前执行,没有用 System.out.println("执行了游离块"); } public StaticBlock(){//给属性初始化 System.out.println("执行了构造方法"); } }
public class TestStaticBlock { public static void main(String[] args) { StaticBlock s = new StaticBlock(); StaticBlock s1 = new StaticBlock(); } }
9、关键字
-
定义:java给一些单词赋予了特殊的意义,我们就不能用来做变量名。
-
简单类型:byte,short,int,long,float,double,char,boolean
-
循环分支:if,for,while,do,swtich
-
void:方法无返回值
-
return:方法的返回
-
break:跳出循环
-
continue:忽略本次循环下面的代码,进入下一次
-
default:默认的
-
class:类
-
new:创建对象
-
package:定义包名,创建文件夹用的
物理上是文件夹,逻辑上是有逻辑关系的类的集合
一般采用公司域名的倒写。
第一级 指该项目的类型,如com,org,gov,edu等, 第二级 指项目所开发或者运行的公司名称,如:chinasofti,icss,huawei,etc等 第三级 指项目的名称,如:corejava,bcms,oa,erp等 第四级 指项目模块的名称,如:bean,action,exception,oop01等
-
import:导入包
-
this:指向被引用对象本身。第一人称引用,理解为"我"的意思。
可以调用自己的属性,方法(可以省略)和构造方法。
-
extends:继承关键字
-
super:和this一样,但不是自己,是父类,理解为“我爹”的意思。
-
private:私有的,不能被其他类调用。
-
public:公开,公共的,所有的类都能调用到。
-
protected:受保护的,它修饰的方法和属性可以跨包在子类中访问
-
abstract:抽象的
-
null:表示空对象,不存在的对象。属于类的默认值。
-
instanceof:判断这个对象,是否是一个类的实例 a instanceof Dog
-
static:静态的
static可以修饰属性和方法,static修饰的属性叫静态属性,修饰的方法叫静态方法。
普通属性,对象之间的值不共享。
static属性,不和对象进行绑定,和类进行绑定,对象之间这个值共享。
静态属性一般使用public修饰,静态属性可以通过类名直接调用。
static修饰的方法,必须使用类名进行调用。
静态方法中,不能引用非静态属性,不需要引用对象属性时,才可以定位静态方法。
如果父类中的静态方法,子类不能重写。如果重写,也需要定义为static的。
public class Emp { private String name;//对象相关的属性,不同对象之间,值不共享 private double salary; public static int nums;//这个公司的员工人数 public Emp() { nums++;//每创建一个对象,nums就加一个 } public Emp(String name, double salary) { this.name = name; this.salary = salary; nums++; } //静态方法,可以通过类名直接调用 //方法中 不需要引用对象属性时 可以定义静态方法 public static void staticMethod() { //System.out.println(name); 静态方法中不能引用非静态变量 System.out.println("调用了静态方法"); } }
public class TestEmp { public static void main(String[] args) { System.out.println("公司人数:"+Emp.nums); Emp emp1 = new Emp("张三",10000); System.out.println("公司人数:"+Emp.nums); Emp emp2 = new Emp("李四",8000); System.out.println("公司人数:"+Emp.nums);//静态属性 可以通过类名直接调用 Emp emp3 = new Emp(); emp3.setName("王五"); emp3.setSalary(6000); System.out.println("公司人数:"+Emp.nums); System.out.println(emp1.getName()+":"+ emp1.getSalary()); System.out.println(emp2.getName()+":"+ emp2.getSalary()); Emp.staticMethod();//调用静态方法 使用类型直接调用 } }
public class FinalMethod { public final void method(){ } public void gongji(){ System.out.println("攻击敌人的代码,大概100行"); } }
-
final:表示最终的
它修饰的类,不能被继承。
它修饰的方法,不能被重写。
它修饰的属性,不能被修改,就是常量,必须进行初始化。
一般情况下使用final修饰的属性,同时使用public static进行修饰,命名规则为所有的字母都大写,如果是多个单词,用_连接。
public final class FinalClass { }
```java //public class FinalSon extends FinalClass{ 错误写法,final修饰的FinalClass不能被继承 public class FinalSon extends FinalMethod{ final int j = 5; //一般情况下使用final修饰的属性, // 同时使用public static进行修饰, // 命名规则为所有的字母都大写,如果是多个单词,用_连接。 public static final int VALUE_KEY_A = 15;//a这个键盘的按键数值 //错误写法,这个因为final修饰的方法,不能被重写 // public void method(){ // // } @Override public void gongji() { super.gongji(); super.gongji(); final int i = 5; // i++;//报错,final修饰的属性 不能被修改 // j = 6;//报错 也不能被重新赋值 } public static void main(String[] args) { FinalSon finalSon = new FinalSon(); finalSon.gongji(); } }
整体项目变量名改名(重构)
10、面相对象的进阶
-
多态
用父类类型表示子类对象,叫多态(多种形态,多种类型),一个对象可以用多个类型(父亲,儿子,丈夫)表示。
当用父类类型表示子类对象时,可以通过造型“(类型)”强制的将这个对象转换为子类型对象。
如果Dog继承了Animal,
Animal a = new Dog(); Dog _d = (Dog)a; _d.sing();
//instance of 是一个运算符 //判断一个对象 是否是这个类型的 Animal a = new Dog(); Animal _a = new Animal(); System.out.println(a instanceof Dog);//true System.out.println(_a instanceof Dog);//false if(a instanceof Dog) { Dog _d = (Dog) a; _d.sing(); } if(_a instanceof Dog){ Dog $d = (Dog)_a; $d.sing();//报错的 }
如果a对象不是Dog类型对象,转换时会有错误。(ClassCastException)
对象强制类型转换之前,可以使用instanceof来判断,保证程序不出造型错误。
-
方法的重写
子类继承父类后,继承了父类的方法,这时,如果父类方法不满足应用需求,可以对这个方法进行重写。
要求该方法方法名,返回类型,参数必须和父类的方法完全一致,权限修饰符不能比父类方法的修饰符权限低。
方法重写后,用父类类型表示子类对象时,调用方法,是子类的方法。
用父类类型表示子类对象时,调用不到子类中新增加的方法。
public class Animal { private String color; public String getColor() { return color; } public void setColor(String color) { this.color = color; } protected void sleep(){ System.out.println("睡觉"); } public void eat(){ System.out.println("吃"); } public void drink(){ System.out.println("喝水"); } }
public class Dog extends Animal{ public void eat(){ System.out.println("吃骨头"); } protected void sleep(){ System.out.println("趴着睡"); } public void sing(){ System.out.println("旺旺"); } }
public class TestDog { public static void main(String[] args) { Animal a = new Animal(); Dog d = new Dog(); Animal ad = new Dog();//创建了一个新的子类对象 用父类类型表示 a.eat();//吃 d.eat();//吃骨头 ad.eat();//吃骨头 无论如何表示,这个ad 他就是一只狗 a.drink(); d.drink(); ad.drink(); //a.sing();//报错 父类中没有这个方法 d.sing(); //ad.sing();//报错,用父类类型表示子类对象时,调用不到子类中新增加的方法。 } }
-
方法的重载
在一个类中,定义多个相同的方法名、参数类型或者个数不同的方法,叫方法的重载。
优势是好记。
重载和重写的区别:这个得记住。
在一个类中,定义多个相同的方法名、参数类型或者个数不同的方法,叫方法的重载。
在子类继承父类的时候,编写一个和父类中方法名、返回类型和参数完全一致的方法,叫重写。
-
抽象类
一个类可用abstract 关键字修饰,表示这个类是抽象类,抽象类不能创建对象。
abstract还可以修饰方法,它修饰的方法,叫抽象方法,抽象方法以;结尾,不能有方法体。
抽象类由于不能创建对象,它是用来给其它类继承使用的。
当一个类继承了一个抽象类,就必须重写这个抽象类中所有的抽象方法,否则这个类就要定义成抽象类。
一般子类重写父类的方法,在方法上加@Override,它会校验重写的语法格式,帮助我们重写不写错。
抽象类可以继承普通类,抽象类还可以继承抽象类。
抽象类中可以不定义抽象方法。
//用abstract修饰的类 不能创建对象 叫抽象类 //abstract 修饰的方法 不能有方法体,以;结尾 //抽象类用来作为其他类的父类使用 public abstract class Animal { private String color; public void eat(){ System.out.println("吃饭"); } public abstract void drink(); }
public class Dog extends Animal{ @Override public void drink() { System.out.println("喝水了"); } }
public class TestAnimal { public static void main(String[] args) { //Animal a = new Animal(); 抽象类不能创建对象 //a.eat(); Dog d = new Dog(); d.drink(); Animal a = new Dog(); a.drink(); } }
-
接口
用interface表示,接口中定义的都是抽象方法,接口中定义的属性都是常量。
接口不能直接使用,需要其它的类来实现(implements,继承的另一种形式)接口。
这个实现类要重写接口里所有的方法,否则,这个类要被定义为抽象类。
一个类只能继承一个类,但可以实现多个接口,中间用,隔开。
一个类可以同时继承一个类,同时实现多个接口。
一个接口可以继承(extends)其它接口,可以继承多个,用,隔开。
接口中可以定义静态的方法,方法有方法体。
接口拓展性:
public interface INumberFilter { /** * 20年后那孩子,你要想使用这个接口 * 你需要重写这个方法,把规则写到方法里 * 如果这个数字是你想要的 就返回true 否则返回false * 其它的事情都已经帮你完成了 * @param num * @return */ public boolean accept(int num); }
public class Number {//当成文件的类 private int[] nums;//要过滤的数字 public Number(){ } public Number(int[] nums){ this.nums = nums; } //返回通过过滤的数字组成的数组 //通过INumberFilter接口,可以将代码写完 public int[] listFilter(INumberFilter filter){ //<Integer>表示 这个动态数组只能添加Integer类型的数据 ArrayList<Integer> list = new ArrayList<Integer>(); if(nums!=null){ for(int num:nums){ if(filter!=null&&filter.accept(num)){ list.add(num); } } int[] rt = new int[list.size()]; for(int i=0;i<rt.length;i++){ rt[i] = list.get(i); } return rt; }else{ return null; } } }
以上两个类可能是20年前写的,但后面如果想修改过滤规则,实现接口重写accept方法即可。
public class MyINumberFilter implements INumberFilter { @Override public boolean accept(int num) { if(num%2==0){ return true; } return false; } }
public class TestNumberFilter { public static void main(String[] args) { int[] nums = new int[]{1,2,3,4,5,6,7,8,9,12,213412,2134,1234,345}; Number number = new Number(nums); MyINumberFilter filter = null; int[] result = number.listFilter(filter); System.out.println(Arrays.toString(result)); } }
-
内部类
在一个类中,定义另一个类,叫内部类。
内部类分4中,成员内部类,局部内部类,匿名内部类,静态内部类。
成员内部类:定义在外部类的内部,跟属性方法构造方法同级,作为外部类的一个成员,可以访问外部类中所有的属性和方法(包括私有的,以及构造方法)
public class Outer { private int age; private Outer(){ } class Inner{ public void display(){ Outer outer = new Outer(); System.out.println(age); } } }
局部内部类:定义在外部类的方法中,可以访问外部类的属性,也可以访问外部类的参数,但不能修改参数的值。
public class Outer2 { private int age; public void display(String name){ class Inner{ public void show(){ age = 15; System.out.println(name); } } } }
匿名内部类:new interface的形式,将整个实现代码作为一个方法的参数,写在参数位置。
public static void main(String[] args) { int[] nums = new int[]{1,2,3,4,5,6,7,8,9,12,213412,2134,1234,345}; Number number = new Number(nums); int[] result = number.listFilter(new INumberFilter() { @Override public boolean accept(int num) { if(num%2==0){ return true; } return false; } }); System.out.println(Arrays.toString(result)); }
静态内部类:同静态属性,静态方法的特点。在静态方法中,可以使用静态内部类。
11、设计模式
-
模式:
人们在工作生活中,为了解决某类问题,总结出来的行之有效的办法。
举个例子,买保险的(讲三讲),为了把保险卖出去有效的套路,可以叫讲三讲模式。
-
工厂模式
将创建对象的过程,通过一个类(WifeFactory)隐藏起来,当我们需要调用具体对象时,直接通过工厂获取对象,不需要关注对象的类的名字。
public class WifeFactory { public static IWife getInstance(){ IWife wife = new XiaoLan1(); return wife; } }
-
单子模式(单例模式)
通过这个模式,可以实现让一个类仅能创建出一个对象。
/** * 将一个类,改成单子模式 共3步 * 1.构造方法私有 * 2.在类中定义一个私有的、静态的,本类类型的全局变量 * 3.提供一个公共的静态的方法,返回这个对象 */ //饿汉模式:不管用不用,先将对象准备好。不用的时候,它也占内存。 public class Singleton { private static Singleton s = new Singleton(); private Singleton(){ } public static Singleton getInstance(){ return s; } }
//懒汉模式 懒得建对象,什么时候用什么时创建,省内存,但容易并发出错(线程问题,后续解决)。 public class Singleton1 { private static Singleton1 s; private Singleton1(){ } public static Singleton1 getInstance(){ if(s==null){//判断s是否为空,为空表示没创建对象 s = new Singleton1(); } return s; } }
12、Api
-
String类型:字符串
//String 在java中表示字符串 他是java.lang包下,不需要import String str = "flkgjqewroiusadflmkndslotqa;lasddkljasdflk;asdfljkg"; //获取字符串的长度 System.out.println(str.length()); //获取第i个索引位置的字符 char c = str.charAt(5); System.out.println(c); //将前面的字符串,替换成后面的 str = str.replace("q","替换"); System.out.println(str); //获取字符串第一次出现的位置 int index = str.indexOf("替");//从头开始找 System.out.println(index); //取第二个替出现的位置 int index2 = str.indexOf("替",index+1);//从index+1位置开始找 System.out.println(index2); //截取字符串 str = str.substring(1,9);//从索引1到6 不包括后面的第6个 1<=index<6 System.out.println(str); str = "a,b,c,d,e"; String[] data = str.split(",");//通过,对字符串进行分割,得到一个字符串数组 for(String tmp:data){ System.out.println(tmp); } str = "aBcdE"; System.out.println(str.toUpperCase());//转换为大写 System.out.println(str.toLowerCase());//小写 System.out.println(str.substring(3));//从第3个位置,到字符串末尾 System.out.println("a".compareTo("b"));//compareTo 比较两个字符串在字典上顺序 System.out.println("b".compareTo("a")); System.out.println("a".compareTo("c")); System.out.println(str.contains("C"));//是否包含这个字符串 区分大小写 System.out.println(str.contains("B")); str = " a b c e "; System.out.println(str.trim());//去掉字符串的前面和后面的空格
String s1 = "abc"; String s2 = "abc"; //true 比较的还是内存地址 abc在常量池中 和整数1道理是一样的 System.out.println(s1s2);
String s3 = "abc";//常量池 String s4 = new String("abc");//堆内存 System.out.println(s3s4);//false System.out.println(s3.equals(s4));//true equals比较的值是否相等 String s5 = new String("abc"); System.out.println(s4s5);//false System.out.println(s4.equals(s5));//true
- 日期时间: ```java import java.util.Date; import java.util.Calendar;//日历 import java.text.SimpleDateFormat;//日期格式工具 public class TestDateTime { public static void main(String[] args) { //当前时间 距1970年1月1日 0点的毫秒值 System.out.println(System.currentTimeMillis()); Date date = new Date();//不常用 System.out.println(date);//当前时间 System.out.println(date.getHours());//当前的小时 不推荐写法 要淘汰了 Calendar calendar = Calendar.getInstance();//日历工具的获取方式` System.out.println(calendar);//Zone表示时区 //日历 可以进行翻转 calendar.add(Calendar.YEAR,-1);//时间往前1年 calendar.add(Calendar.MONTH,2);//两个月后 calendar.add(Calendar.DATE,-8);//8天前 calendar.add(Calendar.HOUR,2);//2个小时后 System.out.println(calendar.getTime()); //将日期转换为yyyy年MM月dd日 HH:mm:ss格式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); String time = sdf.format(calendar.getTime()); System.out.println(time); sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE"); time = sdf.format(calendar.getTime()); System.out.println(time); String str = "20230302"; //得到一周之前的时间 //将字符串转换为日期格式 sdf = new SimpleDateFormat("yyyyMMdd"); try { date = sdf.parse(str);//将字符串解析为Date格式 calendar.setTime(date);//将date的时间 转换为Calendar calendar.add(Calendar.DATE,-7); time = sdf.format(calendar.getTime()); System.out.println(time); } catch (ParseException e) { e.printStackTrace(); } } }
-
Math、Random、UUID
System.out.println(Math.abs(-9));//9 绝对值 System.out.println(Math.round(11.5));//12 四舍五入 System.out.println(Math.round(-11.1));//-11 System.out.println(Math.round(-11.5));//-11 System.out.println(Math.round(-11.6));//-12 System.out.println(Math.pow(5,3));//5的3次方 System.out.println(Math.sqrt(25));//开方 5 UUID uuid = UUID.randomUUID(); System.out.println(uuid);
-
StringBuffer
String类型为定长字符串,StringBuffer类型为变长字符串。
字符串变化次数极多,100次以上时采用。
StringBuilder,功能和StringBuffer一样,StringBuilder性能稍高,但线程不安全。
StringBuffer sb = new StringBuffer("a"); long start = System.currentTimeMillis(); for(int i=0;i<100;i++){ sb.append("a"); } long end = System.currentTimeMillis(); System.out.println(end-start);
-
包装类
Java提供一系列包装类,以便将原始数据类型当作对象进行操作
String str = "123"; int strInt = Integer.parseInt(str); System.out.println(str+1);//连接字符串 System.out.println(strInt+1);//加法运算 str = "123.5"; double strDouble = Double.parseDouble(str); System.out.println(str+1); System.out.println(strDouble+1); System.out.println(Integer.toHexString(255));//转换为16进制 0x System.out.println(Integer.toOctalString(255));//转换为8进制 0开头 System.out.println(Integer.toBinaryString(255));//2进制 System.out.println(Integer.MAX_VALUE); System.out.println(Integer.MIN_VALUE);
-
Object
万事万物皆对象(东西)。
Everything is Object。
Java有一个类,它是所有类的父类。
类中定义了toString,equals,finalize等方法。
//重写toString @Override public String toString() { return "Person{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; }
public class TestPerson { public static void main(String[] args) { // == 简单类型 判断值是否相等 // 引用类型 判断是地址是否相等 StringBuffer sb = new StringBuffer("abc"); StringBuffer sb1 = new StringBuffer("abc"); System.out.println(sb==sb1);//false System.out.println(sb.equals(sb1));//false Person p = new Person("2102","大连"); Person p1 = new Person("2102","大连"); System.out.println(p==p1); //false 因为Object中定义的equals 和==是一样的,重写equals后,返回true System.out.println(p.equals(p1));
finalize方法:
垃圾回收机制,自动调用的方法。
垃圾回收机制:系统觉得你这个对象没用了,它自动就给你回收了,垃圾回收时间不定时,自主回收。
我们可以重写这个方法,在这个对象被垃圾回收时,执行的代码。
equals方法:
//重写equals public boolean equals(Object obj){ if(obj==null){ return false; } if(this==obj){ return true; } if(obj instanceof Person){//是Person类型的 Person tmp = (Person)obj;//强转 将obj转换为Person类型 if(tmp.id.equals(this.id)&&tmp.name.equals(this.name)){ return true; }else{ return false; } }else{//如果obj都不是Person类型的,不可能相等。 return false; } }
13、异常处理
-
异常处理结构
异常(Exception):异常指程序运行时发生的不正常事件;异常能够被程序处理,保证程序继续运行。
错误(Error):错误程序没法处理(内存溢出);发生错误后,虚拟机选择终止运行,需要修改源代码
1.常见异常案例:
demo: 算术异常
public class ExceptionDemo01 { public static void main(String[] args) { System.out.println(100/0); } } "C:\Program Files\Java\jdk1.8.0_112\bin\java.exe" "-javaagent:E:\IntelliJ IDEA \javase\out\production\javase" com.chinasoft.javase.demo410.ExceptionDemo01 Exception in thread "main" java.lang.ArithmeticException: / by zero at com.chinasoft.javase.demo410.ExceptionDemo01.main(ExceptionDemo01.java:5) Process finished with exit code 1
demo02:下标越界异常
public class ExceptionDemo01 { public static void main(String[] args) { int[] arr=new int[2]; System.out.println(arr[3]); } } Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3 at com.chinasoft.javase.demo410.ExceptionDemo01.main(ExceptionDemo01.java:6)
.
demo03:内存溢出异常
public class ExceptionDemo01 { public static void main(String[] args) { int[] arr=new int[1024*1024*800]; System.out.println(arr[3]); } } //1024 byte = 1k //1k*1024 = 1M //800M //JVM 内存默认大小 64M Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.chinasoft.javase.demo410.ExceptionDemo01.main(ExceptionDemo01.java:5)
demo04:空指针异常
public class ExceptionDemo01 { public static void main(String[] args) { String str=null; // System.out.println(str.equals("bbc")); System.out.println("bbc".equals(str)); //将 对象 和参数 调换位置,避免异常产生 } } Exception in thread "main" java.lang.NullPointerException at com.chinasoft.javase.demo410.ExceptionDemo01.main(ExceptionDemo01.java:6)
demo05: 数字转换异常
public class ExceptionDemo01 { public static void main(String[] args) { System.out.println(Integer.parseInt("abc")); //将 对象 和参数 调换位置,避免异常产生 } } Exception in thread "main" java.lang.NumberFormatException: For input string: "abc" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
demo06:类转换异常
public class ExceptionDemo01 { public static void main(String[] args) { Animal ani1=new Dog(); Cat cat=(Cat)ani1; } } Exception in thread "main" java.lang.ClassCastException: com.chinasoft.javase.demo410.Dog cannot be cast to com.chinasoft.javase.demo410.Cat at com.chinasoft.javase.demo410.ExceptionDemo01.main(ExceptionDemo01.java:6) class Animal{ } class Dog extends Animal{ } class Cat extends Animal{ }
BufferUnderflowException:缓冲区下溢异常
操作缓冲区,没有下一个数据还在调用。用limit进行判断。
2.异常结构
API中 异常的顶级父类 Throwable类 Throwable类 有两个子类 Exception Error Exception: 所有异常都是Exception类的直接/间接子类 Error: 所有错误 都是Error的直接/间接子类 Exception的子类 分为两大类 运行时异常: RuntimeException的子类 都是运行时异常 也被称为 非检测异常(uncheck exception): 编译时不检测, 非运行时异常 : 其它 也被称为 检测异常,是指必须处理的异常(括号括错)
异常 | 说明 |
---|---|
Exception | 异常层次结构的根类 |
RuntimeException | 所有运行时异常的根类 |
ArithmeticException | 算术异常 |
IllegalArgumentException | 方法/函数 接收到非法参数 |
ArrayIndexOutOfBoundsException | 数组下标越界 |
NullPointerException | 空指针异常(访问/调用null) |
ClassNotFoundExcetion | 不能加载所需要的类 |
NumberFormatException | 数字转换异常 |
IOException | I/O 输入输出流异常的根类 |
FileNotFoundException | 找不到文件 |
EOFException | 文件结束 |
InterruptedException | 线程中断异常 |
3.异常处理
try,catch和finally
try 尝试 catch 捕获 finally 最终的
public static void main(String[] args) { // 关键字: try catch finally // 格式: // try{ // 可能产生异常的代码块 // }catch(异常类型 异常变量名){ // 1.产生异常时 处理代码 // 2.可以查看异常的相关信息 // }[finally{ // 无论是否发生异常 都会执行的代码 // }] // []表示 可有可无 可以省略 // *技术点: // 1.try中代码块 发现异常 异常后的代码不执行 // 2.try中代码块 发现异常 执行catch中代码块 // 3.try中代码块 无异常 不执行catch代码块 // 4.catch可以根据异常类型的不同 写多个分支 // 5.catch中异常类型 和try中产生的异常类型不匹配时 catch不执行 程序中止 // 6.多个catch语句 异常类型 必须从子类到父类 反之报预编译异常 // try { // System.out.println("try run 1"); // System.out.println(100/2); //出错代码 // System.out.println("try run 2"); // } catch (Exception e) { // e.printStackTrace(); //输出异常内容(报什么异常 异常内容 异常位置) // System.out.println("catch run"); // } try { System.out.println("try run 1"); System.out.println(100/0); //出错代码 System.out.println("try run 2"); }catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); //输出异常内容(报什么异常 异常内容 异常位置) System.out.println("ArrayIndexOutOfBoundsException catch run"); } catch (ArithmeticException e){ e.printStackTrace(); //输出异常内容(报什么异常 异常内容 异常位置) System.out.println("ArithmeticException catch run"); } catch (Exception e){ e.printStackTrace(); //输出异常内容(报什么异常 异常内容 异常位置) System.out.println("Exception catch run"); } System.out.println("今天天气不错"); }
try catch finally 案例
// *技术点: // 1.finally中代码 永远执行 // try { // int[] arr= {1,3,5}; // System.out.println(arr[2]); // } catch (Exception e) { // e.printStackTrace(); //输出异常信息 没有返回值 // String str=e.getMessage(); //异常事件的信息(产生异常的值) // System.out.println(str); // String str2=e.toString(); //字符串形式输出 // System.out.println(str2); //异常对象本身 异常类:异常值 // } finally { // //最终执行: 不管发生异常与否 都执行的代码 // System.out.println("finally run 数组关闭"); // }
finally 和函数 return的关系
函数:
public static int finallyTest(){ int a=10; try { System.out.println("try: "+a); return a; } catch (Exception e) { return a; } finally { a=100; System.out.println("finally: "+a); } }
调用函数
// try catch finally 函数关系 System.out.println(finallyTest()); // 思考:1.函数返回值 2.finally中输出代码
练习:
// case: 有字符串数组 {"3","50","abc","46","57a"} // 将该数组中元素 转换为数字 如果无法转换为数字 输出无法转换的字符串 String[] str_arr={"3","50","abc","46","57a"}; for(int i=0;i<=str_arr.length-1;i++){ try { int str_int=Integer.parseInt(str_arr[i]); // System.out.println(str_int); } catch (NumberFormatException e) { // e.printStackTrace(); System.out.println(e.getMessage()); } }
4.throw throws
有函数:
public static int div(int x,int y) throws Exception { // try catch方案 // int result= 0; // try { // result = x/y; // } catch (Exception e) { // //处理异常 // e.printStackTrace(); // } // return result; // throw 抛出异常 方案 if(y==0){ // Exception e=new Exception(); // throw e; throw new Exception(); } int result=x/y; return result; }
调用函数:
// *技术点: // 1.throw, throws 都是应用在 函数/方法中的 // 2.throw 执行代码 作用是抛出异常 // 3.throws 声明代码 作用是 函数声明该函数可能抛出异常 // 4.调用 声明抛出异常的函数 需要 trycatch(捕获异常)/throw(抛出异常) 异常 // 如果继续throw异常 异常后的代码不执行 public static void main(String[] args) { try { int res=div(5,0); } catch (Exception e) { // e.printStackTrace(); } System.out.println("今天天气不错"); }
5.try中代码 异常时 中止执行
原因: 如果异常没有处理 虚拟机 执行 system.exit();
// System.exit(0); //虚拟机停止执行 中止代码 0 正常退出/ 1 异常退出
6.自定义异常
定义: 在某些业务中 需要人为的制定规则 使某些数据保持在规定范围内 超出该范围 则按异常处理
// 自定义异常 // 1.从业务角度出发,规定异常类型,创建异常类 AgeOutOfBoundsException // 2.确定需要使用该异常的类 新建Person // 3.测试 执行
-
自定义异常类
public class AgeOutOfBoundsException extends Exception{ private String message; //产生异常的信息 // 无参构造方法 public AgeOutOfBoundsException() { } // 有参构造方法 // 1.参数为 年龄 // 2.为异常信息赋值 public AgeOutOfBoundsException(Integer age) { super(); this.message=age+"年龄出现异常,人类的年龄范围为0~100岁"; } //系统调用 getMessage()输出异常信息 @Override public String getMessage(){ //异常发生时,获取异常信息 return this.message; } }
2.创建 异常相关类
public class Person { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) throws AgeOutOfBoundsException { if(age>=0&&age<=100){ //异常数值范围 在使用该异常处做判断 this.age = age; }else{ //不符合条件 抛自定义异常对象 AgeOutOfBoundsException e=new AgeOutOfBoundsException(age); throw e; } } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
3.触发该异常
public static void main(String[] args) { Person p1=new Person(); try { p1.setName("小帅"); p1.setAge(130); } catch (AgeOutOfBoundsException e) { e.printStackTrace(); } finally { System.out.println(p1); } }
练习:
// case:创建Student类 // private String name; // private Integer age; // 实例化Student类对象,给该对象成员变量赋值 // 如果学生姓名中 含有数字(0~9),报 姓名格式异常(NameFormatException) // 提示: // String str="你好"; // char ch=str.charAt(1); // if(ch>=48&&ch<=57){ // System.out.println("是数字"); // }else{ // System.out.println("不是数字"); // }
1.新建自定义异常类:
public class NameFormatException extends Exception{ private String message; public NameFormatException() { } public NameFormatException(String name) { super(); this.message=name+"包含数字,不符合需求"; } @Override public String getMessage(){ return this.message; } }
2.新建student类
public class Student { private String name; private Integer age; public String getName() { return name; } public void setName(String name) throws NameFormatException { for(int i=0;i<=name.length()-1;i++){ // name.charAt(i) if(name.charAt(i)>=48&&name.charAt(i)<=57){ NameFormatException e=new NameFormatException(name); throw e; } } this.name = name; //循环结束后 说明满足条件 // 判断数字 方式2 // String reg = "[0123456789]"; //java中的正则对象 // Pattern p = Pattern.compile(reg); //Pattern java格式对象 // Matcher m = p.matcher(name); //Matcher java匹配对象 // if (m.find()) { //判断 匹配对象中 是否包含 正则对象中的字符 // NameFormatException e = new NameFormatException(name); // throw e; // } else { // this.name = name; // } } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
3.实现异常发现
Student stu=new Student(); try { stu.setAge(20); stu.setName("小1美"); } catch (NameFormatException e) { e.printStackTrace(); } finally { System.out.println(stu); }
14、IO流
-
文件:File类
文件是存储数据的集合 文件夹是存文件的集合
泛指计算机上的文件(包括文件和文件夹)。
// \\ / File.separator(推荐写法,它是操作系统级,匹配所有的操作系统)等价的 File file = new File("f:"+File.separator+"file"+File.separator+"1.txt"); System.out.println(file.getName());//文件名 System.out.println(file.getAbsolutePath());//绝对路径名 System.out.println(file.getParent());//父目录,上一级目录 System.out.println(file.getPath());//绝对路径的字符串 System.out.println(file.lastModified()); file.setLastModified(file.lastModified()-1000*60*60*5);//设置最新的修改时间 long times = file.lastModified(); Date date = new Date(); date.setTime(times); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String modifyTime = sdf.format(date); System.out.println(modifyTime);
File file = new File("f:/abc.txt"); File file1 = new File("f:/sd/ca/abc.txt");//不能同时创建文件夹和文件 File file2 = new File("f:/bc/ad"); //file.createNewFile();//创建一个文件 //file1.mkdirs();//创建目录,abc.txt成为了一个文件夹 //file2.mkdirs();//创建系列目录 //file.delete();//删除文件 file2.delete();//只能删除最后一级
public class TestDir { public static void main(String[] args) { File file = new File("E:\\DL2305"); System.out.println(file.exists());//是否存在 System.out.println(file.isFile());//是否是文件 System.out.println(file.isDirectory());//是否是文件夹 printDir(file); } public static void printDir(File file){ if(file.isDirectory()){ File[] files = file.listFiles(); for(File f:files){ if(f.isDirectory()){ printDir(f);//在方法中,调用自己的方法 叫递归 }else{ System.out.println(f.getAbsolutePath()); } } } } }
public class MyFilenameFilter implements FilenameFilter { @Override public boolean accept(File dir, String name) { return dir.length()>20*1024; } }
public class MyFileFilter implements FileFilter { @Override public boolean accept(File pathname) { return pathname.length()>20*1024*1024; } }
public class TestFilter { public static void main(String[] args) { //MyFilenameFilter myFilenameFilter = new MyFilenameFilter(); MyFileFilter myFileFilter = new MyFileFilter(); File file = new File("E:\\DL2305\\软件"); File[] files = file.listFiles(myFileFilter); for(File f:files){ System.out.println(f.getAbsolutePath()); } } }
-
流:生活中泛指水的移动,叫水流。
电流,电的移动,计算机中的流指的是数据的移动,叫做数据流,简称流。
按流向分:可分为输入流(input,InputStream,所有输入流的父类)和输出流(output,OutputStream,所有输出流的父类),以内存为主,流又叫IO流。
按流的格式分:字节流(一次传输一个字节),字符流
字节流的抽象父类 InputStream字节输入流, OutputStream字节输出流
字符流的抽象父类:Reader 字符输入流, Writer字符输出流
按流的功能分:可分为低级流(节点流),高级流(处理流)。
节点流:可以从某节点读数据或向某节点写数据的流 处理流:对已存在的流的连接和封装,实现更为丰富的流数据处理,提高流读写效率
所有的流都在io包下。
-
FileInputStream 输入流、字节流、节点流
读取时,中文会乱码,能处理,很麻烦,处理普通文件(2进制文件)
FileInputStream fis = null; try { fis = new FileInputStream("F:/1.txt"); int tmp = 0; while ((tmp = fis.read()) != -1) { System.out.print((char) tmp); } } catch (IOException e) { e.printStackTrace(); } finally { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } }
-
FileOutputstream 输出流、字节流、节点流
FileOutputStream fos = null; try { fos = new FileOutputStream("f:/2"); fos.write(97); String str = "岳阳楼记"; fos.write(str.getBytes()); fos.flush();//冲刷缓冲区 } catch (IOException e) { e.printStackTrace(); } finally { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } }
-
BufferedInputStream 缓冲流,输入流,处理流。
-
BufferedOutputStream 缓冲流,输出流,处理流。
FileInputStream fis = null; BufferedInputStream bis = null; FileOutputStream fos = null; BufferedOutputStream bos = null; try { fis = new FileInputStream("E:\\DL2305\\正课\\录屏\\07-习题讲解.mp4"); fos = new FileOutputStream("f:/1.mp4"); bis = new BufferedInputStream(fis);//对fis进行封装,封装成高级流 bos = new BufferedOutputStream(fos); int tmp = 0; while((tmp=bis.read())!=-1){ bos.write(tmp); } bos.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { bos.close(); bis.close(); fos.close(); fis.close(); } catch (IOException e) { e.printStackTrace(); } }
-
FileReader 字符流,低级流,输入流
-
BufferedReader 缓冲流 高级流 输入流 (处理字符的)
FileReader fr = null; BufferedReader br = null; try { fr = new FileReader("f:/1.txt"); br = new BufferedReader(fr); String tmp = ""; while((tmp=br.readLine())!=null){ System.out.println(tmp); } } catch (IOException e) { e.printStackTrace(); } finally { try { br.close(); fr.close(); } catch (IOException e) { e.printStackTrace(); } }
-
InputStreamReader 将字节流转换为字符流
-
OutputStreamWriter
-
FileWriter 字符流,低级流,输出流
-
BufferedWriter 字符流 低级流 处理流
public class TestWriter { public static void main(String[] args) { FileWriter fw = null; BufferedWriter bw = null; try { fw = new FileWriter("f:/3.txt"); bw = new BufferedWriter(fw); bw.write("高朋满座"); bw.write("\r\n"); bw.write("满腹经纶"); bw.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { bw.close(); fw.close(); } catch (IOException e) { e.printStackTrace(); } } } }
15、枚举类型
枚举类型:是一个数据类型,由多个常量组成,是一种新的常量定义的方式。
是一个特定类型的类。所有枚举都是Java中的新类java.lang.Enum的隐式子类。此类不能手工进行子类定义。
相对于传统的常量定义方式(static final形式),可以不指定具体的值。可以防止定义常量时因为常量的值重复引起的程序bug。
在扩展性上,枚举可以随时添加,系统会自动整理,不需要我们过多参与,扩展型好
public enum ColorsEnum { RED,GREEN,BLUE,YELLOW,ORANGE,PINK,BLACK,WHITE,BROWN }
public class TestColorEnum { public static void main(String[] args) { ColorsEnum color = ColorsEnum.YELLOW; switch (color){ case RED: System.out.println("红色"); break; case BLUE: System.out.println("蓝色"); break; case GREEN: System.out.println("绿色"); break; case YELLOW: System.out.println("黄色"); break; } } }
16、lambda表达式
函数式接口:有且仅有一个抽象方法的接口。需要引入@FunctionalInterface进行修饰。
函数型接口才能使用lambda表达式。lambda是jdk1.8引入的新特性。
public class TestLambda { public static void main(String[] args) { IA a = () -> System.out.println("a"); IB b = (int i) -> System.out.println(++i); IC c = (i,j) -> { int k = i+j; System.out.println(k); }; TestLambda t = new TestLambda(); t.test(a,b,c); } public void test(IA a,IB b,IC c){ a.methodA(); b.methodB(5); c.methodC(6,7); } interface IA{ void methodA(); } interface IB{ void methodB(int i); } interface IC{ void methodC(int i,int j); } }
16、泛型
-
定义:将数据类型参数化。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
-
泛型类:在定义类的时候,在类型的后面,增加一个<T>的参数,T表示你要传入的数据类型参数。
-
public class Boxer1<T> { private T obj; public void set(T obj){ this.obj = obj; } public T get(){ return obj; } }
-
泛型接口:跟泛型类一样。就是定义接口时,传递数据类型参数。
-
泛型方法:
(1)public 与返回值中间的<T,E..>可以理解为声明此方法为泛型方法; (2)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T,E,K,V等形式的参数常于表示泛型 (3)在泛型类中使用泛型的方法无需再声明泛型 (4)是否拥有泛型方法,与其所在的类是否泛型类没有关系。要定义泛型方法,只需将泛型参数列表置于返回值前。
public static <E> void printArray(E[] array){ for(E tmp:array){ System.out.println(tmp); } } String[] data = {"abc","bd","de","a"}; Boxer.printArray(data); Integer[] data1 = {123,123,432,56,23}; Boxer.printArray(data1);
-
泛型上限:T extends List 定义的泛型参数,增加了上限,上限是List,类型参数必须是List的子类 或者List本身
-
泛型下限:T super ArrayList 参数类型必须是ArrayList的父类或本身。
public class Boxer2<T extends List> { private T t; private void set(T t){ this.t = t; } public T getT(){ return t; } }
-
public class TestBoxer2 { public static void main(String[] args) { Boxer1<String> b = new Boxer1(); //Boxer中定义的泛型参数,增加了上限,上限是List //类型参数必须是List的子类 或者List本身 //Boxer2<String> b2 = new Boxer2(); 报错 Boxer2<ArrayList> b2 = new Boxer2();//正常 } }
17、NIO
-
NIO是以块的方式处理数据,但是IO是以最基础的字节流的形式一次一个字节地去写入和读出的。所以在效率上的话,肯定是NIO效率比IO效率会高出很多。
-
NIO的通道是可以双向的,但是IO中的流只能是单向的。
-
NIO处理多通道读写,IO处理大文件读写。
-
IO是面向流(Stream)的,NIO是面向块(buffer)的。 面向流意味着从流中一次可以读取一个或多个字节,而且只能顺序从流中读取数据,如果需要跳过一些字节或者再读取已经读过的字节,你必须将从流中读取的数据先缓存起来。 面向块的处理方式有些不同,数据是先被读/写到buffer中的,根据需要你可以控制读取什么位置的数据。这在处理的过程中给用户多了一些灵活性,你需要额外做的工作是检查你需要的数据是否已经全部到了buffer中,你还需要保证当有更多的数据进入buffer中时,buffer中未处理的数据不会被覆盖。
-
IO流都是阻塞的,这意味着,当一条线程执行read()或者write()方法时,这条线程会一直阻塞知道读取到了一些数据或者要写出去的数据已经全部写出,在这期间这条线程不能做任何其他的事情 NIO的非阻塞模式(Java NIO有阻塞模式和非阻塞模式,阻塞模式的NIO除了使用Buffer存储数据外和IO基本没有区别)允许一条线程从channel中读取数据,通过返回值来判断buffer中是否有数据,如果没有数据,NIO不会阻塞,因为不阻塞这条线程就可以去做其他的事情,过一段时间再回来判断一下有没有数据 NIO的写也是一样的,一条线程将buffer中的数据写入channel,它不会等待数据全部写完才会返回,而是调用完write()方法就会继续向下执行
ByteBuffer buffer = ByteBuffer.allocate(15);//开辟Buffer的空间 15个byte //allocate开辟后,缓冲区大小和极限都是15,现在可以写操作 //position可以理解为游标,或者指针 System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());//15,15,0 buffer.put("hello".getBytes());//写入缓冲区 System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());//15,15,5 buffer.put((byte)'w');//写入了一个字符 System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());//15,15,6 //以上都是写操作,写操作结束后,如果需要读 需要反转缓冲区 准备进行读操作 limit变为之前写入的byte长度,position为0 buffer.flip();//反转缓冲区 System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());//15,6,0 System.out.println((char)buffer.get());//获取第一个字符 postion+1 System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());//15,6,1 System.out.println((char)buffer.get());//获取第二个字符 postion+1 System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());//15,6,2 buffer.rewind();//缓冲区不变,从头开始读 limit不变 postion为0 for(int i=0;i<buffer.limit();i++){ System.out.println((char)buffer.get()); } byte[] array = buffer.array();//将buffer的内容写入一个数组 System.out.println(new String(array)); System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());//15,6,6 buffer.clear(); System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());//15,15,0 buffer.flip(); System.out.println(buffer.capacity()+","+buffer.limit()+","+buffer.position());//15,0,0
18、List、Set、Map
容器:能装东西的都叫容器。
集合容器:装对象的容器。数组和集合类同是容器。
数组和集合的区别:数组是固定长度的,数组只能存储相同的数据类型,数组可以存储基本数据类型和引用类型
集合的长度是可变的,随着存储对象的增加和减少,可变化。集合只能存储引用类型,而且可以存储不同类型的数据。
Collection:所有集合的父接口,本身就是集合的意思。
Map:映射接口。也是所有映射工具的父接口。
Iterator:迭代接口。
Collection接口有三个子接口
-
List:元素存取是有序的,可存放重复元素。元素都有下标
ArrayList,是List的子类,我们可以直接使用这个类的对象进行数据存储。还有LinkedList、Vector和Stack。
import java.util.ArrayList; public class TestArrayList { public static void main(String[] args) { ArrayList list = new ArrayList(); System.out.println(list.size());//没有元素 长度为0 System.out.println(list.isEmpty());//没有元素 返回true list.add("abc"); list.add("bcd"); list.add(1);//集合只能添加引用类型数据,这个1会自动装箱为Integer list.add(false);//自动转为Boolean list.add(1);//可以存储,List可重复有序 System.out.println(list);//打印所有元素 System.out.println(list.size());//长度为5 System.out.println(list.contains("abc"));//集合中是否包含abc System.out.println(list.contains(1)); list.add(1,15.2); //add方法 默认放在集合的最后 如果前面加上index //属于插入操作 index 为插入位置 其它元素往后排 System.out.println(list); System.out.println(list.get(5));//得到1 第6个元素 索引从0开始 //System.out.println(list.get(6));//数组下标越界 list.remove(2);//移除的是bcd 索引为2 后面元素自动往前排 System.out.println(list); list.remove("abc");//移除的abc字符串 System.out.println(list); list.remove(2); System.out.println(list);//15.2,1,1 list.remove(1);//参数为数字时 一定是通过索引移除 System.out.println(list); list.add("abc"); list.add("abc"); list.add("bcd"); list.add("bcd"); System.out.println(list); list.remove("abc");//remove 是按照索引 从前到后 一个一个移除 System.out.println(list); System.out.println(list.indexOf(1));//indexOf 第一次出现的索引 list.set(3,"aa");//set 设置 替换该索引位置的元素 System.out.println(list); } }
List接口的4个子类的区别:
ArrayList 是线程不安全的,底层是数组的处理方法,需要连续的内存空间,查询速度快,增删慢。
LinkedList 底层是链表的方式,线程也不安全,查找速度慢,增删快。
Vector 线程安全的,底层也是数组,开辟内存200%开辟,ArrayList是150%,已经完全被ArrayList取代。
Stack 堆栈方式,先进后出(弹夹原理),底层用的Vectory。
public class TestLinkedList { public static void main(String[] args) { //ArrayList 数组方式 数据在一起 增删慢 查找块 靠门一组 //LinkedList 链表方式,数据分散,增删快 查找慢 值日第一小组 //增删测试 ArrayList arrayList = new ArrayList(); long start = System.currentTimeMillis(); for(int i=0;i<100000;i++){ arrayList.add(0,"abc"); } long end = System.currentTimeMillis(); System.out.println(end-start); arrayList.clear();//将集合中元素 清空 LinkedList linkedList = new LinkedList(); start = System.currentTimeMillis(); for(int i=0;i<100000;i++){ linkedList.add(0,"abc"); } end = System.currentTimeMillis(); System.out.println(end-start); //查找测试 System.out.println("---------------------------------"); arrayList = new ArrayList(); linkedList = new LinkedList(); for(int i=0;i<100000;i++) { arrayList.add("a"); linkedList.add("a"); } Random r = new Random(); start = System.currentTimeMillis(); for(int i=0;i<10000;i++){ arrayList.get(r.nextInt(arrayList.size())); } end = System.currentTimeMillis(); System.out.println(end-start); start = System.currentTimeMillis(); for(int i=0;i<10000;i++){ linkedList.get(r.nextInt(linkedList.size())); } end = System.currentTimeMillis(); System.out.println(end-start); } }
ArrayList的Contains,调用的是对象的equals方法进行比较是否已经存在,我们自定义的类如果想使用这个功能(想使用contains方法判断对象是否在集合中存在),必须重写equals方法。
public class TestContains { public static void main(String[] args) { List list = new ArrayList(); Person p = new Person("1","张三"); list.add(p); p = new Person("2","李四"); list.add(p); System.out.println(list);//打印List时,会自动调用对象的toString方法 p = new Person("1","张三"); System.out.println(list.contains(p));//contains里,通过对象的equals方法进行比较 } }
public class Person{ private String id; private String name; public Person(){ } public Person(String id, String name) { this.id = id; this.name = name; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()){ return false; } Person person = (Person) o; if (id != null ? !id.equals(person.id) : person.id != null){ return false; } return name != null ? name.equals(person.name) : person.name == null; } @Override public int hashCode() { int result = id != null ? id.hashCode() : 0; result = 31 * result + (name != null ? name.hashCode() : 0); return result; } @Override public String toString() { return "Person{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; } }
List的遍历方式:
//普通遍历方法,通过集合的索引来获取元素 public static void for1(){ ArrayList list = new ArrayList(); list.add("1"); list.add(1); list.add(new Object()); list.add(false); for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); } } //使用迭代器 通过迭代器来获取所有元素 public static void for2(){ ArrayList list = new ArrayList(); list.add("1"); list.add(1); list.add(new Object()); list.add(false); Iterator iterator = list.iterator(); while(iterator.hasNext()){//如果有下一个元素 System.out.println(iterator.next()); } } //使用增强型循环 来获取所有的元素 public static void for3(){ ArrayList list = new ArrayList(); list.add("1"); list.add(1); list.add(new Object()); list.add(false); for(Object tmp:list){ System.out.println(tmp); } }
-
Set:元素存取是无序的,不可以存放重复元素。元素没有下标。无序指的是不按照你的添加顺序排列。
3个子类。
HashSet:不可重复(对象不可重复),无序(不按添加顺序排列),无排序(排序规则对我们不透明)。
TreeSet:不可重复,无序,有排序。排序规则,需要自定义。通过这个对象的类实现comparable接口,并重写compareTo方法,TreeSet按照compareTo的规则进行排序
LinkedHashSet:不可重复,有序(按添加顺序)。
public class TestHashSet { public static void main(String[] args) { LinkedHashSet<String> set = new LinkedHashSet(); set.add("a"); set.add("2"); set.add("c"); set.add("e"); set.add("b"); set.add("d"); set.add("1"); set.add("2"); set.remove("e");//通过对象移除 System.out.println(set); //HashSet 没有索引只能通过迭代器和增强型循环遍历 for(String tmp:set){ System.out.println(tmp); } Iterator iterator = set.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } } }
public class Student implements Comparable{ private int id; private int age; private String name; public Student(){ } public Student(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } @Override public String toString() { return "Student{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}'; } @Override public int compareTo(Object o) { if(o instanceof Student) { Student s = (Student) o; if(this.age>s.age){ return 1; }else if(this.id==s.age){ return 0; }else{ return -1; } }else { return 0; } } }
public class TestTreeSet { public static void main(String[] args) { TreeSet<String> set = new TreeSet(); set.add("a"); set.add("2"); set.add("c"); set.add("e"); set.add("b"); set.add("d"); set.add("1"); set.add("2"); set.add("ab"); set.add("ac"); System.out.println(set); TreeSet<Integer> set1 = new TreeSet(); set1.add(15); set1.add(672); set1.add(153); set1.add(1531); System.out.println(set1); TreeSet<Student> set2 = new TreeSet<>(); set2.add(new Student(1,25,"张三")); set2.add(new Student(2,15,"李四")); set2.add(new Student(3,35,"王五")); set2.add(new Student(4,5,"赵六")); System.out.println(set2); } }
Queue:队列,实现了先进先出
Map:key-value形式的集合。key是不可以重复的,value可以重复。如果key重复,则替换之前存放的值。
HashMap:线程不安全,效率高,允许null值,无顺序无排序。
Hashtable:线程安全,效率低,不允许null值,无顺序,无排序,已经不用了,都用HashMap。
TreeMap:线程不安全,无顺序(添加顺序),有排序,和TreeSet一样,不允许null值。
LinkedHashMap:线程不安全,有顺序(添加顺序),允许null值。
ConcurrentHashMap:线程安全且高效,其它和HashMap一样。
public class TestHashMap { public static void main(String[] args) { HashMap<Integer,String> map = new HashMap(); map.put(1,"a"); map.put(2,"b"); System.out.println(map); map.put(2,"c");//由于key为2 已经重复 后面的替换前面的 map.put(3,"d"); map.put(4,"d");//3=d 4=d都存在 value可以重复 map.put(null,"e");//key和value 都允许使用null map.put(5,null); System.out.println(map); System.out.println(map.size());//长度 为6 System.out.println(map.get(3));//通过key 获取值 获取key为3的值 为d System.out.println(map.get(0));//获取key为0的值 不存在 得到null值 System.out.println(map.get(null));//key为null 得到值为e System.out.println(map.get(5));//获取到值为null Set<Integer> keys = map.keySet();//keySet 获取到集合中所有的key,无序的 System.out.println(keys); Collection<String> values = map.values();//values 获取所有的值 本身无顺序,按找key的顺序排列 System.out.println(values); System.out.println(map.containsKey(5));//true 判断5是否在key中存在 System.out.println(map.containsValue("f"));//false 判断f是否在value中存在 //遍历所有的元素 key-value for(Integer key:keys){ System.out.println(key+"-->:"+map.get(key)); } } }
Collections:一个工具类,可以对Collection接口下的集合进行操作。
这个类提供了让List、Map、Set变成线程安全的方法。
public class TestCollections implements Runnable{ ArrayList<String> list = new ArrayList();//创建一个线程不安全的集合 //通过Collections,将list转换为一个线程安全的syncList List<String> syncList = Collections.synchronizedList(list); @Override public void run() { for(int i=0;i<10000;i++){ syncList.add("1"); System.out.println(Thread.currentThread().getName()+syncList.size()); } } public static void main(String[] args) { TestCollections testCollections = new TestCollections(); Thread t1 = new Thread(testCollections); Thread t2 = new Thread(testCollections); t1.start(); t2.start(); } }
Collection和Collections的区别:
Collection是一个接口 Collections是一个工具类。Collection是集合的父接口,Collections是对这个接口下的类,提供了一系列工具方法,比如,可以将线程不安全的ArrayList变成线程安全的,对集合排序。
19、多线程
-
进程:操作系统正在运行的应用程序就是一个进程。
-
多进程:操作系统“同时”运行多个进程。操作系统单一时间(时间的最小单位)只能执行一个任务,多个进程(任务)之间交替执行,切换速度非常快,我们感觉不到。
-
多线程:一个进程中,可以同时执行多个任务。一个进程可以拥有多个线程,至少一个线程。
-
优点
多线程最大的好处在于可以同时并发执行多个任务;
多线程可以最大限度地提高性能。
缺点
线程越多占用内存也越多
线程之间对共享资源的访问会相互影响(线程安全问题)
线程太多会导致控制太复杂,最终可能造成很多Bug(编程能力的问题,死锁)。
-
主线程
main方法中执行的任务,叫主线程,其它的线程叫分线程。
-
创建线程三种方式。
-
继承Thread
public class MyThread extends Thread{ @Override public void run(){ //这个方法里就是我们要执行的分线程任务 for(int i=0;i<100000;i++){ System.out.println(getName()+"跑了-->"+i+"米"); } } }
public class TestMyThread { public static void main(String[] args) { MyThread myThread1 = new MyThread(); myThread1.setName("乌龟"); myThread1.setPriority(Thread.MIN_PRIORITY);//设定为最低优先级,CPU调度次数频率低 myThread1.start(); MyThread myThread = new MyThread(); //start方法,相当于通知cpu,我这个线程 可以开始了 myThread.setName("兔子");//设定线程的名字 myThread.setPriority(Thread.MAX_PRIORITY);//最高优先级 myThread.start();//多线程交给cpu调度,不是立即执行 MyThread myThread2 = new MyThread(); //start方法,相当于通知cpu,我这个线程 可以开始了 myThread2.setName("狗");//设定线程的名字 myThread2.setPriority(Thread.NORM_PRIORITY);//正常优先级 参数是int类型 从1到10 myThread2.start();//多线程交给cpu调度,不是立即执行 // //主线程,立即执行 // System.out.println("线程开始执行了"); // for(int i=0;i<100;i++){ // System.out.println("主线程-->"+i); // } } }
-
实现Runnable,因为java中一个类只能继承一个类,一般采用这种方式实现多线程。
-
public class MyRunnable implements Runnable{ @Override public void run() { //本拉登 萨达姆 卡扎菲 System.out.println(Thread.currentThread().getName()+"安装了炸弹"); for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+"跑出了"+i+"米"); } } }
public class TestRunnable { public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread t1 = new Thread(myRunnable,"萨达姆");//等价于t2的写法 Thread t2 = new Thread(myRunnable); t2.setName("卡扎菲"); Thread t3 = new Thread(myRunnable,"本拉登"); t1.start(); t2.start(); t3.start(); try { t1.join();//等待t1线程执行结束 使用join 重载方法可以设定时间 t2.join(200);//最多等待200毫秒 否则就不等他了 t3.join();//0相当于没写,一直等 } catch (InterruptedException e) { e.printStackTrace(); } //炸弹安装完毕后,需要跑到安全位置后 才能引爆 //多个分线程执行任务结束后,主线程才能继续执行 System.out.println("Bomb");//引爆 } }
-
实现Callable
import java.util.Random; import java.util.concurrent.Callable; public class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { Random r = new Random(); int tmp = r.nextInt(100); int sum = 0; for(int i=0;i<tmp;i++){ sum += 10; System.out.println(Thread.currentThread().getName()+"又收10块"); } System.out.println(Thread.currentThread().getName()+"今天收入"+sum); return sum; } }
public class TestCallable { public static void main(String[] args) { MyCallable myCallable = new MyCallable(); //FutureTask 未来任务 线程启动后 得等一会有结果 未来任务就是等着接收这个结果的 FutureTask<Integer> task = new FutureTask(myCallable);//每个线程都要对应一个任务 FutureTask<Integer> task1 = new FutureTask(myCallable); FutureTask<Integer> task2 = new FutureTask(myCallable); Thread t = new Thread(task,"金广鑫"); Thread t1 = new Thread(task1,"李振博"); Thread t2 = new Thread(task2,"倪雨泽"); t.start(); t1.start(); t2.start(); try { int num = task.get();//阻塞方法,等待线程运行结果,必须写在start的后面 int num1 = task1.get(); int num2 = task2.get(); System.out.println("今天收入:"+(num+num1+num2)); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
-
继承Thread类: 优势:Thread类已实现了Runnable接口,故使用更简单 劣势:无法继承其它父类
-
实现Runnable接口: 优势:可以继承其它类 劣势:编程方式稍微复杂,多写一行代码,没有多线程运行的返回值
-
实现Callable接口: 类似于Runnable,但需要使用Futuretask,来接收多线程方法的返回值,并且可以抛出异常。
-
-
守护线程
一种特殊的线程,为其它线程服务。如果其它线程(用户线程)运行结束,守护线程会自动结束。
垃圾回收机制就是守护线程。
public class MyRunnable implements Runnable{ @Override public void run() { for(int i=0;i<80;i++){ System.out.println(Thread.currentThread().getName()+"活了"+i+"年"); try { Thread.sleep(100L);//sleep 休眠 单位为毫秒 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"永远的离开了"); } }
public class MyDaemonRunnable implements Runnable{ @Override public void run() { int i = 0; while(true){ i++; System.out.println(Thread.currentThread().getName()+"活了"+i+"年"); try { Thread.sleep(100L); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class Test { public static void main(String[] args) { MyRunnable runnable = new MyRunnable(); Thread t = new Thread(runnable,"公主"); t.start();//用户线程 MyDaemonRunnable daemonRunnable = new MyDaemonRunnable(); Thread t1 = new Thread(daemonRunnable,"守护神"); t1.setDaemon(true);//设置t1为守护线程 要在start之前执行 System.out.println(t1.isDaemon());//判断是否是守护线程 t1.start(); } }
-
yield
线程主动让出执行机会(cpu时间)。让出后,由CPU调度下一次让哪个线程执行,有可能还是这个线程。虽然在多线程中编写了yield,有可能没有到让出时,cpu也将执行机会交给其它线程。
public class MyYield implements Runnable{ @Override public void run() { for(int i=0;i<10000;i++){ System.out.println(Thread.currentThread().getName()+"运行了"+i+"次"); if(i%5==0){ Thread.yield();//让出时间片 } } } }
public class TestYield { public static void main(String[] args) { Runnable myRunnable = new MyYield(); Thread t1 = new Thread(myRunnable,"线程1"); Thread t2 = new Thread(myRunnable,"线程2"); // t1.setPriority(Thread.MAX_PRIORITY); // t2.setPriority(Thread.MIN_PRIORITY); t1.start(); t2.start(); } }
-
Java中线程状态转换
-
新线程:Thread对象创建完毕
就绪状态:执行了start之后
运行状态:cpu开始调度执行这个线程
阻塞状态:执行了sleep(),在休眠时间内,进入阻塞状态。休眠结束后,进入就绪状态。
Yield让出,起到的作用是从运行状态进入到就绪状态。
结束状态:等待线程执行结束,进入结束状态(循环执行结束)。
-
线程同步:
线程安全问题的原因:多个线程共享了一个对象,或者对一个数据进行了同时操作,有可能出现线程安全问题。
线程同步:在编写程序时,在多个线程对一个对象或者数据进行操作时,要排队。
锁:通过线程同步解决线程安全问题,通过锁来解决。
同步前提:同步需要两个或者两个以上的线程。多个线程使用的是同一个锁。
同步的弊端:性能下降,会带来死锁
同步的优势:解决了线程安全问题、对公共资源的使用有序化。
同步注意:不要盲目对不需要同步的代码使用Synchronized,以避免影响性能和效率。
-
解决方案一:同步代码块
synchronized(共享对象名)
共享对象名,可以是任意类型的对象,这个对象作为锁,在同步代码中起作用。多个线程要使用一个对象作为锁。
被同步的代码段,就是你要锁定的内容,代码段中的代码,就会同步运行(多个线程执行到这个代码段的位置要进行排队)。
加锁之后的线程同步代码块进行排队,性能就会降低,尽可能把不需要同步的代码不要放进去。
public class MyTicketRunnable implements Runnable{ private int nums = 100; private Object obj = new Object();//锁 用Object @Override public void run() { while(true){ synchronized (obj) { if (nums > 0) { nums--; } else { break; } } //不需要同步的代码,不要加在同步代码块里 //这行代码不需要加锁,能提高点性能 System.out.println(Thread.currentThread().getName() + "卖了一张,还剩下" + nums + "张"); } } }
public class TestTicket { public static void main(String[] args) { //MyTicketRunnable定义了全局变量nums,创建一个对象 只有一个nums Runnable runnable = new MyTicketRunnable(); //runnable在3个线程中共享,nums就在3个线程中共享 Thread t1 = new Thread(runnable,"一口"); Thread t2 = new Thread(runnable,"二口"); Thread t3 = new Thread(runnable,"三口"); t1.start(); t2.start(); t3.start(); } }
解决方法二:同步方法 ,普通方法共享锁对象为this对象,静态方法共享锁对象对这个类(Class)。
public synchronized boolean getMoney(){ if(money>0) { Random r = new Random(); int getMoney = r.nextInt(100) + 100; if(money<getMoney){ System.out.println("就剩"+money); getMoney = money; } money -= getMoney; System.out.println(Thread.currentThread().getName() + "取了" + getMoney + ",剩余" + money); return true; }else{ return false; } }
线程死锁
线程死锁指的两个线程互相持有对方依赖的共享资源,造成无限阻塞。
避免死锁:除非必要共享的对像外,属性定义为局部变量。避免使用synchronized嵌套。
public class MyDeadLock implements Runnable{ @Override public void run() { while (true){ if(Thread.currentThread().getName().equals("杨松")){ synchronized ("鱼"){ System.out.println(Thread.currentThread().getName()+"有了鱼,现在要熊掌"); synchronized ("熊掌"){//拿到熊掌那把锁 然后用这个锁 把这段代码同步 System.out.println(Thread.currentThread().getName()+"老开心了"); } } }else{ synchronized ("熊掌"){ System.out.println(Thread.currentThread().getName()+"有了熊掌,现在要鱼"); synchronized ("鱼"){ System.out.println(Thread.currentThread().getName()+"老开心了"); } } } } } }
线程通讯:线程通讯指的是多个线程通过消息传递实现相互牵制,相互调度,即线程间的相互作用。
使用wait,notify和notifyAll实现线程通讯。
这三个方法都是定义在Object中的方法。
为确保wait()和notify()的功能有效,一定要和synchronized一起使用。
如synchronized的锁对象是obj的话,wait和notify正确的使用方法是obj.wait()和obj.notify()。
notify容易造成死锁,一般使用notifyAll。
public class Producer extends Thread{ private Product product; public Producer(Product product){ this.product = product; } @Override public void run() { while(true){ synchronized (product) { if (product.getList().size() == 0) {//没库存了 赶紧做 product.getList().add("冰激凌"); System.out.println(getName() + "生产了一个冰激凌"); product.notifyAll();//激活其它使用Product的线程 通知消费者可以购买了 } else { try { product.wait();//仓库里有商品,wait进入等待 } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
public class Customer extends Thread{ private Product product; public Customer(Product product){ this.product = product; } @Override public void run() { while(true){ synchronized (product) { if (product.getList().size() > 0) { System.out.println(getName() + "购买了一个冰激凌"); product.getList().remove(0); product.notifyAll();//激活其它使用Product的线程 通知生产者 有人买走了 你看看 还有没有 没有赶紧做 } else {//没有货 等着生产 try { product.wait();//仓库里有商品,wait进入等待 } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
public class Test { public static void main(String[] args) { Product product = new Product(); Producer producer = new Producer(product); producer.setName("金广鑫"); Producer producer1 = new Producer(product); producer1.setName("陶红宇"); Customer customer = new Customer(product); customer.setName("于立雪"); Customer customer1 = new Customer(product); customer1.setName("王宇"); producer.start(); producer1.start(); customer.start(); customer1.start(); } }
-
PipedReader和PipedWriter:
可以实现线程之间数据传输。
public class Sender extends Thread{ private PipedWriter out = new PipedWriter(); public PipedWriter getOut() { return out; } @Override public void run() { Random r = new Random(); while(true){ try { char tmp = (char)(r.nextInt(26)+97); System.out.println("发送了一个"+tmp); out.write(tmp); Thread.sleep(100L); } catch (InterruptedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } }
public class Receiver extends Thread { private PipedReader in; public Receiver(PipedWriter out){ try { in = new PipedReader(out); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { while(true){ try { System.out.println((char)in.read()); Thread.sleep(100L); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class Test { public static void main(String[] args) { Sender s = new Sender(); Receiver r = new Receiver(s.getOut()); s.start(); r.start(); } }
-
线程池:
java中所有对线程的增强功能,都在java.util.concurrent包下。
ThreadPoolExecutor:通过ThreadPoolExecutor来创建一个线程池。
corePoolSize - 池中所保存的线程数,包括空闲线程。一般非特殊可以采用cpu核数+1; maximumPoolSize - 池中允许的最大线程数。双倍。 keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。 unit - keepAliveTime 参数的时间单位。 workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 。要执行的多线程队列。 Runnable 任务。
我们可以把并发执行的任务传递给一个线程池,来替代为每个并发执行的任务都启动一个新的线程。
1、减少资源的开销。通过复用线程,降低创建销毁线程造成的消耗。 2、多个线程并发执行任务,提高系统的响应速度。 3、可以统一的分配,调优和监控线程,提高线程的可管理性。
通过工厂方法,可以创建4个类型的线程池。
线程池运行结束时,如果不需要再使用这个线程池 需要关闭。
比如,你有10个包子(10个线程),你正在吃第2个(增在运行第2个)。
shutdownNow:把这个吃完赶紧停。
shutdown:吃完停。
带缓存的线程池 能重用之前的线程 ,它是无限制队列吗,线程会一直的进行调度运行。容易造成cpu100%。
适合处理任务时间极短的多个相同任务(下载1k的图片,很多张)。
固定线程池 newFixedThreadPool 可以限制运行的线程数,对缓存线程池的一个限制。适用于很多相同的任务,任务时间较长,防止CPU100%,使用这个线程(下载任务,下载电视剧,文件100M)。
定时间隔线程池 定时任务场景
单线程化线程池 newSingleThreadExecutor 耗时较长的后台任务
execute,submit(Runnable),submit(Callable)
比如下载1000个文件
execute 全执行结束,就结束线程池。任务跑完,直接退出。
submit(Runnable),可以获取线程运行是否结束。任务跑完,需要判断多少个成功,多少个失败(998个下载成功,2个失败)
submit(Callable),可以获取线程运行结束后的返回值(998个成功,2个失败,平均耗时0.3秒)
public class Test { public static void main(String[] args) { test4(); } public static void test4(){ ExecutorService es = Executors.newSingleThreadExecutor(); for(int i=0;i<10;i++) { es.submit(new Runnable() { @Override public void run() { for(int i=0;i<10;i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } }); } //es.shutdownNow(); 任务正在执行,点击取消按钮 es.shutdown();//必须写 } public static void test3(){ //Executors类提供了一系列工厂方法用于创建任务执行器 //ExecutorService提供了异步的管理一个或多个线程终止、执行过程 Executor执行人的意思 ScheduledExecutorService es = Executors.newScheduledThreadPool(3);//创建一个固定个数线程池 3个线程 es.scheduleWithFixedDelay(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } },3,5, TimeUnit.SECONDS);//延迟3秒启动 间隔5秒执行 } public static void test2(){ //Executors类提供了一系列工厂方法用于创建任务执行器 //ExecutorService提供了异步的管理一个或多个线程终止、执行过程 Executor执行人的意思 //同時执行了3个任务 ExecutorService es = Executors.newFixedThreadPool(3);//创建一个固定个数线程池 3个线程 for(int i=0;i<20;i++){ int index = i;//匿名内部类不能引用非final变量 es.execute(new Runnable() {//execute 将线程交给线程池运行 @Override public void run() { for(int i=0;i<1000;i++) { System.out.println("第" + index + "个线程" + Thread.currentThread().getName()+"-->"+i); } try { Thread.sleep(2000L); } catch (InterruptedException e) { e.printStackTrace(); } } }); } } public static void test1(){ //Executors类提供了一系列工厂方法用于创建任务执行器 //ExecutorService提供了异步的管理一个或多个线程终止、执行过程 Executor执行人的意思 ExecutorService es = Executors.newCachedThreadPool();//创建一个可缓存的线程池 for(int i=0;i<200000;i++){ int index = i;//匿名内部类不能引用非final变量 es.execute(new Runnable() {//execute 将线程交给线程池运行 @Override public void run() { //;'for(int i=0;i<1000;i++) { System.out.println("第" + index + "个线程" + Thread.currentThread().getName()); //} } }); } } }
-
Lock
通过sync,我们并不能直接看到在哪里加了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5中提供了一个新的锁对象Lock(接口),提供了更为清晰的语义
ReentrantLock:
该对象与synchronized关键字有着相同的表现和更清晰的语义,而且还具有一些扩展的功能。可重入锁被最近的一个成功lock的线程占有(unlock后释放)。该类有一个重要特性体现在构造器上,构造器接受一个可选参数,是否是公平锁,默认是非公平锁
公平锁:先来一定先排队,一定先获取锁。构造方法参数为true
非公平锁:不保证上述条件。非公平锁的效率更高。参数为false
public class MyBank implements Runnable{ private int money = 10000; //对多线程的功能加强,都在java.util.concurrent包下 private Lock lock = new ReentrantLock(); @Override public void run() { while(true){ getMoney(); } } public boolean getMoney(){ if(money>0) { lock.lock(); try { int getMoney = 100; if (money < getMoney) { System.out.println("就剩" + money); getMoney = money; } money -= getMoney; System.out.println(Thread.currentThread().getName() + "取了" + getMoney + ",剩余" + money); }finally { lock.unlock(); } return true; }else{ return false; } } }
他同时还是可重入的独占锁。
是同一个线程可无限次 地进入 同一把锁 的不同代码,又因该锁通过线程独占共享资源的方式确保并发安全,又称为独占锁。
例子采用的是同步方法,使用lock和unLock道理一样。
public class TestLocalLock implements Runnable{ @Override public void run() { for(int i=0;i<10000;i++) { methodA(); } } public synchronized void methodA(){ System.out.println("方法A"); methodB(); } public synchronized void methodB(){ System.out.println("方法B"); } public static void main(String[] args) { new Thread(new TestLocalLock()).start(); } }
-
信号量:信号量为多线程协作提供了更为强大的控制方法。(停车场大爷,一共5个车位,获取许可能进去,释放许可,下个车进来,5个一批,可以和线城池配合(newCachedThreadPool))
信号量是对锁的扩展。锁一次都只允许一个线程访问一个资源,而信号量却可以指定多个线程,同时访问某一个资源。
public class TestSemaphore implements Runnable{ private Semaphore semaphore = new Semaphore(5); @Override public void run() { try { semaphore.acquire();// 获取许可 //项目经理说 休眠2秒 Thread.sleep(2000L); System.out.println(Thread.currentThread().getName()+"结束"); semaphore.release();//释放许可 一定要释放 } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { ExecutorService es = Executors.newCachedThreadPool(); TestSemaphore testSemaphore = new TestSemaphore(); for(int i=0;i<20;i++){ es.execute(new Thread(testSemaphore)); } es.shutdown(); } }
-
ThreadLocal 不是本地线程,它是解决多线程共享对象之间的问题。可以理解为本线程属性。
多个线程的共享对象的全局变量(content),在多个线程运行时不想共享(副本),在一个线程中修改这个对象的全局变量(content),对其他线程没有影响,这个时候使用ThreadLocal。
public class TestThreadLocal implements Runnable{ private ThreadLocal<String> threadLocal = new ThreadLocal(); private String content;//多线程不共享 private String name;//多线程共享 public void setName(String name){ this.name = name; } public void setContent(String content){ //this.content = content; threadLocal.set(content); } public String getContent(){ //return this.content; return threadLocal.get(); } @Override public void run() { setContent(Thread.currentThread().getName()); try { Thread.sleep(2000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getContent()); } public static void main(String[] args) { TestThreadLocal local = new TestThreadLocal(); for(int i=0;i<3;i++){ Thread t = new Thread(local); t.start(); } } }
20、网络编程
网络编程主要解决计算机与计算机(手机、平板...)之间的数据传输问题。
-
IP地址:Internet上的每台主机(Host)都有一个唯一的IP地址。
-
Port地址:如果把IP地址比作一间房子 ,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口 可以有65536(即:2^16)个之多!端口是通过端口号来标记的,端口号只有整数,范围是从0 到65535(2^16-1)。
1024以下不推荐我们用。
-
TCP协议:
TCP是面向连接的协议,在正式收发数据前,必须和对方建立可靠的连接,所以速度会慢在连接中传输大数据量; 通信前必须建立连接,效率稍低; 通过三次握手机制连接,四次挥手机制断开连接,TCP提供IP环境下的数据可靠传输,保证数据无差错的、按照顺序的进行传输; TCP使用字节流模式发送数据; TCP适用对可靠性要求高的应用环境;
-
UDP协议:
UDP是面向无连接的协议,不与对方建立连接,而是直接就发送数据包,相对速度快; 因为不需要建立连接,所以速度快; 因为无连接,UDP的传输是不可靠的,不保证数据正确,不保证顺序等,也可能丢包; UDP使用数据报模式; 将数据极其源和目的封装为数据包,不需要建立连接; 每个数据包大小限制在64K中; UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境;
-
HTTP协议:是用于从WWW服务器传输超文本到本地浏览器的传输协议
21、反射
-
反射:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。
-
Class 类型 Constructor 构造方法 Method 方法 Field 属性
Class java.lang包 其它都在java.lang.reflect包下。
public class TestReflect { public static void main(String[] args) throws Exception{ //methodClass(); //methodReflect(); methodReflect1(); } private static void methodReflect1() throws Exception{ Class clazz = Class.forName("com.chinasoft.javase.demo424.Student"); // Constructor constructor1 = clazz.getConstructor(null);//拿到无参构方法 //通过参数类型列表,获取对应的构造方法 Constructor constructor2 = clazz.getConstructor(String.class,String.class,int.class,double.class); Student student1 = (Student) constructor1.newInstance();//采用constructor1构造方法创建对象 System.out.println(student1); //通过带参数的构造方法,创建对象 Student student2 = (Student) constructor2.newInstance("杨慧","女",18,98); System.out.println(student2); //修改student1 这个对象的姓名 //通过Class 获取一个方法 参数 "setName"获取的方法名,String.class 是参数类型 Method setName = clazz.getMethod("setName", String.class); Method setAge = clazz.getMethod("setAge", int.class); Method setSex = clazz.getMethod("setSex", String.class); Method setWeight = clazz.getMethod("setWeight", double.class); Method getName = clazz.getMethod("getName"); setName.invoke(student1,"金广鑫");//给student1这个对象设置名字 名字为金广鑫 setAge.invoke(student1,23); setSex.invoke(student1,"男"); setWeight.invoke(student1,99); System.out.println(student1); Method toString = clazz.getMethod("toString"); Object object = toString.invoke(student2); String str = (String)object; System.out.println(str+"-->"); //toString.getReturnType 获取的是返回类型 .cast方法 是将object强制转换为ReturnType这个类型 System.out.println(toString.getReturnType().cast(object)); } private static void methodReflect(){ Class clazz = Student.class; Constructor[] constructors = clazz.getConstructors(); Field[] fields = clazz.getDeclaredFields();//获取所有属性 getFields()是公共属性; Method[] methods = clazz.getMethods(); for(Constructor c:constructors){ System.out.println(c.getName()); System.out.println("参数为:"); Class[] classes = c.getParameterTypes();//获取构造方法的参数类型列表 for(Class param:classes){ System.out.println(param); } } System.out.println("---------------"); for(Field f:fields){ System.out.println(f.getName()); } System.out.println("---------------"); for(Method m:methods){ System.out.println("方法名:"+m.getName()); System.out.println("返回类型:"+m.getReturnType());//获取方法的返回类型 Class[] classes = m.getParameterTypes(); System.out.println("参数为:"); for(Class param:classes){ System.out.println(param); } System.out.println("==============="); } } private static void methodClass() throws ClassNotFoundException{ String str = "abc";//str是一个字符串对象 Class clazz = str.getClass();//获取str的类 通过对象获取Class Class clazzSuper = clazz.getSuperclass();//通过clazz 获取父类 System.out.println(clazz.getName()); System.out.println(clazzSuper.getName()); //通过字符串路径加载类 (动态加载) 如果路径下没有这个类 会报ClassNotFound异常 clazz = Class.forName("com.chinasoft.javase.demo331.Father");//字符串所在路径的类 System.out.println(clazz.getName()); clazz = Son.class; //通过类名直接指定 System.out.println(clazz.getName()); } }
-
内省:
只要类中有getXXX方法,或者setXXX方法,或者同时有getXXX及setXXX方法,其中getXXX方法没有方法参数,有返回值;setXXX方法没有返回值,有一个方法参数;那么内省机制就认为XXX为一个属性;
内省机制检索属性时,是根据getter和setter方法确认属性名字,而不是根据类里声明的属性名决定;
javaBean:属性私有化,有无参构造方法,setter和getter方法。
内省机制在框架中有广泛的应用,编写JavaBean时一定要注意:
1.属性名一定要首字母小写,按照java规范。
2.没有的属性,千万不要定义setter或getter方法。
public class Person { private String name; private int age; public void setSex(String sex){ } public String getSex(){ return "abcd"; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
public class TestPerson { public static void main(String[] args) throws IntrospectionException { //返回Employee类的BeanInfo对象 BeanInfo employeeInfo= Introspector.getBeanInfo(Person.class); //使用BeanInfo返回PropertyDescriptor对象数组 PropertyDescriptor[] propsDes=employeeInfo.getPropertyDescriptors(); //迭代所有的PropertyDescriptor,返回属性名字。由于从Object类继承getClass方法,所以属性名有class for(PropertyDescriptor prop:propsDes){ System.out.println(prop.getName()); } } }
22、序列化
序列化的作用:Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。
对象序列化:对象的类必须实现java.io.Serializable接口,类中的属性必须全部实现序列化。
如果其中部分属性,不需要被序列化,可以使用transient修饰。
被序列化的类,结构发生了变化,反序列化过程会出错误。
public class TestSakura { public static void main(String[] args){ //writeObject(); readObject(); } public static void readObject(){ FileInputStream fis = null; ObjectInputStream ois = null; try { fis = new FileInputStream("e:/1.txt"); ois = new ObjectInputStream(fis); Sakura sakura = (Sakura)ois.readObject(); System.out.println(sakura); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { if(ois!=null){ ois.close(); } if(fis!=null){ fis.close(); } } catch (IOException e) { e.printStackTrace(); } } } public static void writeObject(){ Person p = new Person(); p.setAge(19); p.setName("姜守瑞"); Sakura s1 = new Sakura("迎春樱","粉红色",1,p,"203公园"); FileOutputStream fos = null; ObjectOutputStream oos = null; try { fos = new FileOutputStream("E:/1.txt"); oos = new ObjectOutputStream(fos); oos.writeObject(s1); oos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if(oos!=null) { oos.close(); } if(fos!=null) { fos.close(); } } catch (IOException e) { e.printStackTrace(); } } } }