Skip to content

UTC should be a class#306

Merged
xiangyushawn merged 1 commit intomicrosoft:devfrom
marschall:utc-class-instead-of-enum
Jun 14, 2017
Merged

UTC should be a class#306
xiangyushawn merged 1 commit intomicrosoft:devfrom
marschall:utc-class-instead-of-enum

Conversation

@marschall
Copy link
Copy Markdown
Contributor

UTC is an enum with one constant and a static variable. It has the
following comment

The enum type delays initialization until first use.

This is demonstrably wrong.

The static variable is initialized when the class is initialized. If we
look at the decompiled static initializer of UTC we see that.

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 522 L0
    NEW com/microsoft/sqlserver/jdbc/UTC
    DUP
    LDC "INSTANCE"
    ICONST_0
    INVOKESPECIAL com/microsoft/sqlserver/jdbc/UTC.<init> (Ljava/lang/String;I)V
    PUTSTATIC com/microsoft/sqlserver/jdbc/UTC.INSTANCE : Lcom/microsoft/sqlserver/jdbc/UTC;
    ICONST_1
    ANEWARRAY com/microsoft/sqlserver/jdbc/UTC
    DUP
    ICONST_0
    GETSTATIC com/microsoft/sqlserver/jdbc/UTC.INSTANCE : Lcom/microsoft/sqlserver/jdbc/UTC;
    AASTORE
    PUTSTATIC com/microsoft/sqlserver/jdbc/UTC.ENUM$VALUES : [Lcom/microsoft/sqlserver/jdbc/UTC;
   L1
    LINENUMBER 524 L1
    NEW java/util/SimpleTimeZone
    DUP
    ICONST_0
    LDC "UTC"
    INVOKESPECIAL java/util/SimpleTimeZone.<init> (ILjava/lang/String;)V
    PUTSTATIC com/microsoft/sqlserver/jdbc/UTC.timeZone : Ljava/util/TimeZone;
    RETURN
    MAXSTACK = 4
    MAXLOCALS = 0

@codecov-io
Copy link
Copy Markdown

codecov-io commented May 20, 2017

Codecov Report

Merging #306 into dev will decrease coverage by 0.08%.
The diff coverage is 0%.

Impacted file tree graph

@@             Coverage Diff              @@
##                dev     #306      +/-   ##
============================================
- Coverage     37.33%   37.25%   -0.09%     
+ Complexity     1669     1667       -2     
============================================
  Files           103      103              
  Lines         23667    23667              
  Branches       3880     3880              
============================================
- Hits           8835     8816      -19     
- Misses        13239    13260      +21     
+ Partials       1593     1591       -2
Flag Coverage Δ Complexity Δ
#JDBC41 37.09% <0%> (+0.01%) 1657 <0> (+2) ⬆️
#JDBC42 37.14% <0%> (-0.06%) 1662 <0> (-1)
Impacted Files Coverage Δ Complexity Δ
...in/java/com/microsoft/sqlserver/jdbc/IOBuffer.java 46.04% <0%> (-0.65%) 0 <0> (ø)
src/main/java/microsoft/sql/DateTimeOffset.java 37.14% <0%> (-2.86%) 8% <0%> (-2%)
...a/com/microsoft/sqlserver/jdbc/PLPInputStream.java 39.05% <0%> (-1.19%) 28% <0%> (-1%)
...om/microsoft/sqlserver/jdbc/ReaderInputStream.java 44.94% <0%> (ø) 16% <0%> (ø) ⬇️
...om/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java 48.9% <0%> (ø) 211% <0%> (ø) ⬇️
...m/microsoft/sqlserver/jdbc/SQLServerResultSet.java 27.23% <0%> (+0.04%) 187% <0%> (+1%) ⬆️
...rc/main/java/com/microsoft/sqlserver/jdbc/DDC.java 30.35% <0%> (+0.66%) 56% <0%> (ø) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 43dda17...1eb610a. Read the comment docs.

@xiangyushawn
Copy link
Copy Markdown
Contributor

Hello @marschall , thank you for your findings and knowledge sharing. However, I created a sample program and it seems like Enum does delay the initialization of the variable..

The following is my sample program.. could you please tell me what I do wrong? Thank you!

public class try_ENUM_initialization {

    public static void main(String[] args) throws InterruptedException {
        Hello hello = new Hello();

        Thread.sleep(3000);

        hello.say();
    }
}

class Hello {

    enum hhh {
        haha;

        static final Long ll = System.currentTimeMillis();
    }

    public Hello() {
        System.out.println("init");
        System.out.println(System.currentTimeMillis());
    }

    public void say() {
        System.out.println("say hello");
        System.out.println(hhh.ll);
    }
}

@marschall
Copy link
Copy Markdown
Contributor Author

Indeed class loading is delayed until the variable is accessed.

@marschall
Copy link
Copy Markdown
Contributor Author

Sorry for closing too early

Consider the following example

public class DemonstraceEnumInitialization {

  public static void main(String[] args) throws InterruptedException {
    System.out.println("not accessing constants");

    TimeZone tz;
    tz = 1 < 2 ? null : UtcEnum.timeZone;
    System.out.println(tz);
    tz = 1 < 2 ? null : UtcClass.timeZone;
    System.out.println(tz);

    System.out.println("accessing constants");
    System.out.println(UtcEnum.timeZone);
    System.out.println(UtcClass.timeZone);

  }
}

enum UtcEnum {
  INSTANCE;

  static final TimeZone timeZone = utcTimeZone();

  private static TimeZone utcTimeZone() {
    System.out.println("UtcEnum.utcTimeZone()");
    return new SimpleTimeZone(0, "UTC");
  }

}

class UtcClass {
  static final TimeZone timeZone = utcTimeZone();

  private static TimeZone utcTimeZone() {
    System.out.println("UtcClass.utcTimeZone()");
    return new SimpleTimeZone(0, "UTC");
  }

}

The output is:

not accessing constants
null
null
accessing constants
UtcEnum.utcTimeZone()
java.util.SimpleTimeZone[id=UTC,offset=0,dstSavings=3600000,useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,endTimeMode=0]
UtcClass.utcTimeZone()
java.util.SimpleTimeZone[id=UTC,offset=0,dstSavings=3600000,useDaylight=false,startYear=0,startMode=0,startMonth=0,startDay=0,startDayOfWeek=0,startTime=0,startTimeMode=0,endMode=0,endMonth=0,endDay=0,endDayOfWeek=0,endTime=0,endTimeMode=0]

What we see is

  • class loading does not happen until the constant is actually used
  • using an enum makes no difference, it behaves the same as a class

@xiangyushawn
Copy link
Copy Markdown
Contributor

xiangyushawn commented Jun 13, 2017

Hello @marschall thank you for your sharing. I also noticed, for static variable, there was no difference between enum or class in term of initialization delay.

My opinion was leaving it as is since there is no difference in behaviour or error caused by it. However, if there is any reason that we should use class over enum, please share with us. Then we can change it after testing in our test lab. Thank you!

@xiangyushawn
Copy link
Copy Markdown
Contributor

xiangyushawn commented Jun 13, 2017

Also, there is one more reason I prefer enum over class. Demonstrated by the following code:

public class DemonstraceEnumInitialization  {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("not accessing constants");

        Long tz;
        tz = 1 < 2 ? null : UtcEnum.longV;
        System.out.println(tz);
        tz = 1 < 2 ? null : UtcClass.longV;
        System.out.println(tz);

        new UtcClass();

        System.out.println("accessing constants");

        Thread.sleep(3000);

        System.out.println(UtcEnum.longV);
        System.out.println(UtcClass.longV);

    }
}

enum UtcEnum {
    INSTANCE;
    static Long longV = System.currentTimeMillis();
}

class UtcClass {
    static Long longV = System.currentTimeMillis();
}

output is:

not accessing constants
null
null
accessing constants
1497377762242
1497377759242

For class, it is possible to call the constructor. Once constructor is called, the static variable is initialized. With enum, we can make sure the variable is initialized only when it is called explicitly. What do you think?

@marschall
Copy link
Copy Markdown
Contributor Author

For class, it is possible to call the constructor. Once constructor is called, the static variable is initialized.

While it is true that the enum prevents calling the constructor it does not prevent using the enum constant. Using the enum constant initializes the class just like calling the constructor. If you change the code to use the enum constant it behaves the same way as with a constructor

public class DemonstrateEnumInitialization  {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("not accessing constants");

        Long tz;
        tz = 1 < 2 ? null : UtcEnum.longV;
        System.out.println(tz);
        tz = 1 < 2 ? null : UtcClass.longV;
        System.out.println(tz);

        UtcEnum utc = UtcEnum.INSTANCE;

        System.out.println("accessing constants");

        Thread.sleep(3000);

        System.out.println(UtcEnum.longV);
        System.out.println(UtcClass.longV);

    }
}

enum UtcEnum {
    INSTANCE;
    static Long longV = System.currentTimeMillis();
}

class UtcClass {
    static Long longV = System.currentTimeMillis();
}

I would add four things

  1. The UTC class is package scoped, it can only be seen by the driver and not by clients. The potential for misuse is minimal.
  2. Using the constructor is almost guaranteed to generate a warning in the IDE. Either the "Non-static access to static member" check for the "Unused object allocation" check which is unfortunately disabled.
  3. Using an enum to prevent instantiation is not idiomatic Java. I have not seen this pattern anywhere else. The pattern everybody else uses is the one suggested in Effective Java which is to use a private constructor. This is also the pattern used in the GregorianChange, Nanos and TDS classes in the same file. I will update the PR with this.
  4. Using an enum to prevent instantiation is not consistent with existing driver code as demonstrated by the GregorianChange, Nanos and TDS classes in the same file.

UTC is an enum with one constant and a static variable. It has the
following comment

> The enum type delays initialization until first use.

This is demonstrably wrong.

The static variable is initialized when the class is initialized. If we
look at the decompiled static initializer of UTC we see that.

```
  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 522 L0
    NEW com/microsoft/sqlserver/jdbc/UTC
    DUP
    LDC "INSTANCE"
    ICONST_0
    INVOKESPECIAL com/microsoft/sqlserver/jdbc/UTC.<init> (Ljava/lang/String;I)V
    PUTSTATIC com/microsoft/sqlserver/jdbc/UTC.INSTANCE : Lcom/microsoft/sqlserver/jdbc/UTC;
    ICONST_1
    ANEWARRAY com/microsoft/sqlserver/jdbc/UTC
    DUP
    ICONST_0
    GETSTATIC com/microsoft/sqlserver/jdbc/UTC.INSTANCE : Lcom/microsoft/sqlserver/jdbc/UTC;
    AASTORE
    PUTSTATIC com/microsoft/sqlserver/jdbc/UTC.ENUM$VALUES : [Lcom/microsoft/sqlserver/jdbc/UTC;
   L1
    LINENUMBER 524 L1
    NEW java/util/SimpleTimeZone
    DUP
    ICONST_0
    LDC "UTC"
    INVOKESPECIAL java/util/SimpleTimeZone.<init> (ILjava/lang/String;)V
    PUTSTATIC com/microsoft/sqlserver/jdbc/UTC.timeZone : Ljava/util/TimeZone;
    RETURN
    MAXSTACK = 4
    MAXLOCALS = 0
```
@xiangyushawn
Copy link
Copy Markdown
Contributor

Hello @marschall Thank you for your reply. I am convinced by point 3

The pattern everybody else uses is the one suggested in Effective Java which is to use a private constructor.

This definitely addresses my concern. I am going to test the changes and merge it once test is done. Thank you very much for your sharing and explanation.

@xiangyushawn xiangyushawn merged commit 244c59d into microsoft:dev Jun 14, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants