0

Today I'm running into the following issue:

I have three classes, (superclass) A, (subclass) B and my Main class. Here are some code examples of that structure:

public abstract class A {
    
    public static String testValue;

    public static boolean valueSet() {
        return testValue != null;
    }

}
public class B extends A {
    
    static {
        System.out.println("TEST");
        testValue = "foo";
    }
    
}
public class Main {

    public static void main(String[] args) {
        System.out.println(B.testValue);
        System.out.println(B.valueSet());
        System.out.println(new B());
        System.out.println(B.testValue);
        System.out.println(B.valueSet());
    }

}

I'd expect the following output:

foo
true
foo
true

however, the code outputs this instead:

null
false
foo
true

My final goal is to set testValue to some value in every subclass, and implement the checking function just once in the superclass. It has to be static, because it should just check for compatibility of a String against an Object of type A. Definining the using a method as public static abstract is not possible, as I've already read, and there is no way to abstract values, so I can't use that as well. I want the code to stay as simple as possible, without reimplementing the check for every subclass.

Thanks for any help or tips!

MonsterDruide1
  • 347
  • 3
  • 15
  • Why exactly does it need to be static? – Murat Karagöz Feb 03 '21 at 11:18
  • @MuratKaragöz This project has to deal with huge amounts of data. Initializing the object just to check whether it is compatible or not is a big waste of resources and speed. Therefor, I think it's faster and more logical to do the check in a static method, as then the object doesn't need to be initialized with the correct values before. – MonsterDruide1 Feb 03 '21 at 11:20
  • How about empty interfaces? You could check if the class is implementing that specific interface and assert the compatibility check that way. – Murat Karagöz Feb 03 '21 at 11:24
  • `B.testValue` is a misuse! The static variable `testValue` only belongs to `A`! – Seelenvirtuose Feb 03 '21 at 11:24
  • The instance data of A is only available once B has been constructed, because it is abstract. – jr593 Feb 03 '21 at 11:30
  • @MuratKaragöz That does not assert that it really is compatible. If it really works depends on the initializer content as well... I'm trying to write a decompression algorithm with a header check. The first few bytes should always identify the compression method being used. So comparing the first bytes makes sure that the correct decompression class is being used. – MonsterDruide1 Feb 03 '21 at 11:30
  • @Seelenvirtuose It's a variable every subclass has to set differently. I found it the most logical way to define it in the superclass, as this makes sure that every subclass of that type has the variable as well. – MonsterDruide1 Feb 03 '21 at 11:32
  • @jr593 Removing the `abstract` keyword from my superclass (A) doesn't change a thing in the output of the main class. – MonsterDruide1 Feb 03 '21 at 11:33

2 Answers2

3

You are not initializing the class B prior to calling new B() (in your code example)!

First of all, you are somewhat misusing the Java possibility of accesing static variables through subtypes. There is no B.testValue, it is only A.testValue. The compiler in fact replaces this. The same is true for the call to B.valueSet(). It is only A.valueSet().

So, you write

System.out.println(B.testValue);
System.out.println(B.valueSet());

but the compiler replaces it with

System.out.println(A.testValue);
System.out.println(A.valueSet());

because static members really belong to the class where they are declared.

A little change to your code shows that:

public class B extends A {
    public static String foo = "foo"; // Note the new static variable here.

    static {
        System.out.println("TEST");
        testValue = "foo";
    }
}

public class Main {

    public static void main(String[] args) {
        System.out.println(B.foo); // Note the new access here.
        System.out.println(B.testValue);
        System.out.println(B.valueSet());
        System.out.println(new B());
        System.out.println(B.testValue);
        System.out.println(B.valueSet());
    }

}

This now correctly prints

TEST
foo
foo
true
B@2a139a55
foo
true

because now your class B is initialized first.

Hint: You should always use the declaring class as the reference when accessing static members.

Seelenvirtuose
  • 20,273
  • 6
  • 37
  • 66
  • 1
    So, just to clarify... In the first half of my code `B` is never used, as it just accesses the fields of the superclass `A`? I just tried to output `Compression.magic` afterwards, it's the value being set by the subclass, which is completely wrong. Thanks for clarifying! I now understand, why this is happening. – MonsterDruide1 Feb 03 '21 at 11:55
1

There is nothing wrong with this question as the comments below the question suggest; however, your code cannot be reproduced, as it will, in no way, print whatever you're saying it prints.

Generally, it's always a great idea to use debugger, and see yourself the execution flow, step by step.

Let's see what is happening:

  1. System.out.println(B.testValue); interacts with class A, as B does not have a static testValue member, but A does (yes, static members are accessible in the hierarchy chain, as long as they are not hidden by another static member, defined into child class); therefore null is printed, as the default value for the reference types, is null;
  2. Similarly, your B.testValue() returns false, as testValue != null is false in the class A;
  3. By executing System.out.println(new B());, you will have extra two lines printed (which you omit in your question) before foo and true, as you interact with class B definition for the first time and it gets loaded into JVM - hence, its static block is executed (TEST is printed and testValue (of the superclass) is set to "foo");
  4. Then, again, you System.out.println(B.testValue);, and this time this field has already been initialized with "foo", so it prints the latter;
  5. and respectively, B.valueSet() will print true.

That is exactly:

null
false
TEST
Object Reference, like B@2133c8f8
foo
true
Giorgi Tsiklauri
  • 9,715
  • 8
  • 45
  • 66