java教程笔记(七)-类变量,代码块,抽象类,接口,内部类

1.类变量

在 Java 中,类变量(Class Variables) 是指用 static 关键字修饰的成员变量。它们属于类本身,而不是类的实例对象。因此,无论创建多少个对象,类变量在整个程序中只有一份副本。任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象取修改它时,修改的也是同一个变量。

1.类变量定义

  • 使用 static 关键字声明。
  • 可以是任何数据类型(基本类型或引用类型)。
  • 可以被访问和修改,不依赖于对象。
  • 类变量在类加载时初始化,且仅初始化一次
// 定义语法:
访问修饰符 static 数据类型 变量名;	// 推荐使用
static 访问修饰符 数据类型 变量名;
// 举例:
public static int count = 0;
 
// 访问语法:
类名.静态变量名	// 推荐使用
对象名.静态变量名
// 举例:
int childCount = Child.count;
public class MyClass {
    // 类变量(静态变量)
    public static int count = 0;

    // 实例变量
    public String name;
}

2.类变量的特点

特性 描述
所属对象 属于类本身,不属于类的实例
内存分配 在类加载时分配内存,且只分配一次
生命周期 从类加载开始到类卸载结束
访问方式 通过类名访问:类名.变量名,也可以通过对象访问(不推荐)
初始化时机 类首次主动使用时(如创建实例、调用静态方法等)

3.类变量 vs 实例变量

对比项 类变量(static) 实例变量(非 static)
声明方式 static 修饰 没有 static
所属对象 类本身 类的每个实例
存储位置 方法区(JVM 规范) 堆中每个对象内部
生命周期 随类加载而存在,类卸载而销毁 随对象创建而存在,垃圾回收而销毁
访问方式 推荐通过类名访问 必须通过对象访问
示例 MyClass.count myObj.name
用途 共享数据、计数器、常量等 描述对象状态

 4.使用场景

1. 共享数据

多个对象之间共享一个值,例如用户登录计数器。

class User {
    public static int loginCount = 0;

    public void login() {
        loginCount++;
    }
}

User u1 = new User();
u1.login();

User u2 = new User();
u2.login();

System.out.println(User.loginCount); // 输出:2

2. 常量定义

将不变的数据作为类变量,通常与 final 一起使用。

class Constants {
    public static final double PI = 3.14159;
    public static final String APP_NAME = "MyApp";
}

System.out.println(Constants.PI);

3. 缓存或配置信息

保存一些全局可用的配置或缓存数据。

class Config {
    public static String ENV = "production";
}

if (Config.ENV.equals("development")) {
    // 开发环境配置
}

 2.类方法

在 Java 中,类方法(Class Method) 是指使用 static 关键字修饰的方法。它与对象无关,而是属于类本身的成员,可以通过类名直接调用。

 成员方法可以直接访问类变量或类方法

静态方法只能访问静态成员,非静态的方法可以访问静态成员和非静态成员。(必须遵守访问权限)

1.类方法的定义 

public class MyClass {
    // 类方法(静态方法)
    public static void sayHello() {
        System.out.println("Hello, static method!");
    }
}

调用方式:

MyClass.sayHello(); // 不需要创建对象

 2.类方法的特点

特点 说明
属于类本身 而不是类的实例
通过类名调用 也可以通过对象调用,但不推荐
不能访问非静态成员 不能直接访问实例变量或实例方法
可以重载 支持方法重载(Overload)
不能被重写 子类不能重写父类的静态方法(静态绑定)
独立于对象存在 即使没有实例,也能调用

3.类方法中不能使用的关键字和操作

  • ❌ this:类方法不属于某个对象。
  • ❌ super:不能访问父类的实例成员。
  • ❌ 直接访问非静态变量或方法。
  • ✅ 可以访问其他静态成员(变量或其他 static 方法)。

4.类方法不能被重写 

静态方法不能被重写(Override),但可以通过相同的方法签名在子类中隐藏(Hide)。具体规则如下:

  1. 绑定机制不同
    静态方法在编译时通过静态绑定确定调用哪个类的方法,而非运行时动态绑定[。例如:

Parent cp = new Child(); 
cp.getCName(); // 调用Parent的静态方法,而非Child的
  1. 隐藏而非重写
    子类定义与父类相同的静态方法时,父类方法会被隐藏,而非被覆盖。调用时根据引用类型决定,而非实际对象类型。

  2. 无法实现多态
    重写的目的是实现多态(运行时根据对象类型调用对应方法),但静态方法的隐藏行为无法满足这一条件。例如:

class Father { 
public static void staticMethod() {
 System.out.println("Father"); 
} 
}
class Son extends Father {
 public static void staticMethod() { 
System.out.println("Son");
 } 
}
Father mBySon = new Son();
mBySon.staticMethod(); // 输出"Father",非"Son"

3.语法限制
在子类中定义与父类相同的静态方法时,若使用@Override注解会编译报错,因为逻辑上不构成重写 

3.理解main方法的static 

在Java中,main方法必须声明为static,这是由JVM(Java虚拟机)的调用机制决定的 

在 Java 中,程序的入口方法是:

public static void main(String[] args)

1.为什么 main 方法必须是 static 的?

Java 程序的执行是从 main 方法开始的,而 JVM(Java 虚拟机)在启动时并不会自动创建该类的对象。

1.main方法是虚拟机调用;

2.java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public;

3.java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static;

2.命令行参数示例

你可以通过命令行向 main 方法传递参数:

public class ArgsDemo { 
public static void main(String[] args) { 
for (String arg : args) { 
System.out.println(arg); 
}
 }
 }

编译并运行:

javac ArgsDemo.java java ArgsDemo hello world 123

输出结果:

hello world 123

4.mian方法详解

1.基本定义

public static void main(String[] args) { // 程序入口代码 }

  • public:必须是公开的,这样 JVM 才能访问它。
  • static:静态方法,JVM 调用它时不需要创建类的实例。
  • void:没有返回值。
  • main:方法名,是 JVM 规定的入口方法名称。
  • String[] args:命令行参数,用于接收运行时传入的参数。

2.main 方法的作用

  1. 程序启动入口

    • 当你使用 java ClassName 命令运行程序时,JVM 会查找并调用该类的 main 方法。
    • 示例:

      java HelloWorld

  2. 接收命令行参数

    • 可以在运行时传入参数,供程序使用。
    • 示例:

      java Calculator add 10 20

      在 main 方法中,args 数组内容为:["add", "10", "20"]

3.main 方法的调用规则

特征 是否允许
必须是 public  是
必须是 static
返回类型必须是 void
方法名必须是 main  是
参数必须是 String[] 类型  是

4.main方法的位置 

1. 任意类中都可以定义 main 方法

只要满足语法要求,main 方法可以写在任何类中。

示例:

public class A { 
public static void main(String[] args) {
 System.out.println("Class A main");
 } 
} 

class B { 
public static void main(String[] args) {
 System.out.println("Class B main");
 } 
}

你可以通过命令行运行不同的类:

java A # 输出: Class A main

java B # 输出: Class B main

2. 一个项目中可以有多个 main 方法

Java 允许你在多个类中定义 main 方法,但每次只能运行一个类的 main 方法,由你指定要执行的类名决定。

3. main 方法可以没有内容或为空

场景 是否允许
多个类都有 main 方法 允许
main 方法写在非 public 类中  允许
main 方法写在接口中 允许(Java 8+)
main 方法写在内部类中  允许
main 方法参数不是 String[] ❌ 不允许
main 方法不是 static ❌ 不允许

 5.main方法如何调用

 一个类中定义了 main 方法后,它可以自己调用自己。这种“自己调用自己的类”在 Java 中是非常常见的做法,尤其是在封装逻辑、复用代码或进行递归调用时。

类自己调用自己的方法 

public class SelfCallingClass {
    public static void main(String[] args) {
        System.out.println("开始执行 main 方法");
        doSomething();
    }

    public static void doSomething() {
        System.out.println("doSomething 方法被调用");
    }
}
/*
SelfCallingClass 类有 main 方法。
在 main 方法中调用了本类的静态方法 doSomething()。
这就是类自己调用自己的方法的一种常见形式。*/

类自己调用自己的 main 方法(递归)

你甚至可以递归地调用 main 方法(虽然不推荐):

public class SelfCallingClass {
 public static void main(String[] args) {
 System.out.println("main 方法被调用"); 
main(null); // 自己调用自己(递归)
 } 
}

 注意:这会导致无限递归,最终抛出 StackOverflowError

类内部调用自己的构造函数(this 和 super)

除了调用方法,还可以在构造函数中调用其他构造函数:

public class Person {
    public Person() {
        this("未知姓名"); // 调用另一个构造函数
        System.out.println("无参构造函数");
    }

    public Person(String name) {
        System.out.println("带参构造函数: " + name);
    }

    public static void main(String[] args) {
        new Person(); // 触发构造函数的自调用
    }
}
/*
输出
带参构造函数: 未知姓名
无参构造函数
*/

静态方法调用非静态方法

如果想在静态方法中调用非静态方法,需要先创建当前类的对象:

public class MyClass {
    public void instanceMethod() {
        System.out.println("实例方法");
    }

    public static void staticMethod() {
        MyClass obj = new MyClass();
        obj.instanceMethod(); // 通过对象调用非静态方法
    }

    public static void main(String[] args) {
        staticMethod(); // main 调用静态方法,间接调用实例方法
    }
}

5.代码块

1.基本概念

代码块:又称为初始化块,属于类中的成员【即是类的一部分】,类似于方法,将逻辑语句封装在方法体中,用 {} 包围起来。

语法:

static {
    // 静态代码块内容
}
/*
修饰符可以选,要写的话,也只能写static;

代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的叫做普通代码块/非静态代码块;

逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
;号可以写上,也可以省略。
*/

 2.代码块的分类

根据所处的位置和修饰符的不同,Java 的代码块可以分为以下几类

类型 关键字 所属对象 执行时机
局部代码块(Local Block) 方法内部或语句块中 随方法调用执行
构造代码块(Instance Block) 类中、方法外 每次创建对象时执行
静态代码块(Static Block) static 类中、方法外 类加载时执行一次
同步代码块(Synchronized Block) synchronized 方法内部或语句块中 多线程访问时控制同步

1.局部代码块

定义:

写在方法内部或任意语句块中的 {}

特点:
  • 属于局部作用域。
  • 变量生命周期仅限于该代码块内。

作用:限制变量作用域,提高代码可读性和安全性。 

示例:

public class Test { 
public void method() { 
{
 int x = 10; 
System.out.println("x = " + x); 
} 
// System.out.println(x); // 编译错误:x 不可见 
}
 }

2. 构造代码块

定义:

定义在类中、方法外部的代码块,没有 static 修饰。

特点:
  • 在每次创建对象时自动执行。
  • 在构造函数之前执行(但构造函数体后执行)。
  • 多个构造函数共享该代码块,避免重复初始化逻辑。

作用:多个构造函数共享的初始化逻辑。 

class Person {
    {
        System.out.println("构造代码块执行");
    }

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

    public Person(String name) {
        System.out.println("带参构造函数:" + name);
    }
}

// 测试
new Person();
new Person("Alice");

// 输出:
// 构造代码块执行
// 无参构造函数
// 构造代码块执行
// 带参构造函数:Alice

 3. 静态代码块

定义:

使用 static {} 定义的代码块。

特点:
  • 类加载时自动执行一次。
  • 用于初始化静态变量或加载资源(如数据库驱动、配置文件等)。
  • 多个静态块按顺序执行。
  • 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。

作用: 类级别的初始化操作。

示例:

class MyClass { 
static { 
System.out.println("静态代码块执行");
 } 
public MyClass() { 
System.out.println("构造函数执行"); 
} 
} 
// 测试 
new MyClass(); 
new MyClass(); 
// 输出:
 // 静态代码块执行 
// 构造函数执行
 // 构造函数执行

 3.类什么时候被加载

 1.首次主动使用时加载
类在首次被主动使用时加载,例如:

  • 创建类的实例(如 new Class())。
  • 访问类的静态变量或静态方法。
  • 使用反射(如 Class.forName("ClassName"))。

2.创建子类对象实例,父类也会被加载;

3.使用子类的静态成员时,父类也会被加载。 

4.代码块执行顺序总结

当一个类包含多种代码块时,其执行顺序如下:

  1. 静态代码块(只执行一次)
  2. 构造代码块
  3. 构造函数

 5. 存在继承关系时,创建一个子类对象,各个方法和属性被虚拟机调用的顺序

首先是在类加载时,调用静态代码块和静态属性的初始化。

  1.   调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按题目定义的顺序调用);
  2.         调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
  3.         调用构造方法。

其次是在堆内存中创建对象空间,调用普通代码块和普通属性的初始化,调用构造方法。

  1. 父类的静态代码块和静态属性(优先级一样,按定义顺序执行);
  2.         子类的静态代码块和静态属性(优先级一样,按定义顺序执行);
  3.         父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行);
  4.         父类的构造方法;
  5.        子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行);
  6.         子类的构造方法
class Parent {
    static { System.out.println("父类静态代码块"); }
    { System.out.println("父类构造代码块"); }
    public Parent() { System.out.println("父类构造函数"); }
}

class Child extends Parent {
    static { System.out.println("子类静态代码块"); }
    { System.out.println("子类构造代码块"); }
    public Child() { System.out.println("子类构造函数"); }
}

// 测试
new Child();

// 输出:
// 父类静态代码块
// 子类静态代码块
// 父类构造代码块
// 父类构造函数
// 子类构造代码块
// 子类构造函数

6.单例模式 

单例模式(Singleton Pattern)是 Java 中最常用的设计模式之一,属于创建型设计模式。它确保一个类只有一个实例,并提供一个全局访问点。

1.单例模式的核心特点

  1. 唯一实例:单例类只能有一个实例。
  2. 自行实例化:单例类必须自己创建自己的实例。
  3. 全局访问:提供静态方法供外部访问唯一实例

2.实现方式

1. 饿汉式(静态常量)

饿汉式在类加载时就创立了对象,可能造成资源浪费。

/*
构造器私有化 =>防止直接new;

 类的内部创建对象;

 向外暴露一个静态的公共方法。

*/
public class Singleton {

    private Singleton() {}
    private static final Singleton INSTANCE = new Singleton();

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

 2.懒汉式(Lazy Initialization)

首次调用时初始化实例,需处理线程安全。

/*
构造器私有化 =>防止直接new;

 类的内部创建对象,但不new;

 向外暴露一个静态的公共方法,调用该方法时候再new,再次调用时候,会返回上一次创建的对象,保证单例。
*/
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

3单例模式的应用场景

  1. 数据库连接池:避免频繁创建和销毁连接。
  2. 日志记录器:统一管理日志输出。
  3. 配置管理器:读取一次配置文件,全局共享。
  4. 线程池:避免重复创建线程,提高性能。
  5. 缓存系统:统一管理缓存数据。 

4.注意事项

  1. 构造函数私有化:防止外部通过 new 创建对象。
  2. 延迟加载 vs 饿汉式:根据需求选择是否需要延迟加载。
  3. 线程安全:在多线程环境下必须保证线程安全。
  4. 防反射攻击:某些实现方式(如懒汉式)可以通过反射破坏单例。
  5. 防序列化破坏单例:如果实现了 Serializable 接口,需重写 readResolve() 方法。

 7.final关键字

1. final 关键字的作用

Java 中的 final 关键字用于限制用户对类、方法和变量的操作。它有以下几种主要用途:

1. 修饰变量

  • 表示该变量为常量,一旦赋值后不能更改。
  • 常用于定义不可变的值,例如配置参数或数学常数。
final int MAX_VALUE = 100;
MAX_VALUE = 200; // 编译错误:无法修改 final 变量

2.修饰方法

  • 防止子类重写该方法。
  • 提高代码安全性,确保方法行为不会被改变。
class Parent { 
final void display() { 
System.out.println("This is a final method."); 
} 
} 
class Child extends Parent { 
// 无法重写 display() 方法
 }

2. 修饰类

  • 防止类被继承。
  • 常见例子如 String 类,确保其不变性。
final class MyClass { // 此类不能被继承 }

2.注意事项

  • final 变量必须在声明时或构造函数中初始化。
  • 如果 final 变量是引用类型,则引用地址不可变,但对象内部状态可以修改
  • inal修饰的属性又叫常量,一般用 XX_XX_XX 来命名
  • 如果final修饰的属性是静态的,则初始化的位置只能是:定义时; 在静态代码块中。不能在构造器中赋值,因为static属性在类加载时候就有了,但是构造器是在对象创建时才调用的。

  • final类不能继承,但是可以实例化对象;

  • 如果类不是final类,但是含有final方法, 则该方法虽然不能重写,但是可以被继承;

  • 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法;

  • final不能修饰构造方法(即构造器);

  • 包装类(Integer,Double,Float,Boolean等都是final),String也是final类。

8.抽象类

当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象到底,那么这个类就是抽象类。

抽象类的价值更多在于设计,是设计者设计好之后,让子类继承并且实现的类。

 在 Java 中,抽象类(Abstract Class) 是一种不能被实例化的类,主要用于作为其他类的基类。它允许定义抽象方法和具体方法,并且可以包含字段、构造函数、静态方法等。 

1.什么是抽象类?

  • 使用 abstract 关键字声明。
  • 不能直接通过 new 创建实例
  • 可以包含 抽象方法(没有实现的方法) 和 具体方法(有实现的方法)
  • 子类继承抽象类时,必须实现所有的抽象方法,否则子类也必须声明为抽象类。
  • 抽象方法必须存在于抽象类中,普通类不能包含抽象方法
abstract class Animal {
    // 抽象方法 不能包含方法体
    public abstract void makeSound();

    // 具体方法
    public void breathe() {
        System.out.println("Breathing...");
    }
}

2.抽象类的特点

特性 描述
不能实例化 new Animal() 会编译错误
抽象类不一定要包含abstract方法。 也就是说,抽象类可以没有abstract方法。旦类包含了abstract方法,则这个类必须声明为abstract
可以有构造函数 被子类调用
可以有具体方法 支持普通方法实现
支持访问控制 成员可以是 privateprotectedpublic
可以继承其他类 支持单继承
可以实现接口 可以实现多个接口

3.抽象类与普通类的区别

比较项 抽象类 普通类
是否能实例化 ❌ 不能 ✅ 可以
是否必须有抽象方法 ❌ 不一定 ❌ 不能有抽象方法
构造函数 ✅ 可以有 ✅ 可以有
成员变量 ✅ 支持 ✅ 支持
继承关系 ✅ 支持 ✅ 支持
接口实现 ✅ 可以实现接口 ✅ 可以实现接口

4.基础抽象类 

//基础抽象类
abstract class Shape {
    abstract double getArea();  // 抽象方法

    void printType() {          // 具体方法
        System.out.println("This is a shape.");
    }
}

class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    double getArea() {
        return Math.PI * radius * radius;
    }
}

5.模板方法模式

模板方法模式(Template Method Pattern)在一个抽象类中定义一个或多个抽象方法,以及一个模板方法,该模板方法调用这些抽象方法,并定义了它们的执行顺序。子类通过实现抽象方法来完成具体的业务逻辑。

核心结构

  1. 抽象类(Abstract Class)

    • 定义模板方法(Template Method),作为算法的骨架,按固定顺序调用基本方法。
    • 包含具体方法(已实现的逻辑)和抽象方法(需子类实现的步骤)。
    • 可选定义钩子方法(Hook Method),提供默认实现或空方法,允许子类选择性重写以影响算法行为。
  2. 具体子类(Concrete Class)
    实现抽象类中的抽象方法,覆盖钩子方法,但不改变模板方法的结构。

使用规则

  • 抽象类不能被实例化,子类必须实现所有抽象方法,除非自身也声明为抽象类。
  • 模板方法通常设计为final,防止子类重写算法结构。
  • 抽象方法不能有方法体,不能使用privatefinalstatic修饰。

典型应用场景

  1. 共性流程 + 差异性步骤
    例如,不同动物的进食行为(猫吃鱼、牛吃草)共享计算耗时的模板,但具体实现不同;汽车启动流程中,启动、停止等步骤共用,但鸣笛逻辑可由子类定制。
  2. 复杂算法的分步实现
    如出国留学手续的通用流程(申请、签证等)中,部分步骤(如公证)各国相同,部分(如签证要求)需子类实。
  3. 代码复用与维护优化
    将重复的逻辑(如性能统计、事务管理)封装到父类,减少子类冗余。
2.核心组成
角色 描述
AbstractClass 定义了一个或多个抽象方法供子类实现,同时定义并实现模板方法,该方法调用这些基本方法来完成整个流程。
ConcreteClass 实现抽象类中定义的抽象方法,完成与特定上下文相关的逻辑。
//模板方法模式
abstract class Game {
 //  抽象方法,由子类具体实现
    abstract void initialize();
    abstract void startPlay();
    abstract void endPlay();

    // 模板方法 定义算法骨架
    public final void play() {
        initialize();
        startPlay();
        endPlay();
    }
}

class Cricket extends Game {
    @Override
    void initialize() {
        System.out.println("Cricket initialized");
    }

    @Override
    void startPlay() {
        System.out.println("Cricket started");
    }

    @Override
    void endPlay() {
        System.out.println("Cricket ended");
    }
}
//使用示例
const game: Game = new Cricket ();
game.play();

 4.注意事项

  1. 抽象类不能是 final 的:因为需要被继承。
  2. 不能用 abstract 修饰私有方法:因为私有方法无法被继承,也无法被重写。
  3. 抽象类可以有 main 方法:可以运行测试。
  4. 抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。
  5. 抽象类可以继承另一个抽象类:无需实现父类的抽象方法。
  6. 抽象类可以实现接口:但不一定要实现接口中的方法

9.接口

在 Java 中,接口(interface)是一种抽象类型,它是对行为的抽象。接口中可以定义常量和抽象方法(隐式 public abstract),但不能包含方法的具体实现(Java 8 之前)。从 Java 8 开始,接口可以包含默认方法和静态方法。

1.如何定义一个接口

[访问修饰符] interface 接口名 [extends 父接口列表] {
    // 常量定义
    // 抽象方法声明
    // 默认方法
    // 静态方法
    // 私有方法
}
//Animal 抽象类
public interface Animal {
  
    void speak() {}
}
public class Dog implements Animal {
    @Override
    public void speak() {
        System.out.println("汪汪!");
    }
}
//多个接口实现

public class Robot implements Animal, Machine {
    @Override
    public void speak() {
        System.out.println("Robot speaking");
    }

    @Override
    public void start() {
        System.out.println("Machine started");
    }
}

2.接口的特点

特性 描述
不可实例化 接口不能被 new 实例化
可多继承 一个类可以实现多个接口
方法默认 public abstract 所有方法默认为 public abstract
常量默认 public static final 接口中定义的变量默认是 public static final 类型

3.Java 8 新增功能:默认方法与静态方法

 默认方法(Default Method)

允许接口提供默认实现:

public interface Animal {
 void speak();
 default void breathe() { System.out.println("Breathing..."); 
} 
}
interface C{
 
    default void fun2(){//如果用default修饰,那么就可以有具体的实现,可以被重写
        System.out.println("fun2 ");
    }
    static void fun3(){//如果用static修饰,那么就可以有具体的实现,可以不用被重写
        System.out.println("static ");
    }
}

 子类可以选择是否重写默认方法。

 静态方法(Static Method)

接口也可以拥有静态方法:

public interface Animal {
 static void info() { 
System.out.println("This is an animal interface."); 
} 
}
Animal.info(); // 直接通过接口名调用

4.接口 vs 抽象类

对比项 接口 抽象类
方法实现 Java 8+ 支持默认/静态方法 可以有具体方法
成员变量 默认 public static final 普通变量可用
构造函数
多继承 支持 不支持
使用目的 行为规范 共享代码逻辑

5.接口的多态 

口的多态特性是指通过接口引用调用不同实现类的方法,实现“一个接口多种行为”的能力。 

接口多态性的实现机制

  • 接口定义行为契约:接口声明抽象方法(或默认方法),规定实现类必须遵循的行为规范 。
  • 实现类重写方法:不同实现类提供接口方法的具体实现,形成差异化行为 。
  • 动态绑定(运行时多态):通过接口引用指向具体实现类对象时,Java虚拟机会在运行时根据实际对象类型调用对应的方法 。

 此处,Animal 接口引用 a1 和 a2 分别指向 Cat 和 Cow 对象,调用 sound() 方法时会根据实际对象类型动态绑定到具体实现

interface Animal {
    void sound();
}

class Cat implements Animal {
    public void sound() { System.out.println("Cat meows"); }
}

class Cow implements Animal {
    public void sound() { System.out.println("Cow moos"); }
}

public class Test {
    public static void main(String[] args) {
        Animal a1 = new Cat();  // 接口引用指向Cat对象
        Animal a2 = new Cow();  // 接口引用指向Cow对象
        a1.sound();  // 输出 "Cat meows"
        a2.sound();  // 输出 "Cow moos"
    }
}
  • 继承多态性:基于类继承链(父类引用指向子类对象),如 Shape s = new Circle()
  • 接口多态性:基于接口实现(接口引用指向实现类对象),如 List list = new ArrayList() 

6.接口和继承的区别 

特性 接口(Interface) 继承(Class Inheritance)
定义方式 使用 interface 关键字定义 使用 class 定义,通过 extends 实现继承
表达关系 表示“能做什么”(行为规范) 表示“是什么”(父子类关系)
方法实现 默认是抽象方法(Java 8+ 支持 default/static) 可以有具体实现
成员变量 默认 public static final 常量 普通成员变量
构造函数 不可以有构造函数 可以有构造函数
多继承 ✅ 支持多接口实现 ❌ 不支持多继承(只能有一个父类)

如果你正在设计一个系统,建议:

  • 如果你需要多种组合行为,优先使用 接口
  • 如果你希望共享代码逻辑构建层级结构,使用 继承

7.接口的继承 

接口的继承是一个非常重要的特性,它允许一个接口从另一个或多个接口中继承方法定义和默认实现。通过接口继承,可以构建出更加灵活、可扩展的系统架构。

  • 允许子接口继承父接口中的抽象方法、默认方法(default)和静态方法(static)。
  • 支持多重继承,即一个接口可以继承多个接口。
  •  接口支持方法重写(Override)
  •  接口不支持方法重载(Overload)
interface A {
    void methodA();
}

interface B extends A {
    void methodB();
}

下面我们详细解释这两者的含义以及它们在接口继承中的表现。

 接口支持方法重写(Override)

定义:

子接口或实现类可以覆盖父接口中定义的方法(包括默认方法),这称为“方法重写”。

示例:接口继承并重写默认方法
// 父接口
 interface Animal { 
default void speak() {
 System.out.println("Animal speaks");
 } 
} 
// 子接口重写父接口的默认方法 
interface Dog extends Animal {
 @Override 
default void speak() {
 System.out.println("Dog barks"); 
} 
} 


// 实现类 
class MyDog implements Dog {
 public static void main(String[] args) { 
MyDog dog = new MyDog(); 
dog.speak(); // 输出: Dog barks 
} 
}
特点:
  • 可以重写抽象方法、默认方法。
  • 如果多个父接口有同名默认方法,子接口或实现类必须显式 @Override 来解决冲突。

 接口不支持“方法重载”(Overload)

定义:

重载(Overloading) 是指在同一作用域内定义多个同名但参数列表不同的方法。

在接口中:
  • 接口中不能直接进行方法重载(因为没有实现体)。
  • 但是可以在实现类中对接口方法进行重载(注意这不是严格意义上的接口方法重载)。
示例说明:
 正确做法:接口定义多个同名不同参方法(不是重载,而是接口声明了多个方法)
interface Calculator {
 int add(int a, int b);
 double add(double a, double b); // 不是重载,而是两个不同方法
 }
 错误做法:试图在接口中“重载”一个方法(这是不允许的)
interface BadInterface { 
void doSomething(); 
void doSomething(int x); // 编译错误?不是,这是合法的!
 }

 注意:上面这个例子其实是合法的,因为接口可以声明多个签名不同的方法,这并不违反 Java 规范。它只是声明了两个方法,并不是“重载”的行为本身发生在接口内部。

更准确地说:
  • 接口可以声明多个同名但参数不同的方法(即接口中存在多个方法签名)。
  • 但这并不是“重载”,而只是接口声明了多个方法。
  • “方法重载”是指在一个类或接口中定义多个相同名称、不同参数的方法,并由编译器根据调用上下文选择具体方法。

 总结对比表

对比项 接口是否支持
方法重写 ✅ 支持(通过子接口或实现类)
方法重载 ❌ 不支持(接口本身不能实现方法,无法执行重载逻辑)
默认方法 ✅ 支持(Java 8+)
静态方法 ✅ 支持(Java 8+)
私有方法 ✅ 支持(Java 9+,用于辅助默认方法)

 补充:接口继承与默认方法冲突处理

当一个类实现多个接口,且这些接口有相同的默认方法时,需要显式 @Override 并指定使用哪个接口的默认方法:

interface A { 
default void show() { 
System.out.println("A");
 } 
} 

interface B { 
default void show() { 
System.out.println("B"); 
} 
} 
class MyClass implements A, B {
 @Override public void show() { 
A.super.show(); // 显式调用 A 的默认方法
 } 
}

 结论

  • 接口支持方法重写,可以通过子接口或实现类来覆盖接口中的方法(包括默认方法)。
  • 接口不支持方法重载,虽然可以声明多个同名不同参数的方法,但这只是多个方法的声明,而不是真正的“重载”行为。
  • 真正的“重载”是在类中为某个方法提供多个实现版本,而接口本身由于没有方法体,无法完成这一操作。

8.注意事项

  1. 接口不能被实例化
  2. 接口中只能声明常量(public static final), 比如:int a=1;实际上是 public static final int a=1;(必须初始化)
  3. 接口方法默认是 public abstract
  4. 实现接口的类必须重写抽象方法(可以使用 alt+enter 来解决),如果一个类实现了接口但没有实现全部抽象方法,那么该类必须声明为 abstract
  5. 接口中属性的访问形式: 接口名.属性名
  6. 接口不能继承其它的类,但是可以继承多个别的接口
  7. 类只能单继承,但可以实现多个接口
  8. 当两个接口都提供相同签名的 default 方法时,实现类必须显式覆盖该方法以解决冲突
  9. 接口的修饰符 只能是 public 和默认,这点和类的修饰符是一样的。
  10. 接口中的静态方法只能通过接口名调用

  11. 接口不能有构造函数

  12. 接口名应使用名词或形容词,表示“能力”或“角色”,常见后缀:ableibletioner

10.内部类 

ava 的内部类(Inner Class)是指定义在另一个类内部的类。它是 Java 中面向对象编程的一个重要特性,常用于封装、逻辑组织和访问外部类成员等场景。

1. 内部类分类

类型 描述
成员内部类 定义在外部类的成员位置上,与方法、属性并列。
静态内部类 使用 static 修饰的成员内部类,不能直接访问外部类的非静态成员。
局部内部类 定义在方法或作用域内的类,只能在该方法内使用。
匿名内部类 没有类名的内部类,通常用于实现接口或继承类并立即实例化。

1. 成员内部类(普通内部类)

public class Outer {
 private String outerMsg = "Outer Message"; 
// 成员内部类 
class Inner {
 void print() { 
System.out.println(outerMsg); // 可以访问外部类的私有成员 
} 
} 
void testInner() {
// // 在外部类中使用内部类
 Inner inner = new Inner(); 
inner.print(); 
} 
}
​
//在其他类中使用成员内部类
Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();

​

特点:

  • 可以访问外部类的所有成员(包括私有)。

  • 成员内部类中不能定义静态成员
  • 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员。
  • 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
  • 实例化时必须依赖于外部类的实例:

2. 静态内部类

静态内部类是定义在外部类的成员位置,并且有static修饰 

1.定义语法
public class Outer { 
// 静态内部类
 public static class StaticNested { 
// 可以包含静态和非静态成员 
} 
}
//创建静态内部类对象的格式:
外部类名.内部类名 对象名 = new 外部类名.内部类名();
//调用静态内部类的静态方法:外部类名.内部类名.方法名();
//调用静态内部类的非静态方法:先创建静态内部类对象,再用对象调用方法
public class Outer {
    private static String staticMsg = "Static Message";
    private String instanceMsg = "Instance Message";

    // 静态内部类
    public static class StaticNested {
        void print() {
            System.out.println(staticMsg); // 合法:访问外部类的静态字段
            // System.out.println(instanceMsg); // 编译错误:不能访问非静态字段
        }
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        Outer.StaticNested nested = new Outer.StaticNested();
        nested.print(); // 输出: Static Message
    }
}
public class Outer {
    // 静态内部类
    static class Inner {
        // 静态方法
        public static void staticMethod() {
            System.out.println("静态内部类的静态方法");
        }
        
        // 非静态方法
        public void normalMethod() {
            System.out.println("静态内部类的非静态方法");
        }
    }
}
 
// 调用方法
public class Test {
    public static void main(String[] args) {
        // 调用静态方法
        Outer.Inner.staticMethod();
        
        // 调用非静态方法
        Outer.Inner inner = new Outer.Inner();
        inner.normalMethod();
    }
}

 特点:

  • 不能直接访问外部类的非静态成员,静态内部类只能访问外部类的静态成员
  • 不需要先创建外部类实例即可创建静态内部类对象。
  • 静态内部类可以包含静态成员,也可以包含非静态成员

3.匿名内部类 

是一种没有名字的类,定义并实例化的同时会创建一个类的对象。它是 在类定义的地方直接实例化 的,通常用于实现接口或继承类,并覆盖或实现必要的方法。

1.基本语法 

匿名内部类是定义在方法代码块中的类,同时直接创建其实例。它本质上是接口或抽象类的子类对象

new 父类构造器(参数列表) 或 接口名() {
    // 匿名内部类的类体部分
    // 可以重写方法、定义新字段、初始化块等
}
//new 关键字:用于创建匿名内部类的实例。
//父类构造器(参数列表) 或 接口名 指的是匿名内部类继承的类或实现的接口。匿名内部类可以继承一个类或实现一个接口。
//实现接口的匿名内部类 这里我们创建了一个 Runnable 接口的匿名实现,并将其传给 Thread 构造器。
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running in anonymous class");
    }
};

new Thread(r).start();
//继承类的匿名内部类(包括抽象类)
abstract class Animal {
    abstract void speak();
}
Animal a = new Animal() {
    @Override
    public void speak() {
        System.out.println("Anonymous animal speaks");
    }
};

a.speak();

特点:

  • 匿名内部类没有显式的名字
  • 通常用于一次性使用的场景
  • 自动继承父类或实现接口,编译器会自动处理
  • 生成的类名由编译器命名,如 Main$1.class
  •  可以直接访问外部类的所有成员,包含私有的。
  •  只不能添加访问修饰符,因为它的地位就是一个局部变量。
  • 如果外部类和匿名内部类的成员重名时,医名内部类访问的话,默认遵循就近原则如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
2.匿名内部类的参数传递 

匿名内部类的参数传递是一个非常关键且容易出错的部分。由于匿名内部类没有显式的类名,它通常定义在方法或表达式内部,因此对外部变量的访问有严格的限制。

1.外部变量访问外部方法变量的规则

匿名内部类可以访问:

  • final 变量
  • effectively final 的变量(Java 8+)

effectively final 指的是虽然没有显式使用 final 修饰,但实际未被修改的局部变量。

 不允许修改的变量

匿名内部类不能修改外部方法中传入的局部变量(即使是非 final 的),否则编译报错。

2.参数传递示例详解

示例 1:访问 final 局部变量 

public class Test { 
public static void main(String[] args) { 
final String name = "Alice"; 
Runnable r = new Runnable() { 
@Override public void run() { 
System.out.println("Hello, " + name); // 正确:访问 final 变量 
} 
}; 
new Thread(r).start(); 
} 
}

示例 2:访问 effectively final 变量 (Java 8+)

public class Test {
 public static void main(String[] args) {
 String message = "Welcome"; // 虽然没加 final,但未被修改
 Runnable r = new Runnable() { 
@Override public void run() { 
System.out.println(message); // 正确:effectively final 
} 
}; 
} 
}

示例 3:尝试修改外部变量 

public class Test { 
public static void main(String[] args) { 
String msg = "Hello"; 
Runnable r = new Runnable() { 
@Override 
public void run() {
 msg = "Hi"; //  编译错误:Cannot assign a value to final variable 'msg' 
} 
}; 
}
 }

即使你没有用 final 修饰,只要你在匿名类中试图修改该变量,Java 就会将其视为 final

3.如何绕过限制?

如果你想在匿名内部类中“修改”外部变量,可以使用以下方式:

 方法一:使用数组包装变量(不推荐)

public class Test { 

public static void main(String[] args) { 
final String[] data = { "Initial" }; 
Runnable r = new Runnable() { 
@Override 
public void run() { 
data[0] = "Modified"; // 修改数组内容是允许的 System.out.println(data[0]); } };
 new Thread(r).start(); 
}
}

注意:数组本身是 final 的,但数组元素是可以变的。

 方法二:使用可变对象(如 AtomicReferenceStringBuilder

import java.util.concurrent.atomic.AtomicReference;
 public class Test { public static void main(String[] args) {
 AtomicReference ref = new AtomicReference<>("Start");
 Runnable r = new Runnable() { 
@Override 
public void run() { 
ref.set("Updated"); 
System.out.println(ref.get()); } }; 
new Thread(r).start(); 
} 
}
4.构造器传参 vs 外部变量引用

除了访问外部变量,你还可以通过构造器传参的方式将数据传递给匿名内部类。

示例:通过构造器传参

public class Test { 
public static void main(String[] args) { 
String user = "Bob"; // 通过构造器传参方式创建一个匿名子类
 Animal a = new Animal(user) { 
private String name; 
{ // 初始化块 this.name = user; }
 @Override 
void speak() { 
System.out.println("Hello, " + name); } }; 
a.speak();
 } 
} 
abstract class Animal { 
Animal(String name) { System.out.println("Animal constructor called with: " + name);
 } 
abstract void speak();
 }
5.注意事项总结
注意事项 说明
访问外部变量必须是 final 或 effectively final 否则编译失败
不能直接修改外部局部变量 即使不是 final,也不允许赋值
可以使用数组/可变对象间接修改 如 String[]AtomicReferenceList 等
构造器传参是一种替代方案 更清晰,适合复杂逻辑
Lambda 表达式也遵循相同规则 Java 8+ 的 Lambda 和匿名内部类一样受限制

 4.局部内部类

在 Java 中,局部内部类(Local Inner Class) 是定义在方法、构造函数或代码块中的类。它仅在定义它的那个方法或代码块内可见和使用。

 1.定义语法
public class Outer {
    void method() {
        // 局部内部类定义在方法中
        class LocalInner {
            void print() {
                System.out.println("Inside LocalInner");
            }
        }

        LocalInner inner = new LocalInner();
        inner.print();
    }
}
public class Outer {
    private String outerMsg = "Outer Message";

    void method() {
        String localMsg = "Local Message"; // JDK 8+ 可以不写 final

        // 局部内部类
        class LocalInner {
            void print() {
                System.out.println(outerMsg);     // 访问外部类成员
                System.out.println(localMsg);     // 访问方法内的局部变量(Effectively Final)
            }
        }

        LocalInner inner = new LocalInner();
        inner.print();
    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.method();
    }
}

特点:

  • 可以直接访问外部类的所有成员,包含私有的
  • 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final 修饰,因为局部变量也可以使用final
  • 作用域:仅仅在定义它的方法或代码块中
  • 外部其他类不能访问局部内部类(因为 局部内部类地位是一个局部变量)
  • 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)  外部类名.this 本质就是外部类的对象, 即哪个对象调用了 外部类方法,外部类.this 就是哪个对象

你可能感兴趣的:(java,笔记,开发语言)