热门标签 | 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

推荐阅读
  • JUC(三):深入解析AQS
    本文详细介绍了Java并发工具包中的核心类AQS(AbstractQueuedSynchronizer),包括其基本概念、数据结构、源码分析及核心方法的实现。 ... [详细]
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • Flutter 2.* 路由管理详解
    本文详细介绍了 Flutter 2.* 中的路由管理机制,包括路由的基本概念、MaterialPageRoute 的使用、Navigator 的操作方法、路由传值、命名路由及其注册、路由钩子等。 ... [详细]
  • Spring – Bean Life Cycle
    Spring – Bean Life Cycle ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • IOS Run loop详解
    为什么80%的码农都做不了架构师?转自http:blog.csdn.netztp800201articledetails9240913感谢作者分享Objecti ... [详细]
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • 结城浩(1963年7月出生),日本资深程序员和技术作家,居住在东京武藏野市。他开发了著名的YukiWiki软件,并在杂志上发表了大量程序入门文章和技术翻译作品。结城浩著有30多本关于编程和数学的书籍,其中许多被翻译成英文和韩文。 ... [详细]
  • 在多线程并发环境中,普通变量的操作往往是线程不安全的。本文通过一个简单的例子,展示了如何使用 AtomicInteger 类及其核心的 CAS 无锁算法来保证线程安全。 ... [详细]
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
  • MySQL 5.7 学习指南:SQLyog 中的主键、列属性和数据类型
    本文介绍了 MySQL 5.7 中主键(Primary Key)和自增(Auto-Increment)的概念,以及如何在 SQLyog 中设置这些属性。同时,还探讨了数据类型的分类和选择,以及列属性的设置方法。 ... [详细]
  • 本文详细介绍了在编写jQuery插件时需要注意的关键要点,包括模块化支持、命名规范和性能优化等内容,旨在帮助开发者提高插件的质量和可维护性。 ... [详细]
  • C语言中全部可用的数学函数有哪些?2.longlabs(longn);求长整型数的绝对值。3.doublefabs(doublex);求实数的绝对值。4.doublefloor(d ... [详细]
  • 版权声明:本文为博主原创文章,遵循CC4.0BY-SA版权协议,转载请附上原文出处链接和本声明。本文链接:https:blog.csdn.netweixin_43171186art ... [详细]
  • 多线程基础概览
    本文探讨了多线程的起源及其在现代编程中的重要性。线程的引入是为了增强进程的稳定性,确保一个进程的崩溃不会影响其他进程。而进程的存在则是为了保障操作系统的稳定运行,防止单一应用程序的错误导致整个系统的崩溃。线程作为进程的逻辑单元,多个线程共享同一CPU,需要合理调度以避免资源竞争。 ... [详细]
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社区 版权所有