1、内部类的概念

内部类顾名思义:将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。对于很多Java初学者来说,内部类学起来真的是一头雾水,根本理解不清楚是个什么东西,包括我自己(我太菜了!哈哈),所以接下来我要好好地来研究一下。

我们来看下内部类的定义格式;

public class OuterClass {
        //code
    class InnerClass{
        //code
    }
}

这里的InnerClass就是一个内部类。无论在我们的学习中还是工作中,内部类用到的地方真的不是很多,一般都出现在源码中,但是我们还是要搞懂内部类,因为后面对我们阅读源码非常有帮助。而且随着后面我们编程能力的提高,自然而然会领悟到它的魅力所在,它能够让我们设计出更加优雅的程序结构。在使用内部类之前我们需要明白为什么要使用内部类,内部类能够为我们带来什么样的好处。

在《Think in java》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

也就是说内部类拥有类的基本特征(可以继承父类,实现接口)。在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。(注:内部类可以嵌套内部类,但是这极大的破换了代码的结构,这里不推荐使用)

那我们来看一下使用内部类如何进行多继承,接口多继承就不举例了,因为接口本身就可以实现多继承。

 class Father{
     public String handsome(){
         return "爸爸很帅气";
     }
 }

 class Mother{
     public String beautiful(){
         return "妈妈很漂亮";
     }
 }

 class Son{
     //内部类继承了Father类
     class MyFather extends Father{
         //重写父类方法
         public String handsome(){
             return "我遗传了爸爸的帅气";
         }
     }
     //内部类继承了Mother类
     class MyMother extends Mother{
         //重写父类方法
         public String beautiful(){
             return "我遗传了妈妈的漂亮";
         }
     }
 }

 public class Test {
     public static void main(String[] args) {
         Son son=new Son();
         Son.MyFather myFather=son.new MyFather();
         System.out.println(myFather.handsome());
         Son.MyMother myMother=son.new MyMother();
         System.out.println(myMother.beautiful());
     }
 }

运行结果:

从上面的举例代码可以看出,两个内部类分别继承了Father、Mother类,并且重写了父类的方法,这是内部类最重要的特性:内部类可以继承一个与外部类无关的类,保证了内部类的独立性,正是基于这一点,多重继承才会成为可能。

可以发现在创建内部类实例的时候,使用了 .new 这个特征,与以往我们创建实例不太相同。.new可以这样理解:根据外部类来创建内部类的对象实例。

Java中内部类可分为四种:成员内部类、局部内部类、匿名内部类、静态内部类。下面我们逐一介绍这四种内部类:

2、成员内部类

成员内部类是定义在类中的类。我们可以把成员内部类看成是外部类的一个成员,所以成员内部类可以无条件访问外部类的所有成员属性和成员方法,包括private成员和静态成员。但是外部类要访问内部类的成员属性和方法则需要通过内部类实例来访问。当成员内部类拥有和外部类同名的成员变量或者方法时,会优先访问的是成员内部类的成员,但是我们可以使用 .this(如果有继承可以使用super)来访问外部类的变量和方法。

在成员内部类中要注意两点:

  1. 成员内部类中不能存在任何static的变量和方法;
  2. 成员内部类是依附于外部类的,所以只有先创建了外围类才能够创建内部类(静态内部类除外)。
 class OuterClass{
     private String outerName="tang_hao_outer";
     private int outerAge=22;

     public OuterClass() {
     }

     //成员方法
     public void outerMethod() {
         System.out.println("我是外部类的outerMethod方法");
     }

     //外部类静态方法
     public static void outerStaticMethod() {
         System.out.println("我是外部类的outerStaticMethod静态方法");
     }
     //定义返回内部类实例的方法,推荐使用该方法来换取内部类实例
     public InnerClass getInnerClassInstance(){
         return new InnerClass();
     }

     //内部类
     class InnerClass{
         private String innerName="tang_hao_Inner";
         private int innerAge=21;

         public InnerClass() {
         }

         public void show(){
             //当名字和外部类一样时,默认调用内部类的成员属性
             System.out.println("内部类变量:"+innerName);
             System.out.println("内部类变量:"+innerAge);
             //当名字和外部类一样时,可以使用 。this来调用外部类属性
             System.out.println("外部类变量:"+OuterClass.this.outerName);
             System.out.println("外部类变量:"+OuterClass.this.outerAge);
             //访问外部类的方法
             outerMethod();
             outerStaticMethod();
         }
     }
 }
 public class Test {
     public static void main(String[] args) {
         //普通方法创建实例
         OuterClass outerClass=new OuterClass();
         OuterClass.InnerClass innerClass=outerClass.new InnerClass();
         innerClass.show();
         System.out.println("-------------------");
         //调用外部类的getInnerClassInstance来创建内部类实例
         OuterClass.InnerClass innerClassInstance = outerClass.getInnerClassInstance();
         innerClassInstance.show();
     }
 }

运行结果:

从上面示例中,当内部类和外部类的变量和方法一样时,我们用了 .this来调用外部类的属性(静态除外,因为静态随类加载而加载,优于对象的创建),它可以理解为:产生一个指向外部类的引用。还有如果该内部类的构造函数无参数,强烈推荐使用类似getInnerClassInstance()这样的方法来获取成员内部类的实例对象。

3、局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。注意:局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。

局部内部类一般都用于返回一个类或实现接口的实例。我们用Comparable接口为例:

 class OuterClass{
     //创建返回一Comparable接口实例的方法
     public Comparable getComparable(){
         //创建一个实现Comparable接口的内部类:局部内部类
         class MyComparable implements Comparable{
             @Override
             public int compareTo(Object o) {
                 return 0;
             }
         }
         //返回实现Comparable接口的实例
         return new MyComparable();
     }
 }

当我们创建外部类的实例调用getComparable()方法时,就可以轻松获取实现Comparable接口的实例了。

注意:局部内部类如果想用方法传入形参,该形参必须使用final声明(JDK8形参变为隐式final声明)。上面的例子如果是getComparable(Object o),那么这个形参前面就隐式加了final关键字。

4、匿名内部类

匿名内部类就是没有名字的内部类。它与局部内部类很相似,不同的是它没有类名,如果某个局部类你只需要用一次,那么你就可以使用匿名内部类。匿名内部类可以使你的代码更加简洁,你可以在定义一个类的同时对其进行实例化。

 //创建一个接口
 interface IPerson{
     public void eat();
     public void sleep();
 }

 public class OuterClass {
     //这里注意,局部内部类如果需要通过方法传入参数,该形参必须使用final声明(JDK8形参变为隐式final声明)
     //我用的JDK8,所以这里没有显式的加final,但是JVM会自动加
     public static IPerson getInnerClassInstance(String eat,String sleep){
         return new IPerson() {
             @Override
             public void eat() {
                 System.out.println(eat);
             }

             @Override
             public void sleep() {
                 System.out.println(sleep);
             }
         };//这个分好要注意
     }

     public static void main(String[] args) {
         IPerson person = OuterClass.getInnerClassInstance("吃饭", "睡觉");
         person.eat();
         person.sleep();
     }
 }

运行结果:吃饭、睡觉

我们知道在抽象类和接口中是不能被实例化的,但是在匿名内部类中我们却看见new了一个IPerson接口,这是怎么回事。这是因为匿名内部类是直接使用new来生成一个对象的引用,而在new对象时,系统会自动给抽象类或接口添加一个它们的实现类,当然这个引用是隐式的,我们看不见。我们自己拆分出来理解一下,注意这里是自己想出来的,运行时并不会有这些类存在:

 class Farmer implements IPerson{

     @Override
     public void eat() {
         System.out.println("农民吃饭");
     }

     @Override
     public void sleep() {
         System.out.println("农民睡觉");
     }
 }

一般我们创建抽象类或接口的实例是这样的:IPerson iPerson = new Farmer();这个可以叫做是非匿名对象非匿名类,而我们创建的是匿名内部类,所以这个实现类不能有名字,所以只好叫父类的名字,所以就看到了前面直接new了一个接口,其实是隐式的创建了实现类的对象。(不知道这样讲对不对,鄙人菜鸟一个,如果有什么不对的或理解错误的地方,欢迎指出,虚心接受!)

匿名内部类最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是继承Runnable接口。

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

  1. 使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
  2. 匿名内部类中是不能定义构造函数的。
  3. 匿名内部类中不能存在任何的静态成员变量和静态方法。
  4. 匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
  5. 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

5、静态内部类

静态内部类是指用static修饰的内部类。在前面夯实Java基础(七)——Static关键字中提到了static关键字可以修饰内部类。我们知道普通类是不允许声明为静态的,只要内部类才可以,被static修饰的内部类它不依赖于外部类的实例。这是因为非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外部类。

static修饰内部类注意几点:

  • 静态内部类可以不依赖于外部类的实例,但是要注意它们创建对象的区别。
  • 静态内部类只能访问外部类的静态变量和静态方法,否则编译会报错。
  • 非静态内部类中可以调用外部类的然后成员,不管是静态的还是非静态的。
  • 如果需要调用内部类的非静态方法,必须先new一个OuterClass的对象outerClass,然后通过outer。new生成内部类的对象,而static内部类则不需要。

简单举例:

 class OuterClass{
     //静态变量
     private static int static_num=66;
     //非静态变量
     private int num=99;

     //静态内部类
     static class InnerStaticClass{
         public void print(){
             //静态内部类只能访问外部类的静态变量和静态方法
             System.out.println("静态内部类方法print()=="+static_num);
             staticShow();
         }
     }
     //非静态内部类
     class InnerClass{
         public void display(){
             //非静态内部类中可以调用外部类的任何成员,不管是静态的还是非静态的
             System.out.println("外部类静态变量=="+static_num);
             System.out.println("外部类普通变量=="+num);
             show();
             System.out.println("非静态内部类方法display()=="+num);

         }
     }
     public void show(){
         System.out.println("外部类非静态show()方法");
     }
     public static void staticShow(){
         System.out.println("外部类静态staticShow()方法");
     }
 }
 public class Test {
     public static void main(String[] args) {
         //static对象实例
         OuterClass.InnerStaticClass staticClass=new OuterClass.InnerStaticClass();
         staticClass.print();

         //非static对象实例
         OuterClass outerClass=new OuterClass();
         OuterClass.InnerClass innerClass=outerClass.new InnerClass();
         innerClass.display();
     }
 }

运行结果:

从上面的例子我们可以看到静态内部类和非静态内部类的区别。

夯实Java基础(十一)——内部类的更多相关文章

  1. 夯实Java基础系列目录

    自进入大学以来,学习的编程语言从最初的C语言.C++,到后来的Java,. NET.而在学习编程语言的同时也逐渐决定了以后自己要学习的是哪一门语言(Java).到现在为止,学习Java语言也有很长一段 ...

  2. 夯实Java基础系列1:Java面向对象三大特性(基础篇)

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 [https://github.com/h2pl/Java-Tutorial](https: ...

  3. 夯实Java基础系列5:Java文件和Java包结构

    目录 Java中的包概念 包的作用 package 的目录结构 设置 CLASSPATH 系统变量 常用jar包 java软件包的类型 dt.jar rt.jar *.java文件的奥秘 *.Java ...

  4. 夯实Java基础系列14:深入理解Java枚举类

    目录 初探枚举类 枚举类-语法 枚举类的具体使用 使用枚举类的注意事项 枚举类的实现原理 枚举类实战 实战一无参 实战二有一参 实战三有两参 枚举类总结 枚举 API 总结 参考文章 微信公众号 Ja ...

  5. Java工程师学习指南第1部分:夯实Java基础系列

    点击关注上方"Java技术江湖",设为"置顶或星标",第一时间送达技术干货. 本文整理了微信公众号[Java技术江湖]发表和转载过的Java优质文章,想看到更多 ...

  6. Java基础(十一) Stream I/O and Files

    Java基础(十一) Stream I/O and Files 1. 流的概念 程序的主要任务是操纵数据.在Java中,把一组有序的数据序列称为流. 依据操作的方向,能够把流分为输入流和输出流两种.程 ...

  7. Java基础十一--多态

    Java基础十一--多态 一.多态定义 简单说:就是一个对象对应着不同类型. 多态在代码中的体现: 父类或者接口的引用指向其子类的对象. /* 对象的多态性. class 动物 {} class 猫 ...

  8. 夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!

    目录 目录 string基础 Java String 类 创建字符串 StringDemo.java 文件代码: String基本用法 创建String对象的常用方法 String中常用的方法,用法如 ...

  9. 夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理

    目录 final使用 final变量 final修饰基本数据类型变量和引用 final类 final关键字的知识点 final关键字的最佳实践 final的用法 关于空白final final内存分配 ...

随机推荐

  1. python的比较关系运算符和逻辑运算符

    比较运算符 运算符 描述 示例 == 检查两个操作数的值是否相等,如果是则条件变为真. 如a=2,b=2则(a == b) 为 true. != 检查两个操作数的值是否相等,如果值不相等,则条件变为真 ...

  2. redux和react-redux做简单组件控制

    这次我们用两种方式实现以下要求 1.三个组件 2.第一个组件有两个按钮 分别控制第二和第三个组件年龄和姓名的改变 3第二个组件展示姓名,第三个组件展示年龄 用到哪些插件 store  redux 一 ...

  3. c++学习书籍推荐《数据结构C++语言描述:应用标准模板库STL(第2版)》下载

    本书是Ford和Topp两位教授于1996看出版的名著Data Structures with C++的第2版,在全球范围内已经有数以万计的学生从中受益.作者将C++语言作为算法描述语言,应用包含规范 ...

  4. Acrobat pro Dc 2018破解版|Adobe Acrobat pro Dc 2018中文破解版下载(附序列号/免破解)

    Acrobat pro Dc 2018破解版是由Adobe公司开发的一款PDF编辑软件,它可以以PDF格式制作和保存用户的文档,以此方便浏览和打印,或使用更高级的功能,且PDF格式的文档可如实地保留原 ...

  5. SpringCloud解析之Eureka

    本文基于Spring Cloud Edgware.SR6版本,从功能和架构上解析Eureka,让大家对Eureka有一个较为清晰的认识(本文默认大家对分布式微服务有一个初步的概念和理解,本文不涉及或少 ...

  6. Windows下通过CMD命令行程序操作MySQL数据库

    注意:如果您的MySQL没有安装在C盘下,先使用命令进入MySQL的安装目录下的bin目录中才可以进行后续操作. 方法如下:例如您安装在D盘.先输入 D:  回车即可进入D盘,再输入cd D:\您my ...

  7. Bzoj 3165 [Heoi2013]Segment题解

    3165: [Heoi2013]Segment Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 668  Solved: 276[Submit][Sta ...

  8. bash 遍历目录

    bash遍历目录脚本traverse.sh: #!/bin/bash datadir=$ declare -a dirlist dirlist=`>/dev/null` for i in ${d ...

  9. RDB和AOF的区别

    redis的持久化方式RDB和AOF的区别   1.前言 最近在项目中使用到Redis做缓存,方便多个业务进程之间共享数据.由于Redis的数据都存放在内存中,如果没有配置持久化,redis重启后数据 ...

  10. 如何启用linux的路由转发功能

    如何使用iptables的NAT功能把红帽企业版Linux作为一台路由器使用? 方法: 提示: 以下方法只适用于红帽企业版Linux 3 以上. 1.打开包转发功能: echo "1&quo ...