热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

夯实Java基础(十一)——内部类

1、内部类的概念内部类顾名思义:将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。对于很多Java初学者来说,内部类学起来真的是一头雾水,根本理解不清楚是

1、内部类的概念

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

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

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

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

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

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

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

1 class Father{
2 public String handsome(){
3 return "爸爸很帅气";
4 }
5 }
6
7 class Mother{
8 public String beautiful(){
9 return "妈妈很漂亮";
10 }
11 }
12
13 class Son{
14 //内部类继承了Father类
15 class MyFather extends Father{
16 //重写父类方法
17 public String handsome(){
18 return "我遗传了爸爸的帅气";
19 }
20 }
21 //内部类继承了Mother类
22 class MyMother extends Mother{
23 //重写父类方法
24 public String beautiful(){
25 return "我遗传了妈妈的漂亮";
26 }
27 }
28 }
29
30 public class Test {
31 public static void main(String[] args) {
32 Son son=new Son();
33 Son.MyFather myFather=son.new MyFather();
34 System.out.println(myFather.handsome());
35 Son.MyMother myMother=son.new MyMother();
36 System.out.println(myMother.beautiful());
37 }
38 }

 运行结果:

夯实Java基础(十一)——内部类 - 文章图片

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

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

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

2、成员内部类

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

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


  1. 成员内部类中不能存在任何static的变量和方法;

  2. 成员内部类是依附于外部类的,所以只有先创建了外围类才能够创建内部类(静态内部类除外)。

1 class OuterClass{
2 private String outerName="tang_hao_outer";
3 private int outerAge=22;
4
5 public OuterClass() {
6 }
7
8 //成员方法
9 public void outerMethod() {
10 System.out.println("我是外部类的outerMethod方法");
11 }
12
13 //外部类静态方法
14 public static void outerStaticMethod() {
15 System.out.println("我是外部类的outerStaticMethod静态方法");
16 }
17 //定义返回内部类实例的方法,推荐使用该方法来换取内部类实例
18 public InnerClass getInnerClassInstance(){
19 return new InnerClass();
20 }
21
22 //内部类
23 class InnerClass{
24 private String innerName="tang_hao_Inner";
25 private int innerAge=21;
26
27 public InnerClass() {
28 }
29
30 public void show(){
31 //当名字和外部类一样时,默认调用内部类的成员属性
32 System.out.println("内部类变量:"+innerName);
33 System.out.println("内部类变量:"+innerAge);
34 //当名字和外部类一样时,可以使用 。this来调用外部类属性
35 System.out.println("外部类变量:"+OuterClass.this.outerName);
36 System.out.println("外部类变量:"+OuterClass.this.outerAge);
37 //访问外部类的方法
38 outerMethod();
39 outerStaticMethod();
40 }
41 }
42 }
43 public class Test {
44 public static void main(String[] args) {
45 //普通方法创建实例
46 OuterClass outerClass=new OuterClass();
47 OuterClass.InnerClass innerClass=outerClass.new InnerClass();
48 innerClass.show();
49 System.out.println("-------------------");
50 //调用外部类的getInnerClassInstance来创建内部类实例
51 OuterClass.InnerClass innerClassInstance = outerClass.getInnerClassInstance();
52 innerClassInstance.show();
53 }
54 }

 运行结果:

夯实Java基础(十一)——内部类 - 文章图片

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

3、局部内部类

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

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

1 class OuterClass{
2 //创建返回一Comparable接口实例的方法
3 public Comparable getComparable(){
4 //创建一个实现Comparable接口的内部类:局部内部类
5 class MyComparable implements Comparable{
6 @Override
7 public int compareTo(Object o) {
8 return 0;
9 }
10 }
11 //返回实现Comparable接口的实例
12 return new MyComparable();
13 }
14 }

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

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

4、匿名内部类

匿名内部类就是没有名字的内部类。

1 //创建一个接口
2 interface IPerson{
3 public void eat();
4 public void sleep();
5 }
6
7 public class OuterClass {
8 //这里注意,局部内部类如果需要通过方法传入参数,该形参必须使用final声明(JDK8形参变为隐式final声明)
9 //我用的JDK8,所以这里没有显式的加final,但是JVM会自动加
10 public static IPerson getInnerClassInstance(String eat,String sleep){
11 return new IPerson() {
12 @Override
13 public void eat() {
14 System.out.println(eat);
15 }
16
17 @Override
18 public void sleep() {
19 System.out.println(sleep);
20 }
21 };//这个分好要注意
22 }
23
24 public static void main(String[] args) {
25 IPerson person = OuterClass.getInnerClassInstance("吃饭", "睡觉");
26 person.eat();
27 person.sleep();
28 }
29 }

 

运行结果:吃饭、睡觉

我们知道在抽象类和接口中是不能被实例化的,但是在匿名内部类中我们却看见new了一个IPerson接口,这是怎么回事。这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。

 

匿名内部类其实就是一个没有名字的方法内部类,所以它符合方法内部类的所有约束,初次之外,还有一些地方需要注意:

匿名内部类是没有访问修饰符的。
匿名内部类必须继承一个抽象类或者实现一个接口
匿名内部类中不能存在任何静态成员或方法
匿名内部类是没有构造方法的,因为它没有类名。
一般使用匿名内部类的场景是,要继承或实现的接口只有一个抽象方法,比如添加一个监听器:

5、静态内部类

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

static修饰内部类注意几点:


  • 静态内部类可以不依赖于外部类的实例,但是要注意它们创建对象的区别。

  • 静态内部类只能访问外部类的静态变量和静态方法,否则编译会报错。

  • 非静态内部类中可以调用外部类的然后成员,不管是静态的还是非静态的。

  • 如果需要调用内部类的非静态方法,必须先new一个OuterClass的对象outerClass,然后通过outer。new生成内部类的对象,而static内部类则不需要。

简单举例:

1 class OuterClass{
2 //静态变量
3 private static int static_num=66;
4 //非静态变量
5 private int num=99;
6
7 //静态内部类
8 static class InnerStaticClass{
9 public void print(){
10 //静态内部类只能访问外部类的静态变量和静态方法
11 System.out.println("静态内部类方法print()=="+static_num);
12 staticShow();
13 }
14 }
15 //非静态内部类
16 class InnerClass{
17 public void display(){
18 //非静态内部类中可以调用外部类的任何成员,不管是静态的还是非静态的
19 System.out.println("外部类静态变量=="+static_num);
20 System.out.println("外部类普通变量=="+num);
21 show();
22 System.out.println("非静态内部类方法display()=="+num);
23
24 }
25 }
26 public void show(){
27 System.out.println("外部类非静态show()方法");
28 }
29 public static void staticShow(){
30 System.out.println("外部类静态staticShow()方法");
31 }
32 }
33 public class Test {
34 public static void main(String[] args) {
35 //static对象实例
36 OuterClass.InnerStaticClass staticClass=new OuterClass.InnerStaticClass();
37 staticClass.print();
38
39 //非static对象实例
40 OuterClass outerClass=new OuterClass();
41 OuterClass.InnerClass innerClass=outerClass.new InnerClass();
42 innerClass.display();
43 }
44 }

运行结果:

夯实Java基础(十一)——内部类 - 文章图片

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

参考文章:https://www.cnblogs.com/chenssy/p/3388487.html

推荐阅读
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
author-avatar
kingseao
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有