内部类也是Java中的一个比较重要的含义,今天我们来整理一下。
虽然类的基本组成就是成员属性和方法,但是在任何语言中结构都是允许被嵌套的,所以在一个类的内部可以定义其他的类,这样的类我们就称为内部类。
1.内部类的基本概念
说到内部类,首先可以肯定的是一个独立完善的类结构,在一个类的内部除了方法与属性之外可以使用class来定义内部类。
我们简单的举一个例子:
class Outer{ //外部类
private String msg = "www.csdn.com"; //这是一个私有属性
public void fun() { //普通方法
Inner in = new Inner(); //实例化内部类对象
in.print(); //调用内部类方法
}
class Inner{ //内部类,在Outer类内部定义
public void print() {
System.out.println(Outer.this.msg); //msg是Outer类中的属性
}
}
}
public class first {
public static void main( String args[] ){
Outer out = new Outer(); //实例化外部类对象
out.fun(); //调用外部类方法
}
}
运行结果如下:
www.csdn.com
从整个代码的逻辑上来看其实并不难理解,甚至可以说其结构及其清晰明了。
但是为什么会有内部类这样的结构呢?因为从整体结构上来说,一个类中只需要有成员属性和方法就可以了,如果可以嵌套内部类,那么一个内部类之中就可以嵌套另一个内部类,所以说从整体的代码结构上来说内部类的结构不合理,所以内部类最大的缺陷就是破坏了程序的整体结构。
但同样的,有缺陷的同时也会有好处,内部类的好处是什么呢?如果要想更好观察出内部类的好处,就要将这个内部类拿到外边来:
class Outer{ //外部类
private String msg = "www.csdn.com"; //这是一个私有属性
public void fun() { //普通方法
Inner in = new Inner(); //实例化内部类对象
in.print(); //调用内部类方法
}
}
class Inner{ //内部类,在Outer类内部定义
public void print() {
System.out.println(Outer.this.msg); //msg是Outer类中的属性
}
}
将一个类变为两个类之后,这段代码是有明显错误的,如果还要再实现刚才的结果,就要思考几个问题:
- msg属性如果要被外部访问需要提供get()方法。
- 如果想要调用外部类中的get()方法,那么一定要有一个Outer类对象。
- Inner类对象实例化需要Outer类的引用,所以我们要想办法接收一个Outer类对象。
- 应该通过Inner类的构造方法来获取Outer类对象。
- 需要将Outer类对象out传递到Inner类对象中。
这些问题都是我们将一个类变为两个类之后却还要实现同样功能的时候要考虑到的,我们来尝试解决一下:
class Outer{ //外部类
String msg = "www.csdn.com"; //这是一个私有属性
public void fun() { //普通方法
//第五个问题:需要将当前对象out传递到Inner类对象中
Inner in = new Inner(this); //实例化内部类对象
in.print(); //调用内部类方法
}
//第一个问题:msg属性如果要被外部访问需要提供get()方法
public String getMsg() {
return this.msg;
}
}
class Inner{ //内部类,在Outer类内部定义
//第三个问题:Inner类对象实例化需要Outer类的引用
private Outer out;
//第四个问题:我们应该通过Inner类的构造方法来获取Outer类对象
Inner(Outer out) {
this.out = out;
}
public void print() {
//第二个问题:如果想要调用外部类中的get()方法,那么一定要有一个Outer类对象
System.out.println(this.out.msg); //msg是Outer类中的属性
}
}
public class first {
public static void main( String args[] ){
Outer out = new Outer(); //实例化外部类对象
out.fun(); //调用外部类方法
}
}
我们编译之后运行,发现结果没有问题,但是现在看看这段代码,如果将所有的注释去掉是不是有种看不懂的感觉,因为里面使用了大量的引用传递。可以发现,整体操作中,折腾了半天的主要目的就是为了让Inner这个内部类可以访问Outer类中的私有属性,但是如果不用内部类的时候整体代码非常的麻烦。
现在我们就知道了内部类的好处:轻松的访问外部类中的私有属性,这就是使用内部类的主要原因。
2.内部类相关说明
刚才我们认识到了内部类的优势以及相关结构,现在我们来对内部类进行一个使用的相关说明。
现在定义的内部类都是我们所说的普通的内部类形式,普通的内部类往往都会有提供成员属性和方法,需要注意的是,内部类虽然可以方便的访问外部类中的私有成员属性和私有方法,但是同样的外部类也可以轻松访问内部类的私有成员和私有方法。
我们来举个例子:
class Outer{ //外部类
private String msg = "www.csdn.com"; //这是一个私有属性
public void fun() { //普通方法
Inner in = new Inner(); //实例化内部类对象
in.print(); //调用内部类方法
System.out.println(in.info); //访问内部类的私有属性
}
class Inner{ //内部类,在Outer类内部定义
private String info = "这是一条来自内部类的消息!"; //这是内部类的私有属性
public void print() {
System.out.println(Outer.this.msg); //msg是Outer类中的属性
}
}
}
public class first {
public static void main( String args[] ){
Outer out = new Outer(); //实例化外部类对象
out.fun(); //调用外部类方法
}
}
上面的代码中fun()方法调用了内部类的私有属性,我们来运行一下,结果如下:
www.csdn.com
这是一条来自内部类的消息!
使用了内部类之后,内部类与外部类之间的私有访问的操作就不用set()、get()方法或其他的间接的方式来处理完成了,可以直接使用内部类来处理操作。
但要注意的是,内部类也是一个类,虽然大部分情况下内部类是被外部类包裹着的,但还是能通过外部类来产生内部类的实例化对象,此时内部类的实例化对象格式如下:
外部类.内部类 内部类对象 = new 外部类().new 内部类();
例:Outer.Inner in = new Outer().new Inner();
为什么要这样说呢?因为当我们编译这段代码的时候,会发现在我们的class文件中有一个“Outer$Inner.class”这样的class文件,而这个“$”转换到编译器中就是“.”,所以内部类的全称就是:“外部类.内部类”。内部类与外部类之间可以进行私有化成员的访问,这样一来内部类如果要提供实例化对象了,就必须要保证外部类也提供了实例化对象。
我们将刚才的例子中的代码放进程序中试一下:
public class first {
public static void main( String args[] ){
Outer.Inner in = new Outer().new Inner();
in.print();
//Outer out = new Outer(); //实例化外部类对象
//out.fun(); //调用外部类方法
}
}
结果如下:
www.csdn.com
如果我们想让内部类Inner只给外部类Outer独自使用,那么我们就使用private 来使Inner类变得私有:
class Outer{ //外部类
private String msg = "www.csdn.com"; //这是一个私有属性
public void fun() { //普通方法
Inner in = new Inner(); //实例化内部类对象
in.print(); //调用内部类方法
System.out.println(in.info); //访问内部类的私有属性
}
//注意这里使用了private
private class Inner{ //内部类,在Outer类内部定义
private String info = "这是一条来自内部类的消息!"; //这是内部类的私有属性
public void print() {
System.out.println(Outer.this.msg); //msg是Outer类中的属性
}
}
}
public class first {
public static void main( String args[] ){
Outer.Inner in = new Outer().new Inner();
in.print();
//Outer out = new Outer(); //实例化外部类对象
//out.fun(); //调用外部类方法
}
}
这时候再次编译,就报错了:
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
The type Outer.Inner is not visible
The type Outer.Inner is not visible
The type Outer.Inner is not visible
此时内部类只能通过外部类来使用,而不能通过外部使用。
在Java中类作为最基础的结构体实际上还有与之类似的抽象类或者是接口,抽象类或接口中都可以定义内部结构,比如我们来定义一个内部接口:
interface IChannel{ // 定义接口
public void send(IMessage msg); // 发送消息
interface IMessage{ // 内部接口
public String getContent(); // 获取消息内容
}
}
class ChannelImpl implements IChannel{
public void send(IMessage msg) {
}
}
大家看一下这段代码有没有错误?没有错误。那我们如何实现这些接口呢?来看一下下面的代码:
interface IChannel{ // 定义接口
public void send(IMessage msg); // 发送消息
interface IMessage{ // 内部接口
public String getContent(); // 获取消息内容
}
}
class ChannelImpl implements IChannel{
public void send(IMessage msg) {
System.out.println("send发送:" + msg.getContent());
}
class MessageImpl implements IMessage{
public String getContent() {
return "www.csdn.com";
}
}
}
public class first {
public static void main( String args[] ){
IChannel chl = new ChannelImpl();
chl.send(((ChannelImpl)chl).new MessageImpl());
}
}
我们运行一下,结果如下:
send发送:www.csdn.com
接下来我们再来看一下内部抽象类,内部抽象类可以定义在普通类、抽象类、接口内部类中。我们来举个例子:
interface IChannel{ // 定义接口
public void send(); // 发送消息
abstract class AbstractMessage{
public abstract String getContent();
}
}
class ChannelImpl implements IChannel{
public void send() {
AbstractMessage msg = new MessageImpl();
System.out.println(msg.getContent());
}
class MessageImpl extends AbstractMessage{
public String getContent() {
return "www.csdn.com";
}
}
}
public class first {
public static void main( String args[] ){
IChannel chl = new ChannelImpl();
chl.send();
}
}
运行结果如下:
www.csdn.com
内部类中还有一些更为有意思的结构:如果现在定义了一个接口,那么就可以在内部利用类来实现该接口,在JDK1.8之后接口中追加了static方法可以不受实例化对象的控制,现在就利用此特性来完成功能。
我们来举个例子,接口内部进行接口实现:
interface IChannel{ // 定义接口
public void send(); // 发送消息
class ChannelImpl implements IChannel{
public void send() {
System.out.println("www.csdn.com");
}
}
public static IChannel getInstance() {
return new ChannelImpl();
}
}
public class first {
public static void main( String args[] ){
IChannel chl = IChannel.getInstance();
chl.send();
}
}
运行结果如下:
www.csdn.com
理解内部类之后就可以灵活地使用,只要语法满足了各种需求都可以实现,后面的很多程序中都会使用内部类。
3.static定义内部类
如果说在内部类上使用static定义,那么这个内部类就变成了“外部类”,static定义都是独立于类的结构,所以说该类的结构就相当于一个独立的程序类了,但是要注意static定义的不管是类还是方法还是成员只能够访问static成员,所以static定义的内部类只能访问外部的static成员或者方法。我们举一个简单的例子:
class Outer{
public static final String MSG = "www.csdn.com";
static class Inner{
public void print() {
System.out.println(Outer.MSG);
}
}
}
此时的Inner类是一个独立的类,此时要想产生Inner类对象只需要根据“外部内.内部类”的结构来实例化即可,这个时候类名称带有“.”:
外部类.内部类 内部类对象 = new 外部类.内部类();
例:Outer.Inner in = new Outer.Inner();
class Outer{
public static final String MSG = "www.csdn.com";
static class Inner{
public void print() {
System.out.println(Outer.MSG);
}
}
}
public class first {
public static void main( String args[] ){
Outer.Inner in = new Outer.Inner();
in.print();
}
}
上述代码运行结果如下:
www.csdn.com
以后在开发过程中如果发现在类名称上带有“.”,应该立刻想到这是一个内部类;如果可以直接实例化,就说明是一个static定义的内部类。
如果以static定义内部类的形式并不常用,而使用static形式定义内部接口的形式是最为常用的:
interface IMessageWarp{ // 消息包装
static interface IMessage{
public String getContent();
}
static interface IChannel{
public boolean content(); // 消息发送通道
}
public static void send(IMessage msg, IChannel chl) {
if(chl.content()) {
System.out.println(msg.getContent());
}else {
System.out.println("发送失败!");
}
}
}
class DefaultMessage implements IMessageWarp.IMessage{
public String getContent() {
return"www.csdn.com";
}
}
class NetMessage implements IMessageWarp.IChannel{
public boolean content() {
return true;
}
}
public class first {
public static void main( String args[] ){
IMessageWarp.send(new DefaultMessage(), new NetMessage());
}
}
同样的,运行结果如下:
www.csdn.com
之所以使用static定义的内部接口,主要是这些操作是一组相关的定义,有了外部接口之后可以更加明确的描述这些接口的主要目的和主要功能,所以内部接口在日后开发过程中见到的会比较多。
4.方法中定义内部类
内部类可以在任意的结构中进行定义,包括了:类中,方法中,代码块中,但是从实际开发的角度来说,在方法中定义内部类的情况较多。我们来举个例子:
class Outer{
private String msg = "www.csdn.com";
public void fun(long time) {
class Inner { // 内部类
public void print() {
System.out.println(Outer.this.msg);
System.out.println(time);
}
}
new Inner().print();
}
}
public class first {
public static void main( String args[] ){
new Outer().fun(9527L);
}
}
此时在fun()方法只提供有Inner内部类的定义,并且发现内部类可以直接访问外部类中的私有属性,也可以直接访问方法中的参数,但是对于方法中的参数访问是从JDK1.8之后才开始支持的,因为JDK1.8提供了Lamda表达式,而在JDK1.8之前内部类要访问方法的参数,方法前必须加上final关键字。
现在我们的环境已经可以帮助优化处理,所以现在加不加final都可以,而这是为了Java扩展其函数式编程而做的准备。
5.匿名内部类
匿名内部类是一种简化的内部类形式,主要是在抽象类与接口的子类上使用。
我们举一个简单的例子:
interface IMessage{
public void send(String str);
}
class MessageImpl implements IMessage{
public void send(String str) {
System.out.println(str);
}
}
public class first {
public static void main( String args[] ){
IMessage msg = new MessageImpl();
msg.send("www.csdn.com");
}
}
这个程序相信大家都能看懂,如果现在IMessage接口中的子类MessageImpl只用一次,那么是否有必要将其定义为一个单独的类?如果按照这个要求来看,定义这个类确实是有些多余了,这个时候就可以使用匿名内部类来解决这个问题。
在上述的程序中,我们将子类MessageImpl的“名”去掉,直接使用它的主体:
interface IMessage{
public void send(String str);
}
public class first {
public static void main( String args[] ){
IMessage msg = new IMessage(){ // 这就是匿名内部类
public void send(String str) {
System.out.println(str);
}
};
msg.send("www.csdn.com");
}
}
运行结果仍然与上述结果相同。
有些时候为了更加方便的体现出匿名内部类的使用,往往可以利用静态方法做一个内部的匿名内部类实现:
interface IMessage{
public void send(String str);
public static IMessage getInstance() {
return new IMessage(){ // 这就是匿名内部类
public void send(String str) {
System.out.println(str);
}
};
}
}
public class first {
public static void main( String args[] ){
IMessage.getInstance().send("www.csdn.com");
}
}
与之前的内部类相比,匿名内部类是一个没有名字的,只能使用一次的,并且结构固定的子类操作。
今天我们整理了内部类的概念以及使用方法,Java系列也快结束了,下次我们将整理关于链表的知识点,我们下次见👋