java学习笔记

java学习

概论

运行java程序的命令java OneJavaApp

快捷键

Alt+F7查找类或方法在哪被使用
相当于eclipse的ctrl+shif+H,但是速度快得多。

基本语法

命名规则

  1. 类名:首字母大写
  2. 方法名和变量名:第一个单词的首字母小写外,其他单词的首字母都要大写,例如getRecordName()
  3. 常量名:每个单词的每个字母都要大写
  4. 包名:每个字母都要小写

常量与变量

常量

常量要用关键字final修饰,通常情况下立即为其赋值,不可被修改

格式:final 常量类型 常量标识符;
final int NUM1 = 14, NUM2 = 25, NUM3 = 36;

为float型常量赋值时,需要在数值的后面加上一个字母“F”或“f”

变量

格式:变量类型 变量标识符;
int partyMemberAge ;

变量数组
image-20221007152802498
要加一条books[i]=new Book();用setName()方法才不会报错

数据类型

基本数据类型

在内存中存入的是数值本身

  1. 整数型:byte、short、int、long
    在为long型常量或变量赋值时,需要在所赋值的后面加上一个字母“L”(或“l”)
    image-20220913225014284
  2. 浮点数型:float、double
    double型后跟D/d,float后跟F/f
    image-20220913225123245
  3. 字符型:char(2字节)
  4. 布尔型:boolean
    可以将逻辑表达式赋值给boolean型变量
  5. 大数值:可以处理包含任意长度数字序列的数值。不能使用人们熟悉的算术运算符(如: + 和*)处理大数值。而需要使用大数值类中的add 和multipl y 方法
    • BigJnteger 类实现了任意精度的整数运算
    • gDecima l 实现了任意精度的浮点数运算。
引用数据类型

在内存中存入的是引用数据的存放地址,并不是数据本身

  1. 类引用:Object object = null; // 声明一个Object类的引用变量
  2. 接口引用:List list = null;
  3. 数组引用:int[] months = null;
String
  • 构造方法:
    • String():初始化新创建的String对象,使其表示空字符序列
    • String(String original):初始化新创建的String对象,使其表示与参数相同的字符序列
  • 成员方法:
    • int length():返回此字符串的长度
    • boolean equals(Object anObject):将此字符串与指定的对象进行比较
    • boolean equalsIgnoreCase(String anotherString):将此String与另-个String比较,忽略了大小写
    • String trim():返回一个字符串,其值为此字符串,删除了所有前导和尾随空格
Integer
自动装箱和拆箱
  • 装箱:把基本数据类型转换为对应的包装类类型
  • 拆箱:把包装类类型转换为对应的基本数据类型

image-20230721094636794

区别
  1. 组成
    • 基本数据类型:一个具体的数字、字符或逻辑值
    • 引用数据类型:一个复杂的数据结构的实例
  2. java虚拟机处理方式
    • 基本数据类型:根据变量的实际类型为其分配内存空间
    • 引用数据类型:存放对象在堆内存中存放的地址(原因:引用传递和动态内存分配、对象的共享和重用)

image-20220913230235848

image-20220913230320153

类型转换
自动类型转换
  1. 含有int、long、float或double型的数据:Java首先会将所有数据类型较低的变量自动转换为表达式中最高的数据类型,然后再进行计算
  2. 含有byte、short或char型的数据:将所有变量的类型自动转换为int型
强制类型转换

int i = (int) 7.5;

image-20220913230817718

数组

声明:数组类型[] 数组标识符; 或 数组类型 数组标识符[];
二维多加括号

创建:new/{}

int[] months = new int[12]; //months的长度为12

boolean[] members = { false, true, true, false }; //members的长度为4

数组拷贝

允许将一个数组变量拷贝给另一个数组变最。这时,两个变量将引用同一个数组:

命令行参数

前面已经看到多个使用Java 数组的示例。每一个Java 应用程序都有一个带String arg[]参数的main 方法。这个参数表明main 方法将接收一个字符串数组,也就是命令行参数。

输入输出
  1. 输出

    1. System.out.println(1111);//换行打印
    2. System.out.print(1111);//不换行打印
    3. System.out.write(2222);//字节输出
    4. System.out.printf(“%+8.3f\n”, 3.14);//按格式输出
  2. 输入

    import java.util.Scanner;
    
    public class Main {
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in); // 创建Scanner对象
            System.out.print("Input your name: "); // 打印提示
            String name = scanner.nextLine(); // 读取一行输入并获取字符串
            System.out.print("Input your age: "); // 打印提示
            int age = scanner.nextInt(); // 读取一行输入并获取整数
            System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化输出
        }
    }
    

    多组输入:while (!(str = in.nextLine()).equals(""))

运算符与流程控制

运算符

与运算:&或者&&

u运算符“&&”只有在其左侧为true时,才运算其右侧的逻辑表达式,否则直接返回运算结果false。

u运算符“&”无论其左侧为true或false,都要运算其右侧的逻辑表达式,最后才返回运算结果。

或运算:|或者||

区别同上

位运算符:~、&(与)、|(或)、^(异或)

image-20220914090029882

移位运算符

<<、>>

“>>>”(无论操作数的最高位是0还是1,右移后的空位都用0填充。这与有符号右移运算符 >> 的区别在于,有符号右移运算符在右移时会保留原来的符号位,即如果操作数是负数,右移后的空位将用1填充。)

对象运算符:对象运算符用来判断对象是否为某一类型,运算结果为boolean型

语法:对象标识符 instanceof 类型标识符System.out.println(date instanceof java.util.Date);

自动递增、递减运算符

优先级

image-20220914092303548

条件语句

  1. if

  2. switch
    实例

    switch(choose) {
                    case 1: guess_number();break;
                    case 2: Narcissistic_number();break;
                    case 3: print_thombus(); break;
                    case 4: print_expression();  break;
                    case 5: System.out.println("退出菜单");
                        System.exit(0);
                        break;
                    default:System.out.println("invalid choose");
                }
    

    switch-case-default

循环语句

for

while

do-while

跳转语句

break

continue

return

对象与类

声明

[private、default、protected、public] class <类名> [extends 父类名][implements 接口列表]{}

类之间的关系
  • 依赖:如果一个类的方法操纵另一个类的对象,我们就说一个类依赖千另一个类。
  • 聚合:聚合关系意味着类A 的对象包含类B 的对象。
  • 继承:用千表示特殊与一般关系的
访问权限

private、default、protected、public

image-20220914093000942

成员方法

[public、protected、private]<方法返回值的类型><方法名>([参数列表]){}

成员变量

[public、protected、private][static][final]<变量类型><变量名>

static:静态变量,可直接通过类名访问
运行程序时就会被分配内存
不加叫实例方法/变量,必须通过new创建实例后通过实例访问

声明变量的时候同时赋予初始值,而对成员变量的操作只能放在方法中

局部变量

方法里声明的变量

不可用static修饰,可用final修饰

this关键字
就近原则

image-20230601001208670

如果age前加this就会使用成员变量的age

UML图
  1. 类UML图:名字层(抽象类为斜体)、变量层、方法层
    image-20220914093521273
  2. 接口
  3. 泛化关系
  4. 关联关系:如果A类中成员变量是用B类声明的对象,那么A和B的关联是关联关系,称A类的对象关联于B类的对象或A类的对象组合了B类的对象。
    image-20220914094543299
    A指向B,实线
  5. 依赖关系:如果A类中某个方法的参数是用B类声明的对象或某个方法返回的数据类型是B类对象,那么A和B的关系是依赖关系,称A依赖于B。
    image-20220914094740251
    A指向B,虚线
  6. 实现关系
构造方法
public class Apple {
     public Apple() {		// 构造方法
     }
}
  1. 它的名字必须与它所在类的名字完全相同
  2. 没有返回值
public类与友好类

另外一个类中使用友好类创建对象时,必须保证它们是在同一个包中。

抽象类

所谓抽象类就是只声明方法的存在而不去具体实现它的类。抽象类不能被实例化,含有抽象方法的类只能被定义成抽象类。

抽象类中可以定义的方法:可以有0个或者多个抽象方法,也可以有普通的实例方法和静态方法,还可以有其他的成员变量和构造方法。 如果类中没有任何形式的抽象方法,那么可以由程序员决定是否将类声明成abstract类型但是只要是下面这些情况之一,那类必定为抽象类,必须加上abstract修饰:

  • 类中明确声明有abstract方法的。

  • 类是抽象类继承下来的,而且没有实现父类中全部的抽象方法。

  • 类实现了一个接口,但没有将其中所有的抽象方法实现。

    抽象类不一定有抽象方法,有抽象方法的类一定是抽象类或者是接口。

语法格式:
abstract class 类名{body}

可以包含属性、方法(普通方法和抽象方法)

抽象方法

abstract 访问权限 返回类型 方法名 ([参数列表]);
特持别注意它的最后有一个分号“;”,而没有方法体的括号“{ }”。

不可以被定义为抽象方法的方法:

  • 构造方法
  • 静态方法
  • final方法
  • private方法
final
  1. final修饰成员变量
    类初始化时,系统会为该类的类属性分配内存,并分配默认值;当创建对象时,系统会为该对象的实例属性分配内存,并分配默认值。
    定义final变量时,要么指定初值,要么在初始化块、构造器中初始化成员变量。当给成员变量指定默认值之后,则不能在初始化块、构造器中为该属性重新赋值。
  2. final修饰局部变量
    只能赋一次值
  3. final修饰类,该类不能被继承,不可被重写
    有时为了程序的安全性,可以将一些重要的类声明为final类。
  4. final修饰实例域
    可以将实例域定义为final,,确保在每一个构造器执行之后,这个域的值被设置,并且在后面的操作中,不能够再对它进行修改。
    final 修饰符大都应用千基本( primiti ve) 类型域,或不可变(immutab l e) 类的域(如果类中的每个方法都不会改变其对象,这种类就是不可变的类。例如, String 类就是一个不可变的类) 。
    对于可变的类,使用fina l 修饰符可能会对读者造成混乱。
内部类

内部类就是在类内部定义的子类

  • 内部类可以直接访问外部类的成员,包括私有
  • 外部类要访问内部类的成员,必须创建对象
public class Zoo{
     ……
     class Wolf{					// 内部类Wolf
     }
}
  1. 成员内部类:成员内部类和成员变量一样,属于类的全局成员

    • 可以用private修饰符修饰,仅能在本类中使用
  2. 局部内部类:和局部变量一样,都是在方法内定义的,其有效范围只在方法内部有效。

  3. 静态内部类:用static关键字修饰,类似静态变量
    这个内部类属于外部类本身,但是不属于外部类的任何对象

    • 非静态内部类不允许定义静态成员
    • 根据静态成员不能访问非静态成员的规则,外部类的静态方法不能访问非静态内部类
    • 非静态内部类的成员只是在非静态内部类范围是可知的,并不能被外部类直接使用,如果要访问非静态内部类的成员必须显示创建非静态内部类对象来调用访问

    静态内部类和非静态内部类最大的区别是:非静态内部类编译后隐式保存着外部类的引用(就算外部类对象没用了也GC不掉),但是静态内部类没有。

创建内部类对象
  1. 成员内部类:外部类名.内部类名 对象名 = new 外部类对象.new 内部类对象();
  2. 局内内部类:只能在方法中,创建对象并访问
  3. 静态内部类:外部类名.内部类名对象名= new外部类名.内部类名();
匿名类

匿名类就是没有名称的内部类,它经常被应用于Swing程序设计中的事件监听处理。

特点:

  1. 匿名类可以继承父类的方法也可以重写父类的方法,可以访问外嵌类中的成员变量和方法
  2. 在匿名类中不能声明静态变量和静态方法。
  3. 使用匿名类时,必须在某个类中直接使用匿名类创建对象。
  4. 在使用匿名类创建对象时,要直接使用父类的构造方法。
作用

简化操作,将继承\实现, 方法重写,创建对象,放在了一步进行
image-20230722100959457
调用多个匿名类方法:image-20230722101134120

在开发中的使用:当方法的形式参数是接口或者抽象类时,可以将匿名内部类作为实际参数进行传递image-20230722101556984


语法格式
new ClassName(){...}

对象

任何对象变量的值都是对存储在另外一个地方的一个对象的引用,new 操作符的返回值也是一个引用。
可以显式地将对象变撮设置为null, 表明这个对象变鼠目前没有引用任何对象。

声明:类名 对象名;

实例化:对象名=new 构造方法名([参数列表]);

销毁:finalize()
没有任何参数和返回值

参数传值:值传递、引用传值

隐式参数

隐式(implicit ) 参数, 是出现在方法名前的Em ployee 类对象image-20230601204216626

可以将实例域与局部变批明显地区分开来。

final实例域

必须确保在每一个构造器执行之后,这个域的值被设置,并且在后面的操作中,不能够再对它进行修改。

image-20230707150056296

静态域与静态方法

类似:C++中,使用:操作符访问自身作用域之外的静态域和静态方法,如
Math :: PI 。

静态域

如果将域定义为static, 每个类中只有一个这样的域。而每一个对象对于所有的实例域却都有自己的一份拷贝。

静态方法也称类方法,与之对应的是实例方法image-20230707150248816

每一个雇员对象都有一个自己的id 域,但这个类的所有实例将共享一个nextld
域。换句话说,如果有1000 个Employee 类的对象,则有1000 个实例域id 。但是,只有一
个静态域nextld

两者区别:

  • 实例方法:当该类创建对象后,类中的实例方法才会分配入口地址
  • 类方法:在该类被加载到内存时,就分配了相应的入口地址(类方法不能直接操作实例变量)
静态常量

静态常量:它的值在程序运行期间不能被修改。静态常量通常用于表示固定不变的值

image-20230708212021342

  • static:表示该常量是静态域常量
  • final:表示常量的值不能被修改
静态方法
  1. 静态方法是一种不能向对象实施操作的方法。
    例如, Math.pow(x, a)
  2. 静态方法可以访问自身类中的静态域。image-20230708214248413
使用场景
  1. 一个方法不需要访问对象状态,其所需参数都是通过显式参数提供(例如: Math.pow ) 。
  2. 一个方法只需要访问类的静态域(例如: Employee . getNextld ) 。
工厂方法
  • 作用:构造对象
  • 原因: image-20230708214621000
    1. 便于命名:无法命名构造器。构造器的名字必须与类名相同。但是,这里希望将得到的货币实例和百分比实例采用不用的名字。
    2. 改变所构造的对象类型:Factory 方法将返回一个DecimalFormat类对象,这是NumberFormat 的子类
main方法
  • 也是静态方法
  • 可以用于对类进行单元测试
方法参数

Java 程序设计语言对对象采用的不是引用调用,是值传递

• 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型) 。
• 一个方法可以改变一个对象参数的状态。
• 一个方法不能让对象参数引用一个新的对象。

对象构造
重载

方法签名:方法名以及参数类型;
返回类型不是方法签名的一部分

不能有两个名字相同、参数类型也相同却返回不同类型值的方法。

默认域初始化

应该对构造器某些域进行初始化,而不是默认赋值

确保不管怎样调用构造器,每个实例域都可以被设置为一个有意义的初值,这是一种很好的设计习惯。

无参数构造器
  • 如果在编写一个类时没有编写构造器,那么系统就会提供一个无参数构造器。
  • 如果类中提供了至少一个构造器,但是没有提供无参数的构造器,则在构造对象时如果没有提供参数就会被视为不合法。
显式域初始化

可以在在执行构造器之前,先执行赋值操作。
只要是在同一个类中,可以在任何地方使用这个方法,不论这个方法是在使用它的代码之前还是之后定义的。

class Employee {
    private static int nextid;
    private int id = assignid();
    
    private static int assignid() {
        int r = nextid;
        nextid++;
        return r;
    }
}

this

表示某个对象,this关键字可以出现在实例方法和构造方法中,但不可以出现在类方法(static)中。

当局部变量和成员变量的名字相同时,成员变量就会被隐藏,这时如果想在成员方法中使用成员变量,则必须使用关键字this

调用另一个构造器

如果构造器的第一个语句形如thi s( ),这个构造器将调用同一个类的另一个构造器。

初始化块

初始化数据域的方法:

  • 在构造器中设置
  • 在声明中赋值(默认参数)
  • 初始化块image-20230709214503398(不常见,通常会直接将初始化代码放在构造器中)
    初始化块应放在域定义之后。
调用构造器的处理步骤
  1. 所有数据域被初始化为默认值(0 、false 或null ) 。
  2. 按照在类声明中出现的次序,依次执行所有域初始化语句和初始化块。
    如果对类的静态域进行初始化的代码比较复杂,那么可以使用静态的初始化块。
  3. 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体。
  4. 执行这个构造器的主体。
对象析构与finalize方法

Java 不支持析构器

可以为任何一个类添加finalize 方法。finalize 方法将在垃圾回收器清除对象之前调用(实际中少用,可以配合Runtime .addShutdownHook方法 添加“关闭钩"(shutdown hook ))

JavaBean

一个Java中的类,其对象可以用于在程序中封装数据(如:学生类)

需满足的要求:

  • 成员变量使用private修饰
  • 提供每-个成员变量对应的setXxx() / getXxx()
  • 提供-一个无参构造方法
image-20230716205041769

类的组织方式,一组相关类和接口的集合

用途:

  1. 将功能相近的类放在同一个包中,可以方便查找与使用。
  2. 由于在不同包中可以存在同名类,所以使用包在一定程度上可以避免命名冲突。
  3. 在Java中,访问权限是以包为单位的。

创建:package 包名;
当包中还有子包时,可以使用“包1.包2.….包n”进行指定

类的导入

一个类可以使用所属包中的所有类,以及其他包中的公有类( public class ) 。

使用包中的类:

  1. 使用长名引用包中的类com.lzw.Round round=new com.lzw.Round();
  2. 使用import语句引入包中的类
命名冲突image-20230709220420176

解决方法:在每个类名的前面加上完整的
包名。
java.util.Date deadline = new java .util.Date();
java.sql.Date today= new java.sql.Date(…) ;

静态导入

import 语句不仅可以导入类,还增加了导入静态方法和静态域的功能。

image-20230709220745839

但是这种编写形式不利千代码的清晰度

包作用域

对于类:

  • 标记为public 的部分可以被任意的类使
  • 标记为private 的部分只能被定义它们的类使用。
  • 如果没有指定public 或private, 这个部分(类、方法或变量) 可以被同一个包中的所有方法访问。

类路径

为了使类能够被多个程序共享,需要做到下面几点:

  1. 把类放到一个目录中,例如/home/user/classdir 。
  2. 将JAR 文件放在一个目录中,例如: /home/user/archives 。
  3. 设置类路径( class path ) 。类路径是所有包含类文件的路径的集合。

文档注释

应该写注释的部分:

  • 公有类与接口
  • 公有的和受保护的构造器及方法
  • ·公有的和受保护的域

格式:

  • 每个/** */文档注释在标记之后紧跟着自由格式文本(free-form text )

  • 标记由@开始,如@author 或@param

  • 自由格式文本的第一句应该是一个概要性的句子。javadoc 实用程序自动地将这些句子抽取出来形成概要页。

    image-20230709224500754

类注释

放在import 语句之后, 类定义之前

方法注释

每一个方法注释必须放在所描述的方法之前。除了通用标记之外,还可以使用下面的标记:

  • @param变量描述

    这个标记将对当前方法的“ param " (参数)部分添加一个条目。这个描述可以占据多行,并可以使用HTML 标记。
    一个方法的所有@p aram 标记必须放在一起。

  • @return描述

  • @throws类描述:用千表示这个方法有可能抛出异常

域注释

只需要对公有域(通常指的是静态常扯)建立文档image-20230709225014883

通用注释

用在类文档的注释中

  • @author姓名:每个author对应一个作者
  • @version文本:对当前版本的描述
  • @since文本:对引入特性的版本描述
  • @deprecated 文本:这个标记将对类、方法或变掀添加一个不再使用的注释。
  • @see引用:这个标记将在“see a l so" 部分增加一个超级链接。
    引用可以是
    • package.class#Jeature label
    • <a href= ". .. ">label</a>
    • "text"

类设计技巧

  • 一定保证数据私有:不要破坏封装性
  • 要对数据初始化
  • 不要在类中使用过多的基本类型:用其他的类代替多个相关的基本类型的使用image-20230709230200343
  • 不是所有的域都需要独立的域访问器和域更改器
  • 将职责过多的类进行分解
  • 类名和方法名要能够体现它们的职责
  • 优先使用不可变的类

继承

继承(extends和implements区别):继承只能继承一个类,但implements可以实现多个接口

  • implements:implements(实现接口就是在接口中定义了方法,这个方法要你自己去实现,接口可以看作一个标准,比如定义了一个动物的接口,它里面有吃(eat())这个方法,你就可以实现这个方法implements,这个方法是自己写,可以是吃苹果,吃梨子,香蕉,或者其他的。

类、超类和子类

定义子类

关键词extends,都是公有继承

  • 超类:已存在的类
  • 子类:新生类
覆盖方法

关键词super:用于调用父类方法image-20230711174933369

子类构造器
  • 用super调用超类构造器:Manager 类的构造器不能访问Emp loyee 类的私有域,所以必须利用E mployee 类的构造器对这部分私有域进行初始化
  • 默认调用:如果子类的构造器没有显式地调用超类的构造器,则将自动地涸用超类默认(没有参数)的构造器。
    • 如果超类没有不带参数的构造器,并且在子类的构造器中又没有显式地调用超类
      的其他构造器,则Java 编译器将报告错误。
多态

指的是同一操作或方法调用可以根据对象的实际类型产生不同的行为

  • is-a规则:

    • 程序中出现超类对象的任何地方都可以用子类对象置换。反之不然。

      即:子类数组的引用可以转换成超类数组的引用,反之不行

  • 前提和体现:

    • 有继承/实现关系
    • 有方法重写
    • 有父类弓|用指向子类对象
成员访问特点
  • 构造方法:super()(同继承)
  • 成员变量:编译看左边(父类) , 执行看左边(父类)(在编译时,编译器只知道父类中定义的成员变量和方法)
  • 成员方法:编译看左边(父类) , 执行看左边(父类)(运行时,父类方法被子类覆盖,运行时绑定)
优缺点
  • 优点:
    • 提高方法扩展性
  • 缺点:
    • 不能调用子类特有的成员内容,但可以直接创建子类对象调用或向下转型
转型
  • 向上转型:
    父类引用指向子类对象Fu f = new Zi();
  • 向下转型:
    父类引用转为子类对象Zi z = (Zi) f;
风险和解决方案

如果被转的引用类型变量,对应的实际类型和目标类型不是同一种类型,那么在转换的时候就会出现ClassCastException
比如说,把动物向下转型成狗,然后去调用猫的方法

避免:instanceof:object instanceof Class
判断关键字左边的变量,是否是右边的类型,返回boolean类型结果a inst anceof Dog

方法调用

假设调用x.f(p aram) ,且隐式参数x声明为C类的对象。

  1. 编译器查看对象的声明类型和方法名
    编译器列举所有C 类中名为f 的方法和其超类中访间属性为publi c 且名为f 的方法( 超类的私有方法不可访问) 。

  2. 重载解析
    编译器将查看调用方法时提供的参数类型,参数类型完全匹配,就选择这个方法。 覆盖方法时,要保证返回类型的兼容性image-20230713103737079

  3. 绑定:

    • 静态绑定:如果是private 方法、static 方法、final 方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法
    • 动态绑定:调用的方法依赖千隐式参数的实际类型,并且在运行时实现动态绑定。程序运行时,虚拟机一定调用与x 所引用对象的实际类型最合适的那个类的方法
  • 方法表:每次调用方法都要进行搜索, 时间开销相当大。因此,虚拟机预先为每个类创建了一个方法表( method table ),其中列出了所有方法的签名和实际调用的方法
  • :在覆盖一个方法的时候, 子类方法不能低于超类方法的可见性
阻止继承:final

不允许扩展的类被称为final 类

final 类中的所有方法自动地成为final 方法

  • 目的:确保它们不会在子类中改变语义

    例如, Calendar类中的getTime 和setTime 方法都声明为final 。这表明Ca lendar 类的设计者负责实现Date 类与日历状态之间的转换,而不允许子类处理这些问题。

  • 内联:如果一个方法没有被覆盖并且很短,编译器就能够对它进行优化处理(它会将被调用函数或方法的代码插入到调用处,而不是实际执行一个函数调用。这样可以避免函数调用的开销)。
    例如,内联调用e.getName( )将被替换为访问e.name 域。

强制类型转换

将一个子类的引用赋给一个超类变量,编译器是允许的。但将一个超类的引用赋给一个子类变量,必须进行类型转换,这样才能够通过运行时的检查。

instanceof操作符:在将超类转换成子类之前,,先查看一下是否能够成功地转换

大多数情况不需要类型转换,动态绑定都能找到,只有在使用Manager 中特有的方法时才需要进行类型转换。

抽象类abstract
  • 为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。
  • 除了抽象方法之外, 抽象类还可以包含具体数据和具体方法。
  • 类即使不含抽象方法,也可以将类声明为抽象类。
  • 抽象类不能被实例化。
受保护访问protected

对本包和所有子类可见

Object:所有类的超类

  • 如果没有明确地指出超类, Object 就被认为是这个类的超类。
  • 可以使用Object 类型的变扯引用任何类型的对象
equals方法

Object 类中的equals 方法用于检测一个对象是否等千另外一个对象。

  • 在Object 类中,这个方法将判断两个对象是否具有相同的引用。
  • 状态的相等性:
  • 如果两个对象的状态相等,就认为这两个对象
    是相等的。例如,如果两个雇员对象的ID,姓名、薪水和雇佣日期都一样,就认为它们是相等的
相等测试与继承

如果隐式和显式的参数不属千同一个类

  • 如果子类能够拥有自己的相等概念, 则对称性盂求将强制采用getC l ass 进行检测。
  • 如果由超类决定相等的概念, 那么就可以使用instanceof 进行检测,这样可以在不同子类的对象之间进行相等的比较。

写一个完美equals方法的建议:

  1. 显式参数命名为otherObject, 稍后需要将它转换成另一个叫做other 的变量。
  2. 检测this 与otherObject 是否引用同一个对象:
    if (this == othe rObj ect) return true;
  3. 检测otherObject 是否为null, 如果为null, 返回false 。这项检测是很必要的。
    if (otherObject == null) return false;
  4. 比较thi s 与otherObject 是否属于同一个类。如果equals 的语义在每个子类中有所改
    变,就使用getClass 检测:
    if (getClass() != otherObject.getClass ()) return false;
    如果所有的子类都拥有统一的语义,就使用instanceof 检测:
    if (! (otherObj ect i nstanceof Cl assName)) return false;
  5. 将otherObject 转换为相应的类类型变量:
    Cl assName other = (Class Name) otherObject
  6. 比较域:使用=比较基本类型域,使用equals 比较对象域。如果所有的域都匹配,就返回true; 否则返回false 。
    return fieldl == other. fieldl
    Objects. equal s(fi el d2, other .fiel d2)

如果在子类中重新定义equals, 就要在其中包含调用super.equals(other) 。

hashCode方法

散列码( hash code ) 是由对象导出的一个整型值。散列码是没有规律的。

  • 如果类中没有定义hashCode方法,它的散列码是由Object 类的默认basbCode 方法导出的对象存储地址。
  • 如果重新定义equa l s 方法,就必须重新定义hashCode 方法
toString方法

用于返回表示对象值的字符串。只要对象与一个字符串通过操作符”+ “连接起来, Java 编译就会自动地调用toString 方法,以便获得这个对象的字符串描述。

如果超类使用了getClass().getName( ),那么子类只要调用super.toString( )就可以了。

多态

对象变量是多态的。一个Employee 变量既可以引用一个Employee类对象,也可以引用一个Employee 类的任何一个子类的对象(例如, Manager 、Exec uti ve 、Secretary 等) 。

向上转型

package com.sheepmu;
 class Animal
 {
	public void eat()
	{
		System.out.println("父类的 eating...");
	}
}
class Bird extends Animal
{	
	@Override
	public void eat()
	{
		System.out.println("子类重写的父类的  eatting...");
	}	
	public void fly()
	{
		System.out.println("子类新方法  flying...");
	}
}
public class Sys
{
	public static void main(String[] args) 
	{
		Animal b=new Bird(); //向上转型
		b.eat(); 
		//  b.fly(); b虽指向子类对象,但此时子类作为向上的代价丢失和父类不同的fly()方法
		sleep(new Male());
		sleep(new Female());//传入的参数是子类-----!!
	}
	
	public static void sleep(Human h) //方法的参数是父类------!!!
        {
 		 h.sleep();
        }
}                        

image-20220920144753592

然而,不能将一个超类的引用赋给子类变量。

super关键字

用途:

  1. 调用父类的构造方法:super([参数列表]);如果父类的构造方法中包括参数,则参数列表为必选项,用于指定父类构造方法的入口参数。
    super([参数列表]);
  2. 操作被隐藏的成员变量和被覆盖的成员方法。
    super.成员变量名 super.成员方法名([参数列表])

Object

Object 类是Java 中所有类的超类

可以使用Object 类型的变扯引用任何类型的对象:
Object obj = new Employee("Harry Hacker ", 35000);
要想对其中的内容进行具体的
操作,还需要清楚对象的原始类型,并进行相应的类型转换:
Employee e = (Employee) obj ;

  • hashcode方法:
    散列码( hash code ) 是由对象导出的一个整型值。散列码是没有规律的。如果x 和y 是两个不同的对象, x . hasbCode( )与y. hashCode( )基本上不会相同。
    由于hashCode 方法定义在Object 类中,每个对象都有一个默认的散列码,其值为对象的存储地址。
  • toString方法:
    它用千返回表示对象值的字符串
    只要对象与一个字符串通过操作符”+ “连接起来, Java 编译就会自动地调用toString 方法,以便获得这个对象的字符串描述。 例如:在调用x.toString( )的地方可以用”“+x 3替代

泛型数组列表

  • ArrayList类:为了指定数组列表保存的元素对象类型,需要用一对尖括号将类名括起来加在后而,例如, A rrayLi st< Employee>
    声明和构造一个保存Employee 对象的数组列表:Arraylist< Employee> staff= new Arraylist<>() ;
  • 没有后缀<. > 仍然可以使用Array List, 它将被认为是一个删去了类型参数的“原始”类型。
  • 如果调用add 且内部数组已经满了, 数组列表就将自动地创建一个更大的数组, 并将所有的对象从较小的数组中拷贝到较大的数组中。
  • ensureCapaci ty 方法:
  • 这个方法调用将分配一个包含100 个对象的内部数组。然后调用100 次add, 而不用重新分
    配空间。
访问数组列表元素

使用get 和set 方法:staff. set (i , harry);

使用下列格式获得数组列表的元素:Employee e = staff. get (i);

将数组元素拷贝到一个数组中:
X[] a= new X[list.size()];
list. toArray (a);

在数组列表的中间插入元素:
int n = staff.size() / 2;
staff. add (n, e);

从数组列表中间删除一个元素:Employee e = staff.remove(n);

使用“for each" 循环遍历数组列表:
for (Emp 1 oyee e : staff)
do something with e

如果数组存储的元素数比较多,又经常需要在中间位置插入、删除元素,就应该考虑使用链表了。

类型化与原始数组列表的兼容性

可以将一个类型化的数组列表传递给update 方法,而并不需要进行任何类型转换。

相反地,将一个原始Array Li st 赋给一个类型化ArrayList 会得到一个警告。
Arraylist re sult = employeeDB .find(query); // yields warning

对象包装器与自动装箱

有时, 需要将int 这样的基本类型转换为对象。所有的基本类型都布一个与之对应的类。
例如, Integer 类对应基本类型int 。通常,这些类称为包装器

Arrayl i st list = new Arrayl i st<>O;

自动装箱( autoboxing )
li st.add (3);
将自动地变换成
list. add (Integer. val ueOf (3)) ;

自动拆箱
int n = list.get(i);
翻译成
int n = list.get (i).i ntValue ();

可以将某些基本方法放置在包装器中,例如,将一个数字字符串转换成数值。
要想将字符串转换成整型, 可以使用下面这条语旬:
int x = Integer. parseint (s);

修改数值参数值的方法:使用在org . o mg.CORBA 包中定义的持有者( holder ) 类型,每个持有者类型都包含一个
公有(!)域值,通过它可以访问存储在其中的值。

参数可变方法

printf 方法是这样定义的:

public class PrintStream
{
	public PrintStream printf(String fmt, Object... args) { return format(fmt, args); }
}

这里的省略号. .是Java 代码的一部分,它表明这个方法可以接收任意数量的对象(除fmt参数之外) 。
实际上, printf 方法接收两个参数, 一个是格式字符串,另一个是Object[ ]数组,其中
保存着所有的参数(如果调用者提供的是整型数组或者其他基本类型的值,自动装箱功能将把它们转换成对象) 。现在将扫描fmt 字符串,并将第1 个格式说明符与args[i] 的值匹配
起来。换句话说,对于printf 的实现者来说, Object… 参数类型与Object[ ] 完全一样。

枚举类

概念
  • 是指将变量的值一一列出来, 变量的值只限于列举出来的值的范围内。

  • 作用:简洁地表示一些固定的值
    public enum Size { SMALL , MEDIUM, LARGE , EXTRA_LARGE }

  • 访问:枚举类名.枚举类名称

  • 构造器:可以有,必须是private,必须包括空参构造器

  • 枚举项的用法:枚举(“”);

    public enum Season { SPRING("春"){} }

  • 特点

    • 所有的枚举类型都是Enum 类的子类

    • 每一个枚举项其实就是该枚举的一个对象

    • 枚举类也可以有抽象方法,但是枚举项必须重写该方法

      public enum Season { 
          SPRING("春"){
              @Override
              //在此处重写
          } 
      }
      
方法
方法名说明
String name()获取枚举项的名称
int ordinal()返回枚举项在枚举类中的索引值
int compareTo(E o)比较两个枚举项,返回的是索引值的差值
String toString()返回枚举常量的名称,一般用于打印
static <T> T valueOf(Class <T> type,String name)获取指定枚举类中的指定名称的枚举值,等同于枚举类名.枚举类名称
values()获得所有的枚举项,返回数组

类加载器

  • 作用:负责将.class文件(存储的物理文件)加载到内存中
  • 时机:
    • 创建类的实例(对象)
    • 调用类的类方法(即静态方法)
    • 访问类或者接口的类变量(即静态变量)或者为该类变量赋值
    • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
    • 初始化某个类的子类
    • 直接使用java.exe命令来运行某个主类
过程

image-20230727144232960

  1. 加载image-20230727144225214
    1. 通过一个类的全限定名来获取定义此类的二进制字节流
      即通过包名+类名,获取这个类,准备用流进行传输
    2. 将这个字节流所代表的静态存储结构转化为运行时数据结构
      即把这个类的字节码文件用流加载到内存中
    3. 在内存中生成一个代表这个类的java.lang.Class对象,任何类被使用时,系统都会为之建立一个java.lang.Class对象。
      即加载完毕创建一个class对象
  2. 链接
    1. 验证:链接阶段的第一步,这一阶段为了确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。
    2. 准备:负责为类的类变量(被static修饰的变量)分配内存并设置默认初始化值。
      即初始化静态变量
    3. 解析:将类的二进制数据流中的符号引用(比如引用变量类型,最开始找不到它就会用符号代替)替换为直接引用
  3. 初始化:根据程序员通过程序制定的主观计划去初始化类变量和其他资源
    即静态变量赋值以及初始化其他资源(如static String school=“传智大学”)
分类
  • 启动类加载器( BootstrapClassLoader ) : 虚拟机内置的类加载器。
  • 平台类加载器( Platform Classloader ) :负责加载JDK中-些特殊的模块。
  • 系统类加载器( SystemClassloader ) : 负责加载用户类路径上所指定的类库。
双亲委派模型

类加载器之间的层次关系image-20230728145316052

常用方法
  • public static ClassLoader getSystemClassLoader()
    获取系统类加载器
  • public InputStream getResourceAsStream(String name)
    加载某一个资源文件,参数:文件的路径;返回值:字节流

image-20230724105333802

文件要新建在src目录下

反射

能够分析类能力的程序称为反射

反射机制可以用来:
· 在运行时分析类的能力
· 在运行时查看对象,例如,编写一个toString 方法供所有类使用。
· 实现通用的数组操作代码
·利用Method 对象,这个对象很像C++中的函数指针。

Class类

实际上是一个泛型类

Java 运行时系统始终为所有的对象绯护一个被称为运行时的类犁标识。
这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。保存这些信息的类被称为Class

获得Class对象的三种方法:image-20230728150301863

  • 源代码阶段-forName()静态方法:获得类名对应的Class 对象(只有在className 是类名或接口名时才能够执行)(无论何时使用这个方法,都应该提供一个异常处理器(exceptionhandler)

    Class.forName("包名+类名")

  • Class对象阶段-T.class:如果T 是任意的Java 类型(或void 关键字) ,T.class 将代表匹配的类对象。(一个类型未必一定是一种类)

  • Runtime运行时阶段-getClass()方法:返回一个Class 类型的实例。
    getName()方法:返回类的名字。如果类在一个包里,包的名字也作为类名的一部分

虚拟机为每个类型管理一个Class 对象:

  • ==运算符:实现两个类对象比较的操作。
  • newInstance():动态地创建一个类的实例
    e.ge tClass(). newinstance();创建了一个与e 具有相同类类型的实例。
获取Class类的对象

把对象用类描述

  • 成员变量:Field
  • 构造方法:Constructor
    • 获取构造方法
      • 返回所有公共构造方法对象的数组:Constructor<?>[] getConstructors ( )
      • 返回所有构造方法对象的数组(包括公有、私有):Constructor<?>[] getDeclaredconstructors
      • 返回单个公共构造方法对象:Constructor<T> getConstructor (Class<?>...parameterTypes)
        小括号中的参数要跟构造方法的形参保持一致,找不到就会报错
      • 返回单个构造方法对象(可以是私有的):Constructor<T> getDeclaredConstructor(Class<?>,...parameterTypes ):
    • 创建对象
      T newInstance(Objet.. initargs) :根据指定的构造方法创建对象
      不能直接创建私有成员的对象,需要先临时取消访问检查:constructor . setAccessible(true);|
  • 成员方法:Method
获取成员变量
image-20230728230938087
  1. 获得class对象
  2. 获得Field对象
    • Field[] getFields():返回所有公共成员变量对象的数组
    • Field[] getDeclaredFields() :返回所有成员变量对象的数组
    • Field getField(String name) :返回单个公共成员变量对象
    • Field getDeclaredField(String name) : 返回单个成员变量对象
  3. 赋值(set)或获取值(get)
利用Field赋值或获取值

操作前要先用newInstance()创建对应对象

  • void set(Object obj, Objectvalue) :赋值
  • Object get(Object obj)获取值。
获取Method对象
  1. 获得class对象
  2. 获得Mehod对象
    • Method[] getMethods():返回所有公共成员方法对象的数组,包括继承的
    • Method[] getDeclaredMethods() :返回所有成员方法对象的数组,不包括继承的
    • Method getMethod(String name, Class<?> ... parameterTypes) :返回单个公共成员方法对象。前一个参数表示要获取的方法的名称,后一个参数表示方法的参数类型。例如Method method = clazz.getMethod("function4", String.class);
    • Method getDeclaredMethod(String name, Class<?> ... parameterTypes)
      返回单个成员方法对象
  3. 运行方法
    1. 如果原方法有参数,则创建对象作为方法的调用者:newInstance
    2. method.invoke(Object obj, Object... args)
      • 参数一:用obj对象调用该方法
      • 参数二:调用方法的传递的参数(如果没有就不写)
      • 返回值:方法的返回值(如果没有就不写)
捕获异常

异常有两种类型:

  • 已检查异常:编译器将会检查是否提供了处理器
  • 未检查异常:编译器不会查看是否为这些错误提供了处理器(例如,访问null 引用)

详见第七章:异常

利用反射分析类的能力

检查类java.lang.reflect的结构: 都有getName方法返回项目名称;都有getModifiers()方法方法返回一个整型数(用不同的位升关描述public 和static 这样的修饰符使用状况)

  • Field类:描述域
    • getType()方法:用来返回描述域所属类型的Class 对象
  • Method类:描述方法
  • Constructor类:描述构造器
  • Modifier类: 用静态方法分析getModifiers()返回的整体数值

Class 类中的get Fi e ld s 、getM ethods 和getConstructors 方法将分别返回类提供的
public 域、方法和构造器数组, 其中包括超类的公有成员。C las s 类的ge tDeclareFie l ds 、getDeclareMethods 和getDeclaredConstructors 方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。

在运行时使用反射分析对象

可查看数据域的实际内容:Field类中的get方法得到可访间域的值,若没有访问权限,则只允许查看任意对象有哪些域,而不允许读取它们的值。

使用toString 方法查看任意对象的内部信息。

Arraylist<Integer> squares = new Arraylist<>();
for (int i = 1; i <= 5; i++) squares.add(i * i);
 System.out.println(new ObjectAnalyzer().toString(squares));

ObjectAnalyzer 将记录巳经被访问过的对象,防止无限递归

使用反射编写泛型数组代码

java.lang . reflect 包中的Array 类允许动态地创建数组

接口

一个类可以实现(implement) 一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象(特殊的抽象类) 为什么不用抽象类表示通用属性:每个类只能扩展一个类。假设Employee 类已经扩展一个类,例如Person , 它就不能再像下面这样扩展第二个类(单继承原则:如果一个类可以扩展多个类,当多个父类中有相同的方法或字段时,就会产生命名冲突和歧义。单继承可以避免这种情况,使代码更易读、理解和维护。)

  • 定义:[修饰符:public、default] interface 接口名 [extends 父接口名列表]{body}
  • 实现:[public、final、abstract] class <类名> [extends 父类名] [implements 接口列表]{body}

接口实现类命名例子:InterImpl

特性

  1. 接口中允许定义的方法:default、abstract、static、private(非抽象、只能在接口内部的方法中被调用)
    接口中的所有抽象、默认和静态方法都是隐式公共的,public可省略
  2. 接口不是类, 尤其不能使用new 运算符实例化一个接口:
    x ; new Comparable(… .) ; // ERROR
    然而,尽管不能构造接口的对象, 却能声明接口的变量,通过变量调用接口的方法:
    Comparable x; // OK
  3. 接口可以被拓展
    image-20220812111348138
  4. 尽管每个类只能够拥有一个超类,但却可以实现多个接口
  5. 接口变量必须引用实现了接口的类对象:(此为向上转型)
    x = new Employee (. . .) ; // OK provided Employee implements Comparable
    然后可以实现接口回调
  6. 接口中只能定义静态常量,不能有普通属性,不能有构造方法,不能有初始化块
  7. 接口的子类(实现类)
    要么重写接口中的所有抽象方法
    要么是抽象类

接口中的成员特点

  • 成员变量:只能是public static final
  • 构造方法:没有
  • 成员方法:是抽象方法,默认是public
    jdk9中允许有private,允许有默认方法default(必须有方法体,即具有默认的实现。实现类可以直接继承该方法,而不需要在实现类中重新实现该方法。)
    涉及到了接口大面积更新方法,而不想去修改每一个实现类 ,就可以将更新的方法,定义为带有方法体的默认方法

UML图

image-20220923091625281

第一层:名字;第二层:常量;第三种:方法

接口回调

,那么该接口变量就可以调用被类实现的接口方法。实际上,当接口变量调用被类实现的接口方法时,就是通知相应的对象调用这个方法。

例子

public class B {
    public void deal() {
        A a = new A();
        a.doRequest("http://请求的url",new CallBackListener() {
            @Override
            public void onFinish() {
                //请求成功的逻辑,如下载完成后的处理,请求到数据后的处理
            }
 
            @Override
            public void onError(Exception ex) {
                // 异常逻辑
            }
        });
    }
}

静态方法

如何增加静态方法:通常的做法都是将静态方法放在伴随类中

默认方法

用default 修饰符标记

实现这个接口的程序员只需要为他们真正关心的事件覆盖相应的监听器。默认方法可以调用任何其他方法

为接口增加一个非默认方法不能保证”源代码兼容”(source compatible) 。

  • 解决默认方法冲突(命名冲突)
    1. 超类优先:如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。
    2. 接口冲突:如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型(不论是否是默认参数)相同的方法,必须狻盖这个方法来解决冲突。
  • 另一种冲突:一个类扩展了一个超类,同时实现了一个接口,并从超类和接口继承了相同的方法。
    在这种情况下,只会考虑超类方法,接口的所有默认方法都会被忽略。

lambda表达式

使用前提:

  • 有一个接口
  • 接口中有且仅有一个抽象方法

功能:将一个代码块传递到某个对象(函数式编程思想 )

表达式形式:(形式参数)->{代码块}

如果可以推导出一个lambda 表达式的参数类型,则可以忽略其类型。

Comparator<String> comp
= (first, second) //Same as (String first, String second)
-> first. length() - second .length() ;

如果方法只有一个参数,而且这个参数的类型可以推导得出,那么甚至还可以省略小括号

函数式接口

对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda 表达式。这种接口称为函数式接口

Timer t = new Timer(lOOO, event ->
}) ;
System.out.println ("At the tone, the time is"+ new Date());
Toolkit. getDefaultToolkit().beep();

接口Predicate :专门用来传递lambda 表达式

方法引用

Timer t = new Timer(lOOO, System .out: :println);中的System.out :: println等价于x -> System.out.println(x)

要用操作符分隔方法名与对象或类名。主要有3 种情况:
• object: :instanceMethod
• Class::staticMethod
• Class::instanceMethod
在前2 种情况中,方法引用等价于提供方法参数的lambda 表达式。前面已经提到,
System.out: : println 等价千x -> System . out.println(x) 。类似地, Math: : pow 等价于(x, y) ->
Math.pow(x, y) 。
对千第3 种情况,第l 个参数会成为方法的目标。例如, String::compareTolgnoreCase 等
同千(x, y) -> x.compareTolgnoreCase(y) 。

构造器引用

构造器引用与方法引用很类似,只不过方法名为new 。

可以用数组类型建立构造器引用。例如, int[]::new 是一个构造器引用,它有一个参数:
即数组的长度。这等价于lambda 表达式x - > new int[x] 。

变量作用域

lambda 表达式中捕获的变量必须实际上是最终变量(effectively final ) 。
实际上的最终变量是指,这个变扯初始化之后就不会再为它赋新值。

public static void repeat(String text, int count)
{
	for (int i = 1; i <= count; itt)
	{
	Ac tion listener listener= event ->
		{
			System.out.println(i + "":· " + text);
			// Er ror: Cannot refer to changing i
		};
	new Timer(lOOO, listener) .start ();
    }
}
                          

内部类

内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
内部类可以对同一个包中的其他类隐藏起来。
当想要定义一个回调函数且不想编写大量代码时,使用匿名( anonymous ) 内部类比较便捷。

内部类的对象总有一个隐式引用,它指向了创建它的外部类对象

外围类引用:OuterC/ass. this

匿名内部类

将局部内部类的使用再深入一步。假如只创建这个类的一个对象,就不必命名了。

语法:

new SuperType(construction parameters)
{
inner class methods and data
}

匿名类不能有构造器,取而代之的是,将构造器参数传递给超类( superclass) 构造器

示例:

abstract class Person {
    public abstract void eat();
}
 
public class Demo {
    public static void main(String[] args) {
        Person p = new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        };
        p.eat();
    }
}

和普通内部类对比:更简洁

image-20220831171902358

静态内部类

有时候,使用内部类只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。为此,可以将内部类声明为static, 以便取消产生的引用

只有内部类可以声明为stati c 。

内部类对象必须是在静态方法中构造,否则编译器将会给出错误报告:没有可用的隐式ArrayAlg 类型对象初始化内部类对象。

与常规内部类不同, 静态内部类可以有静态域和方法。

声明在接口中的内部类自动成为static 和public 类

代理

利用代理可以在运行时创建一个实现了一组给定接口的新类。这种功能只有在编译时无法确定需要实现哪个接口时才有必要使用。

代理类可以在运行时创建全新的类。这样的
代理类能够实现指定的接口。尤其是,它具有下列方法:
. 指定接口所需要的全部方法。
• Object 类中的全部方法,例如, toString 、equals 等。

,不能在运行时定义这些方法的新代码。而是要提供一个调用处理器( invocation
handl er ) 。调用处理器是实现了InvocationHandler 接口的类对象。在这个接口中只有一个方法:
Object i nvoke(Object proxy, Method method , Object[] args)
无论何时调用代理对象的方法,调用处理器的invoke 方法都会被调用, 并向其传递
Method 对象和原始的调用参数。调用处理器必须给出处理调用的方式。

创建代理对象

需要使用Proxy 类的newProxy Instance 方法

参数:

  1. 一个类加载器(class loader )
  2. 一个C lass 对象数组
  3. 一个调用处理器

过程:(使用代理和调用处理器跟踪方法调用)

  1. 定义了一个TraceHander 包装器类存储包装的对象
  2. 其中的invoke 方法打印出被调用方法的名字和参数
  3. 随后用包装好的对象作为隐式参数调用这个方法
  4. 构造用于跟踪方法调用的代理对象

image-20220902092042926

image-20220902092055782

代理类的特性

在程序运行过程中创建,一旦被创建,就变成了常规类

一个代理类只有一个实例域一一调用处理器,所需要的任何附加数据都必须存储在调用处理器中。

对千特定的类加载器和预设的一组接口来说,只能有一个代理类

代理类一定是publi c 和final

常用接口

Comparable
import java.io.*;
import java.util.*;

class Pair implements Comparable<Pair> {
	String x;
	int y;

	public Pair(String x, int y)
	{
		this.x = x;
		this.y = y;
	}

	public String toString()
	{
		return "(" + x + "," + y + ")";
	}

	@Override public int compareTo(Pair a)
	{
		// if the string are not equal
		if (this.x.compareTo(a.x) != 0) {
			return this.x.compareTo(a.x);
		}
		else {
			// we compare int values
			// if the strings are equal
			return this.y - a.y;
		}
	}
}

public class GFG {
	public static void main(String[] args)
	{

		int n = 4;
		Pair arr[] = new Pair[n];

		arr[0] = new Pair("abc", 3);
		arr[1] = new Pair("a", 4);
		arr[2] = new Pair("bc", 5);
		arr[3] = new Pair("a", 2);

		// Sorting the array
		Arrays.sort(arr);

		// printing the
		// Pair array
		print(arr);
	}

	public static void print(Pair[] arr)
	{
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i]);
		}
	}
}

Java内存分配

image-20230716202840603

  1. 编译Java Source源代码为Java Class
  2. 通过类加载子系统加载到内存中:类加载子系统接收Java Class作为输入,进行类加载、链接和初始化等操作,将类的二进制数据加载到内存中的方法区或元空间。

所有局部变量都会在栈内存中创建

  • 局部变量:定义在方法中的变量或者方法声明上的变量
  • 方法执行都会加载到栈中进行
  • 局部变量特点:随着方法的调用而存在,随着方法的调用完毕而消失

所有对象机器对应的实例变量和数组都将存储在此处

  • 简单理解为: new出来的东西,都存储在堆内存
  • 每一个new出来的东西都有一个地址值,使用完毕,会在垃圾回收器空闲时被回收
  • 实例变量(成员变量)有初始化值:
    • 基本数据类型: 整数: 0,浮点数: 0.0,布尔: false, 字符:空字符
    • 引用数据类型: null

代码: Student S = new Student();

image-20230716203359198

异常

对于异常情况,程序应该做到:

  1. 向用户通告错误;
  2. 保存所有的工作结果;
  3. 允许用户以妥善的形式退出程序。

异常分类

异常层次结构:

image-20220927141958570

非受检异常:

  • Error类层次结构:构描述了Ja va 运行时系统的内部错误和资源耗尽错误。
    应用程序不应该抛出这种类型的对象。
  • Exception层次结构:
    • RuntimeException:由程序错误导致的异常属丁RuntimeException
      • 错误的类型转换。
      • 数组访问越界。
      • 访问null 指针。
    • 其他异常:程序本身没有问题,但由于像l/0 错误这类问题导致的异常屈千其他异常
      • 试图在文件尾部后面读取数据。
      • 试图打开一个不存在的文件。
      • 试图根据给定的字符串查找C l ass 对象,而这个字符串表示的类并不存在。

其他的都叫受检异常

处理错误

声明受查异常

public Fil elnpu t Stream(String name) throws Fil eNotFoundException

在遇到下面4 种情况时应该抛出异常:

  1. 调用一个抛出受查异常的方法, 例如, FilelnputStream 构造器。
  2. 程序运行过程中发现错误,并且利用throw 语句抛出一个受查异常
  3. 程序出现错误,例如, a [- 1)=0 会抛出一个ArrayI ndex OutOffiound sException 这样的非受查异常。
  4. Java 虚拟机和运行时库出现的内部错误
抛出异常
String readData(Scanner in) throws EOFException
{
	while (...)
	{
		if (!in. hasNext ()) {// EOF encountered 
            if (n < l en)  throw new EOFException() ;
        }
	return s;
}

throw用于方法内部,throws用在方法声明中来指明方法可能抛出的多个异常

EOFException 类还有一个含有一个字符串型参数的构造器。这个构造器可以更加细致的描述异常出现的情况。
String gripe= "Content-length: "+ len + ", Received: "+ n;
throw new EOFExcepti on (gripe) ;

对于一个巳经存在的异常类的抛出:

  1. 找到一个合适的异常类U
  2. 创建这个类的一个对象。
  3. 将对象抛出。
创建异常类

习惯上,定义的类应该包含两个构造器, 一个是默认的构造器;另一个是带有详细描述信息的构造器

class FileFormatExcepti on extends IOException
{
	public FileFormatException() {}
	public FileFormatException(String gripe)
	{
	super(gri pe);
    }
}

使用:

String readData(BufferedReader in) throws FileFormatExcept1on
{
	while (. ..)
	{
		if (ch == -1) {// EDF encountered
            if (n < l en)
			throw new Fil eformatException();
        }
	}
	return s;
}

捕获异常

如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息,其中包括异常的类型堆栈的内容

使用try-catch语句块
例子(读取数据)

public void read(String filename)
{
    try {
        InputStream in = new FileinputStream(filename);
		int b;
        while ((b = in.read()) != -1) {
            process input
        }
	}
    catch (IOException exception) {
        exception.printStackTrace () ;
    }
}

如果采用这种处理方式,就必须声明这个方法可能会抛出一个IOException
编译器严格地执行throws 说明符。如果淜用了一个抛出受查异常的方法,就必须对它进行处理,或者继续传递。

如果编写一个覆盖超类的方法,而这个方法又没有抛出异常( 如JComponent 中的paintComponent) ,那么这个方法就必须捕获方法代码中出现的每一个受查异常。不允许在子类的throws 说明符中出现超过超类方法所列出的异常类范围。

捕获多个异常:

try {
	code that might throw exceptions
}
catch (FileNotFoundException | UnknownHostException e) {
    ernergency action for missing files and unkrnown hosts
}
catch (IOException e) {
    ermergency action for all other I/O problems
}

捕荻多个异常时,异常变量隐含为final变量。
不能在catch中为e赋不同的值

再次抛出异常与异常链

在catc h 子旬中可以抛出一个异常,这样做的目的是改变异常的类型

try
{
	access the database
}
catch (SQLException e)
{
	Throwable se = new ServletException("database error");
	se.initCause(e);
	throw se;
}

当捕获到异常时,就可以使用下面这条语句重新得到原始异常:
Throwable e = se.getCause();

finally子句

不管是否有异常被捕获, finally 子旬中的代码都被执行,常用于异常发生时妥善地关闭一个文件

解耦合try/catch 和try/finally 语句块。以提高代码的清晰度。

InputStream in =,。.;
try
{
	try
	{
		code that might throw exceptions
		finally 
        {
            in.close();
        }
}
catch (I0Exception e)
{
    show error message
}

内层的try 语句块只有一个职责,就是确保关闭输入流。外层的try 语句块也只有一个职责,就是确保报告出现的错误。这种设计方式不仅清楚,而且还具有一个功能,就是将会报告finally 子句中出现的错误。

字符串

常用方法

  1. 比较方法:

    1. equals():比较两个字符串中的具体内容(==用来比较字符串的存储地址是否相同)
    2. equalsIgnoreCase():比较两个字符串时不区分大小写
    3. startsWith()方法和endsWith()方法:依次用来判断字符串是否以指定的字符串开始或结束
    4. compareTo():判断一个字符串是大于、等于还是小于另一个字符串
  2. length、toLowerCase()和toUpperCase():长度、大小写转换

  3. 查找方法:

    1. indexOf():搜索字符或字符串首次出现的位置
    2. lastIndexOf():搜索最后一次出现的位置
  4. 截取方法:substring()

  5. 去掉字符串的首尾空格:trim()

  6. 替换字符串中的字符或子串:replace()
    public String replace(char oldChar, char newChar)

  7. split():在给定正则表达式的匹配项周围分割给定字符串。根据给定的正则表达式拆分后,此方法返回一个字符串数组。

    Input String: 016-78967
    Regular Expression: - 
    Output : {"016", "78967"}
    
    public class GFG { 
      
        // Main driver method 
        public static void main(String args[]) 
        { 
            // Custom input string 
            String str = "geekss@for@geekss"; 
            String[] arrOfStr = str.split("@", 2); //2是结果阈值
      
            for (String a : arrOfStr) 
                System.out.println(a); 
        } 
    }d
    
  8. tostring():返回对象的字符串表达形式,print会默认调用。建议所有子类都重写此方法。

  9. toCharArray() :方法将给定的字符串转换为字符序列。返回的数组长度等于字符串的长度。

正则表达式

正则表达式可以用字符串来描述规则,并用来匹配字符串。
只需要编写正确的规则,我们就可以让正则表达式引擎去判断目标字符串是否符合规则。

image-20221007150140016

模式

Pattern.compile() 方法的第一个参数是模式。它描述了正在搜索的内容。
括号用于查找一系列字符:

表达式描述
[abc]从括号内的选项中查找一个字符
[^abc]找到一个不在括号内的字符
[0-9]从 0 到 9 范围内查找一个字符
元字符

元字符是具有特殊含义的字符:

元字符描述
|查找由|分隔的任意一种模式匹配,如:cat|fish|dog
.只查找任何字符的一个实例
^查找作为字符串开头的匹配项,如:^Hello
$在字符串末尾查找匹配项,如:World$
\d找一个数字
\s查找空白字符
\b在这样的单词开头查找匹配项:\bWORD,或在这样的单词结尾处查找匹配项:WORD\b
\uxxxx查找十六进制数 xxxx 指定的 Unicode 字符
\w一个字字符: [a-zA-Z_0-9]
名词
名词描述
n+匹配任何包含至少一个 n 的字符串
n*匹配任何包含零次或多次 n 的字符串
n?匹配任何包含零次或一次 n 的字符串
n{x}匹配任何包含 X n 序列的字符串
n{x,y}匹配任何包含 X 到 Y n 序列的字符串
n{x,}匹配任何包含至少 X n 个序列的字符串
pattern和Matcher

正则表达式的编译表示。
指定为字符串的正则表达式必须首先编译为此类的实例。然后可以使用生成的模式创建一个 Matcher 对象,该对象可以将任意字符序列与正则表达式进行匹配。执行匹配所涉及的所有状态都驻留在匹配器中,因此许多匹配器可以共享相同的模式。

使用范例:

 Pattern p = Pattern.compile("a*b");
 Matcher m = p.matcher("aaaaab");
 boolean b = m.matches();

当一个正则表达式只使用一次时,这个类定义了一个matches方法。此方法编译一个表达式并在一次调用中将输入序列与它匹配。该声明
boolean b = Pattern.matches("a*b", "aaaaab");
等效于上面的三个语句,尽管对于重复匹配,它的效率较低,因为它不允许重用已编译的模式。
此类的实例是不可变的,并且可以安全地被多个并发线程使用。 Matcher 类的实例对于这种使用是不安全的。

Matcher方法

方法名描述
matches()尝试将总输入序列与模式匹配。
lookingAt()尝试将输入序列与模式匹配,从头开始。
find()这会扫描输入序列并寻找下一个与模式特别匹配的子序列。
捕获组
  1. 普通捕获组

    从正则表达式左侧开始,每出现一个左括号"("记做一个分组,分组编号从 1 开始。0 代表整个表达式。

    对于时间字符串:2017-04-25,表达式如下

    (\\d{4})-((\\d{2})-(\\d{2}))
    image-20221118154538415

  2. 命名捕获组
    每个以左括号开始的捕获组,都紧跟着 ?,而后才是正则表达式。

    对于时间字符串:2017-04-25,表达式如下:
    (?<year>\\d{4})-(?<md>(?<month>\\d{2})-(?<date>\\d{2}))
    image-20221118154549319

示例代码

import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
public class RegexMatches
{
    public static void main( String[] args ){
 
      // 按指定模式在字符串查找
      String line = "This order was placed for QT3000! OK?";
      String pattern = "(\\D*)(\\d+)(.*)";
 
      // 创建 Pattern 对象
      Pattern r = Pattern.compile(pattern);
 
      // 现在创建 matcher 对象
      Matcher m = r.matcher(line);
      if (m.find( )) {
         System.out.println("Found value: " + m.group(0) );
         System.out.println("Found value: " + m.group(1) );
         System.out.println("Found value: " + m.group(2) );
         System.out.println("Found value: " + m.group(3) ); 
      } else {
         System.out.println("NO MATCH");
      }
   }
}

以上实例编译运行结果如下:

Found value: This order was placed for QT3000! OK?
Found value: This order was placed for QT
Found value: 3000
Found value: ! OK?

常用API

Java API :指的就是jDK中提供的各种功能的Java类。

  • 帮助文档
    • 可以通过索引快速找到想学习的类, 了解
      • 如何导入:看对应的包,只有java.lang类型不需要导入包
      • 了解用途:看类的描述
      • 如何创建:看类的构造方法
      • 了解具体功能:看类的成员方法,了解描述,方法名和参数,返回类型

Date

date and time API

ClassDescription
LocalDateRepresents a date (year, month, day (yyyy-MM-dd))
LocalTimeRepresents a time (hour, minute, second and nanoseconds (HH-mm-ss-ns))
LocalDateTimeRepresents both a date and a time (yyyy-MM-dd-HH-mm-ss-ns)
DateTimeFormatterFormatter for displaying and parsing date-time objects

使用示例:

  1. 前三个:

     LocalDate myObj = LocalDate.now(); // Create a date object
        System.out.println(myObj); // Display the current date
    
  2. Formatting Date and Time:

    import java.time.LocalDateTime; // Import the LocalDateTime class
    import java.time.format.DateTimeFormatter; // Import the DateTimeFormatter class
    
    public class Main {
      public static void main(String[] args) {
        LocalDateTime myDateObj = LocalDateTime.now();
        System.out.println("Before formatting: " + myDateObj);
        DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");
    
        String formattedDate = myDateObj.format(myFormatObj);
        System.out.println("After formatting: " + formattedDate);
      }
    }
    

Scanner

实现输入操作的类

  1. 键盘输入:

     Scanner myObj = new Scanner(System.in);  // Create a Scanner object
        System.out.println("Enter username");
    
        String userName = myObj.nextLine();  // Read user input
        System.out.println("Username is: " + userName);  // Output user input
    

    输入类型:

    MethodDescription
    nextBoolean()Reads a boolean value from the user
    nextByte()Reads a byte value from the user
    nextDouble()Reads a double value from the user
    nextFloat()Reads a float value from the user
    nextInt()Reads a int value from the user
    nextLine()Reads a String value from the user
    nextLong()Reads a long value from the user
    nextShort()Reads a short value from the user

StringBuffer

与String类不同的是,由StringBuffer类创建的字符串对象是可以修改的

StringBuffer s = new StringBuffer(); //初始化的StringBuffer对象是一个空对象

构造方法:

  1. StringBuffer():分配给该对象的初始容量可以容纳16个字符,自动增加
  2. StringBuffer(int size):初始容量是由参数size指定,自动增加
  3. StringBuffer(String s):初始容量为参数字符串s的长度额外再增加16个字符。

常用方法:

  1. append():可以将其他Java类型数据转化为字符串后,再追加到StringBuffer对象中。
  2. charAt(int n):获取参数n指定位置上的单个字符。
  3. setCharAt(int n,char ch):将当前StringBuffer对象实体中的字符对象位置n处的字符用参数ch指定的字符替换。
  4. StringBuffer insert(int index,String str):将参数str指定的字符串插入到参数index的位置,并返回当前对象的引用。
  5. public StringBuffer reverse():将该对象实体中的字符翻转,并返回当前对象的引用。

Fommater

修饰符

  1. “+”加号修饰符:格式化正整数时,强制添加上正号,例如,%+d将12格式化为“+12”。
  2. “,”逗号修饰符:格式化整数时,按“千”分组,例如:String m = String.format(“按千分组:%,d。按千分组带正号%+,d”,123456,7890);

数据宽度

  1. %md:在数字的左面增加空格

    %-md:在数字的右面增加空格

  2. 限制小数位数的“宽度”:“%.nf”可以限制小数的位数

Random

生成随机数

构造方法:

  • Random():创建一个新的随机数生成器

成员方法:

  • int nextlnt(int bound):获取-个int类型的随机数,参数bound表示获取到的随机数在[0,bound)之间

RandomStringUtils

创建随机字符串

名称描述
randomAlphanumeric(int count)创建一个随机字符串,其长度是指定的字符数。

Arrays

sort()
// Java program to demonstrate Working of
// Comparator interface

// Importing required classes
import java.io.*;
import java.lang.*;
import java.util.*;

// Class 1
// A class to represent a student.
class Student {
	int rollno;
	String name, address;

	// Constructor
	public Student(int rollno, String name, String address)
	{
		// This keyword refers to current object itself
		this.rollno = rollno;
		this.name = name;
		this.address = address;
	}

	// Used to print student details in main()
	public String toString()
	{
		return this.rollno + " " + this.name + " "
			+ this.address;
	}
}

// Class 2
// Helper class extending Comparator interface
class Sortbyroll implements Comparator<Student> {
	// Used for sorting in ascending order of
	// roll number
	public int compare(Student a, Student b)
	{
		return a.rollno - b.rollno;
	}
}

// Class 3
// Main class
class GFG {

	// Main driver method
	public static void main(String[] args)
	{
		Student[] arr
			= { new Student(111, "bbbb", "london"),
				new Student(131, "aaaa", "nyc"),
				new Student(121, "cccc", "jaipur") };

		System.out.println("Unsorted");

		for (int i = 0; i < arr.length; i++)
			System.out.println(arr[i]);

		// Sorting on basic as per class 1 created
		// (user-defined)
		Arrays.sort(arr, new Sortbyroll());

		System.out.println("\nSorted by rollno");

		for (int i = 0; i < arr.length; i++)
			System.out.println(arr[i]);
	}
}

包装类

  1. Integer:将基本类型int、long和short封装成一个类

    • 构造方法:
      Integer integer=Integer.*valueOf(8);*
    • 常用方法:该类提供了多个方法,能在类型之间互相转换
      image-20221108113251211image-20221108113422490
    • 常量:image-20221108113445921
  2. Boolean:将基本类型为boolean的值包装在一个对象中

    • 构造方法:
      1. Boolean(boolean value)
      2. Boolean(String str):如果String参数不为null且在忽略大小写时等于true,则分配一个表示true值的Boolean对象,否则获得一个false值的Boolean对象。
    • 常用方法:
      image-20221109124025154
    • 常量:image-20221109124159740
  3. Byte:

    • 构造方法:

      1. Byte(byte value):表示指定的byte值
      2. Byte(String str):可表示String参数所指示的byte值。
    • 常用方法:image-20221109124425049

    • 常量:

      (1) MIN_ VALUE: byte类型可取的最小值。
      (2) MAX
      VALUE: byte类型可取的最大值。
      (3) SIZE: 用于以二进制补码形式表示byte值的位数。
      (4)TYPE:表示基本类型byte的Class实例。

  4. Character:包装一个基本类型为char的值

    • 构造方法:Character(char value)
    • 常用方法:image-20221109124619982
    • 常量:
      (1) CONNECTOR_ PUNCTUATION: 返回byte型值,表示Unicode规范中的常规类别“Pc"
      (2) UNASSIGNED: 返回byte型值,表示Unicode规范中的常规类别“Cn"
      (3) TITLECASE_ _LETTER: 返回byte型值,表示Unicode规范中的常规类别“Lt"
  5. Double、Float:是对double、float基本类型的封装,它们都是Number类的子类

    • 构造方法:

      1. Double(double value):基于double参数创建Double类对象。
      2. Double(String str):构造一个新分配的Double对象,表示用字符串表示的double类型的浮点值。
    • 常用方法:image-20221109125054984

    • 常量:

      (1) MAX_ EXPONENT: 返回int值, 表示有限double变量可能具有的最大指数。
      (2) MIN_ EXPONENT: 返回int值, 表示标准化double变量可能具有的最小指数。
      (3) NEGATIVE_ INFINITY: 返回double值,表示保存double类型的负无穷大值的常量。
      (4) POSITIVE
      INFINITY: 返回double值,表示保存double类型的正无穷大值的常量。

  6. Number:是BigDecimal、BigInteger、Byte、Double、Float、Integer、Long和Short类的父类

    • 常用方法:image-20221109125229080

其他

  1. math:

    两个静态常量:E、PI

    常用方法:

    1. sqrt():求平方根
    2. random():产生一个0到1之间的随机数
  2. Random:

    // create instance of Random class
            Random rand = new Random();
      
            // Generate random integers in range 0 to 999
            int rand_int1 = rand.nextInt(1000);
            int rand_int2 = rand.nextInt(1000);
      
            // Print random integers
            System.out.println("Random Integers: "+rand_int1);
            System.out.println("Random Integers: "+rand_int2);
      
            // Generate Random doubles
            double rand_dub1 = rand.nextDouble();
            double rand_dub2 = rand.nextDouble();
      
            // Print random doubles
            System.out.println("Random Doubles: "+rand_dub1);
            System.out.println("Random Doubles: "+rand_dub2);
        }
    
    

泛型程序设计

泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用

定义简单泛型类

一个泛型类(generic class) 就是具有一个或多个类型变扯的类。

注解

Annotation其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用Annotation,程序员可以在不改变原有逻辑的情况下在源文件中嵌入- -些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署
Annotation可以像修饰符一 样被使用,可用于修饰包,类,构造器,方法,成员变量,参数,局部变量的声明,这些信息被保存在Annotation的“name=value"对中。

常用注解

  • Override:限制重写父类方法,该注解只能用于方法
  • Deprecated:用于表示所修饰的元素(类, 方法等)已过时
  • SuppressWarnings:抑制编译器警告image-20220812144018281

自定义注解

  • 格式:

    • 定义:

      public @interface注解名称{
          属性类型 属性名() default 默认值;
      }
      
    • 使用:使用前在文件头用@引入

      public Anno anno() defalt @Anno;
      

      在使用注解的时候如果注解里面的属性没有指定默认值。
      那么我们就需要手动给出注解属性的设置值。例如@Anno1(name = "itheima")

value属性:如果一个注解中只有一个属性,并且该属性的名称是"value",则在使用注解时可以省略"value="

元注解

即描述注解的注解

  • @Target:指定了注解能在哪里使用(成员变量FIELD、类TYPE、方法METHOD)
    @Target({ElementType.FIELD})
  • @Retention:可以理解为保留时间(生命周期),不写就是默认存活在源码阶段(即java文件,编译时会消失)
    @Retention(value = RetentionPolicy.RUNTIME)(表示运行时可以存在)
  • @Inherited:表示修饰的自定义注解可以被子类继承,默认不继承
  • @Documented:表示该自定义注解,会出现在API文档里面。

JUnit单元测试

使用
  1. 导入jar包到工程中
  2. 编写测试方法该测试方法必须是公共的无参数无返回值的非静态方法
  3. 在测试方法上使用@Test注解标注该方法是个测试方法
  4. 选中测试方法右键通过junit运行该方法
常用注解
  • @Test:表示测试该方法
  • @Before:在测试的方法前运行
  • @After:在测试的方法后运行

集合

集合框架中的接口

image-20221011144959880

image-20221109130131944

访问元素的方法:

  1. 用迭代器遍历(适合链表)
  2. 使用整数索引(随机访问)(适合数组)
Collection接口

image-20221109130142905

具体集合

image-20221011145759040

集合框架中的类image-20221011145835191

List

image-20221109130736556

数组列表ArrayList
public class Main {
  public static void main(String[] args) {
    ArrayList<String> cars = new ArrayList<String>();
    cars.add("Volvo");
    cars.add("BMW");
    cars.add("Ford");
    cars.add("Mazda");
    for (int i = 0; i < cars.size(); i++) {
      System.out.println(cars.get(i));
    }
  }
}

Arrylist的sort()方法的使用

  1. 使用接口Comparable<Stu>
  2. 覆写compareTo
  3. 排序Collections.sort(stus);
package demo2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Scanner;

public class Student implements Comparable<Student>{
    private int id;
    private String name;
    private float score;

    public Student(int id, String name, float score) {
        this.id = id;
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        System.out.println("该学生的学号为:" + id + ",姓名为:" + name + ",成绩为:" + score);
        return super.toString();
    }
    @Override
    public int compareTo(Student o) {
        if (this.score < o.score) return 1;
        else if (this.score > o.score) return -1;
        else return 0;
    }


    public static void main(String[] args) {
        System.out.println("请输入3个学生的学号、姓名和成绩:");
        ArrayList<Student> stus = new ArrayList<Student>();
        Scanner in = new Scanner(System.in);
        int id;
        String name;
        float score;
        for (int i = 0; i < 3; i++) {
            int term = i + 1;
            System.out.println("请输入第" + term + "组:");
            id = in.nextInt();
            name = in.next();
            score = in.nextFloat();
            Student stu = new Student(id, name, score);
            stus.add(stu);
        }
        System.out.println("录入的学生信息如下:");
        for (int i = 0; i <= 2; i++) {
            stus.get(i).toString();
        }
        Collections.sort(stus);
        System.out.println("按成绩降序排序后的学生信息如下:");
        for (int i = 0; i <= 2; i++) {
            stus.get(i).toString();
        }
    }


}

二维ArrayList

/*
 * 集合的嵌套遍历
 * 需求:
 * 		我们班有学生,每一个学生是不是一个对象。所以我们可以使用一个集合表示我们班级的学生。ArrayList<Student>
 * 		但是呢,我们旁边是不是还有班级,每个班级是不是也是一个ArrayList<Student>。
 * 		而我现在有多个ArrayList<Student>。也要用集合存储,怎么办呢?
 * 		就是这个样子的:ArrayList<ArrayList<Student>>
 */
package cn.itcast_02;
 
import java.util.ArrayList;
 
public class ArrayListDemo {
	public static void main(String[] args) {
		// 创建大集合
		ArrayList<ArrayList<Student>> bigArrayList = new ArrayList<ArrayList<Student>>();
 
		// 创建第一个班级的学生集合
		ArrayList<Student> firstArrayList = new ArrayList<Student>();
		// 创建学生
		Student s11 = new Student("唐僧", 30);
		Student s12 = new Student("孙悟空", 29);
		Student s13 = new Student("猪八戒", 28);
		Student s14 = new Student("沙僧", 27);
		Student s15 = new Student("白龙马", 26);
		// 学生进班
		firstArrayList.add(s11);
		firstArrayList.add(s12);
		firstArrayList.add(s13);
		firstArrayList.add(s14);
		firstArrayList.add(s15);
		// 把第一个班级存储到学生系统中
		bigArrayList.add(firstArrayList);
 
		// 创建第二个班级的学生集合
		ArrayList<Student> secondArrayList = new ArrayList<Student>();
		// 创建学生
		Student s21 = new Student("诸葛亮", 30);
		Student s22 = new Student("司马懿", 28);
		Student s23 = new Student("周瑜", 26);
		// 学生进班
		secondArrayList.add(s21);
		secondArrayList.add(s22);
		secondArrayList.add(s23);
		// 把第二个班级存储到学生系统中
		bigArrayList.add(secondArrayList);
 
		// 创建第三个班级的学生集合
		ArrayList<Student> thirdArrayList = new ArrayList<Student>();
		// 创建学生
		Student s31 = new Student("宋江", 40);
		Student s32 = new Student("吴用", 35);
		Student s33 = new Student("高俅", 30);
		Student s34 = new Student("李师师", 22);
		// 学生进班
		thirdArrayList.add(s31);
		thirdArrayList.add(s32);
		thirdArrayList.add(s33);
		thirdArrayList.add(s34);
		// 把第三个班级存储到学生系统中
		bigArrayList.add(thirdArrayList);
 
		// 二维集合的遍历
		for (ArrayList<Student> array : bigArrayList) {
			for (Student s : array) {
				System.out.println(s.getName() + "---" + s.getAge());
			}
		}
	}
}

创建一个元素类型是ArrayList的数组
ArrayList<String> a[] = new ArrayList[9];

常用方法:

NameDescribe
indexOf(Object o)Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element.
get(int index)Returns the element at the specified position in this list.
链表LinkedList
  • 与ArrayList区别:

    1. ArrayList:从数组的中间位置删除一个元素要付出很大的代价
      image-20221011150017434

    2. linked list
      image-20221011150056129

      由于迭代器是描述集合中位置的,所以这种依赖于位置的add 方法将由迭代器负责。只有对自然有序的集合使用迭代器添加元素才有实际意义。list中有set中没有add

  • 异常:
    如果迭代器发现它的集合被另一个迭代器修改了,或是被该集合自身的方法修改了,就会抛出一个
    ConcurrentModificationException 异常。
    避免并发修改异常:
    可以根据需要给容器附加许多的迭代器,但是这些迭代器只能读取列表。另外,再单独附加一个既能读又能写的迭代器。
    检测到并发修改的问题:
    集合可以跟踪改写操作(诸如添加或删除元素)的次数,每个迭代器都维护一个独立的计数值。在每个迭代器方法的开始处检查自已改写操作的计数值是否与集合的改写操作计数值一致。如果不一致,抛出一个Concurrent
    ModificationException 异常。

  • 链表不支待快速地随机访问。

散列集HashTable
  • 散列集将按照有利于其操作目的的原则组织数据。
  • 散列表为每个对象计算一个整数,称为散列码(hash code ) 。
    散列码是由对象的实例域产生的一个整数。具有不同数据域的对象将产生不同的散列码。
    image-20221011152252357
    自定义类要实现hashCode方法,应与equals方法兼容,即如果a.equals(b) 为true, a 与b 必须具有相同的
    散列码。
  • 实现:用链表数组,每个列表被称为桶
    image-20221011152514764
  • 散列冲突:元素插入到桶中时,该桶已经被占满
    再散列(rehashed):如果散列表太满,就需要再散列(rehashed)
    装填因子(load factor) 决定何时对散列表进行再散列。
  • 实现的数据结构:
    set:没有重复元素的元素集合
    HashSet :实现了基于散列表的集,其contains 方法已经被重新定义,用来快速地查看是否某个元素已经出现在集中。它只在某个桶中查找元素,而不必查看集合中的所有元素。随机访问,只有不关心集合中元素的顺序时才应该使用HashSet 。
树集TreeSet
  • 一个有序集合(sorted collection) 。可以以任意顺序将元素插入到集合中。在对集合进行遍历时,每个值将
    自动地按照排序后的顺序呈现。
  • 要使用树集,必须能够比较元素。这些元素必须实现Comparable 接口(或者构造集时必须提供一个Comparator)
队列
  1. (双端)队列:Deque 接口提供
  2. 优先级队列:使用堆

一个优先级队列既可以保存实现了Comparable 接口的类对象,也可以保存在构造器中提供的Comparator 对象。

set
红黑树
  • 规则
    • 每一个节点或是红色的,或者是黑色的。
    • 根节点必须是 黑色
    • 如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil ,这些Ni视为叶节点,每个叶节点(NiI)是黑色的;(Nil是空)
    • 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
    • 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

image-20230805092726191

映射

HashMap 和TreeMap

哈希图HashMap

它将数据存储在(键,值)对中,您可以通过另一种类型的索引(例如整数)访问它们。一个对象用作另一个对象(值)的键(索引)。如果您尝试插入重复键,它将替换相应键的元素。
HashMap 类似于 HashTable,但它是不同步的。它也允许存储空键,但应该只有一个空键对象,并且可以有任意数量的空值。此类不保证Map的顺序。要使用该类及其方法,需要导入 java.util.HashMap 包或其超类。

常用方法

NameDescribe
get(Object key)Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
put(K key, V value)Associates the specified value with the specified key in this map.
containsKey(Object key)Returns true if this map contains a mapping for the specified key.
keySet()Returns a Set view of the keys contained in this map.
remove(Object key)Removes the mapping for the specified key from this map if present.

示例

// Java program to traversal a
// Java.util.HashMap

import java.util.HashMap;
import java.util.Map;

public class TraversalTheHashMap {
	public static void main(String[] args)
	{
		// initialize a HashMap
		HashMap<String, Integer> map = new HashMap<>();

		// Add elements using put method
		map.put("vishal", 10);
		map.put("sachin", 30);
		map.put("vaibhav", 20);

		// Iterate the map using
		// for-each loop
		for (Map.Entry<String, Integer> e : map.entrySet())
			System.out.println("Key: " + e.getKey()
							+ " Value: " + e.getValue());
	}
}

输入/输出

File

功能:新建、删除、重命名文件和目录,但不能访问文件内容本身

相对路径的根目录是project的根文件夹

  1. 常用方法:image-20221109131322151
  2. 文件过滤器
    在File类的list()方法中可以接收-一个FilenameFilter 参数,通过该参数可以只列出符合条件的文件。

I/O流

在程序中以连续的方式传输数据的抽象概念image-20230721212434385

常见方法

分类

  1. 获取Stream流
    创建一条流水线 ,并把数据放到流水线上准备进行操作
    • 单列集合:可以使用Collection接口中的默认方法stream(生成流
      default Stream<E> stream()
    • 双列集合(如map、hashmap):间接的生成流
      可以先通过keySet或者entrySet获取一个Set集合 ,再获取Stream流
      hm.entrySet().stream().forEach(s->System.out.println(s));
    • 数组:Arrays中的静态方法stream生成流
    • 同种数据类型的多个数据:
      使用Stream.of(T…values)生成流
      stream.of(1,2,3,4,5,6,7 ,8). forEach( action: s->System.out.println(s));
  2. 中间方法
    流水线上的操作。
    一次操作完毕之后 ,还可以继续进行其他操作。
    • Stream<T> filter(Predicate predicate) :用于对流中的数据进行过滤
      Predicate接口中的方法
      boolean test(T t) :对给定的参数进行判断,返回-个布尔值
    • Stream<T> limit(long maxSize) :截取指定参数个数的数据
    • Stream<T> skip(long n) :跳过指定参数个数的数据
    • static <T> Stream<T> concat(Stream a, Stream b) :合并a和b两个流为一一个流
    • Stream<T> distinct) :去除流中重复的元素。依赖(hashCode和equals方法)
  3. 终结方法
    一个Stream流只能有一个终结方法,是流水线上的最后一个操作
    • void forEach(Consumer action) :对此流的每个元素执行操作
      Consumer接口中的方法
    • void accept(Tt) :对给定的参数执行此操作
    • long count() :返回此流中的元素数
    • R collect(Collector collector)收集方法(在Stream流中无法直接修改集合,数组等数据源中的数据,只负责收集数据)
      • public static <T> Collector toList() :把元素收集到List集合中
      • public static <T> Collector toSet() :把元素收集到Set集合(与list的区别是,set不可重复)中
      • public static Collector toMap(Function keyMapper,Function valueMapper) :把元素收集到Map集合中

用户输入

System.in

import java.util.Scanner;  // 导入Scanner

public class Main {
  public static void main(String[] args) {
    Scanner myObj = new Scanner(System.in);  // 创建Scanner对象
    System.out.println("请输入用户名");

    String userName = myObj.nextLine();  // 读取用户输入
    System.out.println("UserName = " + userName);  // 输出用户的输入
  }
}

文件输入输出

文件字符输入流FileReader

字符流以字符为单位传送数据,只能传送文本类型的数据。

以下是FileReader类读取指定磁盘文件内容的实例:

package com.io;
 
import java.io.File;
import java.io.FileReader;
 
public class FileInAndOut {
    public static void main(String[] args) {
        //定义指定磁盘的文件的File对象
        File file = new File("D://word.txt");
        if(! file.exists()){
            System.out.println("对不起,不包含指定路径的文件");
        }else{
            //根据指定路径的File对象创建FileReader对象
            try {
                FileReader fr = new FileReader(file);
                char[] data = new char[23];         //定义char数组
                int length = 0;
                while((length = fr.read(data))>0){          //循环读取文件中的数据
                    String str = new String(data,0,length);         //根据读取文件的内容创建String 对象
                    System.out.println(str);                //输出读取内容
                }
                fr.close();                             //关闭流
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
文件字符输出流FileWriter

文件字符输出流继承了Writer类,提供了向文件输出的各种方法,数据通过文件字符输出流以字符为单位输出并保存到文件中。

package com.io;
/**
 * 通过给定的String类型参数的指定文件名称与路径,创建FileWriter类
 */
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
 
public class FileWriterDemo {
    public static void main(String[] args) {
        File file = new File("D://word2.txt");      //创建指定文件
        try {
        if(! file.exists()){
                file.createNewFile();               //如果指定文件不存在,新建文件
        }
        FileReader fr = new FileReader("D://word.txt");
        FileWriter fw = new FileWriter(file);               //创建FileWriter对象
        int length = 0;
        while((length = fr.read()) != -1){          //如果没有读到文件末尾
            fw.write(length);           //向文件写入数据
        }
        fr.close();                         //关闭流
        fw.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

对象序列化

可以直接存取对象

将对象存入一个流称为序列化。而从一个流将对象读出称为反序列化。
使用对象序列化功能可以非常方便地将对象写入输出流,或者从输入流读取对象。

  1. ObjectInput与ObjectOutput
    1. readObject()(反序列化):Object object=readObject()
      使用readObject()方法获取的序列化对象是Object类型的,必须通过强行类型转换才能使用。
    2. writeObject()(序列化):writeObject(object);
  2. ObjectInputStream与ObjectOutputStream:
    ObjectInputStream(InputStream in)
    new ObjectInputStream(in);

Swing

概述

  1. 轻量级组件,全平台运行时效果相同

  2. Swing组件采用MVC ( Model-View-Controller,即模型一视图一控制器)设计模式

  3. Swing 是 Java 为图形界面应用开发提供的一组工具包,是 Java 基础类的一部分。

    Swing 包含了构建图形界面(GUI)的各种组件,如: 窗口、标签、按钮、文本框等

  4. 每个Swing组件都有-一个对应的UI类,如果需要改变程序的外观风格,则可以使用如下代码。
    image-20221030095208650

Swing组件

一个 Java 的图形界面,由各种不同类型的“元素”组成,例如: 窗口、菜单栏、对话框、标签、按钮、文本框等等,这些“元素”统一被称为 组件

组件分类: 顶层容器中间容器基本组件
层级结构:

  • 顶层容器:属于窗口类组件,继承自java.awt.Window
    • 菜单栏
    • 中间容器:继承自javax.swing.JComponent
      • 基本组件
顶层容器

顶层容器属于窗口类组件,可以独立显示,一个图形界面至少需要一个窗口

常用顶层容器:

#组件描述
1JFrame一个普通的窗口(绝大多数 Swing 图形界面程序使用 JFrame 作为顶层容器)
2JDialog对话框
中间容器

中间容器充当基本组件的载体,不可独立显示。中间容器可以添加若干基本组件(也可以嵌套添加中间容器),对容器内的组件进行管理,类似于给各种复杂的组件进行分组管理。最顶层的一个中间容器必须依托在顶层容器(窗口)内。

常用的中间容器

#组件描述
1JPanel一般轻量级面板容器组件
2JScrollPane带滚动条的,可以水平和垂直滚动的面板组件
3JSplitPane分隔面板
4JTabbedPane选项卡面板
5JLayeredPane层级面板

特殊的中间容器:

#组件描述
1JMenuBar菜单栏
2JToolBar工具栏
3JPopupMenu弹出菜单
4JInternalFrame内部窗口
基本组件

基本组件是直接实现人机交互的组件。

常用的简单的基本组件:

#组件描述常用方法
1JLabel标签
2JButton按钮
3JRadioButton单选按钮
4JCheckBox复选框
5JToggleButton开关按钮
6JTextField文本框
7JPasswordField密码框
8JTextArea文本区域
9JComboBox下拉列表框
10JList列表
11JProgressBar进度条
12JSlider滑块

选取器组件:

#组件描述
1JFileChooser文件选取器
2JColorChooser颜色选取器

其他较为复杂的基本组件:

#组件描述
1JTable表格
2JTree
其他
  1. 特殊容器:在用户界面上具有特殊作用的中间容器,如JIntermnalFrame、JRootPane、JLayeredPane和JDestopPane等。
  2. 不可编辑信息的显示组件:向用户显示不可编辑信息的组件,如JLabel JProgressBar和JToolTip等。
  3. 可编辑信息的显示组件:向用户显示能被编辑的格式化信息的组件,如JTable、 JTextArea和JTextField等。
  4. 特殊对话框组件:可以直接产生特殊对话框的组件,如JColorChooser和JFileChooser等。

ImageIcon:创建带图标的按钮、菜单项

//创建“保存”菜单项,并为之指定图标
Icon saveIcon = new ImageIcon("ico/save. png") ;
JMenuItem saveItem = new JMenul tem ("保存",saveIcon) ;

用户单击窗口右上角的‘X"按钮时,程序退出:setDefaultCloseOperation(JFrame.EXIT_ ON_ CLOSE)

布局管理器

通过**setLayout****(LayoutManager mgr)**方法设置组件容器采用的布局管理器,如果不采用任何布局管理器,则可以将其设置为null,如:getContentPane().setLayout(null);

Swing 的各种组件(JComponent)添加到面板容器中(JPanel),需要给面板容器指定布局管理器(LayoutManager),明确容器(Container)内的各个组件之间的排列布局方式。

#布局管理器描述
1FlowLayout流式布局,按组件加入的顺序,按水平方向排列,排满一行换下一行继续排列。
2GridLayout网格布局,把Container按指定行列数分隔出若干网格,每一个网格按顺序放置一个控件。
3GridBagLayout网格袋布局,按网格划分Container,每个组件可占用一个或多个网格,可将组件垂直、水平或沿它们的基线对齐。
4BoxLayout箱式布局,将Container中的多个组件按 水平 或 垂直 的方式排列。
5GroupLayout分组布局,将组件按层次分组(串行 或 并行),分别确定 组件组 在 水平 和 垂直 方向上的位置。
6CardLayout卡片布局,将Container中的每个组件看作一张卡片,一次只能显示一张卡片,默认显示第一张卡片。
7BorderLayout边界布局,把Container按方位分为 5 个区域(东、西、南、北、中),每个区域放置一个组件。
8SpringLayout弹性布局,通过定义组件四条边的坐标位置来实现布局。
9null绝对布局,通过设置组件在Container中的坐标位置来放置组件。

事件监听

The Java ActionListener is notified whenever you click on the button or menu item. It is notified against ActionEvent. The ActionListener interface is found in java.awt.event package. It has only one method: actionPerformed().

image-20221111112123986

窗体事件

image-20221111185517217

在窗体事件上注册监听器:image-20221111195459142

监听适配器

在整个事件处理中提供了很多的Adapter (适配器)类,方便用户进行事件处理的实现,以WindowAdapter为例, 用户只要继承了此类,就可以根据自己的需要覆写方法,如果现在只需要关心窗口关闭方法,则只在子类中覆写windowClosing()方法即可。
image-20221111195617438

如何写一个ActionListener

  1. 在类中实现 ActionListener 接口:
    public class ActionListenerExample Implements ActionListener
  2. 向 Listener 注册组件:
    component.addActionListener(instanceOfListenerclass);
  3. 覆盖 actionPerformed() 方法:
    public void actionPerformed(ActionEvent e){}

事件处理

动作事件

动作事件由ActionEvent类定义,最常用的是当单击按钮后将产生动作事件,可以通过实现ActionListener接口处理相应的动作事件。

ActionListener接口定义:

public interface ActionListener extends EventListener {
    public void actionPerformed(ActionEvent e);
}

常用方法:

  1. getSource():用来获得触发此次事件的组件对象,返回值类型为Object;
  2. getActionCommand():用来获得与当前动作相关的命令字符串,返回值类型为String。
焦点事件

焦点事件由FocusEvent类捕获,所有的组件都能产生焦点事件,可以通过实现FocusListener接口处理相应的动作事件。

FocusListener接口定义:

public interface FocusListener extends EventListener {
    public void focusGained(FocusEvent e); 	// 当组件获得焦点时将触发该方法
    public void focusLost(FocusEvent e);	// 当组件失去焦点时将触发该方法
}
鼠标事件

MouseEvent类捕获,可以通过实现MouseListener接口处理相应的鼠标事件。

MouseListener接口定义:

public interface MouseListener extends EventListener {
	// 光标移入组件时被触发
    public void mouseEntered(MouseEvent e);
	// 鼠标按键被按下时触发
    public void mousePressed(MouseEvent e);
	// 鼠标按键被释放时触发
    public void mouseReleased(MouseEvent e);
	// 发生单击事件时被触发
    public void mouseClicked(MouseEvent e);
	// 光标移出组件时被触发
    public void mouseExited(MouseEvent e);}

常用方法:
image-20221111201608126静态常量:
image-20221111201639761

键盘事件

KeyEvent,通过实现KeyListener接口处理相应的键盘事件。

KeyListener接口定义:

public interface KeyListener extends EventListener {
    public void keyTyped(KeyEvent e);
    public void keyPressed(KeyEvent e);
    public void keyReleased(KeyEvent e);
}

常用方法:image-20221111201818065

示例-聊天室

绑定发送事件

  1. 获取文本框中的内容,清空文本框
  2. 对数据去除空格
  3. 实现多次发送的内容追加实现
sendButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String message = messageField.getText().trim();//获取文本框内容,清除空格
                messageField.setText("");

                messageArea.append(message + "\n");
            }
        });

clearButton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        //清空聊天就是把文本域的内容设置为空
        messageArea.setText("");
    }
});

多线程

概念

并发和并行
  • 并发::在同一时刻,有多个指令在单个CPU上交替执行。
  • 并行:在同一时刻,有多个指令在多个CPU上同时执行。
进程和线程
  • 进程:正在运行的软件

    特点

    • 独立性:进程是-一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
    • 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
    • 并发性:任何进程都可以同其他进程一起并发执行
  • 线程:是进程中的单个顺序控制流,是一 条执行路径。

    • 单线程: -个进程如果只有条执行路径.则称为单线程程序
    • 一个进程如果有多条执行路径,则称为多线程程序

实现方式

继承Thread
  1. 定义一个类MyThread继承Thread类
  2. 在MyThread类中重写run()方法(run(是用来封装被线程执行的代码)
  3. 测试类中创建MyThread类的对象
  4. 启动线程(用start()方法,由JVM调用run())
实现Runnable接口
  1. 定义一个类MyRunnable实现Runnable接口
  2. 在MyRunnable类中重写run0方法
  3. 测试类中创建MyRunnable类的对象
  4. 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
Callable和Fulture
  1. 定义一个类MyCallable实现callable接口
  2. 在MyCallable类中重写call()方法(返回值表示线程运行完毕后返回的结果)
  3. 测试类中创建MyCallable类的对象
  4. 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
  5. 创建Thread类的对象,把FutureTask对象作为构造方法的参数
  6. 启动线程
  7. 得到线程返回的结果:用线程对象的get()方法

三种方式对比image-20230724105608032

Thread方法

创建和操作线程的主要类之一

设置获取名字
  • 获取:String getName():返回此线程的名称
  • Thread类中设置线程的名字
    • void setName(String name) :将此线程的名称更改为等于参数name
    • 构造方法也可以设置线程名称(需要在Mythread类中添加无参构造方法和带有String name的构造方法)
获得当前线程的对象
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用
线程休眠

public static void sleep(long time) :让线程休眠指定的时间,单位为毫秒。

在runnable接口中使用需要try catch异常(因为它父类的方法没有throw异常)

线程调度

多线程的并发运行:
计算机中的CPU ,在任意时刻只能执行条机器指令。每个线程只有获得CPU的使用权才能执行代码。
各个线程轮流获得CPU的使用权,分别执行各自的任务。

调度模型
  • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
  • 抢占式调度模型(java使用的模型):优先让优先级高的线程使用CPU ,如果线程的优先级相同,那么会隨机选择个,优先级高的线程
    获取的CPU时间片相对多- -些(几率多一些)(执行每一条代码都需要抢夺线程)
设置优先级

使用callable 和fulture

image-20230725152133078

image-20230725152247003

  • 查看优先级,thread.getPriority()
  • 设置优先级,thread.setPriority()
守护线程

守护线程的存在并不会阻止Java虚拟机(JVM)的退出,即使它可能还在运行。
与之相对的是用户线程,它的存在会阻止JVM的退出。只有所有的用户线程都执行完毕或手动被终止,JVM才会正常退出。

当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了,

  • 设置为守护线程:public final void setDaemon(boolean on)

线程安全

同步代码块
  • 锁多条语句操作共享数据,可以使用同步代码块实现,默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭

  • 多个线程必须使用同一把锁
    private static object obj = new 0bject();然后synchronized(obj),不要用synchronized(this),否则每调用一次就会创建新的锁

  • 同步方法与同步代码块:

    package com.itheima.threaddemo011;
    
    public class MyRunnable implements Runnable {
        private static int ticketCount = 100;
    
        @Override
        public void run() {
            while(true){
                if("窗口一".equals(Thread.currentThread().getName())){
                    //同步方法
                    boolean result = synchronizedMthod();
                    if(result){
                        break;
                    }
                }
    
                if("窗口二".equals(Thread.currentThread().getName())){
                    //同步代码块
                    synchronized (MyRunnable.class){//使用了 synchronized 关键字,传入了 MyRunnable.class 对象作为锁,确保同一时刻只有一个线程可以进入这个代码块
                        if(ticketCount == 0){
                           break;
                        }else{
                            try {
                                Thread.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            ticketCount--;
                            System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
                        }
                    }
                }
    
            }
        }
    
        private static synchronized boolean synchronizedMthod() {//synchronized表示同一时刻只有一个线程可以访问这个方法,锁对象是该方法所在的Class对象即this即MyRunnable.class
            if(ticketCount == 0){
                return true;
            }else{
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticketCount--;
                System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
                return false;
            }
        }
    }
    

    同步方法与同步代码块都可以达到效果,从而防止多个线程同时进入该代码块。同步方法封装了逻辑,更优。主程序只能创建一个MyRunnable
    对象都是MyRunnable.class,表明实际上使用的是同一个类的静态同步方法锁对象

    MyRunnable mr = new MyRunnable();
    
            Thread t1 = new Thread(mr);
            Thread t2 = new Thread(mr);
    
  • 效率低

synchronized(任意对象){多条语句操作共享数据的代码}

Lock锁
  • 获得锁和释放锁:
    • void lock() :获得锁
    • void unlock() :释放锁,一般放到finally模块中保证该方法一定会被执行
  • 实例化:Lock是接口不能直接实例化,这里采用它的实现类Reentrantl ock来实例化

使用模板:

  1. 定义锁:private ReentranLock lock = new ReentranLock() (可重入(线程可以多次获取同一个锁)的互斥锁)。
  2. 首先try catch(快捷键ctrl + alt + t)在需要加锁的地方lock.lock()
  3. 在需要解锁的地方用finally代码块设置unlock方法
try {
                lock.lock();
                if (ticket <= 0) {
                    //卖完了
                    break;
                } else {
                    Thread.sleep(100);
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
死锁

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

解决方法:尽量不要写锁的嵌套

生产者消费者

代码实现:

  • 方法:
    • P操作(等待操作或减操作):
      • void wait():导致当前线程等待,直到另一个线程调用该对象的notify0方法或notifyAll0方法
    • V操作(发信号操作或增操作):
      • void notify():唤醒正在等待对象监视器的单个线程
      • void notifyAll():唤醒正在等待对象监视器的所有线程
  • 类:
    • 生产者线程
    • 消费者线程
    • 缓冲区(队列)

步骤:

  1. 创建测试类,以及生产者、消费者、缓冲区的对象类
  2. 在缓冲区中,设定信号量public static boolean flag = false;
  3. 在缓冲区中,定义锁对象public static final Object lock = new Object();
    用final关键字保证是同一把锁
  4. 在生产者和消费者线程中分别给缓冲区加锁synchronized(Desk.lock)
    等待和唤醒方法都要通过锁对象调用Desk.lock.wait();
    注意要同步信号量的变化
  5. 在测试类中创建线程对象并开启

保证生产者和消费者用的是同一把锁

MySQL与JDBC

约束

➢NOT NULL:非空约束,指定某列不能为空。
➢UNIQUE:唯一-约束,指定某列或者几列组合不能重复。
➢PRIMARYKEY:主键,指定该列的值可以唯一地标识该条记录。
➢FOREIGN KEY:外键,指定该行记录从属于主表中的一条记录, 主要用于保证参照完整性。
➢CHECK: 检查,指定一个布尔表达式,用于指定对应列的值必须满足该表达式。

视图:建立视图名和查询语句的关联image-20221104222723134

DDL语句

操作数据库对象

  1. 建表

    1. 常见数据库对象image-20221104221143130
    2. 常见列类型
      image-20221104221457116
      image-20221104222118900
  2. 修改
    image-20221104222228483
    修改:motify;增加:add;修改列名:change

  3. 删除:drop;截断(删除表里全部数据):truncate

DML语句

操作数据表里的数据

  1. 插入新数据insert into
    image-20221104223450061
  2. 修改已有数据update
  3. 删除不需要的数据delete from

JDBC常用接口

  1. DriverManager类:用来建立数据库连接,获取Connection对象
  2. Connection接口:代表数据库连接对象。
    常用方法:image-20221113195337521
  3. Statement:用于执行SQL语句的工具接口
    常用方法:image-20221113195524603
  4. PreparedStatement:预编译的Statement对象
    PreparedStatement执行SQL语句时,无须再传入SQL语句,只要为预编译的SQL语句传入参
    数值即可。所以它比Statement多了如下方法。
    常用方法:
    image-20221113195940113
  5. CallableStatement接口:继承并扩展了PreparedStatement接口,用来执行SQL的存储过程。
  6. ResultSet接口: 结果集对象。该对象包含访问查询结果的方法,ResultSet 可以通过列索引或列名获
    得列数据。
    常用方法:image-20221113200317727

JDBC编程步骤

image-20221113200400669

环境准备
  1. 安装MySQL
  2. 用Navicat连接数据库test
  3. 在test上新建数据库data
    image-20221113203552707
  4. 导入驱动包:
    1. 下载地址
      image-20221113204023862
    2. 在项目的src目录下新建名为lib的包,将下载解压好的文件拷贝过来
    3. 在project structure中添加依赖
      1. image-20221113204935021
      2. image-20221113205100388
      3. 找到该jar包:image-20221113205211759
      4. 点击Apply
连接数据库
  1. 加载数据库驱动
    Class.forName(driverClass)

    • 加载MySQL的驱动
      Class. forName (" com.mysql.jdbc.Driver") ;
    • 加载Oracle的驱动
      Class. forName ("oracle. jdbc .driver .OracleDriver") ;
  2. 通过DriverManager获取数据库连接
    DriverManager . getConnection(String url,String user, String pass) ;

    url:

    • 参数:数据库URL、登录数据库的用户名和密码
    • 写法:jdbc: subprotocol :other stuff
      • MySQL:jdbc:mysq1://hostname:port/databasename
      • Oracle:jdbc:oracle:thin:@hostname:port :databasename
  3. 通过Connection对象创建Statement对象。

    • createStatement():创建基本的Statement对象。
    • prepareStatement(String sql):根据传入的SQL语句创建预编译的Statement对象
    • prepareCall(String sq):根据传入的SQL语句创建CallableStatement对象。
  4. 使用Statement执行SQL语句

    • execute():可以执行任何SQL语句,但比较麻烦。
    • executeUpdate();主要用于执行DML和DDL语句。执行DML语句返回受SQL语句影响的行数,执行DDL语句返回0。
    • executeQuery()):只能执行查询语句,执行后返回代表查询结果的ResultSet 对象。
  5. 操作结果集
    操作ResultSet对象取出查询结果:

    • next)、previous)、 first()、 last()、 beforeFirst()、 afterLast()、 absolute(等 移动记录指针的方法。
    • getXxx(方法获取记录指针指向行、特定列的值。该方法既可使用列索引作为参数,也可使用列名作为参数。使用列索引作为参数性能更好,使用列名作为参数可读性更好。
      image-20221113202115074
  6. 回收数据库资源,包括关闭ResultSet、Statement 和Connection等资源。1

示例代码:

package demo1;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class example {
    //定义MySQL的数据库驱动程序
    public static final String DBDRIVER = "com.mysql.cj.jdbc.Driver";
    //定义MySQL数据库的连接地址
    public static final String DBURL = "jdbc:mysql://localhost:3306/data";
    //MySQL数据库的连接用户名
    public static final String DBUSER = "root";
    //密码
    public static final String DBPASS = "Angel22";

    public static void main(String[] args) {
        Connection conn = null;//数据库连接
        try {
            Class.forName(DBDRIVER);//加载驱动程序
            conn = DriverManager.getConnection(DBURL, DBUSER, DBPASS);//获取数据库连接对象
            System.out.println(conn);
            conn.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("加载失败");
        }
    }
}

插入操作

public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Connection conn = null;//数据库连接
        Statement stmt = null;//数据库操作
        String sql = "INSERT INTO user(name,password,age,sex,birthday)" +
                "VALUES ('张三','www.aaa.cn',30,'男','2008-08-27')";
            Class.forName(DBDRIVER);//加载驱动程序
            conn = DriverManager.getConnection(DBURL, DBUSER, DBPASS);//获取数据库连接对象
            stmt = conn.createStatement();//创建并返回一个Statement实例
            stmt.executeUpdate(sql);//执行静态语句
            stmt.close();
            conn.close();
    }

SQL语句执行方法拓展

  1. execute():可执行任何语句
    获取结果集:

    image-20221113213246443image-20221113213259606

  2. PreparedStatement()

image-20221113213418480

管理结果集

xml

概述

是可扩展的标记语言

作用:

  • 用于进行存储数据和传输数据
  • 作为软件的配置文件

规则

根标签

< ?xml version= "1.0" encoding= "UTF-8" standalone= "yes" ?>

  • version :该属性是必须存在的
  • encoding :该属性不是必须的
    打开当前xml文件的时候应该是使用什么字符编码表
    ( -般取值都是UTF-8 )
  • standalone:该属性不是必须的,描述XML文件是否依赖其他的xml文件,取值为yes/no
特殊字符

为了避免歧义,用实体引用代替

特殊字符含义
&lt;<
&gt;>
&amp;&
&apos
&quot"

或者使用CDATA<description><![CDATA[sth]]></description>

解析xml

DOM ( DocumentObject Model )文档对象模型:就是把文档的各个组成部分看做成对应的对象。
会把xml文件全部加载到内存。在内存中形成一个树形结构,再获取对应的值

  • Document对象:整个xml文档
  • Node对象:
    • Element对象:所有标签
    • Attribute对象:所有属性
    • Text对象:所有文本内容
用DOM4J解析xml文件
  1. 下载:下载地址

  2. 解压,将dom4i-1 61 iar文件粘贴到lib再右键选择Add ad Library

  3. //1.获取一个解析器对象
            SAXReader saxReader = new SAXReader();
            //2.利用解析器把xml文件加载到内存中,并返回一个文档对象
            Document document = saxReader.read(new File("myxml\\xml\\student.xml"));
            //3.获取到根标签
            Element rootElement = document.getRootElement();
            //4.通过根标签来获取student标签
            //elements():可以获取调用者所有的子标签.会把这些子标签放到一个集合中返回.
            //elements("标签名"):可以获取调用者所有的指定的子标签,会把这些子标签放到一个集合中并返回
            //List list = rootElement.elements();
            List<Element> studentElements = rootElement.elements("student");
    
    //用来装学生对象
            ArrayList<Student> list = new ArrayList<>();
    
    
            //5.遍历集合,得到每一个student标签
            for (Element element : studentElements) {
                    //element依次表示每一个student标签
    
                //获取id这个属性
                Attribute attribute = element.attribute("id");
                //获取id的属性值
                String id = attribute.getValue();
    
    
                //获取name标签
                //element("标签名"):获取调用者指定的子标签
                Element nameElement = element.element("name");
                //获取这个标签的标签体内容
                String name = nameElement.getText();
    
    
                //获取age标签
                Element ageElement = element.element("age");
                //获取age标签的标签体内容
                String age = ageElement.getText();
    
    //            System.out.println(id);
    //            System.out.println(name);
    //            System.out.println(age);
    
                Student s = new Student(id,name,Integer.parseInt(age));
                list.add(s);
            }
            //遍历操作
            for (Student student : list) {
                System.out.println(student);
            }
    
    

文件约束

用来限定xmI文件中可使用的标签以及属性

DTD约束
引入
本地

< !DOCTYPE 文档根元素名称 SYSTEM' 路径' >

xml文件内部

< DOCTYPE 文档根元素名称[ 直接写内容 ]>

网络dtd

<!DOCTYPE 文档根元素名称 PUBLIC "dtd文件的名称" "dtd文档的URL">

编写

image-20230729210115254

定义一个元素的格式为: <!EL EMENT元素名 元素类型>

  • 简单元素的元素类型:

    • EMPTY:表示标签体为空
    • ANY:表示标签体可以为空也可以不为空
    • PCDATA:表示该元素的内容部分为字符串
  • 复杂元素的元素类型:
    直接写子元素名称。

    多个子元素可以使用",“或者”|"隔开;

    • ","表示定义子元素的顺序;
    • “|”:表示子元素只能出现任意一个

    表示某个子元素出现次数的语法

    • "?"零次或一次
    • '+"一次或多次,
    • ”*"零次或多次;
    • 如果不写则表示出现一次
定义属性

定义一一个属性的格式为: <!ATTLIST 元素名称 属性名称 属性的类型 属性的约束>

例如<!ATTLIST person id CDATA #FIXED "p1" >

  1. 元素名称:表示要定义属性的 XML 元素的名称,即在尖括号中定义的元素名。
  2. 属性名称:表示要为该元素定义的属性的名称。属性是元素的额外信息,由键值对(key-value pair)组成,用于提供关于元素的更多信息。
  3. 属性的类型:表示属性的数据类型。属性的类型可以是以下之一:
    • CDATA:表示属性的值可以包含任何字符数据,没有特定的格式限制。
    • ENUMERATION:表示属性的值必须是预定义的值之一,多个值使用竖线 | 分隔。
    • ID:表示属性的值必须是文档中唯一的标识符,不能重复。
    • IDREF:表示属性的值必须是文档中另一个元素的标识符,用于建立元素之间的关联。
    • 其他已定义的数据类型:DTD 允许自定义属性的数据类型。
  4. 属性的约束:表示属性的约束规则。属性的约束可以是以下之一:
    • #REQUIRED:表示该属性在元素中是必需的,必须提供一个值。
    • #IMPLIED:表示该属性在元素中是可选的,可以省略。
    • #FIXED 值:表示该属性的值是固定的,不能更改,必须使用指定的值。
    • 默认值:可以直接指定一个默认值,当元素中没有提供该属性时,将使用默认值。
schema约束
  • 是一个xml文件,后缀名.xsd,约束xml的同时也被其他文件约束
  • 一个xmI中可以引用多个schema约束文件,多个schema使用名称空间区分(名称空间类似于java包名)
  • dtd里面元素类型的取值比较单一常见的是PCDATA类型 ,但是在schema里面可以支持很多个数据类型
编写

image-20230729213040203

<?xml version="1.0" encoding="UTF-8" ?>
<schema
    xmlns="http://www.w3.org/2001/XMLSchema"
    targetNamespace="http://www.itheima.cn/javase"
    elementFormDefault="qualified"
>

    <!--定义persons复杂元素-->
    <element name="persons">
        <complexType>
            <sequence>
                <!--定义person复杂元素-->
                <element name = "person">
                    <complexType>
                        <sequence>

                            <!--定义name和age简单元素-->
                            <element name = "name" type = "string"></element>
                            <element name = "age" type = "string"></element>

                        </sequence>

                        <attribute name="id" type="string" use="required"></attribute>
                    </complexType>


                </element>
            </sequence>
        </complexType>


    </element>



</schema>
引入

image-20230729213246404

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.itheima.cn/javase"
    xsi:schemaLocation="http://www.itheima.cn/javase person.xsd"
定义标签属性

image-20230729213503931

定义的位置在sequence外面,conplexType里面

网络编程

原理分析

过程 :

  1. 运行服务器
  2. Socket accept ss.accept();等待客户端连接
    accept是阻塞方法
  3. 连接建立:客户端创建Socket对象,通过三次握手协议。
    read方法也是阻塞的,由关流时写的结束标记控制
  4. 断开连接:,通过四次挥手协议保证连接终止
三次握手

image-20230801190015836

通过三次,双方都知道了对方的接收和发送功能没有异常

四次挥手

image-20230801190219513

服务器需要先保存数据再通知对方,再断连

代码实现

按照数据的流向写

客户端
package com.itheima.socketdemo7;

import java.io.*;
import java.net.Socket;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //1.创建客户端对象
        Socket socket = new Socket("127.0.0.1",10000);

        //2.创建输出流和输入流
        OutputStream os = socket.getOutputStream();
        os.write("hello".getBytes());
       // os.close();如果在这里关流,会导致整个socket都无法使用,因为OutputStream的close()方法会调用关联的Socket的close()方法
        socket.shutdownOutput();//仅仅关闭输出流.并写一个结束标记,对socket没有任何影响


        //中文不能用字节流,要用转换流转换为字符流
        /*InputStream is = socket.getInputStream();
        int b;
        while((b = is.read()) !=-1){
            System.out.println((char) b);
        }*/
        
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));//接收,转换,接收
        String line;
        while((line = br.readLine())!=null){
            System.out.println(line);
        }
        br.close();
        os.close();
        socket.close();
    }
}
服务器端
package com.itheima.socketdemo7;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //1.创建服务器端对象
        ServerSocket ss = new ServerSocket(10000);

        //2.等待客户端连接
        Socket accept = ss.accept();

        //3.创建输入流和输入流
        InputStream is = accept.getInputStream();
        int b;
        while((b = is.read())!=-1){
            System.out.println((char) b);
        }

        System.out.println("看看我执行了吗?");
       /* OutputStream os = accept.getOutputStream();
        os.write("你谁啊?".getBytes());*/
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bw.write("你谁啊?");
        bw.newLine();
        bw.flush();

        bw.close();
        is.close();
        accept.close();
        ss.close();
    }
}
改进-实现多次上传

即加一个循环

TCP

TCP通信协议是一种可靠的网络协议,它在通信的两端名建立一个Socket对象。

客户端
package com.itheima.socketdemo6;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        //1,创建一个Socket对象
        Socket socket = new Socket("127.0.0.1",10000);

        //2.获取一个IO流开始写数据
        OutputStream os = socket.getOutputStream();
        os.write("hello".getBytes());

        /*while(true){

        }*/
        //3.释放资源
        os.close();
        socket.close();
    }
}
服务器端
package com.itheima.socketdemo6;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //1. 创建Socket对象
        ServerSocket ss = new ServerSocket(10000);
        //2. 等待客户端连接
        System.out.println(111);
        Socket accept = ss.accept();
        System.out.println(222);
        //3.获得输入流对象
        InputStream is = accept.getInputStream();
        int b;
        while((b = is.read()) != -1){
            System.out.print((char) b);
        }

        System.out.println("看看我执行了吗?");

        //4.释放资源
        is.close();
        ss.close();
    }
}
优化
  • UUID随机命名:BufferedInputStream bis = new BufferedInputStream(new FileInputStream("socketmodule\\ClientDir\\" + UUID.randomUUID().tostring() + "jpg"));

  • 线程池:

  • 解决使用while无法处理多个客户端的请求

  • 提高系统利用效率

    public static void main(String[] args) throws IOException{
        ServerSocket ss = new ServerSocket(10000);
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
        	3,//核心线程数量
            10, //线程池的总数连你那个
            60, //临时线程空闲时间
            TimeUnit.SECONDS, //临时线程空闲时间的单位
            new ArrayBlocking Quewue<>(5), //阻塞队列
            Executors.defaultThreadFactory();//创建线程的方式,使用默认线程工厂
            new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
        );
        while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
            pool.submit(ts);
        }
    }
    

日志

logback

  1. 导入Logback的相关jar包

  2. 编写Logback配置文件

  3. 在代码中获取日志的对象

    private static final Logger *LOGGER* = LoggerFactory.*getLogger*(LogDemo.class);

  4. 按照级别设置记录日志信息

    public static void main(String[] args) {
            //打日志 --- 类似于写输出语句
    
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入您的姓名");
            LOGGER.debug("用户开始输入信息了");
            String name = sc.nextLine();
            //System.out.println(name);
            LOGGER.info("用户输出录入姓名为:" + name);
            System.out.println("请输入您的年龄");
            String age = sc.nextLine();
            try {
                int ageInt = Integer.parseInt(age);
                LOGGER.info("用户输入的年龄格式正确" + age);
            } catch (NumberFormatException e) {
                LOGGER.info("用户输入的年龄格式错误" + age);
            }
        }
    

配置文件

设置输出级别
  • 级别程度依次是: TRACE< DEBUG< INFO<WARN <ERROR < FATAL
    LOGGER. info(" info级别的日志信息");

    • TRACE(追踪):最低级别,用于输出非常详细的日志信息,通常用于追踪程序的执行过程。
    • DEBUG(调试):用于输出调试信息
    • INFO(信息):用于输出程序的正常运行状态和重要的事件信息。
    • WARN(警告):用于输出可能会引起注意的非错误信息。
    • ERROR(错误):用于输出错误信息。
    • FATAL(致命):最高级别,用于输出严重的错误信息
  • 默认级别是debug (忽略大小写)

  • 作用:将开发中不同的日志信息进行分类,只输出大于等于该级别的日志信息。

  • ALL和OFF分别是打开全部日志信息,及关闭全部日志信息。

    <root level="DEBUG"><!-设置级别--->
            <appender-ref ref="CONSOLE"/><!--保存在控制台-->
            <appender-ref ref="FILE" /><!--保存在文件-->
        </root>
    
控制台日志设置
<!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err-->
        <target>System.out</target><!--表示以输出语句的形式设置-->
        <encoder>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
                %msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level]  %c [%thread] : %msg%n</pattern>
        </encoder>
    </appender>
文件日志设置
<!-- File是输出的方向通向文件的 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--日志输出路径-->
        <file>C:/code/itheima-data.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>C:/code/itheima-data2-%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
    </appender>

ring[] args) throws IOException {
//1. 创建Socket对象
ServerSocket ss = new ServerSocket(10000);
//2. 等待客户端连接
System.out.println(111);
Socket accept = ss.accept();
System.out.println(222);
//3.获得输入流对象
InputStream is = accept.getInputStream();
int b;
while((b = is.read()) != -1){
System.out.print((char) b);
}

    System.out.println("看看我执行了吗?");

    //4.释放资源
    is.close();
    ss.close();
}

}


#### 优化

* UUID随机命名:`BufferedInputStream bis = new BufferedInputStream(new FileInputStream("socketmodule\\ClientDir\\" + UUID.randomUUID().tostring() + "jpg"));`

*   线程池:

  * 解决使用while无法处理多个客户端的请求

  * 提高系统利用效率   
    ```java
    public static void main(String[] args) throws IOException{
        ServerSocket ss = new ServerSocket(10000);
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
        	3,//核心线程数量
            10, //线程池的总数连你那个
            60, //临时线程空闲时间
            TimeUnit.SECONDS, //临时线程空闲时间的单位
            new ArrayBlocking Quewue<>(5), //阻塞队列
            Executors.defaultThreadFactory();//创建线程的方式,使用默认线程工厂
            new ThreadPoolExecutor.AbortPolicy()//任务拒绝策略
        );
        while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
            pool.submit(ts);
        }
    }
    ```

    

## 日志 

### logback

1. 导入Logback的相关jar包

2. 编写Logback配置文件

3. 在代码中获取日志的对象  

   `private static final Logger *LOGGER* = LoggerFactory.*getLogger*(LogDemo.class);`

4. 按照级别设置记录日志信息  
   ```java
   public static void main(String[] args) {
           //打日志 --- 类似于写输出语句
   
           Scanner sc = new Scanner(System.in);
           System.out.println("请输入您的姓名");
           LOGGER.debug("用户开始输入信息了");
           String name = sc.nextLine();
           //System.out.println(name);
           LOGGER.info("用户输出录入姓名为:" + name);
           System.out.println("请输入您的年龄");
           String age = sc.nextLine();
           try {
               int ageInt = Integer.parseInt(age);
               LOGGER.info("用户输入的年龄格式正确" + age);
           } catch (NumberFormatException e) {
               LOGGER.info("用户输入的年龄格式错误" + age);
           }
       }

配置文件

设置输出级别
  • 级别程度依次是: TRACE< DEBUG< INFO<WARN <ERROR < FATAL
    LOGGER. info(" info级别的日志信息");

    • TRACE(追踪):最低级别,用于输出非常详细的日志信息,通常用于追踪程序的执行过程。
    • DEBUG(调试):用于输出调试信息
    • INFO(信息):用于输出程序的正常运行状态和重要的事件信息。
    • WARN(警告):用于输出可能会引起注意的非错误信息。
    • ERROR(错误):用于输出错误信息。
    • FATAL(致命):最高级别,用于输出严重的错误信息
  • 默认级别是debug (忽略大小写)

  • 作用:将开发中不同的日志信息进行分类,只输出大于等于该级别的日志信息。

  • ALL和OFF分别是打开全部日志信息,及关闭全部日志信息。

    <root level="DEBUG"><!-设置级别--->
            <appender-ref ref="CONSOLE"/><!--保存在控制台-->
            <appender-ref ref="FILE" /><!--保存在文件-->
        </root>
    
控制台日志设置
<!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err-->
        <target>System.out</target><!--表示以输出语句的形式设置-->
        <encoder>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
                %msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level]  %c [%thread] : %msg%n</pattern>
        </encoder>
    </appender>
文件日志设置
<!-- File是输出的方向通向文件的 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--日志输出路径-->
        <file>C:/code/itheima-data.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>C:/code/itheima-data2-%d{yyyy-MMdd}.log%i.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
    </appender>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值