写点什么

Java 类的特性之内部类

作者:未见花闻
  • 2022 年 7 月 13 日
  • 本文字数:5681 字

    阅读完需:约 19 分钟

1.内部类概念及分类

将一个类定义在另一个类的内部或者接口内部或者方法体内部,这个类就被称为内部类,我们不妨将内部类所在的类称为外围类,除了定义在类,接口,方法中的内部类,还有一种特殊的内部类,那就是使用关键字new创建一个匿名类的对象,而这个匿名类其实就是一个内部类,具体说是一个匿名内部类,经常用于传入构造器实参构造对象,例如PriorityQueue对象的创建。


一个类定义在另一个类的内部或者接口内部,并且没有static修饰时,这个内部类就称为实例内部类,使用static修饰时,这个内部类被称为静态内部类(嵌套类),将一个类定义在代码块内部,特别是方法体内部,这个内部类被称为本地内部类或者局部内部类,还有一种就是上面所说的匿名内部类

2.实例内部类

2.1 实例内部类的创建

实例内部类的定义很简单,就直接定义在外围类的里面就可以了。问题是如何创建一个内部类对象,首先,需要明白内部类与外围类中的成员变量与方法是平起平坐的,都是属于外围类对象的(无static修饰),所以想要内部类对象先得有外围类对象,第一种创建内部类对象的方式就是使用一个方法返回一个内部类对象,并且这个内部类对象的类型为OutClassName.InnerClassName,即外围类名.内部类名


public class Outer {
class Inner1 { private String str; public Inner1(String s) { this.str = s; } public String readStr() { return this.str; } }
class Inner2 { private int val; public Inner2(int i) { this.val = i; } public int readVal() { return this.val; } } //创建内部类 public Inner1 creatInner1(String s) { return new Inner1(s); } public Inner2 creatInner2(int i) { return new Inner2(i); }
public static void main(String[] args) { Outer outer = new Outer(); Outer.Inner1 inner1 = outer.creatInner1("Inner1"); Outer.Inner2 inner2 = outer.creatInner2(2);
System.out.println(inner1.readStr()); System.out.println(inner2.readVal()); }}
复制代码


//output:Inner12
Process finished with exit code 0
复制代码

2.2 使用.this 和.new

当然,创建一个内部类还有其他的方法,就是使用外围类的对象.new来创建一个内部类对象,语法为:


外部类对象.new 内部类构造方法;
复制代码


public class Outer {
class Inner1 { private String str; public Inner1(String s) { this.str = s; } public String readStr() { return this.str; } }
class Inner2 { private int val; public Inner2(int i) { this.val = i; } public int readVal() { return this.val; } }

public static void main(String[] args) { Outer outer = new Outer(); Outer.Inner1 inner1 = outer.new Inner1("Inner1"); Outer.Inner2 inner2 = outer.new Inner2(2);
System.out.println(inner1.readStr()); System.out.println(inner2.readVal()); }}
复制代码


//output:Inner12
Process finished with exit code 0
复制代码


内部类的特性不止如此,内部类还能与外围类链接,首先实例内部类对象的创建是依赖外围类对象的引用,实例内部类与外围类的成员变量地位一样,如果想要通过内部类对象访问外围类的成员变量与方法(特别是同名变量)。在内部类中,可以使用外围类名.this获取外围类对象,然后使用获得的这个外围类对象来使用外围类的变量与方法。


public class Outer {    private String outStr;    public Outer(String str) {        this.outStr = str;    }
public void printOuter() { System.out.println(this.outStr); }
class Inner { private String str; public Inner(String s) { this.str = s; } public String readStr() { return this.str; } //使用.this获取父类对象引用 public Outer outer() { return Outer.this; } }


public static void main(String[] args) { Outer outer = new Outer("outerString"); Outer.Inner inner = outer.new Inner("innerString");
Outer out = inner.outer(); out.printOuter(); }}
复制代码


//output:outerString
Process finished with exit code 0
复制代码


其实内部类对象中有两个this,直接使用this.成员获取的是内部类对象自己的成员,而像上面使用外围类名.this.成员获取的是外围类的成员,也就是说在内部类中this指向内部类对象自己的引用,外部类名.this指向外围类对象的引用。当出现内部类与外围类变量名相同时,这两个this就有很大的用处了。


public class Outer {    public int a = 12;    public int b = 16;    public int c = 20;
class Inner{ public int a = 8; public int c = 48; public int d = 2;
public void printVal() { System.out.println("外围类变量a=" + Outer.this.a); System.out.println("外围类变量b=" + Outer.this.b); System.out.println("外围类变量c=" + Outer.this.c); System.out.println("内部类变量a=" + this.a); System.out.println("内部类变量c=" + this.c); System.out.println("内部类变量d=" + this.d); } }
public Inner creatInner() { return new Inner(); }
public static void main(String[] args) { Outer outer = new Outer();
Outer.Inner inner = outer.creatInner(); inner.printVal(); }}
复制代码


//output:外围类变量a=12外围类变量b=16外围类变量c=20内部类变量a=8内部类变量c=48内部类变量d=2
Process finished with exit code 0
复制代码


如果没有出现同名,直接获取的成员即可,可以不用使用上面所说的this,出现同名,直接访问的成员默认是内部类对象的。

2.3 内部类实现迭代打印

内部类可以与外围类的所有元素的访问权限,所以我们可以尝试使用内部类来遍历输出外围类中的元素,虽然内部类与外围类的成员地位是平等,但内部类毕竟也是类,它也可以像外围类一样继承类和实现接口。


import java.util.Arrays;
interface Selector{ //判断是否迭代完全部元素 public boolean end(); //获取当前元素 public Object current(); //迭代下一个元素 public void next();}
public class SequeArray { private Object[] items; private int usedSize;
public SequeArray(int capacity) { items = new Object[capacity]; } public void add(Object val) { if (usedSize == items.length) items = Arrays.copyOf(items, 2 * usedSize);
items[usedSize++] = val; }
private class SequeSelector implements Selector{ private int index;
public boolean end() { return index == usedSize; } public Object current() { return items[index]; } public void next() { if (index < usedSize) index++; } } public SequeSelector sequeSelector() { return new SequeSelector(); }
public static void main(String[] args) { SequeArray array = new SequeArray(10);
for (int i = 0; i < 10; i++) { array.add(i+1); } //发生了向上转型 Selector selector = array.sequeSelector();
while (!selector.end()) { System.out.print(selector.current() + " "); selector.next(); } }}
复制代码


//output:1   2   3   4   5   6   7   8   9   10   Process finished with exit code 0
复制代码

2.4 内部类的继承

内部类的继承有一点麻烦,因为内部类的构造器必须依赖外围类的引用,所以需要一段特殊的语法来说明内部类与外围类的关联:


外围类引用.super();
复制代码


具体怎么使用看如下一段代码:


public class Outer {    class Inner{            }}class Person extends Outer.Inner {    private String str;    private int id;    public Person(Outer out, String str, int id) {        out.super();        this.str = str;        this.id = id;    }    public void readVal() {        System.out.println(this.str + this.id);            }}
复制代码


当内部类被继承的时候,需要通过外围类的引用传入父类的构造方法中并调用super()才能通过编译。如果内部类继承外部类,直接继承即可,不需要这么麻烦。


程序生成一个java文件的字节码文件时,命名为公共外部类$内部类。内部类可以无限嵌套,一般没人这么干。

3.静态内部类

静态内部类也称嵌套类,它就是在实例内部类的定义前加入一个static关键字,像下面这样:


public class Outer {        static class Inner{            }}
复制代码


与实例内部类的区别是静态内部类是属于类的而不是属于对象的,因此静态内部类的创建不依赖与外部类对象,这是和实例内部类最大的区别之一。


public class Outer {    static class Inner{        private String str;        public Inner(String s) {            str = s;        }    }
public static void main(String[] args) { Outer.Inner inner = new Outer.Inner("我是静态内部类!"); System.out.println(inner.str); }}
复制代码


//output:我是静态内部类!
Process finished with exit code 0
复制代码

4.匿名内部类

匿名内部类的用法我们已经见过了,就是创建 PriorityQueue 对象时,默认创建的是小堆,需要传入比较器来创建大堆。


        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Comparator<Integer>() {            @Override            public int compare(Integer o1, Integer o2) {                return o1 - o2;            }        });
复制代码


像上面这种在方法中使用new关键字创建一个匿名类的对象作为实参,里面这个匿名类就是匿名内部类,创建过程中的Comparator匿名类对象是继承所需传参类型的,在这里也就是Comparator类。如果使用自定义类型传入优先队列,是一定要实现比较器的,实现比较器最常见的方式就是匿名内部类与 Lambda 表达式。


假设我有一个Person类数组,需要对他们的name排序,如果不使用内部类,就需要在Person类中实现比较器。


import java.util.Arrays;import java.util.Comparator;
class Preson { public String name; public Integer id; public Preson(String name, Integer id) { this.name = name; this.id = id; }
@Override public String toString() { return "Preson{" + "name='" + name + '\'' + ", id=" + id + '}'; }}class PersonComparator implements Comparator<Preson> { @Override public int compare(Preson o1, Preson o2) { return o1.name.compareTo(o2.name); }}public class Main { public static void main(String[] args) { Preson preson1 = new Preson("aboluo", 24); Preson preson2 = new Preson("zhousi", 25); Preson preson3 = new Preson("syyfjy", 2); Preson[] presons = {preson1, preson2, preson3}; Arrays.parallelSort(presons, new PersonComparator()); System.out.println(Arrays.toString(presons)); }}
复制代码


//output:[Preson{name='aboluo', id=24}, Preson{name='syyfjy', id=2}, Preson{name='zhousi', id=25}]
Process finished with exit code 0
复制代码


使用内部类上述代码就变为:


import java.util.Arrays;import java.util.Comparator;
class Preson { public String name; public Integer id; public Preson(String name, Integer id) { this.name = name; this.id = id; }

@Override public String toString() { return "Preson{" + "name='" + name + '\'' + ", id=" + id + '}'; }}
public class Main { public static void main(String[] args) { Preson preson1 = new Preson("aboluo", 24); Preson preson2 = new Preson("zhousi", 25); Preson preson3 = new Preson("syyfjy", 2); Preson[] presons = {preson1, preson2, preson3}; Arrays.parallelSort(presons, new Comparator<Preson>() { @Override public int compare(Preson o1, Preson o2) { return o1.name.compareTo(o2.name); } }); System.out.println(Arrays.toString(presons)); }}
复制代码


//output:[Preson{name='aboluo', id=24}, Preson{name='syyfjy', id=2}, Preson{name='zhousi', id=25}]
Process finished with exit code 0
复制代码


还有一个本地内部类,就是类定义在方法中,就不多说了,基本上不用,用法如下:


public class Outer {    public void func() {        class Inner{            //        }    }}
复制代码


发布于: 刚刚阅读数: 3
用户头像

未见花闻

关注

坚持+努力=诗+远方 2021.11.15 加入

一位热爱技术热爱分享的大学生!

评论

发布
暂无评论
Java类的特性之内部类_7月月更_未见花闻_InfoQ写作社区