Java提高篇 —— Java内部类详解

一、简介

 

       内部类是一个非常有用的特性但又比较难理解使用的特性。

       内部类我们从外面看是非常容易理解的,无非就是在一个类的内部在定义一个类。

public class OuterClass {private String name ;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}class InnerClass{public InnerClass(){name = "chenssy";age = 23;}}
}

       在这里InnerClass就是内部类,对于初学者来说内部类实在是使用的不多,但是随着编程能力的提高,我们会领悟到它的魅力所在,它可以使用能够更加优雅的设计我们的程序结构。在使用内部类之间我们需要明白为什么要使用内部类,内部类能够为我们带来什么样的好处。

 

二、为什么要使用内部类?

 

       为什么要使用内部类?在《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 = "chenssy";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:chenssy   ;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中内部类主要分为:成员内部类、局部内部类、匿名内部类、静态内部类

 

四、成员内部类

 

       成员内部类也是最普通的内部类,它是外围类的一个成员,所以他是可以无限制的访问外围类的所有 成员属性和方法,尽管是private的,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。

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

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

       第二:成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类。

public class OuterClass {private String str;public void outerDisplay(){System.out.println("outerClass...");}public class InnerClass{public void innerDisplay(){//使用外围内的属性str = "chenssy...";System.out.println(str);//使用外围内的方法outerDisplay();}}/*推荐使用getxxx()来获取成员内部类,尤其是该内部类的构造函数无参数时 */public InnerClass getInnerClass(){return new InnerClass();}public static void main(String[] args) {OuterClass outer = new OuterClass();OuterClass.InnerClass inner = outer.getInnerClass();inner.innerDisplay();}
}
--------------------
chenssy...
outerClass...

PS:推荐使用getxxx()来获取成员内部类,尤其是该内部类的构造函数无参数时 。

 

五、局部内部类

 

       有这样一种内部类,它是嵌套在方法和作用于内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法和属性中被使用,出了该方法和属性就会失效。

       对于局部内部类实在是想不出什么好例子,所以就引用《Think in java》中的经典例子了。

定义在方法里:

public class Parcel5 {public Destionation destionation(String str){class PDestionation implements Destionation{private String label;private PDestionation(String whereTo){label = whereTo;}public String readLabel(){return label;}}return new PDestionation(str);}public static void main(String[] args) {Parcel5 parcel5 = new Parcel5();Destionation d = parcel5.destionation("chenssy");}
}

定义在作用域内:

public class Parcel6 {private void internalTracking(boolean b){if(b){class TrackingSlip{private String id;TrackingSlip(String s) {id = s;}String getSlip(){return id;}}TrackingSlip ts = new TrackingSlip("chenssy");String string = ts.getSlip();}}public void track(){internalTracking(true);}public static void main(String[] args) {Parcel6 parcel6 = new Parcel6();parcel6.track();}
}

 

六、匿名内部类

 

在做Swing编程中,我们经常使用这种方式来绑定事件:

button2.addActionListener(  new ActionListener(){  public void actionPerformed(ActionEvent e) {  System.out.println("你按了按钮二");  }  });

我们咋一看可能觉得非常奇怪,因为这个内部类是没有名字的,在看如下这个例子:

public class OuterClass {public InnerClass getInnerClass(final int num,String str2){return new InnerClass(){int number = num + 3;public int getNumber(){return number;}};        /* 注意:分号不能省 */}public static void main(String[] args) {OuterClass out = new OuterClass();InnerClass inner = out.getInnerClass(2, "chenssy");System.out.println(inner.getNumber());}
}interface InnerClass {int getNumber();
}----------------
Output:
5

这里我们就需要看清几个地方:

       1、 匿名内部类是没有访问修饰符的。

       2、 new 匿名内部类,这个类首先是要存在的。如果我们将那个InnerClass接口注释掉,就会出现编译出错。

       3、 注意getInnerClass()方法的形参,第一个形参是用final修饰的,而第二个却没有。同时我们也发现第二个形参在匿名内部类中没有使用过,所以当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为final。

       4、 匿名内部类是没有构造方法的。因为它连名字都没有何来构造方法。

 

       上面第一个形参定义成final的,而第二个却没有,那么形参为什么要定义为final呢?在网上找到本人比较如同的解释:

       这是一个编译器设计的问题,如果你了解java的编译原理的话很容易理解。  

       首先,内部类被编译的时候会生成一个单独的内部类的.class文件,这个文件并不与外部类在同一class文件中。  

       当外部类传的参数被内部类调用时,从java程序的角度来看是直接的调用例如: 

public void dosome(final String a,final int b){  class Dosome{public void dosome(){System.out.println(a+b)}};  Dosome some=new Dosome();  some.dosome();  
}  

       从代码来看好像是那个内部类直接调用的a参数和b参数,但是实际上不是,在java编译器编译以后实际的操作代码是:

class Outer$Dosome{  public Dosome(final String a,final int b){  this.Dosome$a=a;  this.Dosome$b=b;  }  public void dosome(){  System.out.println(this.Dosome$a+this.Dosome$b);  }  
}

       从以上代码看来,内部类并不是直接调用方法传进来的参数,而是内部类将传进来的参数通过自己的构造器备份到了自己的内部,自己内部的方法调用的实际是自己的属性而不是外部类方法的参数。

       这样理解就很容易得出为什么要用final了,因为两者从外表看起来是同一个东西,实际上却不是这样,如果内部类改掉了这些参数的值也不可能影响到原参数,然而这样却失去了参数的一致性,因为从编程人员的角度来看他们是同一个东西,如果编程人员在程序设计的时候在内部类中改掉参数的值,但是外部调用的时候又发现值其实没有被改掉,这就让人非常的难以理解和接受,为了避免这种尴尬的问题存在,所以编译器设计人员把内部类能够使用的参数设定为必须是final来规避这种莫名其妙错误的存在。(简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变)

       因为匿名内部类,没名字,是用默认的构造函数的,无参数的,那如果需要参数呢?则需要该类有带参数的构造函数:

public class Outer { public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.getInner("Inner", "gz"); System.out.println(inner.getName()); } public Inner getInner(final String name, String city) { return new Inner(name, city) { private String nameStr = name; public String getName() { return nameStr; } }; } 
} abstract class Inner { Inner(String name, String city) { System.out.println(city); } abstract String getName(); 
} 

       注意这里的形参city,由于它没有被匿名内部类直接使用,而是被抽象类Inner的构造函数所使用,所以不必定义为final。

       而匿名内部类通过实例初始化,可以达到类似构造器的效果:

public class Outer { public static void main(String[] args) { Outer outer = new Outer(); Inner inner = outer.getInner("Inner", "gz"); System.out.println(inner.getName()); System.out.println(inner.getProvince()); } public Inner getInner(final String name, final String city) { return new Inner() { private String nameStr = name; private String province; // 实例初始化 { if (city.equals("gz")) { province = "gd"; }else { province = ""; } } public String getName() { return nameStr; } public String getProvince() { return province; } }; } 
} interface Inner { String getName(); String getProvince(); 
} 

 

七、静态内部类

 

       Static可以修饰成员变量、方法、代码块,其他它还可以修饰内部类,使用static修饰的内部类我们称之为静态内部类,不过我们更喜欢称之为嵌套内部类。静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围内,但是静态内部类却没有。没有这个引用就意味着:

       1、 它的创建是不需要依赖于外围类的。

       2、 它不能使用任何外围类的非static成员变量和方法。

public class OuterClass {private String sex;public static String name = "chenssy";/***静态内部类*/static class InnerClass1{/* 在静态内部类中可以存在静态成员 */public static String _name1 = "chenssy_static";public void display(){/* * 静态内部类只能访问外围类的静态成员变量和方法* 不能访问外围类的非静态成员变量和方法*/System.out.println("OutClass name :" + name);}}/*** 非静态内部类*/class InnerClass2{/* 非静态内部类中不能存在静态成员 */public String _name2 = "chenssy_inner";/* 非静态内部类中可以调用外围类的任何成员,不管是静态的还是非静态的 */public void display(){System.out.println("OuterClass name:" + name);}}/*** @desc 外围类方法* @author chenssy* @data 2013-10-25* @return void*/public void display(){/* 外围类访问静态内部类:内部类. */System.out.println(InnerClass1._name1);/* 静态内部类 可以直接创建实例不需要依赖于外围类 */new InnerClass1().display();/* 非静态内部的创建需要依赖于外围类 */OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2();/* 方位非静态内部类的成员需要使用非静态内部类的实例 */System.out.println(inner2._name2);inner2.display();}public static void main(String[] args) {OuterClass outer = new OuterClass();outer.display();}
}
----------------
Output:
chenssy_static
OutClass name :chenssy
chenssy_inner
OuterClass name:chenssy

       上面这个例子充分展现了静态内部类和非静态内部类的区别。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/446950.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu修改界面的大小

命令 xrandr 就会显示ubuntu支持的屏幕比例使用命令 xrandr --size 1680x1050 切换屏幕大小

Java提高篇 —— String缓冲池

一、String缓冲池 首先我们要明确,String并不是基本数据类型,而是一个对象,并且是不可变的对象。查看源码就会发现String类为final型的(当然也不可被继承),而且通过查看JDK文档会发现几乎每一个修改String对…

C++最新使用开源openssl实现输入是文件,输出是文件的AES加解密的代码

AES.h头文件 #include <cstring> #include <fstream> #include <iostream> #include <openssl/aes.h>//AES文件加密函数 int aes_encrypt_file(const std::string &original_backup_file_path,const std::string &encrypted_file_path,const …

Java基础 —— JVM内存模型与垃圾回收

目录一、概述二、运行时数据区方法区运行时常量池堆栈本地方法栈程序计数器三、对象访问四、垃圾回收如何定义垃圾1、引用计数法2、可达性分析垃圾回收方法1、Mark-Sweep标记-清除算法2、Copying复制算法3、Mark-Compact标记-整理算法4、Generational Collection 分代收集垃圾收…

Report Design

ERP_ENT_STD-CSDN博客

规范化流程化提交自己代码到远程gitlab服务器

流程 进入到build目录使用make命令进行编译 make -j 6&#xff0c;前提是cmake命令已经执行../format.py rungit add .. 添加文件git checkout -b xxx 创建xxx分支&#xff0c;其中xxx是分支名字git branch 查看分支git commit -m "[chy/backup] modify parameters for…

Java提高篇 ——Java注解

目录一、注解注解的定义注解的应用元注解RetentionDocumentedTargetInheritedRepeatable注解的属性Java 预置的注解DeprecatedOverrideSuppressWarningsSafeVarargsFunctionalInterface二、注解的提取三、注解与反射四、注解的使用场景五、亲手自定义注解完成某个目的六、注解应…

linux使用openssl查看文件的md5数值

代码 #include <stdio.h> #include <openssl/md5.h>std::string get_file_md5(const char *path){unsigned char digest [MD5_DIGEST_LENGTH];std::ifstream file(path, std::ios::in | std::ios::binary); //打开文件MD5_CTX md5_ctx;MD5_Init(&md5_ctx);cha…

Android 性能优化四个方面总结

目录一、四个方面二、卡顿优化1、Android系统显示原理2、卡顿根本原因3、性能分析工具&#xff08;1&#xff09;Profile GPU Rendering&#xff08;2&#xff09;TraceView&#xff08;3&#xff09;Systrace UI 性能分析4、优化建议&#xff08;1&#xff09;布局优化&#x…

pycharm/clion/idea等产品多含代码左移右移操作

左移 选中多行代码后&#xff0c;按下Tab键&#xff0c;一次缩进四个字符 右移 鼠标选中多行代码后&#xff0c;同时按住shiftTab键&#xff0c;一次左移四个字符

Android 开源框架选择

目录一、前言二、APP的整体架构三、技术选型的考量点四、日志记录能力五、JSON解析能力1、gson2、jackson3、Fastjson4、LoganSquare六、数据库操作能力1、ActiveAndroid2、ormlite3、greenDAO4、Realm七、网络通信能力1、android-async-http2、OkHttp3、Volley4、Retrofit八、…

使用opensll的md5对于string进行加密

代码 #include <openssl/md5.h>#include <sstream> #include <iomanip> #include <iostream>void get_string_md5(const std::string& await_md5_string) {unsigned char md5[MD5_DIGEST_LENGTH];MD5(reinterpret_cast<unsigned const char*&g…

Android Studio 自定义Gradle Plugin

一、简介 之前公司的一个项目需要用到Gradle插件来修改编译后的class文件&#xff0c;今天有时间就拿出来整理一下&#xff0c;学习一下Gradle插件的编写还是一件十分有意义的事。 二、Gradle插件类型 一种是直接在项目中的gradle文件里编写&#xff0c;这种方式的缺点是无法复…

C++创建临时文件

命令 std::string original_backup_file std::tmpnam(nullptr);

Android Studio Gradle两种更新方式

第一种、Android Studio自动更新 第一步&#xff1a;修改gradle版本 修改项目根目录/gradle/wrapper/gradle-wrapper.properties最后一行的地址&#xff1a; distributionUrlhttps://services.gradle.org/distributions/gradle-3.3-all.zip新gradle地址从官方下载的地方有。…

C++使用openssl实现aes加解密,其中加密是string到文件,解密是文件到string,切合项目背景

代码 使用md5对于用户输入的密码进行保护,也使得密码的长度固定crypto_util.h#pragma once#include <string>namespace hsm{ namespace mgmt{void get_md5_digest(const std::string &data,uint8_t result[16]);void aes_encrypt_to_file(const std::string &fi…

Android Canvas的drawText()和文字居中方案

自定义View是绘制文本有三类方法&#xff1a; // 第一类 public void drawText (String text, float x, float y, Paint paint) public void drawText (String text, int start, int end, float x, float y, Paint paint) public void drawText (CharSequence text, int start…

IntelliJ IDEA配置Tomcat

查找该问题的童鞋我相信IntelliJ IDEA&#xff0c;Tomcat的下载&#xff0c;JDK等其他的配置都应该完成了&#xff0c;那我直接进入正题了。 1、新建一个项目 2、由于这里我们仅仅为了展示如何成功部署Tomcat&#xff0c;以及配置完成后成功运行一个jsp文件&#xff0c;我仅勾…

C++对于文件的相关操作 创建、读写、删除代码

创建 /*** brief 创建文件:在密码设备内部创建用于存储用户数据的文件* param pucFileName 缓冲区指针&#xff0c;用于存放输入的文件名&#xff0c;最大长度128字节* param uiNameLen 文件名长度* param uiFileSize 文件所占存储空间的长度*/void CreateFile(sdf_uint8_t…

Android开发之Path详解

目录一、xxxTo方法1、lineTo(float x, float y)2、moveTo(float x, float y)3、arcTo3.1、arcTo(RectF oval, float startAngle, float sweepAngle)3.2、arcTo(RectF oval, float startAngle, float sweepAngle,boolean forceMoveTo)3.3、arcTo(float left, float top, float r…