基础巩固--内部类

一.什么是内部类

可以将一个类的定义放在另一个类的内部定义,这就是内部类.

public class OuterClass{
    private String name;
    private int age;
    public OuterClass(){}
    public OuterClass(String name, int age){
        this.name = name;
        this.age = age;
    }
    public String toString(){
        return "name:"+name+",age"+age;
    }
    class InnerClass(){
        public String getName(){
            return name;
        }
        public int getAge)_{
            return age;
        }
        public OuterClass getOuter(){
            return OuterClass.this;
        }
    }
}

这里InnerClass就是一个内部类,而OuterClass是该内部类的外部类.从代码中可以看出,内部类可以访问其外部类的所有成员,而不需要任何特殊条件.因此,内部类拥有其外部类的所有元素的访问权.
那么这是如何做到的呢?
想要理解其中的原因,我们首先需要看一下内部类的创建语法:

OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
// 或者
OuterClass.InnerClass inner = new OuterClass().new InnerClass();

通过创建内部类的语法,可以看到:构建内部类对象的时候,需要一个指向其外部类对象的引用,如果编辑器访问不到这个引用就会报错.而且,内部类创建时,此内部类对象必定会秘密的捕获一个指向创建该内部类的外部类对象的引用.然后,在访问此外部类的成员时,就使用这个引用来选择外部类的成员.
从上面的结论中,可以得到另一个重要的概念:在拥有外部类对象之前是不可能创建内部类对象的,必须使用外部类的对象创建内部类对象(嵌套类即静态内部类除外,下文会具体说明).
如果需要生成对外部类对象的引用,可以使用上面代码片段中的OuterClass.this来获取该内部类对象的外部类对象引用.

二.为什么使用内部类

总结’think in java’大体可以得出以下几条结论:
1.每个内部类都能独立地继承一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
2.内部类有效的实现了”多重继承”,也就是说可以通过让内部类继承多个类或抽象类来实现多继承的效果.
3.内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外部类对象的信息相互独立.
4.在单个外部类中,可以让对个内部类一不同的方式实现同一个接口,或继承同一个类.
5.创建内部类对象的时刻不依赖于外部类对象的创建.
6.内部类没有令人迷惑的”is-a”关系,它是一个独立的实体.
7.内部类提供了更好的封装,除了该外围类,其他类都不能访问。

下面将逐一说明内部类的用法.

先看一个demo

public interface MyMath {
    long math(long num);
}

public class MyMathDemo implements MyMath {
    @Override
    public long math(long num) {
        return num+num;
    }
    class MyMathDemo2 implements MyMath{
        @Override
        public long math(long num) {
            return num*num;
        }
    }
    public static void main(String[] args) {
        MyMath demo1 = new MyMathDemo();
        MyMath demo2 = ((MyMathDemo)demo1).new MyMathDemo2();
        System.out.println(demo1.math(10L));
        System.out.println(demo2.math(10L));
    }
}
//output
20
100

从上面的代码中可以看出,一个类中可以通过内部类对接口的一个方法采用不同的实现,无论外部类是否实现了这个接口,对内部类都没有影响,这也从侧面反映出内部类并没有is-a关系,它就是一个独立的实体.而且,我们还可以让多个内部类以不同的方式实现同一个接口,或继承同一个类,达到实际应用中的目的.

接下来我们再看一个demo

interface A {}
interface B {}

class X implements A,B{
}
class Y implements A{
    B markB() {
        return new B() {};//这里是一个匿名内部类
    }
}

上面的两个实现类X,Y从实际的观点来看并没有什么区别,都可以正常运作.但是,如果A,B是两个类呢?如何实现多继承?这是内部类的优势就体现出来了,来看一下的demo

class D {}
abstract class E {}

class Z extends D {
    E markE() {
        return new E() {};
    }
}

通过以上的两个例子,可以发现,内部类有效的实现了”多重继承”,解决了java的单继承限制.

下面看一下”think in java”中的一个优秀的代码片段:

public interface Selector {
    boolean end();
    Object current();
    void next();
}

public class Sequence {
    private Object[] items;
    private int next = 0;
    public Sequence(int size) {
        items = new Object[size];
    }
    public void add(Object x) {
        if(next < items.length)
            items[next++] = x;
    }
    public Selector selector() {
        return new SequenceSelector();
    }

    private class SequenceSelector implements Selector{
        private int i = 0;
        @Override//检测序列是否到达末尾
        public boolean end() {
            return i == items.length;
        }
        @Override//访问当前对象
        public Object current() {
            return items[i];
        }
        @Override//移动到序列的下一个对象
        public void next() {
            if(i < items.length)
                i++;
        }
        public Sequence getSequence() {
            return Sequence.this;
        }
    }
}

这段代码中,内部类实现了Selector接口,外部类中selector()方法可以返回一个Selector接口的引用,它是内部类的实例对象.而这样讲内部类向上转型为其基类,所得到的只是指向其基类或接口的引用,所有能很方便的隐藏其实现细节,内部类中扩展基类的方法也将隐藏.而且,这段代码中内部类是以private修饰的,在Sequence类以外获得的Selector引用不能向下转型.(proceted内部类同样不可向下转型,除非是继承自它的子类).

三.成员内部类

成员内部类也是最普通的内部类,它是外围类的一个成员,所以他是可以无限制的访问外围类的所有 成员属性和方法,尽管是private的,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。
在成员内部类中要注意两点,第一:成员内部类中不能存在任何static的变量和方法;第二:成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类。

四.局部内部类

有这样一种内部类,它是嵌套在方法和作用于内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。
这里引用”think in java”中的代码片段来具体的演示下
定义在方法内局部内部类:

public class Parcel5 {  
    public Destionation destionation(String str){  
        class PDestionation implements Destionation{  
            private String label;  
            private PDestionation(String whereTo){  
                label = whereTo;  
            }  
            public String readLabel(){  
                return label;  
            }  
        }  
        return new PDestionation(str);  
    }  
    public static void main(String[] args) {  
        Parcel5 parcel5 = new Parcel5();  
        Destionation d = parcel5.destionation("chenssy");  
    }  
}  

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

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("chenssy");  
            String string = ts.getSlip();  
        }  
    }    
    public void track(){  
        internalTracking(true);  
    }  
    public static void main(String[] args) {  
        Parcel6 parcel6 = new Parcel6();  
        parcel6.track();  
    }  
} 

五.静态内部类(嵌套类)

关键字static中提到Static可以修饰成员变量、方法、代码块,其他它还可以修饰内部类,使用static修饰的内部类我们称之为静态内部类,不过我们更喜欢称之为嵌套内部类。静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着:
1、 它的创建是不需要依赖于外围类的。
2、 它不能使用任何外围类的非static成员变量和方法。

六.匿名内部类

从上文的代码中,可以发现注释是匿名内部类的代码.我们也可以看下面的例子

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();
    }
}

new Contents(){};语法指的是:创建一个继承自Contents的匿名内部类对象,通过new表达式返回的引用自动向上转型为对Contents的引用.
我们再看一个例子:

public class Parcel8{
    public Destination destination(final String dest, final float price) {
        return new Destination() {
            private int cost;
            {
                cost = Math.round(price);
            }
            private String label = dest;
            public String readLabel() {
                return label;
            }
        };
    }
}

这段代码中有两个问题:
1.为什么destination()方法的参数声明是final的?
在JDK1.7及以前.,如果参数变量在匿名内部类中直接使用,那么编译器要求其参数引用是final的,否则编译报错.如果变量传递给匿名类的基类构造器,不在匿名类中直接使用,就不要求一定是final的.
这项语法规则在JDK1.8之后就没有了.
2.怎么喂匿名内部类创建一个构造器效果?
要知道匿名内部类是不能有构造器的.因为它根本没有名字.所以想实现构造器的效果,只能模拟实现构造器的行为.
首先对象被创建的时候发生了什么.简单地说,分配内存,存放对象自己的实例变量及其从父类继承来的变量.分配内存的同时,实例变量会被赋予默认值,实例变量初始化,接着是实例代码块初始化,最后是构造函数初始化.实际上,如果我们对实例变量直接赋值或者使用实例代码块赋值,那么编译器会将其中的代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后,构造函数本身的代码之前。
所以,代码中加入了代码块对cost实例初始化,可以有效地模拟构造器的效果.

你可能感兴趣的:(基础巩固--内部类)