I need to invoke the field accessor methods, i.e the getter of a generic enum, but cannot figure out how to invoke the methods, or more specifically how to pass a generic enum as a parameter for the invoke-method.
Thanks in advance, any help is appreciated.
this is what I'd like to do more or less.
public void(Class<? extends Enum<?>> enumType) {
Enum<?>[] enumConstants = enumType.getEnumConstants();
String[] text = new String[enumConstants.length];
String[] names = new String[enumConstants.length];
for (int i = 0; i < enumConstants.length; i++ ) {
Method[] methods = enumConstants[i].getClass().getDeclaredMethods();
for (Method m: enumConstants[i].getClass().getDeclaredMethods()) {
System.out.println(enumConstants[i].name() + ": " + m.getName() + "()");
try {
if (GET_KEY_METHOD_NAME.equalsIgnoreCase(m.getName())) {
Object value = m.invoke(I HAVE NO IDEA WHAT TO PUT HERE, "");
System.out.println(value.toString());
}
if (GET_VALUE_METHOD_NAME.equalsIgnoreCase(m.getName())) {
Object value = m.invoke(I HAVE NO IDEA WHAT TO PUT HERE, "");
System.out.println(value.toString());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
The parameters for the Method.invoke method are always the instance the method is called for, followed by the list of parameters.
Object value = m.invoke(enumConstants[i]);
is most likely what you need.
Also you should add a type parameter to the method:
public <T extends Enum<T>> void myMethod(Class<T> enumType) {
T[] enumConstants = enumType.getEnumConstants();
BTW: Have you considered using a interface containing those methods? This would allow you to access the methods without having to use reflection.
Also take a look at the getDeclaredMethod method and keep in mind that enum constants may instances of a subclass of the enum class, so you should use the methods not containing Declared. Also find the methods for the enum class, not for the individual classes for less lookups:
For example consider the following:
public enum MyEnum implements M1M2Interface {
ONE() {
#Override
public String m1(String s) {
return "1";
}
}, TWO() {
#Override
public int m2(BigInteger i) {
return 2;
}
}
;
}
public interface M1M2Interface {
default String m1(String s) {
return "2";
}
default int m2(BigInteger i) {
return 1;
}
}
public static <T extends Enum<T>> void testEnum(Class<T> enumType) throws NoSuchMethodException {
T[] enumConstants = enumType.getEnumConstants();
Method m1 = enumType.getMethod("m1", String.class);
Method m2 = enumType.getMethod("m2", BigInteger.class);
for (int i = 0; i < enumConstants.length; i++) {
System.out.println(enumConstants[i].name() + ":");
try {
System.out.println(" m1:" + m1.invoke(enumConstants[i], "Hello World"));
System.out.println(" m2:" + m2.invoke(enumConstants[i], (BigInteger) null));
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
ex.printStackTrace();
}
}
}
getDeclaredMethod wouldn't work here, since the methods could be declared/implemented by:
The interface (declared only prior to java 8)
the enum class
the enum constant (if there is no declaration at a "higher level" the method cannot be accessed using EnumName.CONSTANT_NAME.methodName() so it's unlikely to be done...)
Reflection is rarely the correct answer to anything. Consider having your enum classes implement a common interface, like StandardCopyOption and Month do.
If you can't modify the enum classes, and if you're using Java 8, you can pass the getter method as an argument:
public <E extends Enum<E>> E findMatch(Class<E> enumClass,
Function<E, String> nameGetter,
Predicate<String> matcher) {
for (E value : EnumSet.allOf(enumClass)) {
String name = nameGetter.apply(value);
if (matcher.test(name)) {
return value;
}
}
return null;
}
Example usage:
public static enum Season {
SPRING("Spr"),
SUMMER("Sum"),
FALL("Fal"),
WINTER("Win");
private final String abbreviation;
private Season(String abbrev) {
this.abbreviation = abbrev;
}
public getAbbreviation() {
return abbreviation;
}
}
public void doStuff() {
// ...
String abbrToFind = "Sum";
Season match = findMatch(Season.class,
Season::getAbbreviation,
Predicate.isEqual(abbrToFind));
}
If you're using a version older than Java 8, you can still do the same thing, but you'll need to define and implement the interfaces yourself:
public interface Function<A, B> {
B apply(A input);
}
public interface Predicate<T> {
boolean test(T value);
}
public void doStuff() {
// ...
final String abbrToFind = "Sum";
Season match = findMatch(Season.class,
new Function<Season, String>() {
#Override
public String apply(Season season) {
return season.getAbbreviation(),
}
},
new Predicate<String>() {
#Override
public boolean test(String name) {
return Objects.equals(name, abbrToFind);
}
});
}
Related
I have a problem with method override checks. I can detect simple override relations, but if the parent class has generics and the abstract method uses type parameters (return value/args), my code breaks down because the method description is not equal to the checked method.
Example:
public interface ISetting<T> {
public T method();
}
public class Setting implements ISetting<Integer> {
public Integer method() {
//Something
}
}
In ISetting, the method description is ()Ljava/lang/Object;
and in Setting, the method description is ()Ljava/lang/Integer;
How I can check this Override ?
On my head no thoughts come, how I can make this >~< All ideas which come to my head are bad (example: ignore check on desc, but overload method just break this idea)
Note that your issue does not only apply to generic supertype. You can also override a method with a more specific return type, with no Generics involved, e.g.
interface SomeInterface {
Object method();
}
class SomeImplementation implements SomeInterface {
#Override
public Integer method() {
return null;
}
}
You have to understand the concept of bridge methods.
A bridge method performs the task of overriding a method on the byte code level, having exactly the same parameter types and return type as the overridden method, and delegates to the actual implementation method.
Since the bridge method only consists of this invocation instruction, some type casts if required, and the return instruction, it is easy to parse such a method to find the actual method it belongs to, without dealing with the complex rules of the Generic type system.
Using, the following helper classes
record MethodSignature(String name, String desc) {}
record MethodInfo(int access, String owner, String name, String desc) {
MethodSignature signature() {
return new MethodSignature(name, desc);
}
}
final class MethodAndBridges {
MethodInfo actual;
final List<MethodInfo> bridges = new ArrayList<>();
MethodAndBridges(MethodSignature sig) {}
void set(MethodInfo mi) {
if(actual != null) throw new IllegalStateException();
actual = mi;
}
void addBridge(MethodInfo mi) {
bridges.add(mi);
}
}
We can gather the information in a form ready for checking override relations with the ASM library as follows:
class MethodCollector extends ClassVisitor {
static Map<MethodSignature, MethodAndBridges> getMethods(ClassReader cr) {
MethodCollector mc = new MethodCollector();
cr.accept(mc, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
return mc.found;
}
final Map<MethodSignature, MethodAndBridges> found = new HashMap<>();
String owner, superClass;
List<String> interfaces;
protected MethodCollector() {
super(Opcodes.ASM9);
}
#Override
public void visit(int version, int acc,
String name, String sig, String superName, String[] ifNames) {
owner = name;
superClass = superName;
this.interfaces = ifNames == null? List.of(): List.of(ifNames);
}
#Override
public MethodVisitor visitMethod(
int acc, String name, String desc, String sig, String[] exceptions) {
MethodInfo mi = new MethodInfo(acc, owner, name, desc);
if((acc & Opcodes.ACC_BRIDGE) == 0) {
found.computeIfAbsent(mi.signature(), MethodAndBridges::new).set(mi);
return null;
}
return new MethodVisitor(Opcodes.ASM9) {
#Override public void visitMethodInsn(
int op, String owner, String name, String tDesc, boolean i) {
found.computeIfAbsent(new MethodSignature(name, tDesc),
MethodAndBridges::new).addBridge(mi);
}
};
}
}
To demonstrate how this work, let’s enhance your example, to address more cases
interface SupplierOfSerializable {
Serializable get();
}
interface ISetting<T extends CharSequence> extends Supplier<T>, Consumer<T> {
T get();
#Override void accept(T t);
Number method(int i);
static void method(Object o) {}
private void method(Number n) {}
}
class Setting implements ISetting<String>, SupplierOfSerializable {
public String get() {
return "";
}
#Override
public void accept(String t) {}
public Integer method(int i) {
return i;
}
static void method(Object o) {}
void method(Number n) {}
}
and check the override relations (only considering the direct interfaces, without recursion)
public class CheckOverride {
public static void main(String[] args) throws IOException {
MethodCollector mc = new MethodCollector();
new ClassReader(Setting.class.getName())
.accept(mc, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
Map<MethodSignature, MethodAndBridges> implMethods = mc.found;
Map<MethodInfo, Set<MethodInfo>> overrides = new HashMap<>();
for(String ifType: mc.interfaces) {
Map<MethodSignature, MethodAndBridges> ifMethods
= MethodCollector.getMethods(new ClassReader(ifType));
System.out.println("interface " + ifType.replace('/', '.'));
printMethods(ifMethods);
System.out.println();
ifMethods.values().removeIf(CheckOverride::nonOverridable);
implMethods.forEach((sig, method) -> {
if(nonOverridable(method)) {
overrides.putIfAbsent(method.actual, Set.of());
return;
}
var overridden = ifMethods.get(sig);
if(overridden == null && method.bridges.isEmpty()) {
overrides.putIfAbsent(method.actual, Set.of());
return;
}
Set<MethodInfo> set = overrides.compute(method.actual,
(k, s) -> s == null || s.isEmpty()? new HashSet<>(): s);
if(overridden != null) set.add(overridden.actual);
for(var mi: method.bridges) {
overridden = ifMethods.get(mi.signature());
if(overridden != null) set.add(overridden.actual);
}
});
}
System.out.println("class " + mc.owner.replace('/', '.'));
printMethods(implMethods);
System.out.println();
System.out.println("Final result");
System.out.println("class " + mc.owner.replace('/', '.'));
overrides.forEach((m,overridden) -> {
System.out.println(" " + toDeclaration(m, false));
if(!overridden.isEmpty()) {
System.out.println(" overrides");
overridden.forEach(o ->
System.out.println(" " + toDeclaration(o, true)));
}
});
}
static boolean nonOverridable(MethodAndBridges m) {
return (m.actual.access() & (Opcodes.ACC_PRIVATE|Opcodes.ACC_STATIC)) != 0
|| m.actual.name().startsWith("<");
}
static void printMethods(Map<MethodSignature, MethodAndBridges> methods) {
methods.forEach((sig, methodAndBridges) -> {
System.out.println(" "+toDeclaration(methodAndBridges.actual,false));
if(!methodAndBridges.bridges.isEmpty()) {
System.out.println(" bridges");
for(MethodInfo mi: methodAndBridges.bridges) {
System.out.println(" " + toDeclaration(mi, false));
}
};
});
}
private static String toDeclaration(MethodInfo mi, boolean withType) {
StringBuilder sb = new StringBuilder();
sb.append(Modifier.toString(mi.access() & Modifier.methodModifiers()));
if(sb.length() > 0) sb.append(' ');
String clName = mi.owner();
var mt = MethodTypeDesc.ofDescriptor(mi.desc());
if(mi.name().equals("<init>"))
sb.append(clName, clName.lastIndexOf('/') + 1, clName.length());
else {
sb.append(mt.returnType().displayName()).append(' ');
if(withType) sb.append(clName.replace('/', '.')).append('.');
sb.append(mi.name());
}
if(mt.parameterCount() == 0) sb.append("()");
else {
String sep = "(";
for(ClassDesc cd: mt.parameterList()) {
sb.append(sep).append(cd.displayName());
sep = ", ";
}
sb.append(')');
}
return sb.toString();
}
}
interface ISetting
public static void method(Object)
public abstract void accept(CharSequence)
bridges
public void accept(Object)
public abstract Number method(int)
private void method(Number)
public abstract CharSequence get()
bridges
public Object get()
interface SupplierOfSerializable
public abstract Serializable get()
class Setting
Setting()
public Integer method(int)
bridges
public Number method(int)
public void accept(String)
bridges
public void accept(Object)
public void accept(CharSequence)
static void method(Object)
public String get()
bridges
public Object get()
public CharSequence get()
public Serializable get()
void method(Number)
Final result
class Setting
public String get()
overrides
public abstract Serializable SupplierOfSerializable.get()
public abstract CharSequence ISetting.get()
Setting()
public Integer method(int)
overrides
public abstract Number ISetting.method(int)
public void accept(String)
overrides
public abstract void ISetting.accept(CharSequence)
void method(Number)
static void method(Object)
The code uses newer Java features, like var, record, and the constant API, but I think, the result is straight-forward enough for converting it to older Java versions, if really required.
Given Java source code and a preprocessor (like C++), I would like to replace all mentions of null with a function that returns null. It finds a call to null and replaces it with the following function.
public static Object returnNull(){
return null;
}
This fails because there are varied classes and:
functionThatWantsCustomClass( returnNull() ); //Object cannot be converted to CustomClass
or
if( cc == returnNull() ) //Object cannot be converted to CustomClass
etc.
Easiest solution I can imagine is having to parametrize the preprocessor, although that would require going through every single null to add the parameter maually, eg: null/*CustomClass*/.
Another method is spending a lot of time writing a much better parser so it always knows the required class for a returnTypedNull() function.
Is there a way to get through this error with minimal modification/parsing?
Use generics:
public static <T> T returnNull() {
return (T) null;
}
Follow-up from comment
The following code is as close to comment as I can decipher, and it compiles fine:
public class Test {
public static void main(String[] args) {
CustomClass cc = new CustomClass();
if (cc != returnNull())
cc.errlog( returnNull() );
}
public static <T> T returnNull() {
return (T) null;
}
}
class CustomClass {
void errlog(Exception e) {
}
}
Now, if there are 2 errlog methods with only one non-primitive parameter:
class CustomClass {
void errlog(Exception e) {
}
void errlog(String s) {
}
}
Then it will fail with error The method errlog(Exception) is ambiguous for the type CustomClass, because the compiler doesn't know whether T should be Exception or String, i.e. which of the two to call.
You have to explicitly tell the compiler:
cc.errlog( Test.<Exception>returnNull() );
Use generics ant it will work.
Example:
public class ReturnNullExample {
public static void main(String[] args) {
ReturnNullExample example = new ReturnNullExample();
example.someMethod(ReturnNullClass.returnNull());
CustomClass cc = null;
if(cc == ReturnNullClass.returnNull()) {
System.out.println("cc is null");
}
cc = new CustomClass();
if(cc != ReturnNullClass.returnNull()) {
System.out.println("cc is not null");
}
}
public void someMethod(CustomClass customClass) {
System.out.println("This method does nothing");
}
}
class CustomClass {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
class ReturnNullClass {
public static <T> T returnNull() {
return null;
}
}
I have a series of enums which look like this except that the name and values are different:
/* Bone Diagnosis. Value is internal code stored in database. */
public enum BoneDiagnosis {
NORMAL(121),
ELEVATED(207),
OSTEOPENIA(314),
OSTEOPOROSIS(315);
private int value;
BoneDiagnosis(final int value) {
this.value = value;
}
/** Get localized text for the enumeration. */
public String getText() {
return MainProgram.localize(this.getClass().getSimpleName().toUpperCase() + ".VALUE." + this.name());
}
/** Convert enumeration to predetermined database value. */
public int toDB() {
return value;
}
/** Convert a value read from the database back into an enumeration. */
public static BoneDiagnosis fromDB(final Integer v) {
if (v != null) {
for (final BoneDiagnosis pc : values()) {
if (v == pc.toDB()) {
return pc;
}
}
}
return null;
}
}
I know I cannot extend enums, but is there some way to abstract this design to remove all the duplicate code in toDB(), fromDB(), and getText() that each class has? I looked at other questions like Is it possible to extend enum in Java 8? which had an example using an interface, but I could not figure out how to handle the constructor and the static method. I also cannot figure out how to remove the explicit reference to type BoneDiagnosis in the fromDB() method.
My dream would be to have each class merely be defined something like what follows, with all the other support wrapped up in whatever BoneDiagnosisComplexTypeDefinition is. Is this possible?
public enum BoneDiagnosisComplexTypeDefinition {
NORMAL(121),
ELEVATED(207);
OSTEOPENIA(314),
OSTEOPOROSIS(315)
}
You can minimize the per-enum code and the per-operation overhead using
#Target(ElementType.FIELD) #Retention(RetentionPolicy.RUNTIME)
public #interface DbId {
int value();
}
final class Helper extends ClassValue<Map<Object,Object>> {
static final Helper INSTANCE = new Helper();
#Override protected Map<Object, Object> computeValue(Class<?> type) {
Map<Object,Object> m = new HashMap<>();
for(Field f: type.getDeclaredFields()) {
if(f.isEnumConstant()) try {
Object constant = f.get(null);
Integer id = f.getAnnotation(DbId.class).value();
m.put(id, constant);
m.put(constant, id);
}
catch(IllegalAccessException ex) {
throw new IllegalStateException(ex);
}
}
return Collections.unmodifiableMap(m);
}
}
public interface Common {
String name();
Class<? extends Enum<?>> getDeclaringClass();
default int toDB() {
return (Integer)Helper.INSTANCE.get(getDeclaringClass()).get(this);
}
default String getText() {
return MainProgram.localize(
getDeclaringClass().getSimpleName().toUpperCase() + ".VALUE." + name());
}
static <T extends Enum<T>&Common> T fromDB(Class<T> type, int id) {
return type.cast(Helper.INSTANCE.get(type).get(id));
}
}
public enum BoneDiagnosis implements Common {
#DbId(121) NORMAL,
#DbId(207) ELEVATED,
#DbId(314) OSTEOPENIA,
#DbId(315) OSTEOPOROSIS;
}
Test example
int id = BoneDiagnosis.OSTEOPENIA.toDB();
System.out.println("id = " + id);
BoneDiagnosis d = Common.fromDB(BoneDiagnosis.class, id);
System.out.println("text = " + d.getText());
Note that the reflective operations are only performed once per class using ClassValue which is especially designed for caching per-class meta data efficiently, thread safe and without preventing class unloading in environments where it matters. The actual toDB and fromDB are reduced to a hash lookups.
By the way, it’s important that this code uses getDeclaringClass() rather than getClass() as enums may have specializations like in enum Foo { BAR { … } … } where getClass() returns the specialization class rather than the enum type.
The interface approach is the only way to go. Here is another way to leverage interfaces to minimize code duplication in your enums.
Update:
Some have complained about reflection and casting. Here are two options without need for casting and one that does not require reflection.
Option1 (clean no reflection but requires extra method in enums):
public static interface BoneDiagnosisType{
public String name();
public int getValue();
default int toDB() {
return getValue();
}
default String getText(){
return MainProgram.localize( this.getClass().getSimpleName().toUpperCase() + ".VALUE." + name() );
}
public static < E extends Enum<E> & BoneDiagnosisType > E fromDB(Class<E> eClass, Integer v) {
if (v != null) {
for ( final E pc : eClass.getEnumConstants() ) {
if ( v == pc.toDB() ) {
return pc;
}
}
}
return null;
}
}
public static enum BoneDiagnosis1 implements BoneDiagnosisType{
NORMAL(121),
ELEVATED(207),
OSTEOPENIA(314),
OSTEOPOROSIS(315);
int value;
BoneDiagnosis1(int value) {
this.value = value;
}
public int getValue(){
return value;
}
}
public static enum BoneDiagnosis2 implements BoneDiagnosisType{
NORMAL(1121),
ELEVATED(1207),
OSTEOPENIA(1314),
OSTEOPOROSIS(1315);
int value;
BoneDiagnosis2(int value) {
this.value = value;
}
public int getValue(){
return value;
}
}
Option2 (requires reflection keeps enums as simple as possible):
public static interface BoneDiagnosisType{
public String name();
default int toDB() {
try{
Class<?> clazz = getClass();
Field field = clazz.getDeclaredField("value");
return field.getInt(this);
}catch(RuntimeException e){
throw e;
}catch(Exception e){
throw new RuntimeException(e);
}
}
default String getText(){
return MainProgram.localize( this.getClass().getSimpleName().toUpperCase() + ".VALUE." + name() );
}
public static < E extends Enum<E> & BoneDiagnosisType > E fromDB(Class<E> eClass, Integer v) {
if (v != null) {
for ( final E pc : eClass.getEnumConstants() ) {
if ( v == pc.toDB() ) {
return pc;
}
}
}
return null;
}
}
public static enum BoneDiagnosis1 implements BoneDiagnosisType{
NORMAL(121),
ELEVATED(207),
OSTEOPENIA(314),
OSTEOPOROSIS(315);
int value;
BoneDiagnosis1(int value) {
this.value = value;
}
}
public static enum BoneDiagnosis2 implements BoneDiagnosisType{
NORMAL(1121),
ELEVATED(1207),
OSTEOPENIA(1314),
OSTEOPOROSIS(1315);
int value;
BoneDiagnosis2(int value) {
this.value = value;
}
}
And the sample printout:
System.out.println( BoneDiagnosis1.NORMAL.toDB() + " : " + BoneDiagnosis1.NORMAL.getText() + " : " +
BoneDiagnosisType.fromDB( BoneDiagnosis1.class, 121 ) );
System.out.println( BoneDiagnosis1.ELEVATED.toDB() + " : " + BoneDiagnosis1.ELEVATED.getText() + " : " +
BoneDiagnosisType.fromDB( BoneDiagnosis1.class, 207 ) );
System.out.println( BoneDiagnosis2.NORMAL.toDB() + " : " + BoneDiagnosis2.NORMAL.getText() + " : " +
BoneDiagnosisType.fromDB( BoneDiagnosis2.class, 1121 ) );
System.out.println( BoneDiagnosis2.ELEVATED.toDB() + " : " + BoneDiagnosis2.ELEVATED.getText() + " : " +
BoneDiagnosisType.fromDB( BoneDiagnosis2.class, 1207 ) );
will give:
121 : BONEDIAGNOSIS1.VALUE.NORMAL : NORMAL
207 : BONEDIAGNOSIS1.VALUE.ELEVATED : ELEVATED
1121 : BONEDIAGNOSIS2.VALUE.NORMAL : NORMAL
1207: BONEDIAGNOSIS2.VALUE.ELEVATED : ELEVATED
Here's another way you could go about it using what some call the "virtual field pattern." It reduces the amount of repeated code per enum to one getter and one field. It also avoids reflection.
First create an interface for all the methods your enums have in common. In this example we'll call it Common.
public interface Common {
int getId();
String getName();
}
Create another interface that extends Common. This will have only one abstract method, which just returns an instance of Common. Give the other methods a default implementation that delegates to the Common instance.
public interface VirtualCommon extends Common {
Common getCommon();
#Override
default int getId() {
return getCommon().getId();
}
#Override
default String getName() {
return getCommon().getName();
}
}
Now create a concrete implementation of Common.
public class CommonImpl implements Common {
private int id;
private String name;
public CommonImpl(int id, String name) {
this.id = id;
this.name = name;
}
#Override
public int getId() {
return this.id;
}
#Override
public String getName() {
return this.name;
}
}
Or, if you want to save a few lines of code, instead of CommonImpl you can put a static method on Common that returns an anonymous class.
static Common of(final int id, final String name) {
return new Common() {
#Override
public int getId() {
return id;
}
#Override
public String getName() {
return name;
}
};
}
Now you can create each of your enums and they'll only need one field and one getter. Any class or enum that implements VirtualCommon will be a Common because it has a Common.
public enum EnumImpl implements VirtualCommon {
ALPHA(1, "Alpha"),
BETA(2, "Beta"),
DELTA(3, "Delta"),
GAMMA(4, "Gamma");
private final Common common;
EnumImpl(int id, String name) {
this.common = new CommonImpl(id, name);
}
#Override
public Common getCommon() {
return this.common;
}
}
This solution still has some boilerplate bridging code.
Use an Interface to define the calling interface and implement the common code in some class.
Here is a simple example:
File: BoneDiagnosis.java
public enum BoneDiagnosis
implements
CommonStuffs
{
NORMAL(121),
ELEVATED(207),
OSTEOPENIA(314),
OSTEOPOROSIS(315);
private CommonStuffsImpl commonStuffsImpl;
private int value;
BoneDiagnosis(final int theValue)
{
value = theValue;
commonStuffsImpl = new CommonStuffsImpl();
}
#Override
public int toDB()
{
return commonStuffsImpl.toDBImplementation(value);
}
}
File: CommonStuffs.java
public interface CommonStuffs
{
int toDB();
}
File: CommonStuffsImpl.java
public class CommonStuffsImpl
{
public int toDBImplementation(
final int value)
{
return value;
}
}
Sometimes methods have the only difference somwhere in the middles of their bodies and it's difficult to generalize them or extract common part of code to a single method.
Question itself: How would you refactor the following implementations of interface methods to avoid duplicate code around for loop body?
interface MyInterface {
Integer myInterfaceMethod(String inputStr);
Integer myInterfaceOtherMethod(String inputStr)
}
class MyClass implements MyInterface {
public Integer myInterfaceMethod(String inputStr) {
#Override
try {
List<String> listDependingOnString = getListByString(inputStr);
Integer result = -1;
if (inputStr != null) {
result = 0;
for (String str : listDependingOnString) {
// Some different code, given just for example
result += str.length();
}
}
return result;
} catch (Exception e) {
exceptionProcessing(e);
return null;
}
}
#Override
public Integer myInterfaceOtherMethod(String inputStr) {
try {
List<String> listDependingOnString = getListByString(inputStr);
Integer result = -1;
if (inputStr != null) {
result = 0;
for (String str : listDependingOnString) {
// Some different code, given just for example
System.out.println(str);
++result;
}
}
return result;
} catch (Exception e) {
exceptionProcessing(e);
return null;
}
}
}
For this particular example, a lambda would work nicely:
private Integer computeStringFunction(String inputStr, BiFunction<Integer,String,Integer> accumulator) {
try {
List<String> listDependingOnString = getListByString(inputStr);
Integer result = -1;
if (inputStr != null) {
result = 0;
for (String str : listDependingOnString) {
result = accumulator.apply(result, str);
}
}
return result;
} catch (Exception e) {
exceptionProcessing(e);
return null;
}
public Integer myInterfaceMethod(String inputStr) {
return computeStringFunction(inputStr,
(Integer oldValue, String str) -> oldValue + str.length());
}
public Integer myInterfaceOtherMethod(String inputStr) {
return computeStringFunction(inputStr,
(Integer oldValue, String str) -> {
System.out.println(str);
return oldValue + 1;
});
}
"accumulator" here is a function that takes an integer and a string and returns another integer, and whose intent is to keep a "running total" of some sort.
BiFunction documentation
Note: not tested
The key to remove duplicate pattern in codes is to abstract the common part to one place and then find a way to pass the different part of "code pieces" as parameters to execute, for languages in which function is first class citizen(JavaScript, Python), you can always wrap the "code pieces" as functions. But it's not applicable for Java because method in Java is not a value, one way to resolve it is to define interfaces, and then pass the instance of a class which implements the interface as parameters, with lambda expression in Java 8 it can be more simpler.
Take the code in question as example, the common pattern is:
iterate the list and process each item
accumulate the result of each item and return
Then we can define two interfaces:
#FunctionalInterface
public interface ItemHandler<T, R> {
/**
* Takes input item of type T, then returns result of type R
*/
R handle(T t);
}
And another interface to accumulate the result:
#FunctionalInterface
public interface ItemResultAccumulator<T> {
T accumulate(T t1, T t2);
}
and then your code could be refactored as(I removed all exception handling and null checking code, to make the code less verbose to view):
public class MyClass implements MyInterface {
private static final ItemResultAccumulator<Integer> ADDER = (t1, t2) -> t1 + t2;
#Override
public Integer myInterfaceMethod(String inputStr) {
return processList(getListByString(inputStr), s -> s.length(), ADDER);
}
#Override
public Integer myInterfaceOtherMethod(String inputStr) {
return processList(getListByString(inputStr), s -> {
System.out.println(s);
return Integer.valueOf(1);
}, ADDER);
}
private Integer processList(List<String> list, ItemHandler<String, Integer> handler, ItemResultAccumulator<Integer> accumulator) {
Integer result = 0;
if (list != null && list.size() > 0) {
for (String item : list) {
result = accumulator.accumulate(result, handler.handle(item));
}
}
return result;
}
private List<String> getListByString(String inputStr) {
// Your logic to generate list by input
return Lists.newArrayList(inputStr.split(","));
}
}
This is a little of my thinking of this problem, hope this could be helpful:-)
I am trying to create a base abstract class for unit testing. It is very easy to do this in C# but couldn't in java. My idea is that I will have a TestFor class which is to be used as base for unit test. T represents the type under test. In this class I want to create the The object of type T with all its parameters of longest constructor MOCKED. That mean I have to reflect the class, get the longest constructor, pull out the parameters, create mock of this parameter and then create the object of type T. I have the following code but not working. Anyone who can try
public abstract class TestFor<T> {
protected Class<T> _class = null;
public HashMap<Class, Class<?>> _mocks = new HashMap<Class, Class<?>>();
protected T Target = null;
protected TestFor(Class<T> cls) {
_class = cls;
Constructor<T>[] allConstructors = (Constructor<T>[]) _class.getDeclaredConstructors();
Constructor<T> ctorWithLongestParameter = null;
int max = 0;
for (Constructor ctor : allConstructors) {
if (ctor.getParameterTypes().length > max) {
ctorWithLongestParameter = ctor;
max = ctor.getParameterTypes().length;
}
}
final List<Object> objects = new ArrayList<Object>();
int i = 0;
for (Class<?> p : ctorWithLongestParameter.getParameterTypes()) {
Class<?> mock = Mockito.mock(p.getClass()); //This does not work
_mocks.put(p.getClass(), mock);
objects.add(mock);
}
try {
Target = (T) ctorWithLongestParameter.newInstance(objects);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public <E> E the(Class clss) {
return (E) _mocks.get(clss);
}
}
There is a several bugs in your code - logic, types, generics... Try this instead:
public abstract class TestFor<T> {
protected Class<T> _class = null;
public Map<Class, Object> _mocks = new HashMap<>();
protected T Target = null;
protected TestFor(Class<T> cls) {
_class = cls;
List<Constructor> allConstructors = Arrays.asList(_class.getDeclaredConstructors());
Constructor ctorWithLongestParameter = Collections.max(allConstructors,
(o1, o2) -> Integer.compare(o1.getParameterCount(), o2.getParameterCount()));
List<Object> objects = new ArrayList<>();
int i = 0;
for (Class<?> type : ctorWithLongestParameter.getParameterTypes()) {
Object mock = _mocks.get(type);
if (mock == null) {
mock = Mockito.mock(type);
_mocks.put(type, mock);
}
objects.add(mock);
}
try {
Target = _class.cast(ctorWithLongestParameter.newInstance(objects.toArray(new Object[objects.size()])));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public <E> E the(Class<E> cls) {
return cls.cast(_mocks.get(cls));
}
public static void main(String[] args) {
TestFor<A> test = new TestFor<A>(A.class) {};
System.out.println(test.Target);
System.out.println(test.the(Object.class));
System.out.println(test.the(Number.class));
}
public static class A {
public A() {
System.out.println("Empty constructor");
}
public A(Object o) {
System.out.println("Constructor [o=" + o + ']');
}
public A(Object o, Number n) {
System.out.println("Constructor [o=" + o + ", n=" + n + ']');
}
}
}
This code works with Java 8, however after small modifications it will work on the elder versions.