概念
Java中的内部类与匿名内部类都是Java面向对象特性的重要组成部分,它们都具有不同的特性和使用场景。以下是关于内部类与匿名内部类的详细区别及应用场景:
内部类(Inner Class)
-
定义:
- 内部类是定义在另一个类内部的类,它可以访问包含它的外部类的所有成员,包括私有成员(private)。
-
分类:
- 成员内部类(Member Inner Class):定义在外部类的成员位置上,可以有访问修饰符(如public、protected、default、private),可以是非静态或静态的。
- 非静态内部类(Non-static inner class):每个非静态内部类实例与外部类实例关联,可以直接访问外部类的实例成员。
- 静态内部类(Static nested class):不需要与外部类实例关联,只能访问外部类的静态成员。
- 局部内部类(Local Inner Class):定义在方法或者作用域内的内部类,只能在其所在的方法或作用域内可见,且可以访问其作用域内的final变量。
- 成员内部类(Member Inner Class):定义在外部类的成员位置上,可以有访问修饰符(如public、protected、default、private),可以是非静态或静态的。
-
使用场景:
- 提供封装,允许内部类直接访问外部类的私有属性和方法。
- 实现逻辑相关的类组织在一起,提高代码的可读性。
- 在某些设计模式中,如装饰器模式、适配器模式等。
匿名内部类(Anonymous Inner Class)
-
定义:
- 匿名内部类是没有名字的内部类,它是内部类的一种特殊形式,通常用作一次性使用的类实现。
- 匿名内部类没有显式的类声明,而是直接通过new操作符后跟一个抽象类或接口的引用,并立即实现该类或接口的方法。
-
特点:
- 匿名内部类必须继承一个类或者实现一个接口。
- 匿名内部类不允许有构造函数,但会自动继承父类构造函数或者实现接口的抽象方法。
- 使用匿名内部类时,由于没有名称,因此无法被多次实例化,只能在同一处创建并初始化。
- 如果匿名内部类要访问外部类的变量,则这些变量要么是final的局部变量,要么是外部类的成员变量。
-
使用场景:
- 当只需要单次实现某个接口或继承某个类,而不需要为此创建独立的类时。
- 在事件监听处理、多线程编程以及Lambda表达式可用之前,匿名内部类常用来简化代码,例如作为
Runnable
接口的实现创建新线程,或者作为OnClickListener
实现按钮点击事件。
例如:
public class OuterClass {
public void someMethod() {
// 匿名内部类示例,实现Runnable接口
new Runnable() {
@Override
public void run() {
System.out.println("Running in anonymous class");
}
}.run();
// 非匿名内部类示例
class InnerClass {
void doSomething() {
System.out.println("Doing something from inner class");
}
}
InnerClass inner = new InnerClass();
inner.doSomething();
}
}
内部类提供了一种在类内部嵌套定义类的方式,增强了代码的封装性和复用性,而匿名内部类则进一步简化了仅需一次性实现类或接口需求的场景。随着Java 8引入Lambda表达式,许多原本由匿名内部类完成的功能得到了更简洁的替代方案。
内部类访问外面的属性和方法调用
对于外部类的属性和方法的访问,无论是内部类还是匿名内部类都有相应的规则:
-
非静态内部类:
- 可以直接访问外部类的所有成员,包括私有属性和方法,无需任何修饰符。
示例:
public class OuterClass { private String outerStr = "Hello"; class InnerClass { void displayOuterProperty() { System.out.println(outerStr); // 直接访问外部类的私有属性 anotherMethod(); // 直接调用外部类的公共方法 } private void anotherMethod() { // ... } } }
-
静态内部类:
- 只能访问外部类的静态成员,不能直接访问外部类的实例成员,如果需要访问实例成员,需要通过外部类的实例来调用。
示例:
public class OuterClass { private static String staticStr = "World"; private String instanceStr = "Java"; static class StaticInnerClass { void displayStaticProperty() { System.out.println(staticStr); // 访问外部类的静态属性 } } } // 要访问实例属性,需要外部类实例: OuterClass outer = new OuterClass(); System.out.println(outer.instanceStr);
-
匿名内部类:
- 匿名内部类的行为与非静态内部类类似,它也能直接访问外部类的所有实例成员,即使它本身可能是一个静态上下文中的类。
示例:
public class OuterClass { private String message = "Hello, World!"; public void demonstrateAnonymousClass() { new Thread(new Runnable() { @Override public void run() { System.out.println(message); // 匿名内部类访问外部类的实例属性 } }).start(); } }
总结来说,无论是内部类还是匿名内部类,都可以方便地访问外部类的属性和方法,这是Java内部类设计的一大优势,有助于提高代码的内聚性和模块化程度。
Lamda表达式
Java中的Lambda表达式与匿名内部类有紧密的联系,同时也有明显的区别:
联系:
-
目的相似性:两者都可以用于简化代码,尤其是当需要快速创建一个只执行一次特定任务的对象时,比如实现某个接口的回调方法。
-
访问权限:Lambda表达式和匿名内部类都可以直接访问外部作用域中“effectively final”(即不会被重新赋值的最终变量)的局部变量,以及外部类的成员变量(包括实例变量和类变量)。
-
接口实现:匿名内部类可以通过实现接口的方式来创建对象,而Lambda表达式正是匿名内部类的一种简写形式,尤其适用于函数式接口(只有一个抽象方法的接口)。
区别:
-
语法简洁性:Lambda表达式比匿名内部类的语法更加简洁,它消除了类型声明和关键字
new
,使得代码更加紧凑和易于阅读。 -
使用范围:Lambda表达式只能用于实现函数式接口,而匿名内部类不仅可以实现接口,还可以继承抽象类或实现多个接口(尽管这需要重写所有抽象方法)。
-
对默认方法的调用:匿名内部类可以调用接口中定义的默认方法,而Lambda表达式主体(代码块)不直接支持调用接口的默认方法(虽然可以在实现接口的方法体内部间接调用)。
-
实例化方式:Lambda表达式本质上是一种轻量级的匿名内部类,编译器会根据上下文自动推断出对应的函数式接口类型,并将其转换为对应的匿名内部类实现,但在Lambda表达式中你不会看到具体的类定义结构。
Lambda表达式是Java语言对匿名内部类概念的一种进化,旨在进一步简化代码,特别是针对函数式编程风格的设计,同时鼓励开发者采用更简洁的函数式编程范式。而在那些需要多重继承或者实现多个接口的情况下,匿名内部类仍然有其独特的用途。
对了java8才支持lamda表达式
在安卓开发中的应用场景
在Android开发中,Lambda表达式和匿名内部类都有广泛的应用场景,下面列举几个常见的例子:
Lambda表达式在Android开发中的应用场景:
-
事件处理:
- Android SDK中的许多UI组件事件监听器都可以使用Lambda表达式进行简化,例如
OnClickListener
、OnLongClickListener
等。
button.setOnClickListener(v -> { Toast.makeText(this, "Button clicked!", Toast.LENGTH_SHORT).show(); });
- Android SDK中的许多UI组件事件监听器都可以使用Lambda表达式进行简化,例如
-
异步操作:
- 使用
Handler
、Runnable
、Thread
、ExecutorService
等进行异步编程时,Lambda表达式可以简化任务的定义。
new Thread(() -> { // 异步任务逻辑... }).start(); executor.execute(() -> { // 执行的任务... });
- 使用
-
Adapter适配器:
- 在创建RecyclerView.Adapter或者其他列表适配器时,可以使用Lambda表达式简化ViewHolder的绑定逻辑。
recyclerView.setAdapter(new RecyclerView.Adapter<ViewHolder>() { @Override public void onBindViewHolder(ViewHolder holder, int position) { // 使用Lambda表达式简化此部分代码... item -> holder.bind(item); } });
-
LiveData、Flow或RxJava响应式编程:
- 在订阅观察者模式下,可以简洁地编写观察者回调。
liveData.observe(this, data -> { // 更新UI逻辑... });
匿名内部类在Android开发中的应用场景(虽然现在大部分已被Lambda表达式取代):
-
在API级别较低不支持Lambda的设备上:
- 对于老版本Android系统,在不支持Lambda的环境中,匿名内部类是必需的。
-
复杂事件处理:
- 在Lambda表达式不足以覆盖全部功能的情况下,如实现复杂的
View.OnTouchListener
接口,需要重写多个方法时,仍需使用匿名内部类。
- 在Lambda表达式不足以覆盖全部功能的情况下,如实现复杂的
-
自定义View:
- 创建自定义视图时,有时需要覆盖一些复杂的接口方法,例如
onDraw()
、onMeasure()
等,这时匿名内部类可以帮助我们临时添加额外的逻辑。
- 创建自定义视图时,有时需要覆盖一些复杂的接口方法,例如
-
多接口实现:
- 若需要同时实现多个接口,匿名内部类仍然是必要的选择,因为Lambda表达式不能直接实现多个接口。
总的来说,随着Java 8及以上版本在Android Studio中的普及,Lambda表达式已经成为现代Android开发中简化代码、提高可读性的首选工具,尤其是在处理简单回调和单个接口实现时。但对于较复杂的场景,匿名内部类依然有其应用空间。