Java学习笔记
1.从安装到卸载
1.1 安装
1.1.1 jdk下载与安装
甲骨文官方网站:https://www.oracle.com/cn/java/
java.net 存档(推荐) https://jdk.java.net/archive/
华为云镜像:https://repo.huaweicloud.com/java/jdk/
1.1.2 环境变量配置
变量名:JAVA_HOME 变量值:JDK安装目录(/jdk)
变量名:Path 变量值:JDK命令文件夹所在目录(/bin)
变量名:CLASSPATH 变量值:JDK类库文件所在位置(/lib)
Linux 下安装(jdk17):
# 创建文件夹
mkdir -p /usr/lib/jvm
cd /usr/lib/jvm
# 解压
tar -vzxf jdk-17_linux-x64_bin.tar.gz
# 配置环境变量
vi /etc/profile
export JAVA_HOME=/usr/lib/jvm/jdk17
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar
# Shift+:后输入wq回车 退出
# 刷新配置
source /etc/profile
1.1.3 IDE的下载使用
免费 Eclipse: https://www.eclipse.org/
收费 IDEA: https://www.jetbrains.com/idea/
激活IDEA:https://3.jetbra.in/
IDEA 快捷键:
1) 删除当前行, 默认是 ctrl + Y
自己配置 ctrl + d
2) 复制当前行, 自己配置 ctrl + alt + 向下光标
3) 补全代码 alt + /
4) 添加注释和取消注释 ctrl + /
5) 导入该行需要的类先配置auto import , 然后使用 alt+enter
即可
6) 快速格式化代码 ctrl + alt + L
7) 快速运行程序自己定义 alt + R
8) 生成构造器等 alt + insert
[提高开发效率]
9) 查看一个类的层级关系 ctrl + H
10) 将光标放在一个方法上,输入 ctrl + B
, 可以定位到方法
11) 自动的分配变量名, 通过在后面加.var
1.2 卸载
删除文件和系统变量
2.语法基础
2.0 基础补充
进制的转换
2.1 标识符
- 包含:类名、方法名、变量名
- 标识符命名规则:
/*
Java标识符(类名、方法名、变量名)命名规则:
1. 所有的标识符都只能以字母(A-Z或a-z),美元符($),下划线(_)开始
A、a、$、_
2. 首字母之后可以是字母(A-Z或a-z),美元符($),下划线(_),数字(0-9)的任何字符组合
Aa、a$、$1、_A
3. 不能使用关键字作为
关键字详见下图
4. 标识符大小写敏感
aa和Aa不相同
5. 可以使用中文命名但不推荐,也不推荐使用pinyin
你好、nihao
*/
Java关键字
java 转义字符
字母前面加上捺斜线"\"来表示常见的那些不能显示的ASCII字符.称为转义字符.如\0,\t,\n等。
- \r 回车
- \n 换行
- \f 换页
- \t 横向跳格
- \b 退格
- \v 垂直制表
- \ \ 代表一个“ \ ”
- \ ‘ 代表一个单引号
- \ " 代表一个双引号
- \0 空字符
2.2 基本数据类型【等补充】
Java是强类型语言,第一次声明类型时必需说明数据类型。
基本数据类型:
基本类型共8种,分为三类,分别是数值类型(整型和浮点)、字符类型、布尔类型。
整型类型 [ 字节型byte、短整形short、整形int、长整型long ] (存储数据所占字节数以此为1、2、4、8)
浮点型 [ 单精度 float、双精度 double ]
字符型 [ char ]
布尔类型 [ boolean ] (包括 true 和 false 两个值)
特别注意:String 不是基本类型,而是引用类型。(后面详细写)
计算机中保存的小数是十进制的小数的近似值,所以不能用浮点数表示金额等重要指标,金额用 BigDecimal 或 Long 表示。
2.3 数据类型转换
2.3.1 自动类型转换
低容量 ——> 高容量,不会损失精度。
byte ——> short ——> int ——> long ——> float ——>double
char 类型比较特殊,char自动转换为int、long、float和double,但byte和short 不能自动转换为char,而且char也不能自动转换为byte或short。
2.3.2 强制类型转换
低容量 <<< 高容量,会丢失数据精度
语法格式:在待转换的变量名前用括号注明要转换的目标类型
1.double型变量转换成int
double a = 16.6;
int b = (int)a;// double类型容量大于int类型,需要强制转换
System.out.println(a);//a = 16.6
System.out.println(b);// b = 16
2.将 int 型变量转换为 short
/*
1. 不能对布尔值进行转换
2. 转换可能出现内存溢出、精度丢失等问题
*/
int a = 10;
short b = (short) a; // int类型容量大于short类型,需要强制转换
System.out.print(b); // b = 10
将 long 类型变量转换为 double 类型
long a = 10;
double b = a; // long类型容量小于double类型,自动转换
System.out.print(b); // b =10.0
2.4 变量
变量的命名规范:可以由字母、数字、下划线、美元符号组成,但不能以数字开头。没有长度限制,但区分大小写,最好简短清楚。一般建议使用驼峰命名法,即第一个单词首字母大写,其后单词首字母大写,如UserName。
语法:
数据类型 变量名 = 值;
注意:数据类型可以 8 中基本数据类型(整型、浮点型、字符型、布尔型)也可以是引用类型(数组、字符串等)
// 数据类型 变量名 = 值;
//例如
int a = 6;
double b = 3.14;
变量分类:局部变量(方法外、类内);类变量(方法外、类内);实例变量(类内,方法外)
public class main {
// 类变量(类内,方法外),比实例对象多static,可直接引用
static double flag = 10;
// 实例变量(类内,方法外),要new对象才能引用
String name = "Jerry";
public static void main(String[] args) {
// 局部变量(方法内)
int i = 10;
System.out.println(i); // 10
}
}
2.5 常量
定义:始终不变的量,赋值后无法再更改
创建常量 PI,其值为 3.14
public class main {
// 语法结构:final 数据类型 常量名 = 值
//常量命名规则:全大写字母,下划线(MAX_NUM)
static final double PI = 3.14; // static、final都是修饰符无先后顺序
public static void main(String[] args) {
System.out.println(PI); // 3.14
}
}
2.6 输入和输出
Scanner类:
输出:使用System.out.println()
来向屏幕输出一些内容。
System.out.println("Hello World") //Hello World
System.out.println(3 + 2) // 5
java占位符
%d | 格式化输出整数 |
---|---|
%x | 格式化输出十六进制整数 |
%f | 格式化输出浮点数 |
%e | 格式化输出科学计数法表示的浮点数 |
%s | 格式化字符串 |
输入
import java.util.Scanner;//类的包
public class main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in); // 创建Scanner对象
System.out.print("请输入姓名: "); // 提示
String name = scan.nextLine(); // 读取一行输入并获取字符串
System.out.print("请输入年龄: "); // 打印提示
int age = scan.nextInt(); // 读取一行输入并获取整数
System.out.printf("姓名:"+name+"年龄:"+age); // 格式化输出
}
}
2.7 运算符
定义:运算符是一种特殊符号,用以表示数据的运算、赋值和比较。
- 算术运算符
- 赋值运算符
- 比较运算符
- 逻辑运算符
- 位运算符(熟悉)
- 三元运算符
2.7.1 算术运算符
- +(正)、-(负)、 +、 -、*、/、%(取余)、++(前、后自增)、–(前、后自减)、+(字符串拼接)
- 基本使用:
```java
<pre><code> /*
除法运算 /
*/
int x = 16;
int y = 5;
int result_1 = x / y;
System.out.println(result_1); // 3, 两int变量相除结果仍为int,结果只保留整数double result_2 = x / y; // 3.0,int除int的double类型的结果是带小数点的整数,丢失数据精度
double result_5 = (x * 1.0) / y; //3.2,double除int,结果为double。不损失精度
double result_6 = ((double) x) / y; //3.2 强制转换后,结果为double。不损失精度/*
取余(模)运算符号 %
运算结果的符号始终和被模数的符号一致。
*/
</code></pre>1.
int x = 12;
int y = 5;
System.out.println(x % y); // 2
2.
int x = -12;
int y = -5;
System.out.println(x % y); // -2
3.
int x = -12;
int y = 5;
System.out.println(x % y); // -2
4.
int x = 12;
int y = -5;
System.out.println(x % y); // 2<pre><code> /*
自增、自减
++i 先自增1,再运算
i++ 先运算,再自增1
自增不会改变变量数据类型
常量不能自加(1++)
*/
int a1 = 10;
int b1 = ++a;
System.out.println(a1 + " , " + b1); // 11 , 11int a2 = 10;
int b2 = a2++;
System.out.println(a2 + " , " + b2); // 11 , 10int a3 = 10;
a3++; // ++a3
System.out.println(a3); // 11// 注意:
short s1 = 10;
// s1=s1+1; //编译失败,s1=(short)(s1+1) //正确编译
s1++; // 自增1不会改变变量本身数据类型// 例
int a4 = 10;
int b4 = a4--;
System.out.println(b4); // 10/*
字符串拼接,要至少要保证一边为字符串类型
*/
int n = 10;
System.out.println("n: " + n); // n:10
</code></pre>```
2.7.2.赋值运算符
=、+=、-=、*=、/=、%=
2.7.3.比较运算符
、!=、<、>、<=、>=
2.7.4.逻辑运算符
&(与)、&&(短路与)、|(或)、||(短路或)、!(非)、^(异或)
注意:逻辑运算符操作的都是布尔类型的变量
- &、&&:两边均为 true,结果为 true;
- |、||:任一边为 true,结果就为 true;
- !:true 变 false;false 变 true;
- ^:两边相异时结果为 true,反之为 false;
public class main {
public static void main(String[] args) {
int a = 16;
double b = 9.5;
String str1 = "hello";
String str2 = "world";
System.out.println("a=b:"+(a==b));
System.out.println("a>b:"+(a> b));
System.out.println("a<=b"+(a<=b));
System.out.println("str1+str2:"+(str1==str2));
}
}
//运行结果
a=b:false
a>b:true
a<=bfalse
str1+str2:false
2.7.5.位运算符
<<(左移)、>>(右移)、>>>(无符号右移)、&、|、! 、^ 、~(取反)
注意:位运算符操作的都是整型的数据
2.7.6 三元运算符
语法:
条件表达式? 表达式1: 表达式2;
运算规则:
- 如果条件表达式为true,运算后的结果是表达式1;
- 如果条件表达式为false,运算后的结果是表达式2;
2.8 程序流程控制
流程分类:
- 顺序结构
- 分支结构(if-else、switch-case)
- 循环结构(while、do…while、for)
2.8.1 分支结构
/*
第一种:
if(条件表达式){
执行表达式
}
*/
public class main {
public static void main(String[] args) {
// TODO 自动生成的方法存根
if(1 < 2) {
System.out.println("true");
}
}
}
/*
第二种:二选一
if(条件表达式){
执行表达式1
}else{
执行表达式2
}
*/
public class main {
public static void main(String[] args) {
// TODO 自动生成的方法存根
if(1 < 2) {
System.out.println("true");
}else {
System.out.println("false");
}
}
}
/*
第三种:多中结果选其一
if(条件表达式){
执行表达式1
}else if(条件表达式){
执行表达式2
}else if(条件表达式){
执行表达式3
}
...
else{
执行表达式n
}
*/
//多重判断
public class main {
public static void main(String[] args) {
int age = 21;
if(age > 60) {
System.out.print("老年");
}else if(age > 40) {
System.out.println("中年");
}else if(age > 18) {
System.out.println("青年");
}else{
System.out.print("少年");
}
}
}
/*
switch-case
*/
switch-case语法:
switch(表达式){
case 常量1:
执行语句1;
// break;
case 常量2:
执行语句2;
// break;
...
default:
执行语句n;
// break;
2.8.2 循环结构
-------for循环-------
/*
for循环结构:
for(循环变量初始化①;循环条件②;循环变量迭代③){
循环体④
}
执行过程:①->②->③->④->②->③->④->...->②
*/
//例
public class main {
public static void main(String[] args) {
// 循环输出10行"hello world"
for (int i = 0; i < 10; i++) {
System.out.println("hello world");
}
}
}
//循环嵌套
public class main {
public static void main(String[] args) {
// 输出3行4列的"hello world"
for(int i=1; i<=3;i++) //外层循环控制行
{
for(int j=1;j<=4;j++) //内层循环控制列
{
System.out.print("Hello World");
}
System.out.println(); //打印换行在内层循环结束后
}
}
}
//循环嵌套打印九九乘法表
int f = 0;
for(int i=1;i<=9;i++) {
for(int j=1;j<=9;j++) {
f = i*j;
System.out.print(i+"*"+j+"="+f+"\t");
}System.out.println();
}
//for嵌套if判断(计算1~100之间偶数之和)
int sum = 0;//初始化
for(int i = 1;i <= 100;i++) {
if(i % 2 == 0) //判断i是否为偶数
{
sum = sum +i;//累加
}
}
增强for循环
int[] nums = {1,3,9};
for (int i:nums) { // 依次从nums 数组中取出元素
System.out.println(i);
}
------while循环-------
/*
while循环的结构
① 初始化条件
while(② 循环条件){
③ 循环体;
④ 迭代条件;
}
执行过程:① - ② - ③ - ④ - ② - ③ - ④ - ... - ②
注意:
while循环不能没有迭代条件,缺少可能导致死循环!
循环条件可以是true ,是为无限循环
for与while区别:for循环和while循环的初始化条件部分的作用范围不同。for循环和while循环是可以相互转换。
*/
public class main {
public static void main(String[] args) {
// 遍历100以内所有奇数
int i = 1;
while (i <= 100) {
if (i % 2 != 0) {
System.out.print(i + " "); // 1 3 5 7 9 ......
}
i++;
}
}
}
------do-while 循环------
/*
do-while循环结构:
① 初始化条件
do{
③ 循环体;
④ 迭代条件;
}while(② 循环条件);
执行过程:① - ③ - ④ - ② - ③ - ④ - ... - ②
注意:
do-while循环至少会执行一次循环体!
*/
public class main {
public static void main(String[] args) {
// 遍历10以内的偶数
int i = 1;
do {
if (i % 2 != 0) {
System.out.print(i + " "); // 1 3 5 7 9 ......
}
i++;
} while (num <= 10);
}
}
2.8.3 跳转语句
/*
break 强制退出循环
continue 直接跳过循环体内剩余语句,只能应用于for、while、do-while循环语句
*/
2.9 编码规范
2.9.1 命名规范
驼峰命名(Camel-Case),又称“骆驼命名法”,是指混合使用大小写字母来命名。驼峰命名又分为小驼峰法和大驼峰法。小驼峰法就是第一个单词是全部小写,后面的单词首字母大写,如 myRoomCount;大驼峰法是第一个单词的首字母也大写,如ClassRoom。
除了包和常量外,Java编码规范命名方法采用驼峰法。
包名:包名是全小写字母,中间可以由点分隔开。作为命名空间,包名应该具有唯一性,一般使用组织域名的倒置,如com.qq.weixin 。但Java核心库包名不采用域名的倒置命名,如java.awt.event。
类和接口名:采用大驼峰法,如SplitViewController。
文件名:采用大驼峰法,如BlockOperation.java。
变量:采用小驼峰法,如studentNumber。
常量名:全大写,如果是由多个单词构成,可以用下划线隔开,如YEAR和 WEEK_OF_MONTH。
方法名:采用小驼峰法,如balanceAccount、isButtonPressed等。
2.9.2 注释规范
Java中注释的语法有三种:单行注释(//)、多行注释(/.../)和文档注释(/**...*/)。
- 文件注释:文件注释就是在每一个文件开头添加注释。文件注释通常包括如下信息:版权信息、文件名、所在模 块、作者信息、历史版本信息、文件内容和作用等。
- 文档注释:文档注释就是指这种注释内容能够生成API帮助文档,JDK中javadoc命令能够提取这些注释信息并生成 HTML文件。文档注释主要对类(或接口)、实例变量、静态变量、实例方法和静态方法等进行注 释。
文档是要给别人看的帮助文档,一般注释的实例变量、静态变量、实例方法和静态方法都 应该是非私有的,那些只给自己看的内容可以不用文档注释。
文档注释标签:
@author 说明类或接口的作者
@deprecated 说明类、接口或成员已经废弃
@param 说明方法参数
@return 说明返回值
@see 参考另一个主题的链接
@exception 说明方法所抛出的异常类
@throws 同@exception标签
@version 类或接口的版本
- 代码注释 : 程序代码中处理文档注释还需要在一些关键的地方添加代码注释,文档注释一般是给一些看不到源代 码的人看的帮助文档,而代码注释是给阅读源代码的人参考的。代码注释一般是采用单行注释(//)和 多行注释(/.../)。
2.9.3 排版
代码排版:空行、空格、断行和缩进等,目的是方便阅读代码,使代码结构层次清晰。
代码排版的一些良好习惯:
空行,代码过长要使用空行截断;类与接口之间间隔两个空行;方法之间空一行;一个方法中两个逻辑代码块之间空行;代码注释前空一行;
空格,在运算符的两边加空格。
2.9.4 避免使用 Magic Number
Magic Number 魔法数值,是指在代码中直接出现的数值,只有在这个数值记述的那部分代码中才能明确了解其含义,在Java 编码中应该避免使用魔法数值。比如 price_tax = 1.05 * price
1.05
即是一个典型的魔术数字.
魔术数字带来的常见的负面影响包括:
- 数值的意义难以了解,影响可读性。
- 数值需要变动时,可能要改不只一个地方。
- 当魔术数字是浮点数时,若在不同地方使用精度不同的数值,可能产生难以溯源的误差问题。例如在程序中某处圆周率使用
3.14159
,另一处又使用3.1415926
。
避免使用魔法数字可改为:
TAX = 0.05
price_tax = (1.0 + TAX) * price
2.10 引用类型
除了8种基本数据类型,Java还有引用类型,String 就属于引用类型,引用类型是可以通过 new 关键字来创建对象的类型,派生自Object。
2.10.1 堆(Heap)和栈(Stack)
JVM 的内存结构主要分为:方法区、堆、栈、程序计数器、本地方法栈。首先理解堆和栈的区别,在JVM的数据结构中 堆内存存储Java 中的对象,栈内存用来存储局部方法和调用。
在Java 中
栈内存(Stack):每个线程私有的。
堆内存(Heap):所有线程公用的。
2.10.2 引用类型的存储方式
举个例子,创建一个StringBuffer 对象:
StringBuffer str = new StringBuffer("Hello world");
首先,new 关键字会在堆(Heap)中申请一块内存,把创建好的 StringBuffer 对象放进去
然后,StringBuffer str 声明了一个引用类型的变量。这个引用类型的变量本身是存储在栈(Stack)上的。(这个引用类型的变量可以理解为指针,这个指针可用用来保存某个StringBuffer 对象的地址)。
最后, = 将 new 关键字申请的那块内存地址保存为 str 的值。
StringBuffer 【对象】的内容是存储在堆(Heap)上的,需要申请堆内存。而变量 str 只不过是针对该 StringBuffer 对象的一个引用(地址)。变量 str 的【值】(也就是 StringBuffer 对象的地址)是存储在【栈】上的。
基本类型的创建:
int n = 123456;
在基本类型中 n 的值也是存储在栈上的,但是不需要再从堆中申请内存。
2.11 自动拆装箱
在Java中的基本数据类型不是面对对象的,实际使用中存在着许多不便,为解决此问题Java为每个基本数据类型设计了一个对应类,称为包装类,包装类位于 Java.lang包中。
2.11.1 包装类
基本数据类型 | 包装类 |
---|---|
byte | Byte |
boolean | Boolean |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Double |
3 .数组字符串
3.1 一维数组
3.1.1 创建数组
•一维数组的声明格式有两种,分别是:
数据类型[] 数组名;
数据类型 数组名[];
int[] scores; //定义存储分数的数组,类型为整型
double height[]; //定义存储身高的数组,类型为浮点型
String[] names; //定义存储姓名的数组,类型为字符串。
/*
初始化方法:
使用关键字new为数组分配存储空间
格式如下:
类型标识符 数组名[]=new 类型标识符[数组长度];
类型标识符[] 数组名=new 类型标识符[数组长度];*/
int[] scores = new int[5];
String names[] = new String[3];
//创建数组并赋值
int array [ ]={1,2,3,4,5};
String hobbys [ ]={"music", "sports", "game"};
3.1.2 数组的下标
在对数组元素创建后,就可以通过数组的下标来访问数组元素,格式为:数组名[下标]
int[] scores = {95,93,80,86,79}
System.out.print("scores数组中第1个元素的值:" + scores[0]);//注意:数组的下标从0开始
循环输出:
int[] ns = {1,2,3,34,4,5,}
//循环遍历输出
for(int i = 0;i <= ns.length-1;i++) { //因为第一个下边时0 所以数组长度为:数组名.length-1
System.out.println(ns[i]);
}
数组拷贝:
int[] arr1 = {10,20,30};
int[] arr2 = new int[arr1.length];
for(int i = 0; i < arr1.length; i++) {
arr2[i] = arr1[i];
System.out.print(arr2[i]); // 10 20 30
}
数组冒泡排序:
int[] ns = {1,2,3,34,4,5,}
//冒泡排序
for(int i = 0;i < ns.length-1;i++) {
for(int j = 0;j < ns.length-i-1;j++) {
if(ns[j] < ns[j+1]) {
int t = ns[j];
ns[j] = ns[j+1];
ns[j+1] = t;
}
}
}
System.out.println(Arrays.toString(ns));//通过arrays方法类输出为字符串,用法是arrays.toString(数组名)。
//循环遍历输出
for(int i = 0;i <= ns.length-1;i++) {
System.out.println(ns[i]);
}
3.2 多维数组
二维数组的声明格式
数据类型[][] 数组名;
数据类型 数组名[][];
数据类型[] 数组名[];
例如:
int[][] array1;
double array2[][];
char[] array3[];
创建:
数组名[行的索引][列的索引] = 值;
二维数组打印直角三角形
public static void main(String[] args) {
int[][] ns = new int[10][];
//为每行分配列数
for(int i = 0;i < ns.length;i++) {
ns[i] = new int[i+1];
}
//赋值
for(int i = 0;i < ns.length;i++) {
for(int j = 0; j < ns[i].length;j++) {
ns[i][j] = i + 1;
System.out.print(ns[i][j]+"\t");
}System.out.println();
}
}
3.3字符串应用
public class T0530 {
public static void main(String[] args) {
// 字符串应用
String s1 = "This is a String";
String s2 = new String("This is a String");
System.out.println(s1);
System.out.println(s2);
//String 类对象的常用方法
//获取字符串的长度 length()
String str = "This is a String";
int len = str.length();
System.out.println(len); //16 包括空格
//字符串比较 equals() 和 equalsIgnoreCase()
System.out.println("JAVA".equals("java"));//equals区分大小写,结果false
System.out.println("JAVA".equalsIgnoreCase("java"));//不区分大小写,结果true
//字符串查找 indexOf()
String str1 = "This is a String";
String str2 = "h";
int index = str.indexOf(str2);//从str1的开始位置查找str2字符串,查找从0位开始
System.out.println(index);//1
//字符串连接 concat()
String str3 = "This is a String";
String str4 = str3.concat("Test");
System.out.println(str4);//This is a StringTest
//字符串替换 replace()
String str5 = "This is a String";
String str6 = str5.replace("T","t");
System.out.println(str6);//this is a String
//字符串截取 substring()
String str7 = "This is a String";
String sub1 = str7.substring(3);//s is a String
String sub2 = str7.substring(3,9);//s is a
System.out.println(sub1);//截取第3个字符后的内容
System.out.println(sub2);//截取3 - 9位之间
//获取指定位置的字符 charAt()
String str8 = "This is a String";
char chr = str8.charAt(3); // s
System.out.println(chr);
//实现字符串的大小写转换 toUpperCase() 和 toLowerCase()
String str9 = "This is a String";
String str10 = str9.toUpperCase();
System.out.println(str10);//转换成大写
String str11 = "THIS IS A STRING";
String str12 = str11.toLowerCase();
System.out.println(str12);//全部转换成小写
//字符串转换成字符数组 toCharArray()
String str13 = "hello";
char[] arr = str13.toCharArray();
//System.out.println(arr[0]);//h
//循环输出
for(int i = 0;i < arr.length;i++){
System.out.print(arr[i]);
}
}
}
4.面对对象
4.1 啥是面对对象
4.1.1 面向过程与面向对象
面向过程:把问题分解成一个一个步骤,每个步骤用函数实现,依次调用。面向过程代码流程化,代码执行效率高,但代码重用性低,扩展能力差,后期维护难度大。
面向对象:将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象之间的调用,组合解决问题。在面向对象中把属性、行为等封装成对象,基于对象及对象的能力进行业务逻辑的实现。面对对象的编程范式写出来的代码扩展性,可维护性都很高。
比如。造一辆车,首先定义车的各种属性,然后把属性封装在一起,抽象成一个Car类。
4.1.2 面对对象三大基本特征
(1)封装
所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏
//定义一个矩形
class Rectangle {
private int length;
private int width;
//设置矩形的长度和宽度
public Rectangle(int length, int width) {
this.length = length;
this.width = width;
}
// 获得矩形面积
public int area() {
return this.length * this.width;
}
}
(2)继承
功能:使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。
// 定义一个正方形继承矩形功能
class Square extends Rectangle {
// 设置正方形边长
public Square(int length) {
super(length, length);
}
}
(3)多态
多态指的是同一个方法在不同的对象上可以有不同的行为。多态机制实现不同的内部实现结构共用同一个外部接口。
多态实现的三个必要条件是:继承、重写(子类继承父类后,对继承的方法重新定义)、父类应用指向子类对象。
例如,有一个Animal类作为父类,有一个Dog类和一个Cat类作为子类。Animal类有一个makeSound()方法用于表示动物发出声音的行为。在Dog类中,重写了makeSound()方法,使得狗发出"汪汪"的声音;而在Cat类中,也重写了makeSound()方法,使得猫发出"喵喵"的声音。
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override //这是一个注解,不会被编译器打包进class文件,作用是让编译器检查该方法是否正确地实现了覆写。
public void makeSound() {
System.out.println("汪汪");
}
}
class Cat extends Animal {
@Override //这是一个注解,不会被编译器打包进class文件,作用是让编译器检查该方法是否正确地实现了覆写。
public void makeSound() {
System.out.println("喵喵");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.makeSound(); // 输出:汪汪
animal = new Cat();
animal.makeSound(); // 输出:喵喵
}
}
4.1.3 面对对象五大基本原则
(1)单一职责原则(SRP)
一个类只做好一件事,只有一个引起它变化的原因。
单一职责原则可以看作高内聚,低耦合在面对对象上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。
例如,一个图书管理系统中,有一个Book类负责表示图书的基本信息,另外有一个BookManager类负责管理图书的借阅和归还。这样,Book类只负责表示图书的信息,BookManager类只负责管理图书的操作,两个类各自承担了不同的职责。
(2)开放封闭原则(OCP)
软件实体(类、模块、函数等)应该是可扩展且不可修改的。对扩展开放,对修改封闭。
对扩展开放:当有新需求新变化时,可以对现有代码进行扩展。
对修改封闭:类一旦设计2完成,就可以独立完成其工作,不要尝试对其进行任何修改。
假设有一个Shape类,表示一个形状的基本信息,包括计算面积的方法。现在需要新增一个圆形类Circle,可以通过继承Shape类并重写计算面积的方法来实现。这样,新增一个圆形类不需要修改已有的Shape类,符合开放封闭原则。
(3)里氏替换原则(LSP)
子类必须能够替换掉父类并且不会影响程序的正确性。
假设有一个Animal类作为父类,有一个Dog类和一个Cat类作为子类。Animal类有一个eat()方法用于表示动物吃东西的行为。根据里氏替换原则,可以将Dog对象或Cat对象赋值给Animal类型的变量,并调用eat()方法,行为应该是一致的。
里氏替换原则是继承和多态的综合体现。里氏替换原则的实现方法是面向接口编程。违反里氏替换原则必然会导致违反开放封闭原则。
(4)接口隔离原则 (ISP)
使用多个小且少的接口,不要使用杂且多的总接口,就是接口要内聚,避免客户端依赖它不需要的接口。
例如,一个电商系统中,有一个Order接口用于处理订单相关的操作,现在需要新增一个Shipping接口用于处理物流相关的操作,可以将Order和Shipping接口分开,避免客户端依赖不需要的接口。
分离接口的手段:
委托分离:通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖。但是会增加系统的开销。
多重继承分离:通过接口的多继承来实现客户的需求,是比较好的方式。
(5)依赖倒置原则(DIP)
程序依赖于抽象接口,而不是具体的实现。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。 抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。
程序要依赖于抽象接口,而不是具体的实现;要对抽象编程,不要对现实编程。
假设有一个电子商务系统,需要发送邮件通知用户订单的状态变化。现在有一个Order类负责表示订单的信息,而且有一个EmailSender类负责发送邮件。
按照依赖倒置原则,我们可以定义一个通知接口Notification,其中包含一个发送通知的方法sendNotification()。然后,Order类依赖于Notification接口,而不是具体的EmailSender类。
这样,当需要更换邮件发送方式时,只需要实现一个新的类,实现Notification接口,并重写sendNotification()方法即可。而不需要修改Order类的代码。
4.2 类和对象
4.2.1 类的定义和构造方法
类的定义是指在面向对象编程中,通过关键字class来定义一个类。类是一种抽象的数据类型,用于描述具有相同属性和行为的对象的集合。
类的定义通常包括以下几个部分:
- 类名:用于标识类的名称,通常采用大写字母开头的驼峰命名法,例如:Person、Car等。
- 属性:用于描述类的特征或状态,也称为成员变量。属性可以是各种数据类型,例如整型、字符串、布尔型等。
- 方法:用于描述类的行为或功能,也称为成员方法。方法可以包含一系列的语句,用于实现特定的功能。
- 构造方法:用于创建类的对象,并进行初始化操作。构造方法的名称与类名相同,没有返回类型,并且在创建对象时自动调用。
声明类的语法格式:
[public][abstract|final] class className [extends superclassName] [implements interfaceNameList] {
//类体
}
例如:
```java
public class Person {
// 属性
private String name;
private int age;
<pre><code> //构造方法用于在创建Person对象时进行初始化操作,接受两个参数name和age,并将其赋值给对应的属性。
// 两种构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
} // 这是有参构造方法
public Persion(){
name = "Alice";
age = 25;
} //这是无参构造方法
// 方法
//sayHello()方法用于输出一个问候语,其中使用了name和age属性的值。
public void sayHello() {
System.out.println("Hello, my name is " + name + ", I am " + age + " years old.");
}
//eat()方法用于输出一个人在吃饭的行为,接受一个参数food,并将其与name属性的值一起输出。
public void eat(String food) {
System.out.println(name + " is eating " + food);
}
//创建了一个Person对象,并调用了其sayHello()和eat()方法,实现了对应的功能
public static void main(String[] args) {
Person person = new Person("Alice", 25);// 有参构造方法创建对象
person.sayHello(); // 输出:Hello, my name is Alice, I am 25 years old.
person.eat("apple"); // 输出:Alice is eating apple
Person person2 = new Person();// 无参构造方法创建对象
person2.sayHello(); // 输出:Hello, my name is Alice, I am 25 years old.
person2.eat("apple"); // 输出:Alice is eating apple
}
</code></pre>
}
```
4.2.2 构造函数
public class Person {
private String name;
private int age;
// 无参构造函数
public Person() {
name = "John Doe";
age = 0;
}
// 带参数的构造函数
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter和Setter方法
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;
}
}
4.3 面向对象
4.3.1 继承与实现
继承(Inheritance):如果多个类的某个部分的功能相同,那么可以抽象出一个类出来,把他们的相同部分都放到父类里,让他们都继承这个类。
实现(Implement):如果多个类处理的目标是一样的,但是处理的方法方式不同,那么就定义一个接口,也就是一个标准,让他们的实现这个接口,各自实现自己具体的处理方法来处理那个目标
继承指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力。所以,继承的根本原因是因为要复用,而实现的根本原因是需要定义一个标准。
在Java中,继承使用
extends
关键字实现,而实现通过implements
关键字。
4.3.2 重载与重写(多态)
重载(Overloading)和重写(Overriding)
重载:指的是在同一个类中,多个函数或者方法有同样的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。
重写:指的是在Java的子类与父类中有两个名称、参数列表都相同的方法的情况。由于他们具有相同的方法签名,所以子类中的新方法将覆盖父类中原有的方法。
- 重载的例子
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
}
public class Main {
public static void main(String[] args) {
Calculator calculator = new Calculator();
int result1 = calculator.add(2, 3);
System.out.println("Result 1: " + result1); // 输出:Result 1: 5
double result2 = calculator.add(2.5, 3.5);
System.out.println("Result 2: " + result2); // 输出:Result 2: 6.0
int result3 = calculator.add(2, 3, 4);
System.out.println("Result 3: " + result3); // 输出:Result 3: 9
}
} - 重写的例子
class People{
public String getName(){
return "people";
}
}
class Student extends People{
public String getName(){
return "student";
}
}
public class main {
public static void main(String[] args) {
People p = new People();
System.out.println(p.getName());// 输出 people
Student s = new Student();
System.out.println(s.getName());//输出 student
}
}
向上转型,向下转型
向上转型:
1) 本质:父类的引用指向了子类的对象
2) 语法:父类类型引用名=new子类类型();
3) 特点:编译类型看左边,运行类型看右边。
(1)可以调用父类中的所有成员(需遵守访问权限)
(2)但是不能调用子类的特有的成员
Animal animal = new Cat();
Object obj = new Cat();//Object 也是 Cat的父类
向下转型:
子类类型 引用名 = (子类类型) 父类引用;
- 只能强转父类的引用,不能强转父类的对象
- 要求父类的引用必须指向的是当前目标类型的对象
- 当向下转型后,可以调用子类类型中所有的成员
Java 动态绑定机制
1.当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
2.当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用,找不到再去父类中寻找。
toString 方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
1) 基本介绍
默认返回:全类名+@+哈希值的十六进制,子类往往重写toString 方法,用于返回对象的属性信息(全类名就是包名 + 类名)
2) 重写toString 方法,打印对象或拼接对象时,都会自动调用该对象的toString 形式.
3) 当直接输出一个对象时, toString 方法会被默认的调用, 比如System.out.println(monster) ;// 就会默认调用monster.toString()
4.3.3 抽象类 抽象方法
1.用absract关键字修饰的类叫抽象类
访问修饰符 abstract 类名{
}
2.用abstract 修饰一个方法,就是一个抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
3。抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()
- 抽象类在框架和设计模式使用比较多。
抽象类,不能被实例化。
抽象类不一定包含abstract 方法,也就是说抽象类可以没有abstract方法。
一旦包含了abstract 方法,则这个类必须声明为abstract
abstract 只能修饰类和方法,不能修饰属性等
4.3.4 接口
所谓interface
,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。因为接口定义的所有方法默认都是public abstract
的,所以这两个修饰符不必要写出。
/**
一个计算图形面积的程序
*/
interface Shape {
public abstract double getArea(); //public abstract 可以省略
public abstract double getGirth();
}
// 当一个具体的class去实现一个interface时,需要使用implements关键字
class Triangle implements Shape {
private double a, b, c;
public double getA() {
return a;
}
public void setA(double a) {
this.a = a;
}
public double getB() {
return b;
}
public void setB(double b) {
this.b = b;
}
public double getC() {
return c;
}
public void setC(double c) {
this.c = c;
}
public double getArea() { //这里使用海伦公式计算三角形面积 S=√p(p-a)(p-b)(p-c)
double p = (getA() + getB() + getC()) / 2; //p = (a+b+c)/2
return Math.sqrt(p * (p - getA()) * (p - getB()) * (p - getC()));
}
public double getGirth() {
return getA() + getB() + getC();
}
public static void main(String[] args) {
Circle cir = new Circle();
cir.setR(10);
System.out.println("圆的半径:"+cir.getR());
System.out.println("圆形的面积:"+cir.getArea()+"\n圆形的周长:"+cir.getGirth());
Triangle tri = new Triangle();
tri.setA(3);
tri.setB(4);
tri.setC(5);
System.out.println("三角形的边长:"+tri.getA()+"、"+tri.getB()+"、"+tri.getC());
System.out.println("三角形的面积:"+tri.getArea()+"\n三角形的周长:"+tri.getGirth());
}
}
4.3.5 包
声明包:通过package 声明包,声明格式为 package packageName;
引用包:使用import 语句导入包中的类,格式为
import packageName.* // 导入使用包的所有类
import packageName.className;// 导入包中的指定类
比如:
Person 类:
package com.pack.Test
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
TestPerson 类:
package com.pack.Test
import com.pack.Test.Person;
public class TestPerson {
public static void main(String[] args) {
Person p = new Person();
p.setName("小明");
System.out.println(p.getName());
}
}
结果输出:小明
4.3.6 super 关键字
super 代表父类的引用,用于访问父类的属性、方法、构造器。
1,访问父类的属性,但不能访问父类的private 属性 。super.属性名
2, 访问父类的方法,不能访问父类的private 方法。super.方法名(参数列表)
3, 访问父类的构造器,super(参数列表只能放在构造器的第一句)
4.4 Java核心类
4.4.1 Object类
Java 中所有的类都默认继承自 java.lang.Object 类,这个类是所有类的超类(所有类的祖先)。Object类属于java.lang包中的类型,写代码时不需要显示使用import语句引入,它是由解释器自动引入的。
Object类中定义的方法:
- clone() :创建并返回此对象的副本。
- equals(Object obj) :指定其他对象是否“等于”此对象。
- hashCode():返回对象的哈希码值。
- notify():唤醒正在等待这个对象的监视器的单个线程。
- notifyAll():唤醒正在等在这个对象的监视器的所有线程。
- toString():返回对象的字符串表示形式。
- wati():使当前线程进入等待状态,直到另一个线程调用此对象的 notify() 方法或 notifyAll() 方法。
- finalize():当垃圾回收器决定回收某对象时,就会运行该对象的 finalize() 方法。
具体示例代码待补充..........
4.4.2 JavaBean
定义一个类时,通常会定义一些属性和方法,会有一些private 的属性和 public 的方法。
public class user {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
JavaBean标准规范的几个条件
- 所有属性都是 private 的。
- 提供默认的构造函数。
- 提供统一的getter和setter 方法。
- 实现 Serializable 接口。
布尔类型的JavsBean 需要特别注意命名(#待补充代码)
4.4.3 Math类
java.lang.Math,Math类是final的不能被继承。Math类中包含用于进行基本数学运算的方法,指数、 对数、平方根和三角函数等。
最大值和最小值:
代码 | 定义 |
---|---|
static int min(int a, int b) | 取两个int整数中较小的一个整数。 |
static int min(long a, long b) | 取两个long整数中较小的一个整数。 |
static int min(float a, float b) | 取两个float浮点数中较小的一个浮点数。 |
static int min(double a, double b) | 取两个double浮点数中较小的一个浮点数。 |
max方法取两个数中较大的一个数与min方法参数类似。
绝对值:
代码 | 定义 |
---|---|
static int abs(int a) | 取int整数a的绝对值。 |
static long abs(long a) | 取long整数a的绝对值。 |
static float abs(float a) | 取float浮点数a的绝对值。 |
static double abs(double a) | 取double浮点数a的绝对值。 |
三角函数:
代码 | 定义 |
---|---|
static double sin(double a) | 返回角的三角正弦。 |
static double cos(double a) | 返回角的三角余弦。 |
static double tan(double a) | 返回角的三角正切。 |
static double asin(double a) | 返回一个值的反正弦。 |
static double acos(double a) | 返回一个值的反余弦。 |
static double atan(double a) | 返回一个值的反正切。 |
static double toDegrees(double angrad) | 将弧度转换为角度。 |
static double toRadians(double angdeg) | 将角度转换为弧度。 |
取舍:
代码 | 定义 |
---|---|
static double ceil(double a) | 返回大于或等于a最小整数。 |
static double floor(double a) | 返回小于或等于a最大整数。 |
static int round(float a) | 四舍五入方法。 |
其他:
对数运算:static double log(double a),返回a的自然对数。
平方根:static double sqrt(double a),返回a的正平方根。
幂运算:static double pow(double a, double b),返回第一个参数的第二个参数次幂的值。
计算随机值:static double random(),返回大于等于 0.0 且小于 1.0随机数。
常量 圆周率PI 自然对数的底数E。
4.4.4 Date 类
Date 类用来表示日期和时间,是一个长整型数据,精确到秒。
- Date():用当前时间创建Date对象,精确到毫秒。
- Date(long date):指定标准基准时间以来的毫秒数创建Date对象。标准基准时间是格林威治时间 1970年1月1日00:00:00。
Date类的普通方法:
- boolean after(Date when):测试此日期是否在when日期之后。
- boolean before(Date when):测试此日期是否在when日期之前。
- int compareTo(Date anotherDate):比较两个日期的顺序。如果参数日期等于此日期,则返回值 0;如果此日期在参数日期之前,则返回小于0的值;如果此日期在参数日期之后,则返回大于0 的值。
- long getTime():返回自1970年1月1日00:00:00以来此Date对象表示的毫秒数。
- void setTime(long time):用毫秒数time设置日期对象,time是自1970年1月1日00:00:00以来此Date 对象表示的毫秒数。
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.toString());
// SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss"); 转换格式
SimpleDateFormat ft = new SimpleDateFormat ("yyyy-MM-dd hh:mm:ss");
System.out.println("当前时间为: " + ft.format(date));
}
4.4.5 枚举类
4.4.6 异常类
详见5
4.4.7 StringBuilder和StringBuffer类 (重点)
Java提供了两个可变字符串类StringBuffer和StringBuilder,中文翻译为“字符串缓冲区”。
StringBuffer和StringBuilder具有完全相同的API,即构造方法和普通方法等内容一样。StringBuilder的中 构造方法有4个:
StringBuilder():创建字符串内容是空的StringBuilder对象,初始容量默认为16个字符。
StringBuilder(CharSequence seq):指定CharSequence字符串创建StringBuilder对象。CharSequence 接口类型,它的实现类有:String、StringBuffer和StringBuilder等,所以参数seq可以是String、 StringBuffer和StringBuilder等类型。
StringBuilder(int capacity):创建字符串内容是空的StringBuilder对象,初始容量由参数capacity指 定的。
StringBuilder(String str):指定String字符串创建StringBuilder对象
5. 异常处理
5.1 异常处理机制
5.1.1 Java异常体系
程序运行时会遇到特殊情况,导致程序出错,异常体系就是为了表达这些“特殊情况”。
Java的异常体系从Throwable类衍生出来的,Throwable 继承自Object, Throeable 有两个重要的子类:Error(错误) 和 Exception (异常) 。
程序员无法处理的问题为 Error ,程序员可处理的问题为 Exception。
(1)Errow
Java 里的错误表示为系统级错误,是Java运行环境的内部错误或者硬件错误,无法使用程序来处理,只能退出运行。
常见的错误:OutOfMemoryErrow(内存溢出) , NoClassDefFoundErrow(类未丁一) , NoSuchMethodErrow(找不到方法)
(2)Exception
异常通常是程序设计不完善的问题, 需要进行捕捉、处理。Java 异常分为两大类:受检异常( checked exception) 和 ⾮受检异常( unchecked exception)。
受检异常是必须在代码里处理的异常,否则程序无法编译;非受检异常是非必须处理的异常,不处理代码也可以正常编译。
非受检异常一般是运行时的异常,继承自RuntimeException 。编写代码时,不需要显式地捕获非受检异常,如果不捕获,运行是运行期发生异常就会中断程序地执行。
常见的异常:
NullPointerException(空指针异常):当尝试访问一个空对象的属性或调用空对象的方法时抛出。
ArrayIndexOutOfBoundsException(数组越界异常):当尝试访问数组中不存在的索引时抛出。
ClassCastException(类转换异常):当尝试将一个对象强制转换为不兼容的类型时抛出。
IllegalArgumentException(非法参数异常):当传递给方法的参数不合法时抛出。
ArithmeticException(算术异常):当发生数学运算错误时抛出,例如除以零。
IOException(输入输出异常):当发生输入输出操作错误时抛出。
FileNotFoundException(文件未找到异常):当尝试打开不存在的文件时抛出。
InterruptedException(中断异常):当一个线程在等待、睡眠或被阻塞时被中断时抛出。
UnsupportedOperationException(不支持的操作异常):当尝试调用不支持的方法或操作时抛出。
SQLException(SQL异常):当在数据库操作中发生错误时抛出。
5.1.2 捕获异常
抛出异常
// 使用 throws 声明异常
public void method() throws Exception{
// 使用 throw 抛出异常
throw new Exception();
}
捕获异常
异常的捕获需要用到 try、catch 、finally 等关键字。
// try...catch..finally;
// try 是必须的,catch 和 finally 至少有一个。
// 其中,try 和 finally 只能有一个,catch 可以有很多个,一次异常捕获过程中,可以同时进行多个异常捕获。
try{
// 代码块
}
catch(异常类型 异常对象){
// 异常处理
}
finally{
// 一定会执行的代码
}
数组越界异常:
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
int arr[] = new int[5];
Scanner scan = new Scanner(System.in);
System.out.println("请输入5个整数:");
try {
for(int i = 0;i<arr.length;i++) {
arr[i] = scan.nextInt();
}
System.out.println(arr[6]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界");
System.out.println("异常:"+e);
}
}
}
5.1.3 处理异常
异常处理的最佳实践
1,不要使用异常来控制业务逻辑
2,如果处理不了,不要捕获异常。
6. I/O
6.1 I/O理论概念
IO就是指Input/Output,即输入和输出。在Java 中 I/O 通过数据流、序列化、和文件系统提供系统的输入输出。
- Input指从外部读入数据到内存,例如,把文件从磁盘读取到内存,从网络读取数据到内存等等。
- Output指把数据从内存输出到外部,例如,把数据从内存写入到文件(磁盘),把数据从内存输出到网络等等。
在计算机里,代码是在内存中运行的,数据也必须读到内存,数据存储的形式是byte数组,字符串等类型,所以必须要放在内存里。在Java中,IO流是一种顺序读写数据的模式,它的特点是单向流动。
6.2.1 字节流与字符流,输入流与输出流
Bit (比特)是指二进制中的一位,0 或 1。
Byte (字节)是计算机中操作数据的最小单位,8 个 bit 为1 Byte 。
Char(字符)是指可读写的最小单位,在Java里面由16占 bit(16位),一个char类型的可以存储一个汉字。
在 I/O 流中,传输的数据类型是字节(Byte)的就是字节流,传输的数据类型是字符(char)的就是字符流。
在Java中,java.io 包提供了所有同步IO的功能。InputStream , OutputStream;Reader,Writer 是Java标准库提供的最基本的输入流,输出流,属于抽象类。
操作字节类型的数据的主要操作类是InputStream , OutputStream的之类,操作字符类型的数据的主要操作类是Reader,Writer 的子类。
I / O 分类 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
字节流与字符流的相互转换:
字节流与字符流的相互转换:
InputStreamReader:是Reader的子类,将输入的字节流变为字符流,即将一个字节流的输入对象变为字符流的输入对象。
OutputStreamWriter:是Writer的子类,将输出的字符流变为字节流,即将一个字符流的输出对象变为字节流输出对象。
// 将字节流转换为字符流:
InputStream inputStream = new FileInputStream("input.txt");
Reader reader = new InputStreamReader(inputStream, "UTF-8");
// 将字符流转换为字节流:
OutputStream outputStream = new FileOutputStream("output.txt");
Writer writer = new OutputStreamWriter(outputStream, "UTF-8");
6.2 File 类
File 类常用的方法:
创建文件或目录:
boolean createNewFile():创建一个新的空文件。
boolean mkdir():创建一个新的目录。
boolean mkdirs():创建一个新的目录,包括所有必需但不存在的父目录。
删除文件或目录:
boolean delete():删除文件或目录。
重命名文件或目录:
boolean renameTo(File dest):将文件或目录重命名为指定的目标文件或目录。
判断文件或目录的属性:
boolean exists():判断文件或目录是否存在。
boolean isFile():判断是否为文件。
boolean isDirectory():判断是否为目录,是返回true,否返回fales。
boolean canRead():判断文件或目录是否可读。
boolean canWrite():判断文件或目录是否可写。
boolean isHidden():判断文件或目录是否隐藏。
获取文件或目录的信息:
String getName():获取文件或目录的名称。
String getPath():获取文件或目录的路径。
String getAbsolutePath():获取文件或目录的绝对路径。
String getCanonicalPath():和获取绝对路径类似,但返回的是规范路径。
String getParent(): 返回文件对象所在父目录路径,如果没有父目录返回NULL
long length():获取文件的大小(字节数)。
long lastModified():获取文件或目录的最后修改时间。
获取目录下的文件和子目录:
File[] listFiles():返回目录下的所有文件和子目录的File对象数组。
boolean canExecute():判断文件是否可执行。
boolean setExecutable(boolean executable):设置文件是否可执行。
boolean setReadOnly():设置文件为只读。
boolean setWritable(boolean writable):设置文件是否可写。
示例: 使用createNewFile()
创建一个新文件,用delete()
删除该文件
如果读取过程中发生了IO错误,
InputStream
就没法正确地关闭,资源也就没法及时释放。因此需要用try ... finally
来保证InputStream
在无论是否发生IO错误的时候都能够正确地关闭
public class FileExample {
public static void main(String[] args) {
try {
// 创建一个新文件
File file = new File("example.txt");
boolean created = file.createNewFile();
if (created) {
System.out.println("文件创建成功");
} else {
System.out.println("文件已存在");
}
// 删除文件
boolean deleted = file.delete();
if (deleted) {
System.out.println("文件删除成功");
} else {
System.out.println("文件删除失败");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
通过键盘录入路径和文件名创建文件
public static void main(String[] args) {
System.out.println("输入独立文件名,回车结束");
Scanner scan = new Scanner(System.in);
String filename = scan.next();
File file = new File(filename);
if(file.exists()){ // exists() 判断文件或目录是否存在
System.out.println("文件已存在,无需创建");
}else {
try {
boolean boo = file.createNewFile();
if (boo){
System.out.println("文件名:" + file.getName());
System.out.println("文件路径:" + file.getParent());
System.out.println("文件大小:" + file.length() + "个字节");
System.out.println("是否隐藏:" + (file.isHidden()?"是":"否"));
System.out.println("是否可写入:" + (file.canWrite()?"是":"否"));
System.out.println("是否可执行:" + (file.canExecute()?"是":"否"));
}else {
System.out.println("文件创建失败!");
}
}catch (IOException e){
System.out.println("文件创建异常,请检查路径和权限。");
e.printStackTrace();
}
}
}
##
6.3 字节流字符流
6.3.1 字节流
FIleInputStream 类和 FileOutputStream 类 ,以字节形式从文件读取和写入数据。
写(读)入文件比较小可以采用逐字节写(读)入的方法。
写(读)入文件大小适中可以采用一次性写(读)入的方法。
如果写(读)文件大小超过了JVM的可用内存大小,需要采用少量多次的方法写(读)。
FileInputStream 的构造方法:
- FileInputStream(String fileName) // FileName 表示要打开的文件名。
- FileInputStream(File file) // file 对象表示要打开的文件对象。
实例化:FileInputStream ins = new FileInputStream("file");
如果文件不存在或没有权限访问,系统会抛出 FileNotFoundException 异常
FileOutputStream 类的构造方法:
- FileOutputStream(String fileName)
- FileOutputStream(File file)
- FileOutputStream(String fileName,boolean boo) // boolean 表示是否追加写入字节,默认false,若boo 为true,则会将内容写入已有内容之后,否则会将文件里原有内容覆写。
- FileOutputStream(File file,boolean boo)
实例化:FileOutputStream fos = new FileOutputStream("file",true);
如果没有文件,FileOutputStream 方法可以创建文件。
6.3.2 字符流
FileReader 类和 FileWriter 类,以字符形式从文件读取和写入数据。
- FileReader 类的构造方法:
- FileReader (String fileName) // FileName 表示要大开的文件名。
- FileReader(File file) // file 对象表示要打开的文件对象。
实例化:FileReader fre = new FileReader("file");
如果文件不存在或没有权限访问,系统会抛出 FileNotFoundException 异常
- FileWriter 类的构造方法:
- FileWriter(String fileName)
- FileWrite(File file)
- FileWrite(String fileName,boolean boo) // boolean 表示是否追加写入字节,默认false,若boo 为true,则会将内容写入已有内容之后,否则会将文件里原有内容覆写。
- FileWrite(File file,boolean boo)
实例化:FileWrite fwr = new FileWriter("file",true)
如果没有文件,FileOutputStream 方法可以创建文件。
// 从键盘输入文字并输出到控制台。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
try {
FileWriter fw = new FileWriter("E:/Test/Test.txt"); // 创建文件字符输出流
System.out.println("请输入内容,以回车结束:");
String s = scan.next();
fw.write(s);
fw.close();
FileReader fr = new FileReader("E:\\Test\\test.txt"); // 创建文件字符流输入对象
BufferedReader bfr = new BufferedReader(fr); // 创建缓存区读取对象
String s1 = ""; // 每行读取的缓存字符串变量
System.out.println("您输入的内容是:");
while ((s1 = bfr.readLine()) != null){ // m每次读一行,循环读取 null表示读取结束
System.out.println(s1);
}
bfr.close();
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
6.4 随机访问文件
Java 提供 RandomAccessFile 类,允许在文件的任何位置进行数据的读写。使用顺序流打开的文件称为顺序访问文件,对于经常需要修改的文件,使用RandomAccessFile 可以在文件的任何位置进行数据读写,使用RandomAccessFile 类打开的文件称为随机访问文件。
RandomAccessFile 继承 Object 对象类,实现 DataInput 和 DataOutput 接口。DataInput 接口定义了读取基本数据类型和字符串的方法,DataOutput 接口定义了输出基本数据类型和字符串的方法,所以 RandomAccessFile 类即可以作为输入流,又可以作为输出流。
随机访问文件由字节序列组成,定义一个文件指针的特殊标记这些字节的某个位置,文件的读写操作就是从文件当前位置指针指示的位置开始。
构造方法:
RandomAccessFile(File file,String Mode)
RandomAccessFile(String name;String mode)
file 是一个文件对象,mode 是访问方式,有两个值:r (只读),和 rw(读写)。
创建一个RandomAccessFile 对象实例:
RandomAccessFile rand = new RandomAccessFile("test.txt","r")
常用方法:
// 读取方法
int read(); 从文件流中读取字节,以int 返回。
int read(byte[] b) 从文件中读取最多b.length个字节,并将其存储在字节数组b中,返回实际读取的字节数,如果已到达文件末尾,则返回-1
int read(byte[] b, int off, int len) 从文件中读取最多len个字节,并将其存储在字节数组b中,从偏移量off开始存储,返回实际读取的字节数,如果已到达文件末尾,则返回-1。
long getFilePointer() 返回当前文件指针的位置。
void seek(long pos) 将文件指针移动到pos位置。
// 写入方法
void write(int b):将指定的字节写入文件。
void write(byte[] b):将字节数组b中的所有字节写入文件。
void write(byte[] b, int off, int len):将字节数组b中从偏移量off开始的len个字节写入文件。
void setLength(long newLength):设置文件的长度为newLength字节。
// 关闭方法:
void close():关闭文件流,释放资源。
// 其他方法
long length():返回文件的长度(以字节为单位)。
void skipBytes(int n):跳过n个字节,不读取。
boolean readBoolean():从文件中读取一个boolean值。
byte readByte():从文件中读取一个字节。
char readChar():从文件中读取一个字符。
double readDouble():从文件中读取一个double值。
float readFloat():从文件中读取一个float值。
int readInt():从文件中读取一个int值。
long readLong():从文件中读取一个long值。
short readShort():从文件中读取一个short值。
void writeBoolean(boolean v):将一个boolean值写入文件。
void writeByte(int v):将一个字节写入文件。
void writeChar(int v):将一个字符写入文件。
void writeDouble(double v):将一个double值写入文件。
void writeFloat(float v):将一个float值写入文件。
void writeInt(int v):将一个int值写入文件。
void writeLong(long v):将一个long值写入文件。
void writeShort(int v):将一个short值写入文件。
读取文件从从第6个下标开始的3个字符
public static void main(String[] args) {
try {
RandomAccessFile raf = new RandomAccessFile("E:/Test/test.txt","r"); // 创建随机访问对象
raf.seek(6); // 移动文件指针到下标6的位置
byte b[] = new byte[3]; //缓存数组
raf.read(b); //读取字节
String s = new String(b); //构建字符串
System.out.println(s); // 输出
raf.close(); //关闭文件流并释放资源
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
}
例子
复制文件
public class CopyImg {
// 蚂蚁搬家复制图片
public static void main(String[] args) {
String efile = "E://test.png";
String efile2 = "E://Test/test.png";
try {
FileInputStream fis = new FileInputStream(efile); // 输入流对象
FileOutputStream fos = new FileOutputStream(efile2, true); // 输出流对象
byte b[] = new byte[64]; // 缓存数组
int len = -1; // 计数器
while ((len = fis.read(b, 0, b.length)) != -1) { // 循环读取
fos.write(b, 0, len); // 循环写入
}
System.out.println("复制成功");
fos.close();
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e){
e.printStackTrace();
}
}
}
7. 反射
Java的反射是指程序在运行期可以拿到一个对象的所有信息,只要有类的名字,那么就可以通过反射机制来获得类的所有属性和方法,反射机制能够在运行时动态加载类,而不是在编译期。(反射在开发框架中用的最多)
使用反射技术,可以在JVM运行时:
- 判断任意一个对象所属的类。
- 判断任意一个类所具有的成员变量和方法。
- 任意调用一个对象的方法
- 构造任意一个类的对象
- 生成动态代理
反射可以应用于框架开发,它能够从配置文件中读取配置信息动态加载类、创建对象,以及调用方法和成员变量。
反射的优点: 可以动态的创建和使用对象。缺点: 反射是解释执行,对执行速度有影响。
Java.lang.Class
java.lang.class 是 Java 反射机制的基础,想要在运行期间获取一个类的相关信息,就需要使用Class 类。JVM 会给每个类都创建一个class 对象,程序运行时JVM 先检查要加载的类对应的 Class 对象是否已经加载,如果没有就要根据类名查找 .class 文件,并将其class 对象载入。
在Java 中除了int 等基本类型外,其他的类型都是class(例如String Object Runnable 等,包括interface),class 的本质是数据类型(Type),且是继承关系。
获取Java类的class对象的方式有3种:
(1) 调用对象的 getClass() 方法获取Class 对象
MyObject obj = new MyObject();
Class clazz = obj.getClass();
// 例如
String str = "Hello";
Class clz = str.getClass();
System.out.println("clz的类名是:" + clz.getName()); // clz的类名是:java.lang.String
(2) 根据类名.class静态变量获取Class 对象
Class clazz = MyObject.class;
//例如
Class clz1 = String.class;
(3) 知道一个class 的完整类名,根据Class 中的静态方法Class.forName()获取Class对象
Class clazz = Class.forName("MyObject");
//例如:
Class clz2 = Class.forName("java.lang.String");
![image-20230916141344574](C:\Users\Aulay\文档\Obsidian Vault\笔记\Java学习笔记.assets\image-20230916141344574.png)
java.lang.reflect包
java.lang.reflect包中提供了反射中用到的类方法,Field表示类中的属性,Method 表示类中的方法,Constructor表示类的构造函数,Annotation 表示类中的注解。
访问字段:
- Field getField(name):根据字段名获取某个public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
调用方法:
Method getMethod(name, Class...)
:获取某个public
的Method
(包括父类)Method getDeclaredMethod(name, Class...)
:获取当前类的某个Method
(不包括父类)Method[] getMethods()
:获取所有public
的Method
(包括父类)Method[] getDeclaredMethods()
:获取当前类的所有Method
(不包括父类)
调用构造方法
在Java 中不只可以用过 new 创建对象,使用反射也可以创建对象,有两种通过反射创建对象的方式。
第一种:使用Class 类的newInstance 方法,此方法通过调用类中定义的无参构造函数来创建对象。
调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
Class clazz = MyObject.class; // 先获取Class 对象
MyObject myObj = clazz.newInstance();
第二种:利用java.lang.refiect.Constructor 类中的 newInstance 方法,通过newInstance 方法调用有参构造函数和私有构造函数。
Constructor[] getConstructors():返回所有公有构造方法Constructor对象数组。
Constructor[] getDeclaredConstructors():返回所有构造方法Constructor对象数组。
Constructor getConstructor(Class... parameterTypes):根据参数列表返回一个共有Constructor对象。参数parameterTypes是Class数组,指定构造方法的参数列表。
Constructor getDeclaredConstructor(Class... parameterTypes):根据参数列表返回一个Constructor对象。参数parameterTypes同上。
Constructor<MyObject> constructor = Myobject.class.getConstructor();
MyObject myObj = constructor.newInstance();
8. 序列化 (待写)
序列化和反序列化
序列化就是将对象转换为为可存储或传输的形式的过程,一般以字节码或XML格式传输对象的。反序列化就是把过程反过来,将字节码或XML 编码格式的对象还原为对象的过程。
9. 动态代理
代理类:
在 Java 中,代理类(Proxy Class)通常是指由 java.lang.reflect.Proxy 类动态创建的代理类。这个类是用于支持动态代理的核心类之一。Proxy 类提供了一种创建动态代理类和实例的方法,这些代理类实现了一个或多个接口,并且由 InvocationHandler 的实现类来处理对代理类的方法的调用。
静态代理
静态代理就是指代理类由程序员自己编写,在编译期就确定了。
静态代理的用途:
通过代理对象控制真实对象的访问权限
避免创建大对象:通过一个代理小对象来代表一个真实的大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
动态代理的实现方式:
(1)、JDK动态代理:Java.lang.reflect 包中的 Proxy 类和 InvocationHandler 接口提供了生成动态代理的能力。
(2 )、CGLib动态代理:CGLib (Code GEneration Library) 是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象,从而实现对目标对象功能的扩展。
JDK动态代理的一个限制:使用动态代理的对象必须实现一个或多个接口,如果代理没有实现接口的类,则可以使用CGLib动态代理。
CGLib 是一个强大的高性能的代码生成包,可以在运行时扩展Java类与实现Java接口,被广泛的使用在许多AOP框架中,比如Spring AOP 、dynaop ,为他们提供 Interception (拦截)。
两种代理方式的区别:
使用JDK动态代理的对象必须实现一个或多个接口,而使用CGLib动态代理的对象无需实现接口,达到代理类无侵入的目的。
动态代理实现步骤:
(1)定义一个委托类和公共接口
(2)自定义一个类,
10. 注解
注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”。注解(Annotation)是Java语言用于工具处理的标注;注解可以配置参数,没有指定配置的参数使用默认值;如果参数名称是value
,且只有一个参数,那么可以省略参数名称。
10.1 基本注解
无论是哪一种注解,本质上都一种数据类型,是一种接口类型,注解都是使用@符号开头的。注解并不能改变程序运行的结果,不会影响程序运行的性能。有些注解可以在编译时给出提示或警告,有的注解可以在运行时读写字节码文件信息。
Java的注解可以分为三类:
第一类是由编译器使用的注解,例如:
@Override
:让编译器检查该方法是否正确地实现了覆写;@SuppressWarnings
:告诉编译器忽略此处代码产生的警告。
这类注解不会被编译进入.class
文件,它们在编译后就被编译器扔掉了。
第二类是由工具处理.class
文件使用的注解,比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class
文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。
第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,这也是最常用的注解。例如,一个配置了@PostConstruct
的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)。
10.2 元注解
元注解就是定义其他注解的注解(描述注解的注解),当自定义一个新的注解类型时,就可以使用元注解。
元注解有六个:
@Documented 如果在一个自定义注解中引用@Documented注解,那么该注解可以修饰代码元素(类、接口、成员变量和成员方法等),javadoc等工具可以提取这些注解信息。
@Target @Target注解用来指定一个新注解的适用目标
- 类或接口:
ElementType.TYPE
;- 字段:
ElementType.FIELD
;- 方法:
ElementType.METHOD
;- 构造方法:
ElementType.CONSTRUCTOR
;- 方法参数:
ElementType.PARAMETER
。
@Retention @Retention注解用来指定一个新注解的有效范围
- 仅编译期:
RetentionPolicy.SOURCE
;- 仅class文件:
RetentionPolicy.CLASS
;- 运行期:
RetentionPolicy.RUNTIME
@Inherited 用来指定一个新注解可以被继承。假定一个类A被该新注解修饰,那么这个A类的子类会继承该新注解。
@Repeatable 允许在相同的程序元素中重复注释,可重复的注释必须使用@Repeatable进行注释。
@Native @Native注解一个成员变量,指示这个变量可以被本地代码引用
10.3 自定义注解
10.3.1 声明注解
使用 @interface 关键字实现声明注解
// 生命注解
public @interface test{
String name(); // 可以定义成员变量
String name() default "你好"; // 可以添加默认值
}
值得注意的是,@interface声明一个注解类型,它前面的访问限定修饰符与类一样有两种:公有访问权限和默认访问权限。注解源文件与创建类一样,一个文件中可以声明多个注解,但只能有一个是公有访问权限,源文件命名与公有访问权限的注解的命名要一致。
注解常用在类,方法,字段上,一般配合元注解使用。
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface test{
}
11. 集合类(重点)
集合就是对象的容器,基于某种数据结构的数据容器,如数组(Array)、堆(Heap)、栈(Stack)、映射(Map)等结构。目的当获取的对象多了,使用集合这个容器把对象管理起来。
Java 中有丰富的集合接口和类,来自于java.util 包,Java中的集合类型分为 Collection 和 map,Set和List 属于Collection 的子接口,每一种集合接口描述一种数据类型。
11.1 集合方法
11.1.1. List
List 集合的元素是有序的,且可重复出现。List 集合只关心集合是否有序,不关系集合元素是否重复。
例如数组:序号从0开始,有序排列
num | value |
---|---|
0 | 张三 |
1 | 李四 |
2 | 王二 |
3 | 张三 |
4 | 麻子 |
List 集合的实现类有 ArrayList 和 LinkedList ,它们的区别:
ArrayList是基于动态数组数据结构的实现,LinkedList是基于链表数据结构的实现。
ArrayList访问元素速度优于LinkedList,LinkedList占用的内存空间比较大,但LinkedList在批量插入或删除数据时优于ArrayList。
List 集合接口的的方法有:
查询元素:
- indexOf(Object o):从前往后查找List集合元素,返回第一次出现指定元素的索引,如果此列表不包含该元素,则返回-1。
- lastIndexOf(Object o):从后往前查找List集合元素,返回第一次出现指定元素的索引,如果此列表不包含该元素,则返回-1。
操作元素:
- get(int index):返回List集合中指定位置的元素。
- set(int index, Object element):用指定元素替换List集合中指定位置的元素。
- add(Object element):在List集合的尾部添加指定的元素。该方法是从Collection集合继承过来的。
- add(int index, Object element):在List集合的指定位置插入指定元素。
- remove(int index):移除List集合中指定位置的元素。
- remove(Object element):如果List集合中存在指定元素,则从List集合中移除第一次出现的指定元素。该方法是从Collection集合继承过来的。
- clear():从List集合中移除所有元素。该方法是从Collection集合继承过来的。
判断元素:
isEmpty():判断List集合中是否有元素,没有返回true,有返回false。该方法是从Collection集合继承过来的。
contains(Object element):判断List集合中是否包含指定元素,包含返回true,不包含返回false。该方法是从Collection集合继承过来的。
其他:
size():返回List集合中的元素数,返回值是int类型。该方法是从Collection集合继承过来的。
具体代码:
public static void main(String[] args) {
List list = new ArrayList();
//String b = "B";
list.add("A");
list.add("b");
list.add("C");
list.add("b");
list.add("D");
list.add("E");
// 打印元素个数
System.out.println("size = " + list.size()); // 5
// 打印集合
System.out.println(list); // [A, b, C, b, D, E]
// 从前往后查找元素B
System.out.println(list.indexOf("b")); // 1
// 从后往前查找元素
System.out.println(list.lastIndexOf("b")); // 1
// 删除集合中第一个B 元素
list.remove("b");
System.out.println(list);// [A, b, C, D, E]
// 删除第四个元素
list.remove(4);
System.out.println(list);// [A, b, C, D]
// 替换元素,替换第二个元素
System.out.println("替换前:" + list); // 替换前:[A, C, b, D]
list.set(1,"G");
System.out.println("替换后:" + list); // 替换后:[A, G, b, D]
// 判断集合中是否包含E元素
System.out.println(list.contains("E")); // true
// 判断集合是否为空
System.out.println(list.isEmpty()); // false
//清空集合
list.clear();
System.out.println(list); // []
list.add(1); // 自动装箱
list.add(3);
int item = (Integer)list.get(0); // 自动拆箱
}
遍历集合:
遍历就是沿着某条搜索路线,依次访问树中每个节点。
public static void main(String[] args) {
List list = new ArrayList();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
// for 循环遍历
System.out.println("------ 使用for循环遍历------");
for (int i = 0;i < list.size();i++){
System.out.print(list.get(i));
}
System.out.println();
// for-each 循环遍历(比for语句更加简洁,也称为增强for循环)
/** 语法格式:
for(元素类型t 元素变量x:遍历对象obj ){
应用x的java语句
.............
}
*/
System.out.println("------ 使用for-each循环遍历------");
for (Object item:list){
String str = (String) item; // 强制转换为String类型
System.out.print(str);
}
System.out.println();
// 使用迭代器遍历 Java提供了多种迭代器,List集合可以使用Iterator和ListIterator迭代器。
/*
Iterator 主要有3个方法
next(): 返回迭代器的下一个元素,并且更新迭代器的状态
hasNext(): 用于检测集合中是否还有元素,可以迭代,有返回true,没有返回false。
remove(): 删除迭代器返回的元素
*/
System.out.println("------ 使用迭代器遍历------");
Iterator it = list.iterator(); // 调用iterator()方法返回迭代器对象
while (it.hasNext()){ // 调用迭代器hasNext()方法
Object item = it.next(); // 调用迭代器的next()返回迭代的下一个元素,该方法返回的Object类型需要强制转换为String类型
String s = (String) item;
System.out.print(s);
}
}
11.1.2 Set
Set 集合是无序的,不能重复的相同类型元素构成的集合。Set接口直接实现类主要是HashSet,HashSet是基于散列表数据结构的实现,Set接口也继承自Collection接口。
Set 接口的方法:
操作元素
- add(Object element):在Set集合的尾部添加指定的元素。该方法是从Collection集合继承过来的。
- remove(Object element):如果Set集合中存在指定元素,则从Set集合中移除该元素。该方法是从Collection集合继承过来的。
- clear():从Set集合中移除所有元素。该方法是从Collection集合继承过来的。
判断元素
- isEmpty():判断Set集合中是否有元素,没有返回true,有返回false。该方法是从Collection集合继承过来的。
- contains(Object element):判断Set集合中是否包含指定元素,包含返回true,不包含返回false。该方法是从Collection集合继承过来的。
其他
- iterator():返回迭代器(Iterator)对象,迭代器对象用于遍历集合。该方法是从Collection集合继承过来的。
- size():返回Set集合中的元素数,返回值是int类型。该方法是从Collection集合继承过来的。
public static void main(String[] args) {
Set set = new java.util.HashSet();
set.add("A");
set.add("B");
set.add("C");
set.add("D");
set.add("E");
System.out.println(set.size());// 5
System.out.println(set); // [A, B, C, D, E]
set.remove("B");
System.out.println(set); // [A, C, D, E]
// 判断是否包含B
System.out.println(set.contains("B")); // false
// 集合是否空
System.out.println(set.isEmpty()); // false
set.clear();
System.out.println(set); // []
}
遍历数组
public static void main(String[] args) {
Set set = new java.util.HashSet();
set.add("A");
set.add("B");
set.add("C");
set.add("D");
set.add("E");
System.out.println("------ 使用for-each循环遍历------");
for (Object item : set) {
String s = (String) item;
System.out.println(s);
System.out.println("------ 使用迭代器遍历------");
Iterator it = set.iterator(); // 返回迭代器对象
while (it.hasNext()) { // 调用迭代器hasNext()方法可以判断集合中是否还有元素可以迭代,有返回true,没有返回false
Object item2 = it.next(); // 调用迭代器的next()返回迭代的下一个元素,该方法返回的Object类型需要强制转换为String类型
String s1 = (String) item2;
System.out.print(s1);
}
}
}
11.1.3 Map
Map集合是K-V 结构,有两个集合构成,一个是键(key)集合,一个是值(value)集合。允许按照某个键来访问元素,键集合是 Set 类型的集合,不能有重复的元素。值集合是Collection类型,可以有重复的元素。Map集合中的键和值是成对出现的。
Map接口直接实现类主要是HashMap,HashMap是基于散列表数据结构的实现。
Map 接口方法:
操作元素:
- get(Object key):返回指定键所对应的值;如果Map集合中不包含该键值对,则返回null。
- put(Object key, Object value):指定键值对添加到集合中。
- emove(Object key):移除键值对。clear():移除Map集合中所有键值对。
判断元素
- isEmpty():判断Map集合中是否有键值对,没有返回true,有返回false。
- containsKey(Object key):判断键集合中是否包含指定元素,包含返回true,不包含返回false。
- containsValue(Object value):判断值集合中是否包含指定元素,包含返回true,不包含返回false。
查看集合
- keySet():返回Map中的所有键集合,返回值是Set类型。
- values():返回Map中的所有值集合,返回值是Collection类型。size():返回Map集合中键值对数。
public static void main(String[] args) {
Map map = new HashMap();
// 指定键值对添加到集合中
map.put(001,"Tom");
map.put(002,"Bob");
map.put(003,"Jerry");
map.put(004,"Tim");
map.put(005,"Bob"); // Bob 值重复
map.put(001,"Tap"); // 001键已存在,替换原Tom
System.out.println(map.size()); // 5
System.out.println(map); // {1=Tap, 2=Bob, 3=Jerry, 4=Tim, 5=Bob}
System.out.println(map.get(003)); // Jerry
System.out.println(map.containsKey(002)); // true
System.out.println(map.containsValue("Tim")); // true
}
遍历集合
public static void main(String[] args) {
Map map = new HashMap();
map.put(001,"Tom");
map.put(002,"Bob");
map.put(003,"Jerry");
map.put(004,"Tim");
Set keys = map.keySet(); // 获取键集合
for (Object key : keys){
int ikey = (Integer) key; // 自动拆箱
String value = (String) map.get(ikey); // 自动装箱
System.out.printf("key=%d - value=%s \n", ikey, value);
System.out.println("-----------------");// 使用迭代器遍历
Collection values = map.values(); // 获取值集合
Iterator it = values.iterator();
while (it.hasNext()){
Object item = it.next();
String s = (String) item;
System.out.print(s+ " ");
}
}
}
11.2 集合类问题
Q:HashMap的数据结构
Java 中有两种简单的数据结构:数组和链表。数组的特点是容易寻址。插入和删除困难。链表的特点是寻址困难,插入和删除容易。
HashMap(哈希表)也是一种数据结构,用于存储键值对。HashMap通过哈希函数将键映射到数组中,以实现快速的插入、查找和删除操作。
Hash是把任意长度的输入通过散列算法转换成固定长度的输出,输出的之叫做散列值。所有的散列函数都有一个基本特性:如果同一散列函数计算出的散列值不同,输入值肯定不同;如果同一散列函数计算出的散列值相同,输入值不一定想同。两个不同的输入值如果用同一散列函数计算出的散列值相同,这种现象叫做碰撞。
12. 泛型
泛型就是代码模板,能最大限度地提高代码的复用性。泛型的本质是参数化类型,可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
泛型的好处:
提高Java程序的类型安全,不需要强制类型转换,它通过编译器对类型进行检查,避免了不必要的装箱、拆箱操作,提高程序的性能。
//无泛型
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
list.add("123");
list.add(123);//编译正常
}
//有泛型
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("123");
list.add(123);//编译报错
}
//没有泛型的代码段需要强制转换
public static void main(String[] args) {
List list = new ArrayList();
list.add(123);
Integer integer = (Integer) list.get(0);
}
//有泛型的代码段不需要强制转换
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(1);
int s = list.get(0);
}
12.1 使用泛型
在集合中,需要对Object 类型的元素进行强制转换,比如List 中的 String str = (String) item;
语句需要转换成String 类型。强制转换可能会有ClassCastException异常,所以可以使用泛型处理集合的数据类型问题。
public static void main(String[] args) {
List<String> list = new ArrayList<String>(); //List和ArrayList 后面添加了<String>
list.add("Tom");
list.add("Jerry");
for (Object item:list){
//String str = (String) item; // 强制转换为String类型
//System.out.print(str);
System.out.println(item);
}
}
class Person<T>{
private T value;
public T getValue() {
return value;
}
public Person(T value) {
this.value = value;
}
public void setValue(T value) {
this.value = value;
}
}
public class GenericClass {
public static void main(String[] args) {
Person<String> name = new Person<String>("张三");
System.out.println(name.getValue()); // 张三
Person<Integer> age = new Person<>(18);
System.out.println(age.getValue()); // 18
}
}
泛型类
泛型类型必须是引用类型,泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口,List、Set、Map就是用了泛型的容器类。
//格式
public class 类名 <泛型类型1,...> {
}
// 定义泛型类Person
class Person<T>{
private T value;
public T getValue() {
return value;
}
public Person(T value) {
this.value = value;
}
public void setValue(T value) {
this.value = value;
}
}
// 使用泛型
public static void main(String[] args) {
Person<String> name = new Person<String>("张三");
System.out.println(name.getValue()); // 张三
Person<Integer> age = new Person<>(18);
System.out.println(age.getValue()); // 18
}
泛型方法
泛型方法,是在调用方法的时候指明泛型的具体类型。
//格式
public <泛型类型> 返回类型 方法名(泛型类型 变量名) {
}
泛型接口
通过使用泛型接口,可以编写更通用、更灵活的代码,使得接口在不同的场景下都能够被有效地使用
泛型接口的语法与泛型类类似,通常在接口名称后面使用尖括号(<>)来声明类型参数,并在接口中的方法中使用这些参数。
MyInterface 是一个泛型接口,它有一个类型参数 T。接口中定义了两个方法 getValue() 和 setValue(T value),它们都使用了类型参数 T。
interface MyInterface<T> {
T getValue();
void setValue(T value);
}
使用泛型接口时,可以在实现接口的类中指定具体的类型参数。
class MyClass implements MyInterface<Integer> {
private Integer value;
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
}
MyClass 类实现了 MyInterface 接口,并指定了 Integer 类型作为类型参数。因此,getValue() 方法返回一个 Integer 类型的值,setValue(Integer value) 方法接受一个 Integer 类型的参数。
13. 枚举
枚举(enumeration),就是一组常量的集合,把一个一个的对象列举出来,就是枚举类。枚举是一种特殊的类,里面只包含一组有限的特定的对象。
实现方式:自定义枚举、使用enum关键字实现枚举。
13.1 自定义枚举
public class Enumeration {
public static void main(String[] args) {
System.out.println(Season.AUTUMN);
System.out.println(Season.SPRING);
System.out.println(Season.SUMMER);
System.out.println(Season.WINTER);
}
}
// 自定义枚举实现
class Season{
private String name;
private String desc;
// 定义4个对象,枚举通常使用全部大写的命名规范
// 加入final 修饰符,优化使用。
public static final Season SPRING = new Season("春天", "温暖");
public static final Season WINTER = new Season("冬天", "寒冷");
public static final Season AUTUMN = new Season("秋天", "凉爽");
public static final Season SUMMER = new Season("夏天", "炎热");
// 构造器私有化,防止被new
// 类里创建一组固定对象
// 不需要set()方法。防止属性被修改
// 对外暴露对象使用 public final static 修饰符
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
13.2 Enum 关键字实现枚举
- toString:Enum 类已经重写过了,返回的是当前对象 名,子类可以重写该方法,用于返回对象的属性信息
- name:返回当前对象名(常量名),子类中不能重写
- ordinal:返回当前对象的位置号,默认从 0 开始
- values:返回当前枚举类中所有的常量
- valueOf:将字符串转换成枚举对象,要求字符串必须 为已有的常量名,否则报异常!
- compareTo:比较两个枚举常量,比较的就是编号
14. 多线程(重点)
概念
进程:进程是运行中的程序,操作系统为进程分配内存空间。
进程是程序的一次执行过程,是一个动态过程,有它自身的产生,存在,销毁。
线程:线程是由进程创建的,是进程的一个实体。一个进程可以拥有多个线程。
并发:同一时刻多个任务交替执行,简单说单核cpu实现的多任务就是并发。
并行:同一时刻,多个任务同时执行,多核cpu可以实现并行。
创建新线程
创建一个多线程的方法,实例化一个Thread
实例,然后调用它的start()
方法
public class Main {
public static void main(String[] args) {
Thread t = new Thread();
t.start(); // 启动新线程
}
}
方法一:从Thread
派生一个自定义类,然后覆写run()
方法
public class ThreadTest {
public static void main(String[] args) {
Thread t = new myThread();
t.start(); // 启动新线程
}
}
class myThread extends Thread {
@Override
public void run(){
System.out.println("启动新线程");
}
方法二:创建Thread
实例时,传入一个Runnable
实例
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start(); // 启动新线程
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("start new thread!");
}
}
简单示例:
public class Test {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
// 线程1的任务,循环输出0~9的数字
for (int i = 0; i < 10; i++) {
System.out.println("线程1:" + i);
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
// 线程2的任务,循环输出10~19的数字
for (int i = 10; i < 20; i++) {
System.out.println("线程2:" + i);
}
}
});
// 启动线程
thread1.start();
thread2.start();
}
}
输出结果:
线程2:10
线程2:11
线程2:12
线程2:13
线程2:14
线程2:15
线程2:16
线程1:0
线程1:1
线程1:2
线程1:3
线程1:4
线程1:5
线程1:6
线程1:7
线程1:8
线程1:9
线程2:17
线程2:18
线程2:19
进程已结束,退出代码为 0
线程操作
线程同步
15. 网络编程
InetAddress 类
方法:
- 获取本机InetAddress对象 getLocalHost();
- 根据指定主机/域名获取ip地址对象 getByName;
- 获取InetAddress对象的主机名 getHostName();
- 获取InetAddress对象的地址 getHostAddress();
package com.net;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddress01 {
public static void main(String[] args) throws UnknownHostException {
// 获取本机的InetAddress 对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost); // 输出机器的名称和ip地址
// 根据指定的主机名获取对象
InetAddress host1 = InetAddress.getByName("Laptop");
System.out.println("host1: " + host1); // 输出机器的名称和ip地址
// 根据域名返回一个 InetAddress 对象
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println("host2: " + host2); // 域名/ip
String hostAddress = host2.getHostAddress();
System.out.println(hostAddress); // 返回ip
String hostName = host2.getHostName();
System.out.println(hostName); // 返回域名
}
}
Socket 编程
套接字(Socket),通信的两端都要有Socket ,底层是TCP /IP协议 。
TCP/UDP
TCP协议,传输控制协议
使用TCP 需要先建立连接,形成数据传输通道,在传输前有3次数据握手,可靠。
在连接中可以传输大量数据,但是传输结束后需要释放已经建立的连接,效率低。
UDP协议,用户数据协议
数据,源,目的地封装成数据包,不需要建立连接
每个数据报大小限制在 64k 内,不适合传输大量数据。
不需要
客户端,服务端
客户端与服务端连接
// 服务端代码
public class SocketTCPServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务的监听9999端口,等待连接.....");
// 如果有客户端连接,返回Socket 对象
Socket socket = serverSocket.accept(); // 通过accept() 可以返回多个客户端(并发)
System.out.println("服务端Socket返回: " + socket.getClass());
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(bytes)) != -1){
System.out.println(new String(bytes,0,readLen));
}
inputStream.close();
socket.close();
serverSocket.close();
}
}
// 客户端代码
public class SocketTCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(),9999); // 连接本机9999端口
System.out.println("客户端 Scoket返回: " + socket.getClass());
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello,Server".getBytes(StandardCharsets.UTF_8));
outputStream.close();
socket.close();
System.out.println("客户端关闭");
}
}
客户端向服务端发送消息,服务端收到消息返回一条消息给客户端(字节流)
// 服务端代码
public class SocketTCPServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务的监听9999端口,等待连接.....");
// 如果有客户端连接,返回Socket 对象
Socket socket = serverSocket.accept(); // 通过accept() 可以返回多个客户端(并发)
System.out.println("服务端Socket返回: " + socket.getClass());
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(bytes)) != -1){
System.out.println(new String(bytes,0,readLen));
}
// 获取Socket 相关联的输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello,client".getBytes());
socket.shutdownOutput(); // 结束标记
// 关闭流
outputStream.close(); // 这个流也要关闭
inputStream.close();
socket.close();
serverSocket.close();
}
}
// 客户端代码
public class SocketTCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(),9999); // 连接本机9999端口
System.out.println("客户端 Scoket返回: " + socket.getClass());
OutputStream outputStream = socket.getOutputStream();
// 输出流写入数据到数据通道
outputStream.write("Hello,Server".getBytes(StandardCharsets.UTF_8));
// 设置结束标记
socket.shutdownOutput();
// 获取和Socket相关联的输入流,读取数据(字节流)
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readlen = 0;
while ((readlen = inputStream.read(buf)) != -1){
System.out.println(new String(buf,0,readlen));
}
// 关闭流
outputStream.close();
socket.close();
System.out.println("客户端关闭");
}
}
客户端向服务端发送消息,服务端收到消息返回一条消息给客户端(字符流)
// 服务端代码
public class SocketTCPServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务的监听9999端口,等待连接.....");
// 如果有客户端连接,返回Socket 对象
Socket socket = serverSocket.accept(); // 通过accept() 可以返回多个客户端(并发)
System.out.println("服务端Socket返回: " + socket.getClass());
InputStream inputStream = socket.getInputStream();
// IO 读取,使用字符流,使用InputStreamReader 将 inputStream 转成字符流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);
// 获取Socket 相关联的输出流
OutputStream outputStream = socket.getOutputStream();
// 字符输出流回复信息
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("Hello Client 字符流");
bufferedWriter.newLine(); // 插入换行符,表示结束
bufferedWriter.flush();
// 关闭流
bufferedReader.close(); // 这个流也要关闭
bufferedWriter.close();
socket.close();
serverSocket.close();
}
}
// 客户端代码
public class SocketTCP03Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(),9999); // 连接本机9999端口
System.out.println("客户端 Scoket返回: " + socket.getClass());
OutputStream outputStream = socket.getOutputStream();
// 输出流写入数据到数据通道 使用字符流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("Hello,Server 字符流");
bufferedWriter.newLine(); // 换行符,表示写入的内容结束,另一端要用readLine() 读
bufferedWriter.flush(); // 使用字节流手动刷新,负责数据不会写入数据通道
// 获取和Socket相关联的输入流,读取数据(字节)
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);
// 关闭流
bufferedReader.close();
bufferedWriter.close();
socket.close();
System.out.println("客户端关闭");
}
}
UDP编程
接收端A
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
// 创建 DatagramSocket 对象 ,端口 9999接受数据
DatagramSocket socket = new DatagramSocket(9999);
// 构建 DatagramPacket 对象,准备接受
// UDP 协议的一个数据包最大是64k
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
// 调用接收方法
System.out.println("接收端A等待接受数据...");
socket.receive(packet); // 如果没有数据发送过来,会在这里阻塞
// 拆包,取出数据
int length = packet.getLength();// 实际接受到得数据长度
byte[] data = packet.getData();
String s = new String(data, 0, length);
System.out.println(s);
// 回复给B端
// 封装发送数据到DatagramPacket 对象
data = "Hello 已收到消息".getBytes();
//
packet = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.124.111"),9998);
socket.send(packet);
// 关闭流
socket.close();
System.out.println("A端退出");
}
}
// 发送端B
public class UDPSenderB {
public static void main(String[] args) throws IOException {
// 创建DatagramSocket ,准备接收数据
DatagramSocket socket = new DatagramSocket(9998);
// 封装发送数据到DatagramPacket 对象
byte[] data = "Hello ".getBytes();
//
DatagramPacket packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.124.111"),9999);
socket.send(packet);
// 接受A回复得消息
// 构建 DatagramPacket 对象,准备接受
// UDP 协议的一个数据包最大是64k
byte[] buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
// 调用接收方法
socket.receive(packet); // 如果没有数据发送过来,会在这里阻塞
// 拆包,取出数据
int length = packet.getLength();// 实际接受到得数据长度
data = packet.getData();
String s = new String(data, 0, length);
System.out.println(s);
// 关闭流
socket.close();
System.out.println("B端退出");
}
}
16. 数据库编程
SQL 语句
- 定义语句:create 表,库....
- 操作语句:insert (增加) , update (修改) ,delete (删除)
- 查询语句:select
- 控制语句:grant revoke (权限)
创建数据库
#使用指令创建数据库
CREATE DATABASE db01;
#删除数据库指令
DROP DATABASE db01
#创建一个使用 utf8 字符集的 db02 数据库
CREATE DATABASE db02 CHARACTER SET utf8
#创建一个使用 utf8 字符集,并带校对规则的 hsp_db03 数据库
CREATE DATABASE db03 CHARACTER SET utf8 COLLATE utf8_bin
#校对规则 utf8_bin 区分大小 默认 utf8_general_ci 不区分大小写
#select 查询 * 表示所有字段 FROM 从哪个表
#WHERE 从哪个字段 NAME = 'tom' 查询名字是 tom
SELECT *
FROM t1
WHERE NAME = 'tom'
创建表
CREATE TABLE `user` (
id INT,
`name` VARCHAR(255),
`password` VARCHAR(255),
`birthday` DATE)
CHARACTER SET utf8 COLLATE utf8_bin ENGINE INNODB;
查,删
#查看当前数据库服务器中的所有数据库
SHOW DATABASES
#查看已创建的 db01 数据库的定义信息
SHOW CREATE DATABASE `db01`
#在创建数据库,表的时候,为了规避关键字,可以使用反引号解决
#删除已创建的 db01 数据库
DROP DATABASE db01
备份数据库
备份语句:
mysqldump -u 用户名 -p -B 数据库1 数据库2 数据库n > 文件名.sql
恢复语句:
Source 文件名.sql
# 备份db02 数据库
mysqldump -u root -p -B db02 > d:\\backup.sql
备份数据的表:
mysqldump -u 用户名 -p -B 数据库 表1 表2 表n > 文件名.sql
单元测试
什么是单元测试呢?单元测试就是针对最小的功能单元编写测试代码。Java程序最小的功能单元是方法,因此,对Java程序进行单元测试就是针对单个Java方法的测试。
测试驱动开发:先编写接口,紧接着编写测试。编写完测试后,我们才开始真正编写实现代码。在编写实现代码的过程中,一边写,一边测,什么时候测试全部通过了,那就表示编写的实现完成了
编写接口
│
▼
编写测试
│
▼
┌─> 编写实现
│ │
│ N ▼
└── 运行测试
│ Y
▼
任务完成
JUnit
JUnit是一个开源的Java语言的单元测试框架,专门针对Java设计,使用最广泛。JUnit是事实上的单元测试的标准框架,任何Java开发者都应当学习并使用JUnit编写单元测试。
当我们已经编写了一个xxx.java
文件后,我们想对其进行测试,需要编写一个对应的xxxTest.java
文件,以Test
为后缀是一个惯例,并分别将其放入src
和test
目录中。最后,在添加`JUnit 的库:
使用Fixture
JUnit提供了编写测试前准备、测试后清理的固定代码,我们称之为Fixture。
- 对于实例变量,在
@BeforeEach
中初始化,在@AfterEach
中清理,它们在各个@Test
方法中互不影响,因为是不同的实例; - 对于静态变量,在
@BeforeAll
中初始化,在@AfterAll
中清理,它们在各个@Test
方法中均是唯一实例,会影响各个@Test
方法。
感谢博主分享,笔记很有用。