Java中的包与类(2)-内部类

小tips:本文可能会缺乏一些关于类与对象更基础的知识点,如果你需要可以访问我的知乎:

Java中的包与类(2)

Java中的内部类

什么是类,什么是对象

面向对象的程序设计过程有两个重要概念:类和对象。对象是一个具体存在的实体,例如我们口中所说的人其实都是人类的实例,人类就是人的实例的抽象概念,即对象是类的实例。类中至少包括了类的成员变量和方法。比如在人类这个类中,头就是人类的成员变量,呼吸就是人类的方法。关于类与对象在下一篇文章中阐述。

类与对象在内存空间的情况

OneCow类的定义来看,这个类包含两个实例变量,变量是需要内存来存储的。因此,当创建OneCow对象时,必然需要有对应的内存来存储。OneCow one = new OneCow();这一行代码实际上创建了两个东西,一个变量one,一个实例化的OneCow类。one中包含一个指向OneCow的指针。只不过这个指针你无法像C那么灵活地去运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Cow {
public static void main(String[] args) {
OneCow one = new OneCow();
one.length = 100;
one.weight = 21;

OneCow two = one;

one.length = 22;
System.out.println(two.length);
}
}

class OneCow{
public int length;
public int weight;
}

运行代码可知最后输出地是22,这很自然,因为OneCow two = one;这段代码你以为是复制一个实例给two?其实并不是,只不过是two和one现在都拥有了同样的指针,同样指向了内存中的OneCow实例而已。

内部类

关于内部类的概念和一些代码可以看我之前的文章:内部类

这里重点关于内部类的作用域的问题。

1
2
class A{}
public class b{}

假设上面的代码是处于一个记事本中,但他们是互相独立的,没有谁是谁的内部类这种说法,内部类一定是放在另一个类的类体也就是花括号中的才算数。

  • 内部类可以访问外部类的private属性或方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    public class Cow {
    private double weight;

    //外部类的两个重载的构造器
    public Cow(){}
    public Cow(double weight){
    this.weight = weight;
    }
    //定义一个非静态内部类
    private class CowLeg{

    //非静态内部类的两个实例变量
    private double length;
    private String color;

    //非静态内部类的两个重构的构造器
    public CowLeg(){}
    public CowLeg(double length,String color){
    this.length = length;
    this.color = color;
    }

    public double getLength() {
    return length;
    }

    public void setLength(double length) {
    this.length = length;
    }

    public String getColor() {
    return color;
    }

    public void setColor(String color) {
    this.color = color;
    }

    //非静态内部类的实例方法
    public void info(){
    System.out.println("当前牛腿颜色是:"
    +color + ",高:" + length);

    //直接访问外部类的private修饰的成员变量
    System.out.println("本牛腿所在的奶牛重:"+weight);
    }
    }
    public void test(){
    CowLeg blackAndWhite = new CowLeg(1.12, "黑白相间");
    blackAndWhite.info();
    }

    public static void main(String[] args) {
    Cow cow = new Cow(378.9);
    cow.test();
    }
    }

    从上面的程序编译以后可以得到它的l两个class文件一个为Cow.clss另一个为Cow$CowLeg.class,运行的方法也非常简单,直接java Cow就行。编译运行以后你可以发现内部类可以直接访问外部类的私有变量private double weight;

  • 内部类可以有和外部类一样的属性名字,当在非静态内部类的方法内访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量,如果不存在,则到该方法的内部类中寻找,如果还是不存在就去外部类中寻找。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class DiscernVariable {
    private String prop = "外部类的实例变量";
    private class InClss
    {
    private String prop = "内部类的实例变量";
    public void info(){
    String prop = "局部变量";

    //通过外部类类名.this.varName访问外部类实例变量
    System.out.println("外部类的实例变量值:"+DiscernVariable.this.prop);

    //通过this.varName 访问内部类实例变量
    System.out.println("内部类的实例变量值:"+this.prop);

    //直接访问局部变量
    System.out.println("局部变量的值:"+prop);
    }
    }

    public void test(){
    InClss inClss = new InClss();
    inClss.info();
    }

    public static void main(String[] args) {
    new DiscernVariable().test();
    }
    }
  • 非静态内部类里不能有静态方法、静态成员变量、静态初始化块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class InnerNoStatic{
    private class InnerClass{
    /*
    下面三个静态声明都将引发如下编译错误:
    非静态内部类不能有静态声明
    */
    static{
    System.out.println("=======");
    }
    private static int inProp;
    private static void test(){}
    }
    }
  • 当你创建一个子类时,子类构造器总会调用父类的构造器,因此在我们创建非静态的内部类的子类时,必须保证让子类构造器可以调用非静态内部类的构造器,调用非静态内部类的构造器时,必须存在一个外部类对象。这样做很合理,我们调用内部类的时候,如果他不是静态的,就需要先有一个外部类的实例。

  • 当通过实现接口来创建匿名内部类时,匿名内部类不能显式地定义构造器,因此匿名内部类只有一个隐式地无参数构造器,故new接口名后地括号里不能传入参数值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    interface Product{
    double getPrice();
    String getName();
    }
    public class AnonymousTest{
    public void test(Product d){
    System.out.println("购买了一个"+d.getName()+",花掉了"+d.getPrice());
    }

    public static void main(String[] args){
    AnonymousTest ta = new AnonymousTest();

    //调用test()方法时们需要传入一个Product参数
    //此处传入其匿名实现类的实例
    ta.test(new Product(){
    public double getPrice(){
    return 567.8;
    }
    public String getName(){
    return "AGP显卡";
    }
    });
    }
    }

    但如果通过继承父类来创建匿名内部类时,匿名内部类将拥有和父类相似的构造器,此处的相似指的是拥有相同的形参列表。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    abstract class Device{
    private String name;
    public abstract double getPrice();
    public Device(){}
    public Device(String name){
    this.name = name;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }
    }

    public class AnonymousInner {
    public void test(Device d){
    System.out.println("购买了一个"+d.getName()+",花掉了"+d.getPrice());
    }

    public static void main(String[] args) {
    AnonymousInner anonymousInner = new AnonymousInner();

    //调用有参构造器创建Device匿名实现类的对象
    anonymousInner.test(new Device("电子示波器") {

    public double getPrice() {
    return 67.8;
    }
    });

    //调用无参构造器创建Device匿名实现类的对象
    Device device = new Device(){

    //初始化块
    {
    System.out.println("匿名内部类的初始化块...");
    }

    //实现抽象方法
    public double getPrice(){
    return 56.2;
    }

    //重写父类的实例方法
    public String getName(){
    return "键盘";
    }
    };
    anonymousInner.test(device);
    }
    }