一、四种内部类

1.1、成员内部类

成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:

 public class OuterAndInnerClass {
public static void main(String[] args) {
Outer outer = new Outer();
//创建内部类的两种方式 (1)在外部
//Outer.Inner inner = outer.new Inner();
//创建内部类的两种方式 (1)在内部类所依附的外部类中创建
Outer.Inner inner = outer.getInnerClass();
outer.out();
inner.in();
inner.testInner();
}
}
class Outer{//外部类
public Inner getInnerClass(){
return new Inner();
}
String outName = "外部类";
String sameName = "同名外部";
public void out(){
System.out.println("外部方法");
}
class Inner{//内部类
String inName = "内部类";
String sameName = "同名内部";
String name = "内部类变量";
public void in(){
System.out.println("内部方法");
}
public void testInner(){
String name = "局部变量";
System.out.println(name);//#内部类变量
System.out.println(this.name);//#局部变量
System.out.println("outName:" + outName);//#outName:外部类
System.out.println("inName:" + inName);//#inName:内部类
System.out.println("sameName:" + sameName);//#sameName:同名内部
System.out.println("sameName:" + this.sameName);//#sameName:同名内部,this指向Inner
System.out.println("sameName:" + Outer.this.sameName);//#sameName:同名外部
}
}
}

1.1.1,创建成员内部类的方法有两种

  虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:

Outer outer = new Outer();
第一种方式:Outer.Inner inner = outer.new Inner();
第二种方式:Outer.Inner inner = outer.getInnerClass();

1.1.2,成员内部类的访问控制修饰符

内部类就如同外部类的成员变量一样。四种访问控制符都是可以的,public,default,protected,private。
内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。比如上面的例子,如果成员内部类Inner用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。

1.1.3,成员内部类调用外部类的成员变量或者方法

调用内部类的成员变量:

System.out.println("inName:" + inName);//#inName:内部类

调用外部类的成员变量:(同调用内部类的成员变量)

System.out.println("outName:" + outName);//#outName:外部类
TIPS:
特殊情况:当外部类和内部类的成员变量同名的情况
当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法

1.2、局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

 //局部内部类
public class LocalInnerClass {
public static void main(String[] args) {
People people = new People();
people.getWoman("形参变量");
}
}
class People {
String peopleName = "people";
String sameName = "外部同名变量";
public People getWoman(final String methodName){
final String localName = "局部变量";
class Woman extends People{
String womanName = "woman";
String sameName = "局部内部类同名变量";
public Woman(){
//methodName = "";//编译错误:Cannot assign a value to final variable 'methodName'
//localName = "";//编译错误:Variable 'localName' is accessed from within inner class, needs to be final or effectively final
System.out.println(methodName);//#形参变量
System.out.println(localName);//#局部变量
System.out.println(peopleName);//#people
System.out.println(womanName);//#woman
System.out.println(sameName);//#局部内部类同名变量
System.out.println(this.sameName);//#局部内部类同名变量
System.out.println(People.this.sameName);//#外部同名变量
}
}
return new Woman();
}
}

在局部内部类中调用外部类的变量或者方法的方式和规则是一样的。

TIPS
值得注意的是,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。

1.3、匿名内部类

  匿名内部类由于没有名字,所以它的创建方式有点儿奇怪。创建格式如下:

new 父类构造器(参数列表)|实现接口()
{
//匿名内部类的类体部分
}

  在这里我们看到使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。

 //匿名内部类
public class AnonInnerClass {
public static void useRunnable(MyRunnable runnable){
runnable.run();
}
public static void main(String[] args) {
AnonInnerClass.useRunnable(new MyRunnable() {
@Override
public void run() {
System.out.println("重写run方法");
}
});
AnonInnerClass.useRunnable(new MyRunnable("name") {
@Override
public void run() {
System.out.println("重写run方法");
}
});
}
}
abstract class MyRunnable {
public MyRunnable(){
System.out.println("调用匿名内部类的无参构造器");
}
public MyRunnable(String name){
System.out.println("调用匿名内部类的有参构造器,参数为:" + name);
}
//抽象方法
public abstract void run();
}

  这里我们能够看到,useRunnable 方法要接受一个MyRunnable的实例参数,但是,MyRunnable是一个抽象的类,不能被实例化,所以只能创建一个新的类继承这个MyRunnable类,然后指向MyRunnable,从而拿到MyRunnable的实例参数。
  在这里JVM会创建一个继承自MyRunnable类的匿名类的对象,该对象转型为对MyRunnable类型的引用。
  对于匿名内部类的使用它是存在一个缺陷的,就是它仅能被使用一次,创建匿名内部类时它会立即创建一个该类的实例,该类的定义会立即消失,所以匿名内部类是不能够被重复使用。对于上面的实例,如果我们需要对test()方法里面内部类进行多次使用,建议重新定义类,而不是使用匿名内部类。

TIPS:
1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
2、匿名内部类中是不能定义构造函数的。(类都是匿名的,没法定义构造方法)
3、匿名内部类中不能存在任何的静态成员变量和静态方法。(类是匿名的,当然没有类方法或类变量)
4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
6、我们给匿名内部类传递参数的时候,若该形参在内部类中需要被使用,那么该形参必须要为final。也就是说:当所在的方法的形参需要被内部类里面使用时,该形参必须为final。
7、匿名内部类的初始化(使用构造代码块)

我们一般都是利用构造器来完成某个实例的初始化工作的,但是匿名内部类是没有构造器的!那怎么来初始化匿名内部类呢?使用构造代码块!利用构造代码块能够达到为匿名内部类创建一个构造器的效果。

 public class InitAnonInnerClass {
public static void main(String[] args) {
OuterClass outer = new OuterClass();
InnerClass inner1 = outer.getInnerClass(15, "变了");
System.out.println(inner1.getStr());
InnerClass inner2 = outer.getInnerClass(20, "变了");
System.out.println(inner2.getStr());
}
} class OuterClass {
public InnerClass getInnerClass(final int num, final String str){
return new InnerClass() {
int num_ ;
String str_ ;
//使用构造代码块完成初始化
{
if(0 < num && num < 18){
//str = "";//编译错误Variable 'str' is accessed from within inner class, needs to be final or effectively final
str_ = str;
}else {
str_ = "没变啊";
}
}
public String getStr(){
return str_;
}
};
}
} abstract class InnerClass {
public abstract String getStr();
}
out:

  变了
  没变啊

1.4、静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

 //静态内部类
public class StaticInnerClass {
public static void main(String[] args) {
//初始化静态内部类,注意和其它内部类的初始化方式的区别
OuterClass1.InnerClass inner = new OuterClass1.InnerClass();
inner.test();
}
}
class OuterClass1 {
String outName = "我是外部类";
static String outType = "外部类";
static class InnerClass {
String innerName = "我是内部类";
static String innerType = "静态内部类";
public InnerClass (){
//System.out.println(outName);//编译错误:Non-static field 'outName' cannot be referenced from a static context
System.out.println(outType);
}
public void test(){
System.out.println("调用内部类方法");
}
}
}

如上所示创建静态内部类对象的一般形式为:

外部类类名.内部类类名 xxx = new 外部类类名.内部类类名()

二、深入理解内部类

2.1.为什么成员内部类可以无条件访问外部类的成员?

  在此之前,我们已经讨论过了成员内部类可以无条件访问外部类的成员,那具体究竟是如何实现的呢?下面通过反编译字节码文件看看究竟。事实上,编译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件,下面是OuterAndInnerClass.java的代码:

 public class OuterAndInnerClass {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.getInnerClass();
outer.out();
}
} class Outer{
public Inner getInnerClass(){
return new Inner();
}
public void out(){
System.out.println("外部方法");
}
class Inner{
public void in(){
System.out.println("内部方法");
}
}
}

编译之后,出现了两个字节码文件:

  编译器会默认为成员内部类添加了一个指向外部类对象的引用,那么这个引用是如何赋初值的呢?
虽然我们在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的Outter this&0 指针便指向了外部类对象,因此可以在成员内部类中随意访问外部类的成员。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对Outter this&0引用进行初始化赋值,也就无法创建成员内部类的对象了。

2.2、为什么局部内部类和匿名内部类只能访问局部final变量?

 public class Test {
public static void main(String[] args) {
Test test = new Test();
test.test(1);
} public void test(final int b) {
final int a = 10;
new Thread(){
public void run() {
// a = 2;//编译错误:Cannot assign a value to final variable 'b'
// b = 3;//编译错误:Cannot assign a value to final variable 'b'
System.out.println(a);
System.out.println(b);
};
}.start();
}
}

  当test方法执行完毕之后,变量a的生命周期就结束了,而此时Thread对象的生命周期很可能还没有结束,那么在Thread的run方法中继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了 复制 的手段来解决这个问题。这个过程是在编译期间由编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开。

  也就说如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。
  从上面可以看出,在run方法中访问的变量a根本就不是test方法中的局部变量a。这样一来就解决了前面所说的 生命周期不一致的问题。但是新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?
  对,会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。
  到这里,想必大家应该清楚为何 方法中的局部变量和形参都必须用final进行限定了。

  凌晨两点了,向科比致敬。

参考文章:

  https://www.cnblogs.com/dolphin0520/p/3811445.html(海子大神)

  https://www.cnblogs.com/chenssy/p/3390871.html

  如有错误的地方还请留言指正。

  原创不易,转载请注明原文地址:https://www.cnblogs.com/hello-shf/p/11192571.html

  

Java内部类你真的会吗?的更多相关文章

  1. Java 内部类摘抄

    关于Java的内部类,要说的东西实在太多,这篇博文中也无法一一具体说到,所以就挑些重点的讲.关于内部类的使用,你可能会疑问,为什么我们要使用内部类?为了回答这个问题,你需要知道一些关于内部类的重点.所 ...

  2. Java内部类超详细总结(含代码示例)

    什么是内部类 什么是内部类? 顾名思义,就是将一个类的定义放在另一个类的内部. 概念很清楚,感觉很简单,其实关键在于这个内部类放置的位置,可以是一个类的作用域范围.一个方法的或是一个代码块的作用域范围 ...

  3. 【原创】这道Java基础题真的有坑!我也没想到还有续集。

    前情回顾 自从我上次发了<这道Java基础题真的有坑!我求求你,认真思考后再回答.>这篇文章后.我通过这样的一个行文结构: 解析了小马哥出的这道题,让大家明白了这题的坑在哪里,这题背后隐藏 ...

  4. Java内部类final语义实现

    本文描述在java内部类中,经常会引用外部类的变量信息.但是这些变量信息是如何传递给内部类的,在表面上并没有相应的线索.本文从字节码层描述在内部类中是如何实现这些语义的. 本地临时变量 基本类型 fi ...

  5. Java内部类详解

    Java内部类详解 说起内部类这个词,想必很多人都不陌生,但是又会觉得不熟悉.原因是平时编写代码时可能用到的场景不多,用得最多的是在有事件监听的情况下,并且即使用到也很少去总结内部类的用法.今天我们就 ...

  6. 黑马----JAVA内部类

    黑马程序员:Java培训.Android培训.iOS培训..Net培训 黑马程序员--JAVA内部类 一.内部类分为显式内部类和匿名内部类. 二.显式内部类 1.即显式声明的内部类,它有类名. 2.显 ...

  7. java 内部类 *** 最爱那水货

    注: 转载于http://blog.csdn.net/jiangxinyu/article/details/8177326 Java语言允许在类中再定义类,这种在其它类内部定义的类就叫内部类.内部类又 ...

  8. java内部类和匿名内部类

    内部类即是包含在类里面的又一个类. java内部类分为: 成员内部类.静态嵌套类.方法内部类.匿名内部类 . 内部类的共性 (1).内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.clas ...

  9. Java内部类小程序(成员内部类,静态内部类,匿名内部类)

    /** * 测试java内部类(成员内部类,静态内部类,匿名内部类) * 局部内部类不常用,就不写了. * @package :java05 * @author shaobn * @Describe ...

随机推荐

  1. android Camera2 API使用详解

    原文:android Camera2 API使用详解 由于最近需要使用相机拍照等功能,鉴于老旧的相机API问题多多,而且新的设备都是基于安卓5.0以上的,于是本人决定研究一下安卓5.0新引入的Came ...

  2. 关于"云服务器被检测到对外攻击已阻断该服务器对其它服务器端口的访问"的解决措施

    前段时间阿里云大量发送云服务器对外攻击的信息到邮箱中,邮件信息大概如下: 您的云服务器(XX.XX.XX.XX)由于被检测到对外攻击,已阻断该服务器对其它服务器端口(TCP:XX)的访问,阻断预计将在 ...

  3. swift 如何控制view的显示与隐藏

    swift 如何控制view的显示与隐藏 UIView有一个属性 hidden let line: UILabel = UILabel() 默认是显示的 需要显示它的时候:line.hidden = ...

  4. 使用IntelliJ IDEA开发SpringMVC网站(五)博客文章管理

    原文:使用IntelliJ IDEA开发SpringMVC网站(五)博客文章管理 摘要 通过对博客文章的管理,实现外键操作. 目录[-] 八.博客文章管理 1.查看文章 2.添加博客        3 ...

  5. js中的scrollTop、offsetTop、clientTop

    scrollHeight:获取对象可滚动的高度. scrollWidth:获取对象可滚动的宽度. scrollTop:获取对象最顶端与对象可见区域最顶端的距离. scrollLeft:获取对象左边界与 ...

  6. 关于 Apache 2.4 配置PHP时的错误记录

    1. 访问虚拟配置的站点抛出 Forbidden 403 错误 解决办法: <Directory E:/Xingzhi/Php/xingzhi.xingzhi.com/>     Opti ...

  7. Android多线程(一)

    在Android应用的开发过程中,我们不可避免的要使用多线程,获取服务器数据.下载网络数据.遍历文件目录查找特定文件等等耗时的工作都离不开线程的知识.Android继承了Java的多线程体系,同时又实 ...

  8. Qt系统对话框中文化及应用程序实现重启及使用QSS样式表文件及使用程序启动界面

    一.应用程序中文化 1).Qt安装目录下有一个目录translations/,在此目录下有qt_zh_CN.ts和 qt_zh_CN.qm把它们拷贝到你的工程目录下. 2).在main函数加入下列代码 ...

  9. Twitter的分布式自增ID算法snowflake(雪花算法) - C#版

    概述 分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的.有些时候我们希望能使用一种简 ...

  10. Delphi 10.2 Tokyo的新特性

    Delphi 10.2(Tokyo)出来一段时间了,最重要的新特性就是支持Linux的服务端. 官网有详细的介绍: 这里是主要的特性介绍:https://www.embarcadero.com/pro ...