Java学习笔记(个人向)


1. 概述

  • 每一个应用程序都以类名开头类名必须与文件名匹配,所以保存文件时,要使用类名保存;
  • java中运行的每一行代码都必须在一个class中;
  • 在类名的开头,可以用权限修饰符来解释,如private或者public
  • main()方法是必需的,每一个Java程序都有
  • main()方法中的任何代码都会被执行
  • 对于xxx类中的main()方法中的方法(函数),相当于嵌套调用;
  • 可以使用println()方法将一行文本打印到屏幕上,如:

    public static void main(String[] args) {
        System.out.println("Hello World");
    }  
    
  • 同样,与c++一致,大括号表示代码块的开始和结束;
  • 每个代码语句必须用分号结尾

2. 注释

  • 单行注释以//开头,java将默认忽略//和行尾之间的任何文本;
  • 多行注释以/*开头,*/结尾,同样两者之间的文本会被Java忽略;

3. 变量类型

  • 变量是存储数据的容器;
  • Java的变量类型有:
    1. string:文本,字符串;
    2. int:有符号数;
    3. float:浮点数;
    4. char:存储单个字符,通过"……"的形式接收,当然'……'也行;
    5. boolean:存储布尔值,有truefalse两种;
  • 建立变量:
    • 指定类型 (+ 赋值);
      type variable (= value);
    • 一般情况下,新的赋值会覆盖掉旧的赋值,如果要避免这种情况,可以使用final关键字声明在变量定义前面,如:
      final int num=1; 
      
    这样会将变量设置为只读不可写;
  • 我们可以使用System.out.println()的方式用于显示变量;
  • 如果要一次性声明多个同样类型的变量,可以用,隔开,如int i1=1,i2=2;
  • java的变量名不应包含空格;
  • 理论上可以使用$_开头;
  • java的名称区分大小写
  • system默认的关键字不要用做变量名,不然会覆盖掉原有设置好的;

4. 数据类型

  1. 基本数据类型

    byte,short之类的;

  2. 非基本数据类型

    String,ArraysClasses

  • 基本八种:
    数据类型大小描述
    byte1B有符号二进制数
    short2B范围小有符号数
    int4B范围适中的有符号数
    long8B范围较大的有符号数
    float4B范围适中的浮点数
    double8B范围较大的浮点数
    boolean1b,即一位存储truefalse
    char2B存储单个字符
  • 对对应数据类型赋值时,应当在数值末尾加上对应的类型符,如long---L,float---f,double---d;
  • boolean型的取值只能是true或者false;
  • 浮点数用e表示10的幂,如12e9=12*10^9;
  • 字符的要求,赋值时推荐:字符用单引号,字符串用双引号

    注意,字符串的string要写成大写的String,而且它不是原始数据类型,因为使用它的时候需要引用到一个对象;

  • 创建的原始类型始终会有数值,而非原始类型则可以以null填充;
  • 原始类型的大小取决于数据类型,而非原属类型的大小都是相同的相对固定);

5. 数据类型转换

  1. 隐式类型转换
    • 自动完成,思路是:通过以零补位的形式实现
    • byte-->short-->char-->int-->long-->float-->double;
  2. 强制类型转换
    • 手动完成,需要强制转换符(……)
      如:int myInt = (int)myDouble;
    • 顺序与上边相反的,将较小的类型转换为较小的类型,需要使用强制转换

6. 运算符

  • 用于对变量和值执行操作;
  • 包括:
    1. 算术运算符;
    2. 赋值运算符;
    3. 关系运算符;
    4. 逻辑运算符
    5. 位运算符
  • %取模;
  • ++自加1,--自减1;
  • +=自加,-=自减,/=自除,|=位或运算,^=异或运算,&=位并运算,>>=无符号部分右移得到移码无符号部分左移得到移码
  • ==等于,!=不等于;
  • &&逻辑与、||逻辑或、!逻辑非;
  • &位交,|位并,~位取反,^位异或,<<左移码,>>右移码,>>>右移码并补零;

7. 字符串

  • 用字符串结构String来存储文本,用双引号包围的字符来传值;
  • 关于字符串有以下几种方法:
    1. 字符串长度:.length()返回;
    2. 大小写转换:toUpperCase()将字符串转变成大写,toLowerCase()将字符串转变成小写;
    3. 查找字符串第一次出现的位置:indexOf()返回第一次出现的位置学了数据结构的都知道从零开始);
    4. 串联:用+可以连接前后两个字符串、又或者使用<前者>.concat(<后者>)的方法;
  • 特殊字符必须写在引号内,否则Java会误解此字符串的性质,并生成错误。
  • 为了避免’,",\被系统误以为有实际功能,可以使用反斜杠\将特殊字符转换为字符串
  • 除此以外,还有一些特殊的转义字符:
    1. \n:换行;
    2. \r:回车;
    3. \t:制表;
    4. \b:空格;
    5. \f:换页;

注意:如果是一个数字和一个字符串执行+操作,则数字部分会被强制转化为字符串进行运算。

8. 数学方法(内置)

Math类的方法

  1. max(x,y):用于查找xy的最大值;
  2. min(x,y):用于查找xy的最小值;
  3. sqrt(x):用于返回x的平方根;
  4. abs(x):用于返回x的绝对值;
  5. random():返回一个介于[0.0,1.0)的随机数,至于如果你要0到10^k之间的随机数,可以用上述随机数乘以10^k+1来获得;

所有的数学方法都是static静态的;

9. 条件语句

  • 使用方法基本和C++差不多;
  • 以下介绍三元运算符
    variable=(condition)? expressionTrue:expressionFalse;
    
    其中前者的表达式是条件为真使用的,后边是条件为假使用的。

10. 选择语句

  • switch……case型;
  • 格式为:
    switch(expression){
        case x:  
            代码块1(break)  
        case y:  
            代码块2(break)
        ····
        (default:  
            代码块n;)
    }
    
  • 意思为:
    1. switch先计算一次;
    2. case将计算的结果和每个case后的情况进行条件比较。如果符合的话执行后边相关的代码块;
    3. breakdefault是可有可无的;
  • 中断关键字
    1. break:停止在块内执行,并跳出所在的程序块;
    2. default设置默认值兜底;
  • 关键点:
    1. expression必须是整数、字符、字符串、枚举或者是它们的表达式
    2. break用于跳出switch语句,没有它会导致继续执行下一个case代码块。所以通常在每一个case语句的末尾添加break
    3. default少用,建议在处理不期望的输入时使用它。

11. 循环语句

  • 基本和c++的一样;
  •   for(代码块第一次执行前执行一次;判断代码块是否执行的条件;代码块执行后执行){  
          代码块
      }
    
  • 除此以外,还有for-each循环,它专门用于循环在数组Array中的元素
    X型数组A=(A1,A2,...);  
    for (X型值i : A){  
        代码块
    }
    
    典型的有:
    String[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
    for (String i : cars) {
        System.out.println(i);
    }
    
  • break跳出循环,continue跳出本轮循环。他们俩常配置着循环语句使用。

12. 数组

  • 用方括号定义变量类型并声明数组:
    数组类型[] 数组名;
    
  • 数组赋的值框在大括号{···}里。
  • 与C++一样,访问元素用索引,索引从0开始;
  • 更改数组元素,可以用索引赋值;
  • 数组有方法.length可以返回数组有多少个元素;
  • for循环常用于遍历数组,不过for-each可读性更强,更推荐使用;
  • 可以通过将数组当成一个元素嵌套到新的数组的方式实现多维数组,如{{1,2},{3,4}}且索引方式也是一级一级打开; 对于多维数组的遍历,多个for循环是不错的方法;

13. 方法(函数)

  • 由于java所有的文件都是类文件(有大有小),所以方法必须在类里边申明
  • 方法使用名称来定义的,后面跟上{代码块};
  • 方法的调用通过方法名配合必要的参数;
  • 一个方法可以被多次调用;
  • 可以在方法名后增加()的方式来传递参数,申明时也要标准形参的数据类型,不同参数之间用,隔开。
  • 当参数被传递给方法后,就从形参转化为实参
    也就是它被实体化了。
  • 在申明方法时,我们会顺便申明返回值的情况。此处可以用返回值的数值类型说明。与C++一样,返回值用return传递。如果是不需要返回值,用void申明;

14. 重载

  • 使用方法重载,可以使多个方法拥有相同的名称和不同参数,便于理清思路和展示联系;
  • 与其定义两个应该做相同事情的方法,不如直接重载一个
  • 这里就体现java的方法都在类里定义的好处了。重载不需要再像C++一样有特殊的operator运算符,直接下边重写就可以了。
  • 只要参数的数量或者类型不同,多个方法就可以具有相同的名称

15. 作用域

  • 在Java中,变量只能够在创建的区域内访问,区域称之为作用域
  • 方法内申明的,申明语句后的语句都可以使用;
  • 代码块内申明的变量,只能够由大括号之间的代码访问,且在申明后方可使用;

16. 递归

  • 对于函数来说,是自己传值给自己,以实现数学上的递归或者说回归,最后企图得到(可能存在的)不动点。
  • 为了防止递归没完没了,显然递归函数都必须要有合理的停止条件,即必须要有能够跳出循环的判断条件;

17. OOP/面向对象编程

  • 同时创建包含的数据和函数的对象,以对象为操作和访问的基本逻辑单位;
  • 有利于多态,封装和模块化,提供了清晰的结构。
  • 体现了局部性原理的思想;
  • 类是对象的模板,对象是类的实例
  • 对象将继承类中的所有变量和方法;

18. 类

  • 在java的语法中,类始终以大写字母开头,类名应该要与文件名相匹配。
  • 与C++一样,类的申明由class关键字开始,类的权限则出现在一开头class的左边。
  • 对象的创建:
    类名 对象名 = new 类名(可能的传参);
    
    其中等号的后半部分是为了申明该对象需要分配的物理存储空间;
  • 一个类可以创建多个对象。
  • 还可以创建一个类的对象并在另一个类中访问它。这有利于更快地组织类(一个类拥有所有的方法和属性(变量),另一个类则含有main()方法;

在命令行中,需要用:

javac 对象名.java//编译文件
java 对象名//执行文件
  • 对于类中的变量,与C++一直,可以使用.取值符来访问,而且可以在新生成的对象中修改它,但只对对象自己有效。这样看来,类中赋值了的属性,字段或者说变量更像是default默认值一般的存在
  • 如果你不想类中的属性值随意的被对象更改掉,可以在申明的时候添加final关键字锁死。

19. 类方法

  • 调用格式:对象名.方法名();
  • 经常可以看到具有static静态和public公共属性和方法的Java程序;
    1. static可以在不创建类的对象的情况下访问该方法,例如Math中的数学计算方法都是static的就是这个原因;
    2. public只能用对象来访问方法
  • 为了使用public的类方法,我们必须先创建对象再调用
  • .用于访问对象的属性和方法
  • 将方法实行和方法建立分散到对应的类和类中的对象中,是很不错的技巧。

20. 构造函数

  • 是一种用于初始化对象的特殊方法
  • 在创建类对象的时候会调用构造函数
  • 它常用于设置对象属性的初始值,等于说原来类的属性是没赋值的,现在创建对象的时候再赋值;
  • 【注意】:构造函数的名称必须与类名相匹配,且不能有返回值,最好直接是void;
  • 但理论上,所有类在创建对象的时候都是有构建函数,只是类中赋值的对象无法设置对象属性的初始值;
  • 典型例子:
    public class MyClass {
        int x;
    
        public MyClass(int y) {
            x = y;
    }
    
        public static void main(String[] args) {
        MyClass myObj = new MyClass(5);
        System.out.println(myObj.x);
        }
    }
    
    // 输出 5
    

21. 修饰符

  • 用于设置类、属性、方法和构造函数的访问级别
  • 一般分为两类:
    1. 访问修饰符控制访问级别
    2. 非访问修饰符:不控制访问级别,提供其他功能
  • 对于class常用的修饰符有:
    1. 访问修饰符
      1. public:任何类都可以访问;
      2. default:只能由同一包中的类访问(一般出现在不指定修改器);
    2. 非访问修饰符
      1. final:该类不能被其他类继承(和用在属性上一样,表示不改了);
      2. abstract:该类被抽象了出来,自身不能创建对象,只能通过继承的方式创建
  • 对于attribute或者meansconstructor来说:
    1. 访问修饰符
      1. public:所有类都可以访问;
      2. private:只能该类内部访问,即通过类的方法其他类无法直接访问属性便于实现封装、隐藏内部实现细节,提高代码的安全性和可维护性);
      3. default:该类只能由同一包中的类进行访问,在不指定修改器时使用;
      4. protected:代码可以在相同包,和子类中访问;
    2. 非访问修饰符
      1. final;
      2. static属性和方法属于类而不是对象
      3. abstract:只能在抽象类中使用,且只能在方法上使用,即主体部分只能由生成的对象提供。
      4. transient*:序列化包含属性和方法的对象时,将跳过属性和方法
        • 在Java中,序列化(Serialization)是指将对象的状态转换为字节流的过程,从而可以将对象保存到文件、数据库或通过网络传输给其他Java虚拟机(JVM)。反序列化(Deserialization)是将字节流转换回对象的过程。序列化的主要作用是持久化对象状态和对象之间的远程通信。
        • 关键点:
          1. 实现Serializable接口:要使一个Java对象可以序列化,该类必须实现java.io.Serializable接口。
          2. 序列化过程:使用ObjectOutputStream类的writeObject()方法将对象转换为字节流。
          3. 反序列化过程:使用ObjectInputStream类的readObject()方法将字节流转换回对象。
          4. transient关键字:用transient关键字修饰的字段不会被序列化
      5. volatile:属性值不是本地缓存的线程,总是从主存中读取
        • 用于修饰变量,确保在多个线程间对该变量的读写操作的可见性。它的主要作用是保证变量的可见性和防止指令重排序。
          主要用途:
          1. 变量的可见性:
            • 当一个变量被声明为volatile时,Java内存模型保证所有线程都能看到该变量的最新值。当一个线程修改了volatile变量的值,新值会立即被刷新到主内存中,其他线程读取时可以立即获得最新值
          2. 防止指令重排序:
            • volatile变量在读取和写入时都会插入内存屏障(memory barrier),这可以防止编译器和处理器对这些操作进行重排序,从而保证了代码执行的顺序一致性。

22. 封装

  • 步骤:
    1. 将类变量/属性声明为private;
    2. 提供公共getset方法来访问和更新private私有变量的值;
    3. get方法返回变量值,set方法设置值,两者的语法都是以getset开头,后跟变量名,第一个字母大写;
  • 意义:
    1. 类属性可以设置为只读(如果只使用get方法),也可以设置为只写如果只使用set方法);
    2. 提高数据的安全性;
    3. 可以在不影响其他部分的情况下更改代码的一部分;

23. 包(package

  • Java 中的包用于对相关类进行分组。可将其视为文件目录中的文件夹
  • 我们使用包来避免名称冲突,并编写更好的可维护代码。
  • 分为两类:
    1. 内置包(来自Java API的包);
    2. 用户定义的包(创建自己的包);

API

  • Java API 是Java开发环境中包含的一个预编写类库,该库分为包和类,可以免费使用;
  • 完整列表可在Oracles网站上找到;
  • 该库包含用于管理输入、数据库编程等的组件;
  • 要使用库中的类和包,需要使用import关键字
  • 使用
    1. 导入
      1. 导入包中的某个类:import <packagename>.<classname>,而且要使用导入的类需要创建该类的对象;
      2. 导入整个包:import <packagename>.*;
    2. 创建自己的包
      • 使用package关键字
      • 格式:
        package <自定义包名>  
        class <自建类>{……}  
        
      • 将上面的文件另存为后编译,这将强制编译器创建"自定义包名"的

        -d 关键字指定保存类文件的目标位置,空格后可以直接跟地址也可以直接跟.表示在同一目录下(linux)。

      • 要使用创建包中的类,在命令行中使用java <自定义包名>.<自建类>;

24. 继承

  • Java 中可以将属性和方法从一个类继承到另一个类。
  • 继承分为两部分:
    1. 子类 (Subclass) - 子,从另一个类继承的类;
    2. 超类 (Superclass) - 父,被继承的类;
  • 要从类继承,请使用extends关键字

总结:访问权限

  1. private:仅在同一个类可访问;
  2. default(无修饰符):仅在同一个包中可访问;
  3. protected:在同一个包,以及不同包的子类中可以访问;
  4. public:在任何地方都可以访问;
    【注意】:protected成员在子类中可以重写,但在子类外部(即使是子类的实例)不能直接访问这些成员。
  • 子类对超类的继承,可以使用protected进行安全性的保障,但要注意子类的实例无法使用带该关键字的属性、方法
  • 如果不想其他类从该类继承,同样使用final关键字;

25. 多态

  • 在子类继承超类时,除默认的超类中的属性、方法外,在代码块中每个子类可以定义属于自己的属性、方法;这种在属性、方法上的差异被解释成多态
  • 也许不同子类的属性、方法名字也叫一样,但是这本质是功能的不同实现,busuanshi“重写”;
  • 继承和多态的意义:对于代码的可重用性很有用,在创建新类时也可以重用现有类的属性和方法。

26. 内部类/嵌套类

  • 类中类
  • 嵌套类的目的是将属于同一类的类分组,这使代码更具可读性和可维护性;
  • 要访问内部类,请创建外部类的对象,然后创建内部类的对象一层一层实体化);
  • 与常规类不同,常规类的访问关键字只能是public或者default两种,内部类新增加了privateprotected两种。如果你想对类设置一些权限,可以采用内部类的方式,甚至直接private禁止访问。
  • 内部类也可以是static的,也就是静态的,属于类但不属于对象,这意味着可以在不创建外部类的对象的情况下访问它。

    但是这与static静态属性、方法一样,static的内部类无法访问外部类的属性、方法

  • 内部类可以访问外部类的属性和方法

27. 抽象类

  • 数据抽象是隐藏某些细节并仅向用户显示基本信息的过程。
  • 可以通过abstract class抽象类或interfaces接口来实现;
  • abstract关键字是非访问修饰符,用于类和方法:
    1. 抽象类不能直接创建对象,只能继承后创建
    2. 抽象方法只能在抽象类中使用,自身没有主体代码块,主体代码块要继承的子类提供
  • 抽象类中除了抽象方法也存在常规方法;

28. 接口

  • Java 中实现abstraction抽象的另一种方法是使用接口,实际上是高度抽象;

  • 接口,即interface关键字定义的部分,是一个完全抽象“类”,它将相关方法和空实体分组,在接口中所有方法都没有主体代码块

  • 要访问接口方式,需要创建一个由implements关键字引导“继承的类”。一般情况下,接口的方法在implement类中定义

  • Java中的类只能继承一个父类,但可以实现多个接口,这实现了多重继承的效果

  • 为了实现安全性-隐藏某些细节,只给对象(接口)显示重要细节,例如api

  • 一些性质的简要说明:

    1. 接口中的所有方法默认都是抽象的(即使不加abstract关键字);
    2. 和抽象类一样,接口不能用于创建对象
    3. 接口方法没有主体,主体由implement类提供;
    4. 在实现接口时,必须重写其所有方法
    5. 接口不能包含构造函数(因为它不能用于创建对象);
    6. 默认情况下,接口方法是abstract抽象的和public公共的;
    7. I接口属性默认为 public, staticfinal;
    8. 接口可以包含常量,所有的字段默认是public static final
  • 在Java中,接口(interface)是一种引用类型,用于定义一组抽象方法和常量。接口不能包含具体实现,具体的实现由实现接口的类提供。接口在Java中扮演着重要角色,特别是在设计良好的API和实现多重继承时。

28.1 接口的主要特点:

  1. 抽象方法:接口中的所有方法默认都是抽象的(即使不加abstract关键字),在Java 8之后,可以包含默认方法和静态方法。
  2. 多重继承:Java中的类只能继承一个父类,但可以实现多个接口,这实现了多重继承的效果。
  3. 常量:接口可以包含常量,所有的字段默认是public static final的。
  4. 隐式public:接口中的方法默认是public的,不能是privateprotected

28.2 定义和实现接口:

定义接口:
public interface Animal {
    void eat();
    void sleep();
}
实现接口:
public class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }

    @Override
    public void sleep() {
        System.out.println("Dog is sleeping");
    }
}

public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("Cat is eating");
    }

    @Override
    public void sleep() {
        System.out.println("Cat is sleeping");
    }
}

28.4 使用接口的优势:

  1. 多态性:接口可以用于实现多态,一个接口类型的引用可以指向任何实现了该接口的对象。
  2. 解耦:接口定义了一组方法的集合,具体实现由不同的类提供,这使得代码更加灵活和可维护。
  3. 代码重用:通过实现接口,不同类可以共享接口的规范,这有助于代码的重用和一致性。
  4. 分离接口和实现:接口可以定义API规范,而具体实现可以独立开发和变化,这有助于分层设计。

示例:

使用接口实现多态性:

public class AnimalFeeder {
    public void feed(Animal animal) {
        animal.eat();
    }

    public static void main(String[] args) {
        AnimalFeeder feeder = new AnimalFeeder();
        Animal dog = new Dog();
        Animal cat = new Cat();
        
        feeder.feed(dog); // 输出: Dog is eating
        feeder.feed(cat); // 输出: Cat is eating
    }
}

28.5 Java 8及之后的接口特性:

  • 默认方法:可以在接口中定义带有默认实现的方法,使用default关键字。
  • 静态方法:可以在接口中定义静态方法,使用static关键字。
  • 私有方法(Java 9及之后):可以在接口中定义私有方法,用于辅助默认方法或静态方法。
示例:
public interface Animal {
    void eat();
    void sleep();

    default void breathe() {
        System.out.println("Animal is breathing");
    }

    static void description() {
        System.out.println("This is an animal");
    }

    private void privateMethod() {
        // 仅供默认方法或静态方法调用
    }
}

28.6 多个接口的实现

  • implements说明之后用,隔开多个接口名。

28.7 总结

  • 接口在Java编程中提供了一种抽象层次,使得代码更加灵活和易于维护。通过理解和应用接口,可以设计出更具扩展性和可维护性的程序。

29. 枚举

  • enum枚举是一个特殊的"类",它表示一组常量;

  • 常量指不可更改的变量,如final定义下的变量;

  • 要创建enum,请使用enum关键字,并用逗号,分隔常量;

  • 【注意】:定义的常量名应该为大写字母

  • 与数组一样,可以使用.来访问枚举中的常量;

  • 可以在类中创建enum枚举;

  • 枚举通常用于switch语句中作为case的比较值检查相应的值;

  • 枚举类型有一个values()方法,该方法返回所有枚举常量的数组。如果要循环遍历枚举的常量,可以用.values()先将枚举转化成数组,再使用for-each方法对其遍历输出/访问即可;

  • 枚举常量的赋值方式是常量名{常量的值,……},属性的赋值方法是常量名(属性值)

29.1 枚举属性和枚举方法

  • enum枚举可以像class一样具有属性和方法。唯一的区别是枚举常量是public static final
    在Java中,枚举(enum)是一种特殊的类,用于表示一组固定的常量。与普通类不同,枚举的实例是有限且固定的。枚举不仅可以包含常量,还可以包含属性、方法和构造函数,从而使其更加灵活和功能强大。
定义枚举的属性和方法
1. 枚举常量带有属性:

枚举常量可以带有属性,这些属性可以在构造函数中进行初始化。

  • 在Java中,枚举常量带有属性时,通常会将这些属性声明为private final并通过构造函数进行初始化,这提供了枚举属性的不可变性和封装的特点。
public enum Day {
    MONDAY("Start of the work week"),
    TUESDAY("Second day"),
    WEDNESDAY("Midweek"),
    THURSDAY("Almost there"),
    FRIDAY("End of the work week"),
    SATURDAY("Weekend"),
    SUNDAY("Rest day");

    private final String description;

    // 构造函数
    Day(String description) {
        this.description = description;
    }

    // 获取描述的方法
    public String getDescription() {
        return description;
    }
}

在这个示例中,每个枚举常量都带有一个描述属性,并通过构造函数进行初始化。

2. 枚举方法:

枚举还可以包含方法,可以根据需要定义实例方法和静态方法。

public enum Operation {
    ADDITION("+") {
        public double apply(double x, double y) {
            return x + y;
        }
    },
    SUBTRACTION("-") {
        public double apply(double x, double y) {
            return x - y;
        }
    },
    MULTIPLICATION("*") {
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVISION("/") {
        public double apply(double x, double y) {
            return x / y;
        }
    };

    private final String symbol;

    Operation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }

    // 抽象方法,每个枚举实例必须实现此方法
    public abstract double apply(double x, double y);
}

在这个示例中,每个枚举常量都表示一个数学运算,并实现了apply方法。

使用示例:
public class EnumTest {
    public static void main(String[] args) {
        // 使用 Day 枚举
        for (Day day : Day.values()) {
            System.out.println(day + ": " + day.getDescription());
        }

        // 使用 Operation 枚举
        double x = 10.0;
        double y = 5.0;
        for (Operation op : Operation.values()) {
            System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
        }
    }
}

输出:

MONDAY: Start of the work week
TUESDAY: Second day
WEDNESDAY: Midweek
THURSDAY: Almost there
FRIDAY: End of the work week
SATURDAY: Weekend
SUNDAY: Rest day

10.000000 + 5.000000 = 15.000000
10.000000 - 5.000000 = 5.000000
10.000000 * 5.000000 = 50.000000
10.000000 / 5.000000 = 2.000000
关键点总结:
  1. 属性:枚举可以包含属性,并通过构造函数初始化。
  2. 方法:枚举可以包含实例方法和静态方法。
  3. 抽象方法:枚举可以定义抽象方法,每个枚举常量必须实现该方法。
  4. 构造函数:枚举的构造函数总是私有的,可以省略private修饰符。

通过这些特性,Java的枚举类型不仅仅是简单的常量集合,还能实现复杂的行为和属性,使其在代码设计中更加灵活和强大。

30. 用户输入

  • java采用scanner类用于获取用户输入,这个类属于java.util包中;
  • 同样,为了使用scanner类,需要创建该类的对象,从而使用scanner类文档中任何可用的方法;
  • 输入类型与之对应的方法,基本名称就是next<数据类型,记得开头字母要大写>
    1. nextBoolean():从用户处读取boolean布尔值;
    2. nextByte():从用户处读取byte字节值;
    3. nextDouble():从用户处读取double双精度值;
    4. ……

31. 日期和时间

  • 与C++不同,Java没有内置的Date类,但是我们可以导入java.time包来使用Date类和time API

  • java.time包含的时间和日期类有:

    1. LocalDate:表示日期,格式是年–月–日;
    2. LocalTime:表示时间,格式是小时–分钟–秒钟–纳秒;
    3. LocalDateTime:表示日期和时间;
    4. DateTimeFormatter格式化显示日期时间;
  • now()方法可以调出当前的时间数据,不同的类都有其自己的now()方法;

  • 如果你想让时间按照自己喜欢的格式显示,可以用DateTimeFormatter类格式化,并用.offpattern描述自己需要的格式,并接纳;

  • 上述描述,小写的dd代表日期,大写的若干个M代表月份,yyyy象征年份。

32,数组列表

  • java.util包中,包含ArrayList类,它是一个可以调整大小的数组array),或者说C++的向量顺序表
  • ArrayLisArray最大的区别就是可以改变大小,可以随意增加或者删除元素;

  1. 生成:

    import java.util.ArrayList; // 导入 ArrayList 类
    
    ArrayList<ElemType> <Name> = new ArrayList<ElemType>(); // 创建一个 ArrayList 对象
    
  2. 增加:.add(<值>)的方法,且增加在末尾

  3. 查询:.get位置索引)的方法;

  4. 修改:。set(位置索引,值)的方法;

  5. 删除:.remove(位置索引)

  6. 计算大小:.size()的方法;

  7. 遍历:for循环 + .size()方法找到数量大小 / for-each循环;

  8. 排序:在java.util包中,还有一个非常有用的类Collection。该类中的.sort()方法可用于按字母或者数字顺序列表的排序,默认是升序。当然除此之外,你还可以传递一个实现了comparator接口的实例给该方法,以自定义的顺序进行排序,如:

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    
    public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(3);
        list.add(1);
        list.add(2);
    
        // 自定义排序:降序
        Collections.sort(list, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1; // 降序排序
            }
        });
        System.out.println(list); // 输出: [3, 2, 1]
    }
    
    
  • 需要注意的是,ArrayList中的元素其实是对象,所以要生成对应数据类型的数组列表,使用的是基本类型,如 Integer(int的包装类)、String、Boolean(boolean的包装类)、Character(char的包装类)、Double(double的包装类)
  • 非常别扭的是,在自动装箱功能普及的时候,对ArrayList的生成依旧要指定包装类名,但在ArrayList以外的代码,包装类名和基本类型基本是通用的

33. 链表

  • ArrayList几乎相同,LinkedList的差异只是它是链表;
  • LinkedList也实现了List接口,所以增删改查是完全一样的。
  • 考虑到效率,回归到数据结构的问题,以下是使用两者的取舍:
    1. 随机访问较多使用链表,增删较多用数组列表;
    2. ArrayList通过数组储存元素,当输入超过容量,它会新建一个更大的数组并迁移其中,删去旧有的;LinkedList则是以容器为单位构建的链表。不同数据类型对应不同的容器。
  • 关于Linkedlist有以下方法,如:
    1. addFirst():将一个项目添加到列表的开头;
    2. addLast():将项目添加到列表末尾;
    3. removeFirst():从列表的开头删除一个项目;
    4. removeLast():从列表末尾删除一个项目;
    5. getFirst():获取列表开头的项目;
    6. getLast():获取列表末尾的项目;

34. HashMap

  • 在数组列表中,查找元素都需要使用int型的索引;而在HashMap中,使用了键值对Python中的数据类型是字典),将元素存在key/value中,故可能可以使用Char型或者String型的索引进行查找;

  • 同样要从java.util的库中调用HashMap类,并创建对象实体化后使用,具体格式如下:

    import java.util.HashMap  
    HashMap<key,value> name = new HashMap<key,value>();
    
  • 关于HashMap有几种常用的方法:

    1. .put():添加键值对,参数用,隔开;
    2. .get():使用key值查找value;
    3. .remove():使用key值删除对应的键值对;
    4. .size():返回这个表的大小;
    5. 可以使用for-each循环遍历整个表;
    6. 对于上述的遍历,如果只需要key值,可以使用.keyset()框定范围;同理,可以使用.values()框定返回所有值;
  • 值得注意的是,使用HashMap<……,……>中创建对象时,数据类型同样填入包含类的而不是基本类型,也就是:

    1. char → \rightarrow Character;
    2. boolean → \rightarrow Boolean;
    3. double → \rightarrow Double;
    4. int → \rightarrow Integer;

35. HashSet

  • 同样在java.util的包中,使用时创建对象;

  • 区别在于它是集合,类似于Python,每个元素都是唯一的

  • 创建:

    import java.util.HashSet;  
    Hashset<ElemType> name = new HashSet<Elemtpye>();
    
  • 一些常用的方法:

    1. .add():添加元素;
    2. .contain():查找是否存在该元素;
    3. .remove():删去对应的元素;
    4. .size():返回这个集合的大小;
    5. .clear():一次性删除所有的元素;
    6. 可以使用for-each循环遍历整个表;
  • 同样,创建对象时声明的数据类型使用的是对象名或者说包装类而不是基本数据类型;

36. 迭代器/iterator

  • 是一个可用于循环遍历集合的对象;
  • ArrayListHashSet就是两种常用的类容器;
  • 关于迭代器有几种常用的方法:
    1. 使用.iterator()可以获取任何集合的迭代器,并创建对象:
      //依照一个集合创建一个迭代器对象
      Iterator<ElemType> Name
      = SetName.iterator(); 
      
    2. 使用.next()访问这个迭代器的头指针,多次使用可以从头开始遍历整个迭代器(很明显的顺序表思想);
    3. 循环遍历集合:
      //.next()方法和.hasNest()方法配合,可以想顺序表一样遍历全部的集合,如head指针和p指针
      //例如:  
      while(Name.hasNext()) {
        System.out.println(Name.next());
      }
      
      使用for循环或者for-each循环删除将无法正常工作。
    4. 可以使用.remove()删除迭代器中的元素;

37. 包装类

原始数据类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
charCharacter
  • 要创建包装器对象,请使用包装器类而不是原始类型。要获取值,您只需打印对象

  • 以下方法用于获取与对应包装对象关联的值: intValue(), byteValue(), shortValue(), longValue(), floatValue(), doubleValue(), charValue(), booleanValue()。等于说你用包装类创建对象并赋值后,需要在执行的时候输出值,可以选择使用这个;

  • 可以使用toString()将包装类中的对象转换为字符串,例如:

    Integer myInt = 100;
    String myString = myInt.toString();
    System.out.println(myString.length());
    

38. 异常处理

  • 在发生错误的时候,java通常会停止运行并生成错误信息,即抛出异常

在Java中,异常可以分为两大类:受检异常(Checked Exceptions)和非受检异常(Unchecked Exceptions)。它们在使用和处理上有一些重要的区别。

非受检异常(Unchecked Exceptions)

定义

非受检异常是指那些不需要强制在代码中进行捕获或声明的异常。它们继承自RuntimeException类及其子类。非受检异常通常用于表示编程错误,例如逻辑错误或使用不当的API。

特点
  1. 无需显式处理:编译器不会强制要求在方法中捕获或声明非受检异常。
  2. 发生频率高:非受检异常通常用于程序中的常见错误,如空指针引用或数组下标越界。
  3. 编译器不检查:在编译期间,编译器不会检查非受检异常是否已经被处理。
常见的非受检异常
  • NullPointerException
  • ArrayIndexOutOfBoundsException
  • ArithmeticException
  • ClassCastException
  • IllegalArgumentException
示例
public class Main {
    public static void main(String[] args) {
        try {
            int result = 10 / 0; // 这里会抛出 ArithmeticException
        } catch (ArithmeticException e) {
            System.out.println("Caught ArithmeticException: " + e.getMessage());
        }
    }
}

受检异常(Checked Exceptions)

定义

受检异常是指那些需要在代码中显式捕获或声明的异常。它们继承自Exception类,但不包括RuntimeException及其子类。受检异常通常用于表示程序的外部条件错误,例如文件未找到或数据库连接失败。

特点
  1. 需要显式处理:编译器要求在方法中捕获或声明受检异常。
  2. 通常表示外部错误:受检异常通常用于处理程序无法控制的外部条件错误。
  3. 编译器检查:在编译期间,编译器会检查受检异常是否已经被处理。
常见的受检异常
  • IOException
  • SQLException
  • ClassNotFoundException
  • InterruptedException
示例
import java.io.*;

public class Main {
    public static void main(String[] args) {
        try {
            FileInputStream file = new FileInputStream("test.txt"); // 可能抛出 FileNotFoundException
        } catch (FileNotFoundException e) {
            System.out.println("Caught FileNotFoundException: " + e.getMessage());
        }
    }
}

主要区别

  1. 处理要求

    • 受检异常:必须显式捕获(使用try-catch)或声明(使用throws)。
    • 非受检异常:无需显式捕获或声明。
  2. 用途

    • 受检异常:用于表示程序外部的错误条件(如I/O错误、数据库错误)。
    • 非受检异常:用于表示编程错误(如逻辑错误、API误用)。
  3. 编译器行为

    • 受检异常:编译器强制检查是否已经处理。
    • 非受检异常:编译器不强制检查。

总结

  • 了解受检异常和非受检异常的区别对于编写健壮和可靠的Java程序非常重要。受检异常用于处理外部错误,需要在代码中显式处理,而非受检异常用于处理程序中的常见逻辑错误,可以选择是否处理。掌握这两种异常的使用方法和场景,有助于编写更好的异常处理代码。

  • trycatch
    • try语句定义一段代码块,以便在执行的时候使用其进行错误测试;如果发现错误,执行catch语句下处理代码块。
    • 格式为:
      try {
      //  要尝试的代码块
      }
      catch(Exception e) {
      //  处理错误的代码块
      }
      
    • 实例:
      public class MyClass {
        public static void main(String[ ] args) {
          try {
            int[] myNumbers = {1, 2, 3};
            System.out.println(myNumbers[10]);
          } catch (Exception e) {
            System.out.println("Something went wrong.");
          }
        }
      }
      
  • finally语句允许在try-catch语句之后照常执行代码,不论后者的执行结果如何。

  • throw语句允许您创建自定义错误;
    • throw语句常与异常类型一起使用
    • Java 中有许多异常类型可用: ArithmeticException(算数异常), FileNotFoundException, ArrayIndexOutOfBoundsException, SecurityException, etc:
    • 格式为:
      throw new <ExceptionType>(说明异常的字符串/)
      
  • 注意,throw只是自定义异常,而不是异常类型。如果你想自定义异常类型,需要使用类的继承:
    class <自定义异常> extends Exception/RuntimeException{
        代码块
    } 
    
    ……
    throw new <自定义异常>(自定义说明语句)
    

39. 正则表达式

  • 是形成搜索模式的字符序列,当您在文本中搜索数据时,您可以使用此搜索模式来描述您要搜索的内容。
  • 可用于执行所有类型的文本搜索和文本替换操作;
  • Java 没有内置的正则表达式类,但我们可以导入 java.util.regex 包来使用正则表达式
    Java中的正则表达式(Regular Expressions)提供了一种强大且灵活的文本处理工具。正则表达式可以用来搜索、编辑或处理文本数据。Java通过java.util.regex包提供对正则表达式的支持。这个包中的主要类是PatternMatcher
  • 对于complie()方法,可以在方法中备注 标志(Flag) 改变搜索的执行方式:
    1. Pattern.CASE_INSENSITIVE - 执行搜索时将忽略英文字母的大小写;
    2. Pattern.LITERAL - 模式中的特殊字符没有任何特殊含义,在执行搜索时将被视为普通字符;
    3. Pattern.UNICODE_CASE - 将它与 CASE_INSENSITIVE 标志一起使用,也可以忽略英文字母表之外的其他字母的大小写

基本概念

Pattern类

Pattern类表示一个编译后的正则表达式。它不能直接实例化,而是通过静态方法compile创建。

Matcher类

Matcher类是一个引擎,用于解释和匹配输入字符串的模式可以通过Pattern对象的matcher方法创建Matcher对象

基本用法

编写和匹配正则表达式
  1. 创建Pattern对象
  2. 创建Matcher对象
  3. 使用Matcher对象进行匹配操作
import java.util.regex.*;

public class RegexExample {
    public static void main(String[] args) {
        // 创建Pattern对象
        Pattern pattern = Pattern.compile("a*b");

        // 创建Matcher对象
        Matcher matcher = pattern.matcher("aaaaab");

        // 检查是否匹配
        boolean matches = matcher.matches();

        System.out.println("Matches: " + matches);  // 输出:Matches: true
    }
}

常用正则表达式语法

当然可以,以下是根据您提供的图片内容编写的Markdown格式文本:

括号和元字符
括号用于查找一系列字符:
  • 表达式 | 描述
    • [abc] | 从括号内的选项中查找一个字符
    • [^abc] | 找到一个不在括号内的字符
    • [0-9] | 从0到9范围内查找一个字符
元字符:
  • 元字符 | 描述
    • . | 查找由.分隔的任意一种模式的匹配项,如:cat.dogfish
    • ? | 查找任何字符的一个实例
    • ^ | 查找作为字符串开头的匹配项,如:^Hello
    • $ | 在字符串末尾查找匹配项,如:World$
    • \\d | 找一个数字
    • \\s | 查找空白字符
    • \\b | 在这样的单词开头查找匹配项:\bWORD,或在这样的单词结尾处查找匹配项:WORD\b
    • \xxxxx | 查找十六进制数xxxx指定的Unicode字符
量词:
  • 量词 | 描述
    • n+ | 匹配任何至少包含一个n的字符串
    • n* | 匹配包含零次或多次出现n的任何字符串
    • n? | 匹配包含零次或一次出现n的任何字符串
    • n(x) | 匹配任何包含一系列Xn的字符串
    • nx,y) | 匹配任何包含X到Yn序列的字符串
    • n(x,) | 匹配任何包含至少Xn的序列的字符串

请注意,Markdown中的反斜杠\是转义字符,所以在Markdown中使用时需要使用两个反斜杠\\来表示一个反斜杠。例如,\d在Markdown中应写为\\d

字符匹配
  • .:匹配任意单个字符
  • \d:匹配数字 [0-9]
  • \D:匹配非数字 [^0-9]^表示取反)
  • \w:匹配字母或数字 [a-zA-Z_0-9]
  • \W:匹配非字母或数字 [^a-zA-Z_0-9]
  • \s:匹配空白字符(包括空格、制表符、换页符等)
  • \S:匹配非空白字符
数量匹配
  • *:匹配前面的字符零次或多次
  • +:匹配前面的字符一次或多次
  • ?:匹配前面的字符零次或一次
  • {n}:匹配前面的字符恰好n次
  • {n,}:匹配前面的字符至少n次
  • {n,m}:匹配前面的字符至少n次但不超过m次
边界匹配
  • ^:匹配行的开头
  • $:匹配行的结尾
  • \b:匹配单词边界
  • \B:匹配非单词边界

【注意】:对选中区域的匹配,正则表达式字符匹配的先后一般没有影响。每一个匹配符都代表可能存在的一种字符类型。

括号包含
  • 先用中括号再用小括号

示例

检查电子邮件地址
import java.util.regex.*;

public class EmailValidator {
    public static void main(String[] args) { 
        //由于在字符串中,所以需要使用转义符号\\。
        String emailPattern = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$";
        Pattern pattern = Pattern.compile(emailPattern);

        String email = "example@example.com";
        Matcher matcher = pattern.matcher(email);

        if (matcher.matches()) {
            System.out.println(email + " is a valid email address.");
        } else {
            System.out.println(email + " is not a valid email address.");
        }
    }
}
查找文本中的所有数字
import java.util.regex.*;

public class FindDigits {
    public static void main(String[] args) {
        String text = "My phone number is 123-456-7890 and my zip code is 98765.";
        String digitPattern = "\\d+";
        Pattern pattern = Pattern.compile(digitPattern);
        Matcher matcher = pattern.matcher(text);

        while (matcher.find()) {
            System.out.println("Found number: " + matcher.group());
        }
    }
}

常用方法

Pattern类
  • compile(String regex):编译正则表达式
  • compile(String regex, int flags):编译带有指定标志的正则表达式
Matcher类
  • matches():尝试将整个区域与模式匹配
  • find():扫描输入的序列,查找与该模式匹配的下一个子序列
  • group():返回由以前的匹配操作所匹配的输入子序列
  • start():返回以前匹配的初始索引
  • end():返回最后匹配字符之后的偏移量

总结

Java中的正则表达式为文本处理提供了强大的功能,通过PatternMatcher类,可以灵活地搜索、匹配和操作字符串。掌握正则表达式的基本语法和使用方法,有助于提高字符串处理的效率和灵活性。

40. 线程

  • 结合操作系统的知识,线程即专注于实现程序的某一项功能的部分指令流,它是一个程序中独立执行的最小单位;

  • 它允许程序通过同时执行多项任务来更有效地运行,可以用来在后台执行复杂的任务而不中断主程序(在内核态中分配CPU资源);

  • 每个Java应用程序至少有一个线程:主线程mainstream)。

  • Java中有两种主要方式创建线程:继承Thread,和实现Runnable接口(后者继承接口后),具体格式如下:

    1. 继承Thread
    class <线程名> extends Thread{  
      代码块;
      
      public static void main(String[] args) {
          <线程名> 对象名 = new <线程名>();
          对象名.start();  // 开启线程
          }
      } 
    
    1. 实现Runnable接口
    class <线程名> implements Runnable{  
        代码块;  
        public static void main(String[] args) {
            <线程名> 对象名1 = new <线程名>();  
            //接口的话需要重新实体化成线程,并创建对象使用(殊途同归)
            Thread 对象名2 = new Thread(对象名1);
            thread.start();  // 开启线程
        }
    }
    
  • 操作系统中,线程有五个状态:

    1. New:新建态;
    2. Runnable:就绪态;
    3. Running:运行态;
    4. Blocked:阻塞态;
    5. Dead:线程执行完毕被注销;
  • 当多个线程共享资源时,可能导致资源的不一致性问题。为了解决这个问题,需要进行线程同步。

40.1 线程同步

  • 保证多个线程对共享资源的访问是有序的、互不干扰的。同步可以防止数据不一致和竞态条件;
  • Java提供了多种方式来实现线程同步,主要包括使用synchronized关键字、 volatile关键字、显示锁(如ReentrantLock),以及更高级的并发工具(如信号量栅栏闭锁)。
  1. synchronized关键字

    • 是Java中最常用的线程同步工具,它可以用来修饰方法或代码块,确保同一时间只有一个线程可以执行被同步的代码。
    • synchronized方法是针对整个方法加锁,而synchronized代码块则可以只对需要同步的部分加锁,从而减少锁的粒度,提高性能。
    • 实例
      //同步方法  
      public class Counter {
          private int count = 0;
      
          public synchronized void increment() {
              count++;
          }
      
          public synchronized int getCount() {
              return count;
          }
      }
      //同步代码块  
      public class Counter {
          private int count = 0;
      
          public void increment() {
              //用this指明需要同步的代码块
              synchronized (this) {
              count++;
              }
          }
      
          public int getCount() {
              synchronized (this) {
              return count;
              }
          }
      }
      
      
  2. volatile关键字

    • 用于声明变量使其在多个线程中可见。它确保变量的值在所有线程中是可见的,但它不保证复合操作(如递增操作)的原子性(回到操作系统的问题,不同的进程在本关键字的加成下可以共享变量的数据,但是不能保证读写操作之间不会冲突。解决方法是使用其他关键字完成同步,例如synchronized和Atomic);
    • 实例:
      public class VolatileExample {
           private volatile boolean flag = true;
      
           public void stop() {
               flag = false;
           }
      
           public void run() {
               while (flag) {
                   // do something
               }
           }
       }
      
      
  3. 显示锁

    • Java在java.util.concurrent.locks包中的提供了锁,如ReentranLock,它们提供了比synchronized更灵活的同步机制。
    • 最常用的显示锁是ReentrantLock,它是一个可重入锁,类似于synchronized关键字,但提供了更多的功能和灵活性;

    具体的使用写在另一份资料里,此处略。

40.2 线程创建

创建线程有几种方法:

  1. 继承Thread类并覆盖其run()方法来创建:
    //常见的格式
    class <线程名> extends Thread{
        public void run() {
            System.out.println("Thread is running...");
        }
    }
    
  2. 实现Runnable接口:
    //例如以下格式
    public class MyRunnable implements Runnable {
        public void run() {
            System.out.println("Thread is running...");
        }
    
        public static void main(String[] args) {
            //建立接口的对象
            MyRunnable myRunnable = new MyRunnable();  
            //将接口对象转变为线程对象
            Thread t1 = new Thread(myRunnable);  
            //启动线程
            t1.start();
        }
    }
    
    
  3. 使用匿名类实现Runnable接口

    此处略,在匿名类笔记中有。

  4. 使用 Lambda 表达式
    • 需要Java 8以上;
    //一个很好的例子  
    public class Main {
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> System.out.println("Thread is running..."));
            t1.start();
        }
    }
    
    

40.3 线程运行

  1. 如果该类是继承自Thread类,则将其实例化后可以调用.start()方法来运行线程;
  2. 如果该类使实现了Runnable接口,则可以通过将类的实例传递给Thread(由变成Thread的对象实例化),并调用线程的.start()运行它。

补充:“继承自类”和“部署接口”的区别:

  • 继承自Thread类时,除了Thread之外无法同时继承其他的类;
  • 部署Runnable接口时,也允许从其他的类继承过来(这两个同时发生);

40.4 并发问题*

  • 因为线程与程序的其他部分同时运行,所以无法知道代码将以何种顺序运行。当线程和主程序读取和写入相同的变量时,其值是不可预测的。由此产生的问题称为并发问题(ReadWriteLock的用武之地);
  • 为了避免并发问题,最好在线程之间共享尽可能少的属性。如果需要共享属性,一种可能的解决方案是在使用线程可以更改的任何属性之前,使用线程的isAlive()方法检查线程是否已完成运行
    一个实例:
public class MyClass extends Thread {
  public static int amount = 0;

  public static void main(String[] args) {
    MyClass thread = new MyClass();
    thread.start();
    // 等待线程完成
    while(thread.isAlive()) {
    System.out.println("Waiting...");
  }  
  //解除循环意味着线程执行完毕
  // 更新 amount 并打印其值
  System.out.println("Main: " + amount);
  amount++;
  System.out.println("Main: " + amount);
  }
  //重写run()方法;
  public void run() {
    amount++;
  }
}

41. Lambda表达式

  • 表达式是一小段代码,它接受参数并返回一个值。 Lambda 表达式类似于方法,但它们不需要名称,并且可以直接在方法体中实现;

  • 设置格式:

    <para1,para2,···> -> expression;
    
  • 由于其过于简练,故在表达方式有限的情况下,它需要立即返回一个值,并且不能包含变量、赋值或语句。由此,为了进行更复杂的操作,可以使用花括号包含代码块。如果 lambda 表达式需要返回一个值,那么代码块应该有一个return语句

  • Lambda 表达式通常作为参数传递给方法。而且如果变量类型是只有一个方法的接口,则Lambda表达式可以存储在变量中,如Consumer<Integer> method = (n) -> { System.out.println(n); };中method接口是Consumer类接口(在java.util.function.Consumer中调用
    ,可以作为存储的容器。

  • 要在方法中使用Lanbda表达式,该方法应该要有一个参数,其类型为单方法接口。调用这个方法的时候就会运行Lambda表达式。


    实例:

//设置单方法接口
interface StringFunction {
  String run(String str);
}

public class MyClass {
  public static void main(String[] args) {  
    //对于main方法,它的参数是StringFunction接口类型的,而这个接口是单方法的,保证了传递Lambda表达式时的唯一性传递。
    StringFunction exclaim = (s) -> s + "!";
    StringFunction ask = (s) -> s + "?";
    printFormatted("Hello", exclaim);
    printFormatted("Hello", ask);
  }
  public static void printFormatted(String str, StringFunction format) {
    String result = format.run(str);
    System.out.println(result);
  }
}

42. 文件

  • Java 有多种创建、读取、更新和删除文件的方法;
  • java.io包中的File文件类允许我们处理文件;
  • 同样,要使用该类的方法,需要先创建该类的对象,制定需要操作的文件名和目录名;
  • 以下是类中一些实用的方法:
    方法类型描述
    canRead()Boolean测试文件是否可读
    canWrite()Boolean测试文件是否可写
    createNewFile()Boolean创建一个空文件
    delete()Boolean删除文件
    exists()Boolean测试文件是否存在
    getName()String返回文件名
    getAbsolutePath()String返回文件的绝对路径名
    length()Long返回文件大小(以字节为单位)
    list()String[]返回目录文件的数组
    mkdir()Boolean创建目录

42.1 创建文件

  • 要在Java中创建文件,可以使用createNewFile()方法,它将返回一个boolean值以表示文件是否创建成功;
  • 请注意,该方法包含在try...catch块中。 这是必要的,因为如果发生错误(如果由于某种原因无法创建文件),它会抛出IOException
  • 所以,创建步骤如下:
    //导入File类和IOException类
    import java.io.File;
    import java.io.IOException;   
    ···  
        //使用try-catch语块
        try {  
            //新建文件类对象,并声明文件名
            File myObj = new File("filename.txt");  
            //根据返回值判断下一步行动
            if (myObj.createNewFile()) {
                System.out.println("File created: " + myObj.getName());
            } else {
                System.out.println("File already exists.");
            }
        } catch (IOException e) {
            System.out.println("An error occurred.");//抛出异常
            e.printStackTrace();  
        }
    
  • 要在特定目录中创建文件(需要权限),请指定文件的路径并使用双反斜杠转义“\ \”字符(对于 Windows)。 在 Mac 和 Linux 上,您可以只写路径,例如:/Users/name/filename.txt
  • 对于我们创建的文件,我们可以使用FileWriter类和write()方法将一些文本写入已经创建的文件中去,如:
    FileWriter myWriter = new FileWriter("filename.txt");
    myWriter.write("Files in Java might be tricky, but it is fun enough!");
    
    之后,如果我们需要关闭我们创建的文本文件,直接使用.close()方法即可,如:
    myWriter.close();
    

42.2 读取文件

  • Java API中有许多可用类(FileReader、BufferedReader、Files、Scanner、FileInputStream、FileWriter、BufferedWriter、FileOutputStream)可用于在Java中读取和写入文件,具体使用那一版取决于需要读取的字节、字符,以及文件/行的大小。
  • 一般情况下,我们使用Scanner类来读取我们创建/储存的文本文件的内容:
    import java.io.File;  // 导入 File 文件类
    import java.io.FileNotFoundException;  // 导入这个类来处理错误
    import java.util.Scanner; // 导入 Scanner 类以读取文本文件
    ···
        File myObj = new File("filename.txt");
        Scanner myReader = new Scanner(myObj);//生成Scanner类的对象以开始扫描读取工作
        while (myReader.hasNextLine()) {
            String data = myReader.nextLine();
            System.out.println(data); 
        }//以行为单位逐行输出文件内容
    

除了.nextLine()方法以外,要获取更多的文件信息,可以使用之前提到的File方法。

42.3 删除文件

  • 使用.delete()方法,它的返回值是一个布尔值;
  • 删除过程中建议加上提示词,如:
    if (myObj.delete()) { 
      System.out.println("Deleted the file: " + myObj.getName());
    } else {
      System.out.println("Failed to delete the file.");
    } 
    
  • 由于在创建文件的时候你可以直接填写路径生成文件夹,故你也可以直接删除文件夹,但是它必须为空如果不为空,delete()方法会返回false,删除操作将失败):
  • 如果我们想删除非空文件夹,可以使用递归的方法
    import java.io.File;
    
    public class DeleteDirectory {
        public static void main(String[] args) {
            // 指定要删除的目录
            File directory = new File("path/to/directory");
        
            // 调用递归删除方法
            boolean result = deleteDirectory(directory);
        
            if (result) {
                System.out.println("目录删除成功");
            } else {
                System.out.println("目录删除失败");
            }
        }
    
        // 递归删除目录的方法
        public static boolean deleteDirectory(File directory) {
            // 如果目录不存在,返回 true
            if (!directory.exists()) {
                return true;
            }
        
            // 如果目录是文件,直接删除并返回删除结果
            if (directory.isFile()) {
                return directory.delete();
            }   
        
            // 获取目录中的所有文件和子目录,用文件数组的形式存储
            File[] files = directory.listFiles();
            if (files != null) {
                // 递归删除目录中的所有内容
                for (File file : files) {
                    if (!deleteDirectory(file)) {
                        return false;
                    }
                }
            }
        
            // 删除目录本身
            return directory.delete();
        }
    }
    
    
    也可以直接使用org.apache.commons.io.FileUtils库中的方法,使用前要先添加到你的项目中。
    添加依赖后,
    import org.apache.commons.io.FileUtils;//调用这个特定的库
    import java.io.File;
    import java.io.IOException;
    
    public class DeleteDirectory {
        public static void main(String[] args) {
            // 指定要删除的目录
            File directory = new File("path/to/directory");
            try{
                // 调用 FileUtils.deleteDirectory 方法删除目录,一步就搞定了
                FileUtils.deleteDirectory(directory);
                System.out.println("目录删除成功");
            }catch (IOException e) {
                e.printStackTrace();
                System.out.println("目录删除失败");
            }
        }
    }
    
    

42.4 接收输入

  • 要接收用户的输入并用文件储存,可以使用Scanner类从控制台接受用户的输入,并使用PrinterWriter类将输入的数据写入文件。若之后还要读取文件内容,继续使用Scanner类对象从文件中读取数据。
    实例:
import java.io.File;//文件的类
import java.io.FileNotFoundException;//解决没找到的文件异常的类
import java.io.PrintWriter;//将输入写入文件的类
import java.io.IOException;//解决文件I/O异常的类
import java.util.Scanner;//接受数据并输出的类

public class FileInputExample {
    public static void main(String[] args) {
        // 创建一个Scanner对象,用于接收用户输入
        Scanner scanner = new Scanner(System.in);
        
        System.out.print("请输入一些文本数据:");  
        //以行为单位接收数据
        String userInput = scanner.nextLine();

        // 指定文件路径,创建文件
        File file = new File("userInput.txt");
        
        // 将用户输入写入文件,创建写入文件类会返回Boolean值
        try (PrintWriter writer = new PrintWriter(file)) {
            writer.println(userInput);//将输入写入到写入文件中
        } catch (IOException e) {
            System.out.println("写入文件时出错:" + e.getMessage());
        }

        // 读取文件中的数据并输出
        try (Scanner fileScanner = new Scanner(file)) {
            System.out.println("从文件中读取的数据:");
            while (fileScanner.hasNextLine()) {
                System.out.println(fileScanner.nextLine());
            }//用循环遍历的方式输出文件的全部信息
        } catch (FileNotFoundException e) {
            System.out.println("读取文件时出错:" + e.getMessage());
        }

        scanner.close();//关闭文件输出
    }
}

  • 如果要接受多行数据写入文件,可以以行为单位接收字符串String并用.append()写入接收文件中,如:
1.
Scanner scanner = new Scanner(System.in);
//System.in是标准输入流,可以从控制台读取输入数据。由Scanner类对象接收它,可以很方便地读取各种类型的输入类型。  
//除此以外,System.in还是一个Inputstream,它抽象了输入源,除了从键盘读取输入,还可以将其重定向到其他输入源,如文件或者网络流,从而使得程序更具灵活性。

2.  
StringBuilder userInput = new StringBuilder();
// 使用StringBuilder来存储多行输入,StringBuilder是标准库的一个类,无需导入,是可以直接使用的。具体情况看附录文件。

3. 
String line;//用字符串来存储一行的数据
while (!(line = scanner.nextLine()).equalsIgnoreCase("exit")) {
    userInput.append(line).append(System.lineSeparator());//在没有接收到exit的情况下循环接收以行为单位的数据,并添加到StringBuilder类对象中,并自动在每行末尾加上自动换行的特殊符
} 

4.  
// 将用户输入的数据写入输入文件中
try (PrintWriter writer = new PrintWriter(file)) {
    writer.print(userInput.toString());//将输入文件中的内容读入要存储的文件中
} catch (IOException e) {
    System.out.println("写入文件时出错:" + e.getMessage());
}//异常处理  

在上述接受的过程中,数据的存储类型发生了几次变化,即:
S t r i n g 字符串 → S t r i n g B u i l d e r 字符串流 → P r i n t W r i t e r 输入文件 → F i l e 文件 String字符串\rightarrow StringBuilder字符串流\rightarrow PrintWriter输入文件\rightarrow File文件 String字符串StringBuilder字符串流PrintWriter输入文件File文件

43. 关键字

  • true,false,null虽然不是关键字,但是它们不能用作标识符的文字或者保留字。

44. 默认方法

  • 采用default关键字定义的方法只能出现在接口Interface中,它为接口提供了一种默认选项。你可以选择在实现它的时候重写它也可以选择不重写它。

45. Java中的XML应用

  • XML 是一种简单的基于文本的语言,旨在以纯文本格式存储和传输数据。

  • 以下是 XML 提供的优势:

    • 与技术无关 − 作为纯文本,XML 与技术无关。 它可以被任何技术用于数据存储和数据传输目的。

    • 人类可读 − XML 使用简单的文本格式。 它是人类可读且易于理解的。

    • 可扩展 − 在 XML 中,可以非常轻松地创建和使用自定义标签。

    • 允许验证 − 使用 XSD、DTD 和 XML 结构可以很容易地进行验证。

  • XML Parser 提供了一种访问或修改 XML 文档中数据的方法。 Java 提供了多种解析 XML 文档的选项。 以下是通常用于解析 XML 文档的各种类型的解析器:

    1. Dom 解析器 − 通过加载文档的完整内容并在内存中创建其完整的层次树来解析 XML 文档。

    2. SAX 解析器 − 在基于事件的触发器上解析 XML 文档。 不将完整的文档加载到内存中。

    3. JDOM 解析器 − 解析 XML 文档的方式与 DOM 解析器类似,但方式更简单。

    4. StAX 解析器 − 以与 SAX 解析器类似的方式解析 XML 文档,但以更有效的方式。

    5. XPath 解析器 − 基于表达式解析 XML 文档,并广泛与 XSLT 结合使用。

    6. DOM4J 解析器 − 一个使用 Java Collections Framework 解析 XML、XPath 和 XSLT 的 java 库。 它提供对 DOM、SAX 和 JAXP 的支持。

    有 JAXB 和 XSLT API 可用于以面向对象的方式处理 XML 解析。

  • 详细内容参考Java XML 教程,此处略。

46. Java中的包

此处只提供教程链接,在看完教程之后还需要详细信息的可以问ChatGpt:

  1. Java.io 包
  2. Java.lang 包
  3. Java.math 包
  4. java.time 包
  5. Java.util 包
  6. java.util.regex 包(配合正则表达式使用)
  7. java.util.zip 包
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值