一、什么是内部类?
- 在java中允许在类的内部再定义一个类,这个定义在类内部的类称之为内部类,包含内部类的类称之为外部类。
- java中的内部类只是java编译器的概念,对于java的虚拟机而言,它是没有java内部类的概念的,也就是说java中的内部类最后也会被编译成一个独立的class文件。
二、为什么使用内部类?
在《Think in java》一书中提到 —— 使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
interface inter1{}
interface inter2{}
abstract class abstractClass1{}
abstract class abstractClass2{}
public class Outer extends abstractClass1 implements inter1 {
class Inner extends abstractClass2 implements inter2{
}
}
1.java语言的特性是单继承的 ,以上代码可以看到Inner内部类可以继承抽象类abstractClass2.且不受外围类的影响。通过内部类可以间接的实现多继承,可以很好的解决java中单继承产生的一系列问题。
2.使用内部类还能够带来以下好处:(摘自《Think in java》)
1、内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。
2、在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
3、创建内部类对象的时刻并不依赖于外围类对象的创建。
4、内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
5、内部类提供了更好的封装,除了该外围类,其他类都不能访问。
三、内部类的使用
1.成员内部类
package com.example.innerclass;
class Outer {
private int id;
public static int count=10;
class Inner {
public void inPrint(){
System.out.println(id);//外部类的private成员
System.out.println(count);//外部类的静态成员
// public static int count=10; 内部类不能有静态的成员
int id=1;
System.out.println(id);//注意:成员内部类拥有和外部类成员变量或者方法同名时,默认访问成员内部类中的成员
System.out.println(Outer.this.id);//使用格式:外部类.this.成员 访问外部类中的成员
}
}
private Inner inner=null;
public Inner getInstance(){
if(inner==null){
inner =new Inner();
}
return inner;
}
public void outPrint(){
getInstance().inPrint();//外部类访问内部类中的成员,需先创建内部类的对象
}
}
public class InnerClassTest{
public static void main(String[] args) {
//成员内部类是依附于外部类而存在的,创建成员内部类的对象,需先出在一个外部类的对象
Outer outer=new Outer();
Outer.Inner inner=outer.new Inner();
//或者
Outer.Inner inner1=outer.getInstance();
outer.outPrint();
}
}
//打印结果
0
10
1
0
2.局部内部类
package com.example.innerclass;
import java.security.PrivateKey;
public class LocalInnerClassTest {
public static void main(String[] args) {
OuterClass outerClass=new OuterClass();
outerClass.OutMethod(128);
}
}
class Prent{
}
class OuterClass{
private int i=1;
public void OutMethod(int value) {
Integer j=127;
class LocalClass extends Prent { //局部内部类不能有修饰符,定义在方法内
public void localPrint() {
// value=value+1;
// j+=1; 此处可以直接访问方法内的局部变量和参数,但是不能修改(jdk1.8之前只能访问final修饰的成员变量)
System.out.println("value="+value);
int i = 0;
i+=2;
System.out.println("方法内中成员j="+j);
System.out.println("内部类中成员i="+i);
System.out.println("外部类中成员i="+OuterClass.this.i);
}
}
LocalClass localClass=new LocalClass(); //局部内部类只能在方法内使用
localClass.localPrint();
System.out.println("***外部类中成员i="+i);
}
}
//打印结果
value=128
方法内中成员j=127
内部类中成员i=2
外部类中成员i=1
***外部类中成员i=1
3.静态内部类
package com.example.innerclass;
public class StaticInnerClassTest {
public static void main(String[] args) {
OuterClass1.StaticInner staticInner=new OuterClass1.StaticInner();//静态内部类可以独立存在,不依赖与外部类
staticInner.innerPrint();
OuterClass1.StaticInner.innerStaticMethod();//访问内部类的静态信息:外部类.内部类.静态信息
}
}
class OuterClass1 {
private int id = 1; //外部类成员变量
private static String name = "123"; //外部类静态成员
static {
System.out.println("执行了外部类静态代码块");
}
static class StaticInner{
private int id=0;
public static String name="1234";
public static void innerStaticMethod(){
System.out.println("执行了静态内部类中静态方法");
}
public void innerPrint(){
System.out.println("id="+id);
System.out.println("内部类中静态成员name="+name);
System.out.println("外部类中静态成员name="+OuterClass1.name); //静态内部类只能访问外部类static关联的信息
}
}
}
//打印结果
id=0
内部类中静态成员name=1234
执行了外部类静态代码块
外部类中静态成员name=123
执行了静态内部类中静态方法
4.匿名内部类
package com.example.innerclass;
public class AnonymousInnerClassTest {
public static void main(String []args){
OuterClass2.test(1);
}
}
class OuterClass2{
private static int id=1001;
private static int a=123;
public static void test(int a){//jdk1.8之后形参列表可以不加final
new Thread(){ //匿名内部类是没有访问修饰符的,没有构造方法,访问方法参数时和局部内部类有同样的限制
private int i;
// private static String name="abc";匿名内部类中不能定义任何静态成员、方法。
@Override
public void run(){
System.out.println(id); //匿名内部类访问外部类成员变量或方法必须用static修饰,可以访问私有的
System.out.println(a);
System.out.println(OuterClass2.a);//访问外部了同名的成员:外部类.成员
}
}.start();
}
}
四、面试题赏析
1.按照要求,补齐代码:要求在控制台输出”HelloWorld”
package com.example.innerclass;
public class CaseInterview {
public static void main(String[] args) {
OuterClass4.method().show();
}
}
interface Inter { void show(); }
class OuterClass4 {
//补齐代码
}
//答案
public static Inter method(){
return new Inter() {
@Override
public void show() {
System.out.println("HelloWorld");
}
};
}
2.为什么内部类调用的外部变量必须是final修饰的?
因为生命周期的原因。方法中的局部变量,方法结束后这个变量就要释放掉,final保证这个变量始终指向一个对象。首先,内部类和外部类其实是处于同一个级别,内部类不会因为定义在方法中就会随着方法的执行完毕而跟随者被销毁。问题就来了,如果外部类的方法中的变量不定义final,那么当外部类方法执行完毕的时候,这个局部变量肯定也就被GC了,然而内部类的某个方法还没有执行完,这个时候他所引用的外部变量已经找不到了。如果定义为final,java会将这个变量复制一份作为成员变量内置于内部类中,这样的话,由于final所修饰的值始终无法改变,所以这个变量所指向的内存区域就不会变。 为了解决:局部变量的生命周期与局部内部类的对象的生命周期的不一致性问题