Java2.0

Java 2026-08-27 59
预计阅读时间:21 分钟

Java基础

一、基本数据类型

  • 整数类型:byte,short,int,long
  • 浮点数类型:float,double
  • 字符类型:char
  • 布尔类型:boolean

各种整型能表示的最大范围:

  • byte:-128 ~ 127(2⁷ − 1)
  • short: -32768 ~ 32767(2¹⁵ − 1)
  • int: -2147483648 ~ 2147483647(2³¹ − 1)
  • long: -9223372036854775808 ~ 9223372036854775807(2⁶³ − 1)
// 定义整型
public class Main {
    public static void main(String[] args) {
        int i = 2147483647;
        int i2 = -2147483648;
        int i3 = 2_000_000_000; // 加下划线更容易识别
        int i4 = 0xff0000; // 十六进制表示的16711680
        int i5 = 0b1000000000; // 二进制表示的512

        long n1 = 9000000000000000000L; // long型的结尾需要加L
        long n2 = 900; // 没有加L,此处900为int,但int类型可以赋值给long
        int i6 = 900L; // 错误:不能把long型赋值给int
    }
}

浮点数能表示的范围虽大,但其精度是有限的。float 通常能保证约 6-7 位有效十进制数字,double 则能保证约 15-16 位

float f1 = 3.14f;
float f2 = 3.14e38f; // 科学计数法表示的3.14x10^38
float f3 = 1.0; // 错误:不带f结尾的是double类型,不能赋值给float

double d = 1.79e308;
double d2 = -1.79e308;
double d3 = 4.9e-324; // 科学计数法表示的4.9x10^-324

除了上述基本类型的变量,剩下的都是引用类型


二、位运算

  • <<运算会将一个数的二进制左移补0
  • >>运算会将一个数的二进制按照符号右移,正数补0,负数补1
  • >>>运算会将一个数的二进制右移补0,不分符号,这意味着会把负数移位成正数
    • 此外,还提供了常规的按位布尔运算& | ~ ^

三、作用域

访问修饰符 同一个类 同一个包 不同包的子类 任何地方
private ✅ ❌ ❌ ❌
default(无修饰符) ✅ ✅ ❌ ❌
protected ✅ ✅ ✅ ❌
public ✅ ✅ ✅ ✅

1.public 类 — 完全公开

// 文件:com/example/Utils.java
package com.example;

public class Utils {
    // 任何地方都能访问这个类
}

2. default类 — 仅包内可见

// 文件:com/example/internal/Helper.java
package com.example.internal;

class Helper {   // 无 public,包内可见
    // 只能被 com.example.internal 下的类访问
}

四、模块

命令 作用 产物
javac 编译源代码 .class 字节码
jar 打包/分发 .jar 可执行包
jmod 转为模块格式 .jmod(给 jlink 用)
jlink 裁剪 JDK,制作定制 JRE 迷你 JRE 文件夹

javac 编译,jar 分发,jlink 裁剪——普通开发用前两个,做精简部署才需要第三个。

五、枚举类

如果希望定义一些枚举常量,可以使用枚举类enum

public enum Color {
    RED, GREEN, BLUE;
}

每个变量都是一个Color类型的实例

实际上,编译器编译出的class大概就像这样:

public final class Color extends Enum { // 继承自Enum,标记为final class
    // 每个实例均为全局唯一:
    public static final Color RED = new Color();
    public static final Color GREEN = new Color();
    public static final Color BLUE = new Color();
    // private构造方法,确保外部无法调用new操作符:
    private Color() {}
}
  • 定义的每个实例都是引用类型的唯一实例,因此可以使用==进行比较
  • 可以将enum类型用于switch语句

这些实例有一些常用方法

  1. name()

    返回常量名,例如:

    java String s = Weekday.SUN.name(); // "SUN"

  2. ordinal()

    返回定义的常量的顺序,从0开始计数,例如:

    java int n = Weekday.MON.ordinal(); // 1

    改变枚举常量定义的顺序就会导致ordinal()返回值发生变化。


实际上,可以自行编写构造方法和实例方法

enum Weekday {
    MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日");

    public final int dayValue;
    private final String chinese;

    private Weekday(int dayValue, String chinese) {
        this.dayValue = dayValue;
        this.chinese = chinese;
    }

    @Override
    public String toString() {
        return this.chinese;
    }
}

并且enum类天然适配switch

六、记录类

如果希望定义一个类,有若干不变量private final ,可以使用记录类record

record Point(int x, int y) {}

对于自定义类,需要覆写equals()和hashCode()方法。而记录类替我们完成了

相当于以下代码:

final class Point extends Record {
    private final int x;
    private final int y;

        //原始构造方法
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() {
        return this.x;
    }

    public int y() {
        return this.y;
    }

    public String toString() {
        return String.format("Point[x=%s, y=%s]", x, y);
    }

    public boolean equals(Object o) {
        ...
    }
    public int hashCode() {
        ...
    }
}

编译器自动创建了构造方法,和字段名同名的方法,以及覆写toString()、equals()和hashCode()方法。

也可以自行给记录类写构造方法和静态方法

七、常用工具类

1. Math

Math类提供了大量的静态方法便于实现数学计算

求绝对值:

Math.abs(-100); // 100
Math.abs(-7.8); // 7.8

取最大或最小值:

Math.max(100, 99); // 100
Math.min(1.2, 2.3); // 1.2

计算xy次方:

Math.pow(2, 10); // 2的10次方=1024

计算 xx:

Math.sqrt(2); // 1.414...

计算ex次方:

Math.exp(2); // 7.389...

计算以e为底的对数:

Math.log(4); // 1.386...

计算以10为底的对数:

Math.log10(100); // 2

三角函数:

Math.sin(3.14); // 0.00159...
Math.cos(3.14); // -0.9999...
Math.tan(3.14); // -0.0015...
Math.asin(1.0); // 1.57079...
Math.acos(1.0); // 0.0

Math还提供了几个数学常量:

double pi = Math.PI; // 3.14159...
double e = Math.E; // 2.7182818...
Math.sin(Math.PI / 6); // sin(π/6) = 0.5

生成一个随机数x,x的范围是0 <= x < 1:

Math.random(); // 0.53907... 每次都不一样

2. BigInteger

long基本类型提供最大2^64-1 。如果需要计算的数超过此界限,则需要使用BigInteger 。

BigInteger 采用数组模拟大数,这让它可以模拟任意大的数。缺点是速度较慢

  • 对BigInteger 进行运算,只能用它提供的实例方法
  • 可以把BigInteger 转化为基本类型

    • 转换为long:longValue()
    • 转换为byte:byteValue()
    • 转换为short:shortValue()
    • 转换为int:intValue()
    • 转换为long:longValue()
    • 转换为float:floatValue()
    • 转换为double:doubleValue()

    转型时超过会丢失高位信息

    而使用longValueExact()等方法转型,则会抛出ArithmeticException

3. BigDecimal

和BigInteger类似,BigDecimal可以表示一个任意大小且精度完全准确的浮点数。BigDecimal也是从Number继承的,也是不可变对象。

BigDecimal bd = new BigDecimal("123.4567");
System.out.println(bd.multiply(bd)); // 15241.55677489
  • 实例方法scale()表示小数位数。返回负数,例如-2,则表示这个数是个整数,并且末尾有2个0。
  • stripTrailingZeros()方法,可以将一个BigDecimal格式化为一个相等的,但去掉了末尾0的BigDecimal
  • setScale() 方法可以设置小数位数

    java BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 四舍五入,123.4568 BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // 直接截断,123.4567 System.out.println(d2);

对BigDecimal做加减乘运算,精度不会丢失,但是做除法,如果出现无法除尽的情况,就必须指定精度以及如何进行截断

BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("23.456789");
BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); // 保留10位小数并四舍五入
BigDecimal d4 = d1.divide(d2); // 报错:ArithmeticException,因为除不尽

在比较两个BigDecimal的值是否相等时,要特别注意,使用equals()方法不但要求两个BigDecimal的值相等,还要求它们的scale()相等:

方法 比较内容
compareTo() 数值大小(忽略末尾的 0)
equals() 数值大小 + 精度(小数位数)
BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("123.45600");
System.out.println(d1.equals(d2)); // false,因为scale不同
System.out.println(d1.equals(d2.stripTrailingZeros())); // true,因为d2去除尾部0后scale变为3
System.out.println(d1.compareTo(d2)); // 0 = 相等, -1 = d1 < d2, 1 = d1 > d2

八、异常

Java标准库定义的常用异常包括:

Exception
├─ RuntimeException    // 非受检异常,不强制要求捕获
│  ├─ NullPointerException        // 在一个null上调用方法或者访问属性
│  ├─ IndexOutOfBoundsException   // 访问数组,字符串或集合索引<0或>=长度
│  ├─ SecurityException           // 
│  └─ IllegalArgumentException    // 调用方法时,传入的参数不符合方法内部的约定
│     └─ NumberFormatException    // 试图将字符串转换为数值,但字符串内容不是合法数字
├─ IOException         // 受检异常,强制要求捕获
│  ├─ UnsupportedCharsetException // 使用了JVM不认识的字符编码名称。
│  ├─ FileNotFoundException       // 试图打开不存在的文件进行操作
│  └─ SocketException   // 通常是网络无法连接、连接被重置(对方突然断开)或网络不可达。
├─ ParseException                 // 文本解析失败
├─ GeneralSecurityException       // 加密解密、数字签名等操作出错
├─ SQLException                   // 访问数据库时发生错误
└─ TimeoutException               // 多线程或异步操作等待结果时,超过了设定的最大等待时间

对于大型项目,可以自定义异常,但是一般JDK已定义的异常就足够

详见

自定义异常 - Java教程 - 廖雪峰的官方网站

Commons Logging

Commons Logging是一个第三方日志库,由Apache创建的日志模块。

依赖:

<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.4.0</version>
</dependency>

创建log实例

Log log = LogFactory.getLog(Main.class);
// 可以传入类,也可以传入字符串,指定该日志的名称
// 传入getClass()可以方便的获取当前类名

Commons Logging定义了6个日志级别:

  • FATAL
  • ERROR
  • WARNING
  • INFO
  • DEBUG
  • TRACE

默认级别是INFO。INFO以下的默认不会被打印

常用log.error(String)和log.error(String, Throwable)打印异常。

九、反射

JVM为每个加载的class以及interface创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等

三种方法获取一个类的Class实例

  • 直接通过一个class的静态变量class获取:

    java Class cls = String.class;

  • 通过某实例变量提供的getClass()方法获取:

    java String s = "Hello"; Class cls = s.getClass();

  • 如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:

    java Class cls = Class.forName("java.lang.String");

每个Class实例在JVM中是唯一的

// 获取一个Class的信息
static void printClassInfo(Class cls) {
      System.out.println("Class name: " + cls.getName());
      System.out.println("Simple name: " + cls.getSimpleName());
      if (cls.getPackage() != null) {
          System.out.println("Package name: " + cls.getPackage().getName());
      }
      System.out.println("is interface: " + cls.isInterface());
      System.out.println("is enum: " + cls.isEnum());
      System.out.println("is array: " + cls.isArray());
      System.out.println("is primitive: " + cls.isPrimitive());
}

如果获取到了一个Class实例,我们就可以通过该Class实例来创建对应类型的实例:

// 获取String的Class实例:
Class cls = String.class;
// 创建一个String实例:
String s = (String) cls.newInstance();

局限是只能调用public的无参数构造方法

1. 获取字段

getField方法获取public字段

getDeclaredField方法获取private和protected 字段

对于非public字段,获取之后还需使用setAccessible(true)方法设置权限

获取到字段实例之后,可以使用get和set方法获取或修改值,get方法传入需要修改的实例对象,set传入对象和新值

package org.example;

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws Exception {
        Person p=new Student(1,2);
        Class cls=p.getClass();
        // 获取字段
        Field scoreField=cls.getField("score");
        Field gradeField=cls.getDeclaredField("grade");
        gradeField.setAccessible(true);
        gradeField.set(p,3);
        System.out.println(scoreField.get(p));
        System.out.println(gradeField.get(p));
    }
}
class Student extends Person {
    public int score;
    private int grade;
    public Student(int score,int grade){
        this.score=score;
        this.grade=grade;
    }
}

class Person {
    public String name;
}

2. 获取方法

同样的,可以通过Class实例获取所有Method信息。

  • Method getMethod(name, Class...)

    获取某个public的Method,包括父类。

  • Method getDeclaredMethod(name, Class...)

    获取当前类的某个Method,不包括父类。可获取private方法

  • Method[] getMethods()

    获取所有public的Method,包括父类。注意所有类都至少有一个父类Object

  • Method[] getDeclaredMethods()

    获取当前类的所有Method,不包括父类,包括private方法

以上方法都传入需要获取的方法的方法名(字符串),以及对应参数的的CLass 类。

重载方法就是按照传入不同参数的CLass类区分。

同样的,对于非public方法,需要setAccessible(true) 设置权限。


一个Method对象包含该方法的所有信息

  • getName():返回方法名称,例如:"getScore";
  • getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
  • getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。
  • getParameterCount():返回方法的参数个数

获取到方法之后,使用invoke()方法可以执行该方法。传入所要执行方法的实例和对应参数。

如果获取的方法是静态方法,则第一个参数传入null而非所要执行方法的实例

package org.example;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Student p=new Student();
        Class cls=p.getClass();

        // 测试public
        Method m1=cls.getMethod("publicF");
        m1.invoke(p);

                // 测试重载public
        Method m11=cls.getMethod("publicF",String.class);
        m11.invoke(p,"hello");

        // 测试private和protected
        Method m2=cls.getDeclaredMethod("privateF");
        m2.setAccessible(true);
        m2.invoke(p);
        Method m3=cls.getDeclaredMethod("protectedF");
        m3.setAccessible(true);
        m3.invoke(p);

        System.out.println("==========");
        // 测试多方法获取
        Method[] ms=cls.getDeclaredMethods();
        for (Method m:ms){
            m.setAccessible(true);   // 处理多方法获取到的非public方法
            if(m.getParameterCount()==0){  // 处理无参方法
                m.invoke(p);
            } else {                   // 有参方法
                m.invoke(p,"hello world");
            }
        }
    }
}

class Student{
    public Student(){}
    // public方法
    public void publicF(){
        System.out.println("publicF");
    }
    // 重载public方法
    public void publicF(String s){
        System.out.println(s);
    }
    // 多个public方法
    public void publicG(){
        System.out.println("publicG");
    }
    // private方法
    private void privateF(){
        System.out.println("privateF");
    }
    // protected方法
    protected void protectedF(){
        System.out.println("protectedF");
    }
}
publicF
hello
privateF
protectedF
==========
publicG
hello world
publicF
protectedF
privateF

反射获取的方法的调用依旧遵循多态

3. 构造方法

前文提到过newInstance()构造有局限。为此使用Constructor对象。

通过Class实例获取Constructor的方法如下:

  • getConstructor(Class...):获取某个public的Constructor;
  • getDeclaredConstructor(Class...):获取某个Constructor;

上述两个方法均传入该构造所需的参数类型的Class类。

不同于普通方法需要首先传入方法名,构造方法只由参数数量类型区分,所以无需传入名。

  • getConstructors():获取所有public的Constructor;
  • getDeclaredConstructors():获取所有Constructor。

Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。 同样,非public的Constructor时,需要setAccessible(true)设置允许访问。


获取Constructor 实例后,使用newInstance() 方法即可构造新实例,传入相应的参数,并且前置转型。

4. 获取接口

有了Class实例,就可获取它的父类的Class ,使用getSuperclass()方法。

除Object类的父类是null外,其他所有类都有父类。


一个类可能实现一个或多个接口,使用getInterfaces() 方法获取该类(不包含父类)实现的所有接口。

返回的接口也是Class类型

package org.example;

public class Main {
    public static void main(String[] args) throws Exception {
        Calculator calculator=new Calculator(1,2);
        Class cls=calculator.getClass();

        Class[] interfazes=cls.getInterfaces();
        for (Class interfaze:interfazes){
            System.out.println(interfaze);
            System.out.println(interfaze.isInterface());
        }
    }
}
interface Calculator1{
    int add(int a,int b);
    int sub(int a,int b);
}
interface Calculator2{
    int mul(int a,int b);
    int div(int a,int b);
}
class Calculator implements Calculator1,Calculator2{
    public int a;
    public int b;
    public Calculator(){}
    public Calculator(int a,int b){
        this.a=a;
        this.b=b;
    }
    @Override
    public int add(int a, int b) {return 0;}
    @Override
    public int sub(int a, int b) {return 0;}
    @Override
    public int mul(int a, int b) {return 0;}
    @Override
    public int div(int a, int b) {return 0;}
}
interface org.example.Calculator1
true
interface org.example.Calculator2
true

十、注解

1. 元注解

元注解用于修饰注解,自定义注解的时候会用到。通常不需要自己去编写元注解


@Target

@Target传入的参数定义了Annotation能够被应用于源码的哪些位置

  • 类或接口:ElementType.TYPE;
  • 字段:ElementType.FIELD;
  • 方法:ElementType.METHOD;
  • 构造方法:ElementType.CONSTRUCTOR;
  • 方法参数:ElementType.PARAMETER。

如果需要定义在多个地方,可以传入数组{ElementType.METHOD, ElementType.FIELD }

实际上@Target定义的value是ElementType[]数组,只有一个元素时,可以省略数组的写法。


@Retention

@Retention定义了Annotation的生命周期:

  • 仅编译期:RetentionPolicy.SOURCE;
  • 仅class文件:RetentionPolicy.CLASS;
  • 运行期:RetentionPolicy.RUNTIME

Annotation默认为CLASS。但自定义的Annotation通常是RUNTIME,所以务必加上@Retention(RetentionPolicy.RUNTIME)


@Repeatable

@Repeatable定义了Annotation可否在一个地方重复声明。

要使用@Repeatable ,首先要定义一个专门的容器

// 1. 定义可重复注解,@Repeatable 的参数是 Schedules.class
@Repeatable(Schedules.class) 
public @interface Schedule {
    ...
}
// 2. 定义容器注解 Schedules,它必须有一个 value() 方法,
//    返回类型是 Schedule[] (可重复注解的数组)
public @interface Schedules {
    Schedule[] value(); // 这是必需的
}

容器注解没有实质用处,专门为了定义@Repeatable 注解服务


@Inherited

@Inherited定义子类是否可继承父类定义的Annotation 。仅针对@Target(ElementType.TYPE)类型有效。

@Inherited 不需要传入参数。

@Inherited
@Target(ElementType.TYPE)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

在使用的时候,如果一个类用到了@Report:

@Report(type=1)
public class Person {
}

则它的子类默认也定义了该注解:

public class Student extends Person {
}

2. 定义注解

  1. 用@interface语法定义注解:

    java public @interface Report { }

  2. 添加参数、默认值(强烈推荐)

    jsx public @interface Report { int type() default 0; String level() default "info"; String value() default ""; }

    最常用的参数是value

  3. 添加元注解

    jsx @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Report { int type() default 0; String level() default "info"; String value() default ""; }

3. 获取注解

注解有何作用完全由人为定义决定。使用反射读取注解信息

判断某个注解是否存在于Class、Field、Method或Constructor:

  • Class.isAnnotationPresent(Class)
  • Field.isAnnotationPresent(Class)
  • Method.isAnnotationPresent(Class)
  • Constructor.isAnnotationPresent(Class)

以上方法传入的参数是自定义注解的Class

// 判断@Report是否存在于Person类:
Person.class.isAnnotationPresent(Report.class);

判断是否存在之后,就可以读取Annotation:

  • Class.getAnnotation(Class)
  • Field.getAnnotation(Class)
  • Method.getAnnotation(Class)
  • Constructor.getAnnotation(Class)

传入注解Class,返回注解类型实例。如果没有经过存在检测就获取,并且没有此注解,则返回null 。

上述没有提到方法参数注解的获取。这是因为一个方法可有多个参数,每个参数又可存在多个注解,所以获取方法参数的注解需要用一个二维数组。

public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
}
// 多参数,且参数可有多注解
// 获取方法参数的注解步骤

// 1.获取Method实例:
Method m = ...
// 2.获取所有参数的Annotation:
Annotation[][] annos = m.getParameterAnnotations();
// 第一个参数(索引为0)的所有Annotation:
Annotation[] annosOfName = annos[0];
for (Annotation anno : annosOfName) {
    if (anno instanceof Range r) { // @Range注解
        r.max();
    }
    if (anno instanceof NotNull n) { // @NotNull注解
        //
    }
}

4. 使用注解

@Range注解,我们希望用它来定义一个String字段的规则:字段长度满足@Range的参数定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)   // 用于字段
public @interface Range {
    int min() default 0;
    int max() default 255;
}

在某个JavaBean中使用该注解:

public class Person {
    @Range(min=1, max=20)
    public String name;

    @Range(max=10)
    public String city;
}

但只是定义和使用了注解,目前对程序还无影响,还要自行编写注解逻辑。

void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {
    // 遍历所有Field:
    for (Field field : person.getClass().getFields()) {
        // 获取Field定义的@Range:
        Range range = field.getAnnotation(Range.class);
        // 如果@Range存在:
        if (range != null) {
            // 获取Field的值:
            Object value = field.get(person);
            // 如果值是String:
            if (value instanceof String s) {
                // 判断值是否满足@Range的min/max:
                if (s.length() < range.min() || s.length() > range.max()) {
                    throw new IllegalArgumentException("Invalid field: " + field.getName());
                }
            }
        }
    }
}

本文由 tazume-sans 原创,转载请注明出处。

相关推荐