Java基础笔记
本文最后更新于 1065 天前,其中的信息可能已经有所发展或是发生改变。

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 标识符

  1. 包含:类名、方法名、变量名
  2. 标识符命名规则:
    /*
    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关键字

image-20210504231607254

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 , 11

    int a2 = 10;
    int b2 = a2++;
    System.out.println(a2 + " , " + b2); // 11 , 10

    int 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.逻辑运算符

&(与)、&&(短路与)、|(或)、||(短路或)、!(非)、^(异或)

注意:逻辑运算符操作的都是布尔类型的变量

image-20210504233141514

  • &、&&:两边均为 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;

运算规则:

  1. 如果条件表达式为true,运算后的结果是表达式1;
  2. 如果条件表达式为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 的值也是存储在栈上的,但是不需要再从堆中申请内存。

image-20230727235322047

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来定义一个类。类是一种抽象的数据类型,用于描述具有相同属性和行为的对象的集合。

类的定义通常包括以下几个部分:

  1. 类名:用于标识类的名称,通常采用大写字母开头的驼峰命名法,例如:Person、Car等。
  2. 属性:用于描述类的特征或状态,也称为成员变量。属性可以是各种数据类型,例如整型、字符串、布尔型等。
  3. 方法:用于描述类的行为或功能,也称为成员方法。方法可以包含一系列的语句,用于实现特定的功能。
  4. 构造方法:用于创建类的对象,并进行初始化操作。构造方法的名称与类名相同,没有返回类型,并且在创建对象时自动调用。

声明类的语法格式:

[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 构造函数

image-20231217224721103

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的父类

向下转型:

子类类型 引用名 = (子类类型) 父类引用;

  1. 只能强转父类的引用,不能强转父类的对象
  2. 要求父类的引用必须指向的是当前目标类型的对象
  3. 当向下转型后,可以调用子类类型中所有的成员

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。抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类()

  1. 抽象类在框架和设计模式使用比较多。

抽象类,不能被实例化。

抽象类不一定包含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 异常处理机制

image-20230823121115924

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...):获取某个publicMethod(包括父类)
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有publicMethod(包括父类)
  • 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为后缀是一个惯例,并分别将其放入srctest目录中。最后,在添加`JUnit 的库:

image-20240305191432071

使用Fixture

JUnit提供了编写测试前准备、测试后清理的固定代码,我们称之为Fixture。

  1. 对于实例变量,在@BeforeEach中初始化,在@AfterEach中清理,它们在各个@Test方法中互不影响,因为是不同的实例;
  2. 对于静态变量,在@BeforeAll中初始化,在@AfterAll中清理,它们在各个@Test方法中均是唯一实例,会影响各个@Test方法。

Java新特性

Java 17

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇