Here's my sample code:
public class ExternalizableClass implements Externalizable
{
final int id;
public ExternalizableClass()
{
id = 0;
}
public ExternalizableClass(int i)
{
id = i;
}
#Override
public void writeExternal(ObjectOutput out) throws IOException
{
out.writeInt(id);
}
#Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
{
id = in.readInt();
}
#Override
public String toString()
{
return "id: " + id;
}
}
It fails to compile because id = in.readInt(); gives Error:(36, 5) java: cannot assign a value to final variable id. However, I can think of real use cases where an immutable field, such as id, needs to be externalized, while we also want to preserve its immutability.
So what's the correct way to resolve this issue?
The read function doesn't make much sense with the idea of a final field, because whatever value it was initialized to should be its value, forever. The read function shouldn't be able to change it.
Clearly objects initialized with the public ExternalizableClass(int i) constructor shouldn't be able to read a new value - if they can then their id value isn't really final. The only other way I could see doing this is making the default constructor initialize an "unread" instance, allowing you to call read on it later. This does, however, require removing the final modifier and working around that. So that would look like this:
public class ExternalizableClass implements Externalizable
{
private int id;
private boolean initted;
int getId(){
return id;
}
public ExternalizableClass(int i, boolean initted){
id = i;
this.initted = initted;
}
public ExternalizableClass(){
this(0, true); //Default instances can't be changed
}
public ExternalizableClass(int i)
{
this(i, true); //Instances from this constructor can't be changed either
}
#Override
public void writeExternal(ObjectOutput out) throws RuntimeException, IOException
{
if(! initted)
throw new RuntimeException("Can't write unitialized instance, " + this);
out.writeInt(id);
}
#Override
public void readExternal(ObjectInput in) throws RuntimeException, IOException, ClassNotFoundException
{
if(initted)
throw new RuntimeException("Can't Read into already initialized object ," + this);
id = in.readInt();
initted = true;
}
#Override
public String toString()
{
if(initted) return "id: " + id;
else return "No id";
}
}
Related
I have a enum defined like this and I would like to be able to obtain the strings for the individual statuses. How should I write such a method?
I can get the int values of the statuses but would like the option of getting the string values from the ints as well.
public enum Status {
PAUSE(0),
START(1),
STOP(2);
private final int value;
private Status(int value) {
this.value = value
}
public int getValue() {
return value;
}
}
if status is of type Status enum, status.name() will give you its defined name.
You can use values() method:
For instance Status.values()[0] will return PAUSE in your case, if you print it, toString() will be called and "PAUSE" will be printed.
Use default method name() as given bellows
public enum Category {
ONE("one"),
TWO ("two"),
THREE("three");
private final String name;
Category(String s) {
name = s;
}
}
public class Main {
public static void main(String[] args) throws Exception {
System.out.println(Category.ONE.name());
}
}
You can add this method to your Status enum:
public static String getStringValueFromInt(int i) {
for (Status status : Status.values()) {
if (status.getValue() == i) {
return status.toString();
}
}
// throw an IllegalArgumentException or return null
throw new IllegalArgumentException("the given number doesn't match any Status.");
}
public static void main(String[] args) {
System.out.println(Status.getStringValueFromInt(1)); // OUTPUT: START
}
I believe enum have a .name() in its API, pretty simple to use like this example:
private int security;
public String security(){ return Security.values()[security].name(); }
public void setSecurity(int security){ this.security = security; }
private enum Security {
low,
high
}
With this you can simply call
yourObject.security()
and it returns high/low as String, in this example
You can use custom values() method:
public enum SortType
{
Scored, Lasted;
public int value(){
return this == Lasted ? 1:0;
}
}
public class ImmutabilityOfReferenceInstance {
public static void main(String[] args) {
MClass mc = new MClass();
mc.setId(1);
ImClass imc1 = new ImClass(mc);
System.out.println("imc1 = "+imc1);
mc.setId(2);
ImClass imc2 = new ImClass(mc);
System.out.println("imc2 = "+imc2);
}
}
final class ImClass {
final private MClass mClass;
public ImClass(MClass mClass) {
this.mClass = mClass;
}
public MClass getmClass() {
return mClass;
}
#Override
public String toString() {
return String.valueOf(mClass.getId());
}
}
class MClass {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
I want to provide complete immutablity to the class IMClass, As we can see IMclass is immutable but it has an instance variable mclass that is the reference of MClass and MClass is a mutable class.
I have tried changing the getter method getmClass() as below
public MClass getmClass() {
return (MClass) mClass.clone();
}
but it is not allowing me to do so, Could some one please correct it that where i am getting wrong.
Thanks in advance
I have tried this but still getting the same result, values are getting updated
public class ImmutabilityOfReferenceInstance {
public static void main(String[] args) {
MClass mc = new MClass();
mc.setId(1);
ImClass imc1 = new ImClass(mc);
System.out.println("imc1 = "+imc1);
mc.setId(2);
ImClass imc2 = new ImClass(mc);
System.out.println("imc2 = "+imc2);
}
}
final class ImClass {
final private MClass mClass;
public ImClass(MClass mClass) {
this.mClass = (MClass)mClass.clone();
}
public MClass getmClass() {
return (MClass)mClass.clone();
}
#Override
public String toString() {
return String.valueOf(mClass.getId());
}
}
class MClass implements Cloneable{
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
There are a lot of good ideas floating around. Here is how I would summarize it:
Avoid using clone if possible, and favor using a copy-constructor instead. See Joshua Bloch's thoughts on this matter.
To ensure immutability, you need to make sure you copy the MClass instance that is passed to the ImClass constructor. Otherwise, whoever originally passed the MClass instance can still make changes to it.
Consider creating an immutable wrapper around the MClass class, perhaps by using inheritance.
This is one way that this can be achieved. There are certainly other ways:
public class ImmutabilityOfReferenceInstance {
public static void main(String[] args) {
MClass mc = new MClass();
mc.setId(1);
ImClass imc1 = new ImClass(mc);
System.out.println("imc1 before = " + imc1);
mc.setId(2);
System.out.println("imc1 after = " + imc1); // continues printing 1.
imc1.getmClass().setId(3); // changes not allowed on the immutable copy, throws exception.
}
}
public final class ImClass {
final private MClass mClass;
public ImClass(MClass mClass) {
this.mClass = (mClass == null ? null : mClass.createImmutableCopy());
}
public MClass getmClass() {
return mClass;
}
#Override
public String toString() {
return String.valueOf(mClass.getId());
}
}
public class MClass {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public MClass createImmutableCopy() {
return new ImmutableMClass(this);
}
private static class ImmutableMClass extends MClass {
public ImmutableMClass(MClass src) {
super.setId(src.getId());
}
#Override
public void setId(int id) {
throw new UnsupportedOperationException("immutable instance.");
}
}
}
EDIT: How to make the clone method work
If you still want to do it the cloning way, make sure you follow these 2 steps:
Expose the clone as a public method (as already suggested), but, ideally, without swallowing the exception so that you don't get an inexplicable NullPointerException if something doesn't work. Although, technically, the CloneNotSupportedException exception should never happen if you don't forget step #2.
Like this:
#Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
Make sure that the MClass implements the Cloneable interface.
Like this:
public class MClass implements Cloneable {
// ...
}
But again, to make sure that the private MClass instance within the ImClass is "immutable", you'll need to call clone in 2 places:
In the ImClass.getmClass() method, as you are already doing.
Also in the ImClass constructor. If you forget this one, then it is still possible to modify it, so immutability hasn't been achieved fully.
Like this:
public ImClass(MClass mClass) {
this.mClass = (MClass)mClass.clone();
}
EDIT 2: About why it appears that your code is still not working
The code should be working now, but if I look at your current main method, you are not testing immutability correctly. You are checking the values from 2 different instances of ImClass.
The following is a more valid test:
public static void main(String[] args) {
MClass mc = new MClass();
mc.setId(1);
ImClass imc = new ImClass(mc);
System.out.println("imc = " + imc); // should print 1
mc.setId(2);
System.out.println("imc = " + imc); // should still print 1 if immutability works
imc.getmClass().setId(3);
System.out.println("imc = " + imc); // should still print 1 if immutability works
}
If you are trying to achieve some kind of immutable wrapper around an a mutable class, perhaps better idea would be extending it and overriding all the places where it is mutated.
class IMWrapper extends MClass {
public IMWrapper(int id) {
super.setId(id);
}
#Override
void setId(int id) {
throw new UnsupportedOperationException("you can't modify this instance");
}
...
}
Defensive copying is a good idea, you should just implement copy constructor for MClass:
class MClass {
// ...
public MClass(MClass copied) {
this.id = copied.id;
}
}
You already narrowed down the problem to copying/cloning an object.
You can find the solution here: How do I copy an object in Java?
The problem is that the clone() method of MClass is not visible in ImClass.
It will work when you add the following method to MClass:
#Override
public Object clone() {
try {
return super.clone();
} catch (Exception e) {
return null;
}
}
And change your constructor to clone the object there as well (as by Jon Skeet's comment):
public ImClass(MClass mClass) {
this.mClass = (MClass)mClass.clone();
}
My working code
public class ImmutabilityOfReferenceInstance {
public static void main(String[] args) {
MClass mc = new MClass();
mc.setId(1);
ImClass imc = new ImClass(mc);
System.out.println("imc = " + imc); // should print 1
mc.setId(2);
System.out.println("imc = " + imc); // should still print 1 if immutability works
imc.getmClass().setId(3);
System.out.println("imc = " + imc); // should still print 1 if immutability works
}
}
final class ImClass {
final private MClass mClass;
public ImClass(MClass mClass) {
this.mClass = (MClass)mClass.clone();
}
public MClass getmClass() {
return (MClass)mClass.clone();
}
#Override
public String toString() {
return String.valueOf(mClass.getId());
}
}
class MClass implements Cloneable{
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
I have a enum defined like this and I would like to be able to obtain the strings for the individual statuses. How should I write such a method?
I can get the int values of the statuses but would like the option of getting the string values from the ints as well.
public enum Status {
PAUSE(0),
START(1),
STOP(2);
private final int value;
private Status(int value) {
this.value = value
}
public int getValue() {
return value;
}
}
if status is of type Status enum, status.name() will give you its defined name.
You can use values() method:
For instance Status.values()[0] will return PAUSE in your case, if you print it, toString() will be called and "PAUSE" will be printed.
Use default method name() as given bellows
public enum Category {
ONE("one"),
TWO ("two"),
THREE("three");
private final String name;
Category(String s) {
name = s;
}
}
public class Main {
public static void main(String[] args) throws Exception {
System.out.println(Category.ONE.name());
}
}
You can add this method to your Status enum:
public static String getStringValueFromInt(int i) {
for (Status status : Status.values()) {
if (status.getValue() == i) {
return status.toString();
}
}
// throw an IllegalArgumentException or return null
throw new IllegalArgumentException("the given number doesn't match any Status.");
}
public static void main(String[] args) {
System.out.println(Status.getStringValueFromInt(1)); // OUTPUT: START
}
I believe enum have a .name() in its API, pretty simple to use like this example:
private int security;
public String security(){ return Security.values()[security].name(); }
public void setSecurity(int security){ this.security = security; }
private enum Security {
low,
high
}
With this you can simply call
yourObject.security()
and it returns high/low as String, in this example
You can use custom values() method:
public enum SortType
{
Scored, Lasted;
public int value(){
return this == Lasted ? 1:0;
}
}
I've a generic field in User.java. I want to use the value of T in json.
public class User<T> {
public enum Gender {MALE, FEMALE};
private T field;
private Gender _gender;
private boolean _isVerified;
private byte[] _userImage;
public T getField() { return field; }
public boolean isVerified() { return _isVerified; }
public Gender getGender() { return _gender; }
public byte[] getUserImage() { return _userImage; }
public void setField(T f) { field = f; }
public void setVerified(boolean b) { _isVerified = b; }
public void setGender(Gender g) { _gender = g; }
public void setUserImage(byte[] b) { _userImage = b; }
}
and mapper class is:
public class App
{
public static void main( String[] args ) throws JsonParseException, JsonMappingException, IOException
{
ObjectMapper mapper = new ObjectMapper();
Name n = new Name();
n.setFirst("Harry");
n.setLast("Potter");
User<Name> user = new User<Name>();
user.setField(n);
user.setGender(Gender.MALE);
user.setVerified(false);
mapper.writeValue(new File("user1.json"), user);
}
}
and the json output is :
{"field":{"first":"Harry","last":"Potter"},"gender":"MALE","verified":false,"userImage":null}
In the output, i want Name to be appeared in place of field. How do i do that. Any help?
I think what u ask is not JSON's default behavior. Field name is the "key" of the json map, not the variable name. U should rename the field or make some String process to do it.
private T field;
change the above to this:
private T name;
You need a custom serializer to do that. That's a runtime data transformation and Jackson has no support for data transformation other than with a custom serializer (well, there's wrapping/unwrapping of value, but let's not go there). Also, you will need to know in advance every type of transformation you want to apply inside your serializer. The following works:
public class UserSerializer extends JsonSerializer<User<?>> {
private static final String USER_IMAGE_FIELD = "userImage";
private static final String VERIFIED_FIELD = "verified";
private static final String FIELD_FIELD = "field";
private static final String NAME_FIELD = "name";
#Override
public void serialize(User<?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonProcessingException {
jgen.writeStartObject();
if (value.field instanceof Name) {
jgen.writeFieldName(NAME_FIELD);
} else {
jgen.writeFieldName(FIELD_FIELD);
}
jgen.writeObject(value.field);
jgen.writeStringField("gender", value._gender.name());
jgen.writeBooleanField(VERIFIED_FIELD, value._isVerified);
if (value._userImage == null) {
jgen.writeNullField(USER_IMAGE_FIELD);
} else {
jgen.writeBinaryField(USER_IMAGE_FIELD, value._userImage);
}
jgen.writeEndObject();
}
}
Suppose I have an enumeration:
public enum SomeEnumType implements Writable {
A(0), B(1);
private int value;
private SomeEnumType(int value) {
this.value = value;
}
#Override
public void write(final DataOutput dataOutput) throws IOException {
dataOutput.writeInt(this.value);
}
#Override
public void readFields(final DataInput dataInput) throws IOException {
this.value = dataInput.readInt();
}
}
I want to pass an instance of it as a part of some other class instance.
The equals would not work, because it will not consider the inner variable of enumeration, plus all enum instances are fixed at compile time and could not be created elsewhere.
Does it mean I could not send enums over the wire in Hadoop or there's a solution?
My normal and preferred solution for enums in Hadoop is serializing the enums through their ordinal value.
public class EnumWritable implements Writable {
static enum EnumName {
ENUM_1, ENUM_2, ENUM_3
}
private int enumOrdinal;
// never forget your default constructor in Hadoop Writables
public EnumWritable() {
}
public EnumWritable(Enum<?> arbitraryEnum) {
this.enumOrdinal = arbitraryEnum.ordinal();
}
public int getEnumOrdinal() {
return enumOrdinal;
}
#Override
public void readFields(DataInput in) throws IOException {
enumOrdinal = in.readInt();
}
#Override
public void write(DataOutput out) throws IOException {
out.writeInt(enumOrdinal);
}
public static void main(String[] args) {
// use it like this:
EnumWritable enumWritable = new EnumWritable(EnumName.ENUM_1);
// let Hadoop do the write and read stuff
EnumName yourDeserializedEnum = EnumName.values()[enumWritable.getEnumOrdinal()];
}
}
Obviously it has drawbacks: Ordinals can change, so if you exchange ENUM_2 with ENUM_3 and read a previously serialized file, this will return the other wrong enum.
So if you know the enum class beforehand, you can write the name of your enum and use it like this:
enumInstance = EnumName.valueOf(in.readUTF());
This will use slightly more space, but it is more save to changes to your enum names.
The full example would look like this:
public class EnumWritable implements Writable {
static enum EnumName {
ENUM_1, ENUM_2, ENUM_3
}
private EnumName enumInstance;
// never forget your default constructor in Hadoop Writables
public EnumWritable() {
}
public EnumWritable(EnumName e) {
this.enumInstance = e;
}
public EnumName getEnum() {
return enumInstance;
}
#Override
public void write(DataOutput out) throws IOException {
out.writeUTF(enumInstance.name());
}
#Override
public void readFields(DataInput in) throws IOException {
enumInstance = EnumName.valueOf(in.readUTF());
}
public static void main(String[] args) {
// use it like this:
EnumWritable enumWritable = new EnumWritable(EnumName.ENUM_1);
// let Hadoop do the write and read stuff
EnumName yourDeserializedEnum = enumWritable.getEnum();
}
}
WritableUtils has convenience methods that make this easier.
WritableUtils.writeEnum(dataOutput,enumData);
enumData = WritableUtils.readEnum(dataInput,MyEnum.class);
I don't know anything about Hadoop, but based on the documentation of the interface, you could probably do it like that:
public void readFields(DataInput in) throws IOException {
// do nothing
}
public static SomeEnumType read(DataInput in) throws IOException {
int value = in.readInt();
if (value == 0) {
return SomeEnumType.A;
}
else if (value == 1) {
return SomeEnumType.B;
}
else {
throw new IOException("Invalid value " + value);
}
}