zoukankan      html  css  js  c++  java
  • java中内部类的积累

    放在一个类的内部的类我们就叫内部类。

    二、 作用

    1.内部类可以很好的实现隐藏

     一般的非内部类,是不允许有 private 与protected权限的,但内部类可以

    2.内部类拥有外围类的所有元素的访问权限

    3.可是实现多重继承

    4.可以避免修改接口而实现同一个类中两种同名方法的调用。

    提起Java内部类(Inner Class)可能很多人不太熟悉,实际上类似的概念在C++里也有,那就是嵌套类(Nested Class),关于这两者的区别与联系,在下文中会有对比。内部类从表面上看,
    就是在类中又定义了一个类(下文会看到,内部类可以在很多地方定义),而实际上并没有那么简单,乍看上去内部类似乎有些多余,它的用处对于初学者来说可能并不是那么显著,
    但是随着对它的深入了解,你会发现Java的设计者在内部类身上的确是用心良苦。学会使用内部类,是掌握Java高级编程的一部分,它可以让你更优雅地设计你的程序结构。
    下面从以下几个方面来介绍:
    第一次见面

    [java] view plain copy
     
    1. public interface Contents {  
    2.     int value();  
    3. }  
    4.   
    5. public interface Destination {  
    6.     String readLabel();  
    7. }  
    8.   
    9. public class Goods {  
    10.     private class Content implements Contents {  
    11.         private int i = 11;  
    12.   
    13.         public int value() {  
    14.             return i;  
    15.         }  
    16.     }  
    17.   
    18.     protected class GDestination implements Destination {  
    19.         private String label;  
    20.   
    21.         private GDestination(String whereTo) {  
    22.             label = whereTo;  
    23.         }  
    24.   
    25.         public String readLabel() {  
    26.             return label;  
    27.         }  
    28.     }  
    29.   
    30.     public Destination dest(String s) {  
    31.         return new GDestination(s);  
    32.     }  
    33.   
    34.     public Contents cont() {  
    35.         return new Content();  
    36.     }  
    37. }  
    38.   
    39. class TestGoods {  
    40.     public static void main(String[] args) {  
    41.         Goods p = new Goods();  
    42.         Contents c = p.cont();  
    43.         Destination d = p.dest("Beijing");  
    44.     }  
    45. }  

    在这个例子里类Content和GDestination被定义在了类Goods内部,并且分别有着protected和private修饰符来控制访问级别。Content代表着Goods的内容,而GDestination代表着Goods的目的地。它们分别实现了两个接口Content和Destination。在后面的main方法里,直接用 Contents c和Destination d进行操作,你甚至连这两个内部类的名字都没有看见!这样,内部类的第一个好处就体现出来了 隐藏你不想让别人知道的操作,也即封装性。同时,我们也发现了在外部类作用范围之外得到内部类对象的第一个方法,那就是利用其外部类的方法创建并返回。
    上例中的cont()和dest()方法就是这么做的。那么还有没有别的方法呢?当然有,其语法格式如下:

    [java] view plain copy
     
    1. outerObject=new outerClass(Constructor Parameters);  
    2. outerClass.innerClass innerObject=outerObject.new InnerClass(Constructor Parameters);   


    注意在创建非静态内部类对象时,一定要先创建起相应的外部类对象。至于原因,也就引出了我们下一个话题 非静态内部类对象有着指向其外部类对象的引用,对刚才的例子稍作修改:

    [java] view plain copy
     
    1. public class Goods {  
    2.     private valueRate = 2;  
    3.   
    4.     private class Content implements Contents {  
    5.         private int i = 11 * valueRate;  
    6.   
    7.         public int value() {  
    8.             return i;  
    9.         }  
    10.     }  
    11.   
    12.     protected class GDestination implements Destination {  
    13.         private String label;  
    14.   
    15.         private GDestination(String whereTo) {  
    16.             label = whereTo;  
    17.         }  
    18.   
    19.         public String readLabel() {  
    20.             return label;  
    21.         }  
    22.     }  
    23.   
    24.     public Destination dest(String s) {  
    25.         return new GDestination(s);  
    26.     }  
    27.   
    28.     public Contents cont() {  
    29.         return new Content();  
    30.     }  
    31. }  


    修改的部分用蓝色显示了。在这里我们给Goods类增加了一个private成员变量valueRate,意义是货物的价值系数,在内部类Content的方法value()计算价值时把它乘上。
    我们发现,value()可以访问valueRate,这也是内部类的第二个好处 一个内部类对象可以访问创建它的外部类对象的内容,甚至包括私有变量!
    这是一个非常有用的特性,为我们在设计时提供了更多的思路和捷径。要想实现这个功能,内部类对象就必须有指向外部类对象的引用。
    Java编译器在创建内部类对象时,隐式的把其外部类对象的引用也传了进去并一直保存着。
    这样就使得内部类对象始终可以访问其外部类对象,同时这也是为什么在外部类作用范围之外向要创建内部类对象必须先创建其外部类对象的原因。
    有人会问,如果内部类里的一个成员变量与外部类的一个成员变量同名,也即外部类的同名成员变量被屏蔽了,怎么办?没事,Java里用如下格式表达外部类的引用:
    outerClass.this 
    有了它,我们就不怕这种屏蔽的情况了。
    静态内部类
    和普通的类一样,内部类也可以有静态的。不过和非静态内部类相比,区别就在于静态内部类没有了指向外部的引用。这实际上和C++中的嵌套类很相像了,Java内部类与C++嵌套类最大的不同就在于是否有指向外部的引用这一点上,当然从设计的角度以及以它一些细节来讲还有区别。
    除此之外,在任何非静态内部类中,都不能有静态数据,静态方法或者又一个静态内部类(内部类的嵌套可以不止一层)。
    不过静态内部类中却可以拥有这一切。这也算是两者的第二个区别吧。
    局部内部类 
    是的,Java内部类也可以是局部的,它可以定义在一个方法甚至一个代码块之内。

    [java] view plain copy
     
    1. public class Goods1 {  
    2.     public Destination dest(String s) {  
    3.         class GDestination implements Destination {  
    4.             private String label;  
    5.   
    6.             private GDestination(String whereTo) {  
    7.                 label = whereTo;  
    8.             }  
    9.   
    10.             public String readLabel() {  
    11.                 return label;  
    12.             }  
    13.         }  
    14.         return new GDestination(s);  
    15.     }  
    16.   
    17.     public static void main(String[] args) {  
    18.         Goods1 g = new Goods1();  
    19.         Destination d = g.dest("Beijing");  
    20.     }  
    21. }  


    上面就是这样一个例子。在方法dest中我们定义了一个内部类,最后由这个方法返回这个内部类的对象。如果我们在用一个内部类的时候仅需要创建它的一个对象并创给外部,就可以这样做。当然,定义在方法中的内部类可以使设计多样化,用途绝不仅仅在这一点。
    下面有一个更怪的例子:

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


    你不能在if之外创建这个内部类的对象,因为这已经超出了它的作用域。不过在编译的时候,内部类TrackingSlip和其他类一样同时被编译,只不过它由它自己的作用域,超出了这个范围就无效,除此之外它和其他内部类并没有区别。
    匿名内部类 
    java的匿名内部类的语法规则看上去有些古怪,不过如同匿名数组一样,当你只需要创建一个类的对象而且用不上它的名字时,使用内部类可以使代码看上去简洁清楚。它的语法规则是这样的:
    new interfacename(){......}; 或 new superclassname(){......}; 
    下面接着前面继续举例子:

    [java] view plain copy
     
    1. public class Goods3 {  
    2.     public Contents cont() {  
    3.         return new Contents() {  
    4.             private int i = 11;  
    5.   
    6.             public int value() {  
    7.                 return i;  
    8.             }  
    9.         };  
    10.     }  
    11. }   


    这里方法cont()使用匿名内部类直接返回了一个实现了接口Contents的类的对象,看上去的确十分简洁。
    在java的事件处理的匿名适配器中,匿名内部类被大量的使用。例如在想关闭窗口时加上这样一句代码:

    [java] view plain copy
     
    1. frame.addWindowListener(new WindowAdapter(){  
    2. public void windowClosing(WindowEvent e){  
    3. System.exit(0);   
    4. }  
    5. });   


    有一点需要注意的是,匿名内部类由于没有名字,所以它没有构造函数(但是如果这个匿名内部类继承了一个只含有带参数构造函数的父类,创建它的时候必须带上这些参数,并在实现的过程中使用super关键字调用相应的内容)。如果你想要初始化它的成员变量,有下面几种方法:
    如果是在一个方法的匿名内部类,可以利用这个方法传进你想要的参数,不过记住,这些参数必须被声明为final。 
    将匿名内部类改造成有名字的局部内部类,这样它就可以拥有构造函数了。 
    在这个匿名内部类中使用初始化代码块。 
    为什么需要内部类? 
    java内部类有什么好处?为什么需要内部类?
    首先举一个简单的例子,如果你想实现一个接口,但是这个接口中的一个方法和你构想的这个类中的一个方法的名称,参数相同,你应该怎么办?这时候,你可以建一个内部类实现这个接口。由于内部类对外部类的所有内容都是可访问的,所以这样做可以完成所有你直接实现这个接口的功能。
    不过你可能要质疑,更改一下方法的不就行了吗?
    的确,以此作为设计内部类的理由,实在没有说服力。
    真正的原因是这样的,java中的内部类和接口加在一起,可以的解决常被C++程序员抱怨java中存在的一个问题 没有多继承。实际上,C++的多继承设计起来很复杂,而java通过内部类加上接口,可以很好的实现多继承的效果。

    java静态与非静态区别

     

    这里的静态,指以static关键字修饰的,包括类,方法,块,字段。

    非静态,指没有用static 修饰的。

    静态有一些特点:

    1.全局唯一,任何一次的修改都是全局性的影响

    2.只加载一次,优先于非静态

    3.使用方式上不依赖于实例对象。

    4.生命周期属于类级别,从JVM 加载开始到JVM卸载结束。

    可参考 :http://blog.csdn.net/zhandoushi1982/article/details/8453522/。

    关于静态内部类(嵌套类)和非静态内部类的区别,可参考:

    http://www.jb51.net/article/74838.htm

    (1)内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。

    (2)非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。

    (3)一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。

    /* 下面程序演示如何在java中创建静态内部类和非静态内部类 */
    class OuterClass{
      private static String msg = "GeeksForGeeks";
      // 静态内部类
      public static class NestedStaticClass{
        // 静态内部类只能访问外部类的静态成员
        public void printMessage() {
         // 试着将msg改成非静态的,这将导致编译错误 
         System.out.println("Message from nested static class: " + msg); 
        }
      }
      // 非静态内部类
      public class InnerClass{
        // 不管是静态方法还是非静态方法都可以在非静态内部类中访问
        public void display(){
         System.out.println("Message from non-static nested class: "+ msg);
        }
      }
    } 
    class Main
    {
      // 怎么创建静态内部类和非静态内部类的实例
      public static void main(String args[]){
        // 创建静态内部类的实例
        OuterClass.NestedStaticClass printer = new OuterClass.NestedStaticClass();
        // 创建静态内部类的非静态方法
        printer.printMessage();  
        // 为了创建非静态内部类,我们需要外部类的实例
        OuterClass outer = new OuterClass();    
        OuterClass.InnerClass inner = outer.new InnerClass();
        // 调用非静态内部类的非静态方法
        inner.display();
        // 我们也可以结合以上步骤,一步创建的内部类实例
        OuterClass.InnerClass innerObject = new OuterClass().new InnerClass();
        // 同样我们现在可以调用内部类方法
        innerObject.display();
      }
    }



       在Java提高篇-----详解内部类中对匿名内部类做了一个简单的介绍,但是内部类还存在很多其他细节问题,所以就衍生出这篇博客。在这篇博客中你可以了解到匿名内部类的使用、匿名内部类要注意的事项、如何初始化匿名内部类、匿名内部类使用的形参为何要为final。

           一、使用匿名内部类内部类

           匿名内部类由于没有名字,所以它的创建方式有点儿奇怪。创建格式如下:

    [java] view plain copy
     
    1. new 父类构造器(参数列表)|实现接口()    
    2.     {    
    3.      //匿名内部类的类体部分    
    4.     }  

           在这里我们看到使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。

    [java] view plain copy
     
    1. public abstract class Bird {  
    2.     private String name;  
    3.   
    4.     public String getName() {  
    5.         return name;  
    6.     }  
    7.   
    8.     public void setName(String name) {  
    9.         this.name = name;  
    10.     }  
    11.       
    12.     public abstract int fly();  
    13. }  
    14.   
    15. public class Test {  
    16.       
    17.     public void test(Bird bird){  
    18.         System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");  
    19.     }  
    20.       
    21.     public static void main(String[] args) {  
    22.         Test test = new Test();  
    23.         test.test(new Bird() {  
    24.               
    25.             public int fly() {  
    26.                 return 10000;  
    27.             }  
    28.               
    29.             public String getName() {  
    30.                 return "大雁";  
    31.             }  
    32.         });  
    33.     }  
    34. }  
    35. ------------------  
    36. Output:  
    37. 大雁能够飞 10000米  

           在Test类中,test()方法接受一个Bird类型的参数,同时我们知道一个抽象类是没有办法直接new的,我们必须要先有实现类才能new出来它的实现类实例。所以在mian方法中直接使用匿名内部类来创建一个Bird实例。

           由于匿名内部类不能是抽象类,所以它必须要实现它的抽象父类或者接口里面所有的抽象方法。

           对于这段匿名内部类代码其实是可以拆分为如下形式:

    [java] view plain copy
     
    1. public class WildGoose extends Bird{  
    2.     public int fly() {  
    3.         return 10000;  
    4.     }  
    5.       
    6.     public String getName() {  
    7.         return "大雁";  
    8.     }  
    9. }  
    10.   
    11. WildGoose wildGoose = new WildGoose();  
    12. test.test(wildGoose);  

           在这里系统会创建一个继承自Bird类的匿名类的对象,该对象转型为对Bird类型的引用。

           对于匿名内部类的使用它是存在一个缺陷的,就是它仅能被使用一次,创建匿名内部类时它会立即创建一个该类的实例,该类的定义会立即消失,所以匿名内部类是不能够被重复使用。对于上面的实例,如果我们需要对test()方法里面内部类进行多次使用,建议重新定义类,而不是使用匿名内部类。

           二、注意事项

           在使用匿名内部类的过程中,我们需要注意如下几点:

          1、使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。

          2、匿名内部类中是不能定义构造函数的。

          3、匿名内部类中不能存在任何的静态成员变量和静态方法。

          4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。

          5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

           三、使用的形参为何要为final

           参考文件:http://android.blog.51cto.com/268543/384844

           我们给匿名内部类传递参数的时候,若该形参在内部类中需要被使用,那么该形参必须要为final。也就是说:当所在的方法的形参需要被内部类里面使用时,该形参必须为final。

          为什么必须要为final呢?

          首先我们知道在内部类编译成功后,它会产生一个class文件,该class文件与外部类并不是同一class文件,仅仅只保留对外部类的引用。当外部类传入的参数需要被内部类调用时,从java程序的角度来看是直接被调用:

    [java] view plain copy
     
    1. public class OuterClass {  
    2.     public void display(final String name,String age){  
    3.         class InnerClass{  
    4.             void display(){  
    5.                 System.out.println(name);  
    6.             }  
    7.         }  
    8.     }  
    9. }  

          从上面代码中看好像name参数应该是被内部类直接调用?其实不然,在java编译之后实际的操作如下:

    [java] view plain copy
     
    1. public class OuterClass$InnerClass {  
    2.     public InnerClass(String name,String age){  
    3.         this.InnerClass$name = name;  
    4.         this.InnerClass$age = age;  
    5.     }  
    6.       
    7.       
    8.     public void display(){  
    9.         System.out.println(this.InnerClass$name + "----" + this.InnerClass$age );  
    10.     }  
    11. }  

           所以从上面代码来看,内部类并不是直接调用方法传递的参数,而是利用自身的构造器对传入的参数进行备份,自己内部方法调用的实际上时自己的属性而不是外部方法传递进来的参数。

           直到这里还没有解释为什么是final?在内部类中的属性和外部方法的参数两者从外表上看是同一个东西,但实际上却不是,所以他们两者是可以任意变化的,也就是说在内部类中我对属性的改变并不会影响到外部的形参,而然这从程序员的角度来看这是不可行的,毕竟站在程序的角度来看这两个根本就是同一个,如果内部类该变了,而外部方法的形参却没有改变这是难以理解和不可接受的,所以为了保持参数的一致性,就规定使用final来避免形参的不改变。

          简单理解就是,拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变。

          故如果定义了一个匿名内部类,并且希望它使用一个其外部定义的参数,那么编译器会要求该参数引用是final的。

     

           四、匿名内部类初始化

           我们一般都是利用构造器来完成某个实例的初始化工作的,但是匿名内部类是没有构造器的!那怎么来初始化匿名内部类呢?使用构造代码块!利用构造代码块能够达到为匿名内部类创建一个构造器的效果。

    [java] view plain copy
     
    1. public class OutClass {  
    2.     public InnerClass getInnerClass(final int age,final String name){  
    3.         return new InnerClass() {  
    4.             int age_ ;  
    5.             String name_;  
    6.             //构造代码块完成初始化工作  
    7.             {  
    8.                 if(0 < age && age < 200){  
    9.                     age_ = age;  
    10.                     name_ = name;  
    11.                 }  
    12.             }  
    13.             public String getName() {  
    14.                 return name_;  
    15.             }  
    16.               
    17.             public int getAge() {  
    18.                 return age_;  
    19.             }  
    20.         };  
    21.     }  
    22.       
    23.     public static void main(String[] args) {  
    24.         OutClass out = new OutClass();  
    25.           
    26.         InnerClass inner_1 = out.getInnerClass(201, "chenssy");  
    27.         System.out.println(inner_1.getName());  
    28.           
    29.         InnerClass inner_2 = out.getInnerClass(23, "chenssy");  
    30.         System.out.println(inner_2.getName());  
    31.     }  
    32. }  
  • 相关阅读:
    webpack基础
    LeetCode232. 用栈实现队列做题笔记
    mysql 时间加减一个月
    leetcode 1381. 设计一个支持增量操作的栈 思路与算法
    LeetCode 141. 环形链表 做题笔记
    leetcode 707. 设计链表 做题笔记
    leetcode 876. 链表的中间结点 做题笔记
    leetcode 143. 重排链表 做题笔记
    leetcode 1365. 有多少小于当前数字的数字 做题笔记
    LeetCode1360. 日期之间隔几天 做题笔记
  • 原文地址:https://www.cnblogs.com/hadoop-dev/p/7027650.html
Copyright © 2011-2022 走看看