一、为什么要使用内部类
1、内部类可以访问其外围类所有元素。
2、每个内部类都能独立继承一个接口的实现,或继承一个类,或者说外部类通过这种方式来实现“多重继承”。
学习了集合之后,现在我们有一个想法就是来创建一个属于自己的集合数组类MyArrayList,我们都知道集合类为了可以使用增强for,都必须实现Iterable接口。
public class MyArrayList<AnyType> implements Iterable<AnyType> {
private static final int DEFAULT_CAPACITY = 10; // 数组的容量
private int theSize; // 数组中的当前项数
private AnyType[] theItems; // 基础数组
@Override
public Iterator<AnyType> iterator() {
return null;
}
}
而实现Iterable接口必须实现iterator方法,该方法返回一个Iterator(迭代器模式的例子)接口的实例。我们一般想的就是那我们再写一个Iterator接口的实现类,然后再iterator方法中返回null改为该类实例就好啦。
// Iterator接口
public void Iterator<AnyType>{
boolean hasNext();
AnyType next();
}
public class ArrayListIterator implements Iterator {
private int current = 0; // 迭代序列中下一项的下标
MyArrayList list; // 数组集合的引用
@Override
public boolean hasNext() {
return current < list.theSize;
}
@Override
public Object next() {
if (!hasNext())
throw new NoSuchElementException();
return list.theItems[current++];
}
}
可是上面都报错了,原因是ArrayListIterator类中就算有数组集合的引用也访问不了MyArrayList类中的theSize和theItems啊,因为这些属性都是私有的。ok我改
- 把theSize和theItems改为public,但是这样破坏了封装性。pass
- 在数组类里开口子(get方法),这样迭代器类就可以访问了,但是如果要访问的变量很多那岂不是得写很多get方法。pass
- 不是,为什么数组类不能自己实现这个迭代器接口,然后返回自己的实例呢?是可以的,但是如果这个迭代器接口是抽象类呢?一个类可不能继承多个类,你这不是抬杠吗,Iterator明明是一个接口,ok,那如果Iterable接口和Iterator接口中有相同的方法呢,这在java中是不可以的。因为编译器根本就不知道该实现哪个方法。pass
那有什么方法既可以访问其他类的私有属性并且没有上面的烦恼呢?使用内部类
我们把ArrayListIterator作为MyArrayList的一个内部类,如下:
public class MyArrayList<AnyType> implements Iterable<AnyType> {
private static final int DEFAULT_CAPACITY = 10; // 数组的容量
private int theSize; // 数组中的当前项数
private AnyType[] theItems; // 基础数组
//内部类
public class ArrayListIterator implements Iterator {
private int current = 0; // 迭代序列中下一项的下标
@Override
public boolean hasNext() {
return current < theSize;
}
@Override
public AnyType next() {
if (!hasNext())
throw new NoSuchElementException();
return theItems[current++];
}
}
@Override
public Iterator<AnyType> iterator() {
return new ArrayListIterator();
}
}
可以看到将一个类的定义放在另一个类中这就是内部类。内部类可以访问外部类的私有属性,因为上面没有报错。为了测试我们给数组集合类添加add方法和构造器。(注意,这里并不是真的一个数组集合,因为不能动态改变数组的容量)
public MyArrayList(int size) {
// 使用泛型类型限界并且对类型数组进行转换
theItems = (AnyType[]) new Object[size];
}
public void add(AnyType e) {
if (theSize < theItems.length)
theItems[theSize++] = e;
}
测试:
public static void main(String[] args) {
MyArrayList list = new MyArrayList(5);
for (int i = 0; i < 5; i++) {
list.add(i);
}
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//foreach内部也是使用上面这种原理
// for (Object o : list) {
// System.out.println(o);
// }
}
输出:
0
1
2
3
4
那么,为什么内部类会自动拥有对其外部类所有成员的访问权呢?
原因是当new MyArrayList(5)实例化一个外部类对象的时候,再调用iterator方法new ArrayListIterator()再实例化一个内部类对象时,这个内部类对象必定会秘密捕获一个指向那个外部类的引用。然后当内部类访问外部类的成员时,就是用的这个外部类引用。
二、内部类的使用
- 如果想在内部类中获取一个外部类的引用,可以使用外部类.this,例如我们在内部类中创建一个返回外部类引用的方法:
public MyArrayList outer(){
return MyArrayList.this;
}
- 如果告诉某些对象去创建其内部类的对象时,可以是用.new如下:
public void main(String[] args){
MyArrayList list = new MyArrayList(5);
MyArrayList.ArrayListIterator iterator = list.new ArrayListIterator();
}
所以想要创建内部类对象,必须使用外部类的对象来创建内部类,而不是使用外部类,如果你的内部类是嵌套类(静态内部类),那么它就不需要外部类对象的引用。
三、嵌套内部类
如果不希望内部类对象与外部类对象之间有联系,那么可以将内部类声明为static,想要理解static嵌套类的含义就必须记住:
- 现在要创建一个嵌套内部类对象并不需要外部类对象而是使用外部类来创建。(注意没有了括号,而还是得是关键字new)
- 不能在嵌套内部类中创建外部非静态类对象
- 普通内部类不能有static数据与static字段或者嵌套类,但是嵌套内部类可以有。
看下面的例子:
class Out {
private static int age = 12;
static class In {
static int age = 13; //3、嵌套内部类可以有static数据
public void print() {
int age = 14;
System.out.println("局部变量:" + age); //输出14
System.out.println("内部类变量:" + this.age); //输出13
//下面报错,2、不能再嵌套内部类中创建非静态类对象
// System.out.println("外部类变量:" + Out.this.age); //输出12
}
}
}
public class Demo {
public static void main(String[] args) {
Out.In in = new Out.In(); //1、不再需要外部类对象,没有了new Out()
in.print();
}
}
- 如果你想创建某些公共代码,使得他们可以被实现该接口的不同实现所共用,那么就可以接口中创建嵌套内部类。
public interface IntefaceDemo {
void howdy();
class Test implements ClassInInterface {
public void howdy() {
System.out.println("howdy!");
}
public static void main(String[] args){
new Test().howdy();
}
}
}
输出:
howdy!
- 嵌套类又可以理解为在内部类里再嵌套一个内部类。可以看到就算你嵌套多少层,还是可以访问外部类的所有成员。
class A {
private void f() {
System.out.println("f()");
}
class B {
private void h() {
System.out.println("h()");
}
class C {
public void g() {
f();
h();
System.out.println("j()");
}
}
}
}
public class ABC {
public static void main(String[] args){
A a = new A();
A.B b = a.new B();
A.B.C c = b.new C();
c.g();
}
}
输出:
f()
h()
j()
四、私有内部类
如果一个内部类只希望被外部类中的方法操作,那么可以使用private声明内部类,下面的代码中,我们必须在Out类里面生成In类的对象进行操作,而无法再使用Out.In in = new Out().new In() 生成内部类的对象
也就是说,此时的内部类只有外部类可控制
class Out {
private int age = 12;
private class In {
public void print() {
System.out.println(age);
}
}
public void outPrint() {
new In().print();
}
}
public class Demo {
public static void main(String[] args) {
//此方法无效
/*
Out.In in = new Out().new In();
in.print();
*/
Out out = new Out();
out.outPrint();
}
}
但在java中凡事并非绝对的,我们可以通过内部类向上转型来访问private或protect内部类。
class Out {
private class In implements InIn { //私有内部类实现某接口
public void print() {
System.out.println("你好");
}
}
public InIn outPrint() {
return new In();
}
}
public class Demo {
public static void main(String[] args) {
InIn in = new Out().outPrint();
in.print();
}
}
通过这种方式可以完全组织任何依赖于类型的编码,并且完全隐藏了细节,而且也不能扩展接口中的方法。
五、方法内部类
我们也可以把内部类的定义写在方法里:
只是如果想往外部类的方法中传入参数,并且该参数会被内部类使用,那么该参数必须是final
class Out {
private int age = 12;
public void Print(final int x) {
class In {
public void inPrint() {
System.out.println(x);
System.out.println(age);
}
}
new In().inPrint();
}
}
public class Demo {
public static void main(String[] args) {
Out out = new Out();
out.Print(3);
}
}
六、匿名内部类
改写上面的例子,把内部类写在方法里并且使用匿名内部类,
这里需要注意的是:由于匿名内部类没有名字,所以不能写有参构造方法,只能往new Iterator(形参),这里的形参可以不需要final,因为该形参是传递给其基类的构造器的。
public Iterator<AnyType> iterator() {
return new Iterator() {
private int current = 0;
@Override
public boolean hasNext() {
return current < theSize;
}
@Override
public AnyType next() {
if (!hasNext())
throw new NoSuchElementException();
return theItems[current++];
}
};
}
参考:
https://www.cnblogs.com/nerxious/archive/2013/01/24/2875649.html
Java编程思想