栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

聊聊Java中的内部类

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

聊聊Java中的内部类

内部类的基本定义

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

  • demo1:内部类的基本定义
class Outer{    //外部类
    private String msg = "Outer Class"; //私有成员属性
    public void fun(){ //普通方法
        Inner in = new Inner(); //实例化内部类对象
        in.print(); //调用内部类方法
    }
    class Inner{    //在Outer类的内部定义了Inner类
        public void print(){
            System.out.println(Outer.this.msg); // Outer类中的属性
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Outer out = new Outer(); //实例化外部类对象
        out.fun();  //调用外部类中的方法
    }
}
为什么需要内部类

每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。从这角度看,内部类使得多重继承的解决方案变得完整。

链接到外部类

内部类可以轻松访问外部类中的私有属性。

因为从整体的代码结构来讲内部类的结构并不合理,所以内部类本身最大的缺陷在于破坏了程序的结构,但是破坏要有目的的破坏,那么它也一定会有其优势。

如果要想更好观察内部类的优势,就可以将内部类拿到外部来。

  • demo2:将demo1分为两个类,将Inner从Outer中拿出来
class Outer{    //外部类
    private String msg = "Outer Class"; //私有成员属性
    public void fun(){ //普通方法
        // 需要将当前对象Outer传递到Inner类之中
        Inner in = new Inner(this); //实例化内部类对象
        in.print(); //调用内部类方法
    }
    // msg属性如果要被外部访问需要提供有getter
    public String getMsg(){
        return this.msg;
    }
}

class Inner{    //在Outer类的内部定义了Inner类
    // Inner这个类对象实例化的时候需要Outer类的引用
    private Outer outer;
    // 应该通过Inner类的构造方法获取Outer类对象
    public Inner(Outer outer){
        this.outer = outer;
    }
    public void print(){
        // 如果要想调用外部类中的getter方法,那么一定需要有outer类对象
        System.out.println(this.outer.getMsg()); // Outer类中的属性
    }
}

public class Main {
    public static void main(String[] args) {
        Outer out = new Outer(); //实例化外部类对象
        out.fun();  //调用外部类中的方法
    }
}

可以发现,整体的操作之中,折腾半天主要的目的就是为了让Inner这个内部类可以访问Outer这个类中的私有属性。如果不用内部类的时候,整体代码非常的麻烦,所以可以得出内部类的优点:轻松访问外部类中的私有属性;同理,外部类也可以轻松访问内部类中的私有成员或私有方法。 内部类如果要是提供有实例化对象了,一定要先保证外部类已经实例化了。

  • demo3:外部类访问内部类中的私有属性
class Outer{    //外部类
    private String msg = "Outer Class"; //私有成员属性
    public void fun(){ //普通方法
        Inner in = new Inner(); //实例化内部类对象
        in.print(); //调用内部类方法
        System.out.println(in.info); //访问内部类私有属性
    }
    class Inner{    //在Outer类的内部定义了Inner类
        private String info = "inner";
        public void print(){
            System.out.println(Outer.this.msg); // Outer类中的属性
        }
    }
}
public class Main {

    public static void main(String[] args) {
        Outer out = new Outer(); //实例化外部类对象
        out.fun();  //调用外部类中的方法
    }
}

使用了内部类之后,内部类与外部类之间的私有操作的访问就不再需要通过getter、setter以及其他间接的方式完成。

内部类自动拥有对其外围类所有成员的访问权,这是如何做到的呢?

  • 当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。幸运的是,编译器会帮你处理所有的细节,但你现在可以看到:内部类的对象只能在与其外围类的对象相关联的情况下才能被创建(就像你应该看到的,在内部类是非static类时)。构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。
内部类标识符

每个类都会产生一个.class文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个“meta-class”,叫做Class对象),内部类也必须生成一个.class文件以包含它们的Class对象信息。这些类文件的命名有严格规则:外围类的名字,加上“$”,再加上内部类的名字。

将demo3的java文件编译后,会生成3个字节码文件:

Main.class
Outer$Inner.class
Outer.class

在内部类编译完成之后会自动形成一个Outer$Inner.class类文件,其中$这个符号换到程序之中就变成了"."。所以内部类全称:“外部类.内部类”。

如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外围类标识符与“$”的后面。

  • 反编译Outer$Inner.class文件
import java.io.PrintStream;
class Outer$Inner
{
  private String info = "inner";
  
  Outer$Inner(Outer this$0) {}
  
  public void print()
  {
    System.out.println(Outer.access$100(this.this$0));
  }
}
.this语法

如果需要生成对外部类对象的引用,可以使用.this语法,即外部类.this。这样产生的引用自动地具有正确的类型,这一点在编译器就被知晓并受到检查,因此没有任何运行时开销。

class Outer{    //外部类
    private String msg = "Outer Class"; //私有成员属性
    class Inner{    //在Outer类的内部定义了Inner类
        public void print(){
            System.out.println(Outer.this.msg); // Outer类中的属性
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Outer.Inner in = new Outer().new Inner();
        in.print();
    }
}

如果此时Inner类只允许Outer类来使用,那么在这样的情况下就可以使用private进行私有定义。

class Outer{    //外部类
    private String msg = "Outer Class"; //私有成员属性
    private class Inner{    //在Outer类的内部定义了Inner类
        public void print(){
            System.out.println(Outer.this.msg); // Outer类中的属性
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Outer.Inner in = new Outer().new Inner();
        in.print();
    }
}
Error:(12, 14) java: Outer.Inner 在 Outer 中是 private 访问控制
.new语法

内部类本身也属于一个类,虽然在大部分的情况下内部类往往是被外部类包裹,但是外部依然可以产生内部类的实例化对象,而此时需要.new语法:

外部类.内部类 内部类对象 = new 外部类().new 内部类();

  • demo4:使用.new创建内部类对象
public class DotNew {
    public class Inner {}

    public static void main(String[] args) {
        DotNew dn = new DotNew();
        DotNew.Inner dni = dn.new Inner();
    }
}
内部接口

在Java之中类作为最基础的机构体实际上还有与之类似的抽象类或者是接口,抽象类与接口中都可以定义内部结构。

  • demo:定义内部接口
interface IChannel{ //定义接口
        public void send(IMessage msg); //发送消息
        interface IMessage{ //内部接口
            public String getContent(); //获取消息内容
        }
    }
    class ChannelImpl implements IChannel{
        public void send(IMessage msg){
            System.out.println("发送消息:"+msg.getContent());
        }
        class MessageImpl implements IMessage{
            public String getContent(){
                return "Inner Interface";
            }
        }
    }
    public class Main {
        public static void main(String[] args) {
            IChannel channel = new ChannelImpl();
            channel.send(((ChannelImpl)channel).new MessageImpl());
        }
    }
内部抽象类
  • 内部抽象类可以定义在普通类、抽象类、接口内部。
interface IChannel{ //定义接口
        public void send(); //发送消息
        abstract class AbstractMessage{
            public abstract String getContent();
        }
    }
    class ChannelImpl implements IChannel{
        public void send(){
            AbstractMessage msg = new MessageImpl();
            System.out.println(msg.getContent());
        }
        class MessageImpl extends AbstractMessage{
            public String getContent(){
                return "abstract class";
            }
        }
    }
    public class Main {
        public static void main(String[] args) {
            IChannel channel = new ChannelImpl();
            channel.send();
        }
    }
接口内部进行接口实现

内部类是一种非常灵活的定义结构。如果现在定义了一个接口,那么可以在内部利用类实现该接口,在JDK1.8之后,接口中追加了static方法可以不受到实例化对象的控制,现在就可以利用此特性来完成功能。

  • demo
interface IChannel{ //定义接口
        public void send(); //发送消息
        class ChannelImpl implements IChannel{
            public void send(){
                System.out.println("send");
            }
        }
        public static IChannel getInstance(){
            return new ChannelImpl();
        }
    }
    public class Main {
        public static void main(String[] args) {
            IChannel channel = IChannel.getInstance();
            channel.send();
        }
    }
static定义内部类

如果说现在在内部类上使用了static定义,那么这个内部类就变为了“外部类”。static定义的都是独立于类的结构,所以该类结构就相当于是一个独立的程序类了。需要注意的是,static定义的不管是类还是方法只能够访问static成员,所以static定义的内部类只能访问外部类中的static属性或方法;

  • 范例:使用static定义内部类
class Outer{
        private static final String MSG = "static";
        static class Inner{
            public void print(){
                System.out.println(Outer.MSG);
            }
        }
    }
    public class Main {
        public static void main(String[] args) {
            Outer.Inner in = new Outer.Inner();
            in.print();
        }
    }

以后在开发之中如果发现类名称上提供有“."首先应该立刻想到这是一个内部类结构,如果可以直接进行实例化,则应该立刻认识到这是一个static定义的内部类。

如果以static定义内部类的形式来讲并不常用,static定义内部接口的形式最为常用。

  • 范例:使用static定义内部接口
interface IMessageWarp{ //消息包装
        static interface IMessage{
            public String getContent();
        }
        static interface IChannel{
            public boolean connect();   //消息的发送通道
        }
        public static void send(IMessage msg,IChannel channel){
            if (channel.connect()) {
                System.out.println(msg.getContent());
            }else{
                System.out.println("消息通道无法建立,消息发送失败");
            }
        }
    }
    class DefaultMessage implements IMessageWarp.IMessage{
        public String getContent(){
            return "msg";
        }
    }
    class NetChannel implements IMessageWarp.IChannel{
        @Override
        public boolean connect() {
            return true;
        }
    }
    public class Main {
        public static void main(String[] args) {
            IMessageWarp.send(new DefaultMessage(),new NetChannel());
        }
    }

之所以使用static定义内部接口,主要是因为这些操作是属于一组相关的定义,有了外部接口之后,可以更加明确描述出这些接口的主要功能。

内部接口 + static 出现形式较多。

方法中定义内部类

内部类可以在任意的结构中进行定义,这就包括了:类中,方法中,代码块中,但是方法中定义较为常见。

在一个方法里面或者在任意的作用域内定义内部类有2个理由:

  1. 你实现了某类型的接口,于是可以创建并返回对其的引用。

  2. 你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。

  • demo:观察在方法中定义内部类
class Outer{
        private String msg ="function Inner";
        public void fun(long time){
            class Inner{ // 内部类
                public void print(){
                    System.out.println(Outer.this.msg);
                    System.out.println(time);
                }
            }
            new Inner().print();
        }
    }
    public class Main {
        public static void main(String[] args) {
            new Outer().fun(12313123);
        }
    }

此时在fun()方法内部提供有Inner内部类的定义,并且可以发现内部类可以直接访问外部类中的私有属性也可以直接访问方法中的参数,但是对于方法中的参数直接访问是从JDK1.8开始支持的

在JDK1.8之前方法中的内部类要想访问方法中的参数,方法中的参数前必须追加final。之所以取消这样的限制,主要是为了其扩展的函数式编程准备的功能。

  • demo
class Outer{
        private String msg ="function Inner";
        public void fun(final long time){
            final String info = "我很好";
            class Inner{ // 内部类
                public void print(){
                    System.out.println(Outer.this.msg);
                    System.out.println(time);
                    System.out.println(info);
                }
            }
            new Inner().print();
        }
    }
    public class Main {
        public static void main(String[] args) {
            new Outer().fun(12313123);
        }
    }
匿名内部类

匿名内部类是一种简化的内部类的处理形式,其主要是在抽象类和接口的子类上使用。

  • demo:使用默认构造器
interface IMessage{
        public void send(String str);
    }
    public class Main {
        public static void main(String[] args) {
            IMessage msg = new IMessage(){
                public void send(String str){
                    System.out.println(str);
                }
            };
            msg.send("匿名");
        }
    }
  • demo:使用有参构造器
public class Wrapping {
    private int i;

    public Wrapping(int i) {
        this.i = i;
    }

    public int value() {
        return i;
    }
}

public class Parcel8 {
    public Wrapping wrapping(int x) {
        // base constructor call:
        return new Wrapping(x) { // Pass constructor argument.
            public int value() {
                return super.value() * 47;
            }
        };
    }

    public static void main(String[] args) {
        Parcel8 parcel8 = new Parcel8();
        Wrapping wrapping = parcel8.wrapping(10);
        System.out.println("wrapping.value() = " + wrapping.value());
    }
}
///: wrapping.value() = 470

有些时候为了更加方便的体现出匿名内部类的使用,往往可以利用静态方法做一个内部的匿名内部类实现。

  • 范例:在接口中直接定义匿名内部类
interface IMessage{
        public void send(String str);
        public static IMessage getInstance(){
            return new IMessage() {
                @Override
                public void send(String str) {
                    System.out.println(str);
                }
            };
        }
    }
    public class Main {
        public static void main(String[] args) {
            IMessage.getInstance().send("niming");
        }
    }

与内部类相比匿名内部类只是一个没有名字的只能够使用一次的,并且结构固定的子类。

构造器

如果只是简单地给一个字段赋值,那么 demo:使用有参构造器 中的方法是很好的。但是,如果想做一些类似构造器的行为,该怎么办呢?在匿名类中不可能有命名构造器(因为它根本没名字!),但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果,就像这样:

  • 匿名内部类的构造器的效果
// Creating a constructor for an anonymous inner class.
abstract class base {
    public base(int i) {
        System.out.println("i = " + i);
    }

    public abstract void f();
}

public class AnonymousConstructor {
    public static base getbase(int i) {
        return new base(i) {
            {
                System.out.println("Inside instance initializer");
            }

            @Override
            public void f() {
                System.out.println("In anonymous f()");
            }
        };
    }

    public static void main(String[] args) {
        base base = getbase(47);
        base.f();
    }
}
//: i = 47
//  Inside instance initializer
//  In anonymous f()

在匿名内部类的构造器的效果中,不要求变量i一定是final的。因为i被传递给匿名类的基类的构造器,它并不会在匿名类内部被直接使用(JDK1.8之前)。

对于匿名类而言,实例初始化的实际效果就是构造器。当然它受到了限制-你不能重载实例初始化方法,所以你仅有一个这样的构造器。

匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。

嵌套类

如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static。这通常称为嵌套类。想要理解static应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了。嵌套类意味着:

  1. 要创建嵌套类的对象,并不需要其外围类的对象。

  2. 不能从嵌套类的对象中访问非静态的外围类对象。

嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/530935.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号