Java基础 (6) 内部类

问题1. 内部类的作用
问题2. 什么是内部类?
问题3. 闭包和局部内部类的区别

内部类指的是在一个类的内部所定义的类,类名不需要和源文件名相同。
内部类是一个编译时的概念,一旦编译成功,内部类和外部类就会成为两个完全不同的类。

Java 提供了两种方式让我们曲折地来实现多重继承:接口和内部类。
事实上,实现多重继承是内部类的一个极其重要的应用。除此之外,内部类还可以很好的实现隐藏(例如,私有成员内部类)。

内部类共有四种类型,即成员内部类静态内部类局部内部类匿名内部类

  • 成员内部类:成员内部类是外围类的一个成员,是 依附于外围类的,所以,只有先创建了外围类对象才能够创建内部类对象。也正是由于这个原因,成员内部类也不能含有 static 的变量和方法;

  • 静态内部类:静态内部类,就是修饰为 static 的内部类,该内部类对象 不依赖于外部类对象,就是说我们可以直接创建内部类对象,但其只可以直接访问外部类的所有静态成员和静态方法;

  • 局部内部类:局部内部类和成员内部类一样被编译,只是它的 作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效;

  • 匿名内部类:定义匿名内部类的前提是,内部类必须要继承一个类或者实现接口,格式为 new 父类或者接口(){定义子类的内容(如函数等)}。也就是说,匿名内部类最终提供给我们的是一个匿名子类的对象。

对于一个名为 Outer 的外部类和在其内部定义的名为 Inner 的内部类,在编译完成后,会出现 Outer.class 和 Outer$inner.class 两个类。
因此,内部类的成员变量/方法名可以和外部类的相同。内部类可以是静态static的,也可用 public,default,protected 和 private 修饰。 特别地,关于 Java源文件名与类名的关系( java源文件名的命名与内部类无关,以下3条规则中所涉及的类和接口均指的是外部类/接口),需要符合下面三条规则:

  • 如果java源文件包含public类(public接口),则源文件名必须与public类名(public接口名)相同。
  • 如果java源文件不包含public类(public接口),则java源文件名没有限制。
  • 类和接口的命名不能冲突。

优点:

  1. 内部类可以很好的实现隐藏(一般的非内部类,是不允许有 private 与 protected 权限的,但内部类可以);
  2. 内部类拥有外围类的所有元素的访问权限;
  3. 可以实现多重继承;
  4. 可以避免修改接口而实现同一个类中两种同名方法的调用。
1)内部类可以很好的实现隐藏
  //测试接口
public interface InterfaceTest {
    public void test();
}

//外部类
public class Example {

    //内部类
    private class InnerClass implements InterfaceTest{
        @Override
        public void test() {
            System.out.println("I am Rico.");
        }
    }

    //外部类方法
    public InterfaceTest getInnerInstance(){
        return new InnerClass();
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        Example ex = new Example();
        InterfaceTest test = ex.getInnerInstance();
        test.test();
    }
}

对客户端而言,我们可以通过 Example 的getInnerInstance()方法得到一个InterfaceTest 实例,但我们并不知道这个实例是如何实现的,也感受不到对应的具体实现类的存在。由于 InnerClass 是 private 的,所以,我们如果不看源代码的话,连实现这个接口的具体类的名字都看不到,所以说内部类可以很好的实现隐藏。

2)内部类拥有外围类的所有元素的访问权限
//外部类
public class Example {
    private String name = "example";

    //内部类
    private class Inner{
        public Inner(){
            System.out.println(name);   // 访问外部类的私有属性
        }
    }

    //外部类方法
    public Inner getInnerInstance() {
        return new Inner();
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        Example ex = new Example();
        ex.getInnerInstance();
    }
}

name 这个成员变量是在Example里面定义的私有变量,这个变量在内部类中可以被无条件地访问。

(3)可以实现多重继承

对多重继承而言,可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。内部类使得Java的继承机制更加完善,是内部类存在的最大理由。Java中的类只能继承一个类,它的多重继承在我们没有学习内部类之前是用接口来实现的。但使用接口有时候有很多不方便的地方,比如,我们实现一个接口就必须实现它里面的所有方法;而内部类可以使我们的类继承多个具体类或抽象类,规避接口的限制性。

//父类Example1
public class Example1 {
    public String name() {
        return "rico";
    }
}

//父类Example2
public class Example2 {
    public int age() {
        return 25;
    }
}

//实现多重继承的效果
public class MainExample {

    //内部类Test1继承类Example1
    private class Test1 extends Example1 {
        public String name() {
            return super.name();
        }
    }

    //内部类Test2继承类Example2
    private class Test2 extends Example2 {
        public int age() {
            return super.age();
        }
    }

    public String name() {
        return new Test1().name();
    }

    public int age() {
        return new Test2().age();
    }

    public static void main(String args[]) {
        MainExample mexam = new MainExample();
        System.out.println("姓名:" + mexam.name());
        System.out.println("年龄:" + mexam.age());
    }
}
/* Output:
        姓名:rico
        年龄:25
 */

在这个类中,包含两个内部类 Test1 和 Test2。其中,类Test1继承了类Example1,类Test2继承了类Example2。这样,类MainExample 就拥有了 类Example1 和 类Example2 的方法,也就间接地实现了多继承。

(4) 避免修改接口而实现同一个类中两种同名方法的调用

考虑这样一种情形,一个类要继承一个类,还要实现一个接口,可是它所继承的类和接口里面有两个相同的方法(方法签名一致),那么我们该怎么区分它们呢?这就需要使用内部类了。

//Test 所实现的接口
public interface InterfaceTest {
    public void test();
}

//Test 所实现的类
public class MyTest {
    public void test(){
        System.out.println("MyTest");
    }
}

//不使用内部类的情形
public class Test extends MyTest implements InterfaceTest{
    public void test(){
        System.out.println("Test");
    }
}

此时,Test中的 test() 方法是属于覆盖 MyTest 的 test() 方法呢,还是实现 InterfaceTest 中的 test() 方法呢?我们怎么能调到 MyTest 这里的方法?显然这是不好区分的。而我们如果用内部类就很好解决这一问题了。

//Test 所实现的接口
public interface InterfaceTest {
    public void test();
}

//Test 所实现的类
public class MyTest {
    public void test(){
        System.out.println("MyTest");
    }
}

//使用内部类的情形
public class AnotherTest extends MyTest {

    private class InnerTest implements InterfaceTest {
        @Override
        public void test() {
            System.out.println("InterfaceTest");
        }
    }

    public InterfaceTest getCallbackReference() {
        return new InnerTest();
    }

    public static void main(String[] args) {
        AnotherTest aTest = new AnotherTest();
        aTest.test(); // 调用类MyTest 的 test() 方法
        aTest.getCallbackReference().test(); // 调用InterfaceTest接口中的 test() 方法
    }
}

通过使用内部类来实现接口,就不会与外围类所继承的同名方法冲突了。

一. 成员内部类

  1. 定义与原理
    成员内部类是最普通的内部类,它是外围类的一个成员,在实际使用中,一般将其可见性设为 private。成员内部类是依附于外围类的,所以,只有先创建了外围类对象才能够创建内部类对象。也正是由于这个原因,成员内部类也不能含有 static 的变量和方法。
public class Outter {
    private class Inner {

        private final static int x=1;   // OK

        /* compile errors for below declaration
        * "The field x cannot be declared static in a non-static inner type, 
        * unless initialized with a constant expression" */
        final static Inner a = new Inner();     // Error  

        static Inner a1=new Inner();     // Error  

        static int y;     // Error  
    }
}
  1. 交互
  • 成员内部类可以直接访问外部类的所有成员和方法,即使是 private 的;
  • 外部类需要通过内部类的对象访问内部类的所有成员变量/方法。
//外部类
class Out {
    private int age = 12;
    private String name = "rico"; 

    //内部类
    class In {
        private String name = "livia"; 
        public void print() {
            String name = "tom"; 
            System.out.println(age);
            System.out.println(Out.this.name);
            System.out.println(this.name);
            System.out.println(name);
        }
    }

    // 推荐使用getxxx()来获取成员内部类的对象 
    public In getInnerClass(){
        return new In();
    }
}

public class Demo {
    public static void main(String[] args) {

        Out.In in = new Out().new In();   // 片段 1
        in.print();

        //或者采用注释内两种方式访问
        /* 
         * 片段 2
        Out out = new Out();

        out.getInnerClass().print();  // 推荐使用外部类getxxx()获取成员内部类对象 

        Out.In in = out.new In();
        in.print();
        
        */
    }
}
/* Output:
        12
        rico
        livia
        tom
 */
  1. 开头的 Out 是为了标明需要生成的内部类对象在哪个外部类当中;
  2. 必须先有外部类的对象才能生成内部类的对象。

成员内部类,外部类和客户端之间的交互关系为:

  • 在成员内部类使用外部类对象时,使用 outer.this 来表示外部类对象;
  • 在外部类中使用内部类对象时,需要先进行创建内部类对象;
  • 在客户端创建内部类对象时,需要先创建外部类对象。
    特别地,对于成员内部类对象的获取,外部类一般应提供相应的 getxxx() 方法。

3、私有成员内部类
如果一个成员内部类只希望被外部类操作,那么可以使用 private 将其声明私有内部类。

class Out {
    private int age = 12;

    private class In {
        public void print() {
            System.out.println(age);
        }
    }
    public void outPrint() {
        new In().print();
    }
}

public class Demo {
    public static void main(String[] args) {

        /*
        * 此方法无效
        Out.In in = new Out().new In();
        in.print();
        */

        Out out = new Out();
        out.outPrint();
    }
}/* Output:
        12
 */ 

我们必须在Out类里面生成In类的对象进行操作,而无法再使用Out.In in = new Out().new In() 生成内部类的对象。也就是说,此时的内部类只对外部类是可见的,其他类根本不知道该内部类的存在。

二. 静态内部类

1、定义与原理
静态内部类,就是修饰为 static 的内部类,该内部类对象不依赖于外部类对象,就是说我们可以直接创建内部类对象。


静态内部类

2、交互

静态内部类与外部类的交互关系为:

  • 静态内部类可以直接访问外部类的所有静态成员和静态方法,即使是 private 的;
  • 外部类可以通过内部类对象访问内部类的实例成员变量/方法;对于内部类的静态域/方法,外部类可以通过内部类类名访问。

3、成员内部类和静态内部类的区别

成员内部类和静态内部类之间的不同点包括:

  • 静态内部类对象的创建不依赖外部类的实例,但成员内部类对象的创建需要依赖外部类的实例;
  • 成员内部类能够访问外部类的静态和非静态成员,静态内部类不能访问外部类的非静态成员;

三. 局部内部类

1、定义与原理
   有这样一种内部类,它是嵌套在方法和作用域内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,但又不希望这个类是公共可用的,所以就产生了局部内部类。局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。

// 例 1:定义于方法内部
public class Parcel4 { 
    public Destination destination(String s) { 
        class PDestination implements Destination { 
            private String label; 

            private PDestination(String whereTo) { 
                label = whereTo; 
            } 

            public String readLabel() { 
                return label; 
            } 
        } 
        return new PDestination(s); 
    } 

    public static void main(String[] args) { 
        Parcel4 p = new Parcel4(); 
        Destination d = p.destination("Tasmania"); 
    } 
} 
// 例 2:定义于作用域内部
public class Parcel5 { 
    private void internalTracking(boolean b) { 
        if (b) { 
            class TrackingSlip { 
                private String id; 
                TrackingSlip(String s) { 
                    id = s; 
                } 
                String getSlip() { 
                    return id; 
                } 
            } 
            TrackingSlip ts = new TrackingSlip("slip"); 
            String s = ts.getSlip(); 
        } 
    } 

    public void track() { 
        internalTracking(true); 
    } 

    public static void main(String[] args) { 
        Parcel5 p = new Parcel5(); 
        p.track(); 
    } 
} 

2、final 参数
对于final参数,若是将引用类型参数声明为final,我们无法在方法中更改参数引用所指向的对象;若是将基本类型参数声明为final,我们可以读参数,但却无法修改参数(这一特性主要用来向局部内部类和匿名内部类传递数据)。

如果定义一个局部内部类,并且希望它的方法可以直接使用外部定义的数据,那么我们必须将这些数据设为是 final 的;特别地,如果只是局部内部类的构造器需要使用外部参数,那么这些外部参数就没必要设置为 final,例如:


四. 匿名内部类

有时候为了免去给内部类命名,便倾向于使用匿名内部类,因为它没有名字。

  • 匿名内部类是没有访问修饰符的;
  • 匿名内部类是没有构造方法的 (因为匿名内部类连名字都没有);
  • 定义匿名内部类的前提是,内部类必须是继承一个类或者实现接口,格式为 new 父类或者接口(){子类的内容(如函数等)}。也就是说,匿名内部类最终提供给我们的是一个匿名子类的对象,例如:
// 例 1
abstract class AbsDemo
{
    abstract void show();
}

public class Outer
{
    int x=3;
    public void function()//可调用函数
    {
        new AbsDwmo()//匿名内部类
        {
            void show()
            {
                System.out.println("x==="+x);
            }
            void abc()
            {
                System.out.println("haha");
            }
        }.abc();  //匿名内部类调用函数,匿名内部类方法只能调用一次
    }   
}
// 例 2
interface Inner {    //注释后,编译时提示类Inner找不到 
    String getName(); 
}

public class Outer { 

    public Inner getInner(final String name, String city) { 
        return new Inner() { 
            private String nameStr = name; 
            public String getName() { 
                return nameStr; 
            } 
        }; 
    } 

    public static void main(String[] args) { 
        Outer outer = new Outer(); 
        Inner inner = outer.getInner("Inner", "gz"); 
        System.out.println(inner.getName()); 

        System.out.println(inner instanceof Inner);  //匿名内部类实质上是一个匿名子类的对象
    } /* Output:
        Inner
        true
      *///:~ 
} 

若匿名内部类 (匿名内部类没有构造方法) 需要直接使用其所在的外部类方法的参数时,该形参必须为 final 的;如果匿名内部类没有直接使用其所在的外部类方法的参数时,那么该参数就不必为final 的。

引用地址:https://blog.csdn.net/justloveyou_/article/details/53245561

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容