How should I declare a constant set visible for every instance of the class?

11,508

Solution 1

Do you want to add elements to your set once and then only read its contents or do you want to be able to add elements to it at any time? If you create it once, do it like this:

class YourClass {
    private static void fillSet(Set<SomeType> set) {
        // here you add elements, like
        set.add(new SomeType());
    }
    private final static Set<SomeType> yourSetField;
    static {
        final Set<SomeType> tempSet = new HashSet<SomeType>();
        fillSet(tempSet);
        yourSetField = Collections.unmodifiableSet(tempSet);
    }
}

Now - it's private, so no one outside your class can access it. And its unmodifiable, so no one can change its content.

If you want to add elements at any time, you have a concurrency problem - read extraneon's answer.

EDIT
As requested I explain what this code does.

First - mysterious <> brackets: I assumed you are using Java 1.5 or higher and used generics. In few words - if you declare a variable of type List, it holds Objects. If you wish to keep Strings in it, you have to cast them when you retrieve them from your List. Example

List myList = new ArrayList();
myList.add("Hello, my Jon Skeet Number decreases");
String firstElement = (String) myList.get(0);

The cast to String is required. Moreover nothing prevents you from adding BigDecimal to myList. But if you retrieve it and try to cast to String, you get ClassCastException.

myList.add(0, BigDecimal.ZERO); // perfectly legal
String anotherString = (String) myList.get(0); // compiles, but ClassCastException at runtime

So Java 1.5 introduces generics. You can specify, that List can contain only Strings. Syntax uses <> brackets:

List<String> myList = new ArrayList<String>();
myList.add("Hi everybody");
String firstElem = myList.get(0); // no cast required
myList.add(BigDecimal.ZERO); // compiler error: cannot cast BigDecimal to String

Same applies to other collections, like Sets. I wrote about Lists because retrieving from Lists is more convenient. I used SomeType in my example because I didn't know what you want to keep in your Set. You should replace it with type of objects you wish to store.

Now - static blocks. There are two ways to initialize static fields - directly in their declaration:

static private int instanceCount = 0;

This is useful if initial value is a simple expression.
Or in static initialization block

static {
    // some code, that can use class static variables, class static methods, declare its own variables etc.
}

This is useful, if initial value for some static fields needs some more complicated computations.

And now your questions

  1. The parameter set of fillSet is used. There is an element added to it: set.add(new SomeType());
  2. As I didn't know what you want to keep in your set, I named the type of its elements SomeType. It is to be replaced with the type you want to use. new SomeType(); creates an instance of (hypothetical) SomeType calling its parameterless constructor.
  3. fillSet does exactly what its name means - takes a set and fills it (adds some values to it). We give an empty set to it so that as a result we get a set with elements fillSet has put in it. fillSet is a place where you should put all the code that initializes your set. It's good to have this separated.
  4. tempSet is a local variable in static initialization block which is assigned once and never reassigned. To express this I declare it final. It's a habit I got from using findBugs and I think it's good for readability.
  5. Making yourSetField final means you can't write yourSetField = new HashSet<SomeType>() once it is initialized. But anyone who can access yourSetField can write yourSetField.add(...). If yourSetField is an unmodifiableSet, adding to it will cause an exception in runtime (UnsupportedOperationException). In other words: final means you cannot make yourSetField point to another object (and compiler guarantees it). unmodifiableSet means you cannot add or remove objects from the set. It will compile but will break in runtime.

Solution 2

In your class you want something like:

private static final Set<Foo> mySet;
static {
 // ...initialize contents here.  guava example looks like:
mySet = ImmutableSet.of( adc, 123, etc );

}

I would go with the Guava ImmutableSet as Jon suggests, so you'd be using the of( ... ) method or the builder interface (if you have some sort of feed of data - if you are hardcoding the data, just use of()), both of which are pretty well covered in the API. Other options include wrapping via the unmodifiable method from Collections.

Solution 3

It sounds like you do want static, not because of how changes are addressed but because it's not specific to any one instance of your class.

I suggest you have a final static variable, and in a static initializer block for your type you build a regular set, then create an immutable set (e.g. the one provided in Guava) from the regular one. Assign that immutable set reference to your static final variable. Job done.

Solution 4

I think you know what static means. As you mentioned the Set, but not the contents, is a "constant", that is, no instance may put a different Set there, you would do well to make it final.

A final static Set is the same for all instances, and the contents of that Set can be changed by all instances.

A different problem now arises; concurrency. What if multiple instances of your class m,odify the Set at the same time? What should the Set do? You could catch that by wrapping your set in a Synchronized Set.

All in all I think the declaration should look like:

private static final Set<YourElement> mySet = Collections.synchronizedSet(new HashSet());

where the contents which you know beforehand can be filled in the static block as Bozho showed, and other elements can be added run-time.

With such a declaration a statement like

void myFoo() {
  mySet = new HashSet(); // will fail as it's final
}

will fail as it's supposed to do, and concurrent updates to the set will work.

If you need a Set with constant values, you could do:

private static final Set<YourElement> mySet;
static {
   Set<YourElement> tmpSet = new HashSet();
   tmpSet.add(...);
   mySet = Collections.unmodifiableSet(tmpSet);
}

But I see someone else was first :)

Share:
11,508
Roman
Author by

Roman

Updated on June 04, 2022

Comments

  • Roman
    Roman almost 2 years

    I would like to have a constant set in my class which would be visible for all instances of the class.

    First, I do not know if I need to declare it as "static". As far as I understand any changes of a static field (done by one of the instances) will be seen by other instances (so static variable is not bound to a specific instance). Moreover, a static field can be changes without usage of any instance (we work directly with the class). So, all these special properties of the static field are related with the way how it can be changed and what be the effect of these changes. But in my case I would like to have a constant (so the "changes" issues are not relevant here). So, probably I do not need to use "static". Right?

    Second, my set will contain a lot of elements and I do not want to define the value of the set at once (when I create the variable). In other words I would like to declare a set, then add elements to this set step by step. But I cannot do it if I work with constants. Is it possible to specified value of the set and then make it constant?

    Third, I have realized that there can be some problems if I try to change value of variables outside of any method. So, how does it work?

    ADDED:

    OK. Thanks to the answer I understood that it should be "final" and "static" (since it is a constant set and it will not be associated with any particular instance, it just should be visible to all instances of the class). However I still have a problem. I wanted to specify the set using "add" and I cannot add to the set if it is constant (final). Moreover, I cannot change values of the variables outside methods (why?). Anyway, I do not insist on the usage of "add" to define the set. I am ready to define it at once. But I do not know how to do it. I tried things like that:

    final static Set allowedParameters = new HashSet("aaa","bbb");
    final static Set allowedParameters = new HashSet(["aaa","bbb"]);
    final static Set allowedParameters = new HashSet({"aaa","bbb"});
    final static Set allowedParameters = new HashSet(Arrays.asList({"userName"}));
    

    And they did not work.

    ADDED 2:

    Can anybody explain me, pleas, the code given by Tadeusz Kopec?

    class YourClass {
        private static void fillSet(Set<SomeType> set) {
            // here you add elements, like
            set.add(new SomeType());
        }
        private final static Set<SomeType> yourSetField;
        static {
            final Set<SomeType> tempSet = new HashSet<SomeType>();
            fillSet(tempSet);
            yourSetField = Collection.unmodifiableSet(tempSet);
        }
    }
    


    1. The fillSet method has one variable called "set". Why it is not used in the method?
    2. What is SomeType() in the fillSet? What does this method do?
    3. What does fillSet do? Later in the example we give an empty set to this method. What for?
    4. What for do we declare tempSet as final?
    5. What exactly unmodifiableSet do? According to the name it generate a set which cannot be modified, I think. But why would it be insufficient to declare yourSetField as final? Than it would be constant too.

  • SteveD
    SteveD about 14 years
    Since it's Java, I assume you mean a static initializer block?
  • Roman
    Roman about 14 years
    Actually, I want to have a constant set. So, I will not have a situation when several instances try to modify the set at the same time. I wanted to define the set using "add" and it was a problem (because I cannot add to a constant set and I cannot change variable outside of methods). At the moment I do not insist on that solution (using add). It is OK if I define the set at once. But I still do not know how to do it. final static Set nameOfSet = new HashSet("aaa","bbb") do not work.
  • Roman
    Roman about 14 years
    I do not understand what static {...} means. I always thought that it is just a modifier for fields and methods. But apparently it is not the case. I also do not understand this kind of stuff <>. Do I really need to put these brackets there? Or they just indicate that it is something optional? Do I replace SomeType or <SomeType>?
  • Roman
    Roman about 14 years
    OK. I got the static block stuff. <> is still under the question.
  • seanhodges
    seanhodges about 14 years
    The <...> are part of the Generics specification. Wikipedia has a good article on it: en.wikipedia.org/wiki/Generics_in_Java
  • Roman
    Roman about 14 years
    I also do not understand the difference between "constant set" and "constant content of the set". For me it is the same. I think that set is constant if its value (i.e. content) is constant.
  • Adam Parkin
    Adam Parkin over 11 years
    Typo in the suggested code: it's Collections.unmodifiableSet (plural), not Collection.unmodifiableSet