分享

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

 汪子熙 2020-09-02

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: 100sub: 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.

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多