程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

Java基础 -- 嵌套类(非静态嵌套类、静态嵌套类)

可以将一个类的定义放在另一个类的内部定义,这样的类就被称为嵌套类,包含嵌套类的类被称为外部类(outer class),也可以叫做封闭类。

嵌套类可以分为两种:

  • 静态嵌套类(Static Nested Classes):使用static声明,一般称为嵌套类(Nested Classes);
  • 非静态嵌套类(Non-static Nested Classes):非static声明,一般称为内部类(Inner Classes);

嵌套类是它的外部类的成员。非静态嵌套类(内部类)可以访问外部类的其他成员,即使该成员是私有的,而静态嵌套类只能访问外部类的静态成员。

嵌套类作为外部类的一个成员,可以被声明为:private,包访问,protected,public(注意:外部类只能被声明为public或者包范围)。

使用嵌套类的主要优点有以下几个方面:

  • 嵌套类可以访问外部类的数据成员和方法,即使它是私有的;
  • 提高可读性和可维护性:因为如果一个类只对另外一个类可用,那么将它们放在一起,这更便于理解和维护;
  • 提高封装性:给定两个类A和B,如果需要访问A类中的私有成员,则可以将B类封装在A类中,这样不仅可以使得B类可以访问A类中的私有成员,并且可以在外部隐藏B类本身;
  • 减少代码的编写量;

静态嵌套类可以使用外部类的名称来访问它。例如:

1
OuterClass.StaticNestedClass

 如果要为静态嵌套类创建对象,则使用以下语法:

1
2
OuterClass.StaticNestedClass nestedObject =
        new OuterClass.StaticNestedClass();

非静态嵌套类包含三种:

  • 成员内部类(Member inner clss);
  • 局部内部类(Local inner class);
  • 匿名内部类(Anonymous inner class);

Nested Classes

 描述
成员内部类 在类中和方法外部创建的类
匿名内部类 为实现接口或扩展类而创建的类,它的名称由编译器决定
局部内部类 在方法内部创建的类
静态嵌套类 在类中创建的静态类
嵌套接口 在类中或接口中创建的接口

 

 

 

 

 

一 非静态嵌套类(内部类)

非静态嵌套类也就是内部类,它有以下几个特点:

  • 实例化内部类必须先实例化一个外部类;
  • 内部类实例与外部类实例相关联(内部类可以访问外部类的成员),所有不能在内部类中定义任何静态成员;
  • 内部类是非静态的;

1、成员内部类

示例一

在外部类中并且在外部类的方法外创建的非静态嵌套类,称为成员内部类。说白了成员内部类就是外部类的一个非静态成员而已。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Parcel1 {
    class Contents{
        private int i = 11;
        public int value() {return i;}
    }
    class  Destination{
        private String label;
        Destination(String whereTo){
            label = whereTo;
        }      
        String readLable() {return label;}
    }  
    public void ship(String dest) {
        Contents c = new Contents();
        Destination d = new Destination(dest);
        System.out.println(d.readLable());
    }  
    public static void main(String[] args) {
        Parcell p = new Parcel1();
        p.ship("Zhengy");
    }
}

在ship()方法中使用成员内部类,与使用普通类没有什么区别,在这里,实际的区别就是内部成员类的定义是嵌套在Parcel1中。

示例二

下面我们尝试在外部类中定义一个方法,该方法返回一个指向成员内部类的引用。

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
public class Parcel2 {
    class Contents{
        private int i = 11;
        public int value() {return i;}
    }  
    class  Destination{
        private String label;
        Destination(String whereTo){
            label = whereTo;
        }      
        String readLable() {return label;}
    }      
    public Destination to(String s) {
        return new Destination(s);
    }  
    public Contents contents() {
        return new Contents();
    }      
    public void ship(String dest) {
        Contents c = new Contents();
        Destination d = new Destination(dest);
        System.out.println(d.readLable());
    }  
    public static void main(String[] args) {
        Parcel2 p = new Parcel2();
        p.ship("南京");      
        Parcel2 q = new Parcel2();
        Parcel2.Destination d = q.to("北京");
        Parcel2.Contents c = q.contents();
    }
}

 如果想在类外创建某个成员内部类的对象,必须像main()方法那样,具体指明这个对象的类型:OuterClassName.InnerClassName。

示例三

当创建一个成员内部类的对象时,此对象与创建它的外部类对象之间就有了一种联系,所以它能够访问其外部类对象的所有成员,而不需要任何特殊条件。下面的例子说明了这一点:

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
//类似迭代器
interface Selector{
    boolean end();    //判断序列是否迭代结束
    Object current();  //获取当前元素
    void next();       //下一个元素
}
//序列类(外部类)
public class Sequence {
    //数组用于保存元素
    private Object[] items;
    //当前追加索引
    private int next = 0;
    public Sequence(int size) {items = new Object[size];}  
    //向数组追加元素
    public void add(Object x) {
        if(next < items.length)
            items[next++] = x;
    }      
    //成员内部类:迭代器(可以访问外部类对象的所有成员),这里用来迭代序列中所有元素
    private class SequenceSelector implements Selector{
        private int  i = 0;
        public boolean end() {return i == items.length;}
        public Object current() {return items[i];}
        public void next() {
            if(i < items.length)
                i++;
        }
    }  
    //创建迭代器对象
    public Selector selector() {
        return new SequenceSelector();
    }  
    public static void main(String[] args) {
        Sequence sequence = new Sequence(10);
        for(int i=0;i<10;i++) {
            sequence.add(Integer.toString(i));
        }
         
        Selector selector = sequence.selector();
        while(!selector.end()) {
            System.out.print(selector.current() + "  ");
            selector.next();
        }      
    }
}

 输出结果如下:

1
0  1  2  3  4  5  6  7  8  9 

上面演示了一个:"迭代器"设计模式的例子。

示例四

在成员内部类中如果需要创建对外部类对象的引用,可以使用外部类的名字后面跟着.this。下面演示如何使用.this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
///使用.this
public class DotThis {
    void f() {
        System.out.println("DotThis.f()");
    }  
    //成员内部类
    public class Inner{
        public DotThis outer() {
            return DotThis.this;
        }
    }  
    public Inner inner() {
        return new Inner();
    }  
    public static void main(String[] args) {
        DotThis dt = new DotThis();
        DotThis.Inner dti = dt.inner();
        dti.outer().f();
    }
}

 输出如下:

1
DotThis.f()

示例五

想要直接创建成员内部类的对象,不可以直接引用外部类的名字,而是必须使用外部类的对象来创建该成员内部类对象。在拥有外部类对象之前是不可能创建成员内部类对象的,这是因为成员内部类对象会暗暗地连接到创建它的外部类对象上。

但是,如果创建的是静态内部类,那么它就不需要对外部类对象的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
///.new
public class DotNew {
    public class Inner{
        public void f(){
            System.out.print("test");
        }
    }
    public static void main(String[] args) {
        DotNew dn = new DotNew();
        DotNew.Inner dni = dn.new Inner(); 
        dni.f();
    }
}

示例六

当将成员内部类(private修饰)向上转型为其基类,尤其是转为一个接口(public或者"包访问")时,由于成员内部类使用private修饰,因此可以隐藏其内部的具体实现,但是由于把成员内部类声明为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
interface Destination{
    String readLabel();
}
interface Contents{
    int value();
}
class Parcel4{
    //私有类,隐藏其具体实现
    private class PContents implements Contents{
        private int i = 11;
        public int value() {return i;}     
    }      
    protected class PDestination implements Destination{
        private String label;
        private PDestination(String whereTo) {
            label = whereTo;
        }      
        public String readLabel(){
            return label;
        }              
    }
    public Destination destination(String s) {
        return new PDestination(s);
    }  
    public Contents contents(){
        return new PContents();
    }
}
public class TestParcel {
    public static void main(String[] args) {
        Parcel4 p = new Parcel4();
        Contents c = p.contents();
        Destination d = p.destination("北京");
    }
}

示例七

一个成员内部类被嵌套多少层并不重要,它能透明的访问所有它所嵌入的外部类的所有成员,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MNA{
    private void f() {}
    //第一个内部类
    class A{
        private void g() {}
        //第二个内部类
        public class B{
            void h() {
                g();
                f();
            }
        }
    }
}
public class MultiNestingAccess {
    public static void main(String[] args) {
        MNA mna = new MNA();
        MNA.A mnaa = mna.new A();
        MNA.A.B mnab = mnaa.new B();
    }
}

成员内部类的工作方式:

Java每个类都会产生一个.class文件,所以Java 编译器还会创建一个成员内部类OuterClass$MemberInnerClass.class,成员内部类文件名格式为外部类名$成员内部类名

2 、局部内部类

局部内部类(Local inner class)通常定义在一个块中。所以通常你会在一个方法块中找到局部内部类。

正如局部变量那样,局部内部类的作用域受到方法的限制。它可以访问外部类的所有成员,和它所在的局部方法中所有局部变量。

如果你想调用局部内部类中的方法,你必须先在局部方法中实例化局部内部类。

局部内部类的一些规则:

  • 无法从方法外部调用局部内部类;
  • 局部内部类不能被声明为private, public, protected;
  • JDK1.7之前局部内部类不能访问非final的局部变量,但是在JDK1.8及之后是可以访问非final的局部变量的;

示例一

在一个方法中定义一个局部内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Parcel5 {
    public Destination destination(String s) {
        class PDestination implements Destination{
            private String label;
            private PDestination(String whereTo) {
                label = whereTo;
            }          
            public String readLabel(){
                return label;
            }
        }
        return new PDestination(s);
    }  
    public static void main(String[] args) {
        Parcel5 p = new Parcel5();
        Destination d = p.destination("北京");
    }
}

示例二

下面我们尝试在一个作用域内定义一个局部内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Parcel6 {
    private void internalTracking(boolean b) {
        if(b) {
            class TrackingSlip{
                private String id;
                TrackingSlip(String s){
                    id = s;
                }
                String getSlip() {
                    return id;
                }
            }
            TrackingSlip ts = new TrackingSlip("slip");
            String s = ts.getSlip();
        }
    }  
    public void track() {
        internalTracking(true);
    }  
    public static void main(String[] args) {
        Parcel6 p = new Parcel6();
        p.track();
    }
}

TrackingSlip类被嵌入在if语句的作用域中,这并不是说该类的创建是有条件的,它其实与别的类一起编译过了。然而,在定义TrackingSlip的作用域之外,它是不可用的,除此之外,它和普通的类一样。

局部内部类的工作方式:

Java每个类都会产生一个.class文件,所以Java 编译器还会创建一个名为localInner$Local.class的文件。

3、匿名内部类

在 Java 中没有命名的内部类称为匿名内部类,当我们需要重写类或接口中的方法时,都会使用到它。匿名内部类在使用的时候将同时声明和实例化。

匿名类可以通过以下两种方式进行创建:

  • 类(可能是抽象类或者其他类);
  • 接口;

实例一

下面将会创建一个匿名类,该匿名类是接口Contents的实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
///匿名内部类
 
public class Parcel7 {
    //
    public Contents contents() {
        //返回一个匿名类对象,该类是Contents接口的实现
        return new Contents() {
            private int i = 11;
            public int value() {
                return i;
            }
        };     
    }
     
    public static void main(String[] args) {
        Parcel7 p = new Parcel7();
        Contents c = p.contents();
    }
 
}

 上述代码中通过new表达式返回的引用 被自动向上转为对Contents类型的引用。上述匿名内部类的语法是下述代码的简化形式:

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 Parcel7 {
     
//  public Contents contents() {
//      //返回一个匿名类对象,该类是Contents接口的实现
//      return new Contents() {
//          private int i = 11;
//          public int value() {
//              return i;
//          }
//      };     
//  }
     
    class PContents implements  Contents{
        private int i = 11;
        public int value() {
            return i;
        }
    }  
    public Contents contents() {
        return new PContents();
    }      
    public static void main(String[] args) {
        Parcel7 p = new Parcel7();
        Contents c = p.contents();
    }
}

实例二

 在示例一匿名内部类中,使用的是默认构造函数,但是如果基类需要一个有参数的构造器,我们只需要简单的传递合适的参数给基类的构造器即可,下面演示一个例子:

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
class Wrapping{
    private int i;
    //需要一个参数
    public Wrapping(int x) {
        i = x;
        System.out.println("基类构造器:"+x);
    }  
    public int value() {
        return i;
    }
}
public class Parcel8 {
    public Wrapping wrapping(int x) {
        //一个匿名类 实现类Wrapping类,并传递x给基类的构造器
        return new Wrapping(x) {
            public int value() {
                return super.value()*47;
            }
        };
    }  
    public static void main(String[] args) {
        Parcel8 p = new Parcel8();
        Wrapping w = p.wrapping(10);
    }
}

Wrapping是一个具有具体实现的简单类。我们将x传递进new Wrapping(x),这个x最终会传递给基类Wrapping的构造函数。

 实例三

如果在匿名类中定义字段,还可以对其执行初始化,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
///匿名类中初始化字段
public class Parcel9 {
    //dest用来初始化匿名类字段,必须声明为final
    public Destination destination(final String dest) {
        return new Destination() {
            private String label = dest;
            public String readLabel(){
                return label;
            }
        };
    }  
    public static void main(String[] args) {
        Parcel9 p = new Parcel9();
        Destination d = p.destination("北京");
    }  

 如果我们我们定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final,就像上面这个例子一样。

实例四

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(1695)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示