整理了一下内部类的相关知识,算是比较全,比较基础的,但愿你们一块儿学习进步。 html
在Java中,能够将一个类的定义放在另一个类的定义内部,这就是内部类。内部类自己就是类的一个属性,与其余属性 定义方式一致。java
一个内部类的例子:android
public class Outer {
private int radius = 1;
public static int count = 2;
public Outer() {
}
class inner{
public void visitOuter() {
System.out.println("visit outer private member variable:" + radius);
System.out.println("visit outer static variable:" + count);
}
}
}
复制代码
内部类能够分为四种:成员内部类、局部内部类、匿名内部类和静态内部类。面试
定义在类内部的静态类,就是静态内部类。算法
public class Outer {
private static int radius = 1;
static class StaticInner {
public void visit() {
System.out.println("visit outer static variable:" + radius);
}
}
}
复制代码
静态内部类能够访问外部类全部的静态变量,而不可访问外部类的非静态变量;静态内部类的建立方式,new 外部类.静态内部类()
,以下:数据库
Outer.StaticInner inner = new Outer.StaticInner();
inner.visit();
复制代码
定义在类内部,成员位置上的非静态类,就是成员内部类。编程
public class Outer {
private static int radius = 1;
private int count =2;
class Inner {
public void visit() {
System.out.println("visit outer static variable:" + radius);
System.out.println("visit outer variable:" + count);
}
}
}
复制代码
成员内部类能够访问外部类全部的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例,它的建立方式外部类实例.new 内部类()
,以下:设计模式
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.visit();
复制代码
定义在方法中的内部类,就是局部内部类。bash
public class Outer {
private int out_a = 1;
private static int STATIC_b = 2;
public void testFunctionClass(){
int inner_c =3;
class Inner {
private void fun(){
System.out.println(out_a);
System.out.println(STATIC_b);
System.out.println(inner_c);
}
}
Inner inner = new Inner();
inner.fun();
}
public static void testStaticFunctionClass(){
int d =3;
class Inner {
private void fun(){
// System.out.println(out_a); 编译错误,定义在静态方法中的局部类不能够访问外部类的实例变量
System.out.println(STATIC_b);
System.out.println(d);
}
}
Inner inner = new Inner();
inner.fun();
}
}
复制代码
定义在实例方法中的局部类能够访问外部类的全部变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。局部内部类的建立方式,在对应方法内,new 内部类()
,以下:ide
public static void testStaticFunctionClass(){
class Inner {
}
Inner inner = new Inner();
}
复制代码
匿名内部类就是没有名字的内部类,平常开发中使用的比较多。
public class Outer {
private void test(final int i) {
new Service() {
public void method() {
for (int j = 0; j < i; j++) {
System.out.println("匿名内部类" );
}
}
}.method();
}
}
//匿名内部类必须继承或实现一个已有的接口
interface Service{
void method();
}
复制代码
除了没有名字,匿名内部类还有如下特色:
匿名内部类建立方式:
new 类/接口{
//匿名内部类实现部分
}
复制代码
咱们为何要使用内部类呢?由于它有如下优势:
public class Outer {
private int radius = 1;
protected void test(){
System.out.println("我是外部类方法");
}
class Inner {
public void visit() {
System.out.println("访问外部类变量" + radius);
test();
}
}
}
复制代码
咱们能够看到,内部类Inner是能够访问外部类Outer的私有变量radius或者方法test的。
当内部类使用 private修饰时,这个类就对外隐藏了。当内部类实现某个接口,而且进行向上转型,对外部来讲,接口的实现已经隐藏起来了,很好体现了封装性。
//提供的接口
interface IContent{
String getContents();
}
public class Outer {
//私有内部类屏蔽实现细节
private class PContents implements IContent{
@Override
public String getContents() {
System.out.println("获取内部类内容");
return "内部类内容";
}
}
//对外提供方法
public IContent getIContent() {
return new PContents();
}
public static void main(String[] args) {
Outer outer=new Outer();
IContent a1=outer.getIContent();
a1.getContents();
}
}
复制代码
咱们能够发现,Outer外部类对外提供方法getIContent,用内部类实现细节,再用private修饰内部类,屏蔽起来,把Java的封装性表现的淋漓尽致。
咱们知道Java世界中,一个类只能有一个直接父类,即以单继承方式存在。可是内部类让“多继承”成为可能:
- 通常来讲,内部类继承某个类或者实现某个接口,内部类的代码操做建立它的外围类的对象。内部类提供了某种进入其外围类的窗口。
- 每一个内部类均可以队里的继承自一个(接口的)实现,因此不管外围类是否已经继承了某个(接口的)实现,对于内部类没有影响
- 接口解决了部分问题,一个类能够实现多个接口,内部类容许继承多个非接口类型(类或抽象类)。
一份来自Java编程思想,内部类实现“多继承”的温暖以下:
class D {}
abstract class E{}
class Z extends D {
E makeE(){ return new E() {}; }
}
public class MultiImplementation {
static void takesD(D d) {}
static void takesE(E e) {}
public static void main(String[] args){
Z z = new Z();
takesD(z);
takesE(z.makeE());
}
}
复制代码
代码中出现了一个类D,一个抽象类E。而后,用类Z继承D,内部类构造返回E。所以,当你无论要的是D仍是E,Z均可以应付,“多继承”的特色完美表现出来。
什么是回调?假设有两个类A和B,在A中调用B的一个方法b,而b在执行又调用了A的方法c,则c就称为回调函数。
//定义一个CallBack接口
public interface CallBack {
void execute();
}
public class TimeTools {
/**
* 测试函数调用时长,经过定义CallBack接口的execute方法
* @param callBack
*/
public void testTime(CallBack callBack) {
long beginTime = System.currentTimeMillis(); //记录起始时间
callBack.execute(); ///进行回调操做
long endTime = System.currentTimeMillis(); //记录结束时间
System.out.println("[use time]:" + (endTime - beginTime)); //打印使用时间
}
public static void main(String[] args) {
TimeTools tool = new TimeTools();
tool.testTime(new CallBack(){
//匿名内部类,定义execute方法
public void execute(){
TestTimeObject testTimeObject = new TestTimeObject();
testTimeObject.testMethod();
}
});
}
}
复制代码
在调用testTime()测时间的时候,用匿名内部类实现一个方法execute(),在该方法内搞事情(执行目标函数),执行完后,又回到testTime方法,很好了实现测试函数调用时长的功能。显然,匿名内部类让回调实现变得简单。
每一个内部类都会产生一个.class文件,其中包含了如何建立该类型的对象的所有信息。内部类也必须生成一个.class文件以包含它们的Class对象信息。内部类文件的命名有严格规则:外围类的名字+$+内部类的名字。
一个简单例子:
public class Outer {
class Inner{
}
}
复制代码
javac Outer.java编译完成后, 生成的class文件以下:
若是内部类是匿名的,编译器会简单地产生一个数字做为其标识符。若是内部类是嵌套在别的内部类之中(静态内部类),只需直接将它们的名字加在其外围类标志符与“$”的后面。
由上一小节,咱们知道内部类能够访问外部类的成员,包括私有数据。那么它是怎么作到的呢?接下来揭晓答案。
先看这个简单地例子:
public class Outer {
private int i = 0;
class Inner{
void method(){
System.out.println(i);
}
}
}
复制代码
一个外部类Outer,一个外部类私有属性i,一个内部类Inner,一个内部类方法method。内部类方法访问了外部类属性i。
先编译,javac Outer.java,生成.class文件,以下:
javap -classpath . -v Outer$Inner
,反编译Outter$Inner.class文件获得如下信息:
咱们能够看到这一行,它是一个指向外部类对象的指针:
final innerclass.Outer this$0;
复制代码
虽然编译器在建立内部类时为它加上了一个指向外部类的引用, 可是这个引用是怎样赋值的呢?编译器会为内部类的构造方法添加一个参数,进行初始化, 参数的类型就是外部类的类型,以下:
innerclass.Outer$Inner(innerclass.Outer);
复制代码
成员内部类中的Outter this&0 指针便指向了外部类对象,所以能够在成员内部类中随意访问外部类的成员。
局部内部类和匿名内部类访问局部变量的时候,为何变量必需要加上final呢?它内部原理是什么呢?
先看这段代码:
public class Outer {
void outMethod(){
final int a =10;
class Inner {
void innerMethod(){
System.out.println(a);
}
}
}
}
复制代码
反编译(Outer$1Inner)获得如下信息
咱们在内部类innerMethod方法中,能够看到如下这条指令:
3: bipush 10
复制代码
以上例子,为何要加final呢?是由于生命周期不一致, 局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,若是局部内部类要调用局部变量时,就会出错。加了final,能够确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。
咱们再来看一段代码,其实就是把变量a挪到传参方式进来
public class Outer {
void outMethod(final int a){
class Inner {
void innerMethod(){
System.out.println(a);
}
}
}
}
复制代码
反编译可得
那么,新的问题又来了,既然在innerMethod方法中访问的变量a和outMethod方法中的变量a不是同一个变量,当在innerMethod方法中修改a会怎样?那就会形成数据不一致的问题了。
怎么解决呢?使用final修饰符,final修饰的引用类型变量,不容许指向新的对象,这就解决数据不一致问题。注意: 在Java8 中,被局部内部类引用的局部变量,默认添加final,因此不须要添加final关键词。
通常咱们在哪些场景下使用内部类呢?
一些算法多的场合,也能够借助内部类,如:
Arrays.sort(emps,new Comparator(){
Public int compare(Object o1,Object o2)
{
return ((Employee)o1).getServedYears()-((Employee)o2).getServedYears();
}
});
复制代码
若是一些语句块,包括if…else语句,case语句等等比较多,很差维护扩展,那么就能够借助内部类+设计模式解决。
适当的使用内部类,可使得你的代码更加灵活和富有扩展性。如JDK的lamda表达式,用内部类很是多,代码优雅不少。以下
// JDK8 Lambda表达式写法
new Thread(() -> System.out.println("Thread run()")).start();
复制代码
若是一个类,不能为其余的类使用;或者出于某种缘由,不能被其余类引用。那咱们就能够考虑把它实现为内部类。数据库链接池就是这样一个典型例子。
最后,咱们来看一道经典内部类面试题吧。
public class Outer {
private int age = 12;
class Inner {
private int age = 13;
public void print() {
int age = 14;
System.out.println("局部变量:" + age);
System.out.println("内部类变量:" + this.age);
System.out.println("外部类变量:" + Outer.this.age);
}
}
public static void main(String[] args) {
Outer.Inner in = new Outer().new Inner();
in.print();
}
}
复制代码
运行结果: