面向对象之内部类

内部类是什么?它有什么特点?有什么使用场景?

  • 内部类,顾名思义就是定义在另一个类里面的类。就像下面这样:

class Outer    // 外部类
{
    class Inner   // 内部类
    {
    }
}

  • 通过编译上面的java文件我们发现最后总共产生了两个字节码文件:Outer.class和Outer$Inner.class。第一个很好理解,我们需要注意最后一个,因为完全有下面这种可能:

class Outer
{
    class Inner
    {
    }
}

class Outer2
{
    class Inner
    {
    }
}

所以内部类编译后的字节码文件名称也需要用外部类名称进行限定

  • 那么内部类用在什么场景下呢?其实内部类就相当于一种封装,它的出现也是因为人们基于现实生活场景的感知。所以内部类一般用于类的设计。比如:现在有一个身体Body类,还有一个心脏Heart类,Heart类是Body类的一部分并且它需要访问Body类的一些资源,那么这个时候我们就可以将Heart类定义成Body类的内部类。如果这个内部类还可供外部使用时,就可以将其定义为public的,比如:Map接口内部的Entry接口。

// 身体类
class Body {
    String resource = "身体的一些资源";

    // 心脏类
    class Heart {
        // 需要访问resource
    }

}

如何访问内部类?

内部类与外部类如何相互访问?

  • 由于内部类定义在外部类里面,所以其实它与外部类的成员是同级的,内部类就相当于外部类的一个"成员"。所以内部类可以直接访问外部类中的成员。如下:

class Outer
{
    private int num = 3;

    class Inner
    {
        void show()
        {
            System.out.println("show run..." + num);   // 可以直接访问Outer类中的num。
        }
    }
}

  • 而内部类中的细节对外部类是隐藏的,所以外部类中的成员要访问内部类,必须先创建内部类的对象。如下:

class Outer
{
    private int num = 3;

    class Inner
    {
        void show()
        {
            System.out.println("show run..." + num);
        }
    }

    public void method()
    {
        Inner in = new Inner();   // 先创建内部类的对象再访问。
        in.show();
    }
}

其他类如何访问内部类?

这其实与内部类的修饰符有关,此处只探讨内部类被默认访问修饰符和static修饰的特点。

  • 内部类被默认访问修饰符修饰时,我们就可以通过下面的方式访问:

class Outer
{
    private int num = 3;

    class Inner
    {
        void show()
        {
            System.out.println("show run..."+num);
        }
    }

    public void method()
    {
        Inner in = new Inner();
        in.show();
    }
}

class InnerClassDemo
{
    public static void main(String[] args)
    {
        Outer.Inner in = new Outer().new Inner();   // 使用此种方式访问
        in.show();
    }
}

  • 内部类被static修饰时,由于此时该内部类就相当于一个外部类,于是我们就可以通过下面的方式访问:

class Outer
{
    private static int num = 3;   

    static class Inner
    {
        void show()
        {
            System.out.println("show run..."+num);
        }
    }

    public void method()
    {
        Inner in = new Inner();
        in.show();
    }
}

class InnerClassDemo
{
    public static void main(String[] args)
    {
        Outer.Inner in = new Outer.Inner();    // 使用此种方式访问
        in.show();
    }
}

我们可以进一步思考,如果内部类中的成员也被static修饰,那么访问方式又有点不同:


class Outer
{
    private static int num = 3;

    static class Inner
    {
        static void function()
		{
			System.out.println("function run ...." + num);
		}
    }
}

class InnerClassDemo
{
    public static void main(String[] args)
    {
        Outer.Inner.function();     // 使用此种方式访问
    }
}

如果是如下情况,那么内部类就必须被static修饰:


class Outer
{
    private static int num = 3;

    static class Inner     
    {
        void show()
        {
            System.out.println("show run..."+num);
        }
    }

    public static void main(String[] args) {
        new Inner();   // 在外部类的主函数中创建内部类对象时,该内部类必须被static修饰。
    }

}

内部类的小细节

我们知道当类的成员变量和局部变量同名时可以用this关键字进行区分,那如果内部类的成员变量与外部类的成员变量以及局部变量同名时又该如何区分呢?代码如下:


class Outer
{
    int num = 3;

    class Inner
    {
        int num = 4;
        void show()
        {
            int num = 5;
            System.out.println(num);             // 输出5:访问内部类中局部变量的方式
            System.out.println(this.num);        // 输出4:访问内部类中成员变量的方式
            System.out.println(Outer.this.num);  // 输出3:访问外部类中成员变量的方式
        }
    }
    void method()
    {
        new Inner().show();
    }
}


class InnerClassDemo2
{
    public static void main(String[] args)
    {
        new Outer().method();
    }
}

实际开发场景中基本不可能出现上面的情况,但是通过上面的代码的运行结果我们能从侧面看出:内部类能直接访问外部类中成员的原因是内部类持有了外部类的引用。这就类似于子类能够访问父类中内容的原因是子类持有super引用。

局部内部类

局部内部类就像局部变量、局部代码块一样,它就是一个位于函数中的类。比如:


class Outer
{
    void method()
    {
        class Inner  // 局部内部类
        {
            void show() {
                System.out.println("show...");
            }
        }
    }
}

我们需要注意的是:如果在局部内部类中调用了局部内部类所在方法中的变量,那么该变量自动会被final修饰。如下:


class Outer
{
    void method()
    {
        int num = 4;
        class Inner  // 局部内部类
        {
            void show() {
                //num = 9;   // 此句代码会报错:Variable 'num' is accessed from within inner class, needs to be final or effectively final,原因是num会自动被final修饰
                System.out.println("show...");
            }
        }
    }
}

:jdk1.8以前的版本会强制程序员手动添加final修饰符,而在jdk1.8及以后的版本编译器会自动添加。(具体细节请参见JDK1.8的新特性 | 技术)。原因在于:方法中的变量在栈内存中,方法一结束变量就消失了,而局部内部类对象在堆内存中,由于两者的生存周期不同,所以需要将变量设置为final的。

匿名内部类

匿名内部类是什么

我们知道匿名对象其实就是对象的简写格式,匿名内部类也是如此:它就是内部类的简写格式。那么简写肯定是有前提的,创建匿名内部类的前提就是:内部类必须继承一个外部类或者实现一个接口。这怎么理解呢:既然这个内部类是匿名的,那我们肯定要通过其他方式来标识这个内部类,就可以通过继承或者实现接口的方式。例如:


abstract class Demo
{
    abstract void show();
}

class Outer
{
    int num = 4;

    public void method()
    {
        new Demo()    //匿名内部类。
        {
            void show()
            {
                System.out.println("show ........" + num);
            }
        }.show();
    }
}

class InnerClassDemo
{
    public static void main(String[] args)
    {
        new Outer().method();
    }
}

通过上面的代码我们其实可以看出匿名内部类其实就是一个匿名子类对象。这其实就是一种多态(关于多态,可参考面向对象之多态)。

Lambda 表达式

Lambda 表达式是jdk1.8的新特性,它与匿名内部类关系密切。为了更好地理解Lambda 表达式,我们考虑如下情景:


// 分类实体类
class Category {
    public int recordNumber;

    public int getRecordNumber() {
        return recordNumber;
    }
    public void setRecordNumber(int recordNumber) {
        this.recordNumber = recordNumber;
    }
}

// 查询出所有的分类
private List<Category> list() {
    List<Category> cs = categoryDao.list();    // 从数据库中查出分类列表

    // 根据Category的recordNumber属性对分类列表进行降序排序 
    // code...

    return cs;
}

上面code的位置如果使用匿名内部类来实现,就可以像下面这样:


Collections.sort(cs, new Comparator<Category>() {
    @Override
    public int compare(Category c1, Category c2) {
        return c2.recordNumber - c1.recordNumber;
    }
});

如果使用Lambda 表达式就可以像下面这样:


Collections.sort(cs, (c1,c2) -> c2.recordNumber - c1.recordNumber);

Lambda 表达式其实是根据匿名内部类一步一步简化而来的,具体的简化步骤:


1.只保留匿名内部类中的方法参数和方法体,并且在两者之间加上符号"->",于是可以简化成:

Collections.sort(cs, (Category c1, Category c2) -> {
        return c2.recordNumber - c1.recordNumber;
    }
);

2.去掉上面的"return"以及"return后的分号"、"{}",于是可以简化成:

Collections.sort(cs, (Category c1, Category c2) -> c2.recordNumber - c1.recordNumber);

3.去掉上面的参数类型和圆括号(注意:只有一个参数的时候,才可以去掉圆括号),于是可以简化成:

Collections.sort(cs, (c1, c2) -> c2.recordNumber - c1.recordNumber);

通过上面我们可以看到:Lambda 表达式虽然简化了书写,但是代码的可阅读性也大大降低了、这样的代码也不便于调试。所以其使用应该视情况而定。

posted @ 2020-03-15 12:07  samsaraaa  阅读(176)  评论(0编辑  收藏  举报