CoreJava读书笔记--接口、lambda表达式、内部类(四)--内部类

内部类

什么是内部类?

其实就是定义在一个类中的另一个类。

为什么要使用内部类?

①内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据

②内部类可以对同一个包中的其他类隐藏起来

③当要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷

下面看一个简单的内部类示例:


public class InnerClassDemo {
	public static void main(String[] args) {
		Outer outer = new Outer();
		outer.method();
		
//		Inner in = new Inner();
		/*
		 * 直接创建内部类对象编译器会报错
		 */
		//创建内部类对象的正确方法
		Outer.Inner inner = new Outer().new Inner();
		inner.show();
	}
}
class Outer{//外部类
	private int num = 100;
	public class Inner{
		void show() {
			System.out.println("show run ..."+num);//内部类可以访问外部类的成员
			//通过外部类引用访问外部类属性
			System.out.println("Outer.this.num = "+Outer.this.num);
		}
	}
	
	public void method() {
		Inner in = new Inner();//外部类里面创建内部类对象,访问内部类成员方法
		in.show();
	}
}

(一)使用内部类访问对象状态

class TalkingClock{
	private int interval;//时间间隔
	private boolean beep;//开关铃声的标志
	
	public TalkingClock(int interval,boolean beep) {//外部类的构造方法
		this.interval = interval;
		this.beep = beep;
	}
	
	public void start() {
		ActionListener listener = new TimePrinter();//TimePrinter类的对象是由TalkingClock的方法构造
		Timer t = new Timer(interval,listener);
		t.start();
	}
	
	public class TimePrinter implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			System.out.println("At the tone,the time is "+new Date());
			if(beep) {//这里检查了beep标志
				Toolkit.getDefaultToolkit().beep();
			}
		}
		
	}
}

从上述代码中,我们可以看到内部类中访问了外部类的实例域beep。所以我们可以说:内部类既可以访问自身的数据域,也可以访问创建它的外围类对象的数据域。

事实上,内部类的对象总有一个隐式引用,它指向了创建它的外部类对象。我们假设外围类对象的引用叫做outer,那么内部类的actionPerformed方法可以等价于:

public void actionPerformed(ActionEvent event){
    System.out.println("At the tone,the time is "+new Date());
    if(outer.beep){
        Toolkit.getDefaultToolkit().beep();
    }
}

外部类的引用事实上在构造器中设置了。我们没有在内部类中添加构造器,但是编译器却为我们添加了一个含有外部类引用参数的构造器。其代码如下:

public TimePrinter(Talking clock){
    outer = clock;
}

当在start方法中创建了TimePrinter对象后,编译器就会将this引用传递给当前的语音时钟的构造器:

ActionListener listener = new TimePrinter(this);

假设我们没有将TimerPrinter设置为内部类,而仅仅是一个普通类,那么我们在访问beep实例域时,就需要通过TalkingClock的访问器来访问beep,而内部类对这种情况就实现了改进,即我们不需要提供仅用于访问其他类的访问器。

package innerClass;

import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;

import javax.swing.JOptionPane;
import javax.swing.Timer;

public class InnerClassTest {
	public static void main(String[] args) {
		TalkingClock tc = new TalkingClock(1000,true);
		tc.start();
		
		JOptionPane.showMessageDialog(null, "Quit program?");
		System.exit(0);
	}
	
}
class TalkingClock{
	private int interval;//时间间隔
	private boolean beep;//开关铃声的标志
	
	public TalkingClock(int interval,boolean beep) {//外部类的构造方法
		this.interval = interval;
		this.beep = beep;
	}
	
	public void start() {
		ActionListener listener = new TimePrinter();//TimePrinter类的对象是由TalkingClock的方法构造
		Timer t = new Timer(interval,listener);
		t.start();
	}
	
	public class TimePrinter implements ActionListener{

		@Override
		public void actionPerformed(ActionEvent e) {
			System.out.println("At the tone,the time is "+new Date());
			if(beep) {//这里检查了beep标志
				Toolkit.getDefaultToolkit().beep();
			}
		}
		
	}
}

(二)内部类的特殊语法规则

在上一节中,我们将外部类的引用叫做outer,事实上,使用外围类引用的正规语法应该是:

OuterClass.this

这表示外围类的引用,举个例子TimePrinter类的actionPerformed方法中访问了外部类的实例域beep,那么可以写成:

public void actionPerformed(ActionEvent e){
    System.out.println("At the tone, the time is "+new Date());
    if(TalkingClock.this.beep){
        Toolkit.getDefaultToolkit().beep();
    }
}

需要注意:在外围类的作用域之外,我们可以这样引用内部类:OuterClass.InnerClass。例如:

Outer.Inner inner = new Outer().new Inner();

(三) 局部内部类

当某个类创建的对象只使用了一次的时候,我们就可以在一个方法中定义局部内部类。

事实上,我们上个例子中所用到的TimePrinter类的对象只在start方法中使用了一次,那么我们就可以将它改写成局部内部类。

public void start() {
		class TimePrinter 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 TimePrinter();
		Timer t = new Timer(interval,listener);
		t.start();
	}

局部内部类不能使用public或private访问修饰符声明。它的作用域被限定在声明这个局部类的块中。局部内部类有一个优势就是,它对外部世界可以完全隐藏,即使是外部类中的其他代码也不能访问它。除了start方法,没有其他任何方法知道它的存在。

(四)由外部方法访问变量

在Java8之前,局部内部类可以访问局部变量,但是局部变量必须是被final修饰的,否则编译器不通过。而Java8之后,局部内部类访问局部变量可以不用显示的声明为final,编译器会将它默认为final。但是当你试图改变这个局部变量时(不管是在局部类的内部还是外部),编译器还是会报错。

(五)匿名内部类

想想局部内部类的使用情况是当某个类的对象只使用了一次时,我们可以在调用该对象的方法中编写局部内部类。那么当我们只需要创建一个这个类的对象,就可以不必命名了,这种类被称为匿名内部类。

通常的语法格式为:

new SuperType(construction parameter){
    inner class data and method
}

 SuperType可以是ActionListener这样的接口,那么我们就要在内部类中实现这个接口;SuperType也可以是一个类,那么我们就要在内部类中扩展它。

由于构造器的名字要与类名相同,而匿名内部类没有类名,所以匿名内部类不能有构造器。取而代之的是将参数传递给超类构造器。当内部类是实现接口的的时候,一定不能含有任何参数。

接下来,我们看看将上面的例子改写成匿名内部类:

package anonymousInnerClass;

import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;

import javax.swing.JOptionPane;
import javax.swing.Timer;

public class AnonymousInnerClass {
	public static void main(String[] args) {
		TalkingClock tc = new TalkingClock();
		tc.start(5000,true);
		
		JOptionPane.showMessageDialog(null, "Quit Program?");
		System.exit(0);
	}
}
class TalkingClock{
	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());
				if(beep) {
					Toolkit.getDefaultToolkit().beep();
				}
			}
			
		};
		Timer t = new Timer(interval,listener);
		t.start();
	}
}

我们可以看到,使用匿名内部类使得代码看起来更简洁。

(六)静态内部类

有时候使用内部类只是为了把一个类隐藏在另一个类的内部,并不需要内部类引用外部类的对象。为此可以将内部类声明为static,以取消产生的引用。

1.如果内部类是静态的,且成员方法是静态的,则访问该静态方法的方式可以为:Outer.Inner.function(); 

2.  如果内部类是静态的,相当于一个外部类,被内部类访问外部类的属性也需要为静态的

3.  如果内部类中定义了静态成员,该内部类也必须是静态的 

package StaticInnerClass;

public class StaticInnerClassDemo {
	public static void main(String[] args) {
		//创建静态内部类对象的正确方法
		Outer.Inner inner = new Outer.Inner();
		inner.show();
		//直接调用静态内部类的静态方法
		Outer.Inner.method();
	}
}
class Outer{
	private static int num = 100;//如果内部类是静态的,那么只能访问外部类的静态成员
	
	static class Inner{//如果内部类里定义了静态方法,那么内部类也必须定义为静态的
		public static void method() {//内部类定义了静态方法
			System.out.println("method方法被访问:"+num);
		}
		
		//内部类中定义了非静态方法
		public void show() {
			System.out.println("show  run :"+num);
			System.out.println("Outer.this.num : "+Outer.num);
		}
	}
}

我们看一个具体的例子,比较数组,得出数组中的最大值和最小值,普通时候我们需要编写两个方法,一个得出最大值,一个得出最小值,需要遍历两次数组。我们试试只遍历一次数组,得出最大值和最小值:

package StaticInnerClass;

public class StaticInnerClassTest {
	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 ap = ArrayAlg.minmax(d);
		System.out.println("min="+ap.getFirst());
		System.out.println("max="+ap.getSecond());
	}
}
class ArrayAlg{
	public static class Pair{
		private double first;
		private double second;
		
		public Pair(double f,double s) {
			this.first=f;
			this.second=s;
		}

		public double getFirst() {
			return first;
		}

		public double getSecond() {
			return second;
		}
		
		
		
	}
	public static Pair minmax(double[] values) {
		double min = Double.POSITIVE_INFINITY;
		double max = Double.NEGATIVE_INFINITY;
		
		for(double v : values) {
			if(min>v) {
				min=v;
			}
			if(max<v) {
				max=v;
			}
		}
		return new Pair(min,max);
	}
}

猜你喜欢

转载自blog.csdn.net/zxyy2627/article/details/82723703