JAVA基础
- 1、进制与转换
- 2、基本数据类型
- 3、运算符
- 4、流程控制
- 5、一维数组
- 6、二维数组
- 7、方法(函数)
- 8、面向对象
- 9、API
- 10、正则表达式
- 11、Math类
- 12、包装类
- 13、日期类
- 14、异常
- 15、集合
- 16、泛型
- 17、Stream
- 18、Map
- 19、File
- 20、IO
- 文本文件/非文本文件
- 字符流:FileReader
- 字符流:FileWriter
- 字符流:FileReader/FileWriter的文件复制
- 字节流:FileInputStream
- 字节流:FileInputStream/FileOuputStream文件复制
- 处理流:BufferedInputStream/BufferedOutputStream文件复制
- 处理流:BufferedReader/BufferedWriter文件复制
- 20.1 流的异常处理
- 20.2 转换流
- 20.3 合并流
- 20.4 System类对IO流的支持
- 20.5 数据流
- 20.6 对象流:序列化/反序列化流
- 20.7 Properties
- 21、线程
- 22、网络编程
- 23、枚举
- 24、Junit单元测试
- 25、反射
- 26、JVM
1、进制与转换
-
二进制向十进制转化:从低位次(数字最右边是低位次)开始,按位次乘以2的位次次幂,然后求和。其他进制向十进制转化与二进制类似。
-
二进制转8进制:从低位次开始,每三位进制划分一组,产生8进制的数,然后连起来,就得到一个八进制的数
-
二进制转16进制:从低位次开始,每四位进制划分一组,产生16进制的数,然后连起来,就得到一个16进制的数
15*4 = 120 几进制?
注意:
绝大部分小数转化为二进制是表示不精确的,导致计算机在存储小数的时候是不精确的。
数据的原、反、补码过程
- 任何数据在计算机中存储的是补码;
- 规定最高位是一个符号位,0表示正数,1表示负数;
- 直接算出来的二进制数字称为原码;
- 规定正数的原码、反码、补码是一致的;
- 负数的反码是在原码的基础上,最高位不变,其余的1变0、0变1;
- 负数的补码是在反码的基础上+1;
- 规定用-0表示当前类型的最小值
2、基本数据类型
2.1 变量
变量:由变量名、数据类型、值三部分组成。它是记录数据存储的容器
2.2 基本类型
在计算机中,是以二进制形式来存储数据,每一位二进制在内存中称之为是一“位”(bit,简写b),8位 = 1字节(byte,简写B);1024B=1KB、MB、GB。。。。
2.2.1 基本数据类型
- 数值型
-
- 整数型
-
-
- byte(字节型) ,1个字节,取值范围-128~127;
-
-
-
- short(短整型),2个字节,取值范围-32768~32767;
-
-
-
- int(整型),4个字节,取值范围-20亿~20亿,在java中整数默认为
int
类型;
- int(整型),4个字节,取值范围-20亿~20亿,在java中整数默认为
-
-
-
- long(长整型), 8个字节,需要在结尾添加l/L作为标识;
-
-
- 浮点型
-
-
- float(单精度),4个字节,虽只有四个字节,但取值范围要比long还要大,需在结尾添加f/F标识
-
-
-
- double(双精度),8个字节,在java中小数默认为
double
类型
- double(双精度),8个字节,在java中小数默认为
-
- 字符型
-
- char(字符型),2个字节,取值范围0~65535,字符在存储过程中需要按照某种规则转化为数字,这种转化规则称之为编码。ASCI表取值范围0~127
-
-
- ISO-8859-1(西欧码表),1个字节 表示1个字符
-
-
-
- 所有的码表必须兼容西欧码表,前256个字符是一样的,而且前256个字符永远占用1个字节。
-
-
-
- gb2312(国标码),gbk兼容gb2312,2个字节 表示1个字符,收录了绝大部分常见的简体汉字和一部分的繁体字。
-
-
-
Unicode
编码体系(万国码):UTF-8,3个字节表示1个字符;utf-16,2个字节表示1个字符
-
- 布尔型:boolean,4个字节
数据类型转换
隐式类型转换
-
小的类型可以自动转化为大的类型,自动类型转换, 也叫隐式类型转换
-
整数可以自动转化为小数,但是可能产生精度损失
-
字符可以自动转化为整数
-
- 如short = ‘a’;char c = 97; 直接赋值不会报错
-
- 如char c = ‘a’;short s = c; 不可以这样赋值。原因是short类型和char类型范围没有完全重合,不能完全包含。
- 如char c = ‘a’;short s = c; 不可以这样赋值。原因是short类型和char类型范围没有完全重合,不能完全包含。
显式类型转换
- 大的类型给小的类型转换,也叫强制类型转换
注意:
大类型可以强制转为小类型,但是在转化的时候因为字节的损失所以可能导致数据
不准确。
2.2.2 引用数据类型
数组:[]、字符串:String、类:class、接口:interface
注意
:字符串
+ 任何数据 = 字符串
;
3、运算符
3.1 算术运算符
- byte/short/char在运算的时候会自动提升为int
- int 在计算完成之后结果一定是 int
- 小类型和大类型运算的时候结果一定是大类型
- 整数/0 ,会报算术异常
- 非零小数/0、非零数字/0.0,得到的结果为Infinity
- 0/0.0、0.0/0、0.0/0.0,得到的结果为NaN
- %的结果的符号看的是%左边数字的符号
- ++/–自增/自减,在变量之前:
先自增再赋值
/先自减再赋值
,在变量之后:先赋值再自增
/先赋值再自减
- byte/short/char可以参与自增/自减运算,运算之后结果类型不变
3.2 赋值运算符
- 除了
=
之外,其他的符号都是相当于在这个变量本身的基础上来进行运算 - 除了
=
之外,其他的符号都要求这个变量必须有值 - java中不支持连等定义,如(int i=j=5);但支持连等运算,如(int i = 5;i -= i *= i++;结果为-20)
- 都可以byte/short/char类型参与运算,运算之后结果类型不变
- 在运算的时候是从左往右编译的,但是在计算结果的时候从右往左计算
- int i = 5;i = i++; 结果为5,通过远算符优先级来解释,因自增运算符的优先级要
高于
赋值运算符。
3.3 关系运算符
- 运算的结果一定是逻辑值(true/false)
3.4 逻辑运算符
主要针对逻辑值
进行运算的
&
: 两边为true,则为true;遇false,则为false|
: 两边为false,则为false;遇true,则为true!
: 非真即假;非假即真^
: 两边相同则为假,两边不同则为真&&
: 运算规则和&
是一致的,如果前边的表达式的结果是false,则后边的表达式就不在运算了,短路,再考虑使用&&
时,两边要有交集才可。||
: 运算规则和|
是一致的,如果前边的表达式的结果是true,则后边的表达式就不在运算了,短路,使用||
时,两边有其一满足条件即可。||
在&&
前边的时候能够把&&
给短路掉,但是&&
在||
前边,不能把||
短路掉
//`||`在`&&`前边的时候能够把`&&`给短路掉
public class test {
public static void main(String[] args) {
int i = 3,j = 5;
boolean b = true || i++ > 1 && j++ > 3;
System.out.println(b); //true
System.out.println(i); //3
System.out.println(j); //5
}
}
//`&&`在`||`前边,不能把`||`短路掉
public class test {
public static void main(String[] args) {
int i = 3,j = 5;
boolean b = false && i++ > 1 || j++ > 3;
System.out.println(b); //true
System.out.println(i); //3
System.out.println(j); //6
}
}
3.5 位运算
注意
:位运算只针对整数的补码进行运算
&
、|
、^
- 交换值方式
-
- 交换值的三种方式的优劣性
-
-
- 亦或法:效率最高,使用频率低,只适用于整数值的交换。
-
-
-
- 加减法:效率低于亦或法,但又高于追尾法,理论上适用于数值型,但在小数上不准确,往往还是在整数上使用多。
-
-
-
- 追尾法:效率最低,使用频率高,适用于所有的类型
-
<<
、>>
、>>>
-
- 在进行移位运算,并不是直接移动对应的位数,而是将要移动的位数对32进行取余,移动的是余数对应的位数。如:28 << 35 = 28 << (35%32) = 28 << 3 = 224;右移为也类似。
~
:取反
3.6 三元运算符
- 逻辑值?表达式1:表达式2
- 执行顺序:先判断逻辑值,如果逻辑值为true,那么执行表达式1;反之执行表达式2
- 三元表达式依然是一个表达式,所以需要有一个计算结果,这个计算结果定义变量来进行存储。如:int i = 5,j = 8;i > j ? System.out.println(i):System.out.println(j);报错原因是没有结果可存储。
- 表达式1和表达式2的计算结果类型要一致或能兼容或能类型能转化
- 三元表达式在嵌套时,最好加上括号加以区分
public class test {
public static void main(String[] args) {
int i = 3,j = 8,k = 5;
int max = i > j ? (i > k ? i : k) : (j > k ? j : k);
System.out.println("最大值:" + max);
}
}
3.7 运算符优先级
()
~
++
--
!
*
/
%
+
-
<<
>>
>>>
关系
逻辑
&
|
三元运算符
赋值
:从左至右,优先级逐渐降低。
总结:一元运算 > 二元运算 > 三元运算 > 赋值
。
3.8 综合案例
- 定义一个整数变量,判断这个整数是一个奇数还是一个偶数
public class test {
public static void main(String[] args) {
/**
* 定义一个整数变量,判断这个整数是一个奇数还是一个偶数
*/
int num = 15;
//1&奇数为奇数,1&偶数为偶数,以此来判断奇偶数
String str = (num & 1) == 1 ? "奇数" : "偶数";
System.out.println(str);
}
}
- 定义一个变量表示分数,分数>=90-A、>=80-B、>=70-C、>=60-D、<60-E
public class test {
public static void main(String[] args) {
/**
* 定义一个变量表示分数,分数>=90-A、>=80-B、>=70-C、>=60-D、<60-E
*/
double score = 80;
//嵌套三元运算符,需要加括号加以区分
char level = score >= 90 ? 'A' : ((score >= 80) ? 'B' : ((score >= 70) ? 'C' : ((score >= 60) ? 'D' : 'E')));
System.out.println("分数对应的等级为:" + level);
}
}
- 定义一个变量表示年份,判断这一年是平年还是闰年
public class test {
public static void main(String[] args) {
/**
* 定义一个变量表示年份,判断这一年是平年还是闰年
*
* 分析:
* 闰年:逢百年整除400,不是百年整除4
* 平年:其他均为平年
* 如:2000为闰年(逢百年整除400)、2012为闰年(不是百年整除4)、2100为平年(逢百年不能整除400)
*/
int year = 1600;
//嵌套三元运算符
String str = year % 100 == 0 ? (year % 400 == 0 ? "闰年" : "平年") : ( year % 4 == 0 ? "闰年" : "平年");
System.out.println(str);
}
}
4、流程控制
4.1 顺序结构
指代码从上到下、从左到右来依次编译执行
4.2 分支结构
4.2.1 判断结构
对于判断结构适用于判断范围
,首先要对逻辑值
进行判断,只有逻辑值为true
,代码块
才会被执行。
if(逻辑值){
code;
}
- 执行顺序
-
- 如果逻辑值为true,那么就执行code,反之,不执行。
if(逻辑值){
code1;
}else{
code2;
}
- 执行顺序
-
- 先执行逻辑值,
-
- 如果逻辑值为true,那么就执行code1;
-
- 如果逻辑值为false,则执行code2。
if(逻辑值1){
code1;
}else if(逻辑值2){
code2;
}...
- 执行顺序
-
- 先执行逻辑值1,如果逻辑值1为true,,就执行code1;
-
- 如果逻辑值1为false,则会执行逻辑值2,如果逻辑值2为true ,执行code2。…
应用案例
- 输入三个整数,获取最大值
import java.util.Scanner;
public class test {
public static void main(String[] args) {
/**
* 输入三个整数,获取最大值
*/
Scanner s = new Scanner(System.in);
int i = s.nextInt();
int j = s.nextInt();
int k = s.nextInt();
//方法一:
int max = i;
if(max < j){
System.out.println("三个数中的最大值为:" + j);
}
if(max < k){
System.out.println("三个数中的最大值为:" + k);
}
//方法二:
if(i > j){
if(i > k){
System.out.println("三个数中的最大值为:" + i);
}else{
System.out.println("三个数中的最大值为:" + k);
}
}else{
if(j > k){
System.out.println("三个数中的最大值为:" + j);
}else{
System.out.println("三个数中的最大值为:" + k);
}
}
}
}
- 输入分数,获取分数的等级
import java.util.Scanner;
public class test {
public static void main(String[] args) {
/**
* 输入分数,获取分数的等级
*/
Scanner s = new Scanner(System.in);
double score = s.nextDouble();
//合法性的校验,要首先考虑
if(score > 100 || score < 0){
System.out.println("分数不合法");
}else if(score >= 90){
System.out.println("A");
}else if(score >= 80){
System.out.println("B");
}else if(score >= 70){
System.out.println("C");
}else if(score >= 60){
System.out.println("D");
}else
System.out.println("E"); //代码块只有`一句话`,那么`{}`可以省略不写
}
}
注意
:如果if或者是else中的代码块只有一句话
,那么{}
可以省略不写
4.2.2 选择结构
- 选项可把它理解为选择,是多个确定的数据供选择
- switch以及case中的选项:只能是byte/short/char/int,JDK1.7之后支持String,枚举
- 如果case之后没有break,那么从匹配的选项开始,然后依次往下执行,直到遇到break或者switch的末尾才结束。
- 打印每一个月有多少天
public class test {
public static void main(String[] args) {
/**
* 打印每一个月有多少天
*
* 案例:
* 31天的有哪几个月:1、3、5、7、8、10、12
* 30天的有哪几个月:4、6、9、11
* 如果是闰年:2月有29天
* 如果是平年:2月有28天
*/
int month = 4;
switch (month){
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
System.out.println(month + " 月为31天");
break;
case 4:
case 6:
case 9:
case 11:
System.out.println(month + " 月为30天");
break;
case 2:
System.out.println(month + " 月为28天");
break;
}
}
}
- 如果每一个case之后都有break,case的顺序不影响结果
如果有一个或者多个case之后没有break,那么case会影响结果
switch(选项){
case 选项1:
code1;
break; //表示当前选项的结束
case 选项2:
code2;
break;
...
default: //如果其他选项都不匹配,则执行default的code;合法性校验,可写在此处
coden;
}
应用案例
- 输入三个数字分别表示年月日,计算这一天是这一年的第几天
import java.util.Scanner;
public class test {
public static void main(String[] args) {
/**
* 输入三个数字分别表示年月日,计算这一天是这一年的第几天
*
*/
Scanner s = new Scanner(System.in);
int year = s.nextInt();
int month = s.nextInt();
int day = s.nextInt();
//定义变量记录总得天数
int sum = 0;
//解题思路:借用没有break,它会依次往下累加,
switch(month){
case 12:sum += 30; //12月的时候,会经历完整的11月
case 11:sum += 31; //11月的时候,会经历完整的10月
case 10:sum += 30;
case 9:sum += 31;
case 8:sum += 31;
case 7:sum += 30;
case 6:sum += 31;
case 5:sum += 30;
case 4:sum += 31;
case 3:
//闰年和平年的判断,来确定2月的天数
if(year % 400 == 0 || year % 100 != 0 && year % 4 == 0){
//闰年29天
sum += 29;
}else{
//平年28天
sum += 28;
}
case 2:sum += 31;
case 1:sum += 0;
}
sum += day;
System.out.println(sum);
}
}
4.3 循环结构
4.3.1 while循环
需要定义变量来控制循环的次数,控制好循环执行的条件,控制次数的变量需要改变
while(逻辑值){
code;
}
- 执行流程
应用案例
- 输入一个整数n,打印1~n中所有的能被3整除,而不能被5整除的数字
import java.util.Scanner;
public class test {
public static void main(String[] args) {
/**
* 输入一个整数n,打印1~n中所有的能被3整除,而不能被5整除的数字
*
* 解题思路:
* 先获取1~n中所有3的倍数
* 再判断这个数是否能够被5整除
*/
Scanner s = new Scanner(System.in);
int n = s.nextInt();
int count = 3;
while(count <= n){
if(count % 5 != 0) System.out.println(count);
count += 3;
}
}
}
- 输入一个整数n,输出这个n是几位数
import java.util.Scanner;
public class test {
public static void main(String[] args) {
/**
* 输入一个整数n,输出这个n是几位数
*
*/
Scanner s = new Scanner(System.in);
int n = s.nextInt();
//定义一个变量来记录位数
int count = 0;
while(n != 0){
count++;
//整数除以10,直到被除等于0,位数就可以判断了,每除以一次10,就减少一位
n /= 10;
}
System.out.println(count);
}
}
- 输入一个整数n,打印这个整数的所有因数
import java.util.Scanner;
public class test {
public static void main(String[] args) {
/**
* 输入一个整数n,打印这个整数的所有因数
*
* 解题思路:
* 1、先获取1~n的所有数
* 2、在判断这个数能否整除n
*/
Scanner s = new Scanner(System.in);
int n = s.nextInt();
int count = 0;
while(count <= n){
if(n % count == 0) System.out.println(count);
count++;
}
}
}
4.3.2 do while循环
无论逻辑值是否满足,代码块至少执行一次,因代码块在循环条件之上,只要稍微改一下while循环条件,就能实现do while的效果。
do{
code; //代码块在逻辑值之上,所以不管循环条件是否满足都会执行一次
}while(逻辑值);
4.3.3 for循环
for(定义循环变量;控制条件;改变循环变量){
code;
}
总结
:
1、如果需要执行多次建议使用循环
2、如果次数固定或者变化比较规律,建议使用for循环;如果次数不固定或者变化没有规律,建议使用while循环。
应用案例
public class test {
public static void main(String[] args) {
/**
* *** 第n行
* **
* * 第1行
*
* 思路:
* 1、行数:n->1
* 2、第i行的*个数:i->1
*/
for (int i = 3; i > 0; i--) {
for (int j = i; j > 0; j--) {
System.out.print("*");
}
System.out.println();
}
/**
* * 第1行
* **
* *** 第n行
*
* 思路:
* 1、行数:1->n
* 2、第i行的空格个数:1->n-i
* 3、第i行的*个数:1->i
*/
for (int i = 1; i <= 3; i++) {
for(int j = 1;j <= 3 - i;j++){
System.out.print(" ");
}
for(int k = 1;k <= i;k++){
System.out.print("*");
}
System.out.println();
}
/**
* *** 第n行
* **
* * 第1行
*
* 思路:
* 1、行数:n->1
* 2、第i行的空格个数:n-i->1
* 3、第i行的*个数:i->1
*/
for (int i = 3; i > 0; i--) {
for(int j = 3 - i;j > 0;j--){
System.out.print(" ");
}
for(int k = i; k > 0;k--){
System.out.print("*");
}
System.out.println();
}
/**
* * 第1行
* ***
* *****
* ******* 第n行
*
* 思路:
* 行数:1->n
* 第i行空格数:1->n-i
* 第i行*数:1->2*i - 1
*/
for (int i = 1; i <= 4; i++) {
for(int j = 1; j <= 4 - i; j++){
System.out.print(" ");
}
for(int k = 1; k <= 2 * i - 1;k++){
System.out.print("*");
}
System.out.println();
}
}
}
public class test {
public static void main(String[] args) {
/**
* *
* **
* ***
* ****
* *****
*/
//方法一:
for (int i = 1; i <= 5; i++) {
for(int j = 1;j <= i;j++){
System.out.print("*");
}
System.out.println();
}
//方法二:
for(int i = 1,j = 1;i <= 5;j++){
//打印*
System.out.print("*");
//判断这一行是否到达了末尾
if(j == i){
//如果到达末尾,换行
System.out.println();
//新的一行,行数+1
i++;
j = 0;
}
}
}
}
4.3.4 break和continue
break
:仅在选择结构和循环结构中是使用,表示终止
当前的一层结构
continue
:只能用于循环结构中。表示跳过当前循环继续下次循环。
应用案例
- 输入一个数字,判断这个数字是否是一个质数
import java.util.Scanner;
public class test {
public static void main(String[] args) {
/**
* 输入一个数字,判断这个数字是否是一个质数
*
* 分析:
* 1、质数->只能被1和自身整除,1不是质数
* 2、n -> 只需从2~n-1判断即可,所以判断的次数就为:n-2
* 3、以因数为例:因数是能整除的数(如:6 :1、2、3、6,规律:只要过了一半的数都
* 未能整除,但要除了自身的数,则就没有能整除的数)
* 4、由此质数的判断次数则会变为:2~n/2(除了1和自身的数,只要判断n/2之间的数就可以了)
*/
Scanner s = new Scanner(System.in);
int num = s.nextInt();
//合法性校验
if(num <= 1){
System.out.println(num + " 不是质数");
}else{
//数字是质数,为true;不是质数,则为false
boolean flag = true;
for(int i = 2; i <= num/2;i++){
if(num % i == 0){
flag = false;
break;
}
}
if(flag) System.out.println(num + " 是质数");
else System.out.println(num + " 不是质数");
}
}
}
- 100文钱买100只鸡,3文买一只公鸡,2文买一只母鸡,1文买3只小鸡
public class test {
public static void main(String[] args) {
/**
* 百钱百鸡:100文钱买100只鸡,3文买一只公鸡,2文买一只母鸡,1文买3只小鸡
*
* 分析:
* 1、3文买一只公鸡,那么100文能买33只公鸡(gj)
* 2、2文买一只母鸡,那么100文能买50只母鸡(mj)
* 3、1文买3只小鸡,那么100文能买300只小鸡(xj)
*/
//方法一:
for(int gj = 0;gj < 33;gj++){
for(int mj = 0;mj < 50;mj++){
for(int xj = 0;xj < 100;xj++){
if(xj % 3 == 0){
if((gj + mj + xj) == 100 && (3 * gj + 2 * mj + xj * 1/3) == 100){
System.out.println("公鸡数量:" + gj + "母鸡数量:" + mj + "小鸡数量:" + xj);
}
}
}
}
}
//方法二:
for(int gj = 0;gj < 33;gj++){
for(int mj = 0;mj < 50;mj++){
int xj = 100 - gj -mj;
//xj的数量必须要满足能被3整除
if(xj % 3 == 0 && (3 * gj + 2 * mj + xj/3) == 100){
System.out.println("公鸡数量:" + gj + ",母鸡数量:" + mj + ",小鸡数量:" + xj);
}
}
}
}
}
- 输入一个数字n,然后将n分解质因数
import java.util.Scanner;
public class test {
public static void main(String[] args) {
/**
* 输入一个数字n,然后将n分解质因数
*
* 分析:
* 1、要从最小质数2开始循环
* 2、分解数字n,得到n被整除的质数,要求上一个质数必须除尽,才能进行下一个质数整除。
* 例如:100从最小质数2开始整除 得50,还能被2整除,得25,分解的数不能被2整除,
* 说明2被除尽了;
* 然后下一个最小质数3开始整除,发现不能整除;
* 在进行下一个数4整数,但4不是质数;
* 所以再进行下一个质数5开始整除,得5,分解的数还能被5整除,得1,
* 分解的数不能再被5整除,说明5被除尽了。
*/
Scanner s = new Scanner(System.in);
int n = s.nextInt();
//循环条件和控制次数可先不写,根据计算的实际情况来填写
for(int i = 2;n != 1;){
if(n % i == 0){
System.out.println(i);
/**
* 整除后将值重新赋给n,如此能继续寻找下一个质数,
* 直到n被整除的结果为1,循环结束,
* 此时把n!=1作为for循环的循环条件
*/
n /= i;
}else{
//上一个质数i已经除尽了,需要自增i++
i++;
}
}
}
}
5、一维数组
- 存储同一类型的多个数据
容器
- 大小固定
- 对存入的数组元素进行了
编号
,这个编号称为下标也称索引,是从0开始算的。
5.1 定义数组
数组5.1.1
、5.1.2
的声明和初始化是能分开的,5.1.3
的声明和初始化不能分开
5.1.1 动态初始化
只知道大小,不知道具体元素
数据类型[] 数组名 = new 数组类型[长度];
例
:int[] arr = new int[5];//表示定义了一个能存储5个整型元素的数组
5.1.2 静态初始化①
数组类型[] 数组名 = new 数据类型[]{元素1,元素2,...};//数组的长度是确定的,长度就不能变了
例
:int[] arr = new int[]{1,2,3,4,5,6};
5.1.3 静态初始化②
数组类型[] 数组名 = {元素1,元素2,...};
例:int[] arr = {1,2,3,4,5,6};
5.1.4 内存
-
方法实参传递给形参的时候一定足以:一切都是值传递
-
如果是基本数据类型,那么传递的就是字面值
-
如果是引用数据类型,那么传递的就是地址值
-
Java将内存分为了5块:
栈内存
、堆内存
、方法区
、本地方法栈
、PC计数器
(寄存器)
A 、栈内存:存储变量。变量在声明的时候存储到栈内存中,不会自动给值,除非程序中手动给值。变量在栈内存中使用完成
之后要立即释放
。
B、堆内存:存储的是对象。对象在存储到堆内存中之后,会被堆内存赋予一个默认值。对象
使用完成不一定
会从堆内存中立即移除
,而是在不确定
的某个时刻被回收
- byte/short/int:默认值为
0
- long:默认值为
0L
- float:默认值为
0.0F
- double:默认值为
0.0
- char:默认值为
\u000
;用的是utf-16来存储的,\u000是空字符 - boolean:默认值为
false
- 引用类型数据:数组、String、类,默认值为
null
- byte/short/int:默认值为
5.2 遍历数组
5.2.1 普通for循环:先获取下标
,然后利用下标获取
元素
备注
:可循环,也可改变
数组元素
for(int i = 0;i < arr.length;i++){
//通过下标获取数组元素
System.out.println(arr[i]);
}
5.2.2 增强for循环:定义变量
来依次表示每一个元素
注意
:只能遍历数组而不能
改变数组中元素
for(int i:arr){
//由于数组的连续性,不依赖下标直接获取元素
System.out.println(i);
}
5.2.3 数组的元素拼接
成字符串返回
String str = Arrays.toString(arr);
System.out.println(str);
5.2.4 数组的排序
冒泡
、选择
排序:可升序也可降序,时间复杂度:O(n^2)Arrays.sort(arr)
:只能升序排序,底层是以快速排序+归并排序,时间复杂度:O(nlogn)
应用案例
- 获取数组中的最值
public class test {
public static void main(String[] args) {
/**
* 获取数组中的最大值
*/
int[] arr = {2,8,9,2,7,3,0,8,3,1,4};
//方法一:
int max1 = arr[0]; //定义最大值
for(int i = 0;i < arr.length;i++){
if(arr[i] > max1) max1 = arr[i];
}
System.out.println("数组中的最大值为:" + max1);
//方法二:
int max2 = 0; //定义最大值的索引
for(int i = 0;i < arr.length;i++){
if(arr[i] > arr[max2]) max2 = i;
}
System.out.println("通过下标获取最大值:" + arr[max2]);
}
}
- 查询指定元素的位置
public class Test5 {
public static void main(String[] args) {
/**
* 查询指定元素的位置
*/
int[] arr = {12,34,56,7,3,10};
//调用方法
int index = getIndex(arr,56);
if(index != -1)
System.out.println("元素对应的索引:" + index);
else //index = -1
System.out.println("没有该元素!");
}
/**
* 定义一个方法:查询数组中指定的元素对应的索引
* 不确定因素:哪个数组,哪个指定元素(形参)
* 返回值:索引
*/
public static int getIndex(int[] arr,int ele){
int index = -1;//这个初始值只要不是数组的索引即可
for (int i = 0; i < arr.length; i++) {
if(arr[i] == ele){
index = i;//只要找到了元素,那么index就变成为1
break;//只要找到这个元素,循环就停止
}
}
return index;
}
}
- 添加元素
import java.util.Scanner;
public class Test5 {
public static void main(String[] args) {
/**
* 添加元素
*/
int[] arr = {12,34,56,7,3,10};
//输出增加元素前的数组
System.out.print("增加元素前的数组:");
for (int i = 0; i < arr.length; i++) {
if(i != arr.length - 1 )
System.out.print(arr[i] + ",");
else
System.out.println(arr[i]);
}
//从键盘接收数据
Scanner sc = new Scanner(System.in);
System.out.print("请录入你要添加元素的指定下标:");
int index = sc.nextInt();
System.out.print("请录入你要添加的元素:");
int ele = sc.nextInt();
//增加元素
//调用方法
insertEle(arr,index,ele);
/**
* arr[5]=arr[4]
* arr[4]=arr[3]
* arr[3]=arr[2]
*
* 有规律的重复,可用循环解决
*/
// int index = 1;
// for(int i = arr.length - 1; i >= (index + 1);i--){
// arr[i] = arr[i - 1];
// }
// arr[index] = 666;
//输出增加元素后的数组
System.out.print("增加元素后的数组:");
for (int i = 0; i < arr.length; i++) {
if(i != arr.length - 1 )
System.out.print(arr[i] + ",");
else
System.out.println(arr[i]);
}
}
/**
* 提取一個添加元素的方法:
* 在数组的指定位置上添加一个指定的元素
* 在哪个数组的哪个位置添加哪个元素
* 不确定因素:形参;哪个数组;哪个位置;哪个元素
* 返回值:无
* @param arr
*/
public static void insertEle(int[] arr,int index,int ele){
for(int i = arr.length - 1; i >= (index + 1);i--){
arr[i] = arr[i - 1];
}
arr[index] = ele;
}
}
- 删除指定元素
import java.util.Arrays;
public class Test5 {
public static void main(String[] args) {
/**
* 添加元素
*/
int[] arr = {12,34,56,7,3,10};
//输出删除元素前的数组
System.out.print("删除元素前的数组:");
System.out.println(Arrays.toString(arr));
//找到要删除的元素对应的索引即可:
int index = -1;
for(int i = 0;i < arr.length;i++){
if(arr[i] == 3){
index = i;
break;
}
}
//删除元素
//调用方法
/**
* arr[2]=arr[3]
* arr[3]=arr[4]
* arr[4]=arr[5]
* arr[5]=0
*
* 有规律的重复,可用循环解决
*/
if(index != -1){
for(int i = index;i <= arr.length - index;i++){
arr[i] = arr[i + 1];
}
arr[arr.length - 1] = 0;
}else{
System.out.println("没有你要删除的元素!!!");
}
//输出增加元素后的数组
System.out.print("增加元素后的数组:");
System.out.println(Arrays.toString(arr));
}
}
- 数组冒泡排序
import java.util.Arrays;
public class test {
public static void main(String[] args) {
/**
* 数组冒泡排序
*
* 分析:
* 1、冒泡排序的轮次:arr.length - 1,可确定外循环需要循环的次数
* 2、冒泡排序过程中,第i轮需比较元素的次数:arr.length - i,可确定内循环需要循环的次数
*/
int[] arr = {2,8,9,2,7,3,0,8,3,1,4};
for(int i = 1;i < arr.length;i++){
for(int j = 1;j < arr.length - i + 1;j++){
if(arr[j - 1] > arr[j]){
//追尾法实现值交换
int temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
}
}
}
//打印排序后的数组
System.out.println(Arrays.toString(arr));
}
}
扩展
:
- 时间复杂度
A、时间复杂度:找重复执行的这段代码,将这段代码的执行时间认为是单位1,那么执行这个单位1的次数就是时间复杂度(用O()表示)----习惯上,只考虑最高阶,不考虑系数
B、冒泡排序的时间复杂度:O(n^2)
C、时间复杂度符合形式:n^x
,(logn)^x
,n^x(logn)^y
- 空间复杂度
A、在已知条件下,执行这段代码需要额外耗费的空间数量- 以冒泡为例解释:数组长度随意变化的情况下,冒泡排序只需要3个变量,不随长度变化而变化。3 = 3 * n ^0 ->n ^0------空间复杂度为:O(1)
注意
:时间复杂度不决定时间的长短,决定的是次数的多少。
- 数组选择排序
import java.util.Arrays;
public class test {
public static void main(String[] args) {
/**
* 数组选择排序
*
* 分析:
* 1、选择排序的轮次:arr.length - 1,可确定外循环需要循环的次数
* 2、选择排序过程中,第i轮选择的下标:i - 1
* 3、要比较的下标:i -> arr.length - 1(控制要比较的下标)
*/
int[] arr = {2,8,9,2,7,3,0,8,3,1,4};
//控制轮数
for(int i = 1;i < arr.length;i++){
//控制要比较的下标
for(int j = i; j < arr.length;j++){
//判断选择的下标与要比较的下标数组元素大小
if(arr[i - 1] > arr[j]){
//交换值
int temp = arr[i - 1];
arr[i - 1] = arr[j];
arr[j] = temp;
}
}
}
//打印排序的结果
System.out.println(Arrays.toString(arr));
}
}
扩展`:
-
时间复杂度
A、选择排序的时间复杂度:O(n^2) -
空间复杂度
A、选择排序的空间复杂度为:O(1)
5.2.5 数组的反转
- 传统方法:创建新数组,从原数组倒着拿,往新数组正着放,时间复杂度:O(n);空间复杂度:O(n)
首尾交换法
:时间复杂度:O(n);空间复杂度:O(1)
应用案例
- 数组反转
import java.util.Arrays;
public class test {
public static void main(String[] args) {
/**
* 数组的反转
*
* 分析:
* 1、首尾交换法实现
*/
int[] arr = {2,8,9,2,7,3,0,8,3,1,4};
for(int i = 0,j = arr.length - 1;i < j;i++,j--){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
//打印排序的结果
System.out.println(Arrays.toString(arr));
}
}
5.2.6 数组元素的查找
- 数组无序的情况,逐个遍历比较
应用案例
public class test {
public static void main(String[] args) {
/**
* 数组无序的情况下,二分法查找数组元素的索引
*
*/
int[] arr = {2,8,9,2,7,3,0,8,3,1,4};
int num = 9;
for(int i = 0;i < arr.length;i++){
if(arr[i] == num){
System.out.println("数组中的 " + num + " 所在的索引为:" + i);
}
}
}
}
- 数组有序的情况,使用二分法查找,时间复杂度:
O(logn)
;空间复杂度:O(1)
应用案例
public class test {
public static void main(String[] args) {
/**
* 数组有序的情况下,查找数组元素的索引
*
*/
int[] arr = {14,26,37,58,64,68,82,90};
int num = 58;
//记录最小值的下标
int min = 0;
//记录最大值的下标
int max = arr.length - 1;
//计算中间值的下标
int mid = (min + max)/2;
//方式一:
while(arr[mid] != num){
if (num > arr[mid]) min = mid + 1;
else max = mid - 1;
if(min > max){
mid = -1;
break;
}
mid = (min + max)/2;
}
System.out.println("数组中的 " + num + " 所在的索引为:" + mid);
//方法二:
while(min <= max){
if(num == arr[mid]) break;
else if(num > arr[mid]) min = mid + 1;
else max = mid - 1;
mid = (min + max)/2;
}
System.out.println("数组中的 " + num + " 所在的索引为:" + mid);
}
}
5.2.7 数组的复制
- 数组复制
System.arraycopy(源数组,复制源数组元素的起始下标,目标数组,目标数组的起始下标,要复制的长度);
- 数组扩容
本质上是在做数组的复制
过程,复制完成之后一定是产生了一个新
的数组
Arrays.copyOf(源数组,改变源数组长度);
应用案例
import java.util.Arrays;
public class test {
public static void main(String[] args) {
int[] arr1 = {5,6,9,11,6,3,8,4,18};
int[] arr2 = new int[5];
/**
* 数组的复制
*/
System.arraycopy(arr1,3,arr2,1,3);
for (int i : arr2) {
System.out.println(i);
}
/**
* 数组的扩容
*
* 实际上数组的扩容本质上就是数组的复制过程
* System.arraycopy(arr1,0,newArr,0,arr1.length); //数组的复制
* arr1 = newArr; //把复制的新数组的内存地址赋给arr1
*
* ============================================
* 底层对数组扩容时,有做数组长度的判断,扩容的最小数组长度遵循小者优先
* int[] newArr = new int[len];
* int min = arr1.length > len ? len : arr1.length;
*/
/**
* arr1.length < len,因此新数组的长度为arr1.length,
* 扩容后数组长度为arr1.length;
* 此种情况:扩容
*/
arr1 = Arrays.copyOf(arr1,15);
/**
* arr1.length > len ,因此新数组的长度为len,
* 扩容后数组长度为5
* 此种情况:缩容
*/
// arr1 = Arrays.copyOf(arr1,5);
}
}
5.2.8 main方法
5.2.9 可变参数
- JDK1.5出现的新特性
- 作用提供一个方法,参数的个数是可变的
- 解决了部分方法的重载问题
- 方法的内部对可变参数的处理跟数组是一样的
- 可变参数和其他数据一起作为形参的时候,可变参数一定要放在最后
public class Test5 {
/**
* 可变参数:作用提供一个方法,参数的个数是可变的
* int... num
* double... num
* 作用:解决了部分方法的重载问题
* @param args
*/
public static void main(String[] args) {
method01(10);
method01();
method01(20,30,40);
method01(new int[]{11,2,33,44});
}
public static void method01(int... num){
System.out.println("------------1");
for (int i : num) {
System.out.print(i + "\t");
}
System.out.println();
}
}
5.2.10 Arrays
import java.util.Arrays;
public class Test5 {
public static void main(String[] args) {
//给定一个数组
int[] arr = {1,3,7,2,4,8};
//toString:对数组进行遍历查看的,返回的是一个字符串,这个字符串比较好看
System.out.println(Arrays.toString(arr));
//binarySearch:二分法查找,使用这个方法前提:一定是一个有序的数组
Arrays.sort(arr);
System.out.println(Arrays.binarySearch(arr,4));
//copyOf:完成数组的复制
int[] arr2 = {1,3,7,2,4,8};
int[] newArr = Arrays.copyOf(arr2,4);
System.out.println(Arrays.toString(newArr));
//copyOfRange:区间复制
int[] newArr2 = Arrays.copyOfRange(arr2,1,4); //[1,4)--->1~3位置
System.out.println(Arrays.toString(newArr2));
//equals:比较两个数组的值是否一样
int[] arr3 = {1,3,7,2,4,8};
int[] arr4 = {1,3,7,2,4,8};
System.out.println(Arrays.equals(arr3,arr4));//true
System.out.println(arr3 == arr4);//false;比较的是左右的地址值,返回结果一定是false
//fill:数组的填充
int[] arr5 = {1,3,7,2,4,8};
Arrays.fill(arr5,10);
System.out.println(Arrays.toString(arr5));
}
}
6、二维数组
存储一维数组的数组。俗称:大盒子装小盒子
6.1 定义数组
6.1.1 动态初始化
数据类型[][] 数组名 = new 数组类型[存储一维数组的个数][每一个一维数组的元素个数];
例
:int[][] arr = new int[3][5]; //定义存储3个元素类型为int的一维数组,每一个一维数组存储的是5个int类型的元素
- 二维数组的存储过程
public class test {
public static void main(String[] args) {
//定义二维数组
int[][] arr = new int[3][2];
for(int i = 0;i < arr.length; i++){
System.out.println(arr[i]);//打印的是每一个一维数组的内存地址
}
}
}
6.1.2 动态初始化
数组类型[][] 数组名 = new 数据类型[一维数组的个数][];//一维数组的元素长度未指定,可自定义一维数组的长度。
例
:
int[][] arr = new int[5][]; //定义了能存储5个元素类型为int的一维数组
arr[0] = new int[5]; //因一维数组的长度未定义,可自定义一维数组的长度
arr[1] = new int[2]; //因一维数组的长度未定义,可自定义一维数组的长度
public class test {
public static void main(String[] args) {
//定义二维数组
int[][] arr = new int[5][];
//未定义一维数组的长度,就给一维数组赋值
arr[0][0] = 10; //能编译通过,但运行后,报NullpointerException
}
}
6.1.3 静态初始化
数据类型[][] 数组名 = {数组1,数组2,...};
例
:int[][] arr = {{2,6},{4,2,3},{1}}; //定义了一维数组的个数,又定义了每一个一维数组的元素
6.2 遍历数组
6.2.1 普通for循环
public class test {
public static void main(String[] args) {
//定义二维数组
int[][] arr = {{2,6},{4,2,3},{1}};
//普通for循环
for(int i = 0; i < arr.length;i++){
for(int j = 0; j < arr[i].length;j++){
System.out.println(arr[i][j]);
}
}
}
}
6.2.2 增强for循环
public class test {
public static void main(String[] args) {
//定义二维数组
int[][] arr = {{2,6},{4,2,3},{1}};
//增强for循环
for (int[] as : arr) {
for (int i = 0; i < as.length; i++) {
System.out.println(as[i]);
}
}
}
}
应用案例
- 打印杨辉三角
public class test {
public static void main(String[] args) {
/**
* 杨辉三角
* 1
* 1 1
* 1 2 1
* 1 3 3 1
* 1 4 6 4 1
* 1 5 10 10 5 1
*
* 分析:
* 1、开头和结尾都是1
* 2、第i行:i + 1个数
* 2、arr[i][j] = arr[i-1][j] + arr[i-1][j-1]
*
* 输入数字n,输出对应的行数
*/
//获取行数
Scanner s = new Scanner(System.in);
int n = s.nextInt();
//定义二维数组来存储杨辉三角
int[][] arr = new int[n][];
for(int i = 0;i < n; i++){
//初始化每一行一维数组
arr[i] = new int[i + 1];
for(int j = 0;j < i -1;j++){
if(j == 0||j == i) arr[i][j] = 1;
else arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1];
System.out.print(arr[i][j] + "\t");
}
System.out.println();
}
}
}
注意
:'\t'
与"\t"
的区别
'\t'
:是char类型,如果与int类型元素运算,在运算时会自动提升为int类型,此时'\t'
会转化成数字9,然后参与运算。"\t"
:是字符串,字符串+任何数据 = 字符串,所以不会运算,但会拼接
7、方法(函数)
- 将重复使用的代码块提取出来,定义成方法,方便被调用,从而能提高代码的复用性,避免代码冗余。
- 方法不能嵌套定义
- 方法中
return语句
一旦执行,方法就结束
了,return后面代码不再
执行。 - 方法中含有多个
return
语句,有且只有一个能执行
7.1 方法的定义格式
- 定义方法两点需明确:
- 1)
结果类型
需明确:计算的结果是什么类型,返回值应什么类型,如果方法执行后,无结果返回,则返回值类型为void
- 2)
未知量
需明确:方法执行时,是否有未知量参与运算,有则需引入形参
,调用方法需传入实参
。
- 1)
修饰符 返回值类型 方法名(参数列表){
方法体;
return 返回值;
}
7.2 方法调用
7.2.1 单独调用
- 方法调用的格式:方法名称(参数);
- 返回值类型为void,这种方法只能单独调用,不能进行打印调用或者赋值调用
7.2.2 打印调用
- 方法调用的格式:System.out.println(方法名称(参数));
7.2.3 赋值调用
- 方法调用的格式:数据类型 变量名称 = 方法名称(参数);
7.2.4 可变参数
- 格式:数据类型后面直接跟…,如:int… i
- 可变参数本质是数组
- 一个方法中只能定义一个可变参数
- 可变参数必须定义到参数列表的末尾
应用案例
- 判断一个数的奇偶性
public class test {
public static void main(String[] args) {
System.out.println(isOddEven(15));
}
/**
* 判断一个数的奇偶性的方法
*
* 分析:
* 1、判断数字的奇偶性
* 2、要定义用true表示奇数,用false表示偶数
* 3、要判断的这个数在方法中不能自动产生,num就是未知量,要以参数形式传入
*/
public static boolean isOddEven(int num){
return num % 2 == 1;
}
}
- 判断一个数是否为质数
public class test {
public static void main(String[] args) {
System.out.println(isPrime(15));
System.out.println(isPrime1(23));
}
/**
* 判断一个数是否为质数
*
* 分析:
* 1、只需要判断n/2之前的数即可
*/
//方法一:
public static boolean isPrime(int num){
if(num < 2) return false; //return的语句一旦执行,方法就结束了,后面的代码不再执行
for(int i = 2; i < num /2;i++){
if(num % i == 0) return false;
}
return true;
}
//方法二的优点在于减少了循环的次数,缩短执行的时间,唯一不足的地方是与方法一在时间复杂度上一样的。
public static boolean isPrime1(int num){
if(num < 2) return false;
if(num == 2) return true;
if(num % 2 == 0) return false;
//去掉了所有偶数的循环,虽然时间复杂度没有变,但执行的时间肯定变短了
for(int i = 3;i <= num /2;i += 2){
if(num % i == 0) return false;
}
return true;
}
}
- 歌德巴赫猜想:如何一个大于等于6的偶数都能分解成两个质数之和
import java.util.Scanner;
public class test {
public static void main(String[] args) {
/**
* 歌德巴赫猜想:如何一个大于等于6的偶数都能分解成两个质数之和
*
* 分析:
* 1、num>=6的条件要满足
* 2、质数的判断
*/
Scanner s = new Scanner(System.in);
int num = s.nextInt();
//大于等于6的偶数的校验
while(num < 6 || num % 2 == 1){
num = s.nextInt();
}
//如 i <= num,结果会有重复,则需改为 i <= num/2,就不会有重复了
for(int i = 3;i <= num/2;i+=2){
if(isPrime(i) && isPrime(num - i)) System.out.println(num + " = " + i + " + " + (num - i));
}
}
/**
* 判断是否为质数
* @param n
* @return
*/
public static boolean isPrime(int n){
if(n < 2) return false;
if(n == 2) return true;
if(n % 2 == 0) return false;
for(int i = 3;i <= n/2;i+= 2){
if(n % i == 0) return false;
}
return true;
}
}
- 亲密数
public class test {
public static void main(String[] args) {
/**
* 亲密数:如果一个整数A的所有因子(含1而不包含本身)之和等于B,
* 并且B的所有因子之和等于A,那么A和B就是一对亲密数,输出5000以内所有的亲密数
*/
for(int a = 1; a <= 5000; a++){
int b = sumAllFac(a);
int n = sumAllFac(b);
//避免两者值相等,对a和b要做限定条件
if(b < a && n == a) System.out.println(a + " 的亲密数是 " + b);
}
}
public static int sumAllFac(int n){
int sum = 0;
for(int i = 1;i <= n/2;i++){
if(n % i == 0) sum += i;
}
return sum;
}
}
7.3 方法重载
- 在
同一个类
中存在了方法名一样
而参数列表(参数个数和参数类型)不同
)的方法 - 如果没有最符合的方法,那么这个时候就看参数类型是否能够转化
例如:add(int,int) ->add(int,double)->add(double,int),long和float不能自动转化,是因为这两个数据类型需要L和F标识。
7.4 方法递归
- 在方法中调用自身
- 结果的计算是通过递归体向递归头的,称之为:逆推
- 递归一定要有结束条件
- 递归常出现的问题:传入的数值过大,会出现StackOverflowError(栈溢出)。
应用案例
- 求1~n的和,n!也可用递归法
public class test {
public static void main(String[] args) {
System.out.println(sum(10));
}
/**
* 求1~n的和
*/
public static int sum(int n){
if(n == 1) return 1;
return n + sum(n - 1);
// return n + sum(--n); //--n与n-1都是-1
}
}
- 10层台阶,每一次迈1层台阶或者2层台阶,10层台阶迈完有多少种走法
public class test {
public static void main(String[] args) {
System.out.println(step(10));
}
/**
* 10层台阶,每一次迈1层台阶或者2层台阶,10层台阶迈完有多少种走法?
*
* 分析:
* f(10) = f(9) + f(8)
* f(9) = f(8) + f(7)
* ...
* f(2) = 2
* f(1) = 1
*/
public static int step(int n){
if(n == 1) return 1;
if(n == 2) return 2;
return step(n - 1) + step(n - 2);
}
}
7.5 方法传值
- 基本数据类型的传值过程,涉及到栈内存。
public class test {
public static void main(String[] args) {
int i = 5;
changeVal(i);
System.out.println(i);
}
//方法传值问题
public static void changeVal(int i){
i++;
}
}
- 数组中元素是基本数据类型传值,涉及到堆内存。
public class test {
public static void main(String[] args) {
int[] arr = {2,5,1,8,4};
changeVal(arr[2]);
System.out.println(arr[2]);
}
//方法传值问题
public static void changeVal(int i){
i++;
}
}
- 数组的传值,实际上传的是数组地址。
public class test {
public static void main(String[] args) {
int[] arr = {2,5,1,8,4};
changeVal(arr);
System.out.println(arr[2]);
}
//方法传值问题
public static void changeVal(int[] arr){
arr[2]++;
}
}
public class test {
public static void main(String[] args) {
int[] arr = {2,5,1,8,4};
changeRef(arr); //arr指向的是arr2的地址,方法调用完成,弹栈
System.out.println(arr.length); //arr.length还是指的是arr数组的长度
}
//方法传值问题
public static void changeRef(int[] arr){
int[] arr2 = new int[arr.length * 2];
System.arraycopy(arr,0,arr2,0,arr.length);
arr = arr2;
}
}
注意
:对于基本类型
而言,传值传递的是实际值
;对于引用类型
而言,传值传递的是地址
。
8、面向对象
- 面向过程:注重流程的每一步,清楚流程中的每一个
细节
。(自己动手做) - 面向对象:注重的是
对象
,只要找到了对象,就能够拥有对象身上的一切功能。(找其他人做) - 面向对象是基于面向过程的
- 类与对象的关系:类是对象的
概括
/抽取
,对象的特征
称之为属性
,对象的行为
称之为方法
。类中只能定义变量
、方法
、代码块
- 通过new关键字来创建对象,然后可以给对象中属性赋值,也可以调用方法
- 成员变量
- 定义在
类
中 - 作用
整个类
中 - 存储在
堆内存
,会自动
赋予默认值
- 生命周期:在
对象创建
的时候存放到堆内存
中,在对象被回收
的时候销毁
- 定义在
- 局部变量
- 定义在
方法
中 - 作用于
方法
或语句
中,出了
方法和语句就不能使用 - 存储在
栈内存
,不会
自动赋予默认值
- 生命周期:方法或者语句执行的时候
创建
,方法或者语句执行完成
的时候销毁
- 定义在
8.1 构造方法
- 构造方法作用:不是为了创建对象,因为在调用构造器之前,这个对象就已经创建好了,并且属性有默认的初始化的值,调用构造器的目的是给属性进行赋值操作的
- 类中
没有手动
指定构造方法,在编译的时候会自动添加
一个构造方法。 - 构造方法的特点
- 与
类
同名 没有
返回值类型
- 与
- 构造方法可以重载
- 构造方法是在
栈
中执行的,如果创建无数个对象,会出现栈溢出
- new关键字
- 1)内存中开辟一个空间,并赋值属性为默认值
- 2)调用构造方法初始化
- 3)把地址赋值给对象
8.2 this关键字
- 由于定义类的时候
无本类
对象,无法调用属性和方法,就利用this
代替本类对象来调用本类中属性和方法 - this代表本类在活动的对象的引用,可以认为是一个虚拟对象
- this(参数):调用
本类
中对应的构造方法
,this语句必须放在构造方法的第一行
8.3 构造代码块
-
构造
代码块- 构造方法中会调用一些相同方法,为了增加代码的复用性,将相同方法放进{}中。
- 也叫初始化代码块:定义在
类
中,使用{}
包裹起来的代码块 - 无论
哪个
构造方法执行,都需要在构造方法之前
执行一次 - 构造代码块与执行顺序
无关
,始终构造代码块先
执行
-
局部
代码块- 定义在
方法
中,使用{}
包裹起来的代码块,有无
{}对代码的执行结果没有
影响 - 可限制变量的
作用域
,变量的生命周期也会受制于影响
。 - 提高
栈
的资源利用率
。
- 定义在
应用案例
public class test {
}
//定义一个代表复数的类
class Complex{
double real; //复数的实部
double im; //复数的虚部
public Complex(double real,double im){
this.real = real;
this.im = im;
}
public Complex add(Complex c){
double real = this.real + c.real;
double im = this.im + c.im;
//匿名对象
return new Complex(real,im);
}
}
//定义代表矩形的类
class Rectangle{
double a;
double b;
public Rectangle(double a,double b){
this.a = a;
this.b = b;
}
//求周长
public double getGirth(){
return (a + b) *2 ;
}
//求面积
public double getArea(){
return a * b;
}
}
8.4 封装
- 方法、属性私有化、内部类
- 提高代码的复用性,保证数据的合法性
- 属性私有化
- 属性用private修饰,然后提供对外的访问(getXXX)和设置(setXXX)的方法,在方法中进行限定,使属性值更加符合场景要求
8.5 继承
- 继承–继承树
- 发现一些类中的属性和方法是相同的,所以把这些相同的属性和方法提取到一个新的类中,然后利用extends关键字让原来的类和新的类产生联系
- 子类通过继承可以使用父类中的一部分方法和属性
- 提高代码的复用性
- Java中,支持单继承,一个子类只能继承一个父类,一个父类可以有多个子类
- 子类继承父类,必须是当前类的子类对象才能调用父类方法,不能利用别的子类对象调用父类方法
注意
:子类在继承父类的时候继承父类全部的数据域(属性、方法),但是只有一部分的数据域对子类可见
8.6 super关键字
- super在子类中表示父类对象的引用,可以认为是一个虚拟对象
- 在子类中,通过super来调用父类中的方法和属性
- 如果在子类的构造函数中没有手动指定,那么在编译的时候就默认添加一个super(),super()语句表示调用父类对应形式的构造方法来创建一个父类对象
- 构建子类对象时,先构建父类对象
应用案例
- 定义了一个代表矩形的类,给这个类提供一个子类正方形
public class test {
public static void main(String[] args) {
Sqare sqare = new Sqare(6.85);
System.out.println(sqare.getArea());
System.out.println(sqare.getGirth());
}
}
class Rectangle{
private double height;
private double width;
public Rectangle(double height, double width) {
if(height > 0) this.height = height;
if(width > 0) this.width = width;
}
//为了提供一个长和宽不变的长方形,因此只提供get方法,不提供set方法,能避免修改长方形的长和宽
public double getHeight() {
return height;
}
public double getWidth() {
return width;
}
public double getGirth(){
return (height + width) * 2;
}
public double getArea(){
return height * width;
}
}
class Sqare extends Rectangle{
public Sqare(double width){
//正方形是特殊的矩形,所以调用父类的构造方法
super(width,width);
}
}
8.7 方法重写
- 在父子类中存在相同的非静态方法(两等一大一小)
- 1、子类方法的权限修饰符的范围要大于父类
- 2、方法名要相同
- 3、如果父类方法的返回值是基本类型/void,那么子类重写的方法的返回值类型要和父类一致
- 4、如果父类方法的返回值类型是引用类型,那么子类重写的方法的返回值类型要么和父类方法返回值类型一致,要么是父类方法返回值类型的子类。
注意
:基本类型的大小指的是范围的大小,所有基本类型都是平等的,没有继承关系。
//返回值类型为引用类型的问题
class A{}
class B extends A{
public void mb(){}
}
class C{
public B m(){};
}
class D extends C{
public A m(){};
}
C c = new D();
B b = c.m();
c.m().mb(); //C类调用的是B类的方法
在执行的时候,实际执行的D类的方法
A a = c.m(); //a类中没有mb()
8.8 多态(基于继承)
- 编译时多态:方法的重载
- 运行时多态
- 向上造型:父类声明,用子类创建对象,只关心创建对象的类和声明对象的类是否有继承关系,父类有的方法,子类有,子类有的方法父类不一定有,所以子类的返回值类型要越来越小。
注意
:利用向上造型创建的对象,能干什么看的是父类;具体的执行看的是子类 - 方法重写
- instanceof:判断对象和类的关系,格式:对象 instanceof 类/接口
- 工厂设计模式
- 向上造型:父类声明,用子类创建对象,只关心创建对象的类和声明对象的类是否有继承关系,父类有的方法,子类有,子类有的方法父类不一定有,所以子类的返回值类型要越来越小。
8.9 static
- 修饰符
- 可修饰变量、方法、代码块、内部类
8.9.1 内存加载
- 方法区首先加载的java自身的核心类库
- java文件编译之后,生成的
.class
文件会加载到方法区
中之后,会分成两个区域,一块是存储非静态的,另一块存储静态的
8.9.2 static修饰变量
- static 修饰变量(静态变量),会被加载到发方法区,并且在方法区会赋予默认值。
- 静态变量
先于
对象出现,所以通过类名来调用静态变量。 - 所有对象存储的是这个静态变量在方法区的
地址
,所以所有对象共享
这个静态变量
注意
: - 1、
类
是加载到方法区
中的,类中的所有信息
都会加载到方法区
中。 - 2、类是第一次使用的时候加载到方法区,加载之后不在移除,意味着类只加载
一次
。 - 3、
静态变量
在类加载的时候加载到方法区
;而构造方法
(构造代码块
)是在创建对象
的时候调用,构造代码块要比构造方法先执行,两者都是在栈内存中执行。 因此静态变量不能
定义到构造方法(构造代码块)中。 - 4、所有的
静态
只能定义在类
中不能定义到代码块
中
8.9.3 static修饰方法
- 用static修饰的方法,也叫类方法
静态方法
随着类的加载而加载到方法区
,只存储在方法区。在被调用
的时候到栈内存
中执行。- 静态方法
先于
对象存在,静态方法不能
调用本类的非静态属性。 - 静态变量在类加载的时候加载到方法区,
静态方法
在调用的时候才执行,并且是在栈内存
中执行。因此静态方法
(属于代码块的一种)中不能
定义静态变量
。 - 静态方法是可以
重载
的 - 静态方法是可以被
继承
的 - 静态方法
不
可以被重写的 - 父子类中可以存在方法名
相同
的静态方法,不是重写,而是构成的是隐藏
。
public class test {
public static void main(String[] args) {
A a = new B();
//静态构成隐藏,执行的是声明类中的方法
a.m(); //结果:A m......
}
}
class A{
public static void m(){
System.out.println("A m.......");
}
}
class B extends A{
public static void m(){
System.out.println("B m.....");
}
}
- 子类中只要存在了方法名相同的方法,要么都是非静态(重写),要么都是静态(隐藏)。
8.9.4 static修饰代码块
- 用static{}定义的代码块。在类加载的时候执行一次,类只加载一次,因此代码块也只能执行
一次
。 - 类加载时机:1、创建对象;2、创建子对象;3、访问静态属性;4、调用静态方法;5、主动加载:Class.forName(“全限定名”);
- 可为静态属性赋值,或必要的初始行为。
- 执行顺序:父类静态–>子类静态–>父类非静态–>子类非静态
public class test {
public static void main(String[] args) {
//创建子类对象,先创建父类对象
//加载父类:父类静态
//加载子类:子类静态
//创建父类对象:父类非静态
//创建子类对象:子类非静态
new SB();
/**
* 运行的结果:
* A 1
* B 1
* A 2
* A 3
* B 2
* B 3
*/
}
}
class SD{
static {
System.out.println("A 1");
}
{
System.out.println("A 2");
}
public SD(){
System.out.println("A 3");
}
}
class SB extends SD{
static {
System.out.println("B 1");
}
{
System.out.println("B 2");
}
public SB(){
System.out.println("B 3");
}
}
8.10 final
- 也是修饰符,修饰数据、方法、类
8.10.1 final修饰数据
-
修饰基本数据,就会是常量,是不可变化的,不能二次赋值的
-
修饰引用类型数据,是地址不可变,但对象中的属性是可以改变的
-
非静态常量在对象创建完成之前给值即可,可通过构造代码块或构造方法给值
- 定义的时候给值
- 构造代码块给值
- 构造方法给值
class A{
final int i;
public A(){
this(5);
}
public A(int i){
this.i = i;
}
}
- 修饰静态属性:静态常量要求在类加载完成之前给值,也可在静态代码块赋值
- 常量可以先声明,后赋值
8.10.2 final修饰方法
- final修饰方法,不能被重写/隐藏,可以被继承,可以重载
8.10.3 final修饰类
- final修饰类,不能被继承 ,那么里面的方法也就没有必要用final修饰了。例:System、String
8.11 abstract
8.11.1 抽象类
- 抽象方法所在的类必须是抽象类
- 一个类继承了抽象类之后必须要重写抽象类中的抽象方法
- 抽象类不能创建对象;依然有构造方法,子类在创建的时候,都会有构造方法,构造方法中必然会有一个super(),因此父类必然有一个构造方法。
- 抽象类不能用final来修饰
- 抽象类可以定义属性和方法,抽象类中不一定有抽象方法
8.11.2 抽象方法
- 抽象类中可以重载方法
- 不能用static修饰抽象方法
- 不能用final修饰抽象方法
- 不能用private修饰抽象方法,被private修饰的方法是不可见的
- 如果抽象方法是默认权限,要求父子类同包。
8.12 接口
-
与抽象类的异同
-
如果一个类中的所有的方法都是抽象方法,那么我们可以把这个声明为一个接口
-
接口就是抽象方法
-
接口不能创建对象,也没有构造方法
-
通过implements关键字让接口和类产生联系–实现
-
类实现接口要重写接口中的所有抽象方法
-
Java中,类是单继承多实现
-
接口可以继承接口,接口之间可以多继承
-
如果实现多个接口,接口中存在方法名相同的时候,可能导致方法重写的冲突
-
接口形成的是一张关系网,任何一个对象可以用任何一个接口进行强转编译不会报错
-
接口中可以定义属性,属性默认是使用public static final修饰
-
接口定义抽象方法,默认使用的是public修饰
- 接口多态
/**
* 1.类是类,接口是接口,他们是同一层次的概念
* 2.接口中没有构造器
* 3.接口如何声明
* 4.在JDK1.8之前,接口中只有两部分内容
* 1)常量:固定修饰符:public static final
* 2)抽象方法:固定修饰符:public abstract
* 注意:修饰符可以省略不写
*/
public interface TestInterface01 {
//常量
public static final int NUM = 10;
//抽象方法:
public abstract void a();
public abstract void b(int num);
public abstract int c(String name);
}
interface TestInterface02{
void e();
void f();
}
/**
* 5.类和接口的关系:实现关系
* 6.一旦实现一个接口,那么实现类要重写接口中的全部抽象方法
* 7.如果没有全部重写抽象方法,那么这个类可以变成一个抽象类
* 8.java只有单继承,java还有多实现,一个继承其他类,只能直接继承一个父类,但是实现类实现接口的话,可以实现多个接口
* 9.写法:先继承 在实现
*/
class Student implements TestInterface01,TestInterface02{
@Override
public void a() {
System.out.println("------1");
}
@Override
public void b(int num) {
System.out.println("------2");
}
@Override
public int c(String name) {
return 0;
}
@Override
public void e() {
System.out.println("------3");
}
@Override
public void f() {
System.out.println("------4");
}
}
class Test{
public static void main(String[] args) {
//10.接口不能创建对象
TestInterface02 t = new Student();//接口指向实现类 ---> 多态
//11.接口中常量如何访问
System.out.println(TestInterface01.NUM);
System.out.println(Student.NUM);
Student s = new Student();
System.out.println(s.NUM);
TestInterface01 t2 = new Student();
System.out.println(t2.NUM);
/**
* 接口的作用: 定义规则,跟抽象类不同的地方在于它是接口不是类,接口定义好规则之后,实现类负责实现即可
*/
}
}
-
JDK1.8,接口可以定义实体方法,提高代码的复用性
- 默认方法:public default修饰的非抽象方法,如果重写非抽象方法,default修饰符必须不能加,否则出错
- 静态方法
-
回调原理
-
接口
-
实现者
-
使用者
-
测试类
-
接口的好处
8.13 内部类
8.13.1 方法内部类
- 在方法中定义了一个类,也叫局部内部类
- 内部类只能在定义的方法中使用
- 方法内部类,不能定义静态变量和静态方法,但可以定义静态常量
- 可以使用外部类的属性和方法
- 外部类和内部类有相同的属性,如果调用外部类的属性,则利用外部类中的this调用外部类中的属性或者方法(外部类的类名.this.属性)
- 外部类的属性在被内部类使用时, 属性会被认为是一个常量
- 方法内部类只能用abstract/final修饰
- 它能保证代码重复使用,又能保证独属某个方法,出了这个方法就不能调用了
public class TestOuter {
public void method(){
//JDK1.8,外部类的成员方法中的变量被final修饰,不能被修改
int num = 10;
class A{
public void a(){
//num = 20;//因为num被final修饰了,不能修改
System.out.println(num);
}
}
}
//如果类B在整个项目中只使用一次,那么就没有必要单独创建一个B类,使用内部类就可以了
public Comparable method2(){
class B implements Comparable{
@Override
public int compareTo(Object o) {
return 100;
}
}
return new B();
}
public Comparable method3(){
//匿名内部类
return new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
}
public void test(){
Comparable com = new Comparable() {
@Override
public int compareTo(Object o) {
return 200;
}
};
System.out.println(com.compareTo("abc"));
}
}
8.13.2 成员内部类
- 定义在类中的类
- 成员内部类不能在本类的静态方法中使用
- 可以使用外部类的一切属性和方法
- 不能定义静态变量和静态方法,但可以定义静态常量
- 如果调用成员内部类的方法,则需创建对象:外部类名称.内部类名称 对象名称 = new 外部类名称().new 内部类名称();
8.13.3 静态内部类
- static修饰的内部类
- 只能使用外部类的静态属性和静态方法
- 可以作用在整个类
- 可以定义静态属性和静态方法
- 由于是static修饰的类,不依赖对象,所以不再通过外部类来创建对象。
外部类名称.内部类名称 对象名称 = new 外部类名称.内部类名称();
/**
* 1.类的组成:属性,方法,构造器,代码块(普通块,静态块,构造块,同步块),内部类
* 2.一个类TestOuter的内部类SubTest叫内部类,内部类:SubTest 外部类:TestOuter
* 3.成员内部类和局部内部类(位置:方法内、块内、构造器内)
* 4.成员内部类:
* 属性,方法,构造器
* 修饰符:private,default,protected,public,final,abstract
*/
public class TestOuter {
//成员内部类
public class D{
int age = 20;
String name;
//内部类可以访问外部类的属性和方法
public void method(){
// System.out.println(age);
// a();
int age = 30;
System.out.println(age); //30
System.out.println(this.age);//20
System.out.println(TestOuter.this.age);//10
}
}
//静态成员内部类
static class E{
public void method(){
//static修饰的类只能访问静态属性和静态方法
/*System.out.println(age);
a();*/
}
}
//属性
int age = 10;
//方法
public void a(){
System.out.println("这是a方法");
{
System.out.println("这是一个普通块");
class B{} //块内内部类
}
class A{} //局部内部类
D d = new D();
/*System.out.println(name);*///成员方法不能访问内部类的属性
//外部类想要方位内部类的东西,需要创建内部类的对象,然后进行调用
System.out.println(d.name);
d.method();
}
static{
System.out.println("这是静态块,它是最先加载的");
}
{
System.out.println("这是构造块,先于构造方法执行");
}
//构造器
public TestOuter(){
class C{} //构造器中的内部类
}
public TestOuter(int age){
this.age = age;
}
}
class Demo{
public static void main(String[] args) {
//创建外部对象
TestOuter to = new TestOuter();
to.a();
//创建内部类对象
//静态内部类创建对象
TestOuter.E e = new TestOuter.E();
//非静态的成员内部类创建对象
TestOuter.D d = new TestOuter().new D();
}
}
8.13.4 匿名内部类
- 匿名内部类实际上是对应类的子类或者对应的接口的实现类
- 任何一个接口都可以存在匿名内部类形式
- 任何一个类只要可以被继承,就可以存在匿名内部类的形式
- 匿名内部类定义在方法中,使用规则和方法内部类一致
- 匿名内部类定义在类中,使用规则和成员内部类一致
8.13.5 扩展
- 在类中可以定义类和接口,在接口中也可以定义类和接口
- 类中定义接口:接口默认必须静态的
- 接口中定义类:内部类默认是静态的
- 接口中定义接口:内部接口默认是静态的
8.14 package
- 导包语句必须在首行,*表示导入当前包下的所有的类而不包括子包下的类
- java.lang 基本包和同包类–不需要导包
- 静态导入后,同一个类中有相同的方法的时候,会优先走自己定义的方法,例如:使用java.lang下的Math类中的所有静态的内容,static不能少,导包格式:import static java.lang.Math.*;
- 在声明包的时候尽量不要使用java/javax/org
- java.util 工具包
- java.io 数据传输
- java.math 数学运算
- java.nio 高并发
- java.net 网络编程
- java.text 格式化
- java.sql 和数据库交互
8.15 垃圾分代回收机制
- 垃圾回收针对堆内存的
- Java中,每一个类型的大小是固定的,所有的内存由Java来自动分配,也是自动回收(垃圾收集器(GC))的。
- 对象在使用完成之后不一定立即回收,而是在不确定的时候回收
- 当堆内存的使用率超过了70%的时候,GC才会启动回收
- 将堆内存划分为新生代和老生代,将新生代划分为尹甸园区和幸存区
- 伊甸园区:刚创建的对象,会放到伊甸园区
- 幸存区:对象多次扫描,对象依然存在,会挪到幸存区
- 老生代:幸存下来的对象,再多次扫描,依然存在,会挪到老生代
- 对象挪到不同区时,标记在改变,但对象的地址没有改变
- 回收
- 老生代的回收:full gc(完全回收)可能会导致程序的卡顿甚至崩溃
- 新生代的回收:minor gc(初代回收)
应用案例
/**
* 披萨类
*/
public class Pizza {
//属性
private String name; //名称
private int size; //大小
private int price; //价格
//方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
//展示披萨信息
public String showPizza(){
return "披萨的名字是:" + name + ",披萨的大小是:" + size + ",披萨的价格:" + price;
}
//构造器
public Pizza() {
}
public Pizza(String name, int size, int price) {
this.name = name;
this.size = size;
this.price = price;
}
}
import java.util.Scanner;
/**
* 披萨工厂模式
*/
public class PizzaStore {
public static Pizza getPizza(int choice){
Pizza p = null;
Scanner sc = new Scanner(System.in);
switch(choice){
case 1:
{
System.out.println("请录入培根的克数:");
int weight = sc.nextInt();
System.out.println("请录入披萨的大小:");
int size = sc.nextInt();
System.out.println("请录入披萨的价格:");
int price = sc.nextInt();
//将录入的信息封装为培根披萨的对象
BaconPizza bp = new BaconPizza("培根披萨",size,price,weight);
p = bp;
}
break;
case 2:
{
System.out.println("请录入你想要加入的水果:");
String burdening = sc.next();
System.out.println("请录入披萨的大小:");
int size = sc.nextInt();
System.out.println("请录入披萨的价格:");
int price = sc.nextInt();
//将录入的信息封装为水果披萨的对象
FruitsPizza fp = new FruitsPizza("水果披萨",size,price,burdening);
p = fp;
}
break;
}
return p;
}
}
//水果披萨
public class FruitsPizza extends Pizza{
//属性
private String burdening;
public String getBurdening(){
return burdening;
}
public void setBurdening(String burdening){
this.burdening = burdening;
}
//构造器
public FruitsPizza(){}
public FruitsPizza(String burdening) {
this.burdening = burdening;
}
public FruitsPizza(String name, int size, int price, String burdening) {
super(name, size, price);
this.burdening = burdening;
}
@Override
public String showPizza() {
return super.showPizza() + "\n你要加入的水果是:" + burdening;
}
}
//培根披萨
public class BaconPizza extends Pizza{
//属性
private int weight;
public int getWeight(){
return weight;
}
public void setWeight(int weight){
this.weight = weight;
}
//构造器
public BaconPizza() {
}
public BaconPizza(int weight) {
this.weight = weight;
}
public BaconPizza(String name, int size, int price, int weight) {
super(name, size, price);
this.weight = weight;
}
@Override
public String showPizza() {
return super.showPizza() + "\n培根的克数是:" + weight;
}
}
import java.util.Scanner;
//测试类
public class Test {
public static void main(String[] args) {
//选择购买披萨
Scanner sc = new Scanner(System.in);
System.out.print("请选择你要购买的披萨(1.培根披萨 2.水果披萨):");
int choice = sc.nextInt();//选择
//通过工厂获取披萨
Pizza pizza = PizzaStore.getPizza(choice);
System.out.println(pizza.showPizza());
}
}
9、API
9.1 Object
- 顶级父类,任何一个类都直接或者间接继承Object
- clone():克隆类的时候,这个类必须实现Cloneable接口,是标识接口,产生了一个新对象
- finalize():不是直接调用gc,而是通知gc进行回收
- getClass():获取对象的实际类型
- hashCode():根据哈希散列算法来计算出来的,值会散列在int的取值范围内,哈希码取值的重合概率比较小,所以认定哈希码是唯一的。
- equals()
class Person{
private String name;
private int age;
@Override
public boolean equals(Object o) {
//判断地址是否一致
if (this == o) return true;
//判断参数是否为null,类型是否一致
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
//比较属性值
return age == person.age && Objects.equals(name, person.name);
}
}
9.2 String
- 字符串是常量,代表字符串的类,字符串是共享的。
- 底层是以字符数组存储的,就是一个char类型的数组,是不可变字符串类型
- “a"和"b"是两个字面量,两个字面量在运算的时候为了提高效率,在编译的时候会进行自动的计算优化,“a” + " b” = “ab”
- 字符串拼接
public class Test5 {
public static void main(String[] args) {
//字符串拼接
String s1 = "a" + "b" + "c";
String s2 = "ab" + "c";
String s3 = "a" + "bc";
String s4 = "abc";
String s5 = "abc" + "";
//new关键字创建对象
String s6 = new String("abc");
}
}
- 有变量参与的字符串拼接
public class Test5 {
public static void main(String[] args) {
String a = "abc";
String b = a + "def";//在编译的时候不知道a是“abc”字符串,所以不会进行编译期优化,不会直接合并为“abcdef”
System.out.println(b);
}
}
注意
:字符串中提供了一系列的操作而不改变原字符串的方法,会产生一个新的字符串
public class ObjectDemo1{
public static void main(String[] args) {
String s1 = "ab";
String s2 = new String("ab");
//两个字面量在运算的时候为了提高效率,在编译的时候会进行自动的计算优化,"a" + " b" = "ab"
String s3 = "a" + "b";
//s4 = new StringBuilder(s4).append("b").toString();
String s4 = "a";
s4 = s4 + "b";
System.out.println(s1 == s2); //false
System.out.println(s1 == s3); //true
System.out.println(s1 == s4); //false
}
}
9.2.1 equals()
9.2.2 charAt()
- 获取字符串指定下标上的字符
char c = str.charAt(index);
9.2.3 toCharArray()
- 将字符串转化为字符数组
char[] cs = str.toCharArray();
9.2.4 new String()
- 将字符数组转化为字符串
String str = new String(strArr);
应用案例
- 输入的字符串,求出字母、数字、其他符号的个数
import java.util.Scanner;
public class ObjectDemo1{
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
String str = s.nextLine();
//记录字母的个数
int letter = 0;
//记录数字的个数
int number = 0;
//记录其他符号的个数
int symbol = 0;
for (char c : str.toCharArray()) {
if(c >= 'A' && c <= 'A'||c >= 'a' && c <= 'z') letter++;
else if(c >= '0' && c <= '9') number++;
else symbol++;
}
System.out.println("letter:" + letter + "\nnumber:" + number + "\nsymbol:" + symbol);
}
}
- 输入一个字符串,判断这个字符是否是一个回文字符串
import java.util.Scanner;
public class ObjectDemo1{
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
String str = s.nextLine();
for(int i = 0,j = str.length() - 1;i < j;i++,j--){
if(str.charAt(i) != str.charAt(str.length() - 1)){
System.out.println("不是回文字符串");
return;
}
}
System.out.println("是回文字符串");
}
}
- 输入的字符串,对字符串中的数字求和
import java.util.Scanner;
public class ObjectDemo1{
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
String str = s.nextLine();
int sum = 0;
for (char c : str.toCharArray()) {
//字符转数字需减48,相当于减 '0',才能得到数字
if(c >= '0' && c <= '9') sum += (c - '0');
}
System.out.println("sum:" + sum);
}
}
9.2.5 compareTo()
- 按字典顺序比较两个字符串
- 1、字符数组的对应位置上字符进行相减
- 2、相减不为0,就将相减的差作为结果返回
- 3、相减为0,则继续遍历,计算下一位
- 4、相减都为0且一个字符串遍历完成,返回两个字符的长度之差
- 5、返回值为0,s1 == s2;返回值为正数,s1 > s2;返回值为负数,s1 < s2。
- 6、忽略大小写,用compareToIgnoreCase()比较
9.2.6 getBytes()
- 将字符串转化为字节数组
byte[] buf = str.getBytes();
import java.io.UnsupportedEncodingException;
import java.util.Scanner;
public class ObjectDemo1{
public static void main(String[] args) throws UnsupportedEncodingException {
Scanner s = new Scanner(System.in);
String str = s.nextLine();
int n = s.nextInt();
//将字符串转化
byte[] bs = str.getBytes("gbk");
if(n <= 0){
System.out.println("");
return;
}else if(n >= bs.length){
System.out.println(str);
return;
}
//按照指定的字节进行截取
String sub = new String(bs,0,n,"gbk");
//判断最后一个字符是否是半个字符
if(sub.charAt(sub.length() - 1) != str.charAt(str.length() - 1))
sub = new String(bs,0,n - 1,"gbk");
System.out.println(sub);
}
}
9.2.7 hasCode()
- 字符数组中的每一个元素是char类型的字符
- char类型用的编码是utf-16
- 同一个字符串的哈希码在任何条件下都是相同的
9.2.8 indexof()
- 获取字符串中指定元素第一次出现的下标
- 找不到返回-1
public class ObjectDemo1{
public static void main(String[] args) {
String str = "jfkdafkaj";
//找出子字符串出现的下标
printAllIndex(str,"a");
}
public static void printAllIndex(String str,String sub){
//记录下标
int index = 0;
while(index <= str.length() -1){
index = str.indexOf(sub,index);
if(index != -1){
System.out.println(index);
index++;
}else {
break;
}
}
}
}
9.2.9 intern()
- 强制返回常量池的值
public class ObjectDemo1{
public static void main(String[] args) {
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s1 == s2); //false
System.out.println(s1 == s2.intern()); //true
}
}
9.2.10 substring()
- 表示从指定下标开始截取子字符串
String str = "jfdkafkj";
str.substring(3); //kafkj
9.2.11 valueOf()
- 表示将其他类型的数据转化为字符串
- 对一个对象进行valueOf操作的时候,实际上调用了这个对象的toString()
- 字符数组特殊,在valueOf和toString上不同,调用valueOf()打印不是地址,toString()打印的是地址
public class ObjectDemo1{
public static void main(String[] args) {
char[] cs = {'A','B','C'};
String str1 = String.valueOf(cs);
String str2 = cs.toString();
System.out.println(str1);//ABC
System.out.println(str2);//[C@29453f44
}
}
9.2.12 可变字符串
9.2.12.1 StringBuilder
9.2.12.2 StringBuilder常用方法
public class Test5 {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("jkdjakfide");
//增
sb.append("这是梦想者");
//删
sb.delete(3,6);//删除位置在[3,6)上的字符
sb.deleteCharAt(10);//删除位置在16上的字符
//改-->插入
StringBuilder sb1 = new StringBuilder("$234457878");
sb1.insert(3,",");//在下标为3的位置上插入
//改-->替换
StringBuilder sb2 = new StringBuilder("$2你好吗8785649");
sb2.replace(3,5,"我好累");//在下标[3,5)位置上插入字符串
sb.setCharAt(3,'!');
//查
StringBuilder sb3 = new StringBuilder("asdfa");
for (int i = 0; i < sb3.length(); i++) {
System.out.println(sb3.charAt(i) + "\t");
}
//截取
String str = sb3.substring(2,4);//截取[2,4)返回的是一个新的String,对StringBuilder没有影响
System.out.println(str);//df
System.out.println(sb3);//asdfa
}
}
9.2.12.3 StringBuffer
- 安全,效率低,线程安全
9.2.12.4 StringBuffer的常用方法与StringBuilder是一样的
10、正则表达式
方式一
- 编译产生正则对象
Pattern p = Pattern.compile(regex);
- 匹配器,将正则对象和实际对象进行匹配
Matcher m = p.matcher(str);
- 判断
boolean b = m.matches();
方式二
boolean b = str.matches(regex);
注意
:
- 匹配 . :\. 先有Java进行转义,转义为. 然后再由正则进行转义,转义为.
- 匹配 \ :\\ 先有Java进行转义,转义为\ 然后再由正则进行转义,转义为\
应用案例
- 输入一个字符串,判断这个字符串是否表示一个小数
import java.util.Scanner;
public class ObjectDemo1{
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
String str = s.nextLine();
System.out.println(str.matches("0\\.\\d+")||str.matches("[1-9]\\d*\\.\\d+"));
}
}
- 将字符用括号作为一个整体进行操作,叫捕获组;\n表示引用编号为n的捕获组;正则表达式会对包含的捕获组进行编号,编号是从1开始计算的;捕获组的编号的计算是从捕获组(的出现位置开始计数的
- 如:(A(BC)(D)E)(F)
- \1 (第1捕获组): A(BC)(D)E
- \2 (第2捕获组): BC
- \3 (第3捕获组): D
- \4 (第4捕获组): F
应用案例
- 输入一个字符串,输出这个字符串中每个字符出现的次数
import java.util.Scanner;
public class ObjectDemo1{
public static void main(String[] args) {
/**
* 输入一个字符串,输出这个字符串中每个字符出现的次数
*/
Scanner s = new Scanner(System.in);
String str = s.nextLine();
while(str.length() > 0){
//字符串长度
int len = str.length();
//获取首位字符
char c = str.charAt(0);
String regex = "";
if(c == '+'||c == '*'||c == '?'||c == '\\'||c == '.') regex = "\\" + c;
else regex = c + "";
str = str.replaceAll(regex,"");
System.out.println(c + " : " + (len - str.length()));
}
}
}
- 交换名字
public class ObjectDemo1{
public static void main(String[] args) {
String str = "Amy Sam Tom Bob Cat";
System.out.println(str.replaceAll("(.*)(Sam)(.*)(Cat)","$1$4$3$2")); //Amy Cat Tom Bob Sam
}
}
- 叠字变单字
public class ObjectDemo1{
public static void main(String[] args) {
String str = "我我我我我我我爱爱爱爱爱雪雪雪雪雪雪雪雪";
System.out.println(str.replaceAll("(.)\\1+","$1")); //我爱雪
}
}
- 计算一个字符串的平均碎片长度
public class ObjectDemo1{
public static void main(String[] args) {
String str = "aaabbbccac";
//字符串的长度
double len = str.length();
//将所有的叠字替换为单字
str = str.replaceAll("(.)\\1+","$1");
//替换之后的字符串长度就是碎片的个数
System.out.println(len / str.length()); //2.0
}
}
- split
- 1、以数字作为边界符,将这个字符串切分
- 2、对个边界符相邻,则中间切割出“”
- 3、如果边界符在最后,则直接切掉
public class ObjectDemo1{
public static void main(String[] args) {
String str = "21313jkfdakf67j473fdja24324";
//以数字切分
String[] arr = str.split("\\d");
System.out.println(Arrays.toString(arr)); //[, , , , , jkfdakf, , j, , , fdja]
}
}
11、Math类
11.1 BigDecimal
- double类型的数据在做运算时,得到的结果会不精确,解决方法是java引入了BigDecimal
- 用于精确存储和运算小数的类,要求参数以字符串形式传递
import java.math.BigDecimal;
public class ObjectDemo1{
public static void main(String[] args) {
//想要精确存储和运算,需要将参数以字符串形式传入
BigDecimal b1 = new BigDecimal("3.24");
BigDecimal b2 = new BigDecimal("2.98");
System.out.println(b1.subtract(b2));
}
}
11.2 BigInteger
- 能存储和计算任意大小的整数的类
11.3 Random类
import java.util.Random;
public class Test5 {
public static void main(String[] args) {
//返回带正号的double值,该值大于等于0.0 且小于1.0
System.out.println("随机数:" + Math.random());//Math.random()底层实际上调用Random类的nextDouble()
//Random类
//利用带参数的构造器创建对象
Random r1 = new Random(System.currentTimeMillis());//参数应传入变化的seed
int i = r1.nextInt();
System.out.println(i);
//利用空参构造器创建对象
Random r2 = new Random();//表面上调用的是无参构造器,实际上还是调用了带参构造器
System.out.println(r2.nextInt(10));//在0(包括)和指定值(不包括)之间均匀分布的int值
}
}
12、包装类
-
基本类型没有方法和属性,为了能快捷的操作这些数据,这对每一中基本类型提供了对应的包装类
-
Void:最终类,不能创建对象
-
调用valueOf()方法进行装箱
-
对于四种整数型,当数值范围在-128~127之间的时候,调用的valueOf(),超过这个范围调用的是new Integer(value);
public class Test {
public static void main(String[] args) {
//compareTo:只返回三个值:要么是0,-1,1
Integer i1 = new Integer(6);
Integer i2 = new Integer(12);
System.out.println(i1.compareTo(i2));//return (x < y)?-1:((x==y)?0:1);
Integer i3 = new Integer(12);
Integer i4 = new Integer(12);
System.out.println(i3 == i4);//false,因为==比较的是两个对象的地址
boolean flag = i3.equals(i4);
System.out.println(flag);//true
//Integer对象通过自动装箱来完成
Integer i5 = 130;
Integer i6 = 130;
//如果自动装箱在-128~127之间,那么比较的就是具体的数据,否则,比较的是对象的底子
System.out.println(i5.equals(i6));//true
System.out.println(i5 == i6);//false
//intValue():作用是将Integer--->int;
Integer i7 = 130;
int i = i7.intValue();
System.out.println(i);//130
//parseInt(String s):String --> int;
int i8 = Integer.parseInt("12");
System.out.println(i8);
//toString:Integer -->String;
Integer i10 = 130;
System.out.println(i10.toString());
}
}
- 调用***Value()方法进行拆箱
- byte、short、int的hashCode是自身,long的hashCode不是自身,基本类型和字面量的的hashCode都是固定不变的,不随条件变化的
- NaN:非数字,唯一,和任何值都不相等,包括自己本身
- 包装类型和基本类型进行运算的时候,会自动拆箱
int i = 200;
Integer in = 200;
Systeom.out.println(i == in); //true
- null的哈希码规定为0;
- 所有的字面量都是存储在方法区的运行常量池中,常量池存储的字面量以及自定义常量
13、日期类
13.1 Date
public class Test5 {
public static void main(String[] args) {
Date d = new Date(743817481047L);
System.out.println(d);
//java.sql.Date和java.util.Date相互转换
//util-->sql
/**
* 父类:Animal 子类:Dog
* Animal an = new Dog();//只有父类对象的引用指向了Dog,才能向下转型,不实现此步会报错
* Dog d = (Dog)an;
*/
/*java.util.Date date11 = new java.util.Date();//会报错:java.util.Date cannot be cast to java.sql.Date,报错的原因是父类对象没有实现对子类的引导致的
Date date1 = (Date) date11;*/
java.util.Date date12 = new Date(743817481047L);//创建util.Date的对象
//方式1:向下转型
Date date1 = (Date) date12;
//方式2:利用构造器
Date date2 = new Date(date12.getTime());
//sql-->util
java.util.Date date3 = d;
//String--->sql.Date
Date date4 = Date.valueOf("2019-03-08");
}
}
- 代表日期的类。
第一种方式:String---->java.util.Date类型转换:
public class Test5 {
public static void main(String[] args) {
//此方法有局限性: 字符串的格式只能是"年-月-日"拼接的格式,换成其他类型,就会出现异常:java.lang.IllegalArgumentException
//1)String--->java.sql.Date
java.sql.Date date1 = java.sql.Date.valueOf("2015-9-24");
// java.sql.Date date1 = java.sql.Date.valueOf("2015/9/24");//报错:java.lang.IllegalArgumentException
//2)java.sql.Date--->java.util.Date
java.util.Date date2 = date1;
System.out.println(date2.toString());
}
}
第二种方式:
- 字符串->日期:parse
- 日期->字符串:format
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test5 {
public static void main(String[] args) {
//日期转换
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//String-->Date
try {
Date d = df.parse("2019-4-6 12:23:54");
System.out.println(d);
} catch (ParseException e) {
e.printStackTrace();
}
//Date-->String,按照自定义格式转换的
String format = df.format(new Date());
System.out.println(format);
//Date-->String
Date date = new Date();
System.out.println(date.toString());
System.out.println(date.toGMTString());
System.out.println(date.toLocaleString());
}
}
13.2 Calendar
- 代表日历的类
import java.util.Calendar;
import java.util.GregorianCalendar;
public class Test5 {
public static void main(String[] args) {
//Calendar是一个抽象类,不可以直接创建对象
Calendar cal = new GregorianCalendar();
Calendar cal2 = Calendar.getInstance();
System.out.println(cal);
//常用方法
System.out.println(cal.get(Calendar.YEAR));
System.out.println(cal.get(Calendar.MONTH));
System.out.println(cal.get(Calendar.DATE));
System.out.println(cal.get(Calendar.DAY_OF_WEEK));
//日期的最大天数
System.out.println(cal.getActualMaximum(Calendar.DATE));
//set方法
cal.set(Calendar.YEAR,1990);
cal.set(Calendar.MONTH,3);
cal.set(Calendar.DATE,16);
//String-->Calendar
//String-->java.sql.Date
java.sql.Date date = java.sql.Date.valueOf("2020-4-5");
//java.sql.Date-->Calendar
cal.setTime(date);
System.out.println(cal);
}
}
应用案例
- 请输入你想要查看的日期:(提示:请按照例如2019-3-7的格式)
import java.util.Calendar;
import java.util.Scanner;
public class Test5 {
public static void main(String[] args) {
//String-->Calendar
//录入日期的String完成了
Scanner sc = new Scanner(System.in);
System.out.print("请输入你想要查看的日期:(提示:请按照例如2019-3-7的格式书写)");
String strDate = sc.next();
java.sql.Date date = java.sql.Date.valueOf(strDate);
//Date --> Calendar
Calendar cal = Calendar.getInstance();
cal.setTime(date);
//星期提示
System.out.println("日\t一\t二\t三\t四\t五\t六");
//获取本月的最大天数
int maxDay = cal.getActualMaximum(Calendar.DATE);
//获取本月当中的日
int nowDay = cal.get(Calendar.DATE);
//将日期调为本月的1号:
cal.set(Calendar.DATE,1);
//获取这个一号是本周的第几天
int num = cal.get(Calendar.DAY_OF_WEEK);
int day = num - 1;
//引入一个计数器
int count = 0;//计数器最开始值为0
//在日期前将空格打印出来
for (int i = 1;i <= num -1;i++){
System.out.print("\t");
}
//空出来的日子也要放入计数器
count+= day;
//遍历
for(int i = 1;i <= maxDay;i++){
if(i == nowDay)
System.out.print(i + "*\t");
else
System.out.print(i + "\t");
count++;//每在控制台输出一个数字,计数器加1操作
if(count%7==0){//当计数器的个数是7的倍数,就换行操作
System.out.println();
}
}
}
}
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class Test5 {
public static void main(String[] args) {
//1.完成实例化
//方法1:now():获取当前的日期,时间,日期+时间
LocalDate localDate = LocalDate.now();
System.out.println(localDate);
LocalTime localTime = LocalTime.now();
System.out.println(localTime);
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
//方法2:of():设置指定的日期
LocalDate of1 = LocalDate.of(2010,5,6);
System.out.println(of1);
LocalTime of2 = LocalTime.of(13,30,40);
System.out.println(of2);
LocalDateTime of3 = LocalDateTime.of(2020,12,23,10,23,30);
System.out.println(of3);
//LocalDateTime的get***方法
System.out.println(localDateTime.getYear());//2021
System.out.println(localDateTime.getMonth());//APRIL
System.out.println(localDateTime.getMonthValue());//4
System.out.println(localDateTime.getDayOfMonth());//18
System.out.println(localDateTime.getDayOfWeek());//SUNDAY
System.out.println(localDateTime.getHour());//15
System.out.println(localDateTime.getMinute());//47
System.out.println(localDateTime.getSecond());//5
//不是set方法,而是with***方法
LocalDateTime localDateTime2 = localDateTime.withMonth(8);
System.out.println(localDateTime);//原来的日期不会改变;2021-04-18T15:56:15.747
System.out.println(localDateTime2);//2021-08-18T15:56:15.747
//提供了加减的操作
LocalDateTime localDateTime1 = localDateTime.plusMonths(4);
System.out.println(localDateTime);//原来的如期不会改变;2021-04-18T15:57:22.225
System.out.println(localDateTime1);//2021-08-18T15:57:22.225
//减:
LocalDateTime localDateTime3 = localDateTime.minusMonths(5);
System.out.println(localDateTime);//2021-04-18T15:57:22.225
System.out.println(localDateTime3);//2020-11-18T15:57:22.225
}
}
13.3 LocalDate
- 日期的类
- of()可指定日期
13.4 LocalTime
- 时间的类
- of()可制定时间
13.5 LocalDateTime
- 日期时间的类
- of()可制定时间
- get***方法
- with***方法
13.6 DateTimeFormatter
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.TemporalAccessor;
public class Test5 {
public static void main(String[] args) {
//格式化类;DateTimeFormatter
//方式一:预定义的标准格式,如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
DateTimeFormatter df1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
//df1可以帮我们完成LocalDateTime和String之间的相互转换
LocalDateTime now = LocalDateTime.now();
String str = df1.format(now);
System.out.println(str);//2021-04-18T16:17:08.069
//String--->LocalDateTime
TemporalAccessor parse = df1.parse("2020-11-18T15:57:22.225");
System.out.println(parse);//{},ISO resolved to 2020-11-18T15:57:22.225
//方式二:本地化相关的格式,如:ofLocalizedDateTime()
//参数:FormatStyle.LONG;FormatStyle.MEDIUM;FormatStyle.SHORT
//FormatStyle.LONG:2021年4月18日 下午04时21分20秒
//FormatStyle.MEDIUM:2021-4-18 16:22:38
//FormatStyle.SHORT:21-4-18 下午4:23
DateTimeFormatter df2 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
//localDateTime-->String
LocalDateTime now1 = LocalDateTime.now();
String str2 = df2.format(now1);
System.out.println(str2);//2021年4月18日 下午04时21分20秒
//String--->LocalDateTime
TemporalAccessor parse1 = df2.parse("2021年4月18日 下午04时21分20秒");
System.out.println(parse1);//{},ISO resolved to 2021-04-18T16:21:20
//方式三:自定义格式,如:ofPattern("yyyy-MM-dd hh:mm:ss"),最为常用
DateTimeFormatter df3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
//LocalDateTime--->String
String str3 = df3.format(now);
System.out.println(str3);//
//String--->LocalDateTime
TemporalAccessor parse2 = df3.parse("2021-04-18 04:27:31");
System.out.println(parse2);
}
}
14、异常
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
try {
//实现一个功能:键盘录入第一个数
Scanner sc = new Scanner(System.in);
System.out.println("请输入第一个数:");
int num1 = sc.nextInt();
System.out.println("请输入第二个数:");
int num2 = sc.nextInt();
System.out.println("商:" + num1/num2);
} catch (Exception e) {
//第一种处理:什么都不写,什么都不做
//第二种处理:
//System.out.println("对不起,你的代码有问题!");
//第三种处理:打印异常信息
//1)调用toString方法,显示异常的类名(全限定名)
//System.out.println(e);
//System.out.println(e.toString());
//2)显示异常描述信息对应的字符串,如果没有就显示null;
//System.out.println(e.getMessage());
//3)显示异常的堆栈信息:将异常信息捕获以后,在控制台将异常的效果给我们展示出来,方便我们查看异常,是最为常用的方式
e.printStackTrace();
//第四种处理:抛出异常,后面的代码就不执行了
//throw e;
}
System.out.println("okokokok");
}
}
- throw抛出异常的情况,后面的代码不执行
- catch中没有正常的进行异常捕获,后面的代码不执行
- 在try中遇到return,后面的代码不执行
14.1 Throwable
- java中的所有错误或异常的超类
- Error:错误,一旦出现不能处理。
- Exception:异常,出现之后可以处理,处理方法有两种: 一种是捕获,一种是继续抛出
- 编译时异常:必须处理;Exception
- 运行时异常:可以处理,也可以不处理:RuntimeException
public class ObjectDemo1{
public static void main(String[] args) {
String msg = null;
try {
msg = readTxt(null);
System.out.println(msg);
} catch (PathNotExistException e) {
System.out.println(e.getMessage());
} catch (FileFormatException e){
System.out.println(e.getMessage());
}catch (NullPointerException e){
System.out.println(e.getMessage());
}
}
public static String readTxt(String path) throws PathNotExistException,FileFormatException,NullPointerException{
if(path == null) throw new NullPointerException("路径不能为空。。。");
if(path.startsWith("H:\\")) {
throw new PathNotExistException("路径不对。。。");
}
if(!path.endsWith(".txt")) throw new FileFormatException("传入的文件格式不对。。。" + path.substring(path.lastIndexOf(".")));
return "读取成功~~~~";
}
}
class PathNotExistException extends Exception{
private String message;
public PathNotExistException(){}
public PathNotExistException(String message){
this.message = message;
}
public String getMessage(){
return message;
}
}
class FileFormatException extends Exception{
public FileFormatException(){}
public FileFormatException(String message){
super(message);
}
}
14.2 异常的捕获方法
- 如果多个异常的处理方法不一样,可以使用多个catch来分别捕获分别处理
- 如果所有异常的处理方式都一样,可以捕获一个父类异常进行统一的处理
- 如果多个异常分成了不同的组来进行处理,那么同一组异常之间可以用|隔开,从JDK1.7开始的。
- 重写的时候,子类抛出的编译时异常不能超过父类异常的范围
- throw和throws的区别
- throw:定义在方法内部;异常对象(检查异常,运行时异常);异常的源头,制造异常
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
devide();
}
public static void devide(){
//实现一个功能:键盘录入第一个数
Scanner sc = new Scanner(System.in);
System.out.println("请输入第一个数:");
int num1 = sc.nextInt();
System.out.println("请输入第二个数:");
int num2 = sc.nextInt();
if(num2 == 0){
//制造运行时异常
//throw new RuntimeException();
//制造检查异常
try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}else{
System.out.println("商:" + num1/num2);
}
}
}
+ throws:方法的签名处,方法的声明处;异常类型(可以多个类型,用逗号拼接);告诉方法调用者,这个方法可能会出现我声明的这些异常,然后调用者对这个异常进行处理:要么自己处理,要么再继续向外抛出异常
总结:重载和重写
- 重载和重写都是行为多态
- 重载
- 同一个类中,方法名相同,参数列表不同。参数列表:参数个数或者参数类型不同。
- 只和方法名有关,和修饰符、返回值类型、异常都没有关系。
- 重载本身是一种编译时多态。
- 重写
-
在父子类存在方法名相同的非静态方法
-
子类方法的权限修饰符的方法要比父类权限修饰符的范围大或保持一致
-
父类的返回值类型是基本类型或者是void,那么子类方法的返回值类型要保持一致。
-
父类方法返回值类型是引用类型,那么子类返回值类型要和父类一致或者父类方法返回值类型的子类
-
子类在重写方法的时候所抛出的编译时异常不能超过父类异常的范围
-
重写本身一种运行时多态
-
14.3 finally
- 无论出现异常与否,都会执行一次
- 关闭数据库资源,关闭IO流资源,关闭Socket资源,需放到finally中
- 必须结合try一起使用
- System.exit(0):终止当前的虚拟机执行,这种情况下finally就不能执行了
14.4 自定义异常
- 自定义的异常可以继承:运行时异常,使用的时候无需额外处理;也可以是检查异常,使用的时候需要try-catch捕获或者throws向上抛
public class MyException extends RuntimeException{//自定义异常
private static final long serialVersionUID = -7733217760844282515L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
package com.test01;
public class Student {
private String name;
private int age;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
if(sex.equals("男")||sex.equals("女")){
this.sex = sex;
}else{
throw new MyException("对不起,性别不对");
}
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
public class Test01 {
public static void main(String[] args) {
//创建对象
Student s = new Student();
s.setName("菲菲");
s.setAge(19);
s.setSex("女fda");
System.out.println(s);
}
}
15、集合
- 存储多个元素的容器
- 大小不固定
- 在集合中,泛型用于定义元素的类型,由于泛型的限制,集合中只能存储对象
15.1 Collection
- toArray():将集合转化为数组,返回的类型为Object,会涉及到类型强转。
- toArray(T[] a):传入的数组决定了结果类型;如:Collection,结果也要String类型,只需出入字符串数组即可,String[] str = c.toArray(new String[0])。数组长度给0,不需要开辟空间,这样能节省内存。
- Iterator:迭代器,在迭代过程中会对每一个元素进行标记,如果标记状态为false,说明该元素要被移除。
- Iterable:实现这个接口的类所产生的对象可以被增强for循环进行遍历,如果一个对象能够使用增强for循环(JDK1.5),那么这个对象对应的类要实现Iterable接口。
15.2 List
- 有序列表,保证了元素的存入顺序
- List中的元素是存在下标的
- ArrayList:底层用数组存储数据。内存空间连续,默认初始容量是10。每次扩容是在当前的基础上增加一半。增删元素的操作相对复杂,查询元素相对要简单一些,线程不安全的集合。
- LinkedList:底层是基于节点来存储的,内存空间不连续。增删元素的操作相对简单,查询元素的操作相对复杂,也是线程不安全的集合。
- 如果增删次数和查询次数在相差不大的情况下,用链表结构,原因是链表对内存的要求要小
- 整体上,链表要高于顺序表。如果增删位置相对靠前,那么链表效率要高,如果增删位置相对靠后,则顺序表的效率要高。
15.3 Vector
- 实现可增长的数组,默认初始容量是10,每次扩容增加一倍,是一个线程安全的集合
- Enumeration是迭代器
- 在迭代过程中,不建议增删原集合
15.4 Stack
- 后进先出(LIFO)
- 最先入栈的元素,称为栈底元素;最后放入元素,称为栈顶元素;将元素放入栈中,称为入栈/压栈;将元素从栈中取出,称为出栈/弹栈
- 从栈顶到栈底依次查找–search(),基数为1
- 获取而不移除栈顶元素–peek()
- 获取并且移除栈顶元素–pop()
/**
* 用数组实现一个栈结构
*/
class ArrayStack{
private String[] data = new String[10]; //存储数据
private int size; //记录个数
public void push(String str){
//判断扩容
if(size >= data.length){
data = Arrays.copyOf(data,data.length * 2);
}
data[size++] = str;
}
public String peek(){
//判断栈是否为空
if(size == 0) throw new EmptyStackException();
return data[size - 1];
}
public String pop(){
//获取栈顶元素
String str = this.peek();
size--;
return str;
}
public boolean empty(){
return size == 0;
}
public int search(String str){
for(int i = size - 1;i >= 0;i--){
if(str == data[i] || str != null && str.equals(data[i])) return size - i;
}
return -1;
}
}
/**
* 用节点实现一个栈结构
*/
class LinkedStack{
private int size;
private Node first;
public void push(String str){
Node node = new Node(str,null);
if(size != 0){
node.next = this.first;
}
this.first = node;
size++;
}
public String peek(){
if(size == 0) throw new EmptyStackException();
return this.first.item;
}
public String pop(){
String str = this.peek();
this.first = this.first.next;
size--;
return str;
}
public boolean empty(){
return size == 0;
}
public int search(String str){
Node node = this.first;
for(int i = 0;i < size;i++){
String item = node.item;
if(str == item || str != null && str.equals(item)) return i + 1;
node = node.next;
}
return -1;
}
//定义了代表节点的类
private class Node{
String item;
Node next;
public Node(String item,Node next){
super();
this.item = item;
this.next = next;
}
}
}
15.5 set
- 不保证元素的顺序
- HashSet的底层基于HashMap来实现的
- HashMap的底层是基于数组和链表来存储的
- 默认初始容量16,默认加载因子是0.75,每次默认增加一倍
16、泛型
- JDK1.5 的特性,参数化类型。把类型作为参数传递
- 将泛型替换为具体类型的过程,叫泛型的擦除,发生在编译期
- 习惯上只使用大写字母来表示泛型
- T:type、E:element、K:key、V:value、R:return/result
- ? extend 类/接口 : 表示传入这个类/接口或者是子类/子接口对象,泛型的上限
- ? super 类/接口 :表示传入类/接口及其父类/父接口对象,泛型的下限
- 允许操作集合,但不允许改变集合,就用此类泛型<?>,?:通配符
- 类泛型
class Demo<T>{
T t;
public T get(){
return t;
}
}
- 方法泛型
public <T> void m(T t){
}
17、Stream
- JDK1.8的特性,操作集合的流式结构,但不是流。利用stream对象对集合进行批量操作
18、Map
- 键:key,值:value;键是唯一的,每一个键都对应了一个值
- 每一个键值对看做一个Entry对象,可以认为一个Map对象是由多个Entry对象组成的,
- 如果键不存在,则返回null
- 如果键重复,则对应的值进行覆盖
- keySet():表示将映射中所有的键放入一个set集合中
- entrySet():表示将映射中所有的键值对放入一个set集合中
- 映射不是集合,但是集合框架的一员
- Java Collections Framework:包含了数组、集合、映射以及一部分操作它们的工具类 — [] Collection Map Arrays Collections Comparable Comparator Iterator
- HashMap:默认初始容量是16,默认加载因子是0.75f,每次默认增加一倍。键和值允许为null
- 如果指定初始容量x,那么x如果在(2^n-1,2^n],初始容量一定是2^n,异步线程不安全
- Hashtable:键和值不允许是null;初始容量11,默认加载因子0.75f。每次扩容的时候在当前的基础上增加一倍,再+1,相当于<< 1 + 1。是一个同步式线程安全的映射
- concurrentHashMap:异步式线程安全的映射
应用案例
- 统计字符串字符出现的次数
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class ObjectDemo1{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.nextLine();
Map<Character,Integer> map = new HashMap<>();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if(map.containsKey(c)) map.put(c,map.get(c) + 1);
else map.put(c,1);
}
for (Map.Entry<Character, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
}
19、File
import java.io.File;
import java.util.Date;
public class FileDemo {
public static void main(String[] args) throws Exception{
separator();
fileOpe();
}
//分隔符
public static void separator(){
System.out.println("路径分隔符:" + File.pathSeparator);//路径分隔符:;
System.out.println("名称分隔符:" + File.separator);//名称分隔符:\
}
//文件操作
public static void fileOpe() throws Exception{
//创建文件
File file = new File("tt.txt");
System.out.println(file.toString());//tt.txt
if(!file.exists()){
boolean b = file.createNewFile();
System.out.println(b);//false,表示文件已经创建成功了
}
//删除文件
// System.out.println("删除结果:" + file.delete());
file.deleteOnExit();//jvm退出是删除
//获取文件信息
System.out.println("获取文件的绝对路径:" + file.getAbsolutePath());
System.out.println("获取路径:" + file.getPath());
System.out.println("获取父目录: " + file.getParent());
System.out.println("获取文件长度:" + file.length());
System.out.println("文件创建时间:" + new Date(file.lastModified()).toLocaleString());
//判断
System.out.println("是否可写:" + file.canWrite());
System.out.println("是否是文件:" + file.isFile());
System.out.println("是否隐藏:" + file.isHidden());
}
}
- 在创建File对象的时候,并不去检查文件是否存在,只是将路径字符串标记为一个File对象
- createNewFile()只有这个文件不存在的时候才能创建文件,不能创建目录,要求文件存放的路径必须真实存在。
import java.io.File;
import java.io.IOException;
public class Test01 {
public static void main(String[] args) throws IOException {
//将文件封装为一个File类的对象
File f = new File("d:\\test.txt");
File f2 = new File("d:/test.txt");
//File.separator:获取当前操作系统的路径拼接符号
File f3 = new File("d:" + File.separator + "test.txt");//建议使用这种
System.out.println("文件是否可读:" + f.canRead());
System.out.println("文件是否可写:" + f.canWrite());
System.out.println("文件的名字:" + f.getName());
System.out.println("上级目录:" + f.getParent());
System.out.println("是否是目录:" + f.isDirectory());
System.out.println("是否是文件:" + f.isFile());
System.out.println("是否隐藏:" + f.isHidden());
System.out.println("文件大小:" + f.length());
System.out.println("文件是否存在;" + f.exists());
// if(f.exists()){//如果文件存在,将文件删除操作
// f.delete();
// }else{//如果不存在,就创建这个文件
// f.createNewFile();
// }
System.out.println(f == f2);//比较两个对象的地址
System.out.println(f.equals(f2));//比较两个对象对应的文件路径,如果路径相同,返回true;
System.out.println("文件的绝对路径:" + f.getAbsolutePath());
System.out.println("文件的相对路径:" + f.getPath());
System.out.println(f.toString());//返回的也是相对路径
}
}
- mkdir()只能创建目录,
- mkdirs()创建多层目录
- delete()删除目录或文件,把这个文件彻底移除,次操作不可逆。只能删除空目录。
- listFiles()获取所有的子目录和子文件
import java.io.File;
public class Test01 {
public static void main(String[] args) {
File f = new File("C:\\Refinitiv\\phonebook");
//创建单层目录
// f.mkdir();
//创建多层目录
// f.mkdirs();
//删除目录,只能删除一层且空目录
// f.delete();
//查看
String[] list = f.list(); //文件夹下的目录和文件对应的名字的数组
for (String s : list) {
System.out.println(s);
}
System.out.println("===================");
File[] files = f.listFiles();//返回的是文件对象数据
for (File file : files) {
System.out.println(file.getName() + "," + file.getAbsolutePath());
}
}
}
- renameTo()可做重命名,也可做剪切,要求存放的路径中没有同名文件
import java.io.File;
import java.util.Date;
public class FileDemo {
public static void main(String[] args) throws Exception{
separator();
fileOpe();
}
//分隔符
public static void separator(){
System.out.println("路径分隔符:" + File.pathSeparator);//路径分隔符:;
System.out.println("名称分隔符:" + File.separator);//名称分隔符:\
}
//文件操作
public static void fileOpe() throws Exception{
//创建文件
File file = new File("tt.txt");
System.out.println(file.toString());//tt.txt
if(!file.exists()){
boolean b = file.createNewFile();
System.out.println(b);//false,表示文件已经创建成功了
}
//删除文件
// System.out.println("删除结果:" + file.delete());
file.deleteOnExit();//jvm退出是删除
//获取文件信息
System.out.println("获取文件的绝对路径:" + file.getAbsolutePath());
System.out.println("获取路径:" + file.getPath());
System.out.println("获取父目录: " + file.getParent());
System.out.println("获取文件长度:" + file.length());
System.out.println("文件创建时间:" + new Date(file.lastModified()).toLocaleString());
//判断
System.out.println("是否可写:" + file.canWrite());
System.out.println("是否是文件:" + file.isFile());
System.out.println("是否隐藏:" + file.isHidden());
}
public static void directoryOpe() throws Exception{
//创建文件
File dir = new File("c:\\aa\\bb\\cc");
System.out.println(dir.toString());
if(!dir.exists()){
// dir.mkdir();//创建单级目录
System.out.println("创建结果:" + dir.mkdirs());//创建多级目录
}
//删除文件
dir.delete();
dir.deleteOnExit();
Thread.sleep(5000);
//获取文件夹信息
System.out.println("获取绝对路径:" + dir.getAbsolutePath());
System.out.println("获取路径:" + dir.getPath());
System.out.println("获取文件夹名称:" + dir.getName());
System.out.println("获取父目录:" + dir.getParent());
System.out.println("获取创建时间:" + new Date(dir.lastModified()).toString());
//判断
System.out.println("是否是文件夹:" + dir.isDirectory());
System.out.println("是否隐藏:" + dir.isHidden());
//遍历文件
File dir2 = new File("d:\\图片");
String[] files = dir2.list();
System.out.println("----------------");
for (String file : files) {
System.out.println(file);
}
}
}
FileFilter接口
File dir = new File("d:\\图片");
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
if(pathname.getName().endsWith(".jpg")){
return true;
}
return false;
}
});
for (File file : files) {
System.out.println(file.getName());
}
应用案例
- 递归遍历文件和删除文件
import java.io.File;
public class ListDemo {
public static void main(String[] args) {
// listDir(new File("d:\\myfiles"));
deleteDir(new File("d:\\myfiles"));
}
//递归文件夹
public static void listDir(File dir){
File[] files = dir.listFiles();
if(files != null && files.length > 0){
for (File file : files) {
if(file.isDirectory()){
listDir(file);//递归
}else{
System.out.println(file.getAbsolutePath());
}
}
}
}
//递归删除文件夹
public static void deleteDir(File dir){
File[] files = dir.listFiles();
if(files != null && files.length > 0){
for (File file : files) {
if(file.isDirectory()){
deleteDir(file);//递归
}else{
System.out.println(file.getAbsolutePath() + " 删除:" + file.delete());//删除文件
}
}
}
System.out.println(dir.getAbsolutePath() + " 删除:" + dir.delete());//删除文件
}
}
20、IO
- 输入流:数据从外部流向程序
- 输出流:数据从程序流向外部
- 读取文件
- 输入流:数据从文件流向程序
- 输出流:数据从程序流向文件
- InputStream、OutputStream、Reader、Writer这四个基本流都是抽象类,不能直接创建对象
- 数据来源/目的地:硬盘、内存、网络、输入设备
- 写出数据时,首先数据会写到缓冲区,所以需要调用flush()方法,亦或是调用close(),数据才能写成功。
- 读取数据,没有缓冲区,所以读一个就会写一个
- 字符流有缓冲区,需手动flush(),数据才能加载成功,而字节流没有缓冲区。
- 系统流都是字节流
文本文件/非文本文件
- 文本文件:.txt、.java、.c、.cpp等===>建议使用字符流操作
- 非文本文件:.jpg、.mp3、.mp4、.doc、.ppt等===>建议使用字节流操作
字符流:FileReader
- 一次一个字符的读取
import java.io.File;
import java.io.FileReader;
public class Test01 {
public static void main(String[] args) throws Exception {
//文件===>程序
//有一个文件:--->创建一个File类的对象
File f = new File("C:\\Refinitiv\\phonebook\\Test.txt");
//利用FileReader这个流,这个管怼到源文件中--->创建一个FileReader流的对象
FileReader fr = new FileReader(f);
//进行操作“吸”的动作====>读取动作
//一次一个字符的读取
int n;
while((n = fr.read()) != -1) {
//每次读取的n,是Unicode
//System.out.println(n);
//可把Unicode转成字符
System.out.println((char)n);
}
//“管”不用了,就要关闭---> 关闭流
fr.close();
}
}
- 缓冲数组读取文件
import java.io.File;
import java.io.FileReader;
public class Test01 {
public static void main(String[] args) throws Exception {
//文件===>程序
//有一个文件:--->创建一个File类的对象
File f = new File("C:\\Refinitiv\\phonebook\\Test.txt");
//利用FileReader这个流,这个管怼到源文件中--->创建一个FileReader流的对象
FileReader fr = new FileReader(f);
//进行操作“吸”的动作====>读取动作
char[] ch = new char[5];//缓冲数组
int len;
//一次读取五个:返回值是这个数组中的有效长度
while((len = fr.read(ch)) != -1){
//将数组转为String
System.out.print(new String(ch,0,len));
}
//“管”不用了,就要关闭---> 关闭流
fr.close();
}
}
字符流:FileWriter
- 一个一个字符输出
import java.io.File;
import java.io.FileWriter;
public class Test01 {
public static void main(String[] args) throws Exception {
//文件===>程序
//有一个文件:--->创建一个File类的对象,如果文件不存在,会自动创建文件
File f = new File("C:\\Refinitiv\\phonebook\\demo.txt");
//FileWriter管怼到文件上去
FileWriter fw = new FileWriter(f,false);//默认为false,相当于只覆盖不追加,如为true,表示追加
//开始动作:输出动作
//一个一个字符的往外输出
String str = "hello您好";
for(int i = 0;i < str.length();i++){
fw.write(str.charAt(i));
}
fw.close();
}
}
- 缓冲数数组向外输出
import java.io.File;
import java.io.FileWriter;
public class Test01 {
public static void main(String[] args) throws Exception {
//文件===>程序
//有一个文件:--->创建一个File类的对象,如果文件不存在,会自动创建文件
File f = new File("C:\\Refinitiv\\phonebook\\demo.txt");
//FileWriter管怼到文件上去
FileWriter fw = new FileWriter(f,false);//默认为false,相当于只覆盖不追加,如为true,表示追加
//开始动作:输出动作
//一个一个字符的往外输出
String str = "hello您好";
//利用缓冲数组
char[] chars = str.toCharArray();
fw.write(chars);
fw.close();
}
}
字符流:FileReader/FileWriter的文件复制
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
public class Test01 {
public static void main(String[] args) throws Exception {
//源文件
File f1 = new File("C:\\Refinitiv\\phonebook\\Test.txt");
//目标文件
File f2 = new File("C:\\Refinitiv\\phonebook\\Demo.txt");
//搞一个输入的管,怼到源文件上
FileReader fr = new FileReader(f1);
//搞一个输出的管,怼到目标文件上
FileWriter fw = new FileWriter(f2);
//方式1:一个字符一个字符的复制
// int n;
// while((n = fr.read()) != -1){
// fw.write(n);
// }
//方式2:利用缓冲数组
char[] ch = new char[5];
int len;
while((len = fr.read(ch)) != -1){
fw.write(ch,0,len);
//可以将数组转为String输出
// String s = new String(ch,0,len);
// fw.write(s);
}
//关闭流:一般流倒着关闭,后用先关
fw.close();
fr.close();
}
}
字节流:FileInputStream
- 一个一个字节的读取
import java.io.File;
import java.io.FileInputStream;
public class Test01 {
public static void main(String[] args) throws Exception {
//利用字节流将文件内容读到程序中来
//源文件
File f = new File("C:\\Refinitiv\\phonebook\\images\\s1.jpg");
//将一个字节流这个管怼到源文件上
FileInputStream fis = new FileInputStream(f);
//读取操作
//一个一个字节读取
/**
* 细节1:
* 该文件是utf-8存储的,英文字符,底层实际占用1个字节
* 中文字符,底层实际占用3个字节
*
* 细节2:
* 如果文件是文本文件,就不要使用字节流读取文件,建议使用字符流
*
* 细节3:
* read()读取一个字节,返回值是int类型,而不是byte类型
* read()方法底层做了处理,让返回值的数据都是“正数”
* 就是为了避免如果字节流返回的是-1的话,那到底是读入的字节?还是到文件的结尾?
*/
int count = 0;//定义一个计数器,用来计读入的字节的个数
int n;
while((n = fis.read()) != -1){
count++;
System.out.println(n);
}
System.out.println("字节个数为:" + count);
//关闭流
fis.close();
}
}
- 字节类型的缓冲数组
import java.io.File;
import java.io.FileInputStream;
public class Test01 {
public static void main(String[] args) throws Exception {
//利用字节流将文件内容读到程序中来
//源文件
File f = new File("C:\\Refinitiv\\phonebook\\images\\s1.jpg");
//将一个字节流这个管怼到源文件上
FileInputStream fis = new FileInputStream(f);
//读取操作
//利用缓冲数组
byte[] b = new byte[1024];
//定义读取数组的有效长度len
int len = fis.read();
while(len != -1){
for(int i = 0;i < len; i++){
System.out.println(b[i]);
}
len = fis.read(b);
}
//关闭流
fis.close();
}
}
字节流:FileInputStream/FileOuputStream文件复制
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Test01 {
public static void main(String[] args) throws Exception {
//利用字节流将文件内容读到程序中来
//源文件
File f1 = new File("C:\\Refinitiv\\phonebook\\images\\s1.jpg");
//目标文件
File f2 = new File("C:\\Refinitiv\\phonebook\\images\\s2.jpg");
//有一个输入的管道怼到源文件
FileInputStream fis = new FileInputStream(f1);
//有一个输出的管道怼到目标文件上
FileOutputStream fos = new FileOutputStream(f2);
//开始复制(边读边写)
// int n = fis.read();
// while(n != -1){
// fos.write(n);
// n = fis.read();
// }
//利用缓冲数组
byte[] b = new byte[1024];
int len = fis.read(b);
while(len != -1){
fos.write(b,0,len);
len = fis.read(b);
}
//关闭流(先用后关)
fos.close();
fis.close();
}
}
- 【1】一个一个字节复制文件
- 【2】缓冲数组复制文件
- 【3】缓冲区复制文件
- FileInputStream、FileOutputStream是不可以完成的,需要功能加强,这时引入BufferedInputStream、BufferedOutputStream,这两个流是处理流,是在FileInputStream、FileOutputStream外面套了一层流,处理流底层也是字节数组
- FileInputStream、FileOutputStream是不可以完成的,需要功能加强,这时引入BufferedInputStream、BufferedOutputStream,这两个流是处理流,是在FileInputStream、FileOutputStream外面套了一层流,处理流底层也是字节数组
处理流:BufferedInputStream/BufferedOutputStream文件复制
import java.io.*;
public class Test01 {
public static void main(String[] args) throws Exception {
//利用字节流将文件内容读到程序中来
//源文件
File f1 = new File("C:\\Refinitiv\\phonebook\\images\\s1.jpg");
//目标文件
File f2 = new File("C:\\Refinitiv\\phonebook\\images\\s2.jpg");
//有一个输入的管道怼到源文件
FileInputStream fis = new FileInputStream(f1);
//有一个输出的管道怼到目标文件上
FileOutputStream fos = new FileOutputStream(f2);
//功能加强,在FileInputStream、FileOutputStream外面套一个管:BufferedInputStream/BufferedOutputStream
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
//开始动作
byte[] b = new byte[1024];
int len = bis.read(b);
while(len != -1){
//BufferedOutputStream底层做了刷新缓冲区的操作,所以不需要手动调用flush()
bos.write(b,0,len);
len = bis.read(b);
}
//关闭流(先用后关)
/**
* 如果处理流包裹着节点流的话,那么其实只要关闭高级流(处理流)
* 那么里面的字节流也会随之关闭
*/
bos.close();
bis.close();
}
}
处理流:BufferedReader/BufferedWriter文件复制
import java.io.*;
public class Test01 {
public static void main(String[] args) throws Exception {
//源文件
File f1 = new File("C:\\Refinitiv\\phonebook\\images\\Test.txt");
//目标文件
File f2 = new File("C:\\Refinitiv\\phonebook\\images\\Demo.txt");
//有一个输入的管道怼到源文件
FileReader fr = new FileReader(f1);
//有一个输出的管道怼到目标文件上
FileWriter fw = new FileWriter(f2);
//功能加强,在FileReader、FileWriter外面套一个管:BufferedReader/BufferedWriter
BufferedReader br = new BufferedReader(fr);
BufferedWriter bw = new BufferedWriter(fw);
//开始动作
//方式1:读取一个字符,输出一个字符
// int n = br.read();
// while(n != -1){
// bw.write(n);
// n = br.read();
// }
//方式2:利用缓冲数组
char[] ch = new char[30];
int len = br.read(ch);
while(len != -1){
bw.write(ch,0,len);
len = br.read(ch);
}
//方式3:读取String
String str = br.readLine();//每次读取文本文件中一行,返回字符串
while(str != null){
bw.write(str);
//在文本文件中应该再写出一个换行
bw.newLine();//新起一行
//再读下一行
str = br.readLine();
}
//关闭流(先用后关)
/**
* 如果处理流包裹着字符流的话,那么其实只要关闭高级流(处理流)
* 那么里面的字符流也会随之关闭
*/
bw.close();
br.close();
}
}
20.1 流的异常处理
- 将流对象放到try之外声明并且赋值为null;放到try之内创建
- 关闭流之前需要判断流对象是否初始化成功—判断流对象是否为null
- 关流之后需要将流对象设置为null
- 为了防止关流失败导致数据丢失,需要在写完数据之后调用flush(),刷新缓冲区。一般缓冲区的合适大小是10~15M
import java.io.*;
/**
* 统计工作空间中Java代码的行数
*/
public class TestDemo {
private static int count = 0;
public static void main(String[] args) {
File file = new File("src/com/bjsxt/java/TestDemo.java");
countLine(file);
System.out.println(count);
}
private static void countLine(File file){
if(file == null) throw new NullPointerException();
if(file.isDirectory()){
File[] files = file.listFiles();
for (File f : files) {
countLine(f);
}
}else if(file.getName().endsWith(".java")){
try(BufferedReader reader = new BufferedReader(new FileReader(file))){
while((reader.readLine()) != null){
count++;
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
20.2 转换流
- OutputStreamWriter:将字符流转化为字节流,在字符转化字节,必要时需要指定字符编码。字符输出流===》字节输出流
- InputStreamReader:将字节流转化为字符流。字节输入流===》字符输入流
InputStreamReader
import java.io.*;
public class Test01 {
public static void main(String[] args) throws Exception {
//源文件
File f = new File("C:\\Refinitiv\\phonebook\\images\\Test.txt");
//需要一个输入的字节流接触文件
FileInputStream fis = new FileInputStream(f);
//加入 一个转换流,将字节流转换为字符流(转换流属于一个处理流)
//将字节转换为字符的时候,需要指定一个编码,这个编码跟文件本身的编码格式统一
//如果编码格式不统一的话,那么在控制台上展示的效果就会出现乱码
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
//将文件中内容显示在控制台
char[] ch = new char[20];
int len = isr.read(ch);
while(len != -1){
System.out.print(new String(ch,0,len));
len = isr.read(ch);
}
//关闭流
isr.close();
}
}
InputStreamReader/OutputStreamWriter文件复制
import java.io.*;
public class Test01 {
public static void main(String[] args) throws Exception {
//源文件
File f1 = new File("C:\\Refinitiv\\phonebook\\images\\Test.txt");
//目标文件
File f2 = new File("C:\\Refinitiv\\phonebook\\images\\Demo.txt");
//需要一个输入的字节流接触文件
FileInputStream fis = new FileInputStream(f1);
//将字节流转换为字符流,且要指定编码格式,此编码格式要与文件的编码一致
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
//将字节流输出到指定的目标文件
FileOutputStream fos = new FileOutputStream(f2);
//将字符流转换为字节流,指定的编码要与输出的目标文件编码一致
OutputStreamWriter osw = new OutputStreamWriter(fos,"utf-8");
char[] ch = new char[20];
int len = isr.read(ch);
while(len != -1){
osw.write(ch,0,len);
len = isr.read(ch);
}
//关闭流
osw.close();
isr.close();
}
}
20.3 合并流
import java.io.*;
import java.util.Enumeration;
import java.util.Vector;
public class TestDemo {
public static void main(String[] args) {
try(FileInputStream fis1 = new FileInputStream("a.txt");
FileInputStream fis2 = new FileInputStream("b.txt");
FileInputStream fis3 = new FileInputStream("bb.txt");
FileOutputStream fos = new FileOutputStream("test.txt");
){
Vector<InputStream> v = new Vector<>();
v.addElement(fis1);
v.addElement(fis2);
v.addElement(fis3);
Enumeration<InputStream> e = v.elements();
//构建合并流对象
SequenceInputStream sis = new SequenceInputStream(e);
//读取数据
byte[] bs = new byte[10];
int len;
while((len = sis.read(bs)) != -1){
fos.write(bs,0,len);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
20.4 System类对IO流的支持
- System.in:标准输入流===》默认情况下,从键盘输入
import java.io.*;
import java.util.Scanner;
public class Test01 {
public static void main(String[] args) throws Exception {
//得到的是标准输入流
InputStream in = System.in;
int n = in.read();//read方法等待键盘的录入,所以这个方法是一个阻塞方法
System.out.println(n);
//Scanner的作用:扫描器:起到扫描作用,扫键盘的从这根管出来的数据
// Scanner sc = new Scanner(System.in);
// int i = sc.nextInt();
// System.out.println(i);
//Scanner不一定非得扫System.in进来的东西,还可以扫描其他管的内容
Scanner sc = new Scanner(new FileInputStream(new File("C:\\Refinitiv\\phonebook\\demo.txt")));
while(sc.hasNext()){
System.out.println(sc.next());
}
}
}
- System.out:标准输出流===》默认情况,输出到控制台,返回的输出流、打印流
import java.io.*;
public class Test01 {
public static void main(String[] args) throws Exception {
//打印流,写到控制台
PrintStream out = System.out;
//直接在控制台输出,并且换行操作
out.println("hello1");
}
}
应用案例
- 键盘录入的内容输出到文件中
import java.io.*;
public class Test01 {
public static void main(String[] args) throws Exception {
//1.准备输入方向
//键盘录入
InputStream is = System.in;
//字节流转换字符流
InputStreamReader isr = new InputStreamReader(is,"utf-8");
//在isr的流上套一层字符缓冲流
BufferedReader br = new BufferedReader(isr);
//2.准备输出方向
//目标文件
File f = new File("C:\\Refinitiv\\phonebook\\Test.txt");
//字符流
FileWriter fw = new FileWriter(f);
//字符缓冲流
BufferedWriter bw = new BufferedWriter(fw);
//操作高级流
String str = br.readLine();
while(str != null){
bw.write(str);
bw.newLine();
str = br.readLine();
}
//关闭流
bw.close();
br.close();
}
}
20.5 数据流
- 用来操作基本数据类型和字符串的
- DataInputStream:将文件中存储的基本数据类型和字符串写入内存的变量中
import java.io.*;
public class Test01 {
public static void main(String[] args) throws Exception {
//DataInputStream:将文件中存储的基本数据类型和字符串写入内存的变量中
File f = new File("C:\\Refinitiv\\phonebook\\Test.txt");
DataInputStream dis = new DataInputStream(new FileInputStream(f));
//将文件中内容读取到程序中来
System.out.println(dis.readUTF());
System.out.println(dis.readBoolean());
System.out.println(dis.readDouble());
dis.close();
}
}
- DataOutputStream:将内存中的基本数据类型和字符串的变量写出文件中
import java.io.*;
public class Test01 {
public static void main(String[] args) throws Exception {
//DataOutputStream:将内存中基本数据类型和字符处的变量写出文件中
File f = new File("C:\\Refinitiv\\phonebook\\Test.txt");
FileOutputStream fos = new FileOutputStream(f);
DataOutputStream dos = new DataOutputStream(fos);
//向外将变量写到文件中
dos.writeUTF("阿辉");
dos.writeBoolean(false);
dos.writeDouble(6.9);
//关闭流
dos.close();;
}
}
20.6 对象流:序列化/反序列化流
- 序列化:将对象转化为字节之后进行存储,如果一个对象要序列化,那么他所对应的类必须实现Serializable接口,用static/transient修饰的属性不会被序列化
import java.io.*;
public class Test01 {
public static void main(String[] args) throws Exception {
//将内存中的字符串写出到文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("C:\\Refinitiv\\phonebook\\Test.txt")));
//将字符串写到文件中(磁盘中)
oos.writeObject("您好");
//关闭流
oos.close();
}
}
- 反序列化:将字节转化为对象的过程
import java.io.*;
public class Test01 {
public static void main(String[] args) throws Exception {
//将文件中保存的字符串渡入到内存
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("C:\\Refinitiv\\phonebook\\Test.txt")));
//读取
String s = (String) ois.readObject();
System.out.println(s);
//关闭流
ois.close();
}
}
- 自定义Person类,序列化的那个对象对应的类,必须要实现Serializable接口
public class Person implements Serializable{
private static final long serialVersionUID = 1223L;
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 自定义Person类的序列化
import java.io.*;
public class Test01 {
public static void main(String[] args) throws Exception {
//序列化:将内存中对象写到文件中
Person p = new Person("lili",19);
//对象流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("C:\\Refinitiv\\phonebook\\Test.txt")));
oos.writeObject(p);
//关闭流
oos.close();
}
}
- 自定义Person类的反序列化
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class Test02 {
public static void main(String[] args) throws Exception{
//对象流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("C:\\Refinitiv\\phonebook\\Test.txt")));
//读入内存中
Person person = (Person) ois.readObject();
System.out.println(person);
//关闭流
ois.close();
}
}
- 凡是实现Serializable接口的类都有一个表示序列化版本标识的静态变量
- private static final long serialVersionUID
- serialVersionUID用来表明类的不同版本间的兼容性
- 如没有显示定义这个静态变量,它的值Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID可能发生变化。故建议,显式声明。
- 序列化的细节
- 被序列化的类的内部的所有属性,必须是可序列化的,基本数据类型都是可序列化的
- static、transient修饰的属性,不可以被序列化
20.7 Properties
- 是一个可以被持久化的映射,键和值得类型都是String
- properties文件的默认编码就是西欧编码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Properties;
import java.util.Set;
public class PropertiesDemo {
public static void main(String[] args) throws Exception{
Properties properties = new Properties();//它是没有泛型的
//添加数据
properties.setProperty("username","zhangsan");
properties.setProperty("age","20");
System.out.println(properties.toString());//{age=20, username=zhangsan}
//遍历
Set<String> pronames = properties.stringPropertyNames();
for (String name : pronames) {
System.out.println(name + "===" + properties.getProperty(name));
}
//和流相关的方法
//list方法
// PrintWriter pw = new PrintWriter("tt.txt");
// properties.list(pw);
// pw.close();
//store保存方法
FileOutputStream fos = new FileOutputStream("ttt.properties");
properties.store(fos,"注释");
fos.close();
//load加载方法
Properties pros = new Properties();
FileInputStream fis = new FileInputStream("ttt.properties");
pros.load(fis);
fis.close();
System.out.println(pros.toString());
}
}
21、线程
- 进程:计算机中在执行的任务,在CPU上执行和计算,一个核上往往只能执行一个进程中的一个线程,计算机看起来像是在运行多个进程,实际上是因为在计算机中任务切换速度非常快。进程在宏观上并行的,在微观上是串行的
- 线程:进程中的小任务
21.1 Thread
- 写一个类继承Thread类,将要执行的逻辑放到run方法中,创建线程对象,调用start方法来启动线程执行任务
- 因为单继承的原因,常用Runnable
21.2 Runnable
- 写一个类实现Runnable接口,重写run方法,创建Runnable对象,然后将Runnable对象作为参数传递到Thread对象中,利用Thread对象来启动线程。
21.3 Callable
- 写一个类实现Callable接口,重写call方法。会给一个结果。
21.4 多线程并发安全
- 多个线程同时执行,而多个线程在执行的时候是相互抢占资源导致出现了不合常理的数据的现象,叫多线程的并发安全问题
注意
:多线程在执行的时候是相互抢占,而且抢占是发生在线程执行的每一步过程中 - 出现重复数字的情况:
- 出现负数的情况:
- 出现数字跳跃的情况:
21.4.1 同步锁
- 同步:一段逻辑在同一时间只能有一个线程执行,同步一定是安全的。安全不一定是同步的
- 异步:一段逻辑在同一时间能有多个线程执行,异步不一定不安全,不安全一定是异步的
注意
:从微观上而言,同步一定是安全的,安全也一定是同步的,从宏观上,同步一定是安全的,安全不一定是同步的 - 锁对象:要求锁对象要被所有的线程都认识
- 共享对象
- 类的字节码,堆内存、方法区是被线程所共享,但堆内存的对象是不唯一的,方法区的类确是唯一的;栈内存、本地方法栈、PC计数器是每一个线程独有的
- this:必须是同一个对象开启了多个线程
- 同步方法是一个非静态方法,this作为锁对象
- 同步方法是一个静态方法,就以当前的类作为锁对象
21.4.2 死锁
- 由于锁之间相互嵌套并且锁对象不同导致线程之间相互锁死,致使代码无法继续往下执行
- 避免死锁:统一锁对象,减少锁的嵌套
21.4.3 活锁
- 这个资源没有被任何的线程持有占有,导致程序无法往下执行
21.4.4 唤醒机制
- 等待唤醒机制必须结合锁来使用,而且锁对象是谁就用谁进行等待唤醒
public class Demo {
public static void main(String[] args) {
Student s = new Student();
s.setName("翠花");
new Thread(new Ask(s)).start();
new Thread(new Change(s)).start();
}
}
class Ask implements Runnable{
private Student s;
public Ask(Student s){
this.s = s;
}
@Override
public void run() {
while(true){
synchronized (s){
if(s.flag == false){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("老师,我是" + s.getName());
System.out.println("向您问一个问题~~~~");
s.flag = false;
s.notify();
}
}
}
}
class Change implements Runnable{
private Student s;
public Change(Student s){
this.s = s;
}
@Override
public void run() {
while(true){
synchronized (s){
if(s.flag == true){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(s.getName().equals("柱子")){
s.setName("翠花");
}else{
s.setName("柱子");
}
s.flag = true;
s.notify();
}
}
}
}
class Student{
private String name;
//flag为true,让ASK线程执行,让Change线程等待
//flag为false,让Change线程执行,让ASK线程等待
public boolean flag = true;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Demo {
public static void main(String[] args) {
Product p = new Product();
new Thread(new Producer(p)).start();
new Thread(new Consumer(p)).start();
}
}
class Product{
private int count;
//规定flag为true,表示生产商品
//规定如果flag为false,表示消费商品
public boolean flag = true;
public int getCount(){
return count;
}
public void setCount(int count){
this.count = count;
}
}
class Producer implements Runnable{
private Product p;
public Producer(Product p){
this.p = p;
}
@Override
public void run() {
while(true){
synchronized (p){
if(p.flag == false) {
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//记录本次生产的最大数量
int max = 1001 - p.getCount();
//计算本次实际的生产数量
int count = (int)(Math.random() * max);
//计算本次提供的商品数量
p.setCount(count + p.getCount());
System.out.println("本次生产了" + count + "件商品,能提供" + p.getCount() + "件商品");
p.flag = false;
p.notify();
}
}
}
}
class Consumer implements Runnable{
private Product p;
public Consumer(Product p){
this.p = p;
}
@Override
public void run() {
while(true){
synchronized (p){
if(p.flag == true) {
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//计算本次消费数量
int count = (int) ((p.getCount() + 1)*Math.random());
//计算本次剩余商品数量
p.setCount(p.getCount() - count);
System.out.println("本次消费了" + count + "件商品,剩余" + p.getCount() + "件商品");
p.flag = true;
p.notify();
}
}
}
}
21.4.5 守护线程
- 守护别的线程,只要被守护的线程结束,无论守护线程完成与否都会结束。
- 在线程中,一个线程要么是守护线程,要么是被守护的线程。当最后一个守护的线程结束才会导致所有的守护线程结束。GC是最大的守护线程
- 启动线程:
- 用户请求创建
- 系统自启
- 被其他线程启动
- 结束线程:
- 线程执行完成后,自然结束
- 守护线程结束
- 出现异常或错误
21.4.6 单例模式
- 在全局中只存在一个实例的这种现象
- 饿汉式:会增加类的加载时间,能避免的并发问题。
- 懒汉式:减少类的加载时间,会导致多线程的并发安全问题。
22、网络编程
- 套接字:进行网络数据传输的一套API — 本质上是可以在网络上使用流
- IP地址:在网络中标记主机
- 端口:计算机与外界交互的媒介
- DNS解析服务器:将域名和IP地址进行对应
22.1 UDP
- 基于流的。不建立连接,不可靠。需要对数据进行封包,每个包不超过64K大小。适用于对速度依赖性比较强但是对可靠性依赖性比较低的场景—视频聊天
22.2 TCP
- 基于流。建立连接,经历三次握手,可靠,传输速率相对较慢,理论上不限制传输的数据的大小。适用于对可靠性的依赖性更高对速度依赖性较低的场景—文件传输
- 三次握手:
注意
:receive/connect/accept/write/read都会引起阻塞
23、枚举
- 取值有限、确定能够一一列举
- 用enum定义枚举—枚举本身是一个类
- 枚举中构造方法默认私有化,构造方法可以重载
- 枚举常量必须定义在在枚举类的首行
- 枚举类中可以定义任意类型的方法和属性,包括抽象方法
- 在java中,所有的枚举默认继承java.lang.Enum
- JDK1.5开始,允许在switch-case中使用枚举常量
- 自定义的枚举类的上层父类:Object
- enum 关键字对应的枚举类的上层父类:java.lang.Enum
- 自定义定义枚举类:
/**
* 自定义定义枚举类:季节
* 自定义的枚举类的上层父类:Object
*/
public class Season {
//属性
private final String seasonName ; //季节名字
private final String seasonDesc ; //季节描述
//利用构造器对属性进行赋值操作
//构造器私有化,外界不能调用这个构造器,只能Season内部自己调用
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("冬天","太冷了");
//额外因素
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
public class TestSeason {
public static void main(String[] args) {
Season summer = Season.SUMMER;
System.out.println(summer);
System.out.println(summer.getSeasonName());
//自定义的枚举类的上层父类:Object
System.out.println(Season.class.getSuperclass().getName());
}
}
- JDK1.5以后使用enum关键字创建枚举类
public enum SeasonEnum {
//enum枚举类要求对象(常量)必须放在最开始位置
//多个对象之间用,进行连接,最后一个对象后面用;结束
//enum 关键字对应的枚举类的上层父类:java.lang.Enum
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;
}
public String toString(){
return "Season{" + "seasonName="+ '\'' + seasonName +
'\'' + "," + "seansonDesc=" + '\'' + seasonDesc + '\'' + "}";
}
}
- 源码中经常看到别人定义的枚举类形态:
- 枚举类底层属性,构造器,toString,get方法都删掉不写了,甚至SPRING()的括号都省略了
public enum Season {
SPRING,
SUMMER,
AUTUMN,
WINTER;
}
public class TestSeason {
public static void main(String[] args) {
//enum关键字创建的Season枚举类上面的父类是:java.lang.Enum,常用方法可直接使用
Season autumn = Season.AUTUMN;
System.out.println(autumn/*.toString()*/); //AUTUMN
//values
Season[] values = Season.values();
for (Season value : values) {
System.out.println(value);
}
//valueOf
//注意:对象的名字必须传正确
Season autumn1 = Season.valueOf("AUTUMN");
System.out.println(autumn1);
}
}
- 枚举类实现接口
-
- 接口
public interface TestInterface {
void show();
}
-
- 接口的实现类
public enum Season implements TestInterface{
//调用的是各自方法
SPRING{
@Override
public void show() {
System.out.println("春天");
}
},
SUMMER{
@Override
public void show() {
System.out.println("夏天");
}
},
AUTUMN{
@Override
public void show() {
System.out.println("秋天");
}
},
WINTER{
@Override
public void show() {
System.out.println("冬天");
}
};
//调用的都是同一个方法
// @Override
// public void show() {
// System.out.println("Season...");
// }
}
-
- 测试类
public class TestSeason {
public static void main(String[] args) {
Season autumn = Season.AUTUMN;
autumn.show();
Season summer = Season.SUMMER;
summer.show();
}
}
应用案例
//枚举类
public enum Gender {
男,
女;
}
//Person
public class Person {
//属性
private int age;
private String name;
private Gender sex;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Gender getSex() {
return sex;
}
public void setSex(Gender sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
//测试类一
public class Test {
public static void main(String[] args) {
Person p = new Person();
p.setAge(19);
p.setName("lili");
p.setSex(Gender.女);
System.out.println(p);
}
}
//测试类二
public class Test02 {
public static void main(String[] args) {
Gender sex = Gender.男;
switch (sex){
case 男:
System.out.println("是个男孩");
break;
case 女:
System.out.println("是个女孩");
break;
}
}
}
24、Junit单元测试
- 测试类单独放在测试的包下
- 测试类的名字:****Test,要求见名知意
- 测试方法的定义,这个方法独立运行,不依托main方法,方法名test***(),也要见名知意,无参无返回值
- 测试方法定义完以后,不能直接独立运行,必须要在方法前加入一个注解:@Test
- 判断代码逻辑上可能出现问题,需加入断言才能解决
//计算逻辑代码
public class Calculator {
//加法
public int add(int a, int b){
return a - b; //加法的测试逻辑不对,通过Junit单元测试来校验
}
//减法
public int sub(int a,int b){
return a - b;
}
}
//Junit单元测试
import com.calculator.Calculator;
import org.junit.Assert;
import org.junit.Test;
public class CalculatorTest {
//测试add方法
@Test
public void testAdd(){
Calculator cal = new Calculator();
int result = cal.add(10,30);
// System.out.println(result); //程序的运行结果可以不关注
//加入断言:预测一下结果,判断一下我预测的结果和实际的结果是否一致
Assert.assertEquals(40,result); //第一个参数:预测结果;第二个参数:实际结果
}
//测试sub方法
@Test
public void testSub(){
Calculator cal = new Calculator();
int result = cal.sub(10,30);
System.out.println(result);
}
}
-
@Before: 加入这个注解的方法中的功能会在测试方法执行前执行,往往是加入申请资源的代码:申请数据库资源,申请IO资源,申请网络资源。。。
-
@After:加入这个注解的方法中的功能会在测试方法执行后执行,往往是加入释放资源的代码:释放数据库资源,释放IO资源,释放网络资源。。。
public class Calculator {
//加法
public int add(int a, int b){
return a + b;
}
//减法
public int sub(int a,int b){
return a - b;
}
}
import com.calculator.Calculator;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class CalculatorTest {
@Before
public void init(){
System.out.println("方法执行开始了。。。");
}
@After
public void close(){
System.out.println("方法执行结束了。。。");
}
//测试add方法
@Test
public void testAdd(){
Calculator cal = new Calculator();
int result = cal.add(10,30);
// System.out.println(result); //程序的运行结果可以不关注
//加入断言:预测一下结果,判断一下我预测的结果和实际的结果是否一致
Assert.assertEquals(40,result); //第一个参数:预测结果;第二个参数:实际结果
}
//测试sub方法
@Test
public void testSub(){
Calculator cal = new Calculator();
int result = cal.sub(10,30);
System.out.println(result);
}
}
25、反射
- -verbose:class 显示类的加载过程
- Class:代表字节码的类
- Package:代表包的类
- Field:代表属性的类
- Method:代表方法的类
- Constructor:代表构造方法的类
- Annotation:代表注解的类
- 反射是在获取这个类的字节码的基础上来解剖这个类
25.1 获取Class对象
- 第一种方式:通过类名.class的方式来获取制定类的字节码
- 第二种方式:通过对象.getClass的方式来获取对象对应的实际类的字节码
- 第三种方式:通过Class.forName(类的全路径文件名),获取指定类的字节码
- 第四种方式:通过类的加载器,获取字节码
//作为一个父类
public class Person {
//属性
private int age;
public String name;
//方法
private void eat(){
System.out.println("Person---eat");
}
public void sleep(){
System.out.println("Person---sleep");
}
}
public class Test {
public static void main(String[] args) throws Exception {
//案例:以Person的字节码信息为案例
//方式1:通过getClass()方法获取字节码信息
Person p = new Person();
Class c1 = p.getClass();
System.out.println(c1);
//方式2:通过内置class属性
Class c2 = Person.class;
System.out.println(c2);
//注意:方式1和方式2 不常用,是因为都有类了,可以直接操作类中的属性和方法,没有必要通过字节码信息去获取
//方式3:调用Class类提供的静态方法forName 最为常用
Class c3 = Class.forName("com.test01.Person");
//方式4:利用类的加载器
ClassLoader loader = Test.class.getClassLoader();
Class c4 = loader.loadClass("com.test01.Person");
System.out.println(c1 == c2); //true
System.out.println(c1 == c3); //true
System.out.println(c1 == c4); //true
}
}
- Class的具体实例:
- 类:外部类、内部类
- 接口
- 注解
- 数组
- 基本数据类型
- void
public class Demo {
public static void main(String[] args) {
/**
* + Class的具体实例:
* + 类:外部类、内部类
* + 接口
* + 注解
* + 数组
* + 基本数据类型
* + void
*/
Class c1 = Person.class;
Class c2 = Comparable.class;
Class c3 = Override.class;
int[] arr1 = {1,2,3};
Class c4 = arr1.getClass();
int[] arr2 = {5,6};
Class c5 = arr2.getClass();
System.out.println(c4 == c5); //true;同一维度,同一元素类型,得到的字节码都是同一个
Class c6 = int.class;
Class c7 = void.class;
}
}
- new Instance():获取对象必须提供了无参构造
25.2 常用方法
import java.io.Serializable;
public class Person implements Serializable,Cloneable {
//姓名
String name;
//年龄
int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name + "正在吃东西。。。。。");
}
//带参方法
public void eat(String food){
System.out.println(name + "...吃..." + food);
}
//私有方法
private void privateMethod(){
System.out.println("这是一个私有方法");
}
//静态方法
public static void staticMethod(){
System.out.println("这是一个静态方法");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Properties;
public class TestPerson {
public static void main(String[] args) throws Exception{
Person zhangsan = new Person();
zhangsan.name = "zhangsan";
// zhangsan.eat();
// getClazz();
// reflectOpe1();
// reflectOpe2();
// reflectOpe3();
Properties properties = new Properties();
// properties.setProperty("name","zhangsan");
// System.out.println(properties.toString());
invokeAny(properties,"setProperty",new Class[]{String.class,String.class},"username","张三");
System.out.println(properties.toString());//{username=张三}
}
//获取类对象的三种方式
public static void getClazz() throws Exception {
//使用对象获取类对象
Person zhangsan = new Person();
Class<?> class1 = zhangsan.getClass();
System.out.println(class1.hashCode());
Class<?> class2 = Person.class;
System.out.println(class2.hashCode());
Class<?> class3 = Class.forName("com.io.test.Person");
System.out.println(class3.hashCode());
}
//使用反射获取类的名字、包名、父类、接口
public static void reflectOpe1() throws Exception {
Class<?> class1 = Class.forName("com.io.test.Person");
//getName()
System.out.println(class1.getName());//com.io.test.Person
//getPackage()
System.out.println(class1.getPackage().getName());//com.io.test
//getSuperClass()
System.out.println(class1.getSuperclass().getName());//java.lang.Object
//getInterfaces()
Class<?>[] classes = class1.getInterfaces();
System.out.println(Arrays.toString(classes));//[interface java.io.Serializable, interface java.lang.Cloneable]
//getSimpleName()
System.out.println(class1.getSimpleName());//Person
//getTypeName()
System.out.println(class1.getTypeName());//com.io.test.Person
}
//使用反射获取类的构造方法,用构造方法创建对象
public static void reflectOpe2() throws Exception {
//获取类的类对象
Class<?> class1 = Class.forName("com.io.test.Person");
//获取类的构造方法Constructor
Constructor<?>[] cons = class1.getConstructors();
// for (Constructor<?> con : cons) {
// System.out.println(con.toString());
// }
//获取类中无参构造
Constructor<?> con = class1.getConstructor();
Person zhangsan = (Person)con.newInstance();
System.out.println(zhangsan);//Person{name='null', age=0}
//简洁方法:获取无参构造;创建对象
Person lisi = (Person) class1.newInstance();
System.out.println(lisi);//Person{name='null', age=0}
//获取类中带参构造方法
Constructor<?> con2 = class1.getConstructor(String.class,int.class);
Person xiaoli = (Person)con2.newInstance("xiaoli",20);
System.out.println(xiaoli);//Person{name='xiaoli', age=20}
}
//使用反射获取类中的方法,并调用方法
public static void reflectOpe3() throws Exception{
Class<?> class1 = Class.forName("com.io.test.Person");
//获取公开的方法(包括从父类继承的方法) Method对象
Method[] methods = class1.getMethods();
for (Method method : methods) {
System.out.println(method);
}
//获取类中的所有方法,包括私有方法、默认、保护的,不包括继承的
Method[] methods1 = class1.getDeclaredMethods();
for (Method method : methods1) {
System.out.println(method);
}
//获取单个方法
Method eatMethod = class1.getMethod("eat");
//调用方法
Person zhangsan = (Person) class1.newInstance();
eatMethod.invoke(zhangsan);
//toString()
Method toStringMethod = class1.getMethod("toString");
Object result = toStringMethod.invoke(zhangsan);
System.out.println(result);
//调用带参方法
Method eatMethod2 = class1.getMethod("eat",String.class);
eatMethod2.invoke(zhangsan,"鸡腿");
//获取私有方法
Method privateMethod = class1.getMethod("privateMethod");
//设置访问权限无效
privateMethod.setAccessible(true);
privateMethod.invoke(zhangsan);
//获取静态方法
Method staticMethod = class1.getMethod("staticMethod");
//静态方法是类方法,传入的对象为null
staticMethod.invoke(null);
}
//使用反射实现一个可以调用任何对象方法的通用方法
public static Object invokeAny(Object obj,String methodName,Class<?>[] types,Object...args) throws Exception{
//获取类对象
Class<?> class1 = obj.getClass();
//获取方法
Method method = class1.getMethod(methodName,types);
//调用方法
return method.invoke(obj,args);
}
//使用反射获取类的属性
public static void reflectOpe4() throws Exception{
//获取类对象
Class<?> class1 = Class.forName("com.io.test.Person");
//获取属性,含公开的字段,父类继承的字段
// Field[] fields = class1.getFields();
Field[] fields = class1.getDeclaredFields();
System.out.println(fields.length);
for (Field field : fields) {
System.out.println(field.toString());
}
//获取name属性
Field nameField = class1.getDeclaredField("name");
//name如果是私有属性,需要设置权限无效
// nameField.setAccessible(true);
//name属性赋值
Person zhangsan = (Person) class1.newInstance();
nameField.set(zhangsan,"zhangsan");
//获取name属性
System.out.println(nameField.get(zhangsan));
}
}
应用案例
public interface MyInterface {//自定义接口
//随便定义一个抽象方法
void myMethod();
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.CONSTRUCTOR,ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
//作为一个父类
public class Person implements Serializable {
//属性
private int age;
public String name;
//方法
private void eat(){
System.out.println("Person---eat");
}
public void sleep(){
System.out.println("Person---sleep");
}
}
//Student作为子类
@MyAnnotation(value = "hello")
public class Student extends Person implements MyInterface{
//属性
private int sno;
double height;
protected double weight;
public double score;
//方法
@MyAnnotation(value = "himethod")
public String showInfo(){
return "我是一名三好学生";
}
public String showInfo(int a,int b){
return "我是一名三好学生";
}
private void work(){
System.out.println("我以后会找工作--》》称为码农");
}
void happy(){
System.out.println("happy happy~~~");
}
protected int getSno(){
return sno;
}
//构造器
public Student(){}
public Student(double weight,double height){
this.weight = weight;
this.height = height;
}
private Student(int sno){
this.sno = sno;
}
Student(int sno,double weight){
this.sno = sno;
this.weight = weight;
}
protected Student(int sno,double height,double weight){
this.sno = sno;
this.height = height;
this.weight = weight;
}
@Override
@MyAnnotation(value = "helloMethod")
public void myMethod() throws RuntimeException{
System.out.println("我重写了myMethod的方法。。。");
}
@Override
public String toString() {
return "Student{" +
"sno=" + sno +
", height=" + height +
", weight=" + weight +
", score=" + score +
'}';
}
}
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
public class Test {
public static void main(String[] args) throws Exception {
//获取字节码信息
Class cls = Student.class;
//通过字节码信息获取构造器
//getConstructors:只能获取当前运行时类被public修饰的构造器
Constructor[] c1 = cls.getConstructors();
for (Constructor c : c1) {
System.out.println(c);
}
System.out.println("------------------------------------");
//getDeclaredConstructors:获取运行时类的全部修饰符的构造器
Constructor[] c2 = cls.getDeclaredConstructors();
for (Constructor c : c2) {
System.out.println(c);
}
//获取指定的构造器
Constructor con1 = cls.getConstructor();//得到的是空构造器
System.out.println(con1);
//得到两个参数的有参构造器
Constructor con2 = cls.getConstructor(double.class, double.class);
System.out.println(con2);
//得到一个参数的有参构造器,并且是private修饰
Constructor con3 = cls.getDeclaredConstructor(int.class);
System.out.println(con3);
//有了构造器之后,可以创建对象
Object o1 = con1.newInstance();
System.out.println(o1);
Object o2 = con2.newInstance(180.5, 170.6);
System.out.println(o2);
System.out.println("==============================");
//获取属性
//getFields:获取运行时类和父类中被public修饰的属性
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field);
}
//getDeclaredFields:获取运行时类中的所有属性
Field[] declaredFields = cls.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field);
}
//获取指定属性
Field score = cls.getField("score");
System.out.println(score);
Field sno = cls.getDeclaredField("sno");
System.out.println(sno);
//获取修饰符
int modifiers = sno.getModifiers();
System.out.println(modifiers);
System.out.println(Modifier.toString(modifiers));
//获取属性的数据类型
Class type = sno.getType();
System.out.println(type);
//获取属性的名字
String name = sno.getName();
System.out.println(name);
System.out.println("++++++++++++++++++++++++++++++++");
//给属性赋值:给属性设置值,必须要有对象
Field sco = cls.getField("score");
Object obj = cls.newInstance();
sco.set(obj, 98); //给obj这个对象的score属性设置具体的值,这个值为98
System.out.println(obj);
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
//获取方法
//getMethods:获取运行时类的方法还有所有父类中的方法(被public修饰)
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println(method);
}
//getDeclaredMethods:获取运行时类中的所有方法
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method);
}
//获取指定方法
Method showInfo1 = cls.getMethod("showInfo");
System.out.println(showInfo1);
Method showInfo2 = cls.getMethod("showInfo", int.class, int.class);
System.out.println(showInfo2);
Method work = cls.getDeclaredMethod("work");
System.out.println(work);
//获取方法的具体结构
/*
@注解
修饰符 返回值类型 方法名(参数列表) throws XXXX{}
*/
//获取方法名字
System.out.println(work.getName());
//获取修饰符
System.out.println(Modifier.toString(work.getModifiers()));
//获取返回值
System.out.println(work.getReturnType());
//获取参数列表
Class[] parameterTypes = work.getParameterTypes();
for (Class parameterType : parameterTypes) {
System.out.println(parameterType);
}
//获取注解
Method myMethod = cls.getMethod("myMethod");
Annotation[] annotations = myMethod.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获取异常
Class[] exceptionTypes = myMethod.getExceptionTypes();
for (Class exceptionType : exceptionTypes) {
System.out.println(exceptionType);
}
//调用方法
Object o = cls.newInstance();
myMethod.invoke(o); //调用o对象的myMethod方法
System.out.println(showInfo2.invoke(o,12,45));
//获取运行时类的接口
Class[] interfaces = cls.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println(anInterface);
}
//获取父类的接口
//先得到父类的字节码信息
Class superclass = cls.getSuperclass();
//获取父类接口
Class[] interfaces1 = superclass.getInterfaces();
for(Class c : interfaces1){
System.out.println(c);
}
//获取运行时类所在的包
Package aPackage = cls.getPackage();
System.out.println(aPackage);
System.out.println(aPackage.getName());
//获取运行时类的注解
Annotation[] annotations1 = cls.getAnnotations();
for(Annotation annotation : annotations1){
System.out.println(annotation);
}
}
}
25.3 注解
- 给程序看的解释,在Java中,所有注解的父类是Annotation
- 在注解中直接定义属性,那么这个属性默认是一个静态常量
- 注解的属性只能是基本类型、枚举、String、Class、其他注解类型以及他们所对应的一维数组
- 如果数组只有一个值,可以不加花括号
- 如果注解中只有一个属性并且名字为value,那么在使用这个注解的时候可以省略属性名不写
- 如果只有一个成员变量的话,名字尽量为value
- 元注解:修饰注解的注解
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(value = RetentionPolicy.RUNTIME)
public @interface PersonInfo {
String name();
int age();
String sex();
}
import java.lang.reflect.Method;
public class Person {
@MyAnnotation
public void show() {
}
public void eat(){}
@PersonInfo(name="小岳岳",age = 30,sex = "女")
public void show(String name,int age,String sex){
System.out.println(name + "=====" + age + "=====" + sex);
}
}
class Demo1{
public static void main(String[] args) throws Exception {
//获取类对象
Class<?> class1 = Class.forName("com.io.test.Person");
//获取方法
Method show = class1.getMethod("show",String.class,int.class,String.class);
//获取方法上的注解信息
PersonInfo personInfo = show.getAnnotation(PersonInfo.class);
System.out.println(personInfo.name());
System.out.println(personInfo.age());
System.out.println(personInfo.sex());
//调用方法
Person yueyue = (Person)class1.newInstance();
show.invoke(yueyue,personInfo.name(),personInfo.age(),personInfo.sex());
}
}
- @Target:限定注解的使用范围
- @Retention:限定注解的生命周期的;没写,默认注解是RetentionPolicy.CLASS
- RetentionPolicy.SOURCE:只在源代码编译的时候有效,字节码文件中没有MyAnnotation这个注解
- RetentionPolicy.CLASS:字节码文件中有MyAnnotation这个注解,但运行Java,MyAnnotation就不会加载了
- RetentionPolicy.RUNTIME:在运行时有效,会加载到内存中
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
String[] value();
}
public class Person {
public Person() {
}
}
- @Documented:限定这个注解能否产生文档的
- @Inherited: 限定次注解可以作用在子类上
26、JVM
- 标准参数:-d -ea
- 非标准参数:-X
- 每一个线程都有一个独立的栈,因此栈内存的大小影响线程的个数
- JVM限定了栈内存总的大小不能超过2G或者物理内存的1/3
- -Xss128K:表示限定栈内存的大小128K
- -Xmn:限定新生代的大小
- -Xms:限定堆内存的初始大小
- -Xmx:限定堆内存的最大值
- -XX:扩展参数
注意
:对象在创建之后先试图放入新生代,如果新生代存不开,则试图放入老生代 - 常见回收方式:
- Copying
- Mark-sweeping(标记整理法)
- Mark-cleaning(标记清理法)