内部类是指在一个外部类的内部再定义一个类。类名不需要和文件夹相同。
(注意,这里的外部类就是最常见的普通类,只是为了对应于内部类,才说成是“内部类”)
内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类, 与虚拟机无关。对于一个名为outer
的外部类和其内部定义的名为inner
的内部类。编译完成后出现outer.class
和outer$inner.class
两类。所以内部类的成员变量/方法名可以和外部类的相同。
一 成员内部类
成员内部类,就是作为外部类的成员,可以直接使用外部类的所有成员和方法,即使是private的。同时外部类要访问内部类的所有成员变量/方法,则需要通过内部类的对象来获取。
要注意的是,成员内部类不能含有static的变量和方法。因为成员内部类需要先创建了外部类,才能创建它自己的。
在成员内部类要引用外部类对象时,使用outer.this来表示外部类对象;
创建内部类对象,可以使用outer.inner obj = outerobj.new inner();
public class Outer {
public class Inner {
public void print(String str) {
System.out.println(str);
}
}
public Inner getInner() {
return new Inner();
}
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.print("Outer.new");
inner = outer.getInner();
inner.print("Outer.get");
}
}
运行结果:
Outer.new
Outer.get
二 局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部内部类
int age =0;
}
return new Woman();
}
}
注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
三 静态内部类
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
class Outter {
public Outter() {
System.out.println("Outter constructor.");
}
static class Inner {
public Inner() {
System.out.println("Inner constructor.");
}
}
}
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
运行结果:
Inner constructor.
四 匿名内部类
(一)定义
顾名思义,匿名内部类是没有名字的内部类。
因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。
使用匿名内部类有个前提条件:必须继承一个父类或实现一个接口。
(二)例子
例1:不使用匿名内部类来实现抽象方法
abstract class Person {
public abstract void eat();
}
class Child extends Person {
public void eat() {
System.out.println("eat something");
}
}
public class Demo {
public static void main(String[] args) {
Person p = new Child();
p.eat();
}
}
运行结果:eat something
可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用。
但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?
这个时候就可以考虑引入匿名内部类。
例2:匿名内部类的基本实现
abstract class Person {
public abstract void eat();
}
public class Demo {
public static void main(String[] args) {
Person p = new Person() {
public void eat() {
System.out.println("eat something");
}
};
p.eat();
}
}
运行结果:eat something
可以看到,我们直接将抽象类Person中的方法在大括号中实现了。这样便可以省略一个类的书写。
匿名内部类还能用于接口上。
例3:在接口上使用匿名内部类
interface Person {
public void eat();
}
public class Demo {
public static void main(String[] args) {
Person p = new Person() {
public void eat() {
System.out.println("eat something");
}
};
p.eat();
}
}
运行结果:eat something
匿名内部类最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或者实现Runnable接口。
例4:Thread类的匿名内部类实现
public class Demo {
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
}
};
t.start();
}
}
运行结果:1 2 3 4 5
例5:Runnable接口的匿名内部类实现
public class Demo {
public static void main(String[] args) {
Runnable r = new Runnable() {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
}
};
Thread t = new Thread(r);
t.start();
}
}
运行结果:1 2 3 4 5
(三)作用
1)一个类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是覆盖。
2)只是为了获得一个对象实例,不需要知道其实际类型。
3)类名没有意义,也就是不需要使用到。
五 内部类的应用
-
封装性
如果我们的内部类不想轻易被任何人访问,可以选择使用private修饰内部类,这样我们就无法通过创建对象的方法来访问,想要访问只需要在外部类中定义一个public修饰的方法,间接调用。
外部类的public方法可以起到过滤作用
-
实现多继承
就是在外部类写几个内部类, 用内部类进行继承, 间接多继承.
-
解决继承及实现接口出现同名方法的问题
外部类继承, 内部类实现接口
public interface Demo { void test(); } public class MyDemo { public void test() { System.out.println("父类的test方法"); } } public class DemoTest extends MyDemo { private class inner implements Demo { public void test() { System.out.println("接口的test方法"); } } public Demo getIn() { return new inner(); } public static void main(String[] args) { //调用接口而来的test()方法 DemoTest dt = new DemoTest(); Demo d = dt.getIn(); d.test(); //调用继承而来的test()方法 dt.test(); } } //运行结果 接口的test方法 父类的test方法
-
用匿名内部类实现回调功能
闭包(closure) 是一个可调用的对象,它记录了一些信息,这些信息来自于它创建的作用域。通过这个定义,Java 的内部类其实就是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private 成员。回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口。通过回调对象能够携带一些信息,这些信息能允许它在稍后的某个时刻调用初始的对象, 下面这个例子就是一个基于内部类与回调的案例, 来自Java编程思想
interface Incrementable{ void increment(); } /** * 实现Incrementable 接口,实现increment() 方法 */ class Callee1 implements Incrementable{ private int i = 0; @Override public void increment() { i++; System.out.println("implements Incrementable ...." + i); } } class MyIncrement{ public void increment(){ System.out.println("Other Operation"); } static void f(MyIncrement mi){ mi.increment();} } /** * 继承MyIncrement 类,重写increment() 方法 * 并定义一个内部类Closure 实现了Incrementable 接口,实现了其中increment() 方法, * 该方法调用了Callee2 中重写的increment() 方法 * getCallbackReference() 方法返回了Incrementable 接口的一个实现类Closure */ class Callee2 extends MyIncrement{ private int i = 0; public void increment(){ super.increment(); //调用父类的构造函数 i++; System.out.println("extends MyIncrement ...." + i); } private class Closure implements Incrementable{ @Override public void increment() { Callee2.this.increment(); //调用Callee2 的increment 方法 } } public Incrementable getCallbackReference(){ return new Closure(); } } class Caller{ private Incrementable callbackReference; Caller(Incrementable incrementable){ callbackReference = incrementable; } void go(){ callbackReference.increment(); } } public class Callbacks { public static void main(String[] args) { Callee1 c1 = new Callee1(); Callee2 c2 = new Callee2(); MyIncrement.f(c2); Caller caller1 = new Caller(c1); Caller caller2 = new Caller(c2.getCallbackReference()); caller1.go(); caller1.go(); caller2.go(); caller2.go(); } }
首先
Callee1
是一个简单的实现了接口Incrementable
与相关方法,在这里起到一个对比的作用而已。然后实现了一个
MyIncrement
类同样实现了一个increment()
方法但是这个与接口中的increment()
没有任何关系,因为这个类自己实现的,并没有实现这个接口,而静态方法f()也只是为了测试一下increment()
方法。而
Callee2
继承自这个类。这里就是重点了。同样写了一个increment()
方法,覆盖了父类方法,但是中间还是调用了父类方法。接下里是一个内部类也就是闭包的具体实现了。内
部类实现了接口
Incrementable
并且直接调用外部类的方法作为具体的实现。内部类实现Incrementable
接口很关键,这样就给外部留下了一个通道,能够接受这个内部类。最后
Callee2
的后面留下了一个钩子,即getCallbackReference()
方法,它返回一个内部类的对象,实现了内部与外部的链接,同时有保证了内部类的安全,因为只有Callee2
的对象可以访问与调用这个内部类的方法,而其他的类都无权访问,即使是基类接口对象。而后面的
Caller
类起到的是一个唤醒作用,通过接受不同的接口对象,实现不同的操作,但还有一个作用是等待接受一个内部类对象,来产生回调。现在大家再回头看一下输出就能够明白了。假装你回头看了,在
main()
方法中,首先是创建对象与声明,然后是调用了一个MyIncrement
的静态方法,传入的是一个Callee2
对象,此时无法触发回调,所以只是正常的输出,然后,在Caller2
的初始化时传入的是一个Closure
对象从而产生了回调。
内部类: https://juejin.cn/post/6844903566293860366#heading-0
闭包与回调:
[1] https://blog.csdn.net/failure01/article/details/8039781?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-6.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-6.control
[2] https://blog.csdn.net/yuwenhao07/article/details/53607117
[3] https://www.cnblogs.com/junqiao/p/6492300.html
[4] https://blog.csdn.net/codejas/article/details/78653429