Java知识整理&总结(四) o(* ̄︶ ̄*)o

43 阅读16分钟

十二、面向对象-高级

1. 关键字 static

1.1 static 概念

将类的成员(属性、方法、代码块、内部类)归属于类本身所有,所有实例对象共享同一个 static 成员

也就是说,无论创建多少个对象,共享同一份 static 修饰的类成员(属性、方法、代码块、内部类)

  • static 的含义是 “静态的
  • 被 static 修饰的属性,称之为静态属性
  • 被 static 修饰的方法,称之为静态方法
  • 被 static 修饰的代码块,称之为静态代码块
  • 被 static 修饰的内部类,称之为静态内部类

1.2 static 的特点

  • 随着类的加载而加载(不需要依托于创建对象时才加载)
  • 被 static 修饰的成员,被该类的所有对象共享
  • 在访问权限允许时,可以直接通过类调用

1.3 静态变量

被 static 修饰的属性(变量),称之为静态属性(变量),常用于作为常量

语法格式:

[权限修饰符] static 数据类型 变量名;
  • 静态变量的默认值规则和实例变量一样
  • 静态变量所有对象共享
  • 静态变量的 get/set 方法也静态的,当局部变量与静态变量重名时,可使用 类名.静态变量 进行区分

内存解析:

20260330085750.png

1.4 静态方法

被 static 修饰的方法,称之为静态方法,常用于工具类中的方法

语法格式:

[权限修饰符] static 返回值类型 方法名(形参列表) {
    // 方法体
}
  • 在 static 方法内部 只能访问类的static修饰的属性或方法,不能访问类的非static的结构
  • 静态方法可以被子类继承,但不能被子类重写
  • 静态方法的调用都只看编译时类型

内存解析: 暂无

1.5 静态代码块

类加载时自动执行一次,常用于初始化静态资源、加载配置、连接数据库等

语法格式:

public class Test { 
    // 静态代码块 
    static { 
        System.out.println("类加载时执行,只执行一次"); 
    } 
}

"tips: 类加载顺序中,静态代码块 > 构造代码块 > 构造方法"

1.6 静态内部类

静态内部类 不需要创建外部类对象 就能使用,但只能访问外部类的 静态成员

语法格式:

public class Outer { 
    static int num = 100; 
    
    // 静态内部类 
    static class Inner { 
        public void show() { 
            System.out.println(num); 
        } 
    } 
}

1.7 拓展知识点

  • 静态方法能不能重写? 不能,静态方法是 属于类 的,重写是针对对象的多态
  • 静态变量存在哪里? JDK8 之前在方法区,JDK8+ 在 堆内存 的类元数据中
  • main 方法为什么是 static? 程序启动时还没有对象,必须用 static 让 JVM 直接调用

2. 关键字 final

含义为:最终的,不可修改的

  • 修饰类时,表示这个类不能被继承,没有子类。提高安全性,提高程序的可读性
  • 修饰变量时,一旦赋值,它的值就不能被修改
  • 修饰方法时,表示这个方法不能被子类重写

3. 关键字 abstract

含义为:抽象的

  • 使用abstract修饰的类称之为 抽象类,表明该类不需要被实例化
  • 使用abstract修饰的方法称之为 抽象方法,表明该方法不需要具体的方法体,必须子类实现
  • 抽象类中可以有抽象方法和普通方法,但抽象方法只能声明在抽象类中

简单来说,抽象类通常是一个 不需要被实例化的父类,抽象方法通常是一个不需要具体方法体的父类方法

语法格式:

// 抽象类
[权限修饰符] abstract class 类名{
    // 抽象方法
    public abstract void test();
}

特点:

  • 抽象类 不能创建对象,只能创建其 非抽象子类 的对象
  • 抽象类中,也 有构造方法,是供子类创建对象时,初始化父类成员变量使用的
  • 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
  • 抽象类的子类,必须重写抽象父类中 所有的 抽象方法(抽象类继承抽象类时,无须遵循此条)
  • 不能用abstract修饰 变量、代码块、构造器
  • 不能用abstract修饰 私有方法、静态方法、final的方法、final的类

4. 关键字 interface

4.1 概念

接口(interface)是一种 完全抽象 的引用类型,是方法的集合(Java 8+ 支持默认方法、静态方法),核心作用是 定义规范

可以把接口理解为 规定 :规定接口(interface)的实现类必须做什么,具体做什么由实现类自己决定

  • 接口是完全抽象的规范,核心是用 implement 实现
  • 普通类实现接口时,必须 实现所有抽象方法
  • 接口与实现类支持多实现,接口与接口支持多继承
  • Java8 以后允许接口存在默认方法,可以直接继承或重写;允许存在静态方法,可通过接口直接调用

4.2 特点

  • 无法实例化:接口不能直接 new 对象

  • 多继承(多实现):一个类可以实现多个接口(可弥补 Java 单继承的缺陷)

  • 访问修饰符:接口中的方法默认是 public abstract、变量默认是public static final,可省略不写

  • 实现规则:普通类实现接口,必须重写所有抽象方法

4.3 语法格式

// 接口定义关键字:interface 
public interface Animal { 
    // 常量:默认 public static final,必须赋值 
    String TYPE = "动物"; 
    // 抽象方法:默认 public abstract,没有方法体 
    void eat(); 
    void sleep(); 
    
    // Java 8+ 默认方法:有方法体,实现类可直接使用/重写 
    default void breathe() { 
        System.out.println("用氧气呼吸"); 
    } 
    // Java 8+ 静态方法:接口直接调用,实现类不能继承 
    static void info() { 
        System.out.println("这是动物接口"); 
    } 
}

4.4 接口的实现 implements

关键字:implements

一个类可以 同时继承一个父类 + 实现多个接口(用逗号分隔)

// 实现接口:必须重写所有抽象方法 
public class Dog implements Animal { 
    // 重写抽象方法(必须加 public)
    @Override 
    public void eat() { 
        System.out.println("狗吃骨头"); 
    } 
    
    @Override 
    public void sleep() { 
        System.out.println("狗趴着睡"); 
    } 
}

// 第二个接口 
interface Run { 
    void run(); 
} 

// 一个类实现多个接口 
public class Cat implements Animal, Run { 
    @Override 
    public void eat() { 
        System.out.println("猫吃鱼"); 
    } 
    
    @Override 
    public void sleep() { 
        System.out.println("猫躺着睡"); 
    } 
    
    @Override 
    public void run() { 
        System.out.println("猫快速跑"); 
    } 
}

4.5 接口的多继承

接口(interface)直接是允许多继承的

interface Bird extends Animal,Fly { 
    void sing(); 
}

4.6 抽象类和接口的区别

特性接口(Interface)抽象类(Abstract Class)
继承方式implements,可多实现extends单继承
成员变量只能是 public static final 常量支持任意类型变量
构造方法无构造方法有构造方法
方法Java8+:抽象、默认、静态、私有方法抽象方法、普通方法
核心用途定义规范 / 行为定义模板 / 共性

4.7 接口的常用场景

  • 定义统一规范:比如所有动物必须实现 eat()sleep()

  • 实现多继承:Java 类只能继承一个父类,用 接口 弥补缺陷

  • 解耦代码:面向接口编程,降低模块间的依赖

  • 框架设计:Spring、MyBatis 等大量使用接口做扩展

5. 内部类

5.1 概念

定义在一个类内部的类,称之为内部类,其所在的类就成为外部类

  • 成员内部类(普通)
  • 静态内部类(嵌套)
  • 局部内部类(方法中)
  • 匿名内部类(最常用,Lambda的前身)

5.2 成员内部类

定义在类中、方法外依赖外部类对象

class Outer { 
    private int a = 10; 
    
    // 成员内部类 
    class Inner { 
        void test() { 
        // 可以直接访问外部类私有成员 
        System.out.println(a); 
        } 
    } 
}

// 创建方式
Outer outer = new Outer(); 
Outer.Inner inner = outer.new Inner();
  • 内部类持有 外部类.this 引用
  • 不能定义 static 成员(除 static final 常量)
  • 可访问外部所有成员(包括 private)

5.3 静态内部类

static 修饰,不依赖外部对象

class Outer {
    private static int a = 10; 
    
    static class Inner { 
        void test() { 
            System.out.println(a); 
            // 只能访问外部静态成员 
        } 
    }
}

// 创建方式
Outer.Inner inner = new Outer.Inner();
  • 最常用、最推荐(无内存泄漏风险)
  • 不持有外部类引用
  • 可包含任意 static / 非 static 成员

5.4 局部内部类

定义在 方法 / 代码块 内部

class Outer { 
    void test() { 
        int num = 20;
        
        // 局部内部类 
        class Inner { 
            void f() { 
                System.out.println(num); 
            } 
        } 
    }
}
  • 作用域仅限当前方法
  • 只能访问方法中 final / 有效 final 变量
  • 极少用

5.5 匿名内部类(重)

没有类名,一次性使用,常用于创建接口 / 抽象类对象

// 接口 
interface Runnable { 
    void run(); 
} 

// 匿名内部类实现接口 
Runnable r = new Runnable() { 
    @Override 
    public void run() { 
        System.out.println("run"); 
    } 
};

// jdk8+ j简化Lambda表达式
Runnable r = () -> System.out.println("run");

场景:

  • 点击事件
  • 线程创建
  • 集合排序
  • 接口回调

5.6 各内部类的核心区别(以及 effectively final 解释)

类型依赖外部对象?能否 static 成员访问外部
成员内部类不能所有成员
静态内部类仅静态成员
局部内部类不能方法内 final 变量
匿名内部类不能同局部
  • 成员内部类会持有外部类引用 → 容易内存泄漏

  • 静态内部类 不会持有外部引用,最安全

  • 匿名内部类访问局部变量必须是 effectively finalEffectively final‌ 指的是:‌虽然没有显式使用 final 关键字修饰,但在初始化后从未被重新赋值的局部变量‌,编译器会将其视为 final 变量处理,允许在 Lambda 或匿名内部类中安全引用)

特性final 变量effectively final 变量
声明方式必须显式加 final 关键字不加 final,但实际未被修改
适用范围实例变量、静态变量、局部变量仅限局部变量(含方法参数)
是否可修改一旦赋值,不可更改初始化后不能重新赋值,否则不再是 effectively final
编译器处理明确禁止修改编译器自动判断并隐式视为 final

6. 枚举 Enum(重)

enum 是一种特殊的类,用来表示固定、有限、确定的一组值

比如:季节、性别、状态、订单状态、颜色、方向等

优点:

  • 代替大量常量(public static final
  • 类型安全,不会传错值
  • 自带方法,功能强大
  • 可加字段、方法、构造器

特点:

  • 枚举类默认继承 java.lang.Enum
  • 不能继承其他类,但可以实现接口
  • 构造器默认 private,自己写也必须 private(不能外部 new)

语法格式(简单版):

public enum Season { 
    SPRING, SUMMER, AUTUMN, WINTER 
}

Season s = Season.SPRING;
  • 枚举常量默认:public static final
  • 常量名大写,逗号分隔
  • 最后一个分号可省略(有其他内容必须加)

语法格式:(高级版)

public enum OrderStatus { 
    // 枚举实例(必须放在第一行) 
    CREATED(1, "已创建"), 
    PAID(2, "已支付"), 
    SHIPPED(3, "已发货"), 
    FINISHED(4, "已完成"); 
    
    // 成员变量 
    private final int code; 
    private final String desc; 
    
    // 构造器(默认 private,可不写) 
    OrderStatus(int code, String desc) { 
        this.code = code; 
        this.desc = desc; 
    } 
    
    // getter 
    public int getCode() { 
        return code; 
    } 
    public String getDesc() { 
        return desc; 
    } 
}

OrderStatus status = OrderStatus.PAID; 
System.out.println(status.getCode()); // 2 
System.out.println(status.getDesc()); // 已支付

枚举类的常用方法:(自带)

OrderStatus status = OrderStatus.CREATED; 

status.name(); // 返回字符串 "CREATED" 
status.ordinal(); // 返回序号 0(从0开始) 
OrderStatus.values();// 返回所有枚举数组 OrderStatus[] 
OrderStatus.valueOf("PAID"); // 字符串转枚举

枚举类不能继承其他类(默认继承Enum类),但是支持实现接口

interface CodeDesc { 
    int getCode(); 
    String getDesc(); 
} 

enum Status implements CodeDesc { 
    YES(1, "是"), 
    NO(0, "否"); 
    
    private final int code; 
    private final String desc; 
    
    Status(int code, String desc) { 
        this.code = code; 
        this.desc = desc; 
    } 
    
    @Override 
    public int getCode() { 
        return code; 
    } 
    @Override 
    public String getDesc() {
        return desc; 
    }
}

枚举单例(最推荐的单例模式)

使用枚举编写单例模式的功能代码,是 最简单、最安全的,无线程安全问题

public enum Singleton { 
    INSTANCE; 
    
    public void doSomething() {
        // 业务方法 
    } 
}

Singleton.INSTANCE.doSomething();

总结:

  • enum 是类,继承 Enum,不能再继承其他类
  • 枚举实例是 public static final
  • 构造器 只能 private
  • 可包含:成员变量、构造器、普通方法、静态方法
  • 实现接口,不能继承类
  • values()、valueOf()、name()、ordinal() 常用
  • 枚举单例是最佳单例
  • 适合:固有限值(状态、类型、性别等)

7. 注解 Annotation

7.1. 概念

Java 注解(Annotation)是 JDK 5 引入的元数据机制,用于为类、方法、字段等程序元素附加额外信息,不直接参与业务逻辑,但可被编译器、工具或运行时框架读取并处理,核心价值是简化配置、编译期校验、运行时动态逻辑

在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码XML配置等。

未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,Struts2有一部分也是基于注解的了。注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式

注解是 Java 简化开发、提升效率的核心机制,从 JDK 内置注解到框架自定义注解,形成了完整的元数据体系。掌握注解的核心是理解生命周期反射解析,这是开发框架、实现自定义逻辑的基础

注解分类:

生命周期保留策略作用场景典型示例
源码注解SOURCE仅源码有效,编译后丢弃,用于编译检查@Override@SuppressWarnings
编译注解CLASS保留至字节码,运行时不加载,用于字节码处理编译时代码生成工具
运行时注解RUNTIME保留至运行时,通过反射解析,框架核心依赖@Autowired@RequestMapping

7.2 JDK中的三个基本注解

1. @Override
  • 用于检测被标记的方法为有效的重写方法,如果不是,则报编译错误!
  • 只能标记在方法上
  • 它会被编译器程序读取
2. @Deprecated
  • 用于表示被标记的数据已经过时,不推荐使用
  • 可以用于修饰 属性、方法、构造、类、包、局部变量、参数
  • 它会被编译器程序读取
3. @SuppressWarnings
  • 抑制编译警告。当我们不希望看到警告信息的时候,可以使用 SuppressWarnings 注解来抑制警告信息

  • 可以用于修饰类、属性、方法、构造、局部变量、参数

  • 它会被编译器程序读取

  • 可以指定的警告类型有(了解)

    • all,抑制所有警告
    • unchecked,抑制与未检查的作业相关的警告
    • unused,抑制与未用的程式码及停用的程式码相关的警告
    • deprecation,抑制与淘汰的相关警告
    • nls,抑制与非 nls 字串文字相关的警告
    • null,抑制与空值分析相关的警告
    • rawtypes,抑制与使用 raw 类型相关的警告
    • static-access,抑制与静态存取不正确相关的警告
    • static-method,抑制与可能宣告为 static 的方法相关的警告
    • super,抑制与置换方法相关但不含 super 呼叫的警告

7.3 元注解

JDK1.5在java.lang.annotation包定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。简单来说就是,对某个注解进行注释说明的注解

(1)@Target: 用于描述注解的使用范围

  • 可以通过枚举类型ElementType的10个常量对象来指定
  • TYPE,METHOD,CONSTRUCTOR,PACKAGE…..

(2)@Retention: 用于描述注解的生命周期

  • 可以通过枚举类型RetentionPolicy的3个常量对象来指定
  • SOURCE(源代码)、CLASS(字节码)、RUNTIME(运行时)
  • 唯有RUNTIME阶段才能被反射读取到

(3)@Documented:表明这个注解应该被 javadoc工具记录

(4)@Inherited: 允许子类继承父类中的注解

7.4 自定义注解

通过 @interface 关键字定义,结合元注解控制特性,配合反射实现逻辑

// 元注解:指定作用范围和生命周期 
@Target({ElementType.METHOD, ElementType.TYPE}) // 可用于类/方法 
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可反射解析 
@Documented 
public @interface MyAnnotation { 
    // 注解属性:支持基本类型、String、Class、枚举、数组 
    String value() default ""; 
    // 单值属性,默认值可选 
    int age() default 18; 
    String[] tags() default {};
}

@MyAnnotation(value = "测试类", age = 25, tags = {"开发", "注解"}) 
public class AnnotationDemo { 
    @MyAnnotation(value = "测试方法", tags = "实战") 
    public void testMethod() {
        System.out.println("执行测试方法"); 
    }
}

7.5 框架常用注解

  • JDK:基础 + 元注解(@Override、@Target、@Retention)
  • Spring:Bean、AOP、配置(@Service、@Autowired)
  • SpringMVC:Web 接口(@GetMapping、@RequestBody)
  • SpringBoot:自动配置(@SpringBootApplication)

8. 包装类

1. 概念

Java提供了两个类型系统,基本数据类型引用数据类型。使用基本数据类型在于效率,然而当要使用只针对对象设计的API或新特性(例如泛型),怎么办呢?

包装类就是为 每一种基本数据类型 提供的 对应引用类型,把基本类型 “包装” 成对象,让它拥有对象的特性

基本数据类型包装类(java.lang)
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

2. 核心作用

  1. Java 集合(List/Map/Set)只能存对象,不能存基本类型
    List<int> list = new ArrayList<>(); // 报错!
    List<Integer> list = new ArrayList<>(); // 正确
    
  2. 包装类提供了大量实用方法(字符串转数字、最大值、最小值等)
  3. 支持泛型(泛型必须用包装类)
  4. 可以赋值为 null(基本类型不能为 null,包装类可以)

3. 自动装箱 & 自动拆箱(JDK 5+ 特性)

  • 自动装箱:基本类型 → 包装类(编译器自动完成)
  • 自动拆箱:包装类 → 基本类型(编译器自动完成)

代码示例:

// 自动装箱:int → Integer
Integer a = 10; 

// 自动拆箱:Integer → int
int b = a;     

// 混合运算也会自动拆箱
Integer c = 20;
int sum = a + c; // 30

4. 包装类常用方法

  1. 字符串 → 基本类型
String str = "123";
int num = Integer.parseInt(str); // 123
double d = Double.parseDouble("3.14");

2. 基本类型 → 字符串

String s1 = Integer.toString(123);
String s2 = String.valueOf(456);

3. 获取常量(最大值 / 最小值)

System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Integer.MIN_VALUE); // -2147483648

5. 重点坑:== 比较 Integer 缓存机制

Integer 默认缓存 -128 ~ 127 之间的数字,这个范围的数字用 == 比较是相等的;超出范围会创建新对象,== 比较地址就不相等

代码示例:

Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true(在缓存里)

Integer x = 200;
Integer y = 200;
System.out.println(x == y); // false(超出缓存,新对象)

正确比较方式:所有包装类都用 equals()

System.out.println(x.equals(y)); // true

6. 基本类型 vs 包装类

对比项基本类型包装类
类型基本数据类型引用类型
存储位置栈(高效)堆(对象)
默认值0 /false 等null
能否存 null不能
集合能否使用不能
比较== 比较值== 比较地址,equals 比较值

7. 总结

  • 包装类是 基本类型的对象版,解决基本数据类型不是对象的问题
  • 8 种基本类型对应 8 个包装类,重点记 IntegerCharacter
  • 自动装箱 / 拆箱 让基本类型和包装类可以直接互相赋值
  • 比较包装类 必须用 equals () ,不要用 ==
  • 集合、泛型只能用 包装类

image.png