• Java编程思想学习(八) 内部类


    可以将一个类的定义放在另一个类的定义内部,这就是内部类. 
    内部类的定义是简单的,但是它的语法确实很是复杂,让人不是很好理解.下面就内部类做一个小结. 
    一.内部类的分类 
    总的来讲内部类分为普通内部类,匿名内部类,局部内部类,嵌套类(静态内部类)等.下面简要的介绍以下这些内部类定义的语法. 
    (1).对于普通的内部类,就是在外围类中插入另一个类的定义.如下面的代码:

     1 package lkl1;
     2 
     3 ///封装一个包裹类
     4 public class Parcel {
     5 
     6     ///在一个类的内部定义的另一个类,称之为内部类
     7     ///它的定义形式并没有什么不同
     8     public class Destination{
     9         private String label;
    10         Destination(String whereto){
    11             label=whereto;
    12         }
    13         public String getLabel(){
    14             return label;
    15         }
    16     }
    17 
    18     public class Contents{
    19         private int i=11;
    20         public int value(){
    21             return i;
    22         }
    23     }
    24     ///提供一个统一的创建内部类的接口
    25     public Destination destination(String s){
    26         return new Destination(s);
    27     }
    28     public Contents contents(){
    29         return new Contents();
    30     }
    31 
    32     public void ship(String dest){
    33         //其实也可以直接调用构造器定义
    34         //Contents c1 = new Contents();
    35         Contents c = contents();
    36         Destination d = destination(dest);
    37         System.out.println(d.label);
    38     }
    39     public static void main(String[] args){
    40         Parcel p = new Parcel();
    41         p.ship("changsha");
    42 
    43         Parcel p1= new Parcel();
    44 
    45         ///为内部类定义引用,注意名字的层次
    46         ///在非静态方法中定义内部类的对像需要具体指明这个对象的类型
    47         ///OuterClassName.InnerClassName
    48         Parcel.Contents  c =p1.contents();
    49         System.out.println(c.i);
    50         Parcel.Destination d = p1.destination("wuhan");
    51     }
    52 }

    (2).所谓的匿名内部类的定义语法比较奇特.匿名内部类是没有名字的,所以我们不能想一般的类那样调用构造器得到它的对象,一般我们都将它放在一个方法中,这个方法负责返回这个匿名内部类的一个对象.因为匿名内部类没有名字,所以也就不能通过构造器来实现初始化了,我们可以通过初始化块的形式达到构造器的效果(当然我们是可以调用基类的构造器来初始化基类的成员变量).如下面的代码:

     1 package lkl1;
     2 
     3 public class Parcel3 {
     4     ///用于匿名内部类变量初始化的形参必须要用final修饰
     5     //Destination是前面定义的一个类.
     6     public Destination destination(String dest,final double price,int i){
     7         ///创建匿名内部类的一般格式
     8         return new Destination(i){  ////可以通过调用基类的构造器进行基类的初始化
     9             private int cost;
    10             {///用初始化块达到类似于构造器的初始化过程
    11                 cost =(int) Math.round(price);
    12                 if(cost>100){
    13                     System.out.println("Over budget!");
    14                 }
    15             }
    16             private String label=dest;
    17             public String readLabel(){
    18                 return label;
    19             }
    20         };
    21     }
    22     public static void main(String[] args){
    23         Parcel3  p3= new Parcel3();
    24         Destination d = p3.destination("changsha", 109.234,10);
    25         System.out.println(d.readLabel());
    26     }
    27 }

    (3)所谓的局部内部类其实就是在方法和作用域内定义的内部类.这种内部类只在一定的范围是有效的(其实上面的内部类就是一种局部内部类).一般我们是将其当成工具使用的,不希望它是公共可见的.如下面的代码:

     1 package lkl1;
     2 
     3 public class Test {
     4 
     5           private PrintId create(){
     6           ///外部类中的一个方法中定义内部类用于实现某个接口;
     7           ///然后返回这个内部类的一个引用
     8             {
     9                   ///PrintId是前面的定义的一个接口
    10                   class Print implements PrintId{
    11                    private String id;
    12                    public Print(){
    13                        id="longkaili";
    14                    }
    15                    public void print(){
    16                        System.out.println(id);
    17                    }
    18                }  
    19                return new Print();
    20           }
    21             //试图在作用域外访问内部类出错
    22             //(new Print()).id;
    23       }
    24       public static void main(String[] args){
    25           ///以下说明了虽然内部类是定义在一个作用域类的
    26           ///但是在外面还是可以使用它实现的功能的
    27           Test ts = new Test();
    28           ts.create().print();  ///在本类方法中创建本类的对象,其private接口是可见的
    29       }
    30 }

    (4).嵌套类其实就是就将内部类声明成static.对于普通的内部类其必须要依赖于一个外部类的对象,而嵌套类是不需要的,我们可以将其看成一个static型的变量.如下面的代码:

     1 package lkl1;
     2 
     3 ///当内部类被声明成静态时,我们就将其当成一个静态成员来看待
     4 ///此时内部类不在和外部类的对象绑定在一起.
     5 public class Parcel1 {
     6 
     7     private static  class Parcel1Contents extends  Contents{
     8         private int i=1;
     9         public int value(){
    10             return i;
    11         }
    12     }
    13     protected static class Parcel1Destination extends Destination{
    14         private String label;
    15         private Parcel1Destination(String whereTo){
    16             label=whereTo;
    17         }
    18         public String readLabel(){
    19             return label;
    20         }
    21     }
    22     //我们可以通过外部类的静态方法创建嵌套类的对象
    23     public  static Destination destination(String s){
    24         return new Parcel1Destination(s);
    25     }
    26     public static Contents contents(){
    27         return new Parcel1Contents();
    28     }
    29     public static void main(String[] args){
    30         Contents c= contents();
    31         Destination d =destination("changsha");
    32     }
    33 }

    二.外部类和内部类的联系. 
    既然内部类定义在了外部类的内部,那么肯定就具有一定的联系的.具体的我们分成非static型的一般内部类和static修饰的嵌套类来分析. 
    (1).首先对于一般的内部类,其和外部类的联系是很紧密的.体现在内部类的对象必须依附于一个外部类的对象,也就是说我们必须通过一个外部类的对象才能创建内部类的对象.其次,外部类的一切成员变量对于内部类都是可见的,包括private成员变量,而外部类也可以访问内部类的所有变量.另外,内部类还可以通过.this方式显式的访问其对于的外部类对象,外部类也可以通过.new方式创建内部类的对象(一般我们都是通过外部类的一个方法返回内部类的对象引用).下面的代码示范了这几点:

     1 package lkl1;
     2 
     3 ///测试外部类对内部类的访问权限
     4 public class Outer {
     5     private int k=100;
     6     private class Inner{
     7         private int i=100;
     8         public int j=1111;
     9         private void print(){
    10             System.out.println("Outer.k = "+ k); ///内部类可以访问外部类的所有成员变量
    11             System.out.println("Inner.print()");
    12         }
    13         ///非static内部类中不能创建static类型的变量
    14        /// public static int k=999;
    15     }
    16     public void print(Inner in){
    17         ///事实证明外部类同样可以访问内部类的所有方法,变量
    18         ///当然前提是我们有一个内部类的对象,直接通过类名来访问是不行的
    19         in.print();
    20         in.i++; in.j++;
    21         System.out.println(in.i);
    22         System.out.println(in.j);
    23     }
    24    public static void main(String[] args){
    25        Outer ot = new Outer();
    26        Outer.Inner in = ot.new Inner(); ///通过.new创建内部类的引用,注意内部类引用的声明方式
    27        ot.print(in); 
    28    }
    29 }

    (2).对于static修饰的嵌套类来说,情况就不同了.通过上面的例子我们可以看到普通的内部类对象隐式的保存了一个引用,指向创建它的外围对象.但对于嵌套类,它的对象不依赖于外部类的对象而存在,当然它也不能访问非静态的外围类对象.另外还有一点.普通类中是不能包括static方法,变量的,但是嵌套类中是可以包含这些东西的.如下面的代码所示:

     1 package lkl1;
     2 
     3 ///当内部类被声明成静态时,我们就将其当成一个静态成员来看待
     4 ///此时内部类不在和外部类的对象绑定在一起.
     5 public class Parcel1 {
     6 //ContentsheDestination都是前面定义的抽象类
     7     private static  class Parcel1Contents extends  Contents{
     8         private static int k=110;
     9         private int i=1;
    10         public int value(){
    11             return i;
    12         }
    13     }
    14 
    15     //我们可以通过外部类的静态方法创建嵌套类的对象
    16     public static Contents contents(){
    17         return new Parcel1Contents();
    18     }
    19     public static void main(String[] args){
    20         ///访问Parecel1的静态变量,注意调用格式
    21         System.out.println("Parcel1Contents的静态变量k  "+Parcel1.Parcel1Contents.k);
    22         Contents c= contents();
    23     }
    24 }

    三.内部类的作用 
    内部类的语法是很复杂的,但是在学习这些复杂语法的时候更令我迷惑的是:内部类有什么用?java编程思想一书是这么讲的: 
    1.每个内部类都能独立的继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响. 
    2.可以更好的实现多重继承.我们知道普通的类是不可以多重继承的,但是现在通过内部类,我们就可以对普通类达到多重继承的效果. 
    3.在一些设计模式中有重要的应用. 
    对于这些现在还不是都能理解的很清楚,希望在以后的学习中能够搞清楚. 
    下面给一个通过内部类实现迭代器的实例.下面的Sequence类是一个封装了一个Object数组的普通类,而Selector是一个通用的迭代器接口,我们在Sequence中通过一个内部类实现了Selector接口,然后通过这个内部类的接口向上转型后的对象对Sequence对象进行迭代访问.其实以前我们接触过得集合类的迭代器也就是这么实现的.

     1 package lkl1;
     2 
     3 ///定义一个通用的迭代器接口
     4 public interface Selector {
     5 
     6     boolean end();
     7     Object current();
     8     void next();
     9 }
    10 
    11 
    12 package lkl1;
    13 
    14 ///Sequence类封装一个固定大小的
    15 ///Object数组,然后提供它自己的一个迭代器
    16 ///这里利用的是内部类可以访问外部类的所有数据的性质
    17 public class Sequence {
    18     private Object[] items;
    19     private int next=0;///保留当前元素个数
    20     public Sequence(int size){
    21         items=new Object[size];
    22     }
    23     public void add(Object obj){
    24         if(next<items.length){
    25             items[next++]=obj;
    26         }
    27     }
    28 
    29     ///封装一个内部类实现迭代器
    30     private class SequenceSelector implements Selector{
    31         private int i=0; ///当前访问到元素的编号
    32         public boolean end(){
    33             return i==items.length;
    34         }
    35         public Object current(){
    36             return items[i];
    37         }
    38         public void next(){
    39             if(i<items.length) i++;
    40         }
    41     }
    42     ///提供迭代器对外的接口
    43     public Selector selector(){
    44         return new SequenceSelector();
    45     }
    46 
    47     public static void main(String[] args){
    48         Sequence sq= new Sequence(10);
    49         for(int i=0;i<10;i++)
    50              sq.add(Integer.toString(i));
    51 
    52         ///利用Sequence本身的迭代器来访问
    53         Selector se =sq.selector();
    54         while(!se.end()){
    55             System.out.print(se.current()+" ");
    56             se.next();
    57         }
    58         System.out.println();
    59     }
    60 }
  • 相关阅读:
    基于Typescript和Jest刷题环境搭建与使用
    一些惊艳到我的运维实施技巧和思路
    Zabbix 5.0:监控MySQL出现的问题
    Linux防火墙IPtables配置策略思路
    聊聊二维码
    如何创建SQLite数据库
    PLC工程师学会编程,是一种什么样的体验?
    让你的上位机程序独占鳌头
    使用delve调试golang
    指纹登录是怎么跑起来的
  • 原文地址:https://www.cnblogs.com/benchao/p/5263184.html
Copyright © 2020-2023  润新知