Java中接口、数组与引用值的深入解析
发布时间: 2025-08-17 02:35:33 阅读量: 1 订阅数: 12 

# Java 中接口、数组与引用值的深入解析
## 1. 接口的基本概念
接口在 Java 编程中扮演着重要的角色,它定义了一组方法的签名,但不提供具体的实现。接口中的成员方法默认是抽象的,并且可以有多个接口声明同一个成员,但只能有一个实现。
### 1.1 接口的扩展
接口可以使用 `extends` 子句扩展其他接口,与类的扩展不同,接口可以扩展多个接口。被扩展的接口称为超接口,扩展的接口称为子接口。子接口继承超接口的所有方法,并且可以重写抽象方法声明,重写的方法不会被继承。抽象方法声明也可以像类中的方法一样进行重载。
以下是一个多接口继承的示例:
```java
interface IStack {
void push();
void pop();
}
interface ISafeStack extends IStack {
boolean isFull();
boolean isEmpty();
}
class StackImpl implements IStack {
@Override
public void push() {
// 实现代码
}
@Override
public void pop() {
// 实现代码
}
}
class SafeStackImpl extends StackImpl implements ISafeStack {
@Override
public boolean isFull() {
// 实现代码
return false;
}
@Override
public boolean isEmpty() {
// 实现代码
return false;
}
}
```
在 UML 中,接口类似于类,可以使用 `«interface»` 原型来区分。接口继承用不间断的继承箭头表示,所有 Java 中的引用类型都是 `Object` 类型的子类型,接口类型也不例外。
### 1.2 接口的继承关系
在定义类和接口之间的继承时,有三种不同的继承关系:
1. **类之间的单实现继承层次结构**:一个类扩展另一个类(子类 - 超类)。
2. **接口之间的多继承层次结构**:一个接口扩展其他接口(子接口 - 超接口)。
3. **接口和类之间的多接口继承层次结构**:一个类实现接口。
### 1.3 接口引用
虽然接口不能被实例化,但可以声明接口类型的引用。对象的引用值可以分配给该对象的超类型引用,通过这些超类型引用可以操作对象。超类型引用的多态行为将在后续讨论。
### 1.4 接口中的常量
接口可以定义命名常量,这些常量通过字段声明定义,被视为公共、静态和最终的,声明时可以省略这些修饰符。常量必须用初始化表达式进行初始化。接口常量可以通过其全限定名被任何客户端(类或接口)访问,无论客户端是否扩展或实现该接口。如果客户端是实现该接口的类或扩展该接口的接口,则可以直接通过简单名称访问这些常量。
以下是一个接口常量的示例:
```java
interface Constants {
double PI_APPROXIMATION = 3.14;
String AREA_UNITS = "sq.cm.";
String LENGTH_UNITS = "cm.";
}
public class Client implements Constants {
public static void main(String[] args) {
double radius = 1.5;
// 直接访问
System.out.printf("Area of circle is %.2f %s%n", PI_APPROXIMATION * radius * radius, AREA_UNITS);
// 使用全限定名访问
System.out.printf("Circumference of circle is %.2f %s%n", 2.0 * Constants.PI_APPROXIMATION * radius, Constants.LENGTH_UNITS);
}
}
```
输出结果:
```
Area of circle is 7.06 sq.cm.
Circumference of circle is 9.42 cm.
```
当定义一组相关常量时,建议使用枚举类型,而不是接口中的命名常量。
### 1.5 接口知识的总结表格
| 特性 | 描述 |
| --- | --- |
| 接口扩展 | 可使用 `extends` 扩展多个接口,子接口继承超接口方法 |
| 继承关系 | 类单继承、接口多继承、类实现接口 |
| 接口引用 | 可声明接口类型引用,对象引用可分配给超类型引用 |
| 接口常量 | 公共、静态、最终,可全限定名或简单名称访问 |
### 1.6 接口相关的继承关系 mermaid 流程图
```mermaid
graph TD;
Object --> StackImpl;
Object --> IStack;
Object --> ISafeStack;
StackImpl --> SafeStackImpl;
IStack --> ISafeStack;
ISafeStack --> SafeStackImpl;
classDef interface fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
class IStack,ISafeStack interface;
```
## 2. 数组与子类型
### 2.1 数组类型总结
Java 中的类型总结如下表所示:
| 类型 | 值 |
| --- | --- |
| 基本数据类型 | 基本数据值 |
| 类、接口、枚举和数组类型(引用类型) | 引用值 |
只有基本数据和引用值可以存储在变量中,只有类和数组类型可以显式实例化来创建对象。
### 2.2 数组与子类型协变
在 Java 中,数组是对象,数组类型(如 `boolean[]`、`Object[]`、`StackImpl[]`)隐式增强了继承层次结构。例如,`SafeStackImpl` 是 `StackImpl` 的子类,对应的数组类型 `SafeStackImpl[]` 和 `StackImpl[]` 在类型层次结构中分别为子类型和超类型。所有引用类型的数组都是 `Object[]` 类型的子类型,但基本数据类型的数组不是。如果一个非泛型引用类型是另一个非泛型引用类型的子类型,那么对应的数组类型也有类似的子类型 - 超类型关系,这称为子类型协变关系,但该关系不适用于参数化类型。
可以创建接口类型的数组,但不能实例化接口。例如:
```java
ISafeStack[] iSafeStackArray = new ISafeStack[5];
```
上述代码创建了一个元素类型为 `ISafeStack` 的数组,数组对象可以容纳五个 `ISafeStack` 类型的引用,但这些引用初始化为默认值 `null`。
### 2.3 数组存储检查
数组引用和其他引用一样具有多态行为,但在将对象插入数组时需要进行运行时检查。以下是一个示例:
```java
StackImpl[] stackImplArray = new SafeStackImpl[2]; // (1)
stackImplArray[0] = new SafeStackImpl(10); // (2)
stackImplArray[1] = new StackImpl(20); // (3) ArrayStoreException
```
在编译时,(3) 处的赋值没有问题,但在运行时会抛出 `ArrayStoreException`,因为 `SafeStackImpl[]` 对象不能包含 `StackImpl` 类型的对象。为了在运行时进行数组存储检查,数组会保留其声明的元素类型信息。
### 2.4 数组类型关系 mermaid 流程图
```mermaid
graph TD;
Object --> Object[];
Object --> StackImpl;
Object --> SafeStackImpl;
Object --> IStack;
Object --> ISafeStack;
StackImpl --> StackImpl[];
SafeStackImpl --> SafeStackImpl[];
IStack --> IStack[];
ISafeStack --> ISafeStack[];
StackImpl[] --> Object[];
SafeStackImpl[] --> StackImpl[];
IStack[] --> Object[];
ISafeStack[] --> IStack[];
classDef interface fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
class IStack,ISafeStack interface;
```
## 3. 引用值与转换
### 3.1 转换的上下文
引用值和基本值一样,可以进行赋值、强制类型转换和作为参数传递。转换可以在以下上下文中发生:
- **赋值**
- **方法调用**
- **强制类型转换**
### 3.2 转换规则
基本数据类型的转换规则是:拓宽转换允许,窄化转换需要显式强制类型转换。引用值的转换规则是:沿类型层次结构向上的拓宽转换允许,向下的窄化转换需要显式强制类型转换。即从子类型到其超类型的转换是允许的,其他转换需要显式强制类型转换或非法。引用值没有提升的概念。
### 3.3 引用值赋值转换
在赋值上下文中,允许以下转换:
- **拓宽基本和引用转换**:如 `long` 到 `int`,`Object` 到 `String`。
- **基本值的装箱转换,后跟可选的拓宽引用转换**:如 `Integer` 到 `int`,`Number` 到 `Integer` 到 `int`。
- **基本值包装对象的拆箱转换,后跟可选的拓宽基本转换**:如 `long` 到 `int` 到 `Integer`。
仅对于赋值转换,还有以下情况:
- **非长整型常量表达式的窄化转换,可选装箱**:如 `Byte` 到 `byte` 到 `int`。
拓宽引用转换通常在沿类型层次结构向上赋值时发生,源引用值隐式转换为目标引用类型。例如:
```java
Object obj = "Up the tree"; // 拓宽引用转换:Object <-- String
String str1 = obj; // 不可以,窄化引用转换需要强制类型转换
String str2 = new Integer(10); // 非法,String 和 Integer 无关系
```
源值可以是基本值,此时该值会被装箱到对应基本类型的包装对象中。如果目标引用类型是包装类型的超类型,则会发生拓宽引用转换。例如:
```java
Integer iRef = 10; // 仅装箱
Number num = 10L; // 装箱,后跟拓宽:Number <--- Long <--- long
Object obj = 100; // 装箱,后跟拓宽:Object <--- Integer <--- int
```
### 3.4 引用值赋值规则
基于以下代码说明引用值赋值规则:
```java
SourceType srcRef;
// srcRef 已适当初始化。
DestinationType destRef = srcRef;
```
如果赋值合法,则称 `srcRef` 的引用值可分配(或赋值兼容)给 `DestinationType` 的引用。规则如下:
- **如果 `SourceType` 是类类型**:`srcRef` 中的引用值可以分配给 `destRef` 引用,前提是 `DestinationType` 是以下之一:
- `DestinationType` 是 `SourceType` 子类的超类。
- `DestinationType` 是 `SourceType` 类实现的接口类型。
- 示例:
```java
SafeStackImpl safeStackRef = new SafeStackImpl(10);
Object objRef = safeStackRef; // (1) 总是可行
StackImpl stackRef = safeStackRef; // (2) 子类到超类赋值
IStack iStackRef = stackRef; // (3) StackImpl 实现 IStack
ISafeStack iSafeStackRef = safeStackRef; // (4) SafeStackImpl 实现 ISafeStack
```
- **如果 `SourceType` 是接口类型**:`srcRef` 中的引用值可以分配给 `destRef` 引用,前提是 `DestinationType` 是以下之一:
- `DestinationType` 是 `Object`。
- `DestinationType` 是 `SourceType` 子接口的超接口。
- 示例:
```java
objRef = iStackRef; // (5) 总是可行
iStackRef = iSafeStackRef; // (6) 子接口到超接口赋值
```
- **如果 `SourceType` 是数组类型**:`srcRef` 中的引用值可以分配给 `destRef` 引用,前提是 `DestinationType` 是以下之一:
- `DestinationType` 是 `Object`。
- `DestinationType` 是数组类型,且 `SourceType` 的元素类型可分配给 `DestinationType` 的元素类型。
- 示例:
```java
Object[] objArray = new Object[3];
StackImpl[] stackArray = new StackImpl[3];
SafeStackImpl[] safeStackArray = new SafeStackImpl[5];
ISafeStack[] iSafeStackArray = new ISafeStack[5];
int[] intArray = new int[10];
objRef = objArray; // (7) 总是可行
objRef = stackArray; // (8) 总是可行
objArray = stackArray; // (9) 总是可行
objArray = iSafeStackArray; // (10) 总是可行
objRef = intArray; // (11) 总是可行
// objArray = intArray; // (12) 编译时错误
stackArray = safeStackArray; // (13) 子类数组到超类数组
iSafeStackArray = safeStackArray; // (14) SafeStackImpl 实现 ISafeStack
```
这些赋值规则在编译时强制执行,保证在运行时赋值不会发生类型转换错误,这种转换是类型安全的。
### 3.5 引用值赋值规则表格总结
| 源类型 | 目标类型条件 | 示例 |
| --- | --- | --- |
| 类类型 | 目标类型是源类型子类的超类或源类型实现的接口类型 | `Object objRef = safeStackRef;` 等 |
| 接口类型 | 目标类型是 `Object` 或源类型子接口的超接口 | `objRef = iStackRef;` 等 |
| 数组类型 | 目标类型是 `Object` 或数组类型且元素类型可分配 | `objRef = objArray;` 等 |
### 3.6 引用值赋值转换 mermaid 流程图
```mermaid
graph LR;
classDef class fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
classDef interface fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
SafeStackImpl:::class --> StackImpl:::class;
SafeStackImpl:::class --> ISafeStack:::interface;
StackImpl:::class --> IStack:::interface;
ISafeStack:::interface --> IStack:::interface;
SafeStackImpl:::class --> Object:::class;
StackImpl:::class --> Object:::class;
IStack:::interface --> Object:::class;
ISafeStack:::interface --> Object:::class;
SafeStackImpl[] --> StackImpl[];
StackImpl[] --> Object[];
ISafeStack[] --> IStack[];
IStack[] --> Object[];
SafeStackImpl[] --> ISafeStack[];
SafeStackImpl[] --> Object[];
```
## 4. 方法调用转换涉及引用
引用值赋值的转换规则也适用于方法调用转换,但不包括非长整型常量表达式的窄化转换。这是合理的,因为 Java 中的参数是按值传递的,要求实际参数的值必须可分配给兼容类型的形式参数。
以下是一个方法调用转换的示例:
```java
interface IStack {
// 方法声明
}
interface ISafeStack extends IStack {
// 方法声明
}
class StackImpl implements IStack {
// 方法实现
}
class SafeStackImpl extends StackImpl implements ISafeStack {
// 方法实现
}
public class ReferenceConversion {
public static void main(String[] args) {
StackImpl stackRef = new StackImpl();
SafeStackImpl safeStackRef = new SafeStackImpl();
IStack iStackRef = stackRef;
ISafeStack iSafeStackRef = safeStackRef;
SafeStackImpl[] safeStackArray = new SafeStackImpl[5];
ISafeStack[] iSafeStackArray = new ISafeStack[5];
System.out.println("First call:");
sendParams(stackRef, safeStackRef, iStackRef, safeStackArray, iSafeStackArray);
System.out.println("Second call:");
sendParams(iSafeStackArray, stackRef, iSafeStackRef, stackArray, safeStackArray);
}
public static void sendParams(Object objRefParam, StackImpl stackRefParam,
IStack iStackRefParam, StackImpl[] stackArrayParam,
final IStack[] iStackArrayParam) {
System.out.println(objRefParam.getClass());
System.out.println(stackRefParam.getClass());
System.out.println(iStackRefParam.getClass());
System.out.println(stackArrayParam.getClass());
System.out.println(iStackArrayParam.getClass());
}
}
```
在这个示例中,`sendParams` 方法的形式参数类型和方法调用时的实际参数类型遵循前面讨论的赋值规则,保证了参数传递的合法性。
### 4.1 方法调用转换示例表格总结
| 调用序号 | 实际参数类型 | 形式参数类型 | 合法性依据 |
| --- | --- | --- | --- |
| 第一次调用 | `StackImpl, SafeStackImpl, IStack, SafeStackImpl[], ISafeStack[]` | `Object, StackImpl, IStack, StackImpl[], IStack[]` | 符合引用值赋值规则 |
| 第二次调用 | `ISafeStack[]
0
0
相关推荐









