引言
本文主要还是记录下内部类的基础语法,具体的作用等接触更多的代码之后回来小结
定义与用法
内部类,顾名思义就是在一个类的内部再定义一个类
public class Outer {
private String id;
public Outer(String id) {
this.id = id;
}
/**
* 内部类可以是任意的访问权限
*/
public class Inner {
// 可以任意的访问外部的属性
private String innerId = "inner" + id;
}
}
访问权限
- 内部类本身可以设置任意的访问权限(
private
,default
,protected
,public
) - 内部类可以访问外部类任意权限的属性或方法(普通内部类)
正是由于内部类可以定义为private
,所以内部类的作用之一就是可以对外屏蔽类的存在
这一点在内部类实现接口时作用会比较明显,例如说Iterator
就是一个比较典型的例子
对于客户端来说,你拿到的是一个List<String> list = new ArrayList<>(); Iterator<String> iterator = list.iterator();
Iterator
,而非ArrayList
的内部类对象Itr
,你能访问的也只有Iterator
接口所提供的方法
也就是说,ArrayList
提供了迭代器的功能,但是完全对客户端屏蔽其实现 - 非静态内部类不能定义静态成员,除非它是final(并且是编译期常量)的
为什么非静态内部类中不能定义静态成员,为什么final又是可以的,参考下面这篇文章:
为什么非静态内部类中不能有static属性的变量,却可以有static final属性的变量? - 静态内部类不能访问外部类的非静态成员(就好比说静态方法不能访问非静态成员一个意思)
内外通信
这一小结主要就是说明访问外部属性以及创建内部类对象的方法
.this
普通的内部类(非静态内部类)之所以能够访问外部类的所有属性,肯定是因为有一个应用指向了外部类
如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟 .this --- 《OnJava8》
- 如果直接使用this,则this指向的是内部类的对象,外部类 + .this才是指向外部类的对象
- 访问外部属性时并非都要加上
.this
例如下面的代码示例中我们使用Outer.this.getId()
,其实直接调用getId()即可
但有时这时必要的,比如说内部类和外部类的方法、属性重名了,我们在访问外部类时就必须使用 【外部类 + .this】,否则就无限循环了
public class Outer {
private String id;
public Outer(String id) {
this.id = id;
}
private String getId() {
return this.id;
}
private void setId(String id) {
this.id = id;
}
/**
* 内部类可以是任意的访问权限
*/
public class Inner {
// 可以任意的访问外部的属性
private String innerId = "inner" + id;
public String getOutId() {
return getId();
}
public void setId(String id) {
Outer.this.setId(id);
}
}
}
.new
创建内部类对象时必须要使用外部类的对象进行创建
public class OuterTest {
public static void main(String[] args) {
Outer outer = new Outer("张三");
Outer.Inner inner = outer.new Inner();
System.out.println("inner class getId: " + inner.getId());
}
}
局部内部类
有时一个内部类的对象只在某个方法中使用,我们可以在方法中定义内部类
局部内部类的另一个优点就是可以访问方法中的局部变量(必须事实上是final的,即在匿名类外部赋值后不能再改变了)
匿名内部类
在局部内部类的基础上再进一步,有时我们不需要创建多个内部类对象,只需要一个,那么使用匿名内部类会更加方便
例如我们在Java多线程中使用Runnable
接口时就是如此。
demo
假如说有一个计算接口
public interface Calculate {
int cal(Integer num1, Integer num2);
}
我们想实现一个加法器
- 常规内部类写法
public class Outer3 {
class Adder implements Calculate{
@Override
public int cal(Integer num1, Integer num2) {
return num1 + num2;
}
}
public Calculate getAdder() {
return new Adder();
}
}
测试一下:
Outer3 outer3 = new Outer3();
Calculate adder = outer3.getAdder();
int r1 = adder.cal(1,2); // 3
- 匿名内部类写法
public class Outer3 {
public Calculate getAdder() {
return new Calculate() {
@Override
public int cal(Integer num1, Integer num2) {
return num1 + num2;
}
};
}
}
同样的测试:
Outer3 outer3 = new Outer3();
Calculate adder = outer3.getAdder();
int r1 = adder.cal(1,2); // 3
- 匿名内部类 + Java8
由于我们的Calculate
只有一个接口,所以我们还可以用Java8的lambda表达式再简化一下
public class Outer3 {
public Calculate getAdder() {
return (num1, num2) -> num1 + num2;
}
}
测试方法还是不变
Outer3 outer3 = new Outer3();
Calculate adder = outer3.getAdder();
int r1 = adder.cal(1,2); // 3
- 直接写在方法里面
当这个内部类的对象并不需要复用的地方,我们也可以直接在使用到它的地方编写匿名内部类对象
Calculate adder = (num1, num2) -> num1 + num2;
int calRes = adder.cal(1,2); // 3
初始化
匿名内部类因为没有名字,所以你没有办法通过构造函数进行初始化,但我们还是有一些其他的方法来达到相同的效果
- 继承一个基类
还是用上面那个加法器的例子
public abstract class AbstractCalculate implements Calculate {
public int baseNum;
public AbstractCalculate(int i) {
baseNum = i;
}
}
测试一下
// 此处的 1 可修改为从外部传参
Calculate adder = new AbstractCalculate(1) {
@Override
public int cal(Integer num1, Integer num2) {
return num1 + num2 + baseNum;
}
};
int res = adder.cal(1,2); // 4
即通过父类来进行初始化并使用父类的成员
- 代码块
继承的方式要求必须有一个父类,有一定的限制,另外一种方法就是通过代码块的方式
public class Outer3 {
public Calculate getAdder() {
return (num1, num2) -> num1 + num2;
}
// 这里我们在初始化的时候做一些复杂的事情,比如初始化一个对象
public Calculate getAdderWithInit(int base) {
Calculate adder = new Calculate() {
Calculate _adder;
{
_adder = getAdder();
}
@Override
public int cal(Integer num1, Integer num2) {
return _adder.cal(num1,num2) + base;
}
};
return adder;
}
}
测试一下:
Calculate adder = new Outer3().getAdderWithInit(2);
int res = adder.cal(1,2); // 5
静态内部类
有时我们只是为了把一个类隐藏在另一个类中,并不需要内部类应用外部类的对象,此时我们就可以将内部类声明为static的
普通的内部类对象隐式地保存了一个引用,指向创建它的外部类对象。然而,当内部类是 static 的时,就不是这样了。
嵌套类(静态内部类)意味着:
- 创建嵌套类的对象时,不需要其外部类的对象。
- 不能从嵌套类的对象中访问非静态的外部类对象。
嵌套类与普通的内部类还有一个区别:
普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有 static 数据和 static 字段,也不能包含嵌套类。
但是嵌套类可以包含所有这些东西:
接口内部类
嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是 public 和 static 的。因为类是 static 的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外部接口
如果你想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便
---- 《OnJava8》
内部类作用小结
命名
使用内部类命名可以避免命名重复的问题
例如Element这样一个类,你在很多类中可能都会使用到,为了避免重复你可能会使用AElement,BElement这种方式,但是使用内部类则可以使代码结构更加清晰一些
代码结构
- 当两个类的关系特别紧密时,通过内部类可以使得代码结构更加清晰
- 同时外部类可以少传递很多参数,因为内部类可以直接访问外部类的成员
如果不使用外部类,我们就需要使用组合的方式来实现对象的复用,相比使用内部类的缺点:
- 内部类的对象其实只有外部类会使用,但如果不使用内部类的方式,你就无法用private来修饰类,最多只能用default,即将这个类暴露给其他类了
- 外部类使用内部类的对象方法时需要传递很多参数过去
访问控制
即在访问权限中提到过的,可以隐藏内部类,当我的内部类实现了某个接口时,则相当于外部类隐藏了接口的实现,比较典型的就是Iterator
"多重继承"
每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。 ---- 《OnJava8》
- 内部类实现接口:如果内部类只是实现接口,那么由外部类自己实现,功能上其实并没有太大的区别
- 内部类继承抽象类:这个就只有内部类才能完成了,因为Java是单继承的体系
参考资料
《OnJava8》
《Java核心技术:卷1》