注释

Java 支持三种注释方式:

  • 单行注释
  • 多行注释
  • 文档注释

前两种分别是 // 和 /* */,第三种被称作文档注释,它以 /** 开始,以 */ 结束。

文档注释允许你在程序中嵌入关于程序的信息。

文档注释,使你更加方便的记录你的程序信息。

JAVA基础

关键字

被赋予特殊含义的单词

  • 字母全部小写
  • 代码编辑器一般对此都有渲染

JAVA的数据类型

字符型char

表示通常意义上字符,用”括起来

如:‘A’,‘中’等

采用unicode编码

布尔值/逻辑值

采用逻辑运算,一般用于程序流程控制

一般取值为ture或false(不能用0/1代替)

整型

有固定表数范围和字符长度,不受操作系统和硬件影响

类型占用字节表数范围
byte1字节-128~127
short2字节-2^15~2^15-1
int4字节-2^31~2^31-1
long8字节-2^63~2^63-1

整型默认问题

java整型变量默认int

声明长整型常量在常量后加’l’或’L’;

如:整型3、长整型3L;

注意默认特性的编程应用:

long k = 922336414641456314L;

Java中数值变量的声明:

二进制变量的声明以0b为前缀; 八进制变量的声明以0为前缀; 十六进制变量的声明以0x为前缀

二进制、八进制、十六进制数值在运用时候自动转为对应的十进制的值

    public static void main(String[] args)
    {
        int a = 0b11;   //声明二进制变量
        int b = 011;    //声明八进制变量
        int c = 11;     //声明十进制变量
        int d = 0x11;   //声明十六进制变量
        System.out.println("a:"+a); //3
        System.out.println("b:"+b); //9
        System.out.println("c:"+c); //11
        System.out.println("d:"+d); //17
    }

十进制转其它进制: 十进制转二进制:Integer.toBinaryString(i); 十进制转八进制:Integer.toOctalString(i); 十进制转十六进制:Integer.toHexString(i);

    public static void main(String[] args)
    {
        int i = 15;
        System.out.println("十进制15对应的二进制为:"+Integer.toBinaryString(i));  //1111
        System.out.println("十进制15对应的八进制为:"+Integer.toOctalString(i));   //17
        System.out.println(i);  //15
        System.out.println("十进制15对应的十六进制为:"+Integer.toHexString(i));    //f
    }

4.浮点型

分类:单精度浮点、双精度浮点

类型占用存储空间表数范围
float4字节-3.403E38~3.403E38
double8字节-1.798E308~1.798E308

JAVA浮点型默认为double型,如要声明float,则在数字后加f或F

double d = 3.14;
float f = 3.14f;

JAVA的引用数据类型

指向一个对象在内存中的位置

  • 本质上是很强的完整性和安全性的指针(不能使用++、—运算)
  • 包括:类、接口、数组

标识符

用来表示变量名、类名、方法名、数组名和文件名等

是有效的字符序列

规则:

正常规则
  • 由字母、下划线(_)、美元符号($)组合而成
  • 以字符、下划线或美元符号开头,不能以数字开头
JAVA潜规则

要求:“见名思义”

  • stuName,area等,循环控制变量除外
  • 类名首字母大写,如String
  • 变量、方法及对象的首字母小写
  • 所有单词靠在一起,大写中间单词首字母
  • 变量大写所有字母
  • JAVA包全部小写
关键字

是指系统所保留使用的表示符

java不允许用户对关键字赋予其他的含义

常量与变量

变量

概念:在程序的运行过程中可变的量,通常用来记录运算中间结果或保存数据

特点:先声明后使用

变量的声明

变量声明是一个完整的语句,用分号结束

变量四要素:名字、类型、值、作用域

变量形式

int min = 10;

常量

概念:一经建立,在整个程序过程中都不会改变

形式与变量基本相同,前面加关键字final

常量形式

final int MIN=10; \\常量标识符一般为大写

Helloworld案例的编写

public class HelloWorld{  //这里的类名称和文件名称保持一致
    public static void main(String[] args){
        System.out.println("HelloWorld");
    }
}

注意:

  • 一个 .java 源文件里面可以写很多个类,但最多只能有一个 public 类。
  • 编译拆分:当代码被编译后,每一个类都会生成一个独立的 .class 文件(即使它们原本写在同一个文件里)。如果你代码里有隐藏的内部类,还会生成类似 MainActivity$1.class 这种带美元符号的文件(你在之前的截图中其实已经见过了)。

运算符

  • 运算符:对常量或者变量进行操作的符号
  • 表达式:用运算符把常量或者变量连接起来符合java语法的式子 就可称为表达式

算数运算符

符号作用
+
-
*
/
%取余

赋值运算符

符号作用
=赋值

自增自减运算符

符号作用
++自增
自减

关系运算符

符号说明
==判断a和b的值是否相等,相等为true,不等为false
!=
>
>=
<

逻辑运算符

基本逻辑运算符

符号说明
&逻辑与
|逻辑或
^逻辑异或
!逻辑非

短路逻辑运算符

符号作用说明
&&短路与
||短路或

区别

  • 短路与左边为true,一定为true;短路或左边为flase,一定为flase

三元运算符

  • 格式:关系表达式?表达式1:表达式2;
  • 范例:a>b?true:false

数据输入

Scanner使用的基本步骤

  1. 导包import java.util.Scanner;
  2. 创建对象 Scanner sc=new Scanner(System.in);
  3. 接受数据 int i = sc.nextInt();

JAVA分支语句

流程控制

分为顺序结构、分支结构、循环结构

顺序结构

是程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序,一次执行,程序中大多数的代码都是这样执行的

分支结构

if语句

格式1

if(关系表达式){
    语句体;
}

执行流程

  1. 受限计算关系表达式的值
  2. 为true就执行语句体
  3. 为false就不执行

格式2

if(关系表达式){
    语句体1;
}else{
    语句体2
}

循环语句

JAVA数组

是用于存储多个相同类型数据的存储模型 一次性声明大量的用于存储数据的变量。同时要存储的数据都是同类型数据

格式

有两种格式可以使用

int[] arr

或者

int arr[]

动态初始化

初始化时只指定数组长度,由系统为数组分配初始值

int[] arr = new int[3]

方法

概述

方法是将具有独立功能的代码块组成一个整体,使其具有特殊功能的代码集

方法必须属于某个类,不能像 C / Python 那样独立存在。

注意

  • 方法必须先创建才可以使用
  • 发布该法创建后不是直接运行的,需要手动使用执行,称为方法调用

使用

格式

public static void 方法名(){
    //方法体
}

调用

JAVA的方法调用不用关心顺序,因为 Java 编译时会先把整个类结构读完,知道这个类里有哪些方法,然后再检查方法调用。

public class Test {
    public static void main(String[] args) {
        sayHello();   // 方法定义在后面,但可以先调用
    }

    public static void sayHello() {
        System.out.println("Hello");
    }
}

带参

定义

public static void 方法名(参数){
    ....
}

比如说

pubilc static void getMax(int number1,int number2){
....
}

返回值

public static boolean isEvenNumber(int number){
    return true;
}

方法重载

简单说就是:

同一个类里,方法名相同,但参数列表不同。

比如你想写一个 add 方法,可以加两个整数,也可以加三个整数,也可以加小数:

public class Test {
    public static int add(int a, int b) {
        return a + b;
    }

    public static int add(int a, int b, int c) {
        return a + b + c;
    }

    public static double add(double a, double b) {
        return a + b;
    }

    public static void main(String[] args) {
        System.out.println(add(1, 2));        // 调用第一个
        System.out.println(add(1, 2, 3));     // 调用第二个
        System.out.println(add(1.5, 2.5));    // 调用第三个
    }
}

什么叫“参数列表不同”?

参数列表不同,主要看这几个:

参数个数不同

public static void test(int a) {}
public static void test(int a, int b) {}

这个可以重载。


参数类型不同

public static void test(int a) {}
public static void test(String a) {}

这个也可以重载。


参数顺序不同

public static void test(int a, String b) {}
public static void test(String a, int b) {}

这个也可以重载。

因为调用时可以区分:

test(1, "abc");
test("abc", 1);

类和对象

对象的概念

万物皆对象,客观存在的事物皆为对象

类的概念

类是指现实生活中一类具有共同属性行为的事物的抽象 特点

  • 是对象的数据类型
  • 是具有相同属性和行为的一组对象的集合

类的基本写法

public class Person {
    String name;
    int age;

    public void sayHello() {
        System.out.println("我叫 " + name + ",今年 " + age + " 岁");
    }
}

这里:

Person 是类名
name、age 是成员变量,也叫属性、字段
sayHello() 是方法

可以理解为:

属性:对象有什么
方法:对象能干什么

创建对象

类只是模板,不能直接代表某个具体的人。

要创建对象,需要使用 new

Person p = new Person();

含义:

Person p        声明一个 Person 类型的变量
new Person()    创建一个 Person 对象
=               把对象的地址/引用交给变量 p

然后可以访问对象的属性和方法:

p.name = "Tom";
p.age = 18;

p.sayHello();

完整例子:

public class Main {
    public static void main(String[] args) {
        Person p = new Person();

        p.name = "Tom";
        p.age = 18;

        p.sayHello();
    }
}

class Person {
    String name;
    int age;

    public void sayHello() {
        System.out.println("我叫 " + name + ",今年 " + age + " 岁");
    }
}

输出:

我叫 Tom,今年 18 岁

类和对象的关系

一个类可以创建多个对象。

Person p1 = new Person();
p1.name = "Tom";
p1.age = 18;

Person p2 = new Person();
p2.name = "Jerry";
p2.age = 20;

p1p2 都是 Person 类型,但它们是两个不同对象。

Person 类:模板
p1 对象:Tom,18 岁
p2 对象:Jerry,20 岁

每个对象都有自己独立的成员变量。


成员变量和局部变量

成员变量

写在类里面、方法外面的变量叫成员变量。

class Person {
    String name;
    int age;
}

成员变量属于对象。

如果没有手动赋值,会有默认值:

int       默认 0
double    默认 0.0
boolean   默认 false
char      默认 '\u0000'
引用类型   默认 null

例如:

Person p = new Person();
System.out.println(p.age);   // 0
System.out.println(p.name);  // null

局部变量

写在方法里面的变量叫局部变量。

public void test() {
    int x = 10;
}

局部变量必须先赋值才能使用。

这个不行:

public void test() {
    int x;
    System.out.println(x); // 报错
}

所以要记住:

成员变量有默认值
局部变量没有默认值,必须手动初始化

构造方法

构造方法用于创建对象时初始化数据。

构造方法特点:

方法名和类名一样
没有返回值,连 void 都不能写
创建对象时自动调用

例子:

class Person {
    String name;
    int age;

    public Person() {
        System.out.println("无参构造方法");
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

使用:

Person p1 = new Person();

Person p2 = new Person("Tom", 18);

this 关键字

this 表示当前对象。

最常见用途:区分成员变量和局部变量。

class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

这里:

this.name 是成员变量
name 是构造方法参数

如果不写 this

name = name;

就会变成参数自己给自己赋值,成员变量没有被正确赋值。

所以构造方法里经常写:

this.name = name;
this.age = age;

对象变量保存的是引用

Java 里的对象变量保存的不是对象本体,而是对象的引用。

例如:

Person p1 = new Person();
p1.name = "Tom";

Person p2 = p1;
p2.name = "Jerry";

System.out.println(p1.name);

输出:

Jerry

原因是:

p1 和 p2 指向同一个对象
通过 p2 修改对象
p1 看到的也是修改后的结果

可以理解成:

变量里存的是对象地址
不是对象的完整拷贝

null 空引用

对象变量可以是 null

Person p = null;

意思是:

p 没有指向任何对象

如果直接调用:

p.sayHello();

会报错:

NullPointerException

也就是空指针异常。

所以使用对象前要确保它已经指向真实对象:

Person p = new Person();
p.sayHello();

封装

实际开发中,一般不建议直接暴露成员变量。

不推荐:

class Person {
    public String name;
    public int age;
}

更常见写法是使用 private 修饰属性,再提供 getter 和 setter 方法。

class Person {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setAge(int age) {
        if (age >= 0 && age <= 150) {
            this.age = age;
        }
    }

    public int getAge() {
        return age;
    }
}

使用:

Person p = new Person();
p.setName("Tom");
p.setAge(18);

System.out.println(p.getName());
System.out.println(p.getAge());

封装的作用:

  • 隐藏内部数据
  • 控制外部访问
  • 可以在 setter 里做校验
  • 提高代码安全性和可维护性

访问修饰符

常见访问修饰符:

public     所有地方都能访问
protected  同包或子类能访问
默认       同一个包能访问
private    只有当前类内部能访问

初学先记住:

public:公开
private:私有

属性一般用 private,方法根据需要用 public


一个文件里多个类

Java 一个 .java 文件里可以写多个类,但有规则:

一个文件最多只能有一个 public 类
public 类名必须和文件名一致

例如文件名是 Main.java

public class Main {
    public static void main(String[] args) {
        Person p = new Person();
    }
}

class Person {
}

这是可以的。

但如果这样:

public class Person {
}

文件名就必须叫:

Person.java

类和对象的完整例子

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("Tom", 18);
        Person p2 = new Person("Jerry", 20);

        p1.sayHello();
        p2.sayHello();
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void sayHello() {
        System.out.println("我叫 " + name + ",今年 " + age + " 岁");
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

输出:

我叫 Tom,今年 18 岁
我叫 Jerry,今年 20 岁

API

概念

Java 官方或第三方提供好的类和方法,可以直接调用,不需要自己从零实现。

Java 官方 API 指 JDK 自带的类和方法,比如 String、Scanner、ArrayList、Math、System 等。 第三方 API 一般需要额外导入 jar 包或 Maven 依赖。

几个常见的API:

String: 字符串类,属于引用类型,不是基本类型。 特点是不可变,适合保存和少量处理字符串。

StringBuilder: 可变字符串类,适合频繁拼接和修改字符串。 常用方法是 append()、toString()、reverse()、insert()、delete()。

String 和 StringBuilder 的区别: String 不可变,每次拼接可能产生新对象。 StringBuilder 可变,适合循环拼接,效率更高。

例子:

public class Main {
    public static void main(String[] args) {
        String name = "Tom";

        StringBuilder sb = new StringBuilder();
        sb.append("Hello, ");
        sb.append(name);
        sb.append("!");

        String result = sb.toString();

        System.out.println(result);
    }
}

集合

概述

编程的时候如果要存储多个数据,使用长度固定的数组存储格式,不一定满足要求,这时候就需要集合

集合类有很多,比如常见的ArrayList,主要分为两大类,Collection(单列集合)和Map(双列集合)

集合提供了一种存储空间可变的方法

双列集合

每次添加添加一堆元素,即键值对 image.png

  • 双列集合一次需要存储一对数据
  • 键不能重复,值可以重复
  • 键和值是一一对应的
  • 在java中称为“Entry对象”

HashMap

概念

HashMap 是 Java 中常用的双列集合,用来存储键值对数据。

格式:

HashMap<键的类型, 值的类型> map = new HashMap<>();

例如:

HashMap<String, Integer> map = new HashMap<>();

表示:

键是 String 类型
值是 Integer 类型

基本使用

import java.util.HashMap;
 
public class Main {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();
 
        map.put("张三", 18);
        map.put("李四", 20);
        map.put("王五", 22);
 
        System.out.println(map);
        System.out.println(map.get("张三"));
    }
}

输出:

{李四=20, 张三=18, 王五=22}
18

注意:HashMap 的输出顺序不一定和添加顺序一致。


HashMap 的特点

1. HashMap 存储的是键值对,也就是 key-value。
2. key 不能重复。
3. value 可以重复。
4. 一个 key 对应一个 value。
5. HashMap 的数据没有固定顺序。

例如:

HashMap<String, Integer> map = new HashMap<>();
 
map.put("张三", 18);
map.put("张三", 20);
 
System.out.println(map);

结果:

{张三=20}

原因是:

key 不能重复。
如果 key 已经存在,再次 put 相同 key,会覆盖原来的 value。

HashMap 常用方法

put(key, value)       添加或修改键值对
get(key)              根据 key 获取 value
remove(key)           根据 key 删除键值对
containsKey(key)      判断是否包含某个 key
containsValue(value)  判断是否包含某个 value
size()                获取键值对数量
isEmpty()             判断集合是否为空
clear()               清空集合

例子:

HashMap<String, Integer> map = new HashMap<>();
 
map.put("Tom", 18);
map.put("Jerry", 20);
 
System.out.println(map.get("Tom"));          // 18
System.out.println(map.containsKey("Tom"));  // true
System.out.println(map.size());              // 2
 
map.remove("Tom");
 
System.out.println(map);                     // {Jerry=20}

HashMap 的遍历

方式一:遍历 key,再通过 key 获取 value
HashMap<String, Integer> map = new HashMap<>();
 
map.put("Tom", 18);
map.put("Jerry", 20);
map.put("Jack", 22);
 
for (String key : map.keySet()) {
    Integer value = map.get(key);
    System.out.println(key + " = " + value);
}

理解:

keySet() 可以拿到所有 key。
然后通过 get(key) 获取对应的 value。

方式二:遍历 Entry 对象
HashMap<String, Integer> map = new HashMap<>();
 
map.put("Tom", 18);
map.put("Jerry", 20);
map.put("Jack", 22);
 
for (HashMap.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey();
    Integer value = entry.getValue();
 
    System.out.println(key + " = " + value);
}

理解:

Entry 对象表示一对键值对。
一个 Entry 里面包含一个 key 和一个 value。
entrySet() 可以拿到所有键值对对象。

也就是说:

HashMap
  ├── Entry(key, value)
  ├── Entry(key, value)
  └── Entry(key, value)

HashSet

概念

HashSet 是单列集合,用来存储不重复的数据。

HashSet<String> set = new HashSet<>();

例如:

import java.util.HashSet;
 
public class Main {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
 
        set.add("Tom");
        set.add("Jerry");
        set.add("Tom");
 
        System.out.println(set);
    }
}

输出中只会有一个 Tom


HashSet 的特点

1. 只能存储单个元素。
2. 元素不能重复。
3. 没有固定顺序。
4. 底层和 HashMap 有关。

HashSet 表面上是单列集合,但底层可以理解成借助 HashMap 实现。

所以 HashSet 判断元素是否重复时,也会用到:

hashCode()
equals()

HashSet 和反序列化的关系

因为 HashSet 底层和 HashMap 有关,所以反序列化时也可能间接触发:

hashCode()
equals()

因此:

HashSet → 底层 HashMap → hashCode / equals

反序列化学习中,看到 HashSet,也要想到它可能和 HashMap 的触发逻辑有关。


PriorityQueue

概念

PriorityQueue 是优先队列。

普通队列一般是先进先出,而优先队列会按照优先级进行排序。

PriorityQueue<Integer> queue = new PriorityQueue<>();

例子:

import java.util.PriorityQueue;
 
public class Main {
    public static void main(String[] args) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
 
        queue.add(30);
        queue.add(10);
        queue.add(20);
 
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}

输出:

10
20
30

因为默认按照从小到大的顺序取出。


PriorityQueue 的特点

1. PriorityQueue 是队列。
2. 它会根据优先级排序。
3. 排序时需要比较元素。
4. 比较时可能调用 compare() 或 compareTo()。

Comparator 和 Comparable

PriorityQueue 排序时有两种常见方式:

Comparable:
对象自己定义比较规则。
 
Comparator:
额外传入一个比较器,专门负责比较两个对象。

例如使用 Comparator

PriorityQueue<Integer> queue = new PriorityQueue<>((a, b) -> b - a);
 
queue.add(10);
queue.add(30);
queue.add(20);
 
System.out.println(queue.poll()); // 30

这里 (a, b) -> b - a 就是自定义比较规则。


PriorityQueue 和反序列化的关系

PriorityQueue 在反序列化恢复数据时,需要重新维护队列的排序结构。

排序时会触发比较逻辑,也就是:

compare()
compareTo()

所以反序列化中经常关注:

PriorityQueue → Comparator.compare()
PriorityQueue → 元素.compareTo()

大致流程:

反序列化 PriorityQueue

恢复队列中的元素

重新排序

调用 compare() / compareTo()

触发后续 gadget 链

所以看到 PriorityQueue,要想到:

它可能通过排序逻辑自动触发 compare()。

TreeMap

概念

TreeMap 是一种有序的双列集合。

它也是存储 key-value,但是会按照 key 的顺序进行排序。

TreeMap<String, Integer> map = new TreeMap<>();

例子:

import java.util.TreeMap;
 
public class Main {
    public static void main(String[] args) {
        TreeMap<String, Integer> map = new TreeMap<>();
 
        map.put("c", 3);
        map.put("a", 1);
        map.put("b", 2);
 
        System.out.println(map);
    }
}

输出:

{a=1, b=2, c=3}

因为 TreeMap 会按照 key 排序。


TreeMap 的特点

1. TreeMap 是双列集合。
2. key 不能重复,value 可以重复。
3. TreeMap 会按照 key 排序。
4. 排序时会调用 compare() 或 compareTo()。

TreeMap 和 HashMap 的区别

HashMap:
无序,重点关注 hashCode() 和 equals()。
 
TreeMap:
有序,重点关注 compare() 和 compareTo()。

所以可以这样记:

HashMap 靠 hash 存数据。
TreeMap 靠排序存数据。

TreeSet

概念

TreeSet 是一种有序的单列集合。

TreeSet<Integer> set = new TreeSet<>();

例子:

import java.util.TreeSet;
 
public class Main {
    public static void main(String[] args) {
        TreeSet<Integer> set = new TreeSet<>();
 
        set.add(30);
        set.add(10);
        set.add(20);
 
        System.out.println(set);
    }
}

输出:

[10, 20, 30]

TreeSet 的特点

1. TreeSet 是单列集合。
2. 元素不能重复。
3. 元素会自动排序。
4. 排序时会调用 compare() 或 compareTo()。

TreeSetTreeMap 的关系类似于:

HashSet 底层和 HashMap 有关。
TreeSet 底层和 TreeMap 有关。

所以:

TreeSet → TreeMap → compare / compareTo