JAVA内部类详解

在我的另一篇java三大特性的封装中讲到java内部类的简单概要,这里将详细深入了解java内部类的使用和应用。

我们知道内部类可分为以下几种:

  • 成员内部类
  • 静态内部类
  • 方法内部类
  • 匿名内部类

这里我们先将以这个分类来详细了解各个内部类的情况。然后给内部类作出总结。

一、成员内部类

  内部类中最常见的就是成员内部类,也称为普通内部类。我们来看如下代码:

  

  运行结果为:

  

  从上面的代码中我们可以看到,成员内部类的使用方法

  1、 Inner 类定义在 Outer 类的内部相当于 Outer 类的一个成员变量的位置,Inner 类可以使用任意访问控制符,如 public 、 protected 、 private 等

  2、 Inner 类中定义的 test() 方法可以直接访问 Outer 类中的数据,而不受访问控制符的影响,如直接访问 Outer 类中的私有属性a

  3、 定义了成员内部类后,必须使用外部类对象来创建内部类对象,而不能直接去 new 一个内部类对象,即:内部类 对象名 = 外部类对象.new 内部类( );

  4、 编译上面的程序后,会发现产生了两个 .class 文件

  

  其中,第二个是外部类的 .class 文件,第一个是内部类的 .class 文件,即成员内部类的 .class 文件总是这样:外部类名$内部类名.class

  另外,友情提示哦:

  1、 外部类是不能直接使用内部类的成员和方法滴。如:

  

  那么外部类如何使用内部类的成员和方法呢??

  答:可先创建内部类的对象,然后通过内部类的对象来访问其成员变量和方法。

  Java 编译器在创建内部类对象时,隐式的把其外部类对象的引用也传了进去并一直保存着。这样就使得内部类对象始终可以访问其外部类对象,同时这也是为什么在外部 类作用范围之外向要创建内部类对象必须先创建其外部类对象的原因。

  2、 如果外部类和内部类具有相同的成员变量或方法,内部类默认访问自己的成员变量或方法,如果要访问外部类的成员变量,可以使用 this 关键字。如:

  

  运行结果:

二、静态内部类

  静态内部类是 static 修饰的内部类,这种内部类的特点是:

  1、 静态内部类不能直接访问外部类的非静态成员,但可以通过 new 外部类().成员 的方式访问。

  2、 如果外部类的静态成员与内部类的成员名称相同,可通过“类名.静态成员”访问外部类的静态成员;如果外部类的静态成员与内部类的成员名称不相同,则可通过“成员名”直接调用外部类的静态成员。

  3、 创建静态内部类的对象时,不需要外部类的对象,可以直接创建 内部类 对象名= new 内部类();

  

  运行结果 : 

三、方法内部类

  方法内部类就是内部类定义在外部类的方法中,方法内部类只在该方法的内部可见,即只在该方法内可以使用

  

  一定要注意哦:由于方法内部类不能在外部类的方法以外的地方使用,因此方法内部类不能使用访问控制符和 static 修饰符。

四、匿名内部类

  匿名类是不能有名称的类,所以没办法引用他们。必须在创建时,作为new语句的一部分来声明他们。

  但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口。
  这就要采用另一种形式 的new语句,如下所示:

   new <类或接口> <类的主体>

这种形式的new语句声明一个 新的匿名类,他对一个给定的类进行扩展,或实现一个给定的接口。他还创建那个类的一个新实例,并把他作为语句的结果而返回。要扩展的类和要实现的接口是
new语句的操作数,后跟匿名类的主体。

注意匿名类的声明是在编译时进行的,实例化在运行时进行。这意味着
for循环中的一个new语句会创建相同匿名类的几个实例,而不是创建几个不同匿名类的一个实例。

从技术上说,匿名类可被视为非静态的内 部类,所以他们具备和方法内部声明的非静态内部类相同的权限和限制。

假如要执行的任务需要一个对象,但却不值得创建全新的对象(原因可能 是所需的类过于简单,或是由于他只在一个方法内部使用),匿名类就显得很有用。匿名类尤其适合在Swing应用程式中快速创建事件处理程式。以下是一个匿名内部类的实例:

  1、匿名内部类的基本实现:

    

  运行结果: 

  可以看到,我们直接将抽象类Person中的方法在大括号中实现了,这样便可以省略一个类的书写,并且,匿名内部类还能用于接口上。

 2、在接口上使用匿名内部类:

  

  运行结果:

  由上面的例子可以看出,只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现。

  在使用匿名内部类的过程中,我们需要注意如下几点:

      1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。

      2、匿名内部类中是不能定义构造函数的。

      3、匿名内部类中不能存在任何的静态成员变量和静态方法。

      4、匿名内部类为局部内部类(即方法内部类),所以局部内部类的所有限制同样对匿名内部类生效。

      5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

  匿名内部类重点难点

1. 如果是在一个方法的匿名内部类,可以利用这个方法传进你想要的参数,不过记住,这些参数必须被声明为 final 。

 使用的形参为何要为final??

 参考博文:http://android.blog.51cto.com/268543/384844 http://blog.csdn.net/chenssy/article/details/13170015

 我们给匿名内部类传递参数的时候,若该形参在内部类中需要被使用,那么该形参必须要为final。也就是说:当所在的方法的形参需要被内部类里面使用时,该形参必须为final。

首先我们知道在内部类编译成功后,它会产生一个class文件,该class文件与外部类并不是同一class文件,仅仅只保留对外部类的引用。当外部类传入的参数需要被内部类调用时,从java程序的角度来看是直接被调用:

    public class OuterClass {
public void display(final String name,String age){
class InnerClass{
void display(){
System.out.println(name);
}
}
}
}

  从上面代码中看好像name参数应该是被内部类直接调用?其实不然,在java编译之后实际的操作如下:

    public class OuterClass$InnerClass {
public InnerClass(String name,String age){
this.InnerClass$name = name;
this.InnerClass$age = age;
} public void display(){
System.out.println(this.InnerClass$name + "----" + this.InnerClass$age );
}
}

 所以从上面代码来看,内部类并不是直接调用方法传递的参数,而是利用自身的构造器对传入的参数进行备份,自己内部方法调用的实际上时自己的属性而不是外部方法传递进来的参数。

 直到这里还没有解释为什么是final。在内部类中的属性和外部方法的参数两者从外表上看是同一个东西,但实际上却不是,所以他们两者是可以任意变化的,也就是说在内部类中我对属性的改变并不会影响到外部的形参,而然这从程序员的角度来看这是不可行的,毕竟站在程序的角度来看这两个根本就是同一个,如果内部类该变了,而外部方法的形参却没有改变这是难以理解和不可接受的,所以为了保持参数的一致性,就规定使用final来避免形参的不改变。

简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变。

   故如果定义了一个匿名内部类,并且希望它使用一个其外部定义的参数,那么编译器会要求该参数引用是final的。

2. 匿名内部类中使用初始化代码块

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

    public class OutClass {
public InnerClass getInnerClass(final int age,final String name){
return new InnerClass() {
int age_ ;
String name_;
//构造代码块完成初始化工作
{
if(0 < age && age < 200){
age_ = age;
name_ = name;
}
}
public String getName() {
return name_;
} public int getAge() {
return age_;
}
};
} public static void main(String[] args) {
OutClass out = new OutClass(); InnerClass inner_1 = out.getInnerClass(201, "chenssy");
System.out.println(inner_1.getName()); InnerClass inner_2 = out.getInnerClass(23, "chenssy");
System.out.println(inner_2.getName());
}
}

五、内部类总结

  学习了上面四种类型的内部类,我们知道了如何使用各个内部类,那么为什么要使用内部类呢??

  参考博文:http://blog.csdn.net/yu422560654/article/details/7466260

  首先举一个简单的例子,如果你想实现一个接口,但是这个接口中的一个方法和你构想的这个类中的一个 方法的名称,参数相同,你应该怎么办?这时候,你可以建一个内部类实现这个接口。由于内部类对外部类的所有内容都是可访问的,所以这样做可以完成所有你直 接实现这个接口的功能。
 
不过你可能要质疑,更改一下方法的不就行了吗?的确,以此作为设计内部类的理由,实在没有说服 力。
  真正的原因是这样的,java中的内部类和接口加在一起,可以的解决常被C++程序员抱怨java中存在的一个问题——没有多继承。实际上,C++的多继承设计起来很复杂,而java通过内部类加上接口,可以很好的实现多继承的效果。
 
内部类:一个内部类的定义是定义在另一个内部的类。
  原因是:
  1.一个内部类的对象能够访问创建它的对象的实现,包括私有数据。
  2.对于同一个包中的其他类来说,内部类能够隐藏起来。
  3.匿名内部类可以很方便的定义回调。
  4.使用内部类可以非常方便的编写事件驱动程序。

内部类可以让你更优雅地设计你的程序结构。下面从以下几个方面来介绍:

  首先看这个例子:

     public interface Contents {
 int value();
} public interface Destination {
 String readLabel();
}
     public class Goods {
  private valueRate=2;  private class Content implements Contents {
  private int i = 11 * valueRate;
  public int value() {
   return i;
  }
 }  protected class GDestination implements Destination {
  private String label;
  private GDestination(String whereTo) {
   label = whereTo;
  }
  public String readLabel() {
   return label;
  }
 }  public Destination dest(String s) {
  return new GDestination(s);
 }   public Contents cont() {
  return new Content();
 }
}

    在这个例子里类 Content 和 GDestination 被定义在了类 Goods 内部,并且分别有着 protected 和 private 修饰符来控制访问级别。Content 代表着 Goods 的内容,而 GDestination 代表着 Goods 的目的地。它们分别实现了两个接口Content和Destination。在后面的main方法里,直接用 Contents c 和 Destination d进行操作,你甚至连这两个内部类的名字都没有看见!这样,内部类的第一个好处就体现出来了——隐藏你不想让别人知道的操作,也即封装性。

  非静态内部类对象有着指向其外部类对象的引用

  修改上面的例子:

     public class Goods {
  private valueRate=2;  private class Content implements Contents {
  private int i = 11 * valueRate;
  public int value() {
   return i;
  }
 }  protected class GDestination implements Destination {
  private String label;
  private GDestination(String whereTo) {
   label = whereTo;
  }
  public String readLabel() {
   return label;
  }
 }  public Destination dest(String s) {
  return new GDestination(s);
 }   public Contents cont() {
  return new Content();
 }
}

  在这里我们给 Goods 类增加了一个 private 成员变量 valueRate,意义是货物的价值系数,在内部类 Content 的方法 value() 计算价值时把它乘上。我们发现,value() 可以访问 valueRate,这也是内部类的第二个好处——一个内部类对象可以访问创建它的外部类对象的内容,甚至包括私有变量!这是一个非常有用的特性,为我们在设计时提供了更多的思路和捷径。要想实现这个功能,内部类对象就必须有指向外部类对象的引用。Java 编译器在创建内部类对象时,隐式的把其外部类对象的引用也传了进去并一直保存着。这样就使得内部类对象始终可以访问其外部类对象,同时这也是为什么在外部 类作用范围之外向要创建内部类对象必须先创建其外部类对象的原因。


  个人想说的话:对于java的内部类我花了比较长的时间在网上搜集资料,这部分的学习也有很多理解不够的地方,如果有疑问可以在下面留言,我尽我所能解答;如果有改进的批评的地方欢迎指正,谢谢!

JAVA基础——内部类详解的更多相关文章

  1. java基础 内部类详解

    什么是内部类? 1.内部类也是一个类: 2.内部类位于其他类声明内部. 内部类的常见类型 1.成员内部类 2.局部内部类 3.匿名内部类 4.静态内部类 简单示例 /** * 外部类 * */ pub ...

  2. Java修炼——内部类详解

    内部类详解 定义:将一个类定义在另一个类的内部,该类就称为内部类 类中定义的内部类特点: 内部类作为外部类的成员,可以直接访问外部类的成员 (包括 private 成员),反之则不行. 内部类做为外部 ...

  3. java基础(十三)-----详解内部类——Java高级开发必须懂的

    可以将一个类的定义放在另一个类的定义内部,这就是内部类. 为什么要使用内部类 为什么要使用内部类?在<Think in java>中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能 ...

  4. Java基础数据类型详解

    在Java中的数据类型一共有8种,大致分为整型(4个)浮点型(2个)布尔(1)字符(1个) 分类 类型 默认值 占用字节 范围 整型 byte 0 1 = 8 bit -2^7 - 2^7 short ...

  5. JAVA基础——异常详解

    JAVA异常与异常处理详解 一.异常简介 什么是异常? 异常就是有异于常态,和正常情况不一样,有错误出错.在java中,阻止当前方法或作用域的情况,称之为异常. java中异常的体系是怎么样的呢? 1 ...

  6. java基础:数组详解以及应用,评委打分案例实现,数组和随机数综合,附练习案列

    1.数组 1.1 数组介绍 数组就是存储数据长度固定的容器,存储多个数据的数据类型要一致. 1.2 数组的定义格式 1.2.1 第一种格式 数据类型[] 数组名 示例: int[] arr;     ...

  7. Java基础 - 异常详解

    异常的层次结构 Throwable Throwable 是 Java 语言中所有错误与异常的超类. Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示 ...

  8. 【干货】用大白话聊聊JavaSE — ArrayList 深入剖析和Java基础知识详解(二)

    在上一节中,我们简单阐述了Java的一些基础知识,比如多态,接口的实现等. 然后,演示了ArrayList的几个基本方法. ArrayList是一个集合框架,它的底层其实就是一个数组,这一点,官方文档 ...

  9. Java基础——枚举详解

    前言: 在第一次学习面向对象编程时,我记得最深的一句话就是“万物皆对象”.于是我一直秉承着这个思想努力的学习着JAVA,直到学习到枚举(Enum)时,看着它颇为奇怪的语法……我一直在想,这TM是个什么 ...

随机推荐

  1. JavaScript、Python、java、Go算法系列之【快速排序】篇

    常见的内部排序算法有:插入排序.希尔排序.选择排序.冒泡排序.归并排序.快速排序.堆排序.基数排序等. 用一张图概括: 选择排序 选择排序是一种简单直观的排序算法,无论什么数据进去都是 O(n²) 的 ...

  2. Scala基础 - 下划线使用指南

    下划线这个符号几乎贯穿了任何一本Scala编程书籍,并且在不同的场景下具有不同的含义,绕晕了不少初学者.正因如此,下划线这个特殊符号无形中增加Scala的入门难度.本文希望帮助初学者踏平这个小山坡. ...

  3. java swing 添加 jcheckbox复选框

    总体上而言,Java Swing编程有两大特点:麻烦.效果差. 麻烦是说由于设计器的使用不方便(如果您希望使用窗体设计器通过快速拖拽控件建立您的Java Swing GUI程序,请您使用MyEclip ...

  4. 浏览器如何生成URL

    点击页面中的链接,浏览器会根据源码中相对URL路径作不同的处理: (1)有协议名称,但没有域名信息 对于这种形式的URL,它的协议,路径,查询字符串和片段ID都以它自身为准,但域名信息的部分,以引用它 ...

  5. 第 8 章 MySQL 数据库 Query 的优化

      前言: 在之前“影响 MySQL 应用系统性能的相关因素”一章中我们就已经分析过了Query语句对数据库性能的影响非常大,所以本章将专门针对 MySQL 的 Query 语句的优化进行相应的分析. ...

  6. 第 15 章 可扩展性设计之 Cache 与 Search 的利用

    前言: 前面章节部分所分析的可扩展架构方案,基本上都是围绕在数据库自身来进行的,这样是否会使我们在寻求扩展性之路的思维受到“禁锢”,无法更为宽广的发散开来.这一章,我们就将跳出完全依靠数据库自身来改善 ...

  7. 开涛spring3(9.4) - Spring的事务 之 9.4 声明式事务

    9.4  声明式事务 9.4.1  声明式事务概述 从上节编程式实现事务管理可以深刻体会到编程式事务的痛苦,即使通过代理配置方式也是不小的工作量. 本节将介绍声明式事务支持,使用该方式后最大的获益是简 ...

  8. salesforce零基础学习(七十二)项目中的零碎知识点小总结(一)

    项目终于告一段落,虽然比较苦逼,不过也学到了好多知识,总结一下,以后当作参考. 一.visualforce标签中使用html相关的属性使用 曾经看文档没有看得仔细,导致开发的时候走了一些弯路.还好得到 ...

  9. ECMAScript 6.0 简介

    ECMAScript 6.0 在es6中有 许多语法.还有lambda的使用.以及 class 的使用 还有一些新的对象来解决一些事情 可以提高开发效率 但更重要的是 颠覆 javascript 在你 ...

  10. javaSE_Java第一周总结:有难度题目集合

    第一周练习总结 说明:尽量采用多种做法解决 1.使用三种方法实现变量交换 public class Test1Change{ public static void main(String[] args ...