• ABAP,Java和JavaScript类的构造函数使用的一些陷阱


    This question draws my attention during a discussion with my colleague recently.
    I will explain it in ABAP, Java and JavaScript.

    In ABAP

    I have a super class and a sub class.
    Source code for super class:

    class ZCL_SUPER definition
      public
      create public .
    public section.
      methods CONSTRUCTOR .
      methods SET_I
        importing
          !IV_I type INT4 .
    protected section.
    private section.
      data MV_SUPER type INT4 .
    ENDCLASS.
    CLASS ZCL_SUPER IMPLEMENTATION.
      method CONSTRUCTOR.
        me->set_i( 100 ).
      endmethod.
      method SET_I.
        me->mv_super = iv_i.
      endmethod.
    ENDCLASS.
    

    In constructor of super class, method set_i is called to set the member attribute mv_super to 100.
    And here is my sub class:

    class ZCL_SUB definition
      public
      inheriting from ZCL_SUPER
      final
      create public .
    public section.
      methods PRINT .
      methods SET_I
        redefinition .
    protected section.
    private section.
      data MV_SUB type I value 1 ##NO_TEXT.
    ENDCLASS.
    CLASS ZCL_SUB IMPLEMENTATION.
      method PRINT.
        WRITE: / ' sub:' , mv_sub.
      endmethod.
      METHOD set_i.
        super->set_i( iv_i = iv_i ).
        me->mv_sub = iv_i.
        WRITE: / 'mv_sub assigned by: ' , iv_i.
      ENDMETHOD.
    ENDCLASS.
    

    The redefinition of set_i is done in order to fill the member attribute mv_sub in sub class.
    And now in report, use this line for test:

    NEW zcl_sub( )->print( ).
    Originally I expect the following lines will be printed:

    mv_sub assigned by: 100
    sub: 100
    

    I am wrong. The actual output:

    sub: 1
    When debugging into the code, we can find the reason:

    in constructor, the redefinition of set_i done by sub class is not called at all, since the technical type of me reference points to super class. This makes sense since the execution context is constructor and the initialization of super class is not finished yet.

    In Java

    Let’s now see what will happen in Java.
    Super class:

    public class SuperClass {
    
    	private int mSuperX;
    
    	public SuperClass() {
    		setX(99);
    	}
    
    	public void setX(int x) {
    		mSuperX = x;
    	}
    }
    

    Sub class:

    public class SubClass extends SuperClass {
    
    	private int mSubX = 1;
    
    	public SubClass() {}
    
    	@Override
    	public void setX(int x) {
    		super.setX(x);
    		mSubX = x;
    		System.out.println("SubX is assigned " + x);
    	}
    
    	public void printX() {
    		System.out.println("SubX = " + mSubX);
    	}
    }
    

    test:

    public static void main(String[] args) {
    		SubClass sc = new SubClass();
    		sc.printX();
    	}
    

    Output:

    The constructor execution behavior is completely different in Java: in constructor, the redefinition of setX done in sub class now gets chance to be called. However, it is overwritten to default value 1 later.
    Use Javap to analyze the byte code.
    The magic of initialization lays in line 59: invokespecial #18.

    the byte code clearly shows the Java code new SubClass() will invoke SubClass.””.
    The #18 represents the entry in constant pool with id 18, which is SubClass.:

    So use Javap to review byte code of sub class again.

    Here explains why in the last line of output, subX is reset to 1, since the initialization of it is done AFTER super class’ constructor call.

    What logic is contained in SuperClass.””? Inspect SuperClass.”” again:

    12 points to SuperClass.setX:

    Byte code of setX:
    line 59: put this reference to stack
    line 60: push parameter x of method setX to stack
    line 61: put stack top element to variable #20, which is SuperClass.mSuperX:


    The execution sequence analyzed so far:
    SubClass.”” -> SuperClass.”” -> SubClass.setX(int) -> SuperClass.setX(int)
    Which could clearly be observed in callstack:

    In JavaScript

    I write an example via JavaScript to illustrate the execution logic in above example.
    Super class:

    function SuperClass(){
    	this.setX(99);
    }
    
    SuperClass.prototype = {
        mSuperX : 0,
        setX : function(x){
             this.mSuperX = x;
        }
    };
    

    Sub class:

    function SubClass(){  
        SuperClass.call(this);  
        this.mSubX = 1;
    }  
    
    SubClass.prototype = new SuperClass();  
    
    SubClass.prototype.setX = function(x){
        	SuperClass.prototype.setX(x);
    		this.mSubX = x;
    		console.log("SubX is assigned " + x);
    };
    
    SubClass.prototype.print = function(){
    	console.log("SubX: " + this.mSubX);
    }
    

    Test code:

    var sub = new SubClass();
    sub.print();
    

    Let’s step into code of new SubClass():

    (1) it will call SuperClass’ constructor:

    (2) In Super class constructor, since now this points to SubClass, so the redefinition of setX is called:

    (3) In redefined setX in SubClass, line 23 and line 24 will set attribute mSuperX in SuperClass and mSubX in Sub class accordingly.

    (4) finally:

    So we get the same output in Java:

    Conclusion

    If you call a non-final method in constructor and that method is redefined by sub class, be careful that your code might not work as you expect when you create a new instance of sub class:

    (1) In ABAP, the redefinition in sub class will NOT be called in constructor.
    (2) In Java, the constructor of super class is called BEFORE the initialization of sub class member attribute.

    要获取更多Jerry的原创文章,请关注公众号"汪子熙":

  • 相关阅读:
    jQuery 核心
    Visual Studio 文件没发布出来
    冷门JS技巧
    项目发布: error CS0103: 当前上下文中不存在名称“*****”
    jQuery编程的最佳实践
    HTML5中类jQuery选择器querySelector的使用
    html dl dt dd标签元素语法结构与使用
    EF Code First 更新数据库, 数据库迁移
    ASP.NET MVC中的拦截器
    C#Linq中的Union All/Union/Intersect和Top/Bottom和Paging和SqlMethods,skip,take,takewhile,skipwhile,编译查询等
  • 原文地址:https://www.cnblogs.com/sap-jerry/p/13598777.html
Copyright © 2020-2023  润新知