一、内部类
内部类(Inner Class)是定义在另一个类(外部类)内部的类。内部类可以访问外部类的成员(包括私有成员),因为它们位于同一个类中。内部类可以是静态的,也可以是非静态的。非静态内部类(也称为实例内部类)隐式地持有对外部类实例的引用,而静态内部类则不会持有外部类的实例引用。
内部类可以分为以下四种类型:
- 成员内部类:在外部类的成员位置定义的类,可以被任意访问修饰符修饰。成员内部类可以访问外部类的所有成员。
- 静态内部类:在外部类的成员位置定义的用static修饰的类。静态内部类不能访问外部类的非静态成员,但可以访问外部类的静态成员。
- 局部内部类:在外部类的方法体内定义的类。局部内部类只能访问外部类的成员以及方法内的final局部变量。
- 匿名内部类:没有名称的内部类,通常用于实现接口或继承类的简化版本。匿名内部类可以访问外部类的成员以及方法内的final局部变量。
类的五大成员指的是一个类的五种基本组成部分:
- 属性(Field):类的属性是用于描述类的状态或特征的变量。属性可以被赋予不同的访问修饰符(如public、private、protected),限制其访问范围。
- 方法(Method):类的方法是用于实现类的行为或操作的函数。方法可以被赋予不同的访问修饰符,限制其访问范围。方法可以是实例方法(需要对象调用)或静态方法(可以直接通过类名调用)。
- 构造方法(Constructor):类的构造方法是用于初始化类的实例的特殊方法。构造方法的名称与类名相同,没有返回类型。一个类可以有多个构造方法,以支持不同方式的初始化。
- 内部类(Inner Class):如前所述,内部类是定义在另一个类内部的类。内部类可以访问外部类的成员,也可以有自己的成员。
- 代码块(Block):代码块是一组在类中定义的语句。代码块可以是普通的代码块、静态代码块或实例代码块。静态代码块在类被加载时执行,实例代码块在创建类的实例时执行。
五种成员共同构成了类的基本结构,它们分别表示类的状态(属性)、行为(方法)、初始化过程(构造方法)、嵌套结构(内部类)以及执行逻辑(代码块)。这些成员相互协作,使得类能够实现其预定功能,满足应用程序的需求。
通过定义不同的属性、方法、构造方法、内部类和代码块,开发者可以设计出具有特定功能和行为的类。这些类可以相互交互,形成一个完整的应用程序。在面向对象编程中,类是基本的组织单元,五种成员使得类具有丰富的表现力和灵活性。
内部类(Inner Class)在某些特定场景下可以提供更好的封装和组织代码的能力。以下是一些内部类的应用场景和原因:
-
当一个类只在另一个类中有用时,可以将其定义为内部类,以避免不必要的外部访问。这有助于减少代码的耦合度,提高类的内聚性。
-
内部类可以访问外部类的成员(包括私有成员),这使得内部类可以与外部类更紧密地协作,实现某些特定功能。
-
内部类可以用于实现某些设计模式,例如工厂模式、装饰器模式、观察者模式等。在这些情况下,内部类可以提供更清晰、简洁的代码结构。
-
当需要实现一个接口,但不希望创建一个完整的类时,可以使用匿名内部类。这种方法常用于创建回调函数、事件处理器等。
定义内部类的时机主要取决于其使用场景。如果你觉得定义一个内部类能够提高代码的可读性、可维护性,或者更好地实现某种设计模式,那么就可以考虑定义一个内部类。
内部类的访问特点如下:
-
内部类可以访问外部类的所有成员(包括私有成员)。
-
静态内部类不能访问外部类的非静态成员,但可以访问外部类的静态成员。
-
非静态内部类(实例内部类)隐式地持有一个对外部类实例的引用。因此,在创建非静态内部类的实例时,需要先创建外部类的实例。例如:OuterClass.InnerClass inner = outerInstance.new InnerClass();
-
外部类可以访问内部类的成员,但需要通过内部类的实例来访问。对于静态内部类,可以直接通过类名访问其静态成员。
-
内部类可以使用任意访问修饰符,如public、private、protected。访问修饰符可以限制内部类在其他类中的可见性。
二、成员内部类
成员内部类是定义在外部类中的非静态内部类。在编写成员内部类时,需要注意以下几点:
-
成员内部类可以访问外部类的所有成员(包括私有成员),因为它们处于同一个外部类中。然而,外部类不能直接访问内部类的成员,需要通过内部类的实例来访问。
-
成员内部类不能定义任何静态成员(除非它们是编译时常量),因为成员内部类与外部类的实例关联。
-
当成员内部类和外部类具有相同名称的成员时,可以使用外部类名.this.成员名来引用外部类的成员。
-
在成员内部类中,不能定义与外部类中的成员同名的局部变量。
要获取成员内部类的实例,有以下几种方式:
- 在外部类的方法中创建成员内部类的实例:
public class Outer {
public class Inner {
// ...
}
public void createInner() {
Inner inner = new Inner();
}
}
- 在外部类的外部创建成员内部类的实例。由于成员内部类与外部类的实例关联,因此需要先创建外部类的实例,然后使用外部类实例.new 内部类名()的语法创建内部类实例:
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
}
}
- 如果内部类与外部类需要相互访问对方的成员,可以将外部类的实例传递给内部类的构造函数,然后在内部类中保存一个对外部类的引用。这样,内部类可以通过引用访问外部类的成员:
public class Outer {
private String name;
public class Inner {
private Outer outer;
public Inner(Outer outer) {
this.outer = outer;
}
public void accessOuter() {
System.out.println("外部类的 name: " + outer.name);
}
}
}
三、静态内部类
编写静态内部类时需要注意以下几点:
- 静态内部类使用 static 关键字进行声明。它不需要外部类的实例就可以被实例化,因此静态内部类不持有外部类的实例引用。
- 静态内部类可以访问外部类的静态成员,但无法直接访问外部类的非静态成员。如果需要访问外部类的非静态成员,可以通过传递外部类的实例给静态内部类来实现。
- 静态内部类可以拥有自己的静态和非静态成员。但请注意,如果静态内部类中有静态成员,这些成员应该与外部类中的静态成员有明确的区分,以避免混淆。
获取静态内部类的方式:
- 直接实例化:由于静态内部类不依赖于外部类的实例,您可以直接实例化它。例如:
Outer.StaticInner staticInner = new Outer.StaticInner();
- 通过外部类的静态方法:如果您希望在外部类中提供一个方便的方式来获取静态内部类的实例,可以在外部类中定义一个静态方法。例如:
public class Outer {
public static class StaticInner {
// ...
}
public static StaticInner createStaticInner() {
return new StaticInner();
}
}
然后,您可以通过调用此静态方法获取静态内部类的实例:
Outer.StaticInner staticInner = Outer.createStaticInner();
四、局部内部类
编写局部内部类时需要注意以下几点:
- 局部内部类定义在一个方法或作用域内,因此它的作用范围仅限于该方法或作用域。
- 局部内部类不能使用访问修饰符(如 public、private 和 protected),因为它的作用范围仅限于声明它的方法或作用域。
- 局部内部类可以访问外部类的所有成员,但对于声明它的方法内的局部变量,只能访问被声明为 final 或实际上是 final 的变量(从 Java 8 开始,局部变量可以隐式地被视为 final,只要它们的值没有被修改)。
获取局部内部类的方式:
由于局部内部类的作用范围仅限于声明它的方法或作用域,因此它只能在这些范围内实例化。以下是一个简单的示例:
public class Outer {
public void createLocalInner() {
class LocalInner {
public void sayHello() {
System.out.println("Hello from LocalInner!");
}
}
LocalInner localInner = new LocalInner();
localInner.sayHello();
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.createLocalInner();
}
}
在这个示例中,局部内部类 LocalInner 在 createLocalInner() 方法中定义。在这个方法的范围内,我们可以实例化 LocalInner 类并调用它的方法。但是,如果我们试图在 createLocalInner() 方法之外实例化 LocalInner,将会导致编译错误。要获取局部内部类,需要在其声明的方法或作用域内实例化它。
局部内部类主要用于在某个方法或作用域中实现特定功能,而这个功能不需要在类的其他地方使用。使用局部内部类的一些典型应用场景如下:
-
封装实现细节:局部内部类可以帮助您将某个方法的具体实现细节封装起来,使得方法的外部逻辑更加清晰和简洁。局部内部类可以访问外部类的成员和方法内的局部变量,这使得它们能够很好地与外部环境进行交互。
-
简化代码:如果一个类的功能只在一个方法中使用,那么将它定义为局部内部类可以避免在外部类中创建不必要的成员。这使得代码更加简洁,并减少了类的维护成本。
-
增强代码可读性:局部内部类将实现细节与外部类的其他部分分离,这有助于提高代码的可读性。因为局部内部类的作用范围仅限于声明它的方法,其他开发者可以更容易地理解代码的结构和逻辑。
-
实现回调和匿名内部类:在某些情况下,您可能需要在方法中实现一个回调或接口的实例。这时,局部内部类可以很方便地实现这样的功能。另外,Java 还支持匿名内部类,这是一种特殊的局部内部类,它们可以更简洁地表示某些功能。
例如,假设您正在开发一个图形用户界面(GUI)应用程序,并需要为一个按钮添加点击事件监听器。这个监听器可能只在一个特定的方法中使用,所以您可以使用局部内部类来实现这个监听器,而无需在外部类中创建一个完整的成员类。这样,您的代码会更加简洁,且更容易理解。
局部内部类在特定方法中实现特定功能时非常有用。它们可以简化代码、提高代码可读性,并帮助封装实现细节。
五、匿名内部类
匿名内部类是一种没有名字的内部类,它通常用于简化代码,主要用于实现接口或继承类的一个实例。在编写匿名内部类时,需要注意以下几点:
-
匿名内部类不能有构造器:由于匿名内部类没有名字,因此无法定义构造器。匿名内部类的实例化与初始化都在一条语句中完成。
-
匿名内部类只能实现一个接口或继承一个类:匿名内部类可以实现一个接口或继承一个类,但不能同时实现多个接口或继承多个类。
-
匿名内部类不能定义静态成员:由于匿名内部类没有类名,因此无法在外部访问其静态成员。因此,匿名内部类不能定义静态成员。
-
匿名内部类可以访问外部类的成员和方法:匿名内部类可以访问外部类的成员变量和方法,但只能访问声明为 final 或实际上是 final 的局部变量。
获取匿名内部类的方式:
- 实现接口:匿名内部类可以通过实现一个接口来创建。以下是一个实现 Runnable 接口的匿名内部类的示例:
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Running...");
}
};
- 继承类:匿名内部类可以通过继承一个类来创建。以下是一个继承 Thread 类的匿名内部类的示例:
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("Thread running...");
}
};
在使用匿名内部类时,应确保代码保持简洁和易读。如果内部类的代码变得复杂,建议将其转换为常规的内部类或独立的类。
匿名内部类常用于简化代码和提高代码的可读性。以下是一些常见的使用场景和示例:
- 实现接口:当需要一个简单的接口实现时,可以使用匿名内部类。例如,实现 java.util.Comparator 接口对集合进行自定义排序:
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Carol");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
System.out.println(names);
这里使用匿名内部类实现了一个自定义的字符串长度比较器。
- 继承类:当需要一个简单的类扩展时,可以使用匿名内部类。例如,自定义一个 java.lang.Thread 子类:
Thread customThread = new Thread() {
@Override
public void run() {
System.out.println("Custom thread running...");
}
};
customThread.start();
这里使用匿名内部类继承了 Thread 类,并重写了 run 方法。
- 事件监听器:在图形用户界面(GUI)编程中,匿名内部类常用于实现事件监听器。例如,使用 Java Swing 添加一个按钮的点击事件监听器:
JButton button = new JButton("Click me");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked!");
}
});
这里使用匿名内部类实现了一个 ActionListener,用于处理按钮点击事件。
这些示例展示了匿名内部类在简化代码和提高可读性方面的优势。然而,如果匿名内部类的实现变得复杂,建议将其转换为常规的内部类或独立的类,以保持代码的结构清晰。
六、迭代器
迭代器(Iterator)是一种设计模式,用于在不暴露集合(如列表、树、图等)内部表示的情况下,顺序地访问集合中的元素。迭代器模式提供了一种统一的方式来遍历各种类型的集合,而无需关心其内部实现。
迭代器模式的核心组件是迭代器接口,该接口通常定义了以下几个方法:
- hasNext(): 检查是否还有下一个元素。
- next(): 返回下一个元素,并将迭代器指针移动到下一个位置。
- remove(): 移除当前指针指向的元素(这个方法是可选的,不是所有的迭代器都需要实现它)。
在 Java 中,迭代器模式已经被广泛地应用在 Java 集合框架中。例如,java.util.Iterator接口就是 Java 标准库中的一个典型实现。几乎所有 Java 集合类(如ArrayList、LinkedList、HashSet、TreeSet等)都支持迭代器,可以使用迭代器来遍历集合中的元素。
迭代器模式的应用场景包括:
-
当需要遍历一个集合,但又不想暴露其内部实现时,可以使用迭代器模式。这可以提高集合类的封装性,使其内部实现可以独立地变化,而不影响客户端代码。
-
当需要为一个集合提供多种遍历方式时,可以使用迭代器模式。例如,在树形结构中,可以使用深度优先遍历或广度优先遍历。通过为每种遍历方式提供一个迭代器,可以让客户端根据需要选择合适的遍历策略。
-
当需要让多个聚合对象(如集合、数据库、文件等)使用相同的遍历接口时,可以使用迭代器模式。这可以简化客户端代码,并提高代码的可重用性和可维护性。
总之,迭代器模式为顺序访问集合中的元素提供了一种统一、灵活的方法,使得客户端可以更方便地处理各种类型的集合,而无需关心它们的内部实现。
七、编译时和运行时
运用多态的概念创建一个对象,对于编译时看等号左边,运行时看等号右边,我们可以分两种情况来理解:
- 方法调用:在多态中,编译时确实会检查等号左边的引用类型是否具有要调用的方法。然而,在运行时,JVM 会根据等号右边的实际对象类型来确定应调用哪个具体的方法实现。这称为动态方法分派(Dynamic Method Dispatch)。
例如:
class Animal {
void makeSound() {
System.out.println("The animal makes a sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("The dog barks");
}
}
public static void main(String[] args) {
Animal myAnimal = new Dog();
myAnimal.makeSound(); // 输出 "The dog barks"
}
这里,myAnimal 的引用类型是 Animal,但实际指向的对象类型是 Dog。编译时,编译器检查 Animal 类是否具有 makeSound() 方法。运行时,JVM 根据实际对象类型 Dog 来调用相应的 makeSound() 方法实现。
- 成员变量访问:然而,对于成员变量的访问,多态并不适用。成员变量的访问只与引用类型(等号左边)有关,而与实际对象类型(等号右边)无关。
例如:
class Animal {
String sound = "The animal makes a sound";
}
class Dog extends Animal {
String sound = "The dog barks";
}
public static void main(String[] args) {
Animal myAnimal = new Dog();
System.out.println(myAnimal.sound); // 输出 "The animal makes a sound"
}
这里,尽管 myAnimal 实际指向的对象类型是 Dog,但访问的成员变量 sound 仍然是 Animal 类中定义的。
多态在方法调用时适用编译时看等号左边,运行时看等号右边,但在成员变量访问时则不适用。