Day1-java基础
java发展史
- 1995年 SUN公司发布了java
- 2005年 将JDK1.5更名为JDK5.0
- 2009年 Oracle收购了SUN公司
- 2014年 推出了JDK8.0
- ....
java的优势
- 简单
- 纯面向对象
- 开源
- 跨平台
前期准备
- 安装JDK
- 配置环境变量
- JAVA_HOME:JDK的安装路径
- 作用:告知计算机其他软件JDK的位置
- Path:%JAVA_HOME%/bin
- 作用:告知计算机其他位置JDK可执行命令的位置
- CLASSPATH:.
- .表示的是当前目录
- 作用:告知计算机生成的字节码文件的存放位置
- JAVA_HOME:JDK的安装路径
- 相关概念:
- JDK:java开发工具包
- 类库+JRE+测试工具+编译器
- JRE:java运行环境
- JVM+解释器
- JVM:java虚拟机
- 可以屏蔽各个操作系统之间的底层差异,模拟操作系统的执行环境,为JAVA跨平台提供了基础支持
- JDK:java开发工具包
java的运行机制
先编译,后解释运行
源文件-->编译器-->将源文件中的类生成对应的字节码文件(平台通用文件)-->解释器-->逐行解释逐行执行
第一个程序
输出“Helloworld”
新建一个后缀名为.java的文件
-
class:类
- 是盛放代码的容器
- 是程序的基本组成单元
-
类名:用来区分每个类
- 同一个源文件中的类一定不可重名
-
{}:用来划分类的边界
-
主函数:程序的入口
- 执行程序本质上就是执行主函数内容的过程
- 写法是固定的
- 一个类至多存在一个主函数
-
输出语句:向外输出展示某个内容
- 写在函数(方法)内部
- 写法是固定的
- 加不加ln的区别:
- 加ln默认输出内容独占一行,不加不会独占一行
- 加ln输出内容可以为空,不加不能为空
-
运行:
-
编译:
javac 源文件名.java
-
解释运行:
java 类名
-
class Hello1{
public static void main(String[] args){
System.out.println("helloworld");
System.out.println("helloworld");
System.out.println();
//System.out.print();
System.out.print("helloworld");
System.out.print("helloworld");
}
}
public-公开类
public class 类名{
}
使用
- 一个源文件至多存在一个公开类
- 类名必须与源文件名保持一致
- 公开类与普通类可以共存
package-包
- 作用:用来整理归纳字节码文件
package 包名1.包名2...;
使用
-
位置:源文件有效代码第一行
-
包名的声明通常不低于3层(com.姓名缩写.xxx)
-
编译运行:
编译:javac -d 目标路径 源文件名.java 运行:java 包名.类名
-
一个源文件至多存在一个package语句
命名规范
硬性规范
不遵守就会报错
- 严格区分大小写
- 不能以数字开头
- 符号只接受_和$(空格也属于符号)
- 不能使用关键字和保留字
软性规范
不遵守不会报错
- 不以汉字命名
- 类名:大驼峰命名法(从第一部分开始首字母大写)(学生类:StudentClass)
- 变量名和方法名:小驼峰命名法(从第二部分开始首字母大写)(学生年龄:studentAge)
- 包名:全小写
- 常量:全大写,每部分之间_连接(学生人数:STUDENT_NUMBER)
- 望名知意
注释
- 作用:用来对代码进行备注说明
- 特点:不参与编译运行
-
单行注释
- 只注释一行内容
//这是单行注释
-
多行注释
- 同时注释多行内容
/* 这是多行注释 这是多行注释 这是多行注释 */
-
文档注释
- 作用:可以额外结合编译语句生成说明文档
- 使用:只作用与公开内容,只能作用于类和方法的上方
/** 这是文档注释 */
-
生成说明文档的编译命令:
javadoc -d 目标路径 源文件名.java
知识点补充:
常见cmd操作指令;
指令 作用 盘符名: 进入指定盘符 cd 文件夹名 进入指定文件夹 cd.. 返回上一层级 dir 查看子目录 cls 清屏
今日重点
- 配置环境变量的步骤
- java 的运行机制
- 公开类的使用特点
- 包的使用特点
- 带包及不带包的编译运行命令
Day2-变量和运算符
什么是程序?
答:利用逻辑操作数据
数据操作:增删改查、存储
变量的概念
是计算机内存中的一块存储空间,作用为存储数据,是数据存储的基本单元
变量的组成
酒店 计算机内存
房间 变量
房间类型 数据类型
房间号 变量名
作用:住人 作用:存储数据
变量由数据类型、变量名、数据组成
变量的创建
-
先声明,后赋值
数据类型 变量名; 变量名=值;
- 赋值之后才能使用
-
声明的同时直接赋值 (常用)
数据类型 变量名=值;
-
同时声明多个变量,之后再一一赋值
数据类型 变量名1,变量名2,..; 变量名1=值1; 变量名2=值; ...
- 同时声明的多个变量数据类型必定一致
-
同时声明多个变量并直接一一赋值
数据类型 变量名1=值1,变量名2=值2,...;
- 可以与第三种写法共存
//先声明
int age;
//后赋值
age=20;
System.out.println(age);
//声明的同时直接赋值
int age2=30;
System.out.println(age2);
//同时声明多个变量
int age3,age4,age5;
age3=21;
age4=22;
age5=23;
System.out.println(age3);
System.out.println(age4);
System.out.println(age5);
//同时声明多个并直接赋值
int age6=31,age7=32,age8;
age8=33;
System.out.println(age6);
System.out.println(age7);
System.out.println(age8);
数据类型
Java是强类型的编码语言:每个数据值都有对应的数据类型
分类
- 基本数据类型
- 整型:byte、short、int、long
- 浮点型(小数):float、double
- 字符:char
- 布尔:boolean
- 引用类型
- 只要不是八大基本类型,就一定是引用类型
整型
- int为整型的默认类型
- long类型如果值超出了int的取值范围,需要在末尾添加L或者l
浮点型
- double为浮点型的默认类型
- float类型值的末尾必须添加F或者f
布尔类型
- 不能参与数学运算和数学判断
- 通常用于接受判断的结果
字符类型
A-Z:65-90
a-z:97-122
//1. 单引号直接赋值:内部只能接收一个字符
char c1='A';
//char c2='66';错误!
//2. 数字赋值:必须在取值范围之间
char c2=65;//A
//3. Unicode编码赋值 了解
char c3='\u0041';//A
引用类型-String
- 字符串:多个字符
String 变量名="值";
- String的值必须写进双引号,写进“”一定是字符串
- 对内容不做具体要求
类型转换
- 基本类型之间相互赋值传递的是值
- byte<char|short<int<long<float<double
- boolean不参与转换
自动类型转换
- 发生在小类型给大类型赋值时
如:
byte a=10;
int b=a;
强制类型转换
- 发生在大类型给小类型赋值时
小类型=(小类型)大类型;
-
大类型的值在小类型的取值范围内:不会出现数据丢失
int a=10; byte b=(byte)a;
-
大类型的值不在小类型取值范围内:会出现数据丢失,正负也有可能会发生紊乱
int a=129; byte b=(byte)a;//-127
-
小数类型给整数类型赋值:会直接舍弃小数位
double d=5.9; int n=(int)d;//5
-
其他数据类型给char类型赋值:都需要强转
byte b=65;//小类型 char c=(char)b;
与String进行转换
- 加上双引号或者拼接双引号一定会变成String类型
String s1="65";
String s2=6+5+"a";//11a
String s3=6+"a"+5;//6a5
String s4="a"+6+5;//a65
数据类型提升
- 在数学运算时,当有大类型参与,则类型会最终提升为大类型
- 最高提升至double,最低提升至int
运算符
表达式
将值或变量通过运算符进行连接,最终可以得到一个结果,该通过运算符连接的式子称为表达式
算数运算符
+、-、*、/、%
System.out.println(10/3);//3
System.out.println(10%3);//1
赋值运算符
=、(+=、-=、*=、/=、%=):先计算再赋值
- +=、-=、*=、/=、%=:不会发送自动类型提升,必要时会自动完成强制类型转换
int n=10;
n+=10;//n=n+10
//n=n+5.5; 会发生自动类型提升double
n+=5.5;
System.out.println(n);//25
比较运算符
<、>、>=、<=、!=(不等于)、==(等于)
- 结果一定为boolean类型
System.out.println(1>2);//f
System.out.println(1<2);//t
System.out.println(1>=2);//f
System.out.println(1<=2);//t
System.out.println(1!=2);//t
System.out.println(1==2);//f
一元运算符
++ | 值+1,相当于n=n+1 | n+=1 |
---|---|
-- | 值-1,相当于n=n-1 | n-=1 |
++在前
- 表达式的值是+1之后的值
++在后
- 表达式的值是+1之前的值
int n1=1;
int n2= (++n1);//1+1
int n3= (n1++);//n1=2+1
System.out.println("n1: "+n1);//3
System.out.println("n2: "+n2);//2
System.out.println("n3: "+n3);//2
int a=1;
int b=(a++)+(--a)+(++a)+(a--);
System.out.println("a: "+a);//1
System.out.println("b: "+b);//6
逻辑运算符
- 连接双方必须是boolean结果的表达式
运算符 | 作用 |
---|---|
&&(逻辑与) | 双方都为true,结果才为true,否则为false |
||(逻辑或) | 有一个为true,结果则为true,都为false时才是false |
!(逻辑非) | 取现有结果的相反值 |
System.out.println((1+1==2) && (2+2==4));//t
System.out.println((1+1==2) && (2+2>4));//f
System.out.println((1+1==2) || (2+2>4));//t
System.out.println((1+1!=2) || (2+2>4));//f
System.out.println(!(1+1!=2));//t
- 结果仍然为boolean类型
短路机制:当执行到能判断最终结果的表达式时,后续表达式将不再执行
&&和||是短路运算符
&和|是非短路运算符:无论如何所有表达式都会进行运算
System.out.println( 1+1!=2 && 5/0==1);//f //System.out.println( 1+1!=2 & 5/0==1);//运行报错
三元运算符
布尔表达式?结果1:结果2
- 执行原理:判断布尔表达式,为true,执行结果1,为false,执行结果2
int n=11;
//判断n是否为偶数并输出
System.out.println(n%2==0?"偶数":"不是偶数");
今日重点
- 八大基本类型及其字节数或字面值
- 自动类型转换和强制的发生时机
- 强转的语法
- 数据的自动类型提升
- ++在前在后的区别
- 什么是短路机制?
- 三元运算符的语法和执行流程
Day3-分支
Scanner-扫描器
- 作用:可以使用户输入数据并接收用户输入数据
使用
-
创建扫描器
java.util.Scanner sc=new java.util.Scanner(System.in);
-
提示用户输入
-
接收用户输入
int:sc.nextInt() double:sc.nextDouble() String:sc.next() char:sc.next().charAt(0)
class Day3_1{
public static void main(String[] args){
//需求:让用户输入学生个人信息 姓名、年龄、成绩
//创建扫描器
java.util.Scanner sc=new java.util.Scanner(System.in);
//提示用户输入
System.out.println("请输入你的姓名:");
//接收用户输入
String name=sc.next();
System.out.println("请输入你的年龄:");
int age=sc.nextInt();
System.out.println("请输入你的成绩:");
double score=sc.nextDouble();
//打印查看用户输入
System.out.println("学生姓名:"+name+",学生年龄:"+age+",学生成绩:"+score);
}
}
import-导包
- 作用:一次性指明源文件中的某个或某些类的来源
import 包名.类名;
使用
-
位置:在源文件第一类的上方,package语句的下方
-
可以存在多个
-
通过包名.*的方式可以一次性引入该包下的所有内容
import java.util.*;
-
路径必须截止至类
import java.util; 错误!
-
*不能导入子包中的类
-
*通配符一个导包语句中至多存在一次
import java.util.*.*; 错误!!
-
每个源文件中默认导入java.lang包
分支
- 根据代码的判断结果执行不同的操作
if分支
if(布尔表达式){
//逻辑代码(操作语句)
}
//判断小明的成绩,如果成绩=100,奖励他一辆自行车
int score=90;
if(score==100){
System.out.println("奖励一辆自行车");
}
if-else分支
if(布尔表达式){
//if的逻辑代码
}else{
//else的逻辑代码
}
![](C:\Users\Administrator\Desktop\一阶段笔记\assets/if else.png)
//判断小明的成绩,如果成绩=100,奖励他一辆自行车,否则奖励一个大嘴巴子
int score=90;
if(score==100){
System.out.println("奖励一辆自行车");
}else{
System.out.println("奖励一个大嘴巴子");
}
多重if分支
if(布尔表达式1){
//逻辑代码1
}else if(布尔表达式2){
//逻辑代码2
}else{
//else中的逻辑代码
}
使用
- 自上而下的判断
- 一个多重if分支结构至多执行一个逻辑代码
- 只要逻辑完整,对分支个数不做要求
- else分支可以省略
//判断小明的成绩,
//如果成绩=100,奖励他一辆玛莎拉蒂,
//成绩>=80,奖励一辆大G
//成绩>=60,奖励一辆五菱宏光mini
//否则奖励一个大嘴巴子
int score=50;
if(score==100){
System.out.println("奖励一辆玛莎拉蒂");
}else if(score>=80){
System.out.println("奖励一辆大G");
}else if(score>=60){
System.out.println("奖励一辆五菱宏光mini");
}else{
System.out.println("奖励一个大嘴巴子");
}
嵌套分支
if(外层布尔表达式){
if(内层布尔表达式){
//内层if逻辑代码
}else{
//内层else的逻辑代码
}
}else{
//外层的else逻辑代码
}
使用
- 对嵌套位置不做具体要求,只要在分支结构的任一大括号内即可
- 只要逻辑完整,对嵌套层数不做要求
//判断小明的成绩,
//如果成绩=100,奖励他一辆玛莎拉蒂,
//成绩>=80,奖励一辆大G
//成绩>=60,奖励一辆五菱宏光mini
int score=49;
if(score==100){
System.out.println("奖励一辆玛莎拉蒂");
}else if(score>=80){
System.out.println("奖励一辆大G");
}else if(score>=60){
System.out.println("奖励一辆五菱宏光mini");
}else{//成绩<60
//判断成绩>=50,奖励一辆自行车
if(score>=50){
System.out.println("奖励一辆自行车");
}else{//否则奖励一个大嘴巴子
System.out.println("奖励一个大嘴巴子");
}
}
switch分支
- 只能作用于具体值判断
switch(值|拥有具体值结果的变量或表达式){
case 值1:
//逻辑代码1
break;
case 值2:
//逻辑代码2
break;
default:
//default中的逻辑代码
}
使用
-
自上而下的比较case
-
执行:当某个case满足条件时,剩余case将不再进行判断
-
break在作用为强制跳出switch的执行,通常情况下,除去最后一个模块外,其他模块都应该添加break
-
可以使多个case共用同一个逻辑代码
//让用户输入月份,判断月份,输出对应的天数 Scanner sc=new Scanner(System.in); System.out.println("请输入一个月份:"); int month=sc.nextInt(); switch(month){ case 1: case 3: case 5://if(month==1 || month==3 || month==5) System.out.println("31天"); break; case 4://if(month==4) System.out.println("30天"); break; default: //else System.out.println("我不知道"); }
-
default模块可以省略,并且可以不写在最后,但是其仍然最后判断,为了不影响后续内容的执行,需要添加break防止其向执行
switch(month){ default: //else System.out.println("我不知道"); break; case 1: case 3: case 5://if(month==1 || month==3 || month==5) System.out.println("31天"); break; case 4://if(month==4) System.out.println("30天"); break; }
-
switch分支可以实现的操作if分支系列一定可以,但是反之则不一定(如:区间判断)
-
能够操作的数据类型:byte、short、char、int、String(JDK7.0)
转义字符
- 将字符的作用进行转换,可以实现将特殊字符转换为普通字符
\字符
- 将普通字符转换成特殊字符
\n :换行,相当于System.out.println()
\t :生成一段制表符距离,相当于键盘的tab键
注意:必须写在双引号内部
//输出一个"
System.out.println(" \" ");
//输出一个\
System.out.println(" \\ ");
System.out.println("abcd\nedfg\tqwer");
/* abcd
edfgn qwer */
今日重点
- Scanner的使用
- import语句的使用
- if分支系列的使用
- switch都能接收哪些数据类型
Day4-循环
概念
在满足某个条件的情况下,使一个或一段操作语句反复执行
while循环
while(布尔表达式){
//逻辑代码
}
//张汪洋跑圈:跑20圈
int i=1;//圈数,从第一圈开始
while(i<=20){//循环条件:圈数<=20
System.out.println("张汪洋正在跑第"+i+"圈");
//圈数+1
i++;
}
死循环:当循环条件永远满足时,循环将无限执行,无法正常结束
执行流程
初始值-->判断循环条件-->为true-->执行操作语句-->迭代初始值-->判断循环条件-->直到循环条件为false-->循环结束
执行特点
先判断,再执行,执行次数为0-n次
局部变量
概念
定义在方法内部的变量
使用
- 作用范围:从定义行开始,到所直属代码块{}结束
- 命名冲突:同一作用范围内,局部变量之间不可重名
do-while循环
do{
//操作语句
}while(循环条件);
执行特点
先执行,再判断,执行次数1-n次
//让张汪洋跑圈,每圈结束之后进行打分,分数>=80则可以停止跑圈,否则继续跑圈
//创建扫描器
Scanner sc=new Scanner(System.in);
int score=0;//用来接收成绩
do{
System.out.println("张汪洋正在跑圈...");
System.out.println("跑圈结束,请打分:");
score=sc.nextInt();
}while(score<80);//循环条件:分数<80
执行流程
操作语句-->判断循环条件-->为true-->操作语句-->判断循环条件-->直到为false-->循环结束
流程控制
break
- 跳出当前所有循环,使当前循环停止
continue
- 跳出本次循环,直接开始下一次循环
区别
- break可以使循环停止,而continue是直接开始下一次
- break可以作用于switch分支,但是continue无法作用于分支
for循环
for(循环初始值;循环条件;迭代语句){
//操作语句
}
初始值-->判断循环条件-->为true-->执行操作语句-->迭代初始值-->判断循环条件-->直到循环条件为false-->循环结束
for(int i=1;i<=20;i++){
System.out.println("张汪洋正在跑第"+i+"圈");
}
对比while
-
与while之间的操作可以互等
-
while更适用于循环次数不确定的时候
-
for更适用于循环次数确定的时候
-
for循环小括号内的三个组成部分可以省略,分号不可省
for(;;){ } 相当于: while(true){ }
嵌套循环
- 一个循环结构中套一个循环结构
使用
- 执行流程:外层循环执行一次,内层循环执行一遍
- 图形输出:外层循环代表行,内层循环代表列
- break和continue只能作用于直属的循环层次
- 只要逻辑完整,对嵌套层数不做要求
//用*输出一个长为10,宽为2的长方形
********** 10
********** 10
for(int i=1;i<=2;i++){//行 i=2
for(int j=1;j<=10;j++){//列 j=1-10
System.out.print("*");
}
//一行内容输出结束,换行
System.out.println();
}
* i=1-1
** 2-2
*** 3-3
**** 4-4
***** 5-5
for(int i=1;i<=5;i++){//行 i=3
for(int j=1;j<=i;j++){
System.out.print("*");
}
//一行内容输出结束,换行
System.out.println();
}
今日重点
- while和do-while的区别
- 局部变量的使用特点
- break和continue的区别
- for循环的语法
- 嵌套循环的执行流程
Day5-函数
概念
是一段可以重复执行的代码,通常情况下一个函数代表一个功能
语法
修饰符 返回值类型 函数名(形参列表){
//操作语句
}
public static void 函数名(){
}
使用
-
位置:类以内,其他函数以外 与主函数平级
-
函数想要执行,必须在主函数中手动调用
函数名(实参列表); 函数名();
-
调用位置:想在哪执行,就在哪调用
-
执行流程:
参数
- 函数执行过程中对不确定因素的提炼
形参
形式上的参数。特点为没有具体值,只有声明部分,写在函数声明处的小括号内
实参
实际上的参数。特点为是一个具体值或拥有具体值结果的表达式,写在函数调用处的小括号内
一个参数
public static void 函数名(数据类型 参数名){
}
调用:函数名(实参);
class Test1{
public static void main(String[] args){
//输出一首古诗,每行内容之后用20个-做分割
System.out.println("床前明月光");
//调用函数输出- 10
printLine(10);
System.out.println("疑是地上霜");
//调用函数输出- 20
printLine(20);
System.out.println("举头望明月");
//调用函数输出- 30
printLine(30);
System.out.println("低头思故乡");
}
//定义函数,用来输出指定数量的-
//n:输出个数
public static void printLine(int n){
//利用循环输出-
for(int i=1;i<=n;i++){
System.out.print("-");
}
System.out.println();
}
}
多个参数
public static void 函数名(数据类型 参数名1,数据类型 参数名2,..){
}
调用:函数名(值1,值2,..);
- 实参与形参的参数列表必须一致
- 参数列表:个数、顺序、数据类型
返回值
- 是函数的执行结果,特点为可以向上(调用者)返回
语法
public static 返回值类型 函数名(形参列表){
//操作语句
return 值;
}
使用
-
void表示没有返回值,如果有,则声明为值对应的数据类型
-
return作用为将值向上返回
-
同一直属作用范围内,return语句下方不能存在其他有效语句
- 无法执行
-
当调用一个有返回值的函数时,必须对其返回值做出处理,否则返回值没有意义
-
先接收,再处理
数据类型 变量名=函数名(实参列表);
- 数据类型应与函数声明的返回值类型保持一致
-
直接对调用结果进行后续操作
public static void main(String[] args){ //直接操作调用结果 if(method(10)%2==0){ System.out.println("偶数"); }else{ System.out.println("不是偶数"); } }
-
-
当函数中存在分支情况,必须保证每种分支情况都有对应return语句执行
-
一个函数至多执行一个return语句
-
当函数中存在复杂分支情况时,推荐使用三部曲完成:
- 在函数最上方定义用来返回的变量,并赋予初始值
- 在操作过程中给变量重新赋值
- 在函数最下方return返回该变量
函数高级
函数的嵌套调用
使用
-
嵌套调用的顺序和向上返回的顺序必定相反
-
只要逻辑完整,对嵌套层数不做要求
-
一个函数的执行结果(返回值)可以作为另一个函数的实参
-
当进行调用的嵌套时,执行顺序为由内向外
public static void main(String[] args){ System.out.println( method( get() ) );//先执行get(),再根据get()的返回值执行method()的调用 } //判断参数是否为偶数 public static boolean method(int n){ return n%2==0; } public static int get(){ return 10; }
JVM内存结构(了解)
栈的特点
- 只会操作栈顶元素,存从顶部存,取从顶部取
- 先存进去的后取出来(FILO)
- 栈帧:是栈的基本组成部分
- 每调用一次函数,都会生成一条对应的栈帧
递归
- 所有能够用递归实现的操作都可以用循环代替
- 概念:自己调用自己(函数之间的循环调用)
- 必须存在最终向上返回的条件,否则会陷入无限递归,最后直到栈帧将栈的内存撑爆,程序才会终止
//求某个数字的阶乘
public static int method(int n){//n=5
//判断本次计算的是否为1的阶乘
if(n==1){
return 1;//1的阶乘值就是1 是最小的阶乘值
}
return n*method(n-1);
}
今日重点
- 函数的完整语法
- 形参和实参的区别
- return关键字的使用
Day6-数组
变量存储多个数据的弊端:
- 书写和命名繁琐
- 无法将多个数据看做一组数据操作,效率低下、使用麻烦
概念
是计算机内存中一块连续的存储空间,作用为同时存储多个相同数据类型的值
创建
-
先声明,后指明长度
数据类型 []数组名; 数组名=new 数据类型[长度];
数据类型 []数组名;
数据类型[] 数组名;
数据类型 数组名[];
- 前后数据类型必须一致
- 长度的作用为方便内存分配空间
- 长度必须为整型
-
声明的同时直接指明长度 (常用)
数据类型 []数组名=new 数据类型[长度];
-
创建的同时直接赋值
数据类型[] 数组名=new 数据类型[]{值1,值2,..};
- 数组长度由值的个数决定
- 中括号指明长度和大括号赋值不能同时存在
数据类型[] 数组名={值1,值2,..}; (常用)
- 不可先声明,后直接赋值
//先声明,后指明长度
int[] arr1;//声明
arr1=new int[5];
//声明的同时直接指明长度
int[] arr2=new int[5];
//创建的同时直接赋值
int[] arr3=new int[]{20,21,22,23,24};
int[] arr4={25,26,27,28,29};
//不能先声明,后赋值
int arr5[];
//arr5={20,21,20}; //错误!
使用
-
通过下标操作数组位置
-
下标范围:从0开始,至数组长度前一位结束
-
使用下标:
- 赋值:数组名[下标]=值;
- 取值:数组名[下标]
-
下标的使用不能超出界限,否则运行时会报下标越界异常:
- java.lang.ArrayIndexOutOfBoundsException
-
通过数组名.length获取数组长度
-
数组创建之后,内部默认存放当前数据类型的默认值,作用为占位,保证内存分配空间
int:0
double:0.0
boolean:false
String:null
遍历
- 依次查看数组数据
for(int i=0;i<数组名.length;i++){
//i就表示下标
//通过数组名[i]获取当前遍历元素
}
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
数组高级
深入数组底层
-
数组属于引用类型
-
引用类型的底层存放是栈与堆同时存放
- 栈:存放引用名,也就是数组名,底层存放着对应的堆地址
- 堆:存放着数组的具体信息,如数据等
-
引用类型之间相互赋值传递的是堆地址
class Test{ public static void main(String[] args){ int a=10; int b=a; a++; System.out.println("a:"+a);//11 System.out.println("b:"+b);//10 int[] arr1={10,20,30,40,50}; int[] arr2=arr1; arr1[2]=66;//将arr1中第三个元素的值改成了66 arr2[1]=99;//将arr2中的第2个元素改成了99 //查看两个数组的元素 System.out.println("arr1:"); for(int i=0;i<arr1.length;i++){ System.out.print(arr1[i]+" "); }//10 99 66 40 50 System.out.println(); System.out.println("arr2:"); for(int i=0;i<arr2.length;i++){ System.out.print(arr2[i]+" "); }//10 99 66 40 50 } }
逢new必开:执行到new关键字,堆空间中一定会开辟新的地址
int[] arr1={10,20,30,40,50};//int[] arr1=new int[]{10,20,30,40,50}; int[] arr2=arr1; arr1[2]=66;//将arr1中第三个元素的值改成了66 arr2[1]=99;//将arr2中的第2个元素改成了99 int[] arr3={11,22,33,44,55}; arr2=arr3; //查看两个数组的元素 System.out.println("arr1:"); for(int i=0;i<arr1.length;i++){ System.out.print(arr1[i]+" "); }//10 99 66 40 50 System.out.println(); System.out.println("arr2:"); for(int i=0;i<arr2.length;i++){ System.out.print(arr2[i]+" "); }//11,22,33,44,55
数组扩容
扩容:扩大数组容量,增加数组长度
缩容:缩小数组容量,减少数组长度
步骤
- 创建一个长度更大的数组,通常为原数组长度2倍
- 将原数组内容复制到新数组中
- 将原数组地址转换为新数组地址
实现
-
利用for循环完成元素复制
int[] a={10,20,30,40,50}; //1. 创建一个长度更大的数组 int[] newA=new int[a.length*2]; //2. 利用for循环完成元素复制 //遍历原数组 for(int i=0;i<a.length;i++){ newA[i]=a[i]; } //3. 地址转换:让原数组a指向新数组newA的堆空间 a=newA; //查看 System.out.println(a.length); for(int i=0;i<a.length;i++){ System.out.print(a[i]+" "); }
-
利用System.arraycopy()方法实现元素复制
- System.arraycopy(原数组名,原数组复制起始下标,新数组名,新数组存放起始下标,复制长度)
- 该方法无返回值
int[] a={10,20,30,40,50}; //1. 创建一个长度更大的数组 int[] newA=new int[a.length*2]; //2.利用方法实现元素复制 System.arraycopy(a,0,newA,0,a.length); //3. 地址转换:让原数组a指向新数组newA的堆空间 a=newA; //查看 System.out.println(a.length); for(int i=0;i<a.length;i++){ System.out.print(a[i]+" "); }
- System.arraycopy(原数组名,原数组复制起始下标,新数组名,新数组存放起始下标,复制长度)
-
利用Arrays.copyOf()方法将三个步骤三合一实现数组扩容
- java.util.Arrays.copyOf(原数组名,预期的数组长度)
- 返回值:扩容后的新数组地址
import java.util.*; class Test{ public static void main(String[] args){ int[] a={10,20,30,40,50}; //将方法返回的新数组地址赋值给原数组 a=Arrays.copyOf(a,a.length*2); //查看 System.out.println(a.length); for(int i=0;i<a.length;i++){ System.out.print(a[i]+" "); } } }
- java.util.Arrays.copyOf(原数组名,预期的数组长度)
排序
冒泡排序
- 原理:让相邻的两个数字作比较,根据比较结果决定是否换位
- 特点:每轮比较结束之后,都会从后往前确定一个数字
- 实现:外层循环代表轮数,内层循环代表次数
class Test{
public static void main(String[] args){
int[] a={1,6,55,9,77};
for(int i=1;i<a.length;i++){//轮数
for(int j=0;j<a.length-i;j++){//次数从0开始 同时代表数组下标
//当前元素:a[j] 下一位元素:a[j+1]
if(a[j+1]>a[j]){//判断下一元素是否大于当前元素 从大到小:大于号 从小到大:小于号
//满足条件,换位
int temp=a[j+1];
a[j+1]=a[j];
a[j]=temp;
}
}
}
//查看
for(int i=0;i<a.length;i++){
System.out.print(a[i]+" ");//从大到小:77 55 9 6 1
}
}
}
选择排序
- 原理:每轮固定一个下标位置,让其他下标位置与其比较,根据比较结果决定是否换位
- 特点:每轮比较之后,固定位置的元素值都能被确定
- 外层循环代表固定下标,内层循环代表比较下标
int[] a={1,6,55,9,77};
for(int i=0;i<a.length-1;i++){//固定下标
for(int j=i+1;j<a.length;j++){//比较下标
if(a[j]>a[i]){//从大到小:大于号 从小到大:小于号
int temp=a[j];
a[j]=a[i];
a[i]=temp;
}
}
}
//查看
for(int i=0;i<a.length;i++){
System.out.print(a[i]+" ");//从大到小:77 55 9 6 1
}
JDK排序
- 原理:借助类库中的sort方法,完成排序
- java.util.Arrays.sort(数组名);
- 特点:只能实现从小到大排序
import java.util.*;
class Test{
public static void main(String[] args){
int[] a={1,6,55,9,77};
Arrays.sort(a);
}
}
可变长参数-了解
- 作用于参数类型确定,但是个数不确定的情况
修饰符 返回值类型 函数名(数据类型... 参数名){
}
使用
- 可变长参数的用法于数组一致
- 一个方法中至多存在一个可变长参数
- 与普通参数同时存在时,可变长参数必须写在末尾
public static void main(String[] args){
System.out.println(method(1,2));
System.out.println(method(1,2,3));
System.out.println(method(1,2,3,4));
System.out.println(method(1,2,3,4));
}
//定义几个函数,用来计算参数之和
public static int method(int... a){//int[] a
int sum=0;//累加器
//遍历参数a
for(int i=0;i<a.length;i++){
sum+=a[i];
}
return sum;
}
今日重点
- 数组的创建
- 数组的使用
- 数组属于什么数据类型
- 基本类型和引用类型相互赋值的区别
- 数组扩容的步骤及arraycopy和copyOf方法的全貌
- 冒泡排序和选择排序的原理及特点
Day7-面向对象基础
面向对象:更注重于问题的解决方式
面向过程:更注重于解决问题的步骤
- 面向对象和面向过程并不对立,面向对象的底层一定包含着面向过程
什么是对象?
- 从java思想上看:“万物皆对象”
- 从内存上看:对象是虚拟机内存中的一块存储空间,存储着对现实生活中对象行为和特征的描述
对象的组成
- 特征:都有什么
- 行为:能做什么
对象与对象间的关系
- is a:一个对象继承自另一个对象
- 狗是一个动物 Dog is a Animal
- 猫是一个动物 Cat is a Animal
- has a:一个对象包含另一个对象
- 电脑是一个对象 硬盘是一个对象 键盘是一个对象
- 电脑包含硬盘、键盘
- use a:一个对象使用另一个对象
- 程序员是一个对象 电脑是一个对象
- 程序员使用电脑
重新认识类
- 测试类:包含主函数,可以直接运行的类
- 描述型的类(实体类):不包含主函数,只对对象做描述
- 是对对象共性的抽取,用来描述同一批相似甚至相同的对象的特征和行为
类和对象的关系
- 类是对象的模板
- 对象是类的实例
- 一个模板可以创建多个相同相似的实例
类的组成
-
属性:用来描述对象特征,也称为成员属性、成员变量、全局变量等
-
语法:
1. 只声明 数据类型 属性名; 2. 声明的同时赋初始值 数据类型 属性名=值;
-
位置:类以内,方法以外,通常写在类的最上方
-
与局部变量的区别:
局部变量 属性 位置 方法内部 方法外部 作用范围 定义行开始,到直属代码块{}结束 整个类 命名冲突 同一作用范围内不可重名 属性和局部变量可以重名,局部变量优先级更高 默认值 没有 有
//2304班的学生类 class Student{ //特征 String name; String sex; int age; double score; String className="2304"; /* //不能在属性的位置先声明再赋值 String className; //再次赋值 className="2304"; */ }
-
-
方法:用来描述对象行为,也称为成员方法、实例方法等
-
语法:
访问修饰符 返回值类型 方法名(形参列表){ //操作语句 }
函数是加了static修饰符的方法,所以方法的参数和返回值的使用与函数无异
-
位置:类以内,其他方法以外,与属性平级
-
//2304班的学生类
class Student{
//特征
String name;
String sex;
int age;
double score;
String className="2304";
//行为
//吃饭
public void eat(){
System.out.println("吃饭饭");
}
//睡觉
public void sleep(){
System.out.println("睡觉觉");
}
//学习
public void study(){
System.out.println("学java");
}
}
创建对象
类名 对象名=new 类名();
对象访问
-
访问属性
取值:对象名.属性名 赋值:对象名.属性名=值;
-
访问方法
对象名.方法名(实参列表);
//测试类
class Test{
public static void main(String[] args){
//创建一个学生对象
Student stu1=new Student();
Student stu2=new Student();
//给stu1的属性进行赋值
stu1.name="张三";
stu1.sex="男";
stu1.age=20;
stu1.score=88.0;
//查看属性值
System.out.println(stu1.name);
System.out.println(stu1.sex);
System.out.println(stu1.age);
System.out.println(stu1.score);
System.out.println(stu1.className);
//调用stu1的方法
stu1.eat();
stu1.sleep();
stu1.study();
}
}
构造方法
- 作用:创建对象,且只能创建对象,创建对象也必须使用构造
无参构造:
访问修饰符 类名(){
}
有参构造:
访问修饰符 类名(数据类型 参数名1,数据类型 参数名2,..){
//用参数给对应属性赋值
属性名1=参数名1;
属性名2=参数名2;
..
}
语法特点
- 没有返回值类型部分
- 方法名必须与类名保持一致
使用特点
-
必须通过new关键字调用
-
语法延伸:
类名 对象名=new 类名();
- 第一个类名:声明创建的为哪个类的对象
- 第二个类名:指明调用的是哪个构造
-
使用有参构造:
类名 对象名=new 类名(实参列表);
-
无参构造和有参构造的区别:
- 无参构造只是创建对象
- 有参构造可以在创建对象同时直接给属性赋初始值
-
通过参数列表决定调用的是哪个构造
-
无参构造可以有0-1个,有参构造可以有0-多个
-
每个类中都默认存在一个无参构造,当手动显式声明任意构造之后,默认给予的构造将会失效
方法重载
当一个类中存放多个功能相似甚至相同的方法时,程序员定义方法时的命名工作及调用方法时的匹配工作及其繁琐
作用
允许功能相似或相同的方法命名一致,以此来简化程序员的开发成本
规则
- 在同一个类中,方法名相同,参数列表不同
- 与访问修饰符、返回值类型、异常没有关系
this关键字
- this:表示当前对象
this.
- 作用:指明当前类的属性或调用当前类的方法
this.属性名
this.方法名(实参) 不常用
- 更改有参构造的语法
public 类名(数据类型 属性名1,数据类型 属性名2,..){ this.属性名=属性名; .. }
修改学生类的有参构造:
public Student(String name,String sex,int age,double score){ //用参数给属性赋值 this.name=name; this.sex=sex; this.age=age; this.score=score; }
this()
- 作用:调用本类其他构造的内容
使用
- 只能写在构造方法有效代码第一行
- 根据参数列表决定执行的是哪个构造内容
- 不能递归调用及循环调用
今日重点
- 类和对象的关系
- 属性和局部变量的区别
- 创建对象的语法
- 构造方法的语法特点
- 方法重载的规则
- this关键字的作用
Day8-封装
现有代码对对象内容未做任何的保护,所以现在代码中对象数据不安全
概念
是一种屏障,用来保护对象数据不被外界任意访问,以此确保对象数据的安全性
步骤
-
属性私有化
private 数据类型 属性名;
含义 作用范围 public 公共的,公开的 任意位置都可访问 private 私有的 本类内部 - 理论上讲,可以根据需求选择性的将属性私有化,但是通常情况下,所有属性都要参与私有化
//银行卡-卡号、密码、余额 class BankCard{ private String cardID; private String password; private double balance; public BankCard(){} public BankCard(String cardID,String password,double balance){ this.cardID=cardID; this.password=password; this.balance=balance; } }
-
书写对应的getter(取值)、setter(赋值)方法
-
get方法:有返回值,无参数
public 返回值类型 getXxx(){ return 属性名; } Xxx:对应的属性名,首字母大写,如果属性是boolean类型,则方法名应为isXxx
- 返回值类型与属性声明保持一致
-
set方法:无返回值,有参数
public void setXxx(数据类型 属性名){ this.属性名=属性名; } Xxx:对应的属性名,首字母大写
可以根据需求为私有化属性选择性添加getter、setter方法,但是通常情况下私有化属性都会提供getter、setter
-
使用
取值:对象名.getXxx()
赋值:对象名.setXxx(实参);
//银行卡-卡号、密码、余额
class BankCard{
//属性私有化
private String cardID;
private String password;
private double balance;
public String getCardID(){
return cardID;
}
public void setCardID(String cardID){
this.cardID=cardID;
}
public String getPassword(){
return password;
}
public void setPassword(String password){
this.password=password;
}
//给balance属性提供get、set方法
//取值
public double getBalance(){
return balance;
}
//赋值
public void setBanalce(double balance){
this.balance=balance;
}
public BankCard(){}
public BankCard(String cardID,String password,double balance){
this.cardID=cardID;
this.password=password;
this.balance=balance;
}
}
class Test{
public static void main(String[]args){
BankCard bc=new BankCard("6288888888888888","000000",100.0);
//获取balance属性的值
System.out.println(bc.getBalance());
//给balance属性赋值
//bc.setBanalce=10000.0; 错误!
bc.setBanalce(10000.0);
System.out.println(bc.getBalance());
}
}
对象创建过程
- 属性的三个赋值时期
- 给属性开辟空间,赋默认值
- 给属性赋初始值
- 利用构造再次赋值
今日重点
- 封装的步骤
- getter、setter的区别
- 对象的创建过程
Day9-继承
概念
将子类的共性进行抽取,生成父类,在继承关系下,子类可以继承拥有父类所有可被继承的内容。
可以解决子类之间的代码冗余问题
语法
class 子类类名 extends 父类类名{
}
//父类-动物类
class Animal{
String name;
String sex;
int age;
String color;
//吃饭
public void eat(){
System.out.println("吃饭饭");
}
//睡觉
public void sleep(){
System.out.println("睡觉觉");
}
}
//狗- 名字、性别、年龄、花色 睡觉、吃饭
class Dog extends Animal{
}
//猫- 名字、性别、年龄、花色 吃饭、睡觉
class Cat extends Animal{
}
规则
- 一个子类只能有一个直接父类,父类可以有多个直接子类(单继承)
- 一个类身为子类的同时也可以是其他类的父类
- 子类可以拥有独有内容
- 父类无法访问子类独有内容
- 子类可以继承拥有所有父类所有能被继承的内容
- 父类的构造子类无法继承
- 父类私有内容子类无法直接继承
方法重写-覆盖
- 子类对从父类继承的方法体进行重新书写
规则
- 建立在继承关系上
- 返回值类型、方法名、参数列表必须与父类保持一致
- 访问修饰符必须与父类相同或者更宽
- 不允许抛出比父类更大或更多的异常
使用
进行方法重写之后,子类优先使用重写之后的内容
父类的作用:
- 解决子类之间的冗余问题
- 强制约束子类必须拥有某些内容
访问修饰符
- 作用:规定内容的访问范围
本类 | 同包 | 非同包子类 | 非同包非子类 | |
---|---|---|---|---|
private(私有的) | √ | |||
default(默认的) | √ | √ | ||
protected(受保护的) | √ | √ | √ | |
public(公开的) | √ | √ | √ | √ |
使用
- default不能显式声明
- 只有public和default可以修饰类
- 以上四个修饰符都可以修饰属性、方法、构造
- 以上四个修饰符都不能修饰局部变量
子类的内存结构
- 先构建父类内容,才能构建子类结构
父类封装
- 父类也是类,需要进行完整封装
//父类-动物类
class Animal{
private String name="小黑";
private String sex;
private int age;
private String color;
//提供getter、setter方法
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public String getSex(){
return sex;
}
public void setSex(String sex){
this.sex=sex;
}
public int getAge(){
return age;
}
public void setAge(int age){
this.age=age;
}
public String getColor(){
return color;
}
public void setColor(String color){
this.color=color;
}
public Animal(){}
//吃饭
public void eat(){
System.out.println("吃饭饭");
}
//睡觉
private void sleep(){
System.out.println("睡觉觉");
}
}
使用
- 父类将属性封装之后,子类无法直接使用封装属性,需要通过调用getter、setter方法完成
//测试类
class Test{
public static void main(String[] args){
//创建一个狗对象
Dog dog=new Dog();
dog.eat();
dog.lookDoor();
dog.setName("大黄");
System.out.println(dog.getName());
}
}
Super
- 表示父类对象
super()
- 作用:调用指定的父类构造内容
- 使用:
- 位置:必须写在子类构造方法有效代码第一行
- 根据参数列表决定调用的是哪个父类构造
- 子类构造第一行默认存在无参的super()
- 在执行子类构造之前先执行父类构造
//父类-动物类
class Animal{
private String name;
private String sex;
private int age;
private String color;
//提供getter、setter方法
...
//书写构造方法
public Animal(){}
public Animal(String name,String sex,int age,String color){
this.name=name;
this.sex=sex;
this.age=age;
this.color=color;
}
//吃饭
...
//睡觉
...
}
class Dog extends Animal{
//书写构造方法
public Dog(){}
public Dog(String name,String sex,int age,String color){
super(name,sex,age,color);
}
//重写父类方法
...
//独有方法-看门
...
}
//测试类
class Test{
public static void main(String[] args){
//创建一个狗对象
Dog dog=new Dog("大黄","公",2,"黑色");
}
}
super.
- 作用:指明父类属性或者调用父类方法
super.属性名
super.方法名(实参)
有继承关系的对象创建过程
- 给父子类属性分配空间,赋默认值
- 给父类属性赋初始值
- 执行父类构造给父类属性再次赋值
- 给子类属性赋初始值
- 执行子类构造给属性再次赋值
- 总结:先构建父类再构建子类
今日重点
- 继承的语法
- 继承的规则
- 方法重写的规则 (面试点:方法重载和重写的区别)
- 四个访问修饰符和其范围
- super的作用(面试点:this和super的区别)
Day10-多态
概念
父类引用可以指向不同的子类对象
语法
父类引用=子类对象;
展开:
父类类名 引用名=new 子类类名();
使用
- 实际创建的是子类对象
- 优先执行子类内容
- 父类引用无法访问子类独有内容
- 编译器关注的是引用类型,解释器关注的是实际对象类型
- 等号左边决定都能做什么,等号右边决定谁去做
引用类型间的相互转换
自动类型转换
父类类型=子类对象;
Animal a=new Dog();
强制类型转换
子类引用=(子类类名)父类引用名;
//将Animal引用a强转为Dog引用
Dog dog=(Dog)a;
-
只能转为原本指向的子类类型
Animal a=new Cat(); //将Animal引用a强转为Dog引用 Dog dog=(Dog)a;//编译不报错,运行报错
-
无继承关系的子类之间不可相互强转
Cat a=new Cat(); //将Cat引用a强转为Dog引用 Dog dog=(Dog)a;//编译报错
多态的使用场景
-
用于数组(容器):将数组类型声明为大类型,则内部可以存储不同的小类型对象
Dog d1=new Dog(); Dog d2=new Dog(); Cat c1=new Cat(); Cat c2=new Cat(); //定义数组,存放以上四个对象 Animal[] as ={d1,d2,c1,c2}; // Animal=Dog // Animal=Cat //遍历数组,调用方法测试 for(int i=0;i<as.length;i++){ //调用当前对象的eat方法 as[i].eat(); }
-
用于参数:将形参类型声明为大类型,则实参可以为不同的小类型对象
public static void main(String[] args){ method1(new Dog());//狗吃大骨头 method1(new Cat());//猫吃小鱼干 } //定义一个函数,传入参数,执行参数的eat()方法,要求可以执行出“狗吃大骨头”或者“猫吃小鱼干” public static void method1(Animal a){//Animal a=new Cat(); a.eat(); }
-
用于返回值:将返回值类型声明为大类型,则可以实际return返回不同的小类型对象
public static void main(String[] args){ //调用方法,接收返回值 Animal a=method2(10);//Animal a=new Dog(); a.eat(); } //定义一个函数,传入整型参数n,要求n为偶数返回Dog对象,否则返回Cat对象 public static Animal method2(int n){ if(n%2==0){ return new Dog();//Animal =new Dog(); }else{ Cat c=new Cat(); return c; } }
instanceof关键字
- 作用:用来判断引用是否与指定类型兼容
引用名 instanceof 类名
//定义一个函数,传入参数,要求可以执行出“看门门”或者“猫吃小鱼干”
public static void method3(Animal a){//Animal a=new Cat();
if(a instanceof Dog){//判断a是否和Dog兼容
//将a引用强转为Dog引用,方便调用独有方法
Dog d=(Dog)a;
d.lookDoor();
}else if(a instanceof Cat){//判断a是否与Cat兼容
a.eat();
}
}
使用
子类对象可以被父类类型兼容,父类对象不可被子类类型兼容
class Super{
}
class Sub1 extends Super{
}
class Sub2 extends Sub1{
}
class Sub3 extends Sub1{
}
//测试类
class Test{
public static void main(String[] args){
//创建子类对象
Sub2 s=new Sub2();
System.out.println(s instanceof Super);//t
System.out.println(s instanceof Sub1);//t
System.out.println(s instanceof Sub2);//t
Sub1 s2=new Sub1();
System.out.println(s2 instanceof Sub2);//f
Sub3 s3=new Sub3();
//System.out.println(s3 instanceof Sub2);编译报错
Sub1 s4=new Sub3();
System.out.println(s4 instanceof Sub2);//f
}
}
多态的好处
- 提升代码的可扩展性
- 更加贴合显示逻辑
今日重点
- 多态的概念
- 引用类型间的类型强转
- 多态的三个使用场景
- instanceof关键字的作用和语法
Day11-三大修饰符
abstract-抽象
- 抽象的,不是真实存在的
抽象类
访问修饰符 abstract class 类名{}
abstract 访问修饰符 class 类名{}
注:修饰符之间的顺序不做要求
使用
- 无法实例化对象
- 可以存在非抽象内容
- 仍然存在构造,方便子类创建对象
- 通常情况下,抽象类都是父类
- 抽象父类仍然可以参与多态
抽象方法
访问修饰符 abstract 返回值类型 方法名(形参列表);
使用
- 没有方法体部分
- 抽象方法必须存在于抽象类
- 子类必须对父类中的抽象方法提供重写,除非子类本身也是抽象类
- 除非子类本身也是父类,否则不要声明为抽象类
- 抽象父类的作用:
- 可以约束子类必须对抽象方法提供重写
抽象的好处
- 更加贴合现实逻辑
- 优化代码结构
static-静态
修饰属性
- 会变成静态属性,也称为类变量
static 数据类型 属性名;
使用
-
特点:不被类的某个对象独有,被该类的所有对象共享
-
静态内容独立存放在方法区中
- 方法区在JDK8.0之后被归纳到了堆空间
-
可以通过类名.属性名直接访问静态属性
-
静态属性封装之后,必须通过任一对象名.getXxx()|setXxx(实参)的方式访问静态属性
class ClassA{ private static int count=0;//静态属性-统计个数 public int getCount(){ return count; } public void setCount(int count){ this.count=count; } public ClassA(){ count++; } } //测试类 class Test{ public static void main(String[] args){ //统计总共创建了几个ClassA的对象 ClassA c1=new ClassA(); ClassA c2=new ClassA(); ClassA c3=new ClassA(); /* System.out.println("总共创建了"+c1.count+"个对象"); System.out.println("总共创建了"+c2.count+"个对象"); System.out.println("总共创建了"+c3.count+"个对象"); System.out.println("总共创建了"+ClassA.count+"个对象");*/ System.out.println("总共创建了"+c3.getCount()+"个对象"); } }
什么是类加载?
当第一次使用类内容时,通过CLASSPATH类路径将字节码文件加载到虚拟机中的过程,称之为类加载,通常类加载只会发生一次
触发类加载的时机:
- 第一次创建对象时
- 第一次访问静态内容时
- 通过Class.forName("全限定名")的方式强制触发类加载
- 全限定名:类的完整路径(包名.类名)
- 子类的类加载也会触发父类的类加载
- 只声明引用不会触发类加载
修饰方法
- 会变成静态方法,也就是函数
使用
- 不能访问非静态内容
- 在访问静态方法时,有可能非静态内容还未出现在内存中
- 可以通过类名.方法名(实参)直接调用静态方法
- 不能使用this和super关键字
- 如果直接通过类名访问静态方法,则当前对象和父类对象有可能还未创建
- 无法修饰局部变量
- 局部变量的作用范围限制其无法成为类变量
- 子类可以继承和重写父类静态方法,但是在多态的前提下,仍然执行父类内容
- 静态内容的执行关注的引用类型
- 不能修饰构造
- 构造方法只能通过new关键字调用,与静态内容的调用方式有冲突
修饰初始代码块-了解
初始代码块
- 是对构造共有内容的提炼,执行构造内容之前会先执行初始代码块内容
- 位置:与属性平级
class 类名{
{
//初始代码块
}
}
- 执行:在创建对象时执行,可以执行多次
静态初始代码块
class 类名{
static{
//静态初始代码块
}
}
- 执行:在类加载的时候执行,只会执行一次
- 静态方法的使用要求与其一致
final-最终
修饰属性
- 变成常量属性,值不可改
使用
- 没有默认值
- 为了保证空间分配,必须直接赋值
- 声明的同时直接赋值
- 每个构造中赋值
class ClassA{
//常量属性
final int NUMBER=100;
final int NUMBER2;
{
//将共有赋值语句提炼到初始代码块
NUMBER2=10;
//NUMBER=200; 错误
}
public ClassA(){
}
public ClassA(int n){
//NUMBER2=10;
}
}
修饰方法
- 可以被继承,不可被重写
修饰类
- 会变成断子绝孙类,不可被继承
修饰局部变量:值不可改。只需确保第一次赋值之后后续未做更改即可
修饰引用:堆地址不可改,对象内容可改
今日重点
- 抽象类和抽象方法的关系
- 抽象类中可以存在非抽象方法
- 抽象方法必须存在与抽象类
- 静态属性的特点
- 静态方法的使用
- final都能修饰什么及修饰后的特点
abstract不能与private、static、final联用
private、static、final之间可以任意结合
idea使用流程:建项目-->建包-->建类
描述性的类(实体类):com.xxx.entity
测试类:com.xxx.test
接口:com.xxx.dao
接口实现类:com.xxx.dao.impl
Day12-接口
概念
从功能上看,实现接口就意味着扩展了某些功能
从规则上看,是接口定义者和实现者都需要遵守的某种规则
从抽象上看,接口是特殊的抽象类
语法
interface 接口名{
}
使用
- 属性必须为公开静态常量(public static final)
- 由于修饰符确定,所以可以省略或缺失,编译器会自动补全
- 方法必须为公开抽象方法(public abstract)
- 没有构造方法
- 无法实例化对象
实现类
class 实现类名 implements 接口名{
}
规则
-
实现类必须重新接口中的所有抽象方法,除非实现类是抽象类
-
一个实现类可以实现多个接口,一个接口也可以拥有多个实现类(多实现)
class 实现类名 implements 接口名1,接口名2,..{ }
-
需要对所有实现的接口中的所有抽象方法都提供方法实现
-
接口可以正常参与多态(推荐)
接口名 引用名=new 实现类名();
-
只能访问接口中声明的内容
-
需要访问实现类独有内容时,可以进行类型强转
实现类名 引用名=(实现类名)接口引用名;
-
package com.bz.dao;
public interface IA {
final int N=100;
// N=200;
public abstract void ma();
public void mb();
void mc();
}
package com.bz.dao;
public interface IB {
void m1();
}
package com.bz.dao.impl;
import com.bz.dao.IA;
import com.bz.dao.IB;
public class IAImpl implements IA, IB {
public void ma() {
System.out.println("iaimpl中的ma");
}
public void mb() {
System.out.println("iaimpl中的mb");
}
public void mc() {
System.out.println("iaimpl中的mc");
}
@Override
public void m1() {
System.out.println("这是实现IB接口中的m1");
}
}
package com.bz.test;
import com.bz.dao.IA;
import com.bz.dao.impl.IAImpl;
public class Test {
public static void main(String[] args) {
//利用多态创建IAImpl实现类对象
IA ia=new IAImpl();
ia.ma();
ia.mb();
ia.mc();
//ia.m1();
//将ia强转为实现类引用
IAImpl impl=(IAImpl) ia;
impl.m1();
}
}
补充:
一个类可以同时继承父类和实现接口
class 类名 extends 父类类名 implements 接口名{}
接口间的继承
一个子接口可以继承多个父接口
public interface 接口名 extends 父接口名1,父接口名2,..{
}
- 子接口可以继承拥有所有父接口能被继承的内容
接口和抽象类的区别
抽象类 | 接口 | |
---|---|---|
关键字 | abstract class | interface |
属性 | 无固定要求 | 公开静态常量 |
方法 | 无固定要求,可以有非抽象方法 | 公开抽象方法 |
构造 | 有 | 没有 |
继承性 | 单继承 | 多继承 |
接口高级
高版本中的接口
JDK8.0
-
公开的默认方法
public default 返回值类型 方法名(参数列表){ }
- 当父类内容与接口内容出现冲突时,优先执行父类内容(类优先原则)
- 当接口之间的方法出现冲突时,实现类必须对该方法提供重写,使用自身重写内容
-
公开的静态方法
public static 返回值类型 方法名(参数列表){ }
- 可以直接通过接口名.方法名(实参)的方式访问接口中的静态方法
JDK9.0
私有方法
private 返回值类型 方法名(参数列表){
}
接口回调
- 将参数声明为接口类型,实参可以传入的不同的实现类对象
"开闭原则":扩展开放,修改关闭
允许扩展功能需求,但是前提是不能修改已有的代码内容,接口回调就是符合开闭原则的
比较器案例
-
创建比较器实现类,书写排序规则
public class 实现类名 implements Comparator<被排序的类名> { @Override public int compare(被排序的类名 o1, 被排序的类名 o2) { //书写排序规则 return 值; } }
-
重写compare方法,书写排序规则
- 从小到大:
- o1的值>o2的值,返回正数
- o1的值<o2的值,返回负数
- 从大到小:
- o1的值>o2的值,返回负数
- o1的值<o2的值,返回正数
- 相等返回0
package com.bz.dao.impl; import com.bz.entity.Student; import java.util.Comparator; public class StudentCompare implements Comparator<Student> { @Override public int compare(Student o1, Student o2) { //根据学生年龄从小到大 if (o1.getAge() > o2.getAge()) { return 1; } else if (o1.getAge() < o2.getAge()) { return -1; } else { return 0; } } }
- 从小到大:
-
调用sort(),传入比较器实现类对象
- Arrays.sort(数组名,比较器实现类对象);
package com.bz.test; import com.bz.dao.impl.StudentCompare; import com.bz.dao.impl.Student_score_Compare; import com.bz.entity.Student; import java.util.Arrays; public class StudentTest { public static void main(String[] args) { //创建学生对象数组 Student[] ss = {new Student("zhangsan", 22, 90), new Student("lisi", 20, 88), new Student("wamgwu", 21, 99)}; //根据学生年龄从小到大的排序 Arrays.sort(ss,new StudentCompare()); for (int i = 0; i < ss.length; i++) { System.out.println("学生姓名:"+ss[i].getName()+" 学生年龄:"+ss[i].getAge()+" 学生成绩:"+ss[i].getScore()); } } }
接口的好处
- 优化代码结构
- 提升代码扩展性
- 对代码解耦合
今日重点
- 接口的使用规则
- 实现类的使用规则
- 接口和抽象类的区别
- 什么是多继承、多实现、类优先
Day13-内部类
- 概念:在类的内部再次声明定义类
作用
打破封装,又不破坏封装
分类
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类(掌握)
成员内部类
- 位置:类的内部,方法的外部,与外部类属性和方法平级
class 外部类类名{
class 内部类类名{
}
}
使用
-
不能定义静态内容,但是可以访问外部类静态内容
-
当内部类局部变量、内部类属性、外部类属性重名时:
- 局部变量:变量名
- 内部类属性:this.属性名
- 外部类属性:外部类类名.this.属性名
-
创建对象依赖于外部类对象
外部类类名.内部类类名 对象名=外部类对象名.new 内部类类名();
package com.bz.entity;
/**
* 外部类-成员内部类
*/
public class Outer1 {
static String s="这是外部类的静态属性";
String str = "这是外部类属性str";
public class Inner1{//成员内部类
//static int n; 报错
String str = "这是内部类属性str";
public void method(){
String str = "这是内部类局部变量str";
System.out.println(s);
System.out.println(str);//这是内部类局部变量str
System.out.println(this.str);//这是内部类属性str
System.out.println(Outer1.this.str);//这是外部类属性str
}
}
}
package com.bz.test;
import com.bz.entity.Outer1;
public class Test {
public static void main(String[] args) {
//创建外部类对象
Outer1 outer1 = new Outer1();
//创建成员内部类对象
Outer1.Inner1 inner1 = outer1.new Inner1();
//调用内部类中的方法method
inner1.method();
}
}
静态内部类
- 位置:与成员内部类相同
class 外部类类名{
static class 内部类类名{
}
}
使用
-
可以定义静态内容,无法访问外部类非静态内容
-
内部类和外部类的静态属性重名时,通过各自类名调用即可
-
可以通过外部类类名.内部类类名.静态内容的方式直接访问内部类静态内容
-
对象创建依赖于外部类类名:
外部类类名.内部类类名 对象名=new 外部类类名.内部类类名();
package com.bz.entity;
/**
* 外部类-静态内部类
*/
public class Outer2 {
static String str = "这是外部类属性str";
//静态内部类
static public class Inner2{
static String s = "这是内部类静态属性s";
String s2 = "这是内部类非静态属性s2";
static String str = "这是内部类属性str";
public static void method(){
String str = "这是内部类局部变量str";
System.out.println(str);
System.out.println(Inner2.str);
System.out.println(Outer2.str);
}
public void method2(){
System.out.println("这是内部类非静态方法");
}
}
}
package com.bz.test;
import com.bz.entity.Outer1;
import com.bz.entity.Outer2;
public class Test {
public static void main(String[] args) {
//直接访问inner2内部的静态方法
Outer2.Inner2.method();
//创建静态内部类对象
Outer2.Inner2 inner2 = new Outer2.Inner2();
inner2.method2();
}
}
局部内部类
- 位置:写在外部类方法内部,与外部类局部变量平级
使用
- 作用范围:定义行开始,直属代码块结束
- 不能定义静态内容,可以访问外部类静态内容
- 只能访问外部类的局部常量,不可访问局部变量
- JDK7.0之前:必须通过final修饰
- JDK7.0之后:未更改值的事实常量即可
- 只能在所属方法内部创建对象
package com.bz.entity;
/**
* 外部类-局部内部类
*/
public class Outer3 {
static String s = "这是外部类静态属性s";
public void method(){
String str = "外部类的局部变量";
// str = "这是在更改局部变量的值";
class Inner3{//局部内部类
public void ma(){
System.out.println(s);
System.out.println(str);
}
}
//创建局部内部类的对象
Inner3 inner3 = new Inner3();
//调用局部内部类对象的方法
inner3.ma();
}
}
package com.bz.test;
import com.bz.entity.Outer3;
public class Test {
public static void main(String[] args) {
//创建外部类对象
Outer3 outer3 = new Outer3();
outer3.method();
}
}
匿名内部类
- 作用:生成一个实现类对象或子类对象
父类类名|接口名 引用名=new 父类类名|接口名(){
//子类|实现类内容
};
使用
- 必须继承自一个父类或实现自一个接口
- 特点:将类的声明、方法的定义、对象的创建三合一
- 一个匿名内部类只能创建一个对象
- 存在一个默认的无参构造,无法显式声明构造
- {}中可以书写类的独有内容,但是引用无法调用,只能在本类中使用
package com.bz.dao;
public interface IA {
void ma();
}
package com.bz.test;
import com.bz.dao.IA;
import com.bz.dao.impl.IAImpl;
public class Test2 {
public static void main(String[] args) {
int n=10;
// n=20;
//利用匿名内部类创建一个IA接口的实现类对象
IA ia=new IA(){
@Override
public void ma() {
System.out.println("这是匿名实现类在实现方法ma");
System.out.println(n);
mb();
}
//声明独有方法mb
public void mb(){
System.out.println("这是匿名实现类的独有方法mb");
}
};
//接口引用=实现类对象();
ia.ma();
// ia.mb();
}
}
lambda表达式
- 产自JDK8.0,作用是生成一个实现类对象,可以简化部分匿名内部类
接口的分类:
- 标记式接口:内部未声明任何内容
- 常量式接口:内部只声明了常量属性,未定义方法
- 函数式接口:内部只有一个需要重写的方法
- 普通接口:内部有多个需要重写的方法
- lambda表达式只能作用于函数式接口
语法
(形参列表)->{方法体}
结合引用:
接口名 引用名=(形参列表)->{方法体};
简化标准
- 小括号内数据类型可省(要省都省)
- 当参数只有一个时,小括号可省
- 当操作语句只有一条时,大括号可省
- 当操作语句只有一条并且为return语句时,return和大括号都可省(要省都省)
package com.bz.dao;
public interface IA {
/**
* 输出hello world
*/
void ma();
}
public interface IB {
/**
* 判断参数是否为偶数并输出
*/
void mb(int n);
}
public interface IC {
/**
* 计算参数之和并返回
* @param a
* @param b
* @return 和的结果
*/
int mc(int a,int b);
}
package com.bz.test;
import com.bz.dao.IA;
import com.bz.dao.IB;
import com.bz.dao.IC;
public class Test3 {
public static void main(String[] args) {
//利用匿名内部类创建IA实现类对象
IA ia=new IA() {
@Override
public void ma() {
System.out.println("ia:helloworld");
}
};
ia.ma();
//利用lambda表达式创建IA实现类对象
// IA ia2=()->{ System.out.println("ia2:helloworld");};
//简化后
IA ia2=()->System.out.println("ia2:helloworld");
ia2.ma();
//利用匿名内部类创建IB实现类对象
IB ib=new IB() {
@Override
public void mb(int n) {
System.out.println(n % 2 == 0 ? "偶数" : "奇数");
}
};
//利用lambda创建IB实现类对象
// IB ib2=(int n)->{
// System.out.println(n % 2 == 0 ? "偶数" : "奇数");
// };
//简化后
IB ib2=n -> System.out.println(n % 2 == 0 ? "偶数" : "奇数");
ib2.mb(10);
//利用匿名内部类创建IC实现类对象
IC ic=new IC() {
@Override
public int mc(int a, int b) {
return a+b;
}
};
//利用lambda创建IC实现类对象
// IC ic2=(int a,int b)->{return a+b;};
//简化后
IC ic2=(a,b)->a+b;
System.out.println(ic2.mc(1,1));
}
}
今日重点
- 内部类的分类
- 匿名内部类的语法
- lambda的语法及简化标准
Day14-常用类
Object
- 祖宗类,最大父类
- 所有类都直接或者间接的继承自Object
- 该类中存放着所有对象都应该拥有的方法,可以根据需求对部分方法提供重写
常用方法
-
Class getClass():获取引用的实际对象类型
package com.bz.test; import com.bz.entity.Person; import com.bz.entity.Student; import com.bz.entity.Teacher; public class Test { public static void main(String[] args) { //利用多态创建学生对象 Person p = new Student(); //利用多态创建一个老师对象 Person p2 = new Teacher(); Student stu = new Student(); //获取实际对象类型 System.out.println(p.getClass()); //判断p和p2的实际对象类型是否相同 System.out.println(p.getClass() == p2.getClass());//f System.out.println(p.getClass() == stu.getClass());//t } }
-
int hashCode():用来获取对象的哈希码值
- 重写原因:该方法默认根据对象堆地址获取哈希码值,但是在某些需求场景下,我们需要关注的是对象内容,内容相同则哈希码值相同,所以需要重写
- 方法签名:public int hashCode()
- 重写规则:
- 整数类型:直接相加(long类型需要强转为int)
- double类型:强转为int后相加
- 类库中的引用类型(如String):调用属性名.hashCode()相加
- 自定义引用类型:也重写其hashCode方法,然后调用属性名.hashCode()相加
package com.bz.entity; public class Student extends Person{ private String name; private int age; private double score; //老师对象属性 private Teacher tea; //重写hashCode public int hashCode(){ return age+name.hashCode()+(int)score+tea.hashCode(); } //省略getter、setter、构造 }
package com.bz.entity; public class Teacher extends Person{ private String name; private String className; //重写hashCode public int hashCode(){ return name.hashCode() + className.hashCode(); } //省略getter、setter、构造 }
-
boolean equals(Object):判断当前对象与参数对象是否相同
-
重写原因:该方法默认比较双方堆地址,但是在某些需求场景下,需要比较双方内容,所以需要重写
-
方法签名:public boolean equals(Object o)
-
重写规则:
//重写equals方法:用this当前对象和参数o进行比较 public boolean equals(Object o){ //自反性 if (this == o) { return true;//自己和自己比,一定相同 } //空值判断 if (o == null) { return false;//与null值比,一定不同 } //类型比较 if (this.getClass() != o.getClass()) { return false;//实际对象类型不同,一定不同 } //类型强转:将参数Object类型强转为当前类型 Student stu=(Student) o; //属性值比较 if (this.name.equals(stu.name) && this.age==stu.age && this.score==stu.score){ return true; } return false; }
==:基本类型比较值,引用类型比较地址
引用类型比较值:调用equals方法
- 如果为自定义引用类型,则也需要重写equals才能比较值
-
-
String toString():获取对象的详细信息
- 特点:直接使用引用名时会默认调用
- 重写原因:该方法默认获取对象地址,但是实际开发中,获取对象具体属性信息更常用,所以需要重写
- 方法签名:public String toString()
- 重写规则:根据需求拼接字符串返回即可
public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + ", score=" + score + ", tea=" + tea + '}'; }
-
void finalize():用来进行垃圾回收
- 垃圾回收机制:当内存满到不足以支撑新对象的创建时,虚拟机会自动调用垃圾对象的finalize方法对其进行回收销毁,以释放空间
- 垃圾对象的判断标准:无任何引用指向的对象(零引用机制)
- 手动垃圾回收:借助垃圾回收器(GC),在代码中调用System.gc()完成手动垃圾回收
包装类
- 作用:将八大基本类型包装为对应的引用类型,目的为其可以存放null值
byte | short | int | long | float | double | char | boolean |
---|---|---|---|---|---|---|---|
Byte | Short | Integer | Long | Float | Double | Character | Boolean |
基本类型与包装类型
基转包
-
构造
包装类名 引用名=new 包装类名(基本类型);
-
valueOf
包装类名 引用名=包装类名.valueOf(基本类型);
包转基
- xxxValue
基本类型 变量名=包装类型对象名.xxxValue();
注:xxx对应的是基本类型
//基转包
int n1 = 10;
Integer i1 = new Integer(n1);
Integer i2 = Integer.valueOf(n1);
//包转基
int n2 = i1.intValue();
//自动转换
Integer i3 = n1;//自动拆箱
int n3 = i2;//自动封箱
- JDK5.0之后,官方提供了自动拆箱和封箱
- 拆箱:包转基
- 封箱:基转包
基本类型与String
基转String
-
字符串拼接
String 引用名=变量名+""; String 引用名=""+变量名;
-
valueOf
String 引用名=String.valueOf(基本类型);
String转基(重点)
-
parseXxx
基本类型 变量名=对应包装类名.parseXxx(String类型); 注:Xxx为对应基本类型,首字母大写
//基本类型转String
int a=10;
String s1 = a + "";
String s2 = "" + a;
String s3 = String.valueOf(a);
//String转基
int a2 = Integer.parseInt(s1);
包装类型与String
包转String
-
字符串拼接
String 引用名=包装类型+""; String 引用名=""+包装类型;
-
toString
String 引用名=包装类型对象名.toString();
String转包
- 与基本类型转包装类型方法一致
//包装类型转String
Integer integer1 = 100;
String str1 = integer1 + "";
String str2 = "" + integer1;
String str3 = integer1.toString();
//String转包装类型
Integer integer2 = new Integer(str1);
Integer integer3 = Integer.valueOf(str1);
String类型转为基本类型或包装类型时,需要保证字符串内容为对应类型能够接收的内容,否则会在运行时报出数据类型转换异常:java.lang.NumberFormatException
String str = "10"; //转成int类型 int n = Integer.parseInt(str); 错误! //转为Integer Integer i = new Integer(str); 错误!
整数缓冲区
- 官方认定,-128至127是开发过程中最常用的256个数字,所以在方法区中设立了整数缓冲区,缓冲区中默认存放这256个数字,当包装类型使用的数字在该区间之内,则直接从区中取出,不再开辟空间,目的为减少空间浪费。
package com.bz.test;
public class Test3 {
public static void main(String[] args) {
Integer i1 = 200;
Integer i2 = 200;
System.out.println(i1 == i2);//f
Integer i3=100;
Integer i4=100;
System.out.println(i3 == i4);//t
Integer i5 = new Integer(127);
Integer i6 = new Integer(127);
System.out.println(i5 == i6);//f
Integer i7 = 127;//缓冲区地址,没有堆地址
Integer i8 = new Integer(127);//拥有堆地址
System.out.println(i7 == i8);//f
}
}
String
- 内存中常量:在内存中一旦赋值,则空间中的值不可改
package com.bz.test;
public class StringTest {
public static void main(String[] args) {
String s1 = "abc";
String s2 = s1;
System.out.println("s1: "+s1);//abc
System.out.println("s2: "+s2);//abc
System.out.println(s1 == s2);//t
s2 = "edf";
System.out.println("s1: "+s1);//abc
System.out.println("s2: "+s2);//edf
System.out.println(s1 == s2);//f
}
}
串池
全称字符串常量池。实际开发中,字符串是使用频率最高的数据类型,并且其复用率也很高,为了避免相同字符串内容重复创建带来的内存浪费,所以在方法区中设立了串池,目的为减轻内存压力。
创建
-
第一种方式
String 引用名="值";
-
第二种方式
String 引用名=new String("值");
区别
第一种创建方式:优先使用串池,串池中不存在指定内容时,则在串池中创建然后指向,如果存在指定内容,则直接指向
第二种创建方式:无论如何都会开辟对象堆地址,查找串池中是否存在指定内容,如果存在,则存放对应串池地址,如果不存在,则先在串池中创建内容,然后存放对应串池地址。
package com.bz.test;
public class StringTest {
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
System.out.println("s1: "+s1);
System.out.println("s2: "+s2);
System.out.println(s1 == s2);//t
String str1 = new String("edf");
String str2 = new String("edf");
System.out.println(str1 == str2);//f
String string1 = "qwe";
String string2 = new String("qwe");
System.out.println(string1 == string2);//f
}
}
可变长字符串
- StringBuffer:JDK1.0 线程安全,效率低
- StringBuilder:JDK5.0 线程不安全,效率高
特点
- 必须通过构造创建
- 字符串操作必须调用对应方法完成
package com.bz.test;
public class StringTest2 {
public static void main(String[] args) {
StringBuilder sb1 = new StringBuilder("abc");
StringBuilder sb2 = sb1;
sb2.append("edf");
//sb2 += "qwe";
System.out.println("sb1: "+sb1);//abcedf
System.out.println("sb2: "+sb2);//abcedf
System.out.println(sb1 == sb2);//t
// StringBuilder sb3 = "edf";
//拼接所有大写字母
StringBuilder sb = new StringBuilder();
for (char c = 65; c <= 90; c++) {
//将当前遍历字符拼接进可变长字符串
sb.append(c);
}
System.out.println(sb);
}
}
实际开发中,对字符串内容的复用率远远高于对其的更改频率,所以能够使用串池的String使用频率会远高于StringBuilder。并且String对字符串内容的操作要更方便
常用方法
- 引用名.方法名(实参)
- char charAt(下标):获取指定下标位置的字符
- boolean contains(字符串):判断是否包含指定内容
- boolean startsWith(字符串):判断是否以指定内容开头
- boolean endsWith(字符串):判断是否以指定内容结尾
- boolean equals(字符串):判断与指定内容是否相同,区分大小写
- boolean equalsIgnoreCase(字符串):判断与指定内容是否相同,不区分大小写
- byte[] getBytes():以byte数组的方式返回字符串的每个部分
- char[] toCharArray():以char数组的方式返回字符串的每个部分
- 下标 indexOf(字符串):获取指定内容第一次出现的下标,不存在返回-1
- 下标 lastIndexOf(字符串):获取指定内容最后一次出现的下标,不存在返回-1
- boolean isEmpty():判断字符串内容是否为空,不能判比null值
- int length():获取字符串长度
- String replace(旧字符串,新字符串):用新字符串内容替换旧字符串内容,替换所有匹配内容
- String[] split(分割符):对字符串根据分隔符进行分割,不保留分割符
- String substring(开始下标):将字符串从开始下标截取至末尾
- String substring(开始下标,结束下标):将字符串从开始下标截取至结束下标前一位
- String toLowerCase():转全小写
- String toUpperCase():转全大写
- String trim():去除字符串的前后空格
今日重点这是
- getClass()的作用(面试点:instanceof关键字和getClass()的区别)
- equals的重写步骤
- 面试点:什么是垃圾回收机制及垃圾对象的判断标准
- 自动封箱和拆箱的概念
- String转基本类型的语法
- 面试点:什么是整数缓冲区
- 面试点:什么是串池
- String两种创建方式及区别
- 面试点:String、StringBuffer、StringBuilder的区别
Day15-List集合
集合的概念
是一个容器,作用为存储多个数据,通常情况下用来替代数组
集合的特点
- 只能存放Object(引用)类型的数据
- 所有集合都产自java.util包
List、Set、Map都是接口
List的存储特点
有序、有下标、元素可以重复
常用实现类
- ArrayList(更常用)
- JDK1.2 底层数组实现 查询快,增删慢 线程不安全,效率高
- LinkedList(基本不用)
- JDK1.2 底层链表实现 增删快,查询慢 线程不安全,效率高
- Vector(压根不用)
- JDK1.0 底层数组实现 都慢 线程安全,效率低
创建
- 建议使用多态
List 集合名=new 实现类名();
常用方法
- 集合名.方法名()
- boolean add(值):将元素添加至集合末尾
- void add(下标,值):将元素添加至指定下标位置
- boolean addAll(集合名):将参数集合的所有元素添加至当前集合末尾
- void addAll(下标,集合名):将参数集合的所有元素添加至当前集合指定下标位置
- boolean contains(值):判断集合是否包含指定元素
- boolean containsAll(集合名):判断当前集合中是否包含参数集合的所有元素
- 值 get(下标):获取指定下标位置的元素
- 下标 indexOf(值):获取元素第一次出现的下标,不存在返回-1
- 下标 lastIndexOf(值):获取元素最后一次出现的下标,不存在返回-1
- 被删除的值 remove(下标):删除指定下标位置的元素
- boolean remove(值):删除第一个匹配元素
- 如果元素值是整数时,编译器会优先认定其为下标
- 旧值 set(下标,新值):把指定下标位置的值替换为新值
- int size():获取集合长度
遍历
-
for循环:下标遍历+get()
for(int i=0;i<集合名.size();i++){ //可以通过集合名.get(i)获取当前集合元素 }
-
迭代器遍历-Iterator
- 迭代器对象.hasNext():判断是否存在下一元素
- 迭代器对象.next():使指针后移一位,获取下一元素
- 集合名.iterator():获取当前集合的迭代器对象
//1.获取当前集合的迭代器 Iterator it= list.iterator(); //2.操作迭代器 while(it.hasNext()){//循环条件:拥有下一元素 //指针后移,获取下一元素 System.out.print(it.next()+" "); }
- 由于指针操作走向固定,无法在遍历过程中增删元素
- 每次执行next方法都会使指针后移,所以应避免一次循环中多次调用next()
-
外遍历forEach-JDK5.0
for(数据类型 元素名:集合名){ //元素名随便起,代表正在被遍历的元素 }
for(Object o:list){ System.out.print(o+" "); }
- 底层实现也是迭代器遍历,所以遍历过程中仍不可增删元素
-
自遍历forEach-JDK8.0
- 集合名.forEach(Consumer接口实现类对象)
//自遍历forEach list.forEach(new Consumer() { @Override public void accept(Object o) { System.out.print(o+" "); } }); //lambda表达式简化 list.forEach( o->System.out.print(o+" ") );
- 不可在过程中增删元素
- 当前匿名内部类为局部内部类,无法访问外部的局部变量,所以不能在其内部进行累加操作
泛型
- 作用:约束集合中存放的数据类型
List<泛型> 集合名=new 实现类名<泛型>();
使用
-
必须为引用类型
-
泛型可以参与多态
如: List<Animal> list2 = new ArrayList<Animal>(); list2.add(new Dog()); list2.add(new Cat());
-
左侧泛型声明不可省略,否则泛型无效
-
右侧泛型省略会导致语法不规范,可以使用<>进行占位
如: List<Integer> list = new ArrayList<>();
-
一个集合只能声明一种泛型
今日重点
- 集合的特点
- List的存储特点
- List的常用实现类及特点
- List的遍历方式
- 泛型的作用
Day16-Set集合
Collection
- 所有集合都是由Collection或Map派生
- Collection是List和Set的父接口
特点
- 存放List和Set的共有内容
- 没有直接实现类
Set的存储特点
无序、无下标、元素不可重复
Set的常用实现类
- HashSet
- JDK1.2 底层哈希表(数组+链表)实现 线程不安全,效率高
- LinkedHashSet
- JDK1.2 是HashSet的子类,底层哈希表实现 线程不安全,效率高
- TreeSet
- JDK1.2 底层红黑树实现,是SortedSet的实现类 线程不安全,效率高
Set的常用方法
- 全部来自于Collection父接口,没有独有方法
Set的创建
- 建议使用多态
Set<泛型> 集合名=new 实现类名<>();
Set的遍历
- 迭代器遍历
- 外遍历forEach
- 自遍历forEach
package com.bz.test;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Test {
public static void main(String[] args) {
//创建一个存放整型的set集合
Set<Integer> set = new HashSet<>();
set.add(10);
set.add(22);
set.add(40);
set.add(88);
set.add(88);
//迭代器遍历
//获取集合的迭代器
Iterator<Integer> it = set.iterator();
while (it.hasNext()) {//判断是否存在下一元素
//指针后移,获取下一元素
System.out.print(it.next()+" ");;
}
System.out.println();
//外遍历
for (Integer i : set) {
System.out.print(i+" ");
}
System.out.println();
//自遍历
set.forEach(i-> System.out.print(i+" "));
}
}
哈希表的去重原理
先调用元素的hashCode()获取元素的哈希码值
通过哈希码值%数组长度得到存放下标
如果下标位置未存有元素,则直接存放
如果存有元素,则调用equals()与下标位置的所有元素进行值的比较
都不相同,继续链表存放
如有相同,则舍弃当前添加元素
使用
-
如果存放的为自定义类型,则必须重写hashCode和equals方法才能保证哈希表的去重
-
LinkedHashSet可以保证元素存入和取出的顺序一致
-
TreeSet可以对元素进行从小到大的默认排序
-
如果存放的为自定义类型,则必须书写排序规则:
-
实现Comparable接口,重写compareTo方法
- 原理:让当前对象this和参数对象o进行比较
- 实现:想对谁排序,就让谁实现
- 返回值规则:
- 从小到大:this的值>o的值,返回正数,this的值<o的值,返回负数
- 从大到小:this的值>o的值,返回负数,this的值<o的值,返回正数
- 相等返回0
package com.bz.entity; public class Student implements Comparable<Student>{ private String name; private int age; private double score; //省略getter、setter、构造 @Override public int compareTo(Student o) { //根据成绩从大到小排序 if (this.score > o.score) { return -1; } else if (this.score < o.score) { return 1; } else { return 0; } } }
-
实现Comparator接口,重写compare方法
- 原理:让参数o1和o2进行比较
- 实现:在集合创建处的小括号内传入该接口实现类对象即可
package com.bz.test; import com.bz.entity.Student; import java.util.Comparator; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; public class StudentTest { public static void main(String[] args) { //Set<Student> set = new HashSet<>(); Set<Student> set = new TreeSet<>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { //根据成绩小到大 if (o1.getScore() > o2.getScore()) { return 1; } else if (o1.getScore() < o2.getScore()) { return -1; }else{ return 0; } } }); Student stu1 = new Student("zhangsan", 20, 88); Student stu2 = new Student("zhangsan", 20, 88.5); Student stu3 = new Student("lisi", 24, 98); Student stu4 = new Student("wangwu", 21, 78); /*System.out.println("stu1::"+stu1.hashCode()); System.out.println("stu2::"+stu2.hashCode()); */ set.add(stu1); set.add(stu2); set.add(stu3); set.add(stu4); set.forEach(stu-> System.out.println(stu)); } }
- 比较器Comparator的优先级更高
- 更推荐使用比较器
-
- 不会破坏类的单一职责
- 对不同集合的排序提供更多的可能性
-
返回值简写:
-
当排序规则只有一条并且判比的为整型时:
- 从小到大:o1的值-o2的值
- 从大到小:o2的值-o1的值
Set<Student> set = new TreeSet<>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { //根据年龄从小到大 return o1.getAge()-o2.getAge(); //从小到大 } }); //lambda简化: Set<Student> set=new TreeSet<>((o1,o2)->o1.getAge()-o2.getAge());
-
-
-
TreeSet的去重规则:当compareTo或compare方法返回值为0时去重
今日重点
- Collection的特点
- Set的存储特点
- Set的常用实现类及特点
- Set的遍历方式
- 哈希表的去重原理
- TreeSet自定义排序规则的两种方式
Day17-Map集合
存储特点
- 键值对存放数据
- 键(key-k):无序、无下标、元素不可重复
- 值(value-v):无序、无下标、元素可以重复
常用实现类
- HashMap
- JDK1.2 底层哈希表实现 线程不安全,效率高
- LinkedHashMap
- JDK1.2 是HashMap的子类,底层哈希表实现 线程不安全,效率高
- TreeMap
- JDK1.2 底层红黑树实现,是SortedMap的实现类 线程不安全,效率高
- Hashtable
- JDK1.0 底层哈希表实现 线程安全,效率低
- Properties
- JDK1.0 是Hashtable的子类,底层哈希表实现 线程安全,效率低
创建
- 建议使用多态
Map<键的泛型,值的泛型> 集合名=new 实现类名<>();
常用方法
- V put(K,V):将键值对添加至集合
- 如果键已经存在,则进行值的覆盖
- boolean containsKey(键):判断是否包含指定键
- boolean containsValue(值):判断是否包含指定值
- V get(K):根据键获取对应值,不存在返回null
- V remove(K):根据键移除整个键值对
- int size():获取集合长度
- 一个键值对是一个元素,长度为1
遍历
-
键遍历
- Set<键的泛型> keySet():获取所有的键放入Set集合返回
//创建一个 说明-具体值的Map集合 Map<String, String> map = new HashMap<>(); map.put("tel", "13333333333"); map.put("username", "zhangsan"); map.put("password", "123456"); map.put("password", "456789"); //键遍历 //先获取所有的键 Set<String> set1 = map.keySet(); //遍历所有的键 set1.forEach(k-> System.out.println("键:"+k+" 值:"+map.get(k))); //外遍历 for (String k : set1) { System.out.println("键:" + k + " 值:" + map.get(k)); }
-
值遍历
- Collection<值的泛型> values():获取所有的值放入Collection集合返回
//获取所有的值 Collection<String> coll = map.values(); //自遍历 coll.forEach(v-> System.out.println("值:"+v)); //外遍历 for (String v : coll) { System.out.println("值:"+v); }
-
键值对遍历
- Set<键值对对象> entrySet():获取所有的键值对对象放入Set集合返回
- Entry:Map接口的内部接口,表现形式为Map.Entry<K,V>
- K getKey():获取键值对对象中的键
- V getValue():获取键值对对象中的值
//获取所有的键值对对象 Set<Map.Entry<String, String>> set2 = map.entrySet(); //遍历所有的键值对对象 set2.forEach(entry-> System.out.println("键:"+entry.getKey()+" 值:"+entry.getValue())); //外遍历 for (Map.Entry<String, String> entry : set2) { System.out.println("键:" + entry.getKey() + " 值:" + entry.getValue()); }
-
自遍历
- forEach(BiConsumer实现类对象)
map.forEach(new BiConsumer<String, String>() { @Override public void accept(String k, String v) { System.out.println("键:"+k+" 值:"+v); } }); System.out.println("lambda简化:::::"); map.forEach((k,v)-> System.out.println("键:"+k+" 值:"+v));
使用
- 如果键为自定义类型,必须重写HashCode和equals方法才能保证哈希表的去重
- HashMap可以存放null值,键可以有0-1个null,值可以有0-多个null
- LinkedHashMap在HashMap的基础上可以保证元素存入和取出的顺序一致
- TreeMap可以根据键对元素进行默认的升序排序
- 如果键为自定义类型,则必须重写排序规则(两种方式与TreeSet一致)
- 键不能为null,会导致空指针
- Hashtable不能存放null值
- Properties只能存放String类型
- 不推荐使用多态
- 不能添加泛型
- setProperty(String K,String V):添加键值对,相当于put方法
- V getProperty(String K):根据键获取值,相当于get方法
- load(输入流对象):通过输入流将配置文件内容读取到集合中
今日重点
- Map的存储特点
- Map的常用实现类和特点
- Map的创建语法
- Map的遍历方式
Day18-异常
- 程序执行中不正常的情况
分类
- Throwable:总父类
- Error:错误
- 无法解决也无法提前避免
- 通常由硬件问题、内存问题等导致
- Exception:异常
- 可以解决或提前避免
- 通常由代码导致
- RuntimeException:运行时异常,也称为未检查异常、未检异常等
- 编译不报错,运行报错
- 特点:可以提前解决,也可以不解决
- java.lang.NullPointerException 空指针异常
- java.lang.StringIndexOutOfBoundsException 字符串下标越界异常
- java.lang.ArrayIndexOutOfBoundsException 数组下标越界异常
- java.lang.NumberFormatException 数据类型转换异常
- java.lang.ClassCastException 类型转换异常
- java.lang.ArithmeticException 数学运算异常
- ...
- 运行时异常都来自于java.lang包
- 非RuntimeException:非运行时异常,也称为已检查异常、已检异常等
- 编译报错
- 特点:必须处理
- 只要不是运行时异常,则一定是非运行时异常
异常的产生
-
自然(自动)产生:编译或运行到有问题的代码时,会自动产生对应异常
-
手动产生:
throw 异常对象;
- throw:作用为手动抛出一个异常对象,当程序编译或运行到该语句时,则抛出异常,强制终止
- 位置:方法内部
- 使用:在同一直属作用范围内,该语句下方不允许存在其他有效语句
- 无法执行
public static void main(String[] args) { System.out.println("main开始"); method(); System.out.println("main结束"); } public static void method(){ System.out.println("method开始"); //手动抛出一个运行时异常 throw new RuntimeException(); // System.out.println("method结束"); }
使程序停止执行的时机:
- 主函数内容执行结束
- 执行到异常(任何位置)
- 执行到return; (主函数内部)
异常的解决
-
throws上抛异常--消极
-
语法:
访问修饰符 返回值类型 方法名(参数列表)throws 异常类名1,异常类名2,.. { }
-
作用:将异常上抛至调用者,如果调用者都未处理,则会最终上抛至虚拟机,程序仍然会终止。
-
特点:只能暂时规避异常问题,无法彻底解决问题
-
使用:父类异常可以解决子类异常问题
-
-
try-catch块捕捉处理--积极
-
语法:
try{ //有可能发生异常的代码 }catch(有可能匹配的异常类名 引用名){ //处理方案的代码 }
-
作用:执行try中内容时,如果发生异常,则通过catch块尝试匹配,匹配成功则执行相应处理代码,程序继续向下执行,若匹配失败,则仍然会导致程序终止
-
特点:如果异常匹配成功,则可以真正解决异常
-
使用:
- 一个try-catch结构只能处理一个异常
- 一旦发生异常,就会开始执行catch尝试捕捉匹配,进入catch块之后try中剩余内容不会再执行
- 可以存在多个catch块
- catch之间自上而下的进行匹配,一个try-catch结构至多执行一个catch块
- 如果未有catch匹配成功,则程序报错终止
- 防止该情况发生,通常会在最下方catch一个Exception
- 父类异常的catch块必须写在子类异常的下方
- 无法捕捉未发生的非运行时异常,否则编译报错
- 处理异常信息的方法:
- String 异常对象.getMessage():获取异常的详细信息
- void 异常对象.printStackTrace():输出异常的追栈(追踪)信息(异常类型+详细信息+触发位置)
-
package com.bz.test;
import java.rmi.activation.ActivationException;
public class Test3 {
public static void main(String[] args) {
System.out.println("main开始");
try {
System.out.println("1");
System.out.println(10 / 0);
System.out.println("2");
} catch (NullPointerException e) {
System.out.println("发生了空指针异常");
} catch (ClassCastException e) {
System.out.println("发生了类型转换异常");
} catch (Exception e) {
System.out.println("发生了异常");
System.out.println("详细信息:"+e.getMessage());
System.out.println("追栈信息:");
e.printStackTrace();
}
System.out.println("main结束");
}
}
finally块
- 作用:通常用于资源关闭
- 特点:无论如何都会执行
try{
}catch(){
}finally{
//资源关闭的代码
}
- 使用:
- 不能单独存在
- 可以与try单独结合
- 当try-catch-finally中出现return语句冲突时,优先执行finally
自定义异常
-
自定义运行时异常:继承RuntimeException
package com.bz.exception; /** * 自定义运行时异常 */ public class MyRuntimeException extends RuntimeException{ public MyRuntimeException(){ } public MyRuntimeException(String message) { super(message); } }
- 有参构造:调用父类有参构造,最终为Throwable中的详细信息属性赋值
-
自定义非运行时异常:继承Exception
package com.bz.exception; /** * 自定义非运行时异常 */ public class MyException extends Exception { public MyException(){} public MyException(String message) { super(message); } }
今日重点
-
异常的分类
-
throw和throws的区别
- 位置不同
- 作用不同
-
try-catch-finally的使用
Day19-IO1
- 作用:将数据在本地和虚拟机之间进行传输
I:input 输入
O:output 输出
流
数据的传输的基本支持,相当于管道
分类
- 从传输方向上:
- 输入流:本地磁盘向JVM传递数据
- 输出流:JVM向本地磁盘传递数据
- 从传输单位上:
- 字节流:以字节为单位进行传输,可以传输任意类型的数据,如视频、图片、音频、文档等
- 字符流:以字符为单位进行传输,只能传输文本类型的数据,如.java、.txt、.html等
- 从功能上:
- 节点流:具有实际传输意义的流
- 过滤流:没有传输意义,作用为给节点流增强传输能力或者添加附加功能
字节流
- InputStream 字节输入流-抽象父类
- 子类-节点流:FileInputStream 文件字节输入节点流
- OutputStream 字节输出流-抽象父类
- 子类-节点流:FileOutputStream 文件字节输出节点流
输入流
创建
FileInputStream fis=new FileInputStream("文件路径");
-
绝对路径:以电脑磁盘为基点的完整路径
FileInputStream fis=new FileInputStream("F:\\code\\a.txt"); FileInputStream fis=new FileInputStream("F:/code/a.txt");
-
相对路径:以项目路径为基点的路径
FileInputStream fis = new FileInputStream("file\\b.txt"); FileInputStream fis = new FileInputStream("file/b.txt");
- 当路径不以盘符开头时,会默认填充当前项目路径
- 文件必须在当前项目路径下
-
路径不存在,则报错
-
路径必须截止至文件
常用方法
- void close():关闭流链接,释放相关资源(每个流都拥有该方法)
- int read():读取一个字节,返回其对应整数值。读取到达末尾,返回-1
- int read(byte[] ):尝试读取一个数组长度的数据存入数组中,返回实际读取个数。读取到达末尾,返回-1
//输入流:将文件中的内容输入到JVM中
FileInputStream fis = new FileInputStream("file/b.txt");
//读取一个字节
System.out.println(fis.read());//65
System.out.println(fis.read());//66
System.out.println(fis.read());//67
//读取文件所有内容
while (true) {
//接收本次读取结果
int n = fis.read();
//判断读取是否到达末尾
if (n == -1) {
break;
}
//正常操作本次读取内容
System.out.println((char) n);
}
//尝试读取一个数组长度
byte[] bs = new byte[10];
//尝试读取5个字节存放到bs数组中,返回实际读取个数
System.out.println("实际读取个数为:"+fis.read(bs));
//遍历bs数组,查看本次读取结果
for (byte b : bs) {
System.out.print( b+" ");
}
//读取文件所有内容
while(true){
//创建用来接收的数组
byte[] bs = new byte[3];
//接收本次读取结果
int n = fis.read(bs);
//判断读取是否到达末尾
if (n == -1) {
break;
}
//操作本次读取内容
for (byte b : bs) {
System.out.print(b+" ");
}
}
输出流
FileOutputStream fos=new FileOutputStream("文件路径");
FileOutputStream fos=new FileOutputStream("文件路径",true|false);
- 文件不存在,会自动创建
- true为追加,false为覆盖
- 如果未做第二个参数的传参,则默认为false
常用方法
- void flush():强制刷新缓冲区(每个输出流都拥有该方法)
- void write(int ):向目标文件写入一个字节
- void write(byte[] ):向目标文件写入一个数组中的数据
package com.bz.test;
import java.io.FileOutputStream;
/**
* 字节输出流
*/
public class Test2 {
public static void main(String[] args) throws Exception{
FileOutputStream fos = new FileOutputStream("file/c.txt",true);
//一次写入一个字节
fos.write(65);
fos.write(66);
fos.write(67);
//一次写入一个数组
String s = "abcdefg";
byte[] bs = s.getBytes();
//将数组内容写入文件
fos.write(bs);
System.out.println("无异常!");
}
}
标准化处理异常
- JDK7.0,发布了自动关流的语法结构try-with-sources
try(
流对象的创建语句
){
其他的操作语句
}catch(...){
...
}
- 原理:JDK7.0之后,所有的流都默认实现了AutoCloseable接口,该接口中提供了自动关流所需的close方法
package com.bz.test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* 标准异常处理
*/
public class Test3_2 {
public static void main(String[] args) {
try(
//创建一个输入流
FileInputStream fis =new FileInputStream("file/b.txt");
) {
//循环读取文件所有内容
while (true) {
//接收本次读取内容
int n = fis.read();
//判断是否读取到达末尾
if (n == -1) {
break;
}
//处理读取结果
System.out.println(n);
}
System.out.println("操作成功!");
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读取失败!");
} catch (Exception e) {
System.out.println("未知异常");
e.printStackTrace();
}
}
}
文件复制
- 原理:借助JVM,先将文件A中的数据读取到JVM,再将读取内容写入到文件B
- 先读后写
package com.bz.test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestFileCopy {
public static void main(String[] args) {
//copy1();
copy2();
}
/**
* 一次复制一个字节
* 将c.txt复制到newc.txt
*/
public static void copy1(){
try (
//创建输出流-被复制到的文件路径
FileOutputStream fos=new FileOutputStream("file/newc.txt");
//创建输入流-复制的文件路径
FileInputStream fis=new FileInputStream("file/c.txt")
) {
while(true){
//接收本次读取内容
int n = fis.read();
//判断读取是否到达末尾
if (n == -1) {
break;
}
//将本次读取字节写入到目标文件
fos.write(n);
}
System.out.println("复制成功!");
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
}
/**
* 一次复制一个字节数组
* 将c.txt复制到newc.txt
*/
public static void copy2(){
try (
//创建输出流-被复制到的文件路径
FileOutputStream fos=new FileOutputStream("file/newc2.txt");
//创建输入流-复制的文件路径
FileInputStream fis=new FileInputStream("file/c.txt")
) {
while(true){
//创建接收数组
byte[] bs = new byte[1024];
//读取本次内容并接收
int n = fis.read(bs);
if (n == -1) {
break;
}
//将本次读取内容写入到目标文件
fos.write(bs);
}
System.out.println("复制成功!");
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
}
}
缓冲过滤流
- 作用:内置数据缓冲区,当进行文件读写时,会先将数据放入至缓冲区中,在需要时统一取出,以此降低数据在文件之间传输的频率,从而提高传输效率
输入流:BufferedInputStream
输出流:BufferedOutputStream
创建
- 基于节点流
BufferedInputStream bis=new BufferedInputStream(fis对象);
BufferedOutputStream bos=new BufferedOutputStream(fos对象);
/**
* 一次复制一个字节+缓冲过滤流
*/
public static void copy3(){
long l1 = System.nanoTime();
try (
//创建输出流-被复制到的文件路径
FileOutputStream fos=new FileOutputStream("E:\\2304班一阶段课上录屏\\file2.wmv");
//创建输入流-复制的文件路径
FileInputStream fis=new FileInputStream("E:\\2304班一阶段课上录屏\\day19-7-作业1讲解.wmv");
//添加缓冲过滤流
BufferedOutputStream bos = new BufferedOutputStream(fos);
BufferedInputStream bis = new BufferedInputStream(fis)
) {
while(true){
//接收本次读取内容
int n = bis.read();
//判断读取是否到达末尾
if (n == -1) {
break;
}
//将本次读取字节写入到目标文件
bos.write(n);
}
System.out.println("复制成功!");
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
long l2 = System.nanoTime();
System.out.println("一次复制一个字节+缓冲过滤流:"+(l2-l1)/1E9);
}
数组复制会在复制过程中大量占用虚拟机内存,并且最后以此复制极有可能浪费空间,所以实际开发中,最常用的是字节复制结合缓冲过滤流,在保证内存不会被占用和浪费的基础上大幅度提高效率s
使用
当对文件进行先写后读操作时,写入完成后需要刷新缓冲区将数据取出放入文件,才能进行后续的读取操作
- bos.close():关流之前会自动刷新缓冲区
- 不推荐,有可能影响流的后续使用
- 关流时只需关闭外层过滤流,内层节点流会一并关闭
- bos.flush():手动强制刷新缓冲区(推荐)
package com.bz.test;
import java.io.*;
public class TestBuffered {
public static void main(String[] args) {
//往文件c.txt中写入一个hello world,再将内容从c.txt中读取到控制台
try (
//先创建节点流对象
FileOutputStream fos = new FileOutputStream("file/c.txt");
FileInputStream fis = new FileInputStream("file/c.txt");
//添加缓冲过滤流
BufferedOutputStream bos = new BufferedOutputStream(fos);
BufferedInputStream bis = new BufferedInputStream(fis)
) {
//写入
String s = "helloworld";
bos.write(s.getBytes());
System.out.println("写入成功!");
//刷新缓冲区,将数据从缓冲区取出放入文件
// bos.close();
bos.flush();
//读取
while (true) {
int n = bis.read();
if (n == -1) {
break;
}
System.out.print((char) n+" ");
}
System.out.println("读取成功!");
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("未知异常");
e.printStackTrace();
}
}
}
对象过滤流
- ObjectInputStream:对象输入流ois
- ObjectOutputStream:对象输出流oos
- 附加功能1:读写八大基本类型
- 附加功能2:读写引用类型
读写基本类型
读取:xxx ois.readXxx()
写入:void oos.writeXxx(值)
注:xxx对应的为基本类型,如:int readInt() | writeInt(10)
- 对象过滤流底层嵌套了缓冲过滤流,所以先写后读操作同一文件时仍然需要在写入完成后刷新缓冲区
- 为了保证数据传输时的安全性,所以在写入数据时会通过魔数机制对其进行加密,就会形成乱码效果,在读取数据时会再对其进行解密
package com.bz.test;
import java.io.*;
public class TestObject1 {
public static void main(String[] args) {
//往c.txt中写入5.5,再从c.txt中读取到控制台
try (
//先创建节点流对象
FileOutputStream fos = new FileOutputStream("file/c.txt");
FileInputStream fis = new FileInputStream("file/c.txt");
//添加对象过滤流
ObjectOutputStream oos=new ObjectOutputStream(fos);
ObjectInputStream ois=new ObjectInputStream(fis)
) {
//先写
oos.writeDouble(5.5);
System.out.println("写入成功!");
//强刷缓冲区
oos.flush();
//再读
System.out.println(ois.readDouble());
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("未知异常");
e.printStackTrace();
}
}
}
读写引用类型
读取:Object ois.readObject()
写入:oos.writeObject(对象)
- readObject方法与readXxx系列方法一致,读取到达末尾,会抛出EOFException异常
- readObject返回值为Object对象类型,必要时需要进行类型强转
- writeObject方法底层自带缓冲区刷新,所以先写后读时不需要手动刷新缓冲区
读写String
package com.bz.test;
import java.io.*;
public class TestObject_String {
public static void main(String[] args) {
try (
//先创建节点流对象
FileOutputStream fos = new FileOutputStream("file/c.txt");
FileInputStream fis = new FileInputStream("file/c.txt");
//添加对象过滤流
ObjectOutputStream oos=new ObjectOutputStream(fos);
ObjectInputStream ois=new ObjectInputStream(fis)
) {
//往c.txt中写入一首打油诗
oos.writeObject("一二三四五");
oos.writeObject("上山打老虎");
oos.writeObject("老虎没打着");
oos.writeObject("打着小松鼠");
oos.writeObject("松鼠说:你有病啊");
System.out.println("写入成功!");
//读取c.txt内容到控制台
while (true) {
try {
String str=(String) ois.readObject();
//对读取内容做后续处理
System.out.print(str);
} catch (EOFException e) {
break;//在读取过程中尝试捕捉异常,捕捉成功,证明读取结束
}
}
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("未知异常");
e.printStackTrace();
}
}
}
读写自定义类型
- 类必须实现Serializable接口,意味着允许被IO流序列化读写
- 通过transient修饰符进行修饰,可以防止某个属性参与序列化
package com.bz.entity;
import java.io.Serializable;
public class Student implements Serializable {
private String name;
//防止年龄参与序列化
private transient int age;
private double score;
//省略getter、setter、构造、toString
}
package com.bz.test;
import com.bz.entity.Student;
import java.io.*;
public class TestObject_Student {
public static void main(String[] args) {
try (
//先创建节点流对象
FileOutputStream fos = new FileOutputStream("file/c.txt");
FileInputStream fis = new FileInputStream("file/c.txt");
//添加对象过滤流
ObjectOutputStream oos=new ObjectOutputStream(fos);
ObjectInputStream ois=new ObjectInputStream(fis)
) {
//往c.txt中写入一个学生对象
oos.writeObject(new Student("zhangsan", 20, 88));
System.out.println("写入成功!");
//读取学生对象
Student stu = (Student) ois.readObject();
System.out.println(stu);
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
e.printStackTrace();
} catch (Exception e) {
System.out.println("未知异常");
e.printStackTrace();
}
}
}
如果类中有属性类型为自定义对象类型,并且也参与了序列化,则该属性类型也必须实现序列化标记接口
今日重点
- 流的分类
- 文件复制的源码(字节复制+缓冲过滤流)
- 对象过滤流读写自定义类型
Day20-IO2
字符流
- 操作char或String类型的数据
分类
- Reader 输入流-抽象父类
- FileReader 节点流
- Writer 输出流-抽象父类
- FileWriter 节点流
缓冲过滤流
输入流
BufferReader br=new BufferedReader(fr对象);
常用方法
- String readLine():一次读取一行内容,读取到达末尾,返回null
package com.bz.test;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class TestBR {
public static void main(String[] args) {
//从当前项目下的a.txt中读取内容到控制台
try (
//创建输入流对象并添加缓冲过滤流
BufferedReader br = new BufferedReader(new FileReader("file/a.txt"))
) {
//循环读取文件所有内容
while (true) {
//接收本次读取内容
String s = br.readLine();
//判断读取是否到达末尾
if (s == null) {
break;
}
//处理本次读取内容
System.out.println(s);
}
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
}
}
输出流
PrintWriter pw=new PrintWriter(fw对象);
BufferedWriter中的方法没有PrintWriter中的实用,所以实际开发时,更推荐使用pw
常用方法
- print(值):将内容写入目标文件,默认不换行
- println(值):将内容写入目标文件,默认换行
- println():写入一个空行
package com.bz.test;
import com.bz.entity.Student;
import java.io.*;
public class TestPW {
public static void main(String[] args) {
//往当前项目下的a.txt中写入内容
try (
//创建输出流对象并添加缓冲过滤流
PrintWriter pw=new PrintWriter(new FileWriter("file/a.txt"))
) {
//往目标文件写入内容
pw.print("abc");
pw.print("def");
pw.println();
pw.println("12345");
pw.println("678910");
pw.println(5.5);
pw.println(new Student("zhangsan", 20, 88));
System.out.println("写入成功");
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
}
}
字符编码集
- 编码:将原内容-->加密-->数字
- 解码:将数字-->解密-->原内容
每个编码集都有自己独特的编码解码
- 常见编码集:
- GBK:简体中文
- Big5:繁体中文
- ASC||:美国
- ISO-8859-1:西欧
- Unicode:
- UTF-16:java默认编码集,内容默认都占2个字节
- UTF-8:行业标准,所占空间由内容决定,区间为1-3个字节
桥转换流
- 作用:可以在数据传输过程中设置编码集
创建
输入流:InputStreamReader isr=new InputStreamReader(fis对象,"编码集")
输出流:OutputStreamWriter osw=new OutputStreamWriter(fos对象,"编码集")
使用
操作同一文件时,必须保证对该文件的编码解码集一致,否则读取时会乱码
package com.bz.test;
import com.bz.entity.Student;
import java.io.*;
/**
* 桥转换流
*/
public class Test {
public static void main(String[] args) {
//往a.txt中写入一个内容,再进行读取
try (
//创建桥转换流,传入字节节点流
OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("file/a.txt"),"GBK");
InputStreamReader isr=new InputStreamReader(new FileInputStream("file/a.txt"),"GBK");
//添加字符缓冲过滤流
PrintWriter pw=new PrintWriter(osw);
BufferedReader br=new BufferedReader(isr)
) {
//先写
pw.println("这是我的测试数据");
//刷新缓冲区
pw.flush();
//后读
System.out.println(br.readLine());
} catch (FileNotFoundException e) {
System.out.println("文件路径不正确");
} catch (IOException e) {
System.out.println("读写失败!");
} catch (Exception e) {
System.out.println("未知异常!");
e.printStackTrace();
}
}
}
知识点补充-输出语句:
System是类库中的一个final类,out是该类的一个静态属性,属性类型为PrintStream标准输出流类型,print或println方法是该流中的独有方法,作用为将内容写入至控制台
今日重点
- 字符缓冲过滤流和对象过滤流读写对象的区别?
- 对象过滤流是真正序列化对象信息
- 字符过滤流是在读写对象的toString方法内容
- PrintWriter和PrintStream中print方法的区别?
- pw是将内容写入文件
- ps是将内容写入控制台
- 桥转换流的创建和作用
Day21-多线程
进程的概念
操作系统(OS)中多个并发(同时)执行的程序任务
进程的特点
- 宏观并行,微观串行
- 原理:在一个时间段内,CPU会将时间段划分为很多个时间片,时间片之间交替执行,一个时间片又只能被一个程序拥有,当时间片的划分足够细小,交替频率足够快,就会形成宏观并行的假象,实际上仍然是串行
线程的概念
是进程中并发执行的多个任务
线程的特点
- 宏观并行,微观串行
- 原理:一个时间片只能被一个程序拥有,一个程序一次只能运行一个线程,争抢到时间片的线程才能执行,其他线程只能等待,所以线程之间也是串行
多线程
- 只存在多线程,不存在多进程
正在进行中的才是进程,其他程序都是等待执行的程序
但是不管有无在运行,线程任务都称为线程
线程的组成
- 时间片:OS调度分配
- 数据
- 代码
线程的创建
所涉及的API:
- Thread:类,表示线程对象
- start():开启线程
- Runnable:接口, 表示线程任务
- run():书写线程任务内容
-
继承Thread类,重写run方法
package com.bz.thread; /** * 自定义线程类 */ public class Mythread extends Thread { @Override public void run() { //输出0-99 for (int i = 0; i < 100; i++) { System.out.println(i); } } }
package com.bz.test; import com.bz.thread.Mythread; public class Test { public static void main(String[] args) { System.out.println("主函数开始"); //创建一个线程对象 Thread t1 = new Mythread(); // t1.run(); //再创建一个线程对象 Thread t2 = new Mythread(); //开启线程 t1.start(); t2.start(); /*t1.run(); t2.run();*/ for (int i = 100; i <300 ; i++) { System.out.println(i); } System.out.println("主函数结束"); } }
-
实现Runnable接口,重写run方法
线程创建:Thread 线程名=new Thread(Runnable实现类对象);
package com.bz.thread; /** * 自定义线程任务 */ public class MyRunnable implements Runnable{ @Override public void run() { //输出100-199 for (int i = 100; i <= 199; i++) { System.out.println("t1::::::"+i); } } }
package com.bz.test; import com.bz.thread.MyRunnable; public class Test2 { public static void main(String[] args) { //先创建任务对象 Runnable r = new MyRunnable(); //创建线程对象,传入线程任务 Thread t1 = new Thread(r); //利用匿名内部类创建一个线程对象 Thread t2=new Thread(new Runnable() { @Override public void run() { for (int i = 200; i <300 ; i++) { System.out.println("t2>>>"+ i); } } }); //利用lambda创建一个线程对象 Thread t3=new Thread(()->{ for (int i =300; i <400 ; i++) { System.out.println("t3="+ i); } }); t1.start(); t2.start(); t3.start(); } }
实际开发中,更推荐第二种创建方式
使用
- JVM进程中的第一条线程为主线程(主函数),其一定为第一个被运行的线程
- 开启多线程后, 程序执行结束的标志就不再是主函数执行完毕,而是所有线程内容执行完毕
- 执行流程:线程之间相互争抢时间片,拿到时间片的线程才能执行自身内容,如果执行中丢失时间片,则暂停执行,继续争抢,直到自身内容执行完毕才会退出争夺队列,所有线程内容执行完成,则JVM进程终止
- 开启线程需要调用start方法,线程开启后会自动运行run方法
线程状态
基本状态
等待状态
-
sleep():使当前线程进入到有限期休眠状态,会释放自身时间片,直到休眠结束,才会回到时间片的争夺队列
- Thread.sleep(long 毫秒数);
- 1秒=1000毫秒
- 执行:当执行到该方法,当前线程会进入有限期等待状态,休眠结束,回到就绪状态
package com.bz.test; public class TestSleep { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000);//使t1休眠3秒钟 } catch (InterruptedException e) { System.out.println("休眠异常"); } for (int i = 1; i <=50 ; i++) { System.out.println("t1:::"+i); } } }); Thread t2 = new Thread(()->{ for (int i = 51; i <=100 ; i++) { System.out.println("t2> "+i); } }); //开启线程 t1.start(); t2.start(); } }
- Thread.sleep(long 毫秒数);
-
join():使调用者线程的优先级高于当前线程
- 线程对象.join()
- 运行:提升调用者线程的优先级,当前线程进入无限期等待状态,只有等待调用者线程执行完毕死亡之后,才能回到就绪状态
package com.bz.test;
public class TestSleep {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(300);//使t1休眠3秒钟
} catch (InterruptedException e) {
System.out.println("休眠异常");
}
for (int i = 1; i <=50 ; i++) {
System.out.println("t1:::"+i);
}
}
});
Thread t2 = new Thread(()->{
for (int i = 51; i <=100 ; i++) {
System.out.println("t2> "+i);
}
});
Thread t3 = new Thread(()->{
try {
Thread.sleep(500);//使t3休眠500毫秒
} catch (InterruptedException e) {
System.out.println("休眠异常");
}
for (int i = 101; i <=150 ; i++) {
System.out.println("t3="+i);
}
});
//开启线程
t1.start();
t2.start();
t3.start();
}
}
sleep()和join()的区别:
- sleep进入的是有限期等待状态,join进入的是无限期等待状态
- sleep是静态方法,可以通过Thread直接调用,join必须通过线程对象调用
- sleep不会与其他线程的执行绑定,join必须使调用者线程和当前线程绑定
sleep()和join()都需要处理非运行时异常,run方法不支持上抛,必须try-catch
线程池
- 作用:可以承载管理多个线程任务,需要时将任务提交执行,任务执行结束并不会立即销毁,而是回到池中等待下次执行,直至线程池关闭。可以大大降低一个任务反复执行时多次创建线程对象带来的内存压力。
相关API
- ExecutorService:线程池接口
- 对象.submit(任务对象):将线程任务对象提交执行
- 对象.shutdown():关闭线程池
- Excutors:线程池工具类
- ExecutorService newFixedThreadPool(int ):获取一个固定并发数量的线程池对象
- 执行:当设置的并发数量大于等于任务提交数量时,则任务会直接并发执行。当设置的并发数量小于提交数量时,先提交的任务先并发执行,当有任务执行结束让位之后,剩余任务才能进入池中参与执行
- ExecutorService newCachedThreadPool():获取一个不固定并发数量的线程池
- 执行:所有提交的任务都会同时并发执行
- ExecutorService newFixedThreadPool(int ):获取一个固定并发数量的线程池对象
线程任务
-
Runnable:run()
- 特点:无返回值,不能上抛异常
package com.bz.test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestES { public static void main(String[] args) { //获取一个固定并发数量的线程池 // ExecutorService es = Executors.newFixedThreadPool(2); //获取不固定并发数量的线程池 ExecutorService es = Executors.newCachedThreadPool(); Runnable r1=new Runnable() { @Override public void run() { for (int i = 1; i <=50 ; i++) { System.out.println("r1:"+i); } } }; Runnable r2=new Runnable() { @Override public void run() { for (int i = 51; i <=100 ; i++) { System.out.println("r2="+i); } } }; Runnable r3=new Runnable() { @Override public void run() { for (int i = 101; i <=150 ; i++) { System.out.println("r3>>>"+i); } } }; es.submit(r1); // es.submit(r1); es.submit(r2); es.submit(r3); //关闭线程池 es.shutdown(); } }
-
Callable<返回值泛型>:call()
- 特点:有返回值,可以上抛异常,默认上抛Exception
- 使用:会将返回值封装进一个Future对象,该对象中的get()可以获取到被封装的返回值
package com.bz.test; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class TestES_Callable { public static void main(String[] args) throws Exception{ //创建一个线程池对象 ExecutorService es = Executors.newCachedThreadPool(); //计算1-100的和 Callable<Integer> c1=new Callable<Integer>() { @Override public Integer call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } return sum; } }; //提交执行并接收返回值 Future<Integer> future =es.submit(c1); System.out.println("查看返回值:"+future.get()); //关闭线程池 es.shutdown(); } }
线程安全问题
- 当多个线程同时访问同一个临界资源时,有可能破坏其原子操作,从而导致数据缺失,就会发生线程安全问题
- 临界资源:被多个线程同时访问的对象
- 原子操作:访问临界资源对象时不可缺失或更改的操作步骤
互斥锁
- 每个对象都默认拥有互斥锁,当开启互斥锁之后,线程会进入同步状态,只有同时拥有时间片和锁标记的线程才有资格执行自身内容,其他线程只有等正在执行的线程完成原子操作之后才能争抢资源
- synchronized:开启互斥锁
同步方法
-
原理:在被线程同时访问的方法上加锁
访问修饰符 synchronized 返回值类型 方法名(参数列表){}
package com.bz.util; import java.util.ArrayList; import java.util.List; /** * 操作集合属性的工具类 * 1. 往集合中添加元素 * 2. 查看集合元素 */ public class MyList { private List<Integer> list = new ArrayList<>(); //添加元素 //同步方法 public synchronized void insert(int n){ list.add(n); } //查看集合元素 public void query(){ //长度 System.out.println("集合长度为:"+list.size()); //查看内容 for (Integer i : list) { System.out.print(i+" "); } } }
同步代码块
-
原理:访问临界资源的线程们自己加锁
synchronized(临界资源对象){ //访问语句 }
package com.bz.test; import com.bz.util.MyList; public class TestMyList { public static void main(String[] args) throws Exception{ //创建一个工具类对象 MyList m = new MyList(); //线程1:添加1-5 Thread t1 = new Thread(()->{ synchronized (m) { for (int i = 1; i <= 5; i++) { m.insert(i); } } }); //线程2:添加6-10 Thread t2 = new Thread(()->{ //同步代码块 synchronized (m){ for (int i = 6; i <= 10; i++) { m.insert(i); } } }); t1.start(); t2.start(); //使t1和t2在主函数查看之前执行 t1.join(); t2.join(); //查看集合内容 m.query(); } } /* * 线程1:张汪洋上厕所 * * 线程2:龚家辉上厕所 * *临界资源:厕所 *原子操作:步骤:脱裤子-->蹲下去-->上厕所-->擦屁股-->提裤子-->站起来走人 * *加锁的第一种:给厕所大门上锁(厕所管理者) * 上厕所的条件:时间片(资格)+钥匙(争抢) *加锁的第二种:给坑位上锁(自己) * 上厕所的条件:时间片+钥匙(不需要争抢,自己上锁) * * */
区别
- 同步方法线程需要争抢时间片和锁标记,效率较慢
- 同步代码块线程只需要争抢时间片,拥有时间片的线程默认拥有锁标记,效率较快(更推荐)
线程安全的集合类
悲观锁:悲观的认为线程一定会发生安全问题,所以统加锁
- Vactor
- Hashtable
- Properties
乐观锁:乐观的认为线程不会发生安全问题,所以不加锁,等到安全问题真正发生时,再用算法结合极少量的synchronized处理问题
JDK5.0
都属于java.util.concurrent
-
ConcurrentHashMap:CAS算法
比较并交换:比较预期值和原有值,当其相等时才会将结果值放入内存
int i=1;
i++;
原有值:1 预期值:1 结果值2
-
CopyOnWriteArrayList
- 原理:在对集合进行写操作(增删改)时,先复制出一个副本,在副本上完成写操作,如果副本中出现安全问题,则直接舍弃该副本,再次复制新的副本重复操作,直至副本中正常执行写操作,然后将集合地址转换成副本地址。
- 特点:舍弃写的效率,提高读的效率。适用于读操作远多于写操作时
-
CopyOnWriteArraySet
- 原理:与CopyOnWriteArrayList一致,在写入时会执行去重操作
今日重点
- 线程和进程的特点
- 线程的两种创建方式
- 基本状态及触发时机
- sleep和join的区别
- 线程池的作用
- Runnable和Callable的区别
- 什么是线程安全问题
- 同步方法和同步代码块的区别
- 线程安全的集合类都有哪些
Day22-反射+设计模式
反射
- 是一种底层技术,通常用于底层框架的编写
类对象-Class
- 类的对象:是类实例化的产物,可以存在多个
- 类对象:包含着类的所有信息,如属性、方法、构造、父类信息、接口信息等,是类加载的产物,通常只有一个
获取
-
类名.class
Class<Student> c1 = Student.class;
-
引用名.getClass()
Student stu = new Student(); Class c2 = stu.getClass();
-
Class.forName("全限定名")
Class c3 = Class.forName("com.bz.entity.Student");
常用方法
- newInstance():利用无参构造构建类的实例对象
- Constructor<?> getDeclaredConstructor(形参的类对象):获取指定形参列表的有参构造器对象
- 构造器对象.newInstance(实参列表):通过指定有参构造器构建类的实例对象
//利用c1对构建类的实例
Student stu1 = c1.newInstance();
//利用c3构建类的实例
Student stu2 =(Student) c3.newInstance();
//获取全属性的有参构造器对象
Constructor<Student> con = c1.getDeclaredConstructor(String.class, int.class, double.class);
//构建一个学生对象实例
Student stu3 = con.newInstance("zhangsan", 20, 98.0);
优缺点
- 优点:
- 打破封装
- 提升代码的扩展性
- 缺点:
- 打破封装
- 代码可读性变差
反射重点
- 类对象和类的对象的区别
- 获取类对象的三种方式
- 通过反射构建类的实例的两种方式
设计模式
- 是程序员们在开发中总结的编码套路
单例模式
- 一个类只能创建出一个实例对象
饿汉式
- 思路:直接将唯一的实例对象创建出来
package com.bz.entity;
/**
* 单例模式-饿汉式
*/
public class ClassA {
//唯一实例
//static:1. 保证getClassA可以返回 2. 保证只会创建一次
//private:为了防止外界直接访问
private static ClassA ca = new ClassA();
//static:保证外界可以直接通过类名调用
public static ClassA getClassA(){
return ca;
}
//构造私有化:防止外界自由调用构造创建对象
private ClassA(){
}
}
缺点:有可能浪费空间
懒汉式
- 思路:只有在需要获取对象时,才会创建实例
package com.bz.entity;
/**
* 单例模式-懒汉式基础版
*/
public class ClassB {
//唯一实例
//static:1. 保证getClassA可以返回 2. 保证只会创建一次
//private:为了防止外界直接访问
private static ClassB cb =null;
//static:保证外界可以直接通过类名调用
//同步方法:预防线程安全问题
public static synchronized ClassB getClassB(){
//调用该方法,意味着需要实例化对象
if (cb == null) {//当第一次获取时
cb = new ClassB();
}
return cb;
}
//构造私有化:防止外界自由调用构造创建对象
private ClassB(){
}
}
缺点:线程效率低
懒汉式-进阶版
- 思路:在懒汉式的基础上,利用同步代码块+二次校验尽可能提高线程效率
package com.bz.entity;
/**
* 单例模式-懒汉式进阶版
*/
public class ClassB {
//唯一实例
//static:1. 保证getClassA可以返回 2. 保证只会创建一次
//private:为了防止外界直接访问
private static ClassB cb =null;
//static:保证外界可以直接通过类名调用
public static ClassB getClassB(){
if (cb==null) {//判断线程是否需要加锁执行
synchronized (ClassB.class) {//临界资源:当前的类对象
//调用该方法,意味着需要实例化对象
//二次校验:在实例化之前确定引用为空
if (cb == null) {//当第一次获取时
cb = new ClassB();
}
}
}
return cb;
}
//构造私有化:防止外界自由调用构造创建对象
private ClassB(){
}
}
工厂模式
- 对象的创建和销毁全部交由工厂完成,也是底层技术,通常用于底层框架
案例:利用工厂模式获取对象实例(Properties集合+配置文件、反射)
步骤
-
书写实体类
package com.bz.entity; public class Student { private String name; private int age; private double score; //省略getter、setter、构造 }
-
创建配置文件
- 在项目下新建后缀名为.properties的文件
- 书写内容
- 格式:自定义键=全限定名
- 等号左右不加双引号
- 语句末尾不加分号
- 一行只能书写一个键值对
- 不允许添加无关符号,比如空格
StudentClassName=com.bz.entity.Student
-
书写工厂类
package com.bz.util; import com.bz.entity.Student; import java.io.FileInputStream; import java.util.Properties; /** * 自定义工厂类 */ public class MyFactory { //获取Student的实例对象 public static Student getStudent(){ Student stu =null;//用来返回 try ( //创建一个字节输入流 FileInputStream fis=new FileInputStream("ClassNames.properties") ) { //将配置文件中的内容读取到集合中 Properties p = new Properties(); p.load(fis); //获取全限定名 String className=p.getProperty("StudentClassName"); //获取类对象 Class c = Class.forName(className); //利用反射构建对象 stu = (Student) c.newInstance(); } catch (Exception e) { System.out.println("执行异常!"); e.printStackTrace(); } return stu; } }
-
书写测试类
package com.bz.test; import com.bz.entity.Student; import com.bz.util.MyFactory; public class TestMyFactory { public static void main(String[] args) { //利用工厂类获取一个学生对象实例 Student stu1 = MyFactory.getStudent(); Student stu2 = MyFactory.getStudent(); System.out.println(stu1); System.out.println(stu2); } }