类变量与类方法
static修饰的成员变量(类变量,静态变量)的特性?
同一个类所有对象共享
类变量是随着类的加载而创建, 所以即使没有创建对象实例也可以访问 ,但是类变量的访问, 必须遵守 相关的访问权限.
static可以修饰类或者函数吗?
可以,使用static修饰的方法称为类方法或静态方法。
static方法可以被重写吗?
静态方法可以被继承,但是,不能被覆盖,即重写。如果父类中定义的静态方法在子类中被重新定义,那么在父类中定义的静态方法将被隐藏。可以使用语法:父类名.静态方法调用隐藏的静态方法。
如果父类中含有一个静态方法,且在子类中也含有一个返回类型、方法名、参数列表均与之相同的静态方法,那么该子类实际上只是 将父类中的该同名方法进行了隐藏,而非重写。 换句话说,父类和子类中含有的其实是两个没有关系的方法,它们的行为也并不具有多态性。
因此,通过一个指向子类对象的父类引用变量来调用父子同名的静态方法时,只会调用父类的静态方法。
所以父类和子类中同名同参数的方法:要么全声明成static的【不考虑重写】;要么全声明成非static的【考虑重写】
静态变量与成员变量的区别?
特性 | 静态变量(类变量) | 成员变量(实例变量) |
---|---|---|
声明方式 | 使用static 关键字修饰 |
不使用static 关键字 |
所属范围 | 属于类,所有实例共享 | 属于对象实例,每个实例独有一份 |
内存分配时机 | 类加载时分配内存 | 创建对象实例时分配内存 |
内存位置 | 方法区(元空间)中的静态区 | 堆内存 |
初始化时机 | 类加载时初始化 | 对象创建时初始化 |
生命周期 | 从类加载到程序结束 | 从对象创建到被垃圾回收 |
访问方式 | 可通过类名直接访问 | 必须通过对象实例访问 |
线程安全性 | 需要考虑线程安全问题 | 每个线程有自己的实例,相对安全 |
默认值 | 与成员变量相同(0, false, null等) | 与静态变量相同 |
静态变量的两种访问方式?
一般什么情况下会将方法使用static设置静态方法?
理解 main 方法语法
如何理解java的main()函数?
代码块
static代码块与普通代码块的区别?
特性 | static代码块(静态初始化块) | 普通代码块(实例初始化块) |
---|---|---|
关键字 | 使用static 修饰 |
无特殊关键字 |
执行时机 | 类加载时执行(仅一次) | 每次创建对象实例时执行 |
执行次数 | 整个程序生命周期只执行一次 | 每次实例化对象都会执行 |
访问权限 | 只能访问静态成员 | 可以访问静态和非静态成员 |
内存位置 | 方法区(元空间) | 堆内存 |
主要用途 | 初始化静态变量或执行静态初始化逻辑 | 初始化实例变量或执行对象公共初始化逻辑 |
执行顺序 | 类加载时最先执行 | 在构造方法之前执行 |
普通代码块在类加载时会执行吗?
普通代码块(非静态代码块)在类加载时不会执行。普通代码块只会在创建类的实例对象时执行,且每次创建对象时都会执行一次。
类加载的时机
创建类的实例:使用new
关键字创建对象时
访问类的静态成员:包括静态变量和静态方法
使用反射:通过Class.forName()等方法
初始化子类:当子类被初始化时,其父类也会被加载。创建子类对象实例, 父类也会被加载, 而且, 父类先被加载, 子类后被加载
class Parent {}
class Child extends Parent {} // 初始化Child时Parent也会被加载
作为主类:包含main()方法的类在程序启动时被加载
Java中创建一个对象时类内部各部分的初始化顺序
- 静态代码块和静态变量初始化的优先级相同:
- 它们按照在类中定义的先后顺序执行
- 这部分只在类第一次被加载时执行一次,之后再加载时第一部分不会加载,只会加载后面两部分
- 普通代码块和普通属性初始化的优先级相同:
- 同样按照定义顺序执行
- 构造方法总是最后执行:
- 在所有初始化工作完成后才执行构造方法
记忆技巧
- 先静态后实例:先处理所有静态的,再处理实例相关的
- 同级按顺序:同级别的代码块和初始化按定义顺序执行
- 构造最后走:构造方法总是最后一步执行
单例设计模式
什么是设计模式?
什么是单例设计模式?
单例设计模式的两种实现方法?
饿汉式
.assets/image-20250329152558895.png)
懒汉式
饿汉式 VS 懒汉式
Java中final关键字的用法总结
1. final修饰属性(常量)
- final修饰的属性称为常量,命名规范:全部大写字母,单词间用下划线连接(如
MAX_VALUE
) - 必须赋初值,且后续不能修改,赋值位置三选一:
- 定义时直接赋值。
public final double TAX_RATE = 0.08;
- 在构造器中赋值
- 在代码块中赋值
- 定义时直接赋值。
2. final修饰静态属性
-
如果是
static final
修饰的静态常量,初始化位置只能是:-
定义时直接赋值
-
在静态代码块中赋值
注意:不能在构造器中赋值构造器是在创建对象的时候才会被调用,而使用final修饰静态属性需要在类加载时就要赋值,所以不能在构造器中初始化。
-
3. final修饰类
-
final
类不能被继承(不能有子类) -
但可以实例化对象
示例:java
复制
final class A2 {} // 不能被继承 A2 obj = new A2(); // 可以创建对象
4. final修饰方法
- 如果类不是final类但包含
final
方法:- 该方法不能被子类重写
- 但可以被继承使用
5. final类与final方法的关系
- 如果一个类已经是final类(不能被继承),那么:
- 不需要再将其方法修饰为final方法
- 因为final类本身禁止继承,所有方法自然也无法被重写
6. final与构造方法
- final不能修饰构造方法(构造器)
- 构造方法本身就不能被继承或重写
- 语法上不允许使用final修饰构造器
7. final与static的搭配使用
-
final和static经常组合使用(static final)
-
优势:
- 效率更高:编译器会进行优化处理
- 避免类加载:使用static final常量不会导致类的初始化
参考《类加载的时机》访问类的静态成员:包括静态变量和静态方法类会被加载。
8. Java核心类中的final应用
- 包装类都是final类:
- Integer, Double, Float, Boolean等
- String类也是final类
- 这些类设计为final是为了保证核心功能的安全性和不可变性
抽象类
为什么父类方法访问修饰符为 private/final/static 则子类就不能重写该方法
private 修饰符:
private 修饰的方法是私有方法,只能在当前类中访问,子类无法访问到这个方法,更不用说重写了。
final 修饰符:
final 修饰的方法表示该方法是最终版本,不允许被子类重写。如果子类尝试重写一个被 final 修饰的方法,编译器会报错。
static 修饰符:
在Java中,static方法属于类级别,这意味着它们与特定的实例对象无关,而是与整个类相关联。当你调用一个static方法时,你是通过类名来调用它,而不是通过实例对象。因此,static方法在编译时就确定了调用哪个方法,而不是在运行时根据实例对象的类型来确定。
而子类重写父类方法的过程是通过动态绑定(也称为运行时多态)来实现的。这意味着在运行时,通过实例对象的类型来确定调用的方法。
由于static方法的调用在编译时就确定了,而不是在运行时确定,所以子类无法重写父类的static方法。子类可以定义与父类static方法同名的方法,但这只是在子类中隐藏了父类的方法,并不能称之为重写
接口
接口的特性与实现
1. 接口不能被实例化(抽象类也不能被实例化)
- 知识点:接口不能被实例化。
- 解释:接口是一种抽象类型,不能直接创建对象。它需要通过类来实现其定义的方法。
2. 接口中的方法
- 知识点:
- 接口中所有的方法默认是
public
的。 (不写public也行,这样默认为public) - 接口中的抽象方法可以省略
abstract
关键字。
- 接口中所有的方法默认是
- 示例:
void aaa(); // 实际上是 public abstract void aaa();
- 注意:接口中不能有方法体(所有的方法都不能有方法体,即不能有实现)。
- 错误示例:
void aaa(){} // 错误!接口中不能包含具体实现。
3. 普通类实现接口
- 知识点:一个普通类实现接口时,必须实现接口中定义的所有方法。 (IDEA中使用alt+enter快速实现接口)
- 解释:普通类需要为接口中的每个抽象方法提供具体的实现,否则会编译错误。
4. 抽象类实现接口
- 知识点:抽象类实现接口时,可以选择不实现接口中的方法。
- 解释:抽象类本身可以包含抽象方法,因此可以将接口的抽象方法延迟到子类中实现。
接口中属性和方法的区别
特性 | 方法 | 属性 |
---|---|---|
默认修饰符 | public abstract (可省略) |
public static final (可省略) |
是否需实现 | 普通类必须实现 | 无需实现(常量不可修改) |
访问方式 | 通过实现类实例调用 | 通过接口名直接访问(如 接口名.属性名 ) |
初始化要求 | 无方法体(抽象方法) | 必须显式初始化 |
阿里巴巴开发规范中提到:
-
多实现:一个类可同时实现多个接口(解决单继承局限)。
class Dog implements Runnable, Barkable {}
-
一个类可同时继承父类且实现多个接口。
class LittleMonkey extends Monkey implements Fishable,Birdable{}
-
接口继承:接口可继承多个父接口(但不能继承类)。
interface A extends B, C {}
-
修饰符限制:接口的修饰符仅限
public
或默认(包访问权限)。
如何理解接口的多态特性?
接口的多态是面向对象编程的核心特性之一,它允许同一接口引用指向不同实现类的对象,从而在运行时动态调用具体实现。以下是接口多态的三种典型体现:
1. 多态参数(灵活接收不同实现对象)
- 核心思想:接口类型作为方法参数,可以接收任何实现了该接口的类的对象。
- 示例(
InterfacePolyParameter.java
):interface UsbInterface {void start();void stop(); }class Phone implements UsbInterface {public void start() { System.out.println("手机连接USB"); }public void stop() { System.out.println("手机断开USB"); } }class Camera implements UsbInterface {public void start() { System.out.println("相机连接USB"); }public void stop() { System.out.println("相机断开USB"); } }public class Test {// 多态参数:方法接收UsbInterface类型,实际传入Phone或Camera对象public static void work(UsbInterface usb) {usb.start();usb.stop();}public static void main(String[] args) {work(new Phone()); // 输出手机逻辑work(new Camera()); // 输出相机逻辑} }
- 关键点:
- 方法
work()
无需关心具体是Phone
还是Camera
,只需调用接口定义的方法。 - 新增设备类(如
Keyboard
)时,只需实现UsbInterface
,无需修改work()
方法。
- 方法
2. 多态数组(统一管理不同实现对象)
- 核心思想:通过接口类型数组,存储多种实现类的对象,并统一处理。
- 示例(
InterfacePolyArray.java
):UsbInterface[] usbs = new UsbInterface[2]; usbs[0] = new Phone(); // 存放手机对象 usbs[1] = new Camera(); // 存放相机对象for (UsbInterface usb : usbs) {usb.start();usb.stop();// 调用Phone特有方法:需类型判断和向下转型if (usb instanceof Phone) {((Phone)usb).call(); // 调用Phone的call()} }
- 关键点:
- 数组
usbs
统一管理所有UsbInterface
实现类。 - 通过
instanceof
判断具体类型,访问实现类特有方法(如call()
)。
- 数组
3. 多态传递(接口继承时的多态)
- 核心思想:接口类型的引用可以指向实现了该接口的子接口或实现类的对象。
- 示例(
InterfacePolyPass.java
):interface A { void f1(); } interface B extends A {} // 子接口继承父接口 class C implements B {public void f1() { System.out.println("实现f1"); } }public class Test {public static void main(String[] args) {A a = new C(); // 父接口引用指向实现类对象a.f1(); // 输出"实现f1"} }
- 关键点:
- 子接口
B
继承A
后,实现类C
必须实现所有父接口方法。 - 父接口类型(如
A
)可以接收子接口或实现类的对象。
- 子接口
Java内部类与外部类
类的五大成员
属性,方法,构造器,代码块,内部类
内部类,外部类,外部其他类举例
class Outer { // 外部类:Outerclass Inner { // 内部类:Inner}
}
class Other { // 外部其他类:Other
}
内部类的分类
1.局部内部类(有类名)
- 定义在方法或代码块中,有显式的类名。
- 不能添加访问修饰符(如
public
/private
),因其地位等同于局部变量。允许final
修饰(与局部变量规则一致)。 - 作用域仅限于所在方法或代码块。
- 可直接访问外部类的所有成员(包括私有成员)。访问方式:直接调用(无需特殊语法)。
- 外部类访问局部内部类成员,必须在局部内部类的作用域内(定义它的方法或代码块)创建对象后访问。
- 外部其他类不能访问局部内部类,因为局部内部类的地位是一个局部变量。
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,可以使用(外部类名.this.成员)去访问。
外部类名.this
:代表外部类的当前实例对象。
代码演示
class Outer {private int num = 10; // 外部类成员变量public void method() {class LocalInner {int num = 20; // 局部内部类成员变量(与外部类重名)public void show() {System.out.println(num); // 默认访问局部内部类的num(输出20)System.out.println(Outer.this.num); // 显式访问外部类的num(输出10)Outer.this 本质就是外部类的对象, 即哪个对象调用了 method, Outer.this 就是哪个对象。本例中Outer.this是outer_example}}LocalInner inner = new LocalInner();inner.show();}
}public class Test {public static void main(String[] args) {Outer outer_example = new Outer();outer_example.method();}
}
2.匿名内部类(无类名,重点!)
匿名:没有名字的意思。内部类:写在其他类内部的类。匿名内部类的作用是简化代码。
原本我们需要创建子类或者实现类,去继承父类(包含抽象类)和实现接口,才能重写其中的方法。但是有时候我们这样做了,然而子类和实现类却只使用了一次(定义了一个对象)。这个时候我们就可以使用匿名内部类,不用去写子类和实现类,起到简化代码的作用。
匿名内部类的格式:父类/接口 对象 = new 父类/接口(){ 重写父类/接口中的方法 };
这样做就把子类继承父类,重写父类中的方法,创建子类对象,合成了一步完成,减少了其中创建子类的过程。或者将实现类实现接口,重写接口中的方法,创建实现类对象,合成了一步完成,减少了其中创建实现类的过程。下面将会用代码演示如何使用匿名内部类。
匿名内部类实现接口或抽象类是否需要重写所有抽象方法?
需要。匿名内部类在实现接口时,必须重写接口中定义的所有抽象方法。
3.成员内部类(无static修饰)
- 属于外部类的实例成员,可访问外部类的所有实例变量和方法。
- 必须先创建外部类对象才能实例化。
4.静态内部类(有static修饰)
- 属于外部类的静态成员,仅能访问外部类的静态成员。
- 可直接通过外部类名访问,无需外部类实例。
5.内布类比较
特性 | 局部内部类 | 匿名内部类 | 成员内部类 | 静态内部类 |
---|---|---|---|---|
定义位置 | 外部类的方法或代码块内 | 外部类的方法或代码块内 | 外部类的成员位置(非静态) | 外部类的成员位置(静态) |
访问修饰符 | 不能添加(仅允许final 修饰) |
不能添加 | 可添加(如public /private ) |
可添加(如public /private ) |
作用域 | 仅限于定义它的方法或代码块 | 仅限于定义它的方法或代码块 | 整个外部类 | 整个外部类 |
访问外部类成员 | 直接访问所有成员(包括私有成员) | 直接访问所有成员(包括私有成员) | 直接访问所有成员(包括私有成员) | 仅能访问静态成员(包括私有成员) |
外部类访问内部类 | 需在作用域内创建对象访问 | 无法直接访问(匿名无类名) | 需通过外部类实例创建内部类对象 | 可直接访问(无需外部类实例) |
外部类与内部类成员重名时的访问规则 | 就近原则,外部类名.this.成员名 | 就近原则,外部类名.this.成员名 | 就近原则,外部类名.this.成员名 | 就近原则,外部类名.静态成员名 |