广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。
一、内部类基础:
1、成员内部类:
成员内部类看起来像是外部类的一个成员,所以内部类可以拥有private、public等访问权限修饰;当然,也可以用static来修饰。成员内部类分为:
静态成员内部类:使用static修饰类;
非静态成员内部类:未用static修饰类,在没有说明是静态成员内部类时,默认成员内部类指的就是非静态成员内部类;
注:只有成员内部类才能加static变成静态内部类。
1.1)静态内部类:
使用static修饰的内部类我们称之为静态内部类,我们要知道只要是static修饰的类那它一定是内部类,不可能是外部类。静态内部类与非静态内部类之间存在一个最大的区别,非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类的对象,但是静态内部类却没有。没有这个引用就意味着:
它的创建是不需要依赖于外围类的对象
它不能使用任何外围类的非static成员变量和方法(因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象)
静态内部类内允许有static属性、方法;
class OutClass {
//。。。
static class InnerClass {
static String test = "test";
int a = 1;
static void fun1() {}
void fun2() {}
}
}
1.2)非静态成员内部类:
public class Circle {
private double radius = 0.0;
public static int count = 1;
public Circle(double radius) {
this.radius = radius;
}
public class Draw {//内部类
public void drawSahpe() {
System.out.println(radius);//外部类的private成员
System.out.prinlt(count);//外部类的静态成员
}
}
}
1)成员内部类访问外部类的信息:
类Draw像是类Circle的一个成员,Circle称为外部类。成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
注:当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法
内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。如果成员内部类Inner用private修饰,则只能在外部类的内部访问;如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。
2)创建内部类对象:
成员内部类是依附外部类而存在的,所以要创建成员内部类的对象,前提是必须存在一个外部类的对象。通常有如下两种方法:
public class OutClass {
private InnerClass getInner() {
return new InnerClass();
}
public class InnerClass{}
public static void main(String... str) {
OutClass out = new OutClass();
InnerClass inner1 = out.getInner();
InnerClass inner2 = out.new InnerCLass();
}
}
3)外部类访问成员内部类信息:
同样,外部类也可以访问内部类的所有成员变量和方法(包括private),但外部类想访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:
public class OutClass {
public static int count = 12;
private double radius;
public OutClass(double radius) {
this.radius = radius;
}
public class InnerClass {//内部类
public String name = "test";
public void test() {
System.out.println(count);//访问外部类成员
System.out.println(radius);//访问外部类成员
}
}
public static void main() {
OutClass out = new OutClass(1.2);
InnerClass inner = out.new InnerClass();
inner.test();//内部类方法
inner.name = "my test";//内部类属性
}
}
4)成员内部类中不能存在任何static的变量和方法:
对于成员内部内并不是完全不能出现static字段的,如果你是使用final和static同时修饰一个属性字段,并且这个字段是基本类型或者String类型的,那么是可以编译通过的。原因:
非静态成员内部类要依赖外部类,所以不能有static变量;
对于final static的变量是存放在常量池中的,不涉及到类的加载;
说明:这一条对于局部内部类也是适用的。
1.3)应用:
1)api接口响应数据中,我们可以使用成员内部类这种方式来定义复杂的结构,实现json序列化:
@Data
@EqualsAndHashCode
public class PaoPaoMallCrowdActivityInfoResponse {
private String code;
private String msg;
private PaoPaoMallCrowdActivityInfoResponseData data;
public boolean isSuccess() {
return "A00000".equals(code) && data!=null;
}
@Data
public class PaoPaoMallCrowdActivityInfoResponseData {
private long activityId;
private int orderNum;
private int targetNum;
private long startTime;
private long endTime;
}
}
2)常量:
public class Constants {
class RecallConstans {
public final static String my_test1 = "abc";
}
class PredictConstans {
public final static String my_test1 = "123";
}
}
3) 静态内部类实现单例:
public class SingleTon{
private SingleTon(){}
private static class SingleTonHoler{
private static SingleTon INSTANCE = new SingleTon();
}
public static SingleTon getInstance(){
return SingleTonHoler.INSTANCE;
}
静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
4)成员内部类实现多继承:
class Father {
public int str(){
return 2;
}
}
class Mother {
public int can(){
return 2;
}
}
class Son {
class Father_1 extends Father{
public int str(){
return super.str() + 1;
}
}
class Mother_1 extends Mother{
public int can(){
return super.can() - 2;
}
}
public int get(){
return new Father_1().str();
}
public int getcan(){
return new Mother_1().can();
}
}
public class Outer {
public static void main(String[] args) {
Son son = new Son();
System.out.println( son.get());
System.out.println( son.getcan());
}
}
2、局部内部类:
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
public void test() {
class InnerClass {
private String name;
final static String test = "1";
public InnerClass(String name) {
super();
this.name = name;
}
public void say(String str) {
System.out.println(name+":"+str);
}
}
new InnerClass("test").say("hello");
}
注:局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
3、匿名内部类:
public class Demo {
private Runnable runnable = new Runnable() {
@override
public void run() {}
}
}
匿名内部类是唯一一种没有构造器的类(不能自定义构造器)。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
二、其他:
1、内部类(包括匿名内部类、局部内部类)会持有外部类的引用:
以普通的内部类为例:
public class Demo {
// 普通内部类
public class DemoRunnable implements Runnable {
@Override
public void run() {
}
}
}
编译后:Demo.class 和 Demo$DemoRunnable.class
Demo$DemoRunnable.class 就是我们的内部类编译出来的,它的命名也是有规律的,外部类名Demo+$+内部类名DemoRunnable.class。
匿名内部类和普通内部类实现基本一致,只是编译器自动给它拼了个名字,所以匿名内部类不能自定义构造器,因为名字编译完成后才能确定。
2、为什么匿名内部类使用到外部类方法中的局部变量时需要是final类型的?
这里先申明一下,这个问题本身是有问题的,问题在哪呢?因为java8中并不一定需要声明为final。我们来看个例子
// Demo.java
public void run() {
int age = 10;
Runnable runnable = new Runnable() {
@Override
public void run() {
int myAge = age + 1;
System.out.println(myAge);
}
};
}
匿名内部类对象runnable,使用了外部类方法中的age局部变量。编译运行完全没问题,而age并没有final修饰啊! 那我们再在run方法中,尝试修改age试试
public void run() {
int age = 10;
Runnable runnable = new Runnable() {
@Override
public void run() {
int myAge = age + 1;
System.out.println(myAge);
age = 20; // error
}
};
}
编译器报错了,提示信息是”age is access from inner class, need to be final or effectively final“。很显然编译器很智能,由于我们第一个例子并没有修改age的值,所以编译器认为这是effectively final,是安全的,可以编译通过,而第二个例子尝试修改age的值,编译器立马就报错了。
3、Lambda表达式并不是匿名内部类的语法
4、为什么局部内部类和匿名内部类只能访问局部final变量?
如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。
匿名内部类:
必须继承一个父类或者实现一个接口
匿名内部类不能加访问修饰符
new 匿名内部类,这个类是要先定义的,如果不先定义,编译时会报错
当所在方法的形参要在内部类里面使用时,改形参必须为final:将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象)
首先内部类对象的生命周期与局部变量的生命周期是不一致的。原因是因为当成员方法已调用结束那么局部变量就会被销毁,因为局部变量是存储在栈帧中的而栈帧在方法调用结束后就会被销毁,但内部类的对象和其他类对象的生命周期一致需要没有引用指向该对象了才会被jvm回收。所以就会出现成员方法已调用结束,局部变量已死亡,但局部内部类或匿名内部类的对象仍然活着。而如果内部类还活着那么他访问的局部变量就不能死亡。所以为了处理这个问题他会将方法中的局部变量作为局部内部类或匿名内部类构造方法的参数传入内部类中。相当于在内部类中复制了一份方法的局部变量。给方法中局部变量添加上final修饰的原因是为了保持数据的一致性。因为如果不加final修饰,当方法中的局部变量或内部类复制的变量发生了变化就会导致两边数据不一致的情况,因此如果局部内部类或匿名内部类要访问方法中的局部变量时,局部变量必须通过final修饰