前言
在Java的世界里,接口和修饰符是面向对象编程的基石。接口定义了“做什么”,而修饰符则决定了“怎么做”以及“能做什么”。老师要求我们深入理解这两个部分,于是我结合课堂讲义,梳理了这份博客。如果你也正在为这些概念苦恼,不妨跟着我一起,从零开始,逐步击破。
---
第一部分:接口(Interface)
接口是Java中非常重要的概念,它代表一种标准和规范。打个比方,就像USB接口,所有符合USB规范的设备(U盘、鼠标)都可以插到电脑上使用,而电脑制造商和设备制造商都遵循同一套USB标准。在Java中,接口就是这样的“USB规范”。
1. 接口的基本概念与语法
什么是接口?
接口是一种引用数据类型,它只定义了方法的名字、参数列表和返回值类型,但不提供方法的具体实现(在Java 8之前)。接口是“实现者”和“使用者”之间的契约。
接口的语法要点:
· 使用关键字 interface 定义,编译后生成独立的 .class 文件。
· 不能创建对象(不能 new 接口),但可以声明引用变量(即接口类型的变量)。
· 接口中的属性默认是 public static final 的,即公开、静态、常量。一旦赋值就不能改变。
· 接口中的方法默认是 public abstract 的,即公开、抽象方法(没有方法体)。
· 接口没有构造方法,因为它不是类,不能被实例化。
```java
// 定义一个接口
interface MyInterface {
int MAX_VALUE = 100; // 等价于 public static final int MAX_VALUE = 100;
void doWork(); // 等价于 public abstract void doWork();
}
```
2. 接口的实现类( implements )
接口定义好后,需要由类来实现它。实现类使用 implements 关键字。
实现规则:
· 如果实现类不想成为抽象类,就必须重写(实现)接口中所有的抽象方法。
· 重写时,方法的访问修饰符必须是 public(因为接口中的方法默认是public,子类重写时权限不能更小)。
· 接口类型的引用可以指向实现类的对象,这就是多态的典型用法。强制使用多态是开发中的最佳实践。
```java
interface MyInter {
int NUM = 3;
void m1();
int m2();
}
class MyClass implements MyInter {
@Override
public void m1() {
System.out.println("m1的方法...");
}
@Override
public int m2() {
System.out.println("m2的方法...");
return 0;
}
}
public class TestInter {
public static void main(String[] args) {
MyInter mi = new MyClass(); // 多态:接口引用指向实现类对象
mi.m1();
int result = mi.m2();
System.out.println("result = " + result);
}
}
```
3. 接口的继承性(多继承 vs 多实现)
Java中类之间是单继承,但接口之间支持多继承,一个接口可以继承多个父接口。
接口多继承语法:
```java
interface A { void a(); }
interface B { void b(); }
interface C extends A, B { // 多继承
void c();
}
```
类与接口之间是多实现,一个类可以同时实现多个接口。
```java
class MyClass implements A, B {
@Override
public void a() { }
@Override
public void b() { }
}
```
类继承父类的同时实现接口(先继承后实现):
```java
class Parent { }
class Child extends Parent implements A, B {
// 必须实现所有接口的方法
}
```
注意顺序:extends 在前,implements 在后。
4. 接口多继承带来的类型转换影响
当接口类型的引用进行强制类型转换时,如果转换的目标是接口类型,编译期总是通过(因为接口之间可能有继承关系)。但运行时是否成功,取决于引用中实际存储的对象类型是否与目标类型兼容。
· 如果实际对象类型就是目标类型(或它的子类),则转换成功。
· 否则抛出 ClassCastException(类型转换异常)。
```java
A a = new MyClass(); // MyClass实现了A
B b = (B) a; // 如果MyClass也实现了B,则OK;否则运行报错
```
5. 接口的作用(开发应用)
作用一:扩充子类能力
由于Java类是单继承,子类从父类获得的功能有限。接口可以给子类“附加”额外的能力。通常把主要功能放在父类中,次要的、扩展的功能放在接口中。
作用二:降低耦合度
接口将“使用者”和“实现者”分离。使用者只依赖接口,不关心具体实现;实现者只需按接口规范编码。借助多态,各模块之间可以独立演化,维护更方便。
6. 接口回调(理解)
接口回调是指:接口先定义好,然后由接口的使用者(比如框架)调用,而具体的实现代码由我们开发人员编写。这种模式在事件监听、回调函数中非常常见。
简单来说,就是“你定义规则,我来实现,你按规则调用我的实现”。开发时,我们需要关注的是根据接口规范,给出正确的实现类。
---
接口与抽象类的区别(面试高频)
对比项 接口 (interface) 抽象类 (abstract class)
关键字 interface abstract class
属性 只能是 public static final 无限制(实例变量、静态变量等)
方法 只能是 public abstract(Java 8前) 可以有抽象方法,也可以有非抽象方法
构造方法 没有 有(用于子类构造时调用)
继承/实现关系 接口可以多继承,类可以多实现 类只能单继承
选择建议:如果需要定义公共的、不变的行为规范,用接口;如果需要复用代码(有共同的成员变量或已实现的方法),用抽象类。
---
第二部分:三个修饰符(abstract、static、final)
修饰符用来控制类、方法、属性的行为和访问权限。这里重点讲解 abstract、static 和 final。
一、abstract(抽象的)
1. 抽象类
· 使用 abstract 修饰类,该类就是抽象类。
· 抽象类不能实例化(不能 new),但可以声明引用(用于多态)。
· 抽象类中可以有成员变量、成员方法,也可以有构造方法(供子类调用)。
· 抽象类的作用:强制使用多态,让子类必须实现某些方法。
```java
abstract class Animal {
String name;
public Animal(String name) { this.name = name; }
public abstract void sound(); // 抽象方法
}
```
2. 抽象方法
· 使用 abstract 修饰方法,该方法只有声明,没有方法体(连 {} 都没有)。
· 抽象方法只能定义在抽象类中。
· 子类继承抽象类后,必须实现所有抽象方法,否则子类也必须声明为抽象类。
```java
class Dog extends Animal {
public Dog(String name) { super(name); }
@Override
public void sound() {
System.out.println("汪汪");
}
}
```
二、static(静态的)
静态成员属于类级别,不属于某个对象,被所有对象共享。
1. 静态属性(类变量)
· 用 static 修饰的属性称为静态属性。
· 所有对象共用一份,推荐使用 类名.属性名 访问。
· 静态属性在类加载时初始化。
```java
class Counter {
static int count = 0; // 静态变量
int id;
public Counter() {
count++;
id = count;
}
}
// 使用:Counter.count
```
2. 静态方法
· 用 static 修饰的方法称为静态方法。
· 通过 类名.方法名() 调用。
· 注意:
· 静态方法中不能直接访问本类的非静态成员(属性和方法),因为非静态成员属于对象,而静态方法调用时可能还没有对象。
· 但非静态方法可以访问静态成员。
· 静态方法中不能使用 this 和 super。
· 静态方法可以被子类继承,但只能被静态方法覆盖(重写),且没有多态效果(调用时看引用类型,而非实际对象)。
```java
class MyClass {
int value = 10;
static int b = 30;
static void staticMethod() {
// System.out.println(value); // 错误!不能直接访问非静态
System.out.println(b); // 正确
}
void instanceMethod() {
System.out.println(value); // 正确
System.out.println(b); // 正确,可以访问静态
}
}
```
3. 静态代码块(static initialization block)
· 位置在类内、方法外,用 static {} 包裹。
· 在类加载时执行,且只执行一次,用于初始化静态属性。
· 执行顺序:先执行静态属性的初始化(按定义顺序),再执行静态代码块。
类加载的时机:
· 第一次创建该类的对象;
· 第一次使用该类的静态成员;
· 子类加载会导致父类先加载。
完整加载与创建对象的顺序(以继承为例):
1. 加载父类(静态属性初始化 → 静态代码块)
2. 加载子类(静态属性初始化 → 静态代码块)
3. 创建父类对象(实例属性初始化 → 动态代码块 → 构造方法)
4. 创建子类对象(实例属性初始化 → 动态代码块 → 构造方法)
```java
class Parent {
static int a = 10;
static { System.out.println("Parent static block"); }
{ System.out.println("Parent instance block"); }
Parent() { System.out.println("Parent constructor"); }
}
class Child extends Parent {
static int b = 20;
static { System.out.println("Child static block"); }
{ System.out.println("Child instance block"); }
Child() { System.out.println("Child constructor"); }
}
// 当 new Child() 时,输出顺序如上所述。
```
4. 静态内部类(略,本文不展开)
三、final(最终的)
final 表示不可改变。
1. final 修饰变量(常量)
· 局部变量、实例变量、静态变量都可以被 final 修饰,一旦赋值就不能再修改。
· 实例变量(非静态)不会默认赋值,必须显式初始化,时机:
· 声明时直接赋值;
· 在构造方法中赋值(所有构造方法都必须赋值);
· 在动态代码块中赋值。
· 静态变量:必须在声明时或静态代码块中赋值。
```java
class FinalDemo {
final int a; // 必须在构造器或代码块中赋值
final static int B; // 必须在静态代码块中赋值
{
a = 10; // 可以
}
static {
B = 20;
}
}
```
注意:final 修饰引用类型变量时,表示引用指向的地址不能变,但对象内部的内容可以改变(除非对象本身不可变)。
2. final 修饰方法
· 方法可以被继承,但不能被子类重写(覆盖)。这可以防止子类修改父类的重要方法。
3. final 修饰类
· 类不能被继承,即没有子类。例如 String、System、Math 都是 final 类。
---
总结
通过这次整理,我对接口和三个修饰符有了更清晰的认识:
· 接口定义了行为规范,支持多继承和多实现,是降低耦合、实现多态的利器。
· abstract用于定义抽象类和抽象方法,强制子类实现,是多态的基础。
· static让成员属于类,方便共享和调用,但要注意静态上下文不能直接访问非静态。
· final保证不可变性,用于常量、防止方法重写或类继承。
这些知识点是Java开发中的常客,也是面试中的高频考点。大一阶段打好基础,后面学框架、设计模式时会更加得心应手。
希望我的笔记能对你有所帮助。如果发现错误或有更好的理解,欢迎在评论区交流讨论!