序言
有位小同学要我写一篇这个的总结,我说那好吧,那就动手写总结一下这个内部类的知识,感觉这个在面试中也会经常遇到,内部类、反射、集合、IO流、异常、多线程、泛型这些重要的基础知识大家都比较容易记不住。大概是自己平常用的比较少,所以经常性的会忘记,现在通过博文的方式记录下来,以后忘记可以回过头来自己看。
--WZY
一、什么是内部类
顾名思义,内部类就是在一个类的内部在定义一个类,比如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了。
在一个类中有很多属性,比如、成员变量,成员方法,局部变量,静态方法等(这些区别应该知道把,如果不知道的应该先把这几个弄清楚,然后在继续往下看,对,说的就是你),那么这样一比较,B类在A类中的不同位置就可以被分为了不同的内部类了。总共有四种,
1、成员内部类
2、局部内部类
3、静态内部类
4、匿名内部类
现在看的一脸懵逼没关系,接下来我会一一讲解这四种内部类和用法及区别
二、成员内部类
1、什么是成员内部类?
例如: Outer类中有一个私有的成员变量id、还有一个out方法。 在内部类Inner中只有一个in方法 (注明:写的类中没有具体意义,就是为了说明问题而写的)
//在A类中申明了一个B类,此B类就在A的内部,并且在成员变量的位置上,所以就称为成员内部类
public class Outer {
private int id;
public void out(){
System.out.println("这是外部类方法");
}
class Inner{
public void in(){
System.out.println("这是内部类方法");
}
}
}
2、如何实例化成员内部类呢?
public class Outer {
private int id;
public void out(){
System.out.println("这是外部类方法");
}
class Inner{
public void in(){
System.out.println("这是内部类方法");
}
}
}
public class Test{
public static void main(String[] args) {
//实例化成员内部类分两步
//1、实例化外部类
Outer outObject = new Outer();
//2、通过外部类调用内部类
Outer.Inner inObject = outObject.new Inner();
//测试,调用内部类中的方法
inObject.in();//打印:这是内部类方法
}
}
分析:这个由外部类创建内部类对象不难理解把,想想如果你要使用一个类中方法或者属性,你就必须要先有该类的一个对象,同理,一个类在另一个类的内部,那么想要使用这个内部类,就必须先要有外部类的一个实例对象,然后在通过该对象去使用内部类。
3、成员内部类能做哪些事情?
·访问外部类的所有属性(这里的属性包括私有的成员变量,方法),例子大多数还是上面的内容,稍微改变一点点
public class Outer {
private int id;
public void out(){
System.out.println("这是外部类方法");
}
class Inner{
public void in(){
System.out.println("这是内部类方法");
}
//内部类访问外部类私有的成员变量
public void useId(){
System.out.println(id+3);。
}
//内部类访问外部类的方法
public void useOut(){
out();
}
}
}
public class Test{
public static void main(String[] args) {
//实例化成员内部类分两步
//1、实例化外部类
Outer outObject = new Outer();
//2、通过外部类调用内部类
Outer.Inner inObject = outObject.new Inner();
//测试
inObject.useId();//打印3,因为id初始化值为0,0+3就为3,其中在内部类就使用了外部类的私有成员变量id。
inObject.useOut();//打印:这是外部类方法
}
}
如果内部类中的变量名和外部类的成员变量名一样,那么如何会怎么样呢?
public class Outer {
private int id;//默认初始化0
public void out(){
System.out.println("这是外部类方法");
}
class Inner{
private int id=8; //这个id跟外部类的属性id名称一样。
public void in(){
System.out.println("这是内部类方法");
}
public void test(){
System.out.println(id);//输出8,内部类中的变量会暂时将外部类的成员变量给隐藏
//如何调用外部类的成员变量呢?通过Outer.this,想要知道为什么能通过这个来调用,就得明白下面这个原理
//想实例化内部类对象,就必须通过外部类对象,当外部类对象来new出内部类对象时,会
//把自己(外部类对象)的引用传到了内部类中,所以内部类就可以通过Outer.this来访问外部类的属性和方法,到这里,你也就可以知道为什么内部类可以访问外部类的属性和方法,这里由于有两个相同的
属性名称,所以需要显示的用Outer.this来调用外部类的属性,平常如果属性名不重复,那么我们在内部类中调用外部类的属性和方法时,前面就隐式的调用了Outer.this。
System.out.println(Outer.this.id);//输出外部类的属性id。也就是输出0
}
}
}
借助成员内部类,来总结内部类(包括4种内部类)的通用用法:
1、要想访问内部类中的内容,必须通过外部类对象来实例化内部类。
2、能够访问外部类所有的属性和方法,原理就是在通过外部类对象实例化内部类对象时,外部类对象把自己的引用传进了内部类,使内部类可以用通过Outer.this去调用外部类的属性和方法,一般都是隐式调用了,但是当内部类中有属性或者方法名和外部类中的属性或方法名相同的时候,就需要通过显式调用Outer.this了。
4、成员内部类的面试题
//要求:使用已知的变量,在控制台输出30,20,10。
class Outer {
public int num = 10;
class Inner {
public int num = 20;
public void show() {
int num = 30;
System.out.println(?);
System.out.println(??);
System.out.println(???);
}
}
}
class InnerClassTest {
public static void main(String[] args) {
Outer.Inner oi = new Outer().new Inner();
oi.show();
}
}
答案:num、this.num、Outer.this.num
解析:这题你如何明白了上面总结中的第二点,那么非常简单,考察的就是1、局部变量 2、this,和3、Outer.this,也就是内部类访问外部类属性方法的原理。这考察三个东西,
1、在一个方法中,使用直接使用变量名,肯定使用的是局部变量,因为会把大的成员变量给隐藏掉,这题中,也就是说show方法中的num会将内部类中的成员变量num隐藏掉,而内部类中的成员变量num又会把外部类中的成员变量num隐藏掉,所以通过直接输出num,会是show方法中的局部变量的值30.
2、使用this.num调用,其this代表的是调用该方法的对象,调用show方法的是oi,oi也就是内部类对象,所以oi.num也就标识内部类的成员变量num的值20
3、Outer.this.num,调用的外部类中的成员变量num的值也就是10,这个如果不清楚就看上面总结中的第二点,就是那个原理。
三、局部内部类
1、什么是局部内部类,跟局部变量一样,在一个方法中定义的,那么就是局部内部类了。
例如
public class Outer {
private int id;
//在method01方法中有一个Inner内部类,这个内部类就称为局部内部类
public void method01(){class Inner{
public void in(){
System.out.println("这是局部内部类");
}
}
}
}
2、局部内部类一般的作用跟在成员内部类中总结的差不多,但是有两个要注意的地方,
1、在局部内部类中,如果要访问局部变量,那么该局部变量要用final修饰。看例子
为什么需要使用final?
final修饰变量:变为常量,会在常量池中放着,
逆向思维想这个问题,如果不实用final修饰,当局部内部类被实例化后,方法弹栈,局部变量随着跟着消失,这个时候局部内部类对象在想去调用该局部变量,就会报错,因为该局部变量已经没了,当局部变量用fanal修饰后,就会将其加入常量池中,即使方法弹栈了,该局部变量还在常量池中呆着,局部内部类也就是够调用。所以局部内部类想要调用局部变量时,需要使用final修饰,不使用,编译度通不过。
public class Outer {
private int id;
public void method01(){
final int cid = 3; //这个就是局部变量cid。要让局部内部类使用,就得变为final并且赋值,如果不使用final修饰,就会报错
class Inner{
//内部类的第一个方法
public void in(){
System.out.println("这是局部内部类");
}
//内部类中的使用局部变量cid的方法
public void useCid(){
System.out.println(cid);
}
}
}
}
2、局部内部类不能通过外部类对象直接实例化,而是在方法中实例化出自己来,然后通过内部类对象调用自己类中的方法。看下面例子就知道如何用了
public class Outer {
private int id;
public void out(){
System.out.println("外部类方法");
}
public void method01(){
class Inner{
public void in(){
System.out.println("这是局部内部类");
}
}
//关键在这里,如需要在method01方法中自己创建内部类实例,然后调用内部类中的方法,等待外部类调用method01方法,就可以执行到内部类中的方法了。
Inner In = new Inner();
In.in();
}
}
总结:局部内部类就只要注意那亮点就行了
1、在局部内部类中,如果要访问局部变量,那么该局部变量要用final修饰
2、如何调用局部内部类方法。
四、静态内部类
根据名字就知道,使用static修饰的内部类,就叫做静态内部类了。
回顾static的用法:一般只修饰变量和方法,平常不可以修饰类,但是内部类却可以被static修饰。
1、static修饰成员变量:整个类的实例共享静态变量
2、static修饰方法:静态方法,只能够访问用static修饰的属性或方法,而非静态方法可以访问static修饰的方法或属性
3、被static修饰了的成员变量和方法能直接被类名调用。
4、static不能修饰局部变量,切记,不要搞混淆了,static平常就用来修饰成员变量和方法。
注意:
1、我们上面说的内部类能够调用外部类的方法和属性,在静态内部类中就行了,因为静态内部类没有了指向外部类对象的引用。除非外部类中的方法或者属性也是静态的。这就回归到了static关键字的用法。
2、静态内部类能够直接被外部类给实例化,不需要使用外部类对象
Outer.Inner inner = new Outer.Inner();
3、静态内部类中可以什么静态方法和静态变量,但是非静态内部类中就不可以申明静态方法和静态变量
五、匿名内部类
这个在四种内部类中,使用的是最多的,在以后遇到的程序中,大多数出现的也就是它。
什么是匿名对象?如果一个对象只要使用一次,那么我们就是需要new Object().method()。 就可以了,而不需要给这个实例保存到该类型变量中去。这就是匿名对象。例如
public class Test {
public static void main(String[] args) {
//讲new出来的Apple实例赋给apple变量保存起来,但是我们只需要用一次,就可以这样写
Apple apple = new Apple();
apple.eat();
//这种就叫做匿名对象的使用,不把实例保存到变量中。
new Apple().eat();
}
}
class Apple{
public void eat(){
System.out.println("我要被吃了");
}
}
匿名内部类跟匿名对象是一个道理,
匿名对象:我只需要用一次,那么我就不用声明一个该类型变量来保存对象了,
匿名内部类:我也只需要用一次,那我就不需要在类中先定义一个内部类,而是等待需要用的时候,我就在临时实现这个内部类,因为用次数少,可能就这一次,那么这样写内部类,更方便。不然先写出一个内部类的全部实现来,然后就调用它一次,岂不是用完之后就一直将其放在那,那就没必要那样。
匿名内部类是如何个格式呢?
前提:存在一个类或者接口
为什么呢?
例如:A接口 new A(){}; 必须实现A接口中所有抽象方法,实现完之后,new A(){} 这个整体就是A的一个匿名实现类对象,肯定可以调用自己类中的方法
不一定是接口,可以是抽象类,也可以是完整的类,如果是抽象类的话,跟接口一样,实现抽象的方法,如果是完整的类,就可以重写自己所需要的方法,然后在调用,
举个例子,看看不使用匿名内部类和使用了匿名内部类的区别
不实用匿名内部类,真麻烦啊。
public class Test {
public static void main(String[] args) {
//如果我们需要使用接口中的方法,我们就需要走3步,1、实现接口 2、创建实现接口类的实例对象 3、通过对象调用方法
//第二步
Test02 test = new Test02();
//第三步
test.method();
}
}
//接口Test1
interface Test01{
public void method();
}
//第一步、实现Test01接口
class Test02 implements Test01{
@Override
public void method() {
System.out.println("实现了Test接口的方法");
}
}
使用了匿名内部类。
public class Test {
public static void main(String[] args) {
//如果我们需要使用接口中的方法,我们只需要走一步,就是使用匿名内部类,直接将其类的对象创建出来。
new Test1(){
public void method(){
System.out.println("实现了Test接口的方法");
}
}.method();
}
}
interface Test1{
public void method();
}
解析:其实只要明白一点,new Test1(){实现接口中方法的代码}; Test1(){...}这个的作用就是将接口给实现了,只不过这里实现该接口的是一个匿名类,也就是说这个类没名字,只能使用这一次,我们知道了这是一个类, 将其new出来,就能获得一个实现了Test1接口的类的实例对象,通过该实例对象,就能调用该类中的方法了,因为其匿名类是在一个类中实现的,所以叫其匿名内部类,不要纠结为什么Test1(){...}就相当于实现了Test1接口,这其中的原理等足够强大了,在去学习,不要钻牛角尖,这里就仅仅是需要知道他的作用是什么,做了些什么东西就行。
匿名内部类的面试题:
- A:面试题
按照要求,补齐代码
interface Inter { void show(); }
class Outer { //补齐代码 }
class OuterDemo {public static void main(String[] args) { Outer.method().show(); }
}
要求在控制台输出”HelloWorld”
答案:
- A:面试题
- 按照要求,补齐代码
interface Inter { void show(); }
class Outer {
//补齐代码
public static Inter method(){
return new Inter(){
void show(){
System.out.println("HelloWorld");
}
};
}
}
class OuterDemo {
public static void main(String[] args) {
Outer.method().show();
}
}
要求在控制台输出”HelloWorld”
解析:这个题目挺有趣的,考了很多东西,一开始只能说我自己度懵逼了,是一个局部匿名内部类。 通过看主方法中的调用,Outer.method(),能直接用类名调用方法,那么肯定该方法就是一个static方法,然后又直接调用了show()方法,说明这个method方法有一个返回值,其返回值类型就是实现该接口类的类型,因为只有接口中有show()这个方法。所以在method中就是一个匿名内部类。