java基础(十三)-----详解内部类——Java高级开发必须懂的
java基础(十三)-----详解内部类——Java高级开发必须懂的
正文
可以将一个类的定义放在另一个类的定义内部,这就是内部类。
为什么要使用内部类
为什么要使用内部类?在《Think in java》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。
public interface Father { } public interface Mother { } public class Son implements Father, Mother { } public class Daughter implements Father{ class Mother_ implements Mother{ } }
其实对于这个实例我们确实是看不出来使用内部类存在何种优点,但是如果Father、Mother不是接口,而是抽象类或者具体类呢?这个时候我们就只能使用内部类才能实现多重继承了。
其实使用内部类最大的优点就在于它能够非常好的解决多重继承的问题,但是如果我们不需要解决多重继承问题,那么我们自然可以使用其他的编码方式,但是使用内部类还能够为我们带来如下特性(摘自《Think in java》):
1、内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。
2、在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
3、创建内部类对象的时刻并不依赖于外围类对象的创建。
4、内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
5、内部类提供了更好的封装,除了该外围类,其他类都不能访问。
内部类基础
在这个部分主要介绍内部类如何使用外部类的属性和方法,以及使用.this与.new。
当我们在创建一个内部类的时候,它无形中就与外围类有了一种联系,依赖于这种联系,它可以无限制地访问外围类的元素。
public class OuterClass { private String name ; private int age; /**省略getter和setter方法**/ public class InnerClass{ public InnerClass(){ name = "chenhao"; age = 23; } public void display(){ System.out.println("name:" + getName() +" ;age:" + getAge()); } } public static void main(String[] args) { OuterClass outerClass = new OuterClass(); OuterClass.InnerClass innerClass = outerClass.new InnerClass(); innerClass.display(); } } -------------- Output: name:chenhao ;age:23
在这个应用程序中,我们可以看到内部了InnerClass可以对外围类OuterClass的属性进行无缝的访问,尽管它是private修饰的。这是因为当我们在创建某个外围类的内部类对象时,此时内部类对象必定会捕获一个指向那个外围类对象的引用,只要我们在访问外围类的成员时,就会用这个引用来选择外围类的成员。
其实在这个应用程序中我们还看到了如何来引用内部类:引用内部类我们需要指明这个对象的类型:OuterClasName.InnerClassName。同时如果我们需要创建某个内部类对象,必须要利用外部类的对象通过.new来创建内部类:OuterClass.InnerClass innerClass = outerClass.new InnerClass();。
同时如果我们需要生成对外部类对象的引用,可以使用OuterClassName.this,这样就能够产生一个正确引用外部类的引用了。当然这点实在编译期就知晓了,没有任何运行时的成本。
public class OuterClass { public void display(){ System.out.println("OuterClass..."); } public class InnerClass{ public OuterClass getOuterClass(){ return OuterClass.this; } } public static void main(String[] args) { OuterClass outerClass = new OuterClass(); OuterClass.InnerClass innerClass = outerClass.new InnerClass(); innerClass.getOuterClass().display(); } } ------------- Output: OuterClass...
到这里了我们需要明确一点,内部类是个编译时的概念,一旦编译成功后,它就与外围类属于两个完全不同的类(当然他们之间还是有联系的)。对于一个名为OuterClass的外围类和一个名为InnerClass的内部类,在编译成功后,会出现这样两个class文件:OuterClass.class和OuterClass$InnerClass.class。
在Java中内部类主要分为静态内部类、成员内部类、局部内部类、匿名内部类。
静态内部类
静态内部类: 一般也称”静态嵌套类“,在类中用static
声明的内部类。
因为是static,所以不依赖于外围类对象实例而独立存在,静态内部类的可以访问外围类中的所有静态成员,包括private
的静态成员,但是它不能使用任何外围类的非static成员变量和方法。
同时静态内部类可以说是所有内部类中独立性最高的内部类,其创建对象、继承(实现接口)、扩展子类等使用方式与外围类并没有多大的区别。
public class OuterClass {//外围类 public int aa; //实例成员 private static float f = 1.5f;//private的静态成员 static void println() { System.out.println("这是静态方法"); } protected static class StaticInnerClass{//protected的静态内部类 float a; public StaticInnerClass() { a = f;// 外围类的private静态变量 println();//外围类的静态方法 } } } class OtherClass{ public static void main(String[] args) { //创建静态内部类的对象 OuterClass.StaticInnerClass staticInnerClass = new OuterClass.StaticInnerClass(); } }
成员内部类
成员内部类: 定义在类的内部,而且与成员方法、成员变量同级,即也是外围类的成员之一,因此 成员内部类 与 外围类 是紧密关联的。
注意:
这种紧密关联指的是,成员内部类的对象的创建必须依赖于外围类的对象(即没有外围类对象,就不可能创建成员内部类)。因此,成员内部类有以下3个特点:
-
成员内部类可以访问外围类的所有成员,包括私有成员;
-
成员内部类是不可以声明静态成员(包括静态变量、静态方法、静态成员类、嵌套接口),但有个例外---可以声明
static final
的变量, 这是因为编译器对final
类型的特殊处理,是直接将值写入字节码; -
成员内部类对象都隐式地保存了一个引用,指向创建它的外部类对象;或者说,成员内部类的入口是由外围类的对象保持着(静态内部类的入口,则直接由外围类保持着)
成员内部类中的 this,new关键字:
-
-
获取外部类对象:
OuterClass.this
-
明确指定使用外部类的成员(当内部类与外部类的名字冲突时):
OuterClass.this
.成员名 -
创建内部类对象的new:
外围类对象.new
-
//先创建外围类对象 OuterClass outer=new OuterClass(); //创建成员内部类对象 OuterClass.InnerClass inner=outer.new InnerClass();
成员内部类的对象创建
我们知道,成员内部类就像外围类的实例成员一样,一定要存在对象才能访问,即成员内部类必须绑定一个外围类的对象。上面已经介绍了成员内部类的创建格式了,我们直接看一个例子
public class OuterClass {//外围类 public int aa; //实例成员 private static float f = 1.5f;//private的静态成员 public void initInnerClass() { System.out.println("内部类的初始化方法"); } public void createInnerClass() {// //外围类的成员方法中创建成员内部类对象 InnerClass innerClass = new InnerClass(); } class InnerClass{//成员内部类 private double aa; //与围类的变量aa的名字重复 public InnerClass(){ this.aa = OuterClass.this.aa + f;//明确指定两个aa的所属 initInnerClass(); } } } //其他类 class OtherClass{ public static void main(String[] args) { //其他类中创建成员内部类 OuterClass oc = new OuterClass();//外部类对象 //创建内部类对象 OuterClass.InnerClass innerClass = oc.new InnerClass(); } }
补充几点:
-
成员内部类可以继续包含成员内部类,而且不管一个内部类被嵌套了多少层,它都能透明地访问它的所有外部类所有成员;
-
成员内部可以继续嵌套多层的成员内部类,但无法嵌套静态内部类;静态内部类则都可以继续嵌套这两种内部类。
class InnerClass{//成员内部类 private double aa; //与围类的变量aa的名字重复 public InnerClass(){ this.aa = OuterClass.this.aa + f;//明确指定两个aa的所属 initInnerClass(); } public class InnerInnerCalss2{//成员内部类中的成员内部类 protected double aa = OuterClass.this.aa;//最外层的外围类的成员变量 }//InnerInnerCalss2 }//InnerClass
继承成员内部类
在内部类的访问权限允许的情况下,成员内部类也是可以被继承的。由于成员内部类的对象依赖于外围类的对象,或者说,成员内部类的构造器入口由外围类的对象把持着。因此,继承了成员内部类的子类必须要与一个外围类对象关联起来。同时,子类的构造器是必须要调用父类的构造器方法,所以也只能通过父类的外围类对象来调用父类构造器。
下面的例子也是基于上面的例子的,只贴出多出的部分代码。
class ChildClass extends OuterClass.InnerClass{ //成员内部类的子类的构造器的格式 public ChildClass(OuterClass outerClass) { outerClass.super();//通过外围类的对象调用父类的构造方法 } }
局部内部类
局部内部类: 就是在方法、构造器、初始化块中声明的类,在结构上类似于一个局部变量。因此局部内部类是不能使用访问修饰符。
局部内部类的两个访问限制:
-
对于局部变量,局部内部类只能访问
final
的局部变量。不过,后期JDK(忘了是JDK几了)局部变量可不用final修饰,也可以被局部内部类访问,但你必须时刻记住此局部变量已经是final了,不能再改变。 -
对于类的全局成员,局部内部类定义在实例环境中(构造器、对象成员方法、实例初始化块),则可以访问外围类的所有成员;但如果内部类定义在静态环境中(静态初始化块、静态方法),则只能访问外围类的静态成员。
public class OuterClass { private int a = 21; static {//静态域中的局部内部类 class LocalClass1{ // int z = a; //错误,在静态的作用域中无法访问对象成员 } } {//实例初始化块中的局部内部类 class localClass2{ } } public OuterClass(){ int x = 2; final int y = 3; // x = 3;//若放开此行注释,编译无法通过,因为局部变量x已经是final类型 //构造器中的局部内部类 class localClass3{ int z = y; //可以访问final的局部变量 int b = a;//可以访问类的所有成员 //访问没有用final修饰的局部变量 int c = x; } } public void createRunnable() { final int x = 4; //方法中的局部内部类 class LocalClass4 implements Runnable {// @Override public void run() { System.out.println("局部final变量:"+x); System.out.println("对象成员变量:"+a); } } } }
推荐博客
匿名内部类
匿名内部类: 与局部内部类很相似,只不过匿名内部类是一个没有给定名字的内部类,在创建这个匿名内部类后,便会立即用来创建并返回此内部类的一个对象引用。
作用:匿名内部类用于隐式继承某个类(重写里面的方法或实现抽象方法)或者实现某个接口。
匿名内部类的访问限制: 与局部内部类一样,请参考局部内部类;
匿名内部类的优缺点:
优点: 编码方便快捷;
缺点:
-
只能继承一个类或实现一个接口,不能再继承其他类或其他接口。
-
只能用于创建一次对象实例;
下面的例子是我们创建线程时经常用到的匿名内部类的方式来快速地创建一个对象的例子:
class MyOuterClass { private int x = 5; void createThread() { final int a = 10; int b = 189; // 匿名内部类继承Thread类,并重写Run方法 Thread thread = new Thread("thread-1") { int c = x; //访问成员变量 int d = a; //final的局部变量 int e = b; //访问没有用final修饰的局部变量 @Override public void run() { System.out.println("这是线程thread-1"); } }; // 匿名内部类实现Runnable接口 Runnable r = new Runnable() { @Override public void run() { System.out.println("线程运行中"); } }; } }
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
asp.net core系列 32 EF查询数据 必备知识(1)
asp.net core系列 32 EF查询数据 必备知识(1) 一.查询的工作原理 Entity Framework Core 使用语言集成查询 (LINQ) 来查询数据库中的数据。通过 LINQ 可使用 C#(或你选择的其他 .NET 语言)基于派生上下文和实体类编写强类型查询。LINQ 查询的表示形式会传递给数据库提供程序,进而转换为特定的数据库查询语言(例如,适用于关系数据库的 SQL)。 1.1 查询的生命周期, 下面是每个查询所经历的过程概述: (1) LINQ 查询由 E F处理,用于生成已准备好的表示形式,由数据库提供程序处理。缓存结果,以便每次执行查询时都不需要执行此处理。 (2) 结果将传递给数据库提供程序 a.数据库提供程序会识别出查询的哪些部分可以在数据库中求值。 b. 查询的这些部分会转换为特定数据库的查询语言(例如,关系数据库的 SQL) c. 将一个或多个查询发送到数据库并返回结果集(结果是来自数据库的值,而不是实体实例) (3) 返回结果集处理 a.如果这是跟踪查询,EF会检查数据是否代表一个实体,已存在于上下文实例的更改跟踪器中。 如果是,则会返回现...
- 下一篇
大数据成人工智能应用重点
随着AI技术的细分场景越来越多,人工智能带来的第四次工业革命浪潮已成汹涌之势,众多传统行业借助AI赋能产业结构,不断升级换代与创新变革,新产品也在不断涌现,AI也在潜移默化改变着生活的方方面面,生物识别、视频识别、内容审核、智能安防等。国内更是诞生了诸如旷视科技、商汤科技、极链科技Video++、依图科技等优秀人工智能初创企业。当前,人工智能已经不仅仅是提升工作效率的一种技术手段,同时还在重塑着产业链和价值创造方式。 人工智能这几年有了这么大的突破,其中一个重要的推动力就是大数据。在大数据这个概念出现之前计算机并不能很好的解决需要人去做判别的一些问题。所以说如今的人工智能不如说是数据智能,人工智能其实就是用大量的数据作导向,让需要机器来做判别的问题最终转化为数据问题。 互联网科技发展蓬勃兴起,人工智能时代来临,抓住下一个风口。为帮助那些往想互联网方向转行想学习,却因为时间不够,资源不足而放弃的人。我自己整理的一份最新的大数据进阶资料和高级开发教程,大数据学习抠qun: 74加零零4加13八1就可以找到组织学习 欢迎进阶中和进想深入大数据的小伙伴加入 技术型的高科技创业公司都喜欢特别的新...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Linux系统CentOS6、CentOS7手动修改IP地址
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Red5直播服务器,属于Java语言的直播服务器
- CentOS6,CentOS7官方镜像安装Oracle11G
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池