java笔记

规范

类名首字母大写

一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同

数据类型

对于整型类型,Java只定义了带符号的整型,因此,最高位的bit表示符号位(0表示正数,1表示负数)。各种整型能表示的最大范围如下:

类型 位数 最小值 最大值 2 的幂形式
byte 8 bit -128 127 $−2^7∼2^7−1$
short 16 bit -32768 32767 $-2^{15} \sim 2^{15}-1$
int 32 bit -2147483648 2147483647 $-2^{31} \sim 2^{31}-1$
long 64 bit -9223372036854775808 9223372036854775807 $-2^{63} \sim 2^{63}-1$

对于float类型,需要加上f后缀。

Java的char类型除了可表示标准的ASCII外,还可以表示一个Unicode字符:

引用类型类似指针

final修饰变成常量

使用var自动推断变量类型,类似auto,让编译器根据右边的值自动推断变量类型。

与或非运算,|或 ,&与,~非,^异或

引用类型和字符串

null = 没有对象

** = 有一个字符串对象,但里面没有字符

字符串比较技巧,比较常量字符串和变量时可以这样,预防可能为null的情况

1
"-version".equals(arg)

可变参数

相当于数组类型

1
2
3
4
5
6
class Group {
private String[] names;
public void setNames(String... names) {
this.names = names;
}
}

传入null默认为数组类型String[]

1
g.setNames(null); ====  g.setNames((String[]) null);

构造方法

没有在构造方法中初始化字段时,引用类型的字段默认是null,数值类型的字段用默认值,int类型默认值是0,布尔类型默认值是false

一个构造方法可以调用其他构造方法,调用其他构造方法的语法是this(…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person {
private String name;
private int age;

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

public Person(String name) {
this(name, 18); // 调用另一个构造方法Person(String, int)
}

public Person() {
this("Unnamed"); // 调用另一个构造方法Person(String)
}
}

继承 is

在Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super()

子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的

sealed 用于限制一个类或接口允许哪些类型继承或实现

1
2
3
public sealed class Shape
permits Circle, Rectangle {
}

表示 Shape 只允许 CircleRectangle 继承。

直接继承 Shape 的子类必须声明为以下三种之一:

  • final:不能再被继承
  • sealed:继续限制它的子类
  • non-sealed:解除限制,允许任意类继承

向下转型,向上转型

1
2
Student s = new Student();
Person p = new Student();

向上转型必须是子类继承父类

1
2
3
4
Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error!

强制向下转型必须保证本来就是子类类型,加instanceof可以避免转换出错

判断instanceof后,可以直接避免再次强制转型。例如,对于以下代码:

1
2
3
4
5
Object obj = "hello";
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.toUpperCase());
}
1
2
3
4
5
Object obj = "hello";
if (obj instanceof String s) {
// 可以直接使用变量s:
System.out.println(s.toUpperCase());
}

组合 has

1
2
3
4
class Student extends Person {
protected Book book;
protected int score;
}

对于多态

编译时看左边,运行时看右边

1
2
3
4
5
6
Animal a = new Dog();
^^^^^^ ^^^^^^^^^
左边 右边

编译时类型 实际对象类型
Animal Dog
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Animal {
void eat() {
System.out.println("Animal eat");
}
}

class Dog extends Animal {
@Override
void eat() {
System.out.println("Dog eat");
}

void bark() {
System.out.println("汪汪");
}
}
1
2
3
4
Animal a = new Dog();

a.eat(); // 可以
a.bark(); // ❌ 编译错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
【编译阶段】                            
看左边 Animal

Animal 有 eat() 吗?



编译通过


【运行阶段】
看实际对象 new Dog()

Dog 重写 eat() 了吗?



执行 Dog.eat()

抽象类,接口类

在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface

default

假设原来有一个接口:

1
2
3
interface Person {
String getName();
}

并且已经有很多实现类:

1
2
3
class Student implements Person { }
class Teacher implements Person { }
class Worker implements Person { }

如果直接新增一个抽象方法:

1
2
3
4
interface Person {
String getName();
void run();
}

所有实现类都必须立即实现 run(),否则无法编译。

如果新增为默认方法:

1
2
3
4
5
6
7
interface Person {
String getName();

default void run() {
System.out.println(getName() + " run");
}
}

原来的实现类不需要修改就能继续使用;只有需要特殊行为的类才重写 run()

default方法和抽象类的普通方法是有所不同的。因为interface没有字段,default方法无法访问字段,而抽象类的普通方法可以访问实例字段。

关于包

包没有父子关系。java.util和java.util.zip是不同的包,两者没有任何继承关系。

匿名类

  • 没有类名的类,定义类的同时立刻创建对象。
  • 语法:
1
2
3
new 父类或接口() {
// 类体
}
  • 可以:
    • 实现接口
    • 继承一个类
    • 重写方法
    • 定义字段、方法、实例初始化块
  • 匿名类没有名字,所以不能像普通类一样单独重复 new
  • 匿名类可以访问外部类成员,包括 private 成员。
  • 访问外部类同名成员时可用:
1
Outer.this.xxx
  • 例如:
1
2
3
4
5
6
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};

模块

概念 全称 / 文件形式 是什么 主要作用 和其他概念的关系
module-info.java 模块描述文件 用来描述一个 Java 模块 使用 requires 声明依赖,使用 exports 开放包 它只是描述模块,不是模块本身
JAR .jar Java 最常见的打包格式 打包 .class、资源文件、第三方库、应用程序等 JAR 也可以包含 module-info.class,成为模块化 JAR
JRE Java Runtime Environment Java 程序的运行环境 提供 JVM 和运行 Java 程序需要的标准库 Java 程序最终运行在 JRE/JVM 提供的环境中
1
2
3
4
5
6
7
8
9
10
11
java.base.jmod        ← 一个 JMOD 文件

java.base ← 一个模块
├── java.lang ← 包
│ ├── String
│ └── Object
├── java.util ← 包
│ ├── ArrayList
│ └── HashMap
└── java.io ← 包
└── File
1
2
3
4
5
6
7
Java 程序 / JAR

JRE

JVM

操作系统

字符串类

结论:两个字符串比较,必须总是使用equals()方法。

trim()并没有改变字符串的内容,而是返回了一个新字符串。

另一个strip()方法也可以移除字符串首尾空白字符,类似中文的空格字符\u3000/也会被移除:

Java核心类

创建新对象时,优先选用静态工厂方法而不是new操作符。

1
2
3
4
5
BigDecimal 金额:
new BigDecimal("0.9") ✅
BigDecimal.valueOf(0.9) ✅

new BigDecimal(0.9) ❌ 尽量不要

instanceof

instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。

1
2
3
4
Number n = new Integer(...);
编译时类型 运行时类型
↓ ↓
Number Integer
1
Number n = Integer.valueOf(100);

这里有三件事要分开:

1
2
3
变量名:          n
变量的声明类型: Number
实际对象类型: Integer
1
2
3
4
n instanceof Integer   // true
n instanceof Number // true

n.getClass() // Integer.class

java bean

有很多class的定义都符合这样的规范:

  • 若干private实例字段;
  • 通过public方法来读写实例字段。

例如:

1
2
3
4
5
6
7
8
9
10
public class Person {
private String name;
private int age;

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

public int getAge() { return this.age; }
public void setAge(int age) { this.age = age; }
}

如果读写方法符合以下这种命名规范:

1
2
3
4
// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)

那么这种class被称为JavaBean

异常

  • Checked Exception 必须处理:要么 catch,要么 throws
  • catch我在这里处理异常
  • throws我不处理,交给调用我的方法处理
  • throw真正抛出一个异常对象
  • 异常会沿着调用栈向上传递:
1
2
3
4
5
6
7
getBytes()
↓ 抛异常
toGBK()
↓ throws
main()
↓ catch
处理异常
关键字 作用 写在哪
throw 真正抛出一个异常对象 方法体里面
throws 声明这个方法可能抛异常 方法声明后面
1
2
3
4
5
void test(int age) throws Exception {
if (age < 0) {
throw new Exception("年龄不能小于0");
}
}

多个 catch:子类异常放前面,父类异常放后面;否则编译不过。

捕获异常后再次抛出时,尽量保留原始异常作为 cause。

1
2
3
catch (NullPointerException e) {
throw new IllegalArgumentException(e);
}
知识点 作用
throw new XxxException() 主动抛异常
异常传播 当前方法不捕获 → 向调用者传播
printStackTrace() 打印完整异常调用栈
new XxxException(e) 抛新异常并保存原异常
getCause() 获得原始异常
finally 即使 catch 再抛异常,一般仍执行
finally 抛异常 可能屏蔽原本异常,应避免
addSuppressed() 保存被屏蔽的异常
getSuppressed() 获取被屏蔽的异常

image-20260909162547999

1
2
3
4
5
6
catch 能不能捕获异常
不是看它们“都是 RuntimeException”

而是看:
抛出的异常是不是 catch 类型本身
或者 catch 类型的子类。

JDK Logging

  1. 用日志代替 System.out.println()

  2. 使用 java.util.logging.Logger

  3. 创建Logger:
    Logger.getGlobal()
    Logger.getLogger(ClassName.class.getName())

  4. 常用日志:
    logger.severe()
    logger.warning()
    logger.info()
    logger.fine()

  5. 日志级别:
    SEVERE
    WARNING
    INFO
    CONFIG
    FINE
    FINER
    FINEST

  6. 默认一般是INFO:
    INFO及以上显示
    FINE等较低级别默认不显示

  7. 日志可以:

    • 自动记录时间、类、方法
    • 设置级别
    • 输出到文件
    • 通过配置控制输出
  8. 异常处理中可以使用logger记录错误

Java第三方日志

  1. Commons Logging

    • 日志接口/门面
    • LogFactory创建Log
    • Log负责写日志
    1
    2
    3
    4
    5
    6
    7
    static final Log log =
    LogFactory.getLog(Main.class);

    log.info()
    log.warn()
    log.error()
    log.debug()

    异常:

    1
    log.error("发生异常", e);
  2. Log4j

    • 具体日志实现

    • 可作为Commons Logging背后的实现

    • 主要组件:
      Appender:输出位置
      Filter:过滤日志
      Layout:日志格式

    • log4j2.xml配置

    • 可以输出:
      Console
      File
      Socket
      JDBC

    • 支持日志文件滚动

  3. SLF4J

    • 日志接口/门面
    • 类似Commons Logging
    1
    2
    3
    4
    5
    6
    7
    static final Logger logger =
    LoggerFactory.getLogger(Main.class);

    logger.info()
    logger.warn()
    logger.error()
    logger.debug()
    • 支持{}占位符:

      1
      logger.info("user={}, age={}", user, age);
  4. Logback

    • 具体日志实现
    • 通常配合SLF4J
    • logback.xml进行配置
    • 可输出控制台、文件等

关系:

1
2
3
Commons Logging → Log4j

SLF4J → Logback

最重要思想:代码面向日志接口,具体日志实现通过依赖和配置决定。

1
2
3
4
5
6
7
       日志接口 / 门面

Commons Logging SLF4J
↓ ↓
日志实现

Log4j Logback

反射

通过Class实例获取class信息的方法称为反射(Reflection);

动态代理

动态代理主要用于在不修改原有业务类代码的情况下,在运行时为对象的方法统一增加额外功能,例如日志、事务、权限校验、性能统计等。它避免了为每个目标类手动编写代理类,同时实现了业务逻辑和通用功能的解耦。