Java Compat Mapping for Classes
On this page:
Basic Java Mapping for Classes
A Slice class is mapped to a Java class with the same name. The generated class contains a public data member for each Slice data member (just as for structures and exceptions). Consider the following class definition:
class TimeOfDay { short hour; // 0 - 23 short minute; // 0 - 59 short second; // 0 - 59 string tz; // e.g. GMT, PST, EDT... }
The Slice compiler generates the following code for this definition:
public class TimeOfDay extends Ice.ObjectImpl { public short hour; public short minute; public short second; public TimeOfDay(); public TimeOfDay(short hour, short minute, short second); // Server-side implementation of various Ice::Object operations: public boolean ice_isA(String s, Ice.Current __current); public String[] ice_ids(Ice.Current __current); public String ice_id(Ice.Current __current); public static String ice_staticId(); public TimeOfDay clone(); // ... }
There are a number of things to note about the generated code:
- The generated class
TimeOfDay
inherits (indirectly) fromIce.Object
. This means that all classes implicitly inherit fromIce.Object
, which is the ultimate ancestor of all classes. Note thatIce.Object
is not the same asIce.ObjectPrx
. In other words, you cannot pass a class where a proxy is expected and vice versa.
If a class has only data members, but no operations, the compiler generates a non-abstract class. - The generated class contains a public member for each Slice data member.
- The generated class has a constructor that takes one argument for each data member, as well as a default constructor.
There is quite a bit to discuss here, so we will look at each item in turn.
Inheritance from Ice::Object
Classes implicitly inherit from a common base class, Value
, which is mapped to Ice.Object
. Ice.Object
is the base class for all servants, as described in the Server-Side Java Compat Mapping for Interfaces.
Ice.Object
contains a number of methods:
public interface Object { boolean ice_isA(String s); boolean ice_isA(String s, Current current); void ice_ping(); void ice_ping(Current current); String[] ice_ids(); String[] ice_ids(Current current); String ice_id(); String ice_id(Current current); void ice_preMarshal(); void ice_postUnmarshal(); SlicedData ice_getSlicedData(); boolean ice_dispatch(Request request, DispatchInterceptorAsyncCallback cb); }
The Ice.Object
methods behave as follows:
ice_isA
This method returnstrue
if the object supports the given type ID, andfalse
otherwise.
ice_ping
As for interfaces,ice_ping
provides a basic reachability test for the class.
ice_ids
This method returns a string sequence representing all of the type IDs supported by this object, including::Ice::Object
.
ice_id
This method returns the actual run-time type ID for a class instance. If you callice_id
through a reference to a base instance, the returned type id is the actual (possibly more derived) type ID of the instance.
ice_preMarshal
The Ice run time invokes this method prior to marshaling the object's state, providing the opportunity for a subclass to validate its declared data members.
ice_postUnmarshal
The Ice run time invokes this method after unmarshaling an object's state. A subclass typically overrides this method when it needs to perform additional initialization using the values of its declared data members.ice_getSlicedData
This functions returns theSlicedData
object if the value has been sliced during un-marshaling ornull
otherwise.
ice_dispatch
This function dispatches an incoming request to a servant. It is used in the implementation of dispatch interceptors.
Note that the generated class does not override hashCode
and equals
. This means that classes are compared using shallow reference equality, not value equality (as is used for structures).
All Slice classes derive from Ice.Object
via the Ice.ObjectImpl
abstract base class. ObjectImpl
implements the java.io.Serializable
interface to support Java's serialization facility. ObjectImpl
also supplies an implementation of clone
that returns a shallow member-wise copy.
Operations Interfaces
If a Slice class declares or inherits operations, or implements an interface, the Slice-to-Java translator will generate two additional interfaces named _<class-name>Operations
and _<class-name>OperationsNC
. The methods in the _<class-name>Operations
interface have an additional trailing parameter of type Ice.Current
, whereas the methods in the _<class-name>OperationsNC
interface lack this additional trailing parameter. The methods without the Current
parameter simply forward to the methods with a Current
parameter, supplying a default Current
. For now, you can ignore this parameter and pretend it does not exist.
Class Data Members in Java
By default, data members of classes are mapped exactly as for structures and exceptions: for each data member in the Slice definition, the generated class contains a corresponding public data member. A JavaBean-style API is used for optional data members, and you can customize the mapping to force required members to use this same API.
If you wish to restrict access to a data member, you can modify its visibility using the protected
metadata directive. The presence of this directive causes the Slice compiler to generate the data member with protected visibility. As a result, the member can be accessed only by the class itself or by one of its subclasses. For example, the TimeOfDay
class shown below has the protected
metadata directive applied to each of its data members:
class TimeOfDay { ["protected"] short hour; // 0 - 23 ["protected"] short minute; // 0 - 59 ["protected"] short second; // 0 - 59 ["protected"] string tz; // e.g. GMT, PST, EDT... }
The Slice compiler produces the following generated code for this definition:
public class TimeOfDay extends ... { protected short hour; protected short minute; protected short second; protected String tz; public TimeOfDay(); public TimeOfDay(short hour, short minute, short second); // ... }
For a class in which all of the data members are protected, the metadata directive can be applied to the class itself rather than to each member individually. For example, we can rewrite the TimeOfDay
class as follows:
["protected"] class TimeOfDay { short hour; // 0 - 23 short minute; // 0 - 59 short second; // 0 - 59 string tz; // e.g. GMT, PST, EDT... }
You can optionally customize the mapping for data members to use getters and setters instead.
Class Operations in Java
Operations on classes are deprecated as of Ice 3.7. Skip this section unless you need to communicate with old applications that rely on this feature.
Operations of classes are mapped to abstract member functions in the generated class. This means that, if a class contains operations (such as the format
operation of our TimeOfDay
class), you must provide an implementation of the operation in a class that is derived from the generated class. For example:
public class FormattedTimeOfDayI extends FormattedTimeOfDay { public String format(Ice.Current current) { DecimalFormat df = (DecimalFormat)DecimalFormat.getInstance(); df.setMinimumIntegerDigits(2); return new String(df.format(hour) + ":" + df.format(minute) + ":" + df.format(second)); } }
Value Factories in Java
While value factories are necessary when using classes with operations (a now deprecated feature), value factories may be used for any kind of class and are not deprecated.
Having created a class such as FormattedTimeOfDayI
, we have an implementation and we can instantiate the FormattedTimeOfDayI
class, but we cannot receive it as the return value or as an out-parameter from an operation invocation. To see why, consider the following simple interface:
interface Time { FormattedTimeOfDay get(); }
When a client invokes the get
operation, the Ice run time must instantiate and return an instance of the FormattedTimeOfDay
class. However, FormattedTimeOfDay
is an abstract class that cannot be instantiated. Unless we tell it, the Ice run time cannot magically know that we have created a FormattedTimeOfDayI
class that implements the abstract format
operation of the FormattedTimeOfDay
abstract class. In other words, we must provide the Ice run time with a factory that knows that the FormattedTimeOfDay
abstract class has a FormattedTimeOfDayI
concrete implementation.
To supply the Ice run time with a value factory for our FormattedTimeOfDayI
class, we must implement the ValueFactory
interface:
class ValueFactory implements Ice.ValueFactory { public Ice.Object create(String type) { assert(type.equals(FormattedTimeOfDay.ice_staticId())); return new FormattedTimeOfDayI(); } }
The factory's create
method is called by the Ice run time when it needs to instantiate a FormattedTimeOfDay
class.
The create
method receives the type ID of the class to instantiate. For our FormattedimeOfDay
class, the type ID is "::Module::FormattedTimeOfDay"
. Our implementation of create
checks the type ID: if it matches, the method instantiates and returns a FormattedTimeOfDayI
object. For other type IDs, the method asserts because it does not know how to instantiate other types of objects.
Note that we used the ice_staticId
method to obtain the type ID rather than embedding a literal string. Using a literal type ID string in your code is discouraged because it can lead to errors that are only detected at run time. For example, if a Slice class or one of its enclosing modules is renamed and the literal string is not changed accordingly, a receiver will fail to unmarshal the object and the Ice run time will raise NoValueFactoryException
. By using ice_staticId
instead, we avoid any risk of a misspelled or obsolete type ID, and we can discover at compile time if a Slice class or module has been renamed.
Given a factory implementation, such as our ValueFactory
, we must inform the Ice run time of the existence of the factory:
Ice.Communicator ic = ...; ic.getValueFactoryManager().add(new ValueFactory(), FormattedTimeOfDay.ice_staticId());
Now, whenever the Ice run time needs to instantiate a class with the type ID "::Module::FormattedTimeOfDay"
, it calls the create
method of our factory, which returns a FormattedTimeOfDayI
instance to the Ice run time.
Finally, keep in mind that if a class has only data members, but no operations, you do not need to create and register a value factory to receive instances of such a class. You're only required to register a value factory when a class has operations.
Class Constructors in Java
Classes have a default constructor that initializes data members as follows:
Data Member Type | Default Value |
---|---|
string | Empty string |
enum | First enumerator in enumeration |
struct | Default-constructed value |
Numeric | Zero |
bool | False |
sequence | Null |
dictionary | Null |
class /interface | Null |
The constructor won't explicitly initialize a data member if the default Java behavior for that type produces the desired results.
If you wish to ensure that data members of primitive and enumerated types are initialized to specific values, you can declare default values in your Slice definition. The default constructor initializes each of these data members to its declared value instead.
The generated class also contains a second constructor that accepts one argument for each member of the class. This allows you to create and initialize a class in a single statement, for example:
TimeOfDay tod = new TimeOfDay(14, 45, 00, "PST"); // 14:45pm PST
For derived classes, the constructor requires an argument for every member of the class, including inherited members. For example, consider the the definition from Class Inheritance once more:
class TimeOfDay { short hour; // 0 - 23 short minute; // 0 - 59 short second; // 0 - 59 } class DateTime extends TimeOfDay { short day; // 1 - 31 short month; // 1 - 12 short year; // 1753 onwards }
The constructors for the generated classes are as follows:
public class TimeOfDay extends ... { public TimeOfDay() {} public TimeOfDay(short hour, short minute, short second) { this.hour = hour; this.minute = minute; this.second = second; } // ... } public class DateTime extends TimeOfDay { public DateTime() {} public DateTime(short hour, short minute, short second, short day, short month, short year) { super(hour, minute, second); this.day = day; this.month = month; this.year = year; } // ... }
If you want to instantiate and initialize a DateTime
instance, you must either use the default constructor or provide values for all of the data members of the instance, including data members of any base classes.