To chain any other exception types, use the initCause() method rather than the constructor.
// exceptions/DynamicFields.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// A Class that dynamically adds fields to itself to
// demonstrate exception chaining
class DynamicFieldsException extends Exception {}
public class DynamicFields {
private Object[][] fields;
public DynamicFields(int initialSize) {
fields = new Object[initialSize][2];
for (int i = 0; i < initialSize; i++) {
fields[i] = new Object[] {null, null};
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
for (Object[] obj : fields) {
result.append(obj[0]);
result.append(": ");
result.append(obj[1]);
result.append("\n");
}
return result.toString();
}
private int hasField(String id) {
for (int i = 0; i < fields.length; i++) {
if (id.equals(fields[i][0])) {
return i;
}
}
return -1;
}
private int getFieldNumber(String id) throws NoSuchFieldException {
int fieldNum = hasField(id);
if (fieldNum == -1) {
throw new NoSuchFieldException();
}
return fieldNum;
}
private int makeField(String id) {
for (int i = 0; i < fields.length; i++)
if (fields[i][0] == null) {
fields[i][0] = id;
return i;
}
// No empty fields. Add one:
Object[][] tmp = new Object[fields.length + 1][2];
for (int i = 0; i < fields.length; i++) {
tmp[i] = fields[i];
}
for (int i = fields.length; i < tmp.length; i++) {
tmp[i] = new Object[] {null, null};
}
fields = tmp;
// Recursive call with expanded fields:
return makeField(id);
}
public Object getField(String id) throws NoSuchFieldException {
return fields[getFieldNumber(id)][1];
}
public Object setField(String id, Object value) throws DynamicFieldsException {
if (value == null) {
// Most exceptions don't have a "cause"
// constructor. In these cases you must use
// initCause(), available in all
// Throwable subclasses.
DynamicFieldsException dfe = new DynamicFieldsException();
dfe.initCause(new NullPointerException()); // [1]
throw dfe;
}
int fieldNumber = hasField(id);
if (fieldNumber == -1) {
fieldNumber = makeField(id);
}
Object result = null;
try {
result = getField(id); // Get old value
// System.out.println("old value:" + result);
} catch (NoSuchFieldException e) {
// Use constructor that takes "cause":
throw new RuntimeException(e); // [2]
}
fields[fieldNumber][1] = value;
return result;
}
public static void main(String[] args) {
DynamicFields df = new DynamicFields(3);
System.out.println(df);
try {
df.setField("d", "A value for d");
df.setField("number", 47);
df.setField("number2", 48);
System.out.println(df);
df.setField("d", "A new value for d");
df.setField("number3", 11);
System.out.println("df: " + df);
System.out.println("df.getField(\"d\") : " + df.getField("d"));
Object field = df.setField("d", null); // Exception
} catch (NoSuchFieldException | DynamicFieldsException e) {
e.printStackTrace(System.out);
}
}
}
/* My Output:
null: null
null: null
null: null
d: A value for d
number: 47
number2: 48
df: d: A new value for d
number: 47
number2: 48
number3: 11
df.getField("d") : A new value for d
DynamicFieldsException
at DynamicFields.setField(DynamicFields.java:78)
at DynamicFields.main(DynamicFields.java:109)
Caused by: java.lang.NullPointerException
at DynamicFields.setField(DynamicFields.java:79)
... 1 more
*/
Class Throwable's method:
initCause
public Throwable initCause(Throwable cause)
Initializes the cause of this throwable to the specified value. (The cause is the throwable that caused this throwable to get thrown.)
This method can be called at most once. It is generally called from within the constructor, or immediately after creating the throwable. If this throwable was created with Throwable(Throwable)
orThrowable(String,Throwable)
, this method cannot be called even once.
An example of using this method on a legacy throwable type without other support for setting the cause is:
try { lowLevelOp(); } catch (LowLevelException le) { throw (HighLevelException) new HighLevelException().initCause(le); // Legacy constructor }
Parameters:
cause
- the cause (which is saved for later retrieval by the getCause()
method). (A null
value is permitted, and indicates that the cause is nonexistent or unknown.)
Returns:
a reference to this Throwable
instance.
Throws:
IllegalArgumentException
- if cause
is this throwable. (A throwable cannot be its own cause.)
IllegalStateException
- if this throwable was created with Throwable(Throwable)
or Throwable(String,Throwable)
, or this method has already been called on this throwable.
Since:
1.4
references:
1. On Java 8 - Bruce Eckel
2. https://github.com/wangbingfeng/OnJava8-Examples/blob/master/exceptions/DynamicFields.java
3. https://docs.oracle.com/javase/8/docs/api/java/lang/Throwable.html#initCause-java.lang.Throwable-