Java内部类

一.概述

上一篇讲了lambda表达式,它可以让编码量降低,但不要忘记保持代码的可读性。链接:Java之lambda表达式
这篇讲内部类,为什么使用内部类呢?

  • 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
  • 内部类可以对同一个包中的其它类隐藏起来。
  • 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。

二.使用内部类访问对象状态

1.基本使用

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;

/**
 * 内部类访问对象状态
 */
public class InnerClassDemo {
	public static void main(String[] args) {
		TalkingClock clock = new TalkingClock(1000,true);
		clock.start();

		JOptionPane.showMessageDialog(null,"Quit program?");
		System.exit(0);

		// 创建内部类对象
		TalkingClock.TimerPrinter printer = clock.new TimerPrinter();
	}
}

class TalkingClock{
	/**
	 * 时间间隔
	 */
	private int interval;
	/**
	 * 开关铃声
	 */
	private boolean beep;

	public TalkingClock(int interval, boolean beep) {
		this.interval = interval;
		this.beep = beep;
	}
	public void start(){
		// 当外围类创建内部类的时候,会将自身的引用this传递给内部类
//		ActionListener listener = new TimerPrinter(this);
		// 正确的编写创建内部类的语法
//		ActionListener listener = this.new TimerPrinter();
		ActionListener listener = new TimerPrinter();
		Timer t = new Timer(interval,listener);
		t.start();
	}

	/**
	 * 内部类
	 * 它拥有一个对外部类的引用,这个引用是不可见的.
	 * 编译器修改了所有的内部类的构造器,添加一个外围类引用的参数。
	 * 只有内部类可以将访问控制权限改为private这样代表只有TalkingClock类创建该类对象
	 */
	public class TimerPrinter implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			System.out.println("At the tone,the time is" + new Date());
			// 可以直接访问外围类对象的数据域
//			TalkingClock.this.beep 可以简写
			if (beep) {
				Toolkit.getDefaultToolkit().beep();
			}
		}
	}
}

2.语法规则

内部类中声明的所有静态域都必须是final。因为如果这个域不是final的,它可能就不是唯一的。
内部类不能有static方法。也可以允许有静态方法,但只能访问外围类的静态域和方法。

3.内部类信息和安全

内部类是一种编译现象,与虚拟机无关。编译器会把内部类翻译成用$分隔外部类名与内部类名的常规类文件,而虚拟机则对此一无所知。

// 测试内部类的具体信息
		Class c1 = TalkingClock.TimerPrinter.class;
		// 调用反射章节的封装方法
		ReflectDemo.getClassInfo(c1);
		// 结果
//		public class com.gyx.demo.chapter11.TalkingClock$TimerPrinter
//		{
//   			public com.gyx.demo.chapter11.TalkingClock$TimerPrinter(com.gyx.demo.chapter11.TalkingClock);
//
//   			public void actionPerformed(java.awt.event.ActionEvent actionPerformed );

//				编译器为了引用外部类会自动生成一个实例域(名字由编译器合成,自己编写的代码不能使用)
//   			final com.gyx.demo.chapter11.TalkingClock this$0 
//		}

		Class c2 = TalkingClock.class;
		// 调用反射章节的封装方法
		ReflectDemo.getClassInfo(c2);
		// 结果
//		class com.gyx.demo.chapter11.TalkingClock
//		{
//  		public com.gyx.demo.chapter11.TalkingClock(int, boolean);
//			
			// 编译器为外围类添加的静态方法,将返回作为参数返回外围类的beep
//			static boolean access$000(com.gyx.demo.chapter11.TalkingClock);
//			在进行判断时if (TalkingClock.access$000(TalkingClock)) 替换成 if (beep)来提高效率
		
//			public void start();
//
//			private int interval
//			private boolean beep
//		}

这样做会有安全问题,虽然access$000名字是编译器取的但同样可以使用来调用外部类的私有属性。这个方法是包可见性的,所以攻击代码可以放在同一个包中。

三.内部类的种类

1.局部内部类

放在方法中的内部类

public void start() {
		// 如果内部类只被一个方法使用,可以使用局部内部类
		// 局部内部类不能用public或private修饰符
		// 优势:只对此方法可见,对外部完全隐藏
		class TimerPrinter implements ActionListener {
			@Override
			public void actionPerformed(ActionEvent e) {
				System.out.println("At the tone,the time is" + new Date());
				if (beep) {
					Toolkit.getDefaultToolkit().beep();
				}
			}
		}	
		ActionListener listener = new TimerPrinter();
		Timer t = new Timer(interval, listener);
		t.start();
	}

2.由外部方法访问变量

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;

/**
 * 访问方法的局部变量
 */
public class InnerClassDemo1 {
	public static void main(String[] args) {
		TalkingClock1 clock = new TalkingClock1();
		clock.start(1000,true);

		JOptionPane.showMessageDialog(null, "Quit program?");
		System.exit(0);
		
	}
}

class TalkingClock1 {
	// 在Java8之前要想使用局部变量要将beep声明为final类型
	public void start(int interval,boolean beep) {

		class TimerPrinter implements ActionListener {

			@Override
			public void actionPerformed(ActionEvent e) {
				System.out.println("At the tone,the time is" + new Date());
				// 可以访问start方法中的局部变量
				if (beep) {
					Toolkit.getDefaultToolkit().beep();
				}
			}
		}
		// 在创建内部类对象的时候,内部类中会产生一个boolean类型的实例域,并把start方法中的局部变量传递给它并使用。
		ActionListener listener = new TimerPrinter();
		Timer t = new Timer(interval, listener);
		t.start();
	}
}

3.匿名内部类

假如只创建内部类的一个对象,就可以不必命名直接使用。

/**
 * 匿名内部类
 */
public class InnerClassDemo2 {
	public static void main(String[] args) {
		TalkingClock2 clock = new TalkingClock2();
		clock.start(1000,true);

		JOptionPane.showMessageDialog(null, "Quit program?");
		System.exit(0);
		
		// 双括号初始化 这个功能利用的是内部类 但是这样会使代码没有可读性,不推荐使用
		clock.add(new ArrayList<String>(){{add("Harry");}});
	}
}

class TalkingClock2 {
	
	
	public void add(ArrayList<String> list) {
		
	}
	public void start(int interval,boolean beep) {

		// 匿名内部类
		// 因为匿名所以不能有构造器。
		ActionListener listener = new ActionListener() {
			// 匿名内部类
			// 因为匿名所以不能有构造器。
			@Override
			public void actionPerformed(ActionEvent e) {
				System.out.println("At the tone,the time is" + new Date());
				// 可以访问start方法中的局部变量
				if (beep) {
					Toolkit.getDefaultToolkit().beep();
				}
			}
		};
		// 使用lambda表达式更加简洁推荐使用
		ActionListener listener1 = e -> {
			System.out.println("At the tone,the time is" + new Date());
			// 可以访问start方法中的局部变量
			if (beep) {
				Toolkit.getDefaultToolkit().beep();
			}
		};
		
		Timer t = new Timer(interval, listener);
		t.start();
	}
}

4.静态内部类

有时候我们只是为了把一个类隐藏在另外一个类的内部,并不需要内部类引用外围类对象。

/**
 * 静态内部类
 *
 */
public class StaticInnerClassDemo {
	public static void main(String[] args) {
		double[] d = new double[20];
		for (int i = 0; i < d.length; i++) {
			d[i] = 100 * Math.random();
		}
		ArrayAlg.Pair p = ArrayAlg.minmax(d);
		System.out.println("min = " + p.getFirst());
		System.out.println("max = " + p.getSecond());
	}
}

class ArrayAlg {

	/**
	 * 静态内部类
	 */
	public static class Pair {

		private double first;
		private double second;

		public Pair(double first, double second) {
			this.first = first;
			this.second = second;
		}

		public double getFirst() {
			return first;
		}

		public double getSecond() {
			return second;
		}
	}

	/**
	 * 因为需要将大数和小数一起返回,所以需要封装成一个对象,并且希望这个对象和定义其它类的对象有所区分
	 * @param values
	 * @return
	 */
	public static Pair minmax(double[] values) {
		double min = Double.POSITIVE_INFINITY;
		double max = Double.NEGATIVE_INFINITY;
		for (double value : values) {
			if (max < value) {
				max = value;
			}
			if (min > value) {
				min = value;
			}
		}
		// 由于静态方法中需要创建内部类对象,所以内部类必须是static的
		return new Pair(min, max);
	}
}

四.总结

内部类可以更方便的进行类之间的访问,并且比常规类更灵活。但是也存在一定的安全问题。
下一篇讲代理的基本使用
有些可能我理解的不够深刻,大家如果觉得我说的不够详细可以参考我的推荐书,详细的看一下。欢迎大家评论。第一时间我会回复大家。谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值