ITPub博客

Java编程思想学习笔记1 - 内部类

原创 Java 作者:zhuyiquan90 时间:2018-06-28 18:26:18 0 删除 编辑

内部类简单来说就是定义在一个类内部的类。一直很难理解为什么要使用内部类,对内部类的理解始终停留在表明。今天详细学习了Java内部类的机制,总结下内部类的使用。归纳大纲如下: 
1. 内部类的基础结构 
2. 内部类的优点和使用场景 
3. 内部类的分类 
4. 内部类的继承 
若有不正之处,请批评指教,共同成长!请尊重作者劳动成果,转载请标明原文链接




1. 内部类的基础结构

package c10; public class Parcel1 { class Contents { private int i = 11; public int value(){ return i;
        }
    }

    class Destination { private String label;
        Destination (String whereTo) {
            label = whereTo;
        }
        String readLabel() { return label;
        }
    } public void ship(String dest) {
        Contents c = new Contents();
        Destination d = new Destination(dest);
        System.out.println(d.readLabel());
    } public static void main(String[] args) {

        Parcel1 p = new Parcel1();
        Parcel1.Contents contents = p.new Contents();
        Parcel1.Destination dest = p.new Destination("Tasmania");
    }   
}

输出结果:Tasmania
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

内部类其实是一个编译时的概念(内部类与普通类的不同体现在编译上),如上例所示,编译完成后,分别生成三个class文件:Parcel1.class, Parcel1$Contents.class, Destination$Contents.class。 
.class文件包含了如何创建该类型对象的全部信息。内部类生成的.class文件有严格的规则:外部类名称+$+内部类名称。如果是匿名内部类,编译器会简单地产生一个数字作为其表示符,如Parcel1$1.class.


2. 内部类的优点和使用场景

Thinking in Java中通过一个章节详细讨论了为什么需要内部类,可能是因为笔者的理解能力有限,直到今天也无法明确体会作者的意思,总结起来有以下这些: 
1. 内部类最吸引人的原因是:每个内部类都能独立继承一个(接口或类)的实现,所以无论外围类是否已经继承了某个(接口或类)实现,对于内部类都没有影响 
2. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与外围类对象的信息相互独立 
3. 单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或集成同一个类 
4. 内部类并没有令人迷惑的”is-a”关系,是一个独立的实体 
也就是说通过内部类可以变相的实现类的多重继承(我们知道Java中只能引用多个接口,而不能继承多个类)。比如这样:

//基类A、B、C class A {} abstract class B {}
class C {} //派生类通过内部类同时继承ABC class Z extends A {
    class Z1 extends B{
        C makeC() { return new C(){}; }
    }
}
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

也看了网上各位大神的文章,总结归纳,自己对为什么使用内部类的理解是这样的:

使用内部类会破坏良好的代码结构(第一次看到会觉得怪怪的),但为类的设计者提供了一种途径来隐藏类的实现细节(这些往往是客户端程序员所不关注的),同时也是代码变的更加灵活。


3. 内部类的分类

笔者认为内部类之所以很难理解,正是因为语法覆盖了大量难以理解的技术(如果都像基础内部类那样,就没有多少意思了)。内部类可以分为四种:成员内部类,局部内部类,嵌套类,匿名内部类。

  • 静态内部类的应用场景是:只可以访问外部类的静态成员变量和静态成员方法。
  • 成员内部类的应用场景是:它可以访问它的外部类的所有成员变量和方法,不管是静态的还是非静态的都可以。
  • 局部内部类:像局部变量一样,不能被public, protected, 
    private和static修饰。只能访问方法中定义的final类型的局部变量。
  • 匿名内部类:匿名内部类就是没有名字的局部内部类,不使用关键字class, extends, implements,没有构造方法。匿名内部类隐式地继承了一个父类或者实现了一个接口。匿名内部类使用得比较多,通常是作为一个方法参数。

成员内部类

成员内部类拥有对外部类所有元素的访问权。 
在成员内部类要引用外部类对象时,使用outer.this来表示外部类对象; 
而需要创建内部类对象,可以使用outer.inner obj = outer.new inner(); 
(注意,在拥有外部类对象之前是不可能创建内部类对象的,除非你创建的是嵌套类) 
举个例子:

package c10; public class Parcel { private int num = 11;

    class Contents { private int num = 12; public void print() { int num = 13;
            System.out.println("局部变量:" + num);
            System.out.println("内部局部变量:" + this.num);
            System.out.println("外部局部变量:" + Parcel.this.num);
        }

    } public static void main(String[] args) {
        Parcel p = new Parcel();
        Parcel.Contents c = p.new Contents();
        c.print();
    }
}

局部变量:13 内部局部变量:12 外部局部变量:11 
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

局部内部类

当你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但又不希望这个类是公共可用的时,可以通过以下方式实现:

  • 一个定义在方法中的类
  • 一个定义在作用域内的类
  • 一个实现了接口的匿名类
  • 一个扩展了非默认构造器的匿名类
  • 执行字段初始化的匿名类

定义在方法中的内部类:

public class Parcel5 { 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"); 
    } 
} 

Tasmania
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

PDestination类是destination()方法的一部分,在之外不能被访问。注意return语句中的向上转型,返回的是Destination的引用,它是PDestination的基类。

定义在作用域中的内部类:

public class Parcel6 { 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(); 
    } 
}
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

匿名内部类

一个匿名内部类的例子如下,匿名类是内部类比较常用的方式,简化了代码,更加灵活:

package c10; //注释后,编译报错:Contents cannot be resolved to a type //interface Contents { } public class Parcel7 { public Contents contents() { return new Contents() { private int i = 11; public int value(){ return i; }
        };
    } public static void main(String[] args) {
        Parcel7 p = new Parcel7();
        Contents c = p.contents();
    }

}
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

需要注意: 
1. new匿名类前,这个类需要定义,否则编译报错; 
2. 当所在的方法的形参需要被内部类里面使用时,该形参必须为final,否则编译报错,如下例所示:

package c10;

interface Destination {} public class Parcel10 { public Destination destination( final String dest,final float price) { return new Destination() { private int cost;
            {
                cost = Math.round(price); if( cost > 100 ) {
                    System.out.println("Over budget");
                }
            } private String label = dest; public String readLabel() { return label; }

        };
    } public static void main(String[] args) {
        Parcel10 p = new Parcel10();
        Destination d = p.destination("Nanjing", 101.396F);
    }
}
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

通过匿名内部类,可以写出一个完美的工厂模式:

package c10;

interface Service { void method1(); void method2();
}

interface ServiceFacotry {
    Service getService();
}

class Implementation1 implements Service { private Implementation1() {} @Override public void method1() {
        System.out.println("Implementation1 method1");      
    } @Override public void method2() {
        System.out.println("Implementation1 method2");          
    } public static ServiceFacotry factory = new ServiceFacotry() { @Override public Service getService() { return new Implementation1();
                }
            };
}

class Implementation2 implements Service { private Implementation2() {} @Override public void method1() {
        System.out.println("Implementation2 method1");      
    } @Override public void method2() {
        System.out.println("Implementation2 method2");          
    } public static ServiceFacotry factory = new ServiceFacotry() { @Override public Service getService() { return new Implementation2();
                }
            };
} public class Factories { public static void serviceConsumer(ServiceFacotry fact) {
        Service s = fact.getService();
        s.method1();
        s.method2();
    } public static void main(String[] args) {
        serviceConsumer(Implementation1.factory);
        serviceConsumer(Implementation2.factory);
    }
}

Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

嵌套类

如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为static。嵌套类意味着: 
1. 要创建嵌套类的对象,并不需要先创建外部类的对象 
2. 不能从嵌套类的对象中访问非静态的外部类对象 
3. 嵌套类和普通的内部类还有一个区别:普通内部类不能有static数据和static属性,也不能包含嵌套类,但嵌套类可以。而嵌套类不能声明为private,一般声明为public,方便调用。

package c10; public class Parcel11 { private static int age = 12; static class Contents { public void print() {
            System.out.println(age);
        }
    } public static void main(String[] args) {
        Contents c = new Contents();
        c.print();
    }
} 12 
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

4. 内部类的继承

内部类的继承,是指内部类被继承,普通类extends内部类。而这时候代码上要有点特别处理,具体看以下例子:

public class InheritInner extends WithInner.Inner { // InheritInner() 是不能通过编译的,一定要加上形参  InheritInner(WithInner wi) { 
        wi.super(); 
    } public static void main(String[] args) { 
        WithInner wi = new WithInner(); 
        InheritInner obj = new InheritInner(wi); 
    } 
} 

class WithInner { 
    class Inner {  
    } 
}
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

可以看到子类的构造函数里面要使用父类的外部类对象.super();而这个对象需要从外面创建并传给形参。


参考资料

请登录后发表评论 登录
全部评论

注册时间:2018-06-07

  • 博文量
    21
  • 访问量
    3592