关于java:浅谈java内部类

36次阅读

共计 6464 个字符,预计需要花费 17 分钟才能阅读完成。

前言

说到 java 外部类,想必大家首先会想到比拟罕用的“匿名外部类”,但实际上,这只是外部类的其中一种应用形式而已。外部类的应用形式实际上总共包含:成员外部类,办法部分类,匿名外部类,上面,我就给大家来一一介绍:

为什么要应用外部类

有的时候你可能有这样一种需要:对一个类(假如它为 MyClass.java)创立一个和它相干的类(假如它是 Part.java),但因为 Part.java 和 MyClass 之间的分割“严密”且“繁多”,导致咱们在这种状况下,不心愿像上面这样减少一个额定的兄弟类

├─MyClass      
└─Part

而心愿能将 Part.java 的数据暗藏在 MyClass.java 外部,于是这个时候外部类就堂而皇之地呈现了

那么,这个不请自来的外部类到底给咱们上述的场面造成了怎么的扭转呢?让咱们来看看:

减少一个额定的兄弟类 Part:

  1. 对一些没有关联的类可见(如果 protected 则对同一包内类可见,如果 public 则对所有类可见)
  2. 不能齐全自在的拜访 MyClass 中的公有数据(必须通过拜访器办法)
  3. 新增了一个 java 文件

应用外部类,将 Part 类的定义写入 MyClass 外部

  1. 能够缩小多余的可见性,例如可把 Part 在 MyClass 外部定义为公有,这样对同一包内其余类也不可见了
  2. 外部类(Part)能够自在拜访外围类的所有数据(MyClass),包含公有数据
  3. 缩小了一个 java 文件,使得类构造更简洁

成员外部类

故名思议,成员外部类嘛~ 应用当然和成员变量很类似咯

你能够像

private String data

这样定义一个“平行的”成员外部类:

private class Inner

具体看上面的例子:

Outter.java:

public class Outter {
  // 成员变量 data
  private String data = "内部数据";
 
  // 定义一个外部类
  private class Inner {public void innerPrint () {System.out.println(data);
    }
   } 
     
  // 外部类的办法,new 一个外部类的实例并调用其 innerPrint 办法
  public void outterPrint () {Inner i = new Inner();
    i.innerPrint();}
}

Test.java:

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

后果输入:

内部数据

看来这还是能达到咱们预期的成果的:因为将 Inner 外部类设为 private,它变得只对咱们以后的外部类 Outter 类可见,咱们胜利地把它 ” 暗藏 ” 在了 Outter 类外部,与此同时,它还自在地拜访到了 Outter 类的公有成员变量 data

两个 this

尽管下面的例子看起来挺简略的,但实际上外部类的作用机制还是比较复杂的。

首先要思考的是“this”的问题,外部类和外部类各有一个 this,关键在于外部类中咱们如何对这两个 this 作出辨别:

咱们假如下面的例子中的 Inner 类外部有一个办法 fn:

private class Inner {public  void fn () {
    Outter.this // 指向 Outter 实例对象的 this 援用
    this  // 指向 Inner 实例对象的 this 援用
  }
} 

在这个办法 fn 里,Outter.this 是指向 Outter 实例对象的 this 的援用,而 this 是指向 Inner 实例对象的 this 的援用

咱们拜访类中成员变量有两种形式:隐式拜访(不加 this)和显式拜访(加 this)

隐式拜访类中成员变量

让咱们对下面的 Outter.java 做一些改变,减少一行代码:

public class Outter {
  // 成员变量 data
  private String data = "内部数据"; 
  // 定义一个外部类
  private class Inner {
    // 减少 Inner 类对 data 成员变量的申明
    private String data = "外部数据" 
    public void innerPrint () {System.out.println(data);
    }
  } 
     
  // 外部类的办法,new 一个外部类的实例并调用其 innerPrint 办法
  public void outterPrint () {Inner i = new Inner();
    i.innerPrint();}
}

后果输入:

外部数据

如此可见,外部类内申明的数据会笼罩外部类的同名数据。或者说, 在上述例子中,对于 data 成员变量,它会首先在 Inner 的 this 中查找有无这个成员变量,而后没有,那么就再在 Outter.this 中查找

显式拜访类中成员变量

但有的时候咱们心愿既能拜访外部类的成员变量,同时也能拜访外部类的成员变量,这个时候咱们就要应用到 this 了,然而如何辨别外部类和外部类的 this 呢?你能够这样:

以上述例子为例:

拜访外部类定义的成员变量:Outter.this.data
拜访外部类定义的成员变量:this.data

如下图所示

public class Outter {
  // 外部类的成员变量 data
  private String data = "内部数据"; 
  // 定义一个外部类
  private class Inner {
    // 外部类的成员变量 data
    private String data = "外部数据";
    public void innerPrint () {System.out.println(Outter.this.data);
      System.out.println(this.data);
    }
  } 
     
  // 外部类的办法,new 一个外部类的实例并调用其 innerPrint 办法
  public void outterPrint () {Inner i = new Inner();
    i.innerPrint();}
}

部分外部类

部分外部类是外部类的第二种模式,它让外部类的“暗藏”得更深一层——写在外部类的办法外部,而不是处于和外部类办法平行的地位。

让咱们对下面成员外部类解决的场景做些思考:咱们的 Inner 外部类仅仅只在 outterPrint 办法中应用了一次:

public void outterPrint () {Inner i = new Inner();
  i.innerPrint();}

那么咱们能不能把 Inner 外部类间接定义在 outterPrint 的外部呢?这样的话,它就能更好地暗藏起来,即便是类 Outter 中除 outterPrint 外的办法,也不能拜访到它:

当初的 Outter 的类看起来像这样:

public class Outter {public void outterPrint () {// 外部类办法
    class LocalInner { // 部分外部类
      public void innerPrint () {}
    } 
    LocalInner i = new LocalInner(); // 实例化部分外部类
    i.innerPrint();}
}

相比于成员外部类,部分外部类多了一项能拜访的数据,那就是局部变量(由外部类办法提供)

成员外部类:外部类数据,外部类数据

部分外部类:外部类数据,外部类数据,部分数据

具体示例如下:

Outter.java

public class Outter {
  private String data = "内部数据";  // 外部类数据
  public void outterPrint (final String localData) { // 部分数据
    class LocalInner {
      private String data = "外部数据";  // 外部类数据
      public void innerPrint () {System.out.println(Outter.this.data);  // 打印外部类数据
        System.out.println(this.data);   //  打印外部类数据
        System.out.println(localData);  // 打印部分数据
      }
    } 
    LocalInner i = new LocalInner();
    i.innerPrint();}
}

Test.java:

public class Test {public static void main (String [] args) {Outter o = new Outter();
    o.outterPrint("部分数据");
  }
}

后果输入:

内部数据
外部数据
部分数据

部分类所应用的外部类办法的形参必须用 final 润饰

这里要留神一点,部分类所应用的外部类办法的形参必须用 final 润饰,否则会编译不通过,也就是说传入后不许扭转

为什么这个办法形参肯定要用 final 润饰?

(仅集体了解,如有不同的意见或者更好的了解欢送在评论区探讨)

如果不必 final 润饰会怎么?且听我缓缓道来:

首先要说一下:

1. 外部类和外部类在编译之后模式上是一样的,不会有内外之分
2. 部分外部类对于应用的内部办法的值会用构造函数做一个拷贝(编译后)

例如对于上面 outterPrint 办法中的 LocalInner


public void outterPrint (final String data) {
  class LocalInner {public void innerPrint () {// 应用 data}
  }
}/./* 欢送退出 java 交换 Q 君样:909038429 一起吹水聊天

编译之后大略长这样:

public class Outter$LocalInner{public LocalInner(String data){this.LocalInner$data = data; // 对于应用的 data 做了一次拷贝}
  public void innerPrint (){ /* 应用 data */}
}

这里要留神的是:

  1. 编译后,LocalInner 并非间接应用 data,而是用结构器拷贝一份后再应用
  2. java 是值传递的,所以包裹 LocalInner 的内部办法 outterPrint 也会对传入的 data 参数做一次拷贝(根本类型数据拷贝正本,对象等则拷贝援用)

OK,当初的状况是:

办法内的部分类对 data 拷贝了两次:内部办法 outterPrint 值传递时的拷贝,和 LocalInner 构造函数的拷贝
办法内除了部分类外的作用域只拷贝了 data 一次:内部办法 outterPrint 值传递时的拷贝

拷贝两次和拷贝一次,导致在 outterPrint 办法外部,部分类外部的 data 和部分类内部的 data 是不同步的!也即你在部分类外部改了 data 不影响部分类内部的 data,在部分类内部改了 data 也不影响部分类外部的 data(留神一个前提,值是根本类型的,如果是对象的话因为拷贝的是援用依然能够“同步”)

图示一:

图示二:

于是 java 说:哎呀妈呀,这都 data 都不同步了,要是让你批改这还了得!!! 于是就强行要求咱们加上 final

【留神】所谓的不同步次要是针对根本类型来说的,如果是对象之类的话因为拷贝的是援用所以依然能够“同步”

如何冲破必须用 final 的限度

咱们下面说到,部分外部类所应用的办法形参必须用 final 润饰的限度。

例如

public void outterPrint (String data) {// 没加上 final
  class LocalInner {public void changeData () {data = "我想批改 data 的值";  // 在这一行编译报错}
   } 
}

提醒:

Cannot refer to a non-final variable data inside an inner class defined in a different method

那么,如果咱们有对该形参必须能批改的硬性需要怎么办?

你能够通过一种乏味的形式绕开它:应用一个单元素数组。因为用 final 润饰的根本类型的变量不容许批改值,然而却容许批改 final 润饰的单元素数组里的数组元素,因为寄存数组的变量的值只是一个援用,咱们批改数组元素的时候是不会批改援用指向的地址的,在这点上 final 并不会障碍咱们:

Outter.java

public class Outter {public void outterPrint (final String []  data) { 
    class LocalInner {public void innerPrint () {data[0] = "堂而皇之地批改它!!";   // 批改数据
        System.out.print(data[0]);  // 输入批改后的数据
      }
    } 
    LocalInner i = new LocalInner();
    i.innerPrint();}
}

Test.java:

public class Test {public static void main (String [] args) {Outter o = new Outter();
    String [] data = new String [1];
    data[0] = "我是数据";
    o.outterPrint(data);  // 批改数据并且输入
  }
}

后果输入:

堂而皇之地批改它!!

【留神】部分类不能用 public 或 private 拜访符进行申明!!

匿名外部类

假使咱们再把部分外部类再深入一下,那就是匿名外部类

匿名外部类的应用形式

new [超类 / 接口] {/* 类体 */}

让咱们看看上面这个例子:
Other.java:

public class Other {}

Outter.java:

public class Outter {public void outterPrint (String data) {Other o = new Other() {}; // 匿名外部类}
}

何谓之匿名?

“诶,不是说好的匿名吗?那么为什么还有个 Other 的类名呢?”

Other o = new Other() {  /* 匿名外部类的类体 */};

实际上,这里的 Other 并不是咱们的匿名外部类,而是咱们匿名外部类的超类,下面一行代码其实相当于(用成员外部类来示意的话)

// annoymous 翻译为匿名
public class Outter {private class annoymous extends Other{}  
  public void outterPrint () {Other a = new annoymous();
  }
}

同时要留神,咱们在应用匿名外部类的形式,是在定义一个外部类的同时实例化该外部类:

new Other() {  /* 匿名外部类的类体 */};  // new 操作和定义类的代码是紧紧联合在一起的

匿名函数的作用

用匿名函数的作用在于在一些特定的场景下写起来很简略,例如事件监听器:

ActionListener listener = new ActionListener() {public void actionPerformed(ActionEvent e) {}};

防止了再创立另外一个类文件

讲的有点乱,对匿名外部类做个总结:

  1. 省略被定义的类的类名
  2. 必须联合超类或者接口应用,即 new [超类 / 接口] {/ 类体 / }
  3. 在定义该匿名类的同时实例化该匿名类
  4. 在一些场景下能简化代码

【留神】匿名类不能有结构器,因为结构器和类同名,而匿名类没有类名,所以匿名类不能有结构器

文章总结
咱们应用外部类的起因次要有三点:
1. 实现数据暗藏,防止多余的可见性
2. 自在拜访外部类的变量

  1. 在应用监听器等场景的时候应用匿名外部类,防止减少的大量代码

对于成员外部类,办法部分类,匿名外部类的关系

从成员外部类,办法部分类到匿名外部类是一个不断深入的关系,成员外部类进一步暗藏可见性就成为了办法部分类,办法部分类省去类名,并将类的定义和实例化操作合并到一起,就是匿名外部类。因而,匿名外部类因循了成员外部类和办法部分类的根本特个性

外部类的一些非凡的要求
1. 部分类不能用 public 或 private 拜访符进行申明
2. 部分类所应用的外部类办法的形参必须用 final 润饰

  1. 匿名外部类不能有结构器

最新 2020 整顿收集的一些高频面试题(都整顿成文档),有很多干货,蕴含 mysql,netty,spring,线程,spring cloud、jvm、源码、算法等具体解说,也有具体的学习规划图,面试题整顿等,须要获取这些内容的敌人请加 Q 君样:909038429
/./* 欢送退出 java 交换 Q 君样:909038429 一起吹水聊天

正文完
 0