How should I declare a constant set visible for every instance of the class?
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 (开发者_StackOverflow中文版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.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
- The parameter
set
offillSet
is used. There is an element added to it:set.add(new SomeType());
- 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. 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 elementsfillSet
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.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.- Making yourSetField final means you can't write
yourSetField = new HashSet<SomeType>()
once it is initialized. But anyone who can accessyourSetField
can writeyourSetField.add(...)
. IfyourSetField
is an unmodifiableSet, adding to it will cause an exception in runtime (UnsupportedOperationException
). In other words: final means you cannot makeyourSetField
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.
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.
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.
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 :)
精彩评论