当前位置:  编程技术>移动开发
本页文章导读:
    ▪(转载)JVM怎么理解Java泛型类        (转载)JVM如何理解Java泛型类 转载:http://www.iteye.com/topic/549509     Java代码   //泛型代码    public   class  Pair<T>{          private  T first= null ;          private  T sec.........
    ▪ Toast展示和关闭自个控制的方法        Toast显示和关闭自个控制的方法  Toast信息提示框之所以在显示一定时间后会自动关闭,是因为在系统中有一个Toast队列。系统会依次从队列中取(出队列)一个Toast,并显示 它。在显示一段.........
    ▪ Dalvik(札记)       Dalvik(笔记) Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一。它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik设计.........

[1](转载)JVM怎么理解Java泛型类
    来源: 互联网  发布时间: 2014-02-18
(转载)JVM如何理解Java泛型类

转载:http://www.iteye.com/topic/549509

 

 

Java代码  
  • //泛型代码   
  • public   class  Pair<T>{  
  •        private  T first= null ;  
  •        private  T second= null ;  
  •   
  •        public  Pair(T fir,T sec){  
  •             this .first=fir;  
  •         this .second=sec;  
  •        }  
  •        public  T getFirst(){  
  •              return   this .first;  
  •        }  
  •        public  T getSecond(){  
  •              return   this .second;  
  •        }  
  •        public   void  setFirst(T fir){  
  •          this .first=fir;  
  •        }  
  • }  
  •  上面是一个很典型的泛型(generic)代码。T是类型变量,可以是任何引用类型。

     

    1、Generic class 创建对象
                Pair<String> pair1=new Pair("string",1);           ...①
                Pair<String> pair2=new Pair<String>("string",1)    ...②
          有个很有趣的现象:  ①代码在编译期不会出错,②代码在编译期会检查出错误。
          这个问题其实很简单
          (1) JVM本身并没有泛型对象这样的一个特殊概念。所有的泛型类对象在编译器会全部变成普通类对象(这一点会在下面详细阐述)。
          比如①,②两个代码编译器全部调用的是 Pair(Object fir, Object sec)这样的构造器。
          因此代码①中的new Pair("string",1)在编译器是没有问题的,毕竟编译器并不知道你创建的Pair类型中具体是哪一个类型变量T,而且编译器肯定了String对象和Integer对象都属于Object类型的。
           但是一段运行pair1.getSecond()就会抛出ClassCastException异常。这是因为JVM会根据第一个参数"string"推 算出T类型变量是String类型,这样getSecond也应该是返回String类型,然后编译器已经默认了second的操作数是一个值为1的 Integer类型。当然就不符合JVM的运行要求了,不终止程序才怪。
           (2) 但代码②会在编译器报错,是因为new Pair<String>("string",1)已经指明了创建对象pair2的类型变量T应该是String的。所以在编译期编译器就知道错误出在第二个参数Integer了。
           小结一下:
           创建泛型对象的时候,一定要指出类型变量T的具体类型。争取让编译器检查出错误,而不是留给JVM运行的时候抛出异常。

     

    2、JVM如何理解泛型概念 —— 类型擦除
        事实上,JVM并不知道泛型,所有的泛型在编译阶段就已经被处理成了普通类和方法。
        处理方法很简单,我们叫做类型变量T的擦除(erased) 。
        无论我们如何定义一个泛型类型,相应的都会有一个原始类型被自动提供。原始类型的名字就是擦除类型参数的泛型类型的名字。
             如果泛型类型的类型变量没有限定(<T>) ,那么我们就用Object作为原始类型;
             如果有限定(<T extends XClass>),我们就XClass作为原始类型;
             如果有多个限定(<T extends XClass1&XClass2>),我们就用第一个边界的类型变量XClass1类作为原始类型;
        比如上面的Pair<T>例子,编译器会把它当成被Object原始类型替代的普通类来替代。

    Java代码  
  • //编译阶段:类型变量的擦除   
  • ublic class  Pair{  
  •        private  Object first= null ;  
  •        private  Object second= null ;  
  •   
  •        public  Pair(Object fir,Object sec){  
  •            this .first=fir;  
  •     this .second=sec;  
  •        }  
  •       public  Object getFirst(){  
  •             return   this .first;  
  •       }  
  •       public   void  setFirst(Object fir){  
  •      this .first=fir;  
  •       }  
  •    }  
  •  

    3、泛型约束和局限性—— 类型擦除所带来的麻烦

    (1)  继承泛型类型的多态麻烦。(—— 子类没有覆盖住父类的方法 )

         

         看看下面这个类SonPair

    Java代码  
  • class  SonPair  extends  Pair<String>{  
  •           public   void  setFirst(String fir){....}  
  • }  
  •      很明显,程序员的本意是想在SonPair类中覆盖父类Pair<String>的setFirst(T fir)这个方法。但事实上,SonPair中的setFirst(String fir)方法根本没有覆盖住Pair<String>中的这个方法。
         原因很简单,Pair<String>在编译阶段已经被类型擦除为Pair了,它的setFirst方法变成了setFirst(Object fir)。 那么SonPair中 setFirst(String)当然无法覆盖住父类的setFirst(Object)了。

         这对于多态来说确实是个不小的麻烦,我们看看编译器是如何解决这个问题的。

         编译器 会自动在 SonPair中生成一个桥方法(bridge method ) :
               public void setFirst(Object fir){
                       setFirst((String) fir)
                }
          这样,SonPair的桥方法确实能够覆盖泛型父类的setFirst(Object) 了。而且桥方法内部其实调用的是子类字节setFirst(String)方法。对于多态来说就没问题了。

          问题还没有完,多态中的方法覆盖是可以了,但是桥方法却带来了一个疑问:

          现在,假设 我们还想在 SonPair 中覆盖getFirst()方法呢?

    Java代码  
  • class  SonPair  extends  Pair<String>{  
  •       public  String getFirst(){....}  
  • }  
  •       由于需要桥方法来覆盖父类中的getFirst,编译器会自动在SonPair中生成一个 public Object getFirst()桥方法。
          但是,疑问来了,SonPair中出现了两个方法签名一样的方法(只是返回类型不同):

                ①String getFirst()   // 自己定义的方法

                ②Object getFirst()  //  编译器生成的桥方法
          难道,编译器允许出现方法签名相同的多个方法存在于一个类中吗?

          事实上有一个知识点可能大家都不知道:
          ① 方法签名 确实只有方法名+参数列表 。这毫无疑问!
          ② 我们绝对不能编写出方法签名一样的多个方法 。如果这样写程序,编译器是不会放过的。这也毫无疑问!
          ③ 最重要的一点是:JVM会用参数类型和返回类型来确定一个方法。 一旦编译器通过某种方式自己编译出方法签名一样的两个方法(只能编译器自己来创造这种奇迹,我们程序员却不能人为的编写这种代码)。JVM还是能够分清楚这些方法的,前提是需要返回类型不一样。

     

    (2) 泛型类型中的方法冲突

     

           还是来看一段代码:

    Java代码  
  • //在上面代码中加入equals方法   
  • public   class  Pair<T>{  
  •       public   boolean  equals(T value){  
  •             return  (first.equals(value));  
  •       }  
  • }  
  •         这样看似乎没有问题的代码连编译器都通过不了:

           【Error】    Name clash: The method equals(T) of type Pair<T> has the same erasure as equals(Object) of type Object but does not override it。

            编译器说你的方法与Object中的方法冲突了。这是为什么?

     

            开始我也不太明白这个问题,觉得好像编译器帮助我们使得equals(T)这样的方法覆盖上了Object中的equals(Object)。经过大家的讨论,我觉得应该这么解释这个问题?

     

            首先、我们都知道子类方法要覆盖,必须与父类方法具有相同的方法签名(方法名+参数列表)。而且必须保证子类的访问权限>=父类的访问权限。这是大家都知道的事实。

            然后、在上面的代码中,当编译器看到Pair<T>中的equals(T)方法时,第一反应当然是equals(T)没有覆盖住父类Object中的equals(Object)了。

            接着、编译器将泛型代码中的T用Object替代(擦除)。突然发现擦除以后equals(T)变成了equals(Object),糟糕了,这个方法与 Object类中的equals一样了。基于开始确定没有覆盖这样一个想法,编译器彻底的疯了(精神分裂)。然后得出两个结论:①坚持原来的思想:没有覆 盖。但现在一样造成了方法冲突了。   ②写这程序的程序员疯了(哈哈)。

     

            再说了,拿Pair<T>对象和T对象比较equals,就像牛头对比马嘴,哈哈,逻辑上也不通呀。

     

    (3) 没有泛型数组一说

     

          Pair<String>[] stringPairs=new Pair<String>[10];

          Pair<Integer>[] intPairs=new Pair<Integer>[10];

          这种写法编译器会指定一个Cannot create a generic array of Pair<String>的错误

     

          我们说过泛型擦除之后,Pair<String>[]会变成Pair[],进而又可以转换为Object[];

          假设泛型数组存在,那么

                Object[0]=stringPairs[0]; Ok

                Object[1]=intPairs[0]; Ok

           这就麻烦了,理论上将Object[]可以存储所有Pair对象,但这些Pair对象是泛型对象,他们的类型变量都不一样,那么调用每一个 Object[]数组元素的对象方法可能都会得到不同的记过,也许是个字符串,也许是整形,这对于JVM可是无法预料的。

     

          记住: 数组必须牢记它的元素类型,也就是所有的元素对象都必须一个样,泛型类型恰恰做不到这一点。即使Pair<String>,Pair<Integer>... 都是Pair类型的,但他们还是不一样。

     


    总结:泛型代码与JVM
        ① 虚拟机中没有泛型,只有普通类和方法。
        ② 在编译阶段,所有泛型类的类型参数都会被Object或者它们的限定边界来替换。(类型擦除)
        ③ 在继承泛型类型的时候,桥方法的合成是为了避免类型变量擦除所带来的多态灾难。


        
    [2] Toast展示和关闭自个控制的方法
        来源: 互联网  发布时间: 2014-02-18
    Toast显示和关闭自个控制的方法

     Toast信息提示框之所以在显示一定时间后会自动关闭,是因为在系统中有一个Toast队列。系统会依次从队列中取(出队列)一个Toast,并显示 它。在显示一段时间后,再关闭,然后再显示下一个Toast信息提示框。直到Toast队列中所有Toast都显示完为止。那么有些时候需要这个 Toast信息提示框长时间显示,直到需要关闭它时通过代码来控制,而不是让系统自动来关闭Toast信息提示框。不过这个要求对于Toast本身来说有 些过分,因为Toast类并没有提供这个功能。虽然如此,但方法总比问题多。通过一些特殊的处理还是可以实现这个功能的,而且并不复杂。

          从7.3.1节的内容可以知道,Toast信息提示框需要调用Toast.show方法来显示。下面来看一下show方法的源代码。

    public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }
        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        TN tn = mTN;
        try {
            //  将当前Toast加入到Toast队列
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }
    show方法的代码并不复杂,可以很容易找到如下的代码。

    service.enqueueToast(pkg, tn, mDuration);

          从上面的代码可以很容易推断出它的功能是将当前的Toast加入到系统的Toast队列中。看到这里,各位读者应该想到。虽然show方法的表面功能是 显示Toast信息提示框,但其实际的功能是将Toast加入到队列中,再由系统根据Toast队列来显示Toast信息提示框。那么我们经过更进一步地 思考,可以大胆地做出一个初步的方案。既然系统的Toast队列可以显示Toast信息提示框,那么我们为什么不可以自己来显示它呢?这样不是可以自己来 控制Toast的信息提示框的显示和关闭了吗!当然,这就不能再调用show方法来显示Toast信息提示框了(因为show方法会将Toast加入队 列,这样我们就控制不了Toast了)。

         既然初步方案已拟定,现在就来实施它。先在Toast类找一下还有没有其他的show方法。结果发现了一个TN类,该类是Toast的一个内嵌类。 在TN类中有一个show方法。TN是ITransientNotification.Stub的子类。从ITransientNotification 和TN类中的show方法初步推断(因为Transient的中文意思是“短暂的”)系统是从Toast队列中获得了Toast对象后,利用TN对象的 show方法显示Toast,再利用TN.hide方法来关闭Toast。首先声明,这只是假设,我们还不知道这么做是否可行!当然,这也是科学研究的一 般方法,先推断或假设,然后再证明推断或假设。

         现在关键的一步是获得TN对象。遗憾的是TN被声明成private类型,外部无法访问。不过别着急。在Toast类中有一个mTN变量。虽然不是 public变量,但仍然可以通过反射技术访问该变量。mTN变量会在创建Toast对象时初始化。因此,只要获得mTN变量,就获得了TN对象。下面的 代码显示了一个永远不会自动关闭的Toast信息提示框。

    //  先创建一个Toast对象
    Toast toast = Toast.makeText(this, "永不消失的Toast", Toast.LENGTH_SHORT);
    //  设置Toast信息提示框显示的位置(在屏幕顶部水平居中显示)
    toast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 0);
    try
    {
        //  从Toast对象中获得mTN变量
        Field field = toast.getClass().getDeclaredField("mTN");
        field.setAccessible(true);
                Object obj = field.get(toast);
        //  TN对象中获得了show方法
                Method method =  obj.getClass().getDeclaredMethod("show", null);
        //  调用show方法来显示Toast信息提示框
                method.invoke(obj, null);
    }
    catch (Exception e)
    {
    }
         上面的代码中try{…}catch(…){…}语句中的代码是关键。先利用事先创建好的Toast对象获得了mTN变量。然后再利用反射技术获得了TN对象的show方法。
    关闭Toast和显示Toast的方法类似,只是需要获得hide方法,代码如下:
    try
    {
    //  需要将前面代码中的obj变量变成类变量。这样在多个地方就都可以访问了
    Method method =  obj.getClass().getDeclaredMethod("hide", null);
        method.invoke(obj, null);
    }
    catch (Exception e)
    {
    }
         上面的代码已经很完美地实现了通过代码控制Toast信息提示框显示和关闭的功能。但如果想实现得更完美,可以在Android SDK源代码中找一个叫ITransientNotification.aidl的文件(该文件是AIDL服务定义文件,将在后面详细介绍),并在 Android工程的src目录中建一个android.app包,将这个文件放到这个包中。然后ADT会自动在gen目录中生成了一个 android.app包,包中有一个ITransientNotification.java文件。由于Android SDK自带的ItransientNotification接口属于内部资源,外部程序无法访问,因此,只能将从Toast对象中获得的mTN变量转换成 刚才生成的ITransientNotification对象了。这样就不需要使反射技术获得show和hide方法了。经过改良的显示和关闭Toast 信息提示框的代码如下:
    ITransientNotification  notification = (ITransientNotification) field.get(toast);
    //  显示Toast信息提示框
    notification.show();
    //  关闭Toast信息提示框
    notification.hide();


        
    [3] Dalvik(札记)
        来源: 互联网  发布时间: 2014-02-18
    Dalvik(笔记)
    Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一。它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。
    dx 是一套工具,可以将 Java .class 转换成 .dex 格式. 一个dex档通常会有多个.class。由于dex有时必须进行最佳化,会使档案大小增加1-4倍,以ODEX结尾。
    Dalvik虚拟机有自己的 bytecode, 并非使用 Java bytecode.

        
    最新技术文章:
    ▪Android开发之登录验证实例教程
    ▪Android开发之注册登录方法示例
    ▪Android获取手机SIM卡运营商信息的方法
    ▪Android实现将已发送的短信写入短信数据库的...
    ▪Android发送短信功能代码
    ▪Android根据电话号码获得联系人头像实例代码
    ▪Android中GPS定位的用法实例
    ▪Android实现退出时关闭所有Activity的方法
    ▪Android实现文件的分割和组装
    ▪Android录音应用实例教程
    ▪Android双击返回键退出程序的实现方法
    ▪Android实现侦听电池状态显示、电量及充电动...
    ▪Android获取当前已连接的wifi信号强度的方法
    ▪Android实现动态显示或隐藏密码输入框的内容
    ▪根据USER-AGENT判断手机类型并跳转到相应的app...
    ▪Android Touch事件分发过程详解
    ▪Android中实现为TextView添加多个可点击的文本
    ▪Android程序设计之AIDL实例详解
    ▪Android显式启动与隐式启动Activity的区别介绍
    ▪Android按钮单击事件的四种常用写法总结
    ▪Android消息处理机制Looper和Handler详解
    ▪Android实现Back功能代码片段总结
    ▪Android实用的代码片段 常用代码总结
    ▪Android实现弹出键盘的方法
    ▪Android中通过view方式获取当前Activity的屏幕截...
    ▪Android提高之自定义Menu(TabMenu)实现方法
    ▪Android提高之多方向抽屉实现方法
    ▪Android提高之MediaPlayer播放网络音频的实现方法...
    ▪Android提高之MediaPlayer播放网络视频的实现方法...
    ▪Android提高之手游转电视游戏的模拟操控
     


    站内导航:


    特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

    ©2012-2021,,E-mail:www_#163.com(请将#改为@)

    浙ICP备11055608号-3