Java中的内部类

Posted lol-toulan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中的内部类相关的知识,希望对你有一定的参考价值。

内部类:顾名思义,定义在内部的类,所以,在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。

               为什么要用内部类,普通的创建一个新类,创建一个新对象,也能完成相同的作用,下面举例来说明一下:

               这是没有用内部类的实现方法:

abstract  class  Demo{
    //因为Demo类是抽象类,所以把不用实现方法体。谁调用该方法谁重写。
    public abstract void eat();
}

class Son extends Demo{
    //父类中的抽象方法由子类重写。
    public void eat(){
        System.out.println("show son");
    }
}

public class Test {
    public static void main(String[] args) {
        //Son s = new Son();
        //s.eat();
        
        Demo d = new Son();
        d.eat();
    }
}
//运行结果:show son

 我们可以看到,在上述代码中,Son类继承Demo类之后才能重写Demo类中的抽象方法,但是,如果Son类只用一次,那么将其编写为一个新类岂不是太不方便了,

这时就用到了匿名内部类,代码如下:

abstract  class  Demo{
    //因为Demo类是抽象类,所以把不用实现方法体。谁调用该方法谁重写。
    public abstract void eat();
}

//class Son extends Demo{
//    //父类中的抽象方法由子类重写。
//    public void eat(){
//        System.out.println("show son");
//    }
//}

public class Test {
    public static void main(String[] args) {
        //Son s = new Son();
        //s.eat();

        Demo d = new Demo() {
            @Override
            public void eat() {
                System.out.println("show son");
            }
        };
        d.eat();
    }
}

//运行结果:show son
//

//可以看到,我们直接将抽象类Person中的方法在大括号中实现了

//这样便可以省略一个类的书写

//并且,匿名内部类还能用于接口上

下面就来详细了解一下这四种内部类的用法。

              1.成员内部类:成员内部类声明在类中,方法体、代码块之外。和成员变量、成员方法在同一级别。

   看下面例子:

//成员内部类
class Demo{
    public int num1 = 100;
    private  int num2 = 200;

    class Inner{
        public int num3 = 300;
        private int num4 = 400;
        public void test(){
            System.out.println(num1);
            System.out.println(num2);
            System.out.println(num3);
            System.out.println(num4);
        }
    }
    public void test2(){

        Inner i = new Inner();
        i.test();
        System.out.println("-------------");
        System.out.println(num1);
        System.out.println(num2);
        System.out.println(i.num3);
        System.out.println(i.num4);
    }
}


public class Test {
    public static void main(String[] args) {
        Demo d = new Demo();
        d.test2();

    }
}

运行结果:

技术图片
1 100
2 200
3 300
4 400
5 -------------
6 100
7 200
8 300
9 400
View Code

由上述代码我们可以清楚的发现,内部类调用外部类的方法时,可以直接调用(static修饰的内部类除外,因为静态类无法调用非静态变量),而外部类调用内部类时,则需要进行一系列的操作,例如创建对象,才能调用。

静态内部类:

我们知道,以前在学习static时说过,当成员变量或者成员方法被static修饰的,可以通过类名调用,而且,被static修饰的方法不能调用非静态变量,同理,当成员内部类被static修饰时,我们可以把静态内部类当作一个static修饰的变量,或者方法。所以静态内部类也可以通过外部类的类名进行调用。例子如下:

//静态内部类
class Demo {
    public int num1 = 100;
    private static int num2 = 200;

        //静态内部类
    public static class Inner {
        public static int num3 = 300;
        private int num4 = 400;

        public void test() {
            // System.out.println(num1);
            System.out.println(num2);
            System.out.println(num3);
            System.out.println(num4);
        }

        //这是由静态修饰的方法,只能调用静态成员变量和方法
        public static void test2() {
            System.out.println(num2);
            System.out.println(num3);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        //
        Demo.Inner di = new Demo.Inner();
        di.test();
        System.out.println("-------------");
        //因为Inner类是Demo类的静态内部成员方法,所以可以通过,类名.方法名来调用。
        di.test2();
        System.out.println("----------");
        Demo.Inner.test2();
    }
}

//        200
//        300
//        400
//        -------------
//        200
//        300
//        ----------
//        200
//        300

其实我们可以把成员内部类和静态内部类放在一起来看。此外,我们应把内部类当作一个成员变量或者成员方法来看,这样会方便我们理解成员内部类。

局部内部类:

下面我们先看一道面试题:

                           局部内部类访问局部变量的注意事项?:

                    我们通过举例来看结论:

//局部内部类
class Outer {
    private int num = 100;

    public void method() {
       int num2 = 200;
        class Inner {
            public void show() {
                num2 = 200;
                System.out.println(num);
                System.out.println(num2);
            }
        }
        Inner i = new Inner();
        i.show();
    }
}

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

//Error:(11, 17) java: 从内部类引用的本地变量必须是最终变量或实际上的最终变量

我们发现虽然不用final修饰num2,但是当我们尝试改变num2的值时,编译器会告诉我们本地变量必须是最终变量或者实际上的最终变量。所以由此我们可以知道局部内部类访问局部变量是必须用final修饰。

原因是:局部变量随着方法的调用而调用,随着方法的调用结束而消失,而这个时候,局部对象并没有从堆内存中消失,还要用那个变量,为了能让数据继续被使用。所以我们加final修饰。

我们先看一下正确的代码,同时再来看一下Outer类的反编译文件。

技术图片
package com.ni_ming_nei_bu_lei;

//局部内部类
class Outer {
    private int num = 100;

    public void method() {
       int num2 = 200;
        class Inner {
            public void show() {

                System.out.println(num);
                System.out.println(num2);
            }
        }
        Inner i = new Inner();
        i.show();
    }
}

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

// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space
// Source File Name:   Test.java

//反编译文件

//package com.ni_ming_nei_bu_lei;
//
//        import java.io.PrintStream;
//
//class Outer
//{
//
//    private int num;
//
//    Outer()
//    {
//        num = 100;
//    }
//
//    public void method()
//    {
//        final int num2 = 200;
//        class 1Inner
//        {
//
//            final int val$num2;
//            final Outer this$0;
//
//            public void show()
//            {
//                System.out.println(num);
//                System.out.println(num2);
//            }
//
//            1Inner()
//            {
//                this.this$0 = Outer.this;
//                num2 = i;
//                super();
//            }
//        }
//
//        1Inner i = new 1Inner();
//        i.show();
//    }
//
//}
View Code

由反编译文件我们可以清晰看到,虽然在JDK1.7之后局部内部类访问局部变量时,虽然我们不需要手动加上final修饰符,但系统自动的将num2转化为final类型的常量。

匿名内部类:

前提: 存在一个接口或者一个类,,这里的类可以是具体类也可以是抽象类。

格式: new 类名或者接口() {    重写方法     };

在本篇第一个代码,笔者就写了一个匿名内部类呦,如果不理解  ,仔细看下去。

匿名对象的本质:是一个继承了该类或者实现该接口的子类匿名对象。

通过一个例子来解释:

 

//匿名内部类
interface Inter{
    public abstract  void show();
}

class Outer{
    public void method (){
        new Inter (){  //作为一个javaer,我们知道new出来的是对象,既然是对象,那就能调用方法
            public void show(){
                System.out.println("show ");
            };
        }.show();      //所以此处本质上是new出来的对象在调用show方法。
    }
}

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

//结果:show

 

但是问题又来了,这是接口中只有一个方法,那要是有两个哪?有同学可能会说,那还不简单,new  两次,这次写次数都少,那要是有10个,20个哪?都这样带调用难道不是代码冗余太严重了吗?毕竟我们作为一个优秀Javaer,我们需要用最简单的方法解决复杂的问题,So,Javaer大牛们想了一个方法,既然创建的是对象,哪我能不能象普通的创建对象那样,A a = new B();利用a调用对象,答案是肯定的,

格式是:

A a = new A(){    } //注意,这不是第一眼理解的那样,这一句语句意思是,把子类对象赋给父接口,是多态。这个等式左边是父接口或类名,右边是子类匿名对象。

下面通过代码来解释:

package com.ni_ming_nei_bu_lei;

//匿名内部类
interface Inter {
    public abstract void eat();

    public abstract void eat2();
}

class Outer {

//    public void method (){
//        new Inter (){  //作为一个javaer,我们知道new出来的是对象,既然是对象,那就能调用方法
//            public void eat(){
//                System.out.println("show ");
//            };
//        }.eat();      //所以此处本质上是new出来的对象在调用show方法。
//    }


    //这种方法,就算父接口,或者父类中有再多的方法,都可以这样调用无比方便 匿名内部类中必须实现父接口或者父类中的所有方法。
    public void method() {
        Inter i = new Inter() {  //作为一个javaer,我们知道new出来的是对象,既然是对象,那就能调用方法
            public void eat() {
                System.out.println("eat ");
            }

            public void eat2() {
                System.out.println("eat2");
            }
        };     //所以此处本质上是new出来的对象在调用show方法。
        i.eat();
        i.eat2();
    }
}

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

//eat
//eat2

 

补充:匿名内部类在开发中的作用。

代码:

package com.ni_ming_nei_bu_lei;

//匿名内部类开发中的作用
interface Person {
    public abstract void study();
}


class PersonDemo {
    //接口名作为形式参数
    //其实这里要的不是接口,要的是接口实现类对象。
    public void method(Person p) {
        p.study();
    }
}

//实现类接口
class Student implements Person {
    public void study() {
        System.out.println("好好学习,天天向上");
    }
}

public class Test {
    public static void main(String[] args) {
        //普通
        PersonDemo pd = new PersonDemo();
        Person p = new Student();  //多态
        pd.method(p);
        System.out.println("----------");

        //匿名内部类,简单
        pd.method(new Person() {
            @Override
            public void study() {
                System.out.println("好好学习,天天向上");
            }
        });
    }
}

//   好好学习,天天向上
//   ----------
//   好好学习,天天向上

 

 

 

                  

以上是关于Java中的内部类的主要内容,如果未能解决你的问题,请参考以下文章

java中的内部类

java中的内部类

Java中的四种内部类总结

java内部类的匿名内部类

浅谈Java中的内部类

并发包java.util.concurrent.locks.Lock