Implementing toString method in Java with The Visitor Pattern? - java

Here is some of my Java code
public List<OBJ> a = new ArrayList<OBJ>();
public String A;
public String B;
public String C;
for (OBJ o : a) {
// .... TODO
}
So I have an interface OBJ and there are three objects that implements OBJ say X, Y, Z. So I store X/Y/Z objects in List a. Now say that I want to go through the loop and if o is of instance X store X.value in A, if Y store Y.value in B, and if Z store Z.value in C. So the problem is really how do you figure out what object type o is (X,Y,Z) to store their values in the right string.
NOTE: I want to use the Visitor pattern or something like it, but I don't really have a firm grasp of it so I'm asking for your help.
This means NO Instanceof(s) or Type Casts and NO Dedicated Methods like
interface OBJ {
void blah();
}
class X implements OBJ {
public void blah();
} // etc
Thanks! I really want to get this import aspect of software engineering down!
Hey wow thanks for the detailed and fast responses, but my situation is a bit more complicated and sorry I didn't add this before.
So String A, B, C are actually housed in another class like
class ARGH {
public List<OBJ> a = new ArrayList<OBJ>();
public String A;
public String B;
public String C;
//invisible constructor here
public String toString () {
for (OBJ o : a) {
// .... TODO
}
return "some string"
}
}
public void main (String[] args) {
ARGH argh = new ARGH();
// Setup some X, Y, Z objects and their values here
String D = argh.toString();
// Do something to D
}
So the Strings and List are actually not global variables so I don't think this would work:
ObjVisitor v = new ObjVisitor() {
#Override
public void visit(X x) {
A = x.value();
}
// And so on.
}
I am assuming I have to somehow pass in the String A, B, C into the visit method but I don't know how to do that and still stay with The Visitor Pattern.

In a nut-shell you'd do like this:
Create a Visitor interface ObjVisitor with one visit-method for each type.
Add an abstract accept(ObjVisitor v) to OBJ.
Add an accept(ObjVisitor v) { v.visit(this); } to each OBJ implementation.
Call o.accept(yourVisitorImpl) in the loop.
You did indeed get some code from Bringer128. I elaborated a bit and added the String-stuff.
import java.util.*;
interface OBJ {
String accept(ObjVisitor v);
}
interface ObjVisitor {
String visit(X x);
String visit(Y y);
String visit(Z z);
}
class X implements OBJ {
public String accept(ObjVisitor v){ return v.visit(this); }
}
class Y implements OBJ {
public String accept(ObjVisitor v) { return v.visit(this); }
}
class Z implements OBJ {
public String accept(ObjVisitor v) { return v.visit(this); }
}
Usage:
class Test {
public static void main(String[] args) {
List<OBJ> objs = Arrays.asList(new Z(), new X());
ObjVisitor toStringVisitor = new ObjVisitor() {
public String visit(X x) { return "X object"; }
public String visit(Y y) { return "Y object"; }
public String visit(Z z) { return "Z object"; }
};
String result = "";
for (OBJ o : objs)
result += o.accept(toStringVisitor) + "\n";
System.out.println(result);
// Prints
// Z object
// X object
}
}
An alternative (perhaps better approach) would be to let the visitor implementation maintain a StringBuilder, let the visit-methods return void, and after the loop just call stringVisitor.getCurrentString() or something like that.

aioobe spelled it out for you, but here is what the implementation would look like.
interface OBJ {
void blah();
// New method!
void accept(ObjVisitor v);
}
class X implements OBJ {
public void blah() {}
#Override
public void accept(ObjVisitor v) {
v.visit(this);
}
}
interface ObjVisitor {
public void visit(X x);
public void visit(Y y);
}
Now use it:
public List<OBJ> a = new ArrayList<OBJ>();
public String A;
public String B;
public String C;
public void myMethod() {
ObjVisitor v = new ObjVisitor() {
#Override
public void visit(X x) {
A = x.value();
}
// And so on.
}
for (OBJ o : a) {
o.accept(v);
}
}

Related

Java ASM method override check

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.

Looking for an implementation of an abstract method

I need to make a programm which is like a rally, theres 2 types of vehicles, motorcycle and cars, two types of motorcycle, with and without sidecar, the thing is that I need to verify if there is just a motorcycle in an array list, I mean, two wheels vehicle. That verification should be done in a method called esDe2Ruedas(), which is called by an abstract overrided method called check() that should be the one that verifies if a group of vehicles from an array are able to run in the rally, if its true all the elements of the array must be from the same type.
Here is the code
this is how the program arrays the vehicles
GrandPrix gp1 = new GrandPrix();
gp1.agregar(v1);
//gp1.mostrar(v1);
gp1.agregar(v2);
System.out.println(gp1.check());
GrandPrix gp2 = new GrandPrix();
gp2.agregar(vt1);
gp2.agregar(vt2);
gp2.agregar(m2);
System.out.println(gp2.check());
GrandPrix gp3 = new GrandPrix();
gp3.agregar(vt1);
gp3.agregar(vt2);
gp3.agregar(m1);
System.out.println(gp3.check());
GrandPrix gp4 = new GrandPrix();
gp4.agregar(m1);
gp4.agregar(m2);
System.out.println(gp4.check());
This is the class that is using
import java.util.ArrayList;
public class GrandPrix extends Rally{
ArrayList<Vehiculo> ve = new ArrayList<Vehiculo>();
public void agregar(Vehiculo v) {
ve.add(v);
}
public void agregar(Carro c) {
ve.add(c);
}
public void agregar(Moto m) {
ve.add(m);
}
#Override
boolean check() {// HERE I VERIFY IF THE VEHICLES ARE COMPATIBLE
return false;
}
}
This is the class where everything goes on
public class Vehiculo {
private String Nombre;
private double velocidad_max;
private int peso;
private int comb;
public Vehiculo() {
setNombre("Anónimo");
setVel(130);
setPeso(1000);
setComb(0);
}
public Vehiculo(String string, double d, int i, int j) {
setNombre(string);
setVel(d);
setPeso(i);
setComb(j);
}
double rendimiento() {
return velocidad_max/peso;
}
public boolean mejor(Vehiculo otroVehiculo) {
return rendimiento()>otroVehiculo.rendimiento();
}
public String toString() {
return getNombre()+"-> Velocidad máxima = "+getVel()+" km/h, Peso = "+getPeso()+" kg";
}
/**************************************
---------SET And GET Nombre------------
***************************************/
public String getNombre() {
return Nombre;
}
public void setNombre(String nuevoNombre) {
this.Nombre=nuevoNombre;
}
/**************************************
---------SET And GET velocidad_max------------
***************************************/
public double getVel() {
return velocidad_max;
}
public void setVel(double nuevaVel) {
this.velocidad_max=nuevaVel;
}
/**************************************
---------SET And GET peso------------
***************************************/
public double getPeso() {
return peso;
}
public void setPeso(int nuevoPeso) {
this.peso=nuevoPeso;
}
/**************************************
---------SET And GET comb------------
***************************************/
public int getComb() {
return comb;
}
public void setComb(int comb) {
this.comb = comb;
}
boolean esDe2Ruedas() {
return false;
}
}
This is the class of motorcycles, which is in theory the same as the car's class, without sidecar thing
public class Moto extends Vehiculo{
private boolean sidecar;
public Moto(String string, double d, int i, int j) {
setNombre(string);
setVel(d);
setPeso(i);
setComb(j);
setSidecar(false);
}
public Moto(String string, double d, int i, int j, boolean b) {
setNombre(string);
setVel(d);
setPeso(i);
setComb(j);
setSidecar(b);
esDe2Ruedas(false);
}
public String toString() {
String str = null;
if(isSidecar())
str =super.toString()+", Moto, con sidecar";
else
str =super.toString()+", Moto";
return str;
}
public boolean isSidecar() {
return sidecar;
}
public void setSidecar(boolean sidecar) {
this.sidecar = sidecar;
}
I guess what you presented is what is given. If you came up with the design it is ok, but I believe it could be improved. Anyway, I try to respond to what I believe was your question straight away.
Vehiculo is the super type of Moto (which can have a side car and becomes 3 wheeler).
Vehiculo has a method esDe2Ruedas, which returns false.
Moto inherits that method <-- this is wrong, it should override it and, depending on side car, return the expected boolean value.
In the check method you can now distinguish between Moto and "Moto with sidecar" by using that method.

Interface to unite calculate methods with different primitive paramter types

I have two types of objects that can perform a calculate() operation with either an int or a byte:
public class A {
public int calculate(int n) {...}
}
and
public class B {
public byte calculate(byte n) {...}
}
I want to have an ArrayList of objects that I can loop over calling the calculate method.
How do I do that, using an interface?
Considering the difference in the int/byte signature
Would something like this be a good approach?
public interface Calculatable {
int calculate(int number);
default byte calculate(byte number) {
return (byte) calculate((int) number);
}
}
Maybe using a 3-rd class and check based on type could be useful
public class TestCalc {
public static void main(String[] args) {
List<Object> l = new ArrayList<Object>();
l.add(Integer.valueOf(300));
l.add(Byte.valueOf("120"));
l.add(Integer.valueOf(1));
TestCalc tc = new TestCalc();
ComputeAB cab = tc.new ComputeAB();
for (Object o : l) {
System.out.println(cab.calculate(o) + ":" + cab.type);
}
}
class A {
public int calculate(int n) {
return n;
}
}
class B {
public byte calculate(byte n) {
return n;
}
}
class ComputeAB {
String type = "";
public Object calculate(Object o) {
if (o instanceof Integer) {
type = "int";
return new A().calculate((int) o);
}
type = "byte";
return new B().calculate((byte) o);
}
}
}
Output
300:int
120:byte
1:int

Please Explain Java 8 Method Reference to instance Method using class name

public interface MyFunc<T> {
boolean func(T v1, T v2);
}
public class HighTemp {
private int hTemp;
HighTemp(){
}
public HighTemp(int ht) {
this.hTemp = ht;
}
boolean sameTemp(HighTemp ht2){
return hTemp == ht2.hTemp;
}
boolean lessThanTemp(HighTemp ht2){
return hTemp < ht2.hTemp;
}
}
class InstMethWithObjRef {
static <T> int counter(T[] vals, MyFunc<T> f, T v){
int count = 0;
for (int i = 0; i < vals.length; i++) {
if(f.func(vals[i], v)) count++;
}
return count;
}
public static void main(String[] args) {
int count;
//Create an array of HighTemp objects.
HighTemp[] weekDayHighs = {new HighTemp(89), new HighTemp(82),
new HighTemp(90), new HighTemp(89),
new HighTemp(89), new HighTemp(91),
new HighTemp(84), new HighTemp(83)};
count = counter(weekDayHighs, HighTemp::lessThanTemp,new HighTemp(89));
System.out.println(count);
}
}
Please explain how
boolean sameTemp() is compatible with func() in Functional interface.
sameTemp() method got implemented on func() in Functional Interface.
count = counter(weekDayHighs, HighTemp::sameTemp, new HighTemp(89)); is working
Please Explain All points separately.
Equivalent lambda expression of HighTemp::lessThanTemp is
(highTemp1, highTemp2) -> {
return highTemp1.lessThanTemp(highTemp2);
}
This is one of the features of Java8 named Reference to an Instance Method of an Arbitrary Object of a Particular Type
Consider following example,
interface FIface<T> {
int testMethod(T a, T b);
}
class Test2 {
private String str;
Test2(String str) {
this.str = str;
}
int ok(Test2 test2) {
System.out.println("Currnet String : "+ this.str);//Refer to t1
System.out.println("Test String : "+test2.str);//Refer to t2
return 0;
}
}
public class Test {
public static <T> int checkCall(T t1, T t2, FIface<T> fiFace) {
//Here Test2 :: ok is equivalent to t1.ok(t2)
return fiFace.testMethod(t1, t2);
}
public static void main(String[] args) {
checkCall(new Test2("a"), new Test2("b"), Test2 :: ok);
}
}
OUTPUT
Currnet String : a
Test String : b
Note here that Test2 :: ok is valid for the call even ok method is not static.
When you call the method checkCall for the functional interface you still have two arguments which are t1 and t2 and for that valid lambda expression can have parameters as (Test t1, Test t2) so your method Test2 :: ok here becomes valid for the call. Internally it works this way t1.ok(t2).
So, fiFace.testMethod(t1, t2); will will invoke method as t1.ok(t2)
For starters I'm not a professional programmer. I too had a great difficulty in understanding the so called "Reference to an Instance Method of an Arbitrary Object of a Particular Type" I think this might be helpful for somebody who comes here from a google search.
I understood it a little bit with the help of lambda expressions.
In your code HighTemp::lessThanTemp as a Lambda expression would look like (x,y)->{x.lessThanTemp(y);} Replacing the method reference with this lambda expression would produce the same result. The above Lambda expression or the method reference both tell the interface method what to do.
When you use the method reference it tells the interface method to use the referred method from the given class, to carryout its function. Therefore if you convert HighTemp::lessThanTemp to English words it would sound something like "implement the lessThanTemp method form the class HighTemp as the implementation of the interface function". As you might've noticed in that case the return types and the argument types should be compatible. Otherwise you cannot implement an interface.
I would provide you another simple example code. More examples helps to understand this concept.
interface myint{
int returnit(Test t ,int y);
}
class Test{
int x=0;
public Test(int x){
this.x=x;
}
public int addNumbers(int y){
return x+y;
}
public int subtractNumbers(int y){
return x-y;
}
}
public class myclass{
private static void myMethod(Test t,myint inf,int y){
int x=inf.returnit(t, y);
System.out.println(x+"");
}
public static void main(String[] args){
myMethod(new Test(4),Test::addNumbers,7);
myMethod(new Test(4),Test::subtractNumbers,7);
}
}
Output would be:
11
-3
This is the simplest way I could imagine it. See how return types and argument types gets matched using the above sentence pattern. Spend some time on it.
This is the Interface
package learninglambdaexp;
#FunctionalInterface
public interface TempInterface {
public boolean validTemp(Temperature temp);
}
This is the class
package learninglambdaexp;
public class Temperature {
private int temp;
public Temperature(int temp) {
this.temp = temp;
}
public boolean isEvenTemp() {
return temp % 2 == 0;
}
public boolean isOddTemp(){
return !isEvenTemp();
}
}
This is the Class with the Main Method
package learninglambdaexp;
import java.util.ArrayList;
import java.util.List;
public class AnotherMainClass {
public static void main(String[] args) {
List<Temperature> tempCollection = new ArrayList<>();
tempCollection.add(new Temperature(100));
tempCollection.add(new Temperature(20));
tempCollection.add(new Temperature(30));
tempCollection.add(new Temperature(40));
tempCollection.add(new Temperature(50));
tempCollection.add(new Temperature(60));
tempCollection.add(new Temperature(70));
int k1 = countVariation(tempCollection, Temperature::isEvenTemp);
//int k2 = countVariation(Temperature::lowTemp);
System.out.println(k1);
// System.out.println(k2);
}
private static int countVariation(List<Temperature> tempCollection, TempInterface ti) {
int count = 0;
for (Temperature eachTemp : tempCollection) {
if (ti.validTemp(eachTemp)) { // (eachTemp) -> {return eachTemp.isEvenTemp();};
count++;
}
}
return count;
}
}
With one argument its easier to understand
Please, correct me if I am wrong, but the way I think about this type of method references (Reference to an Instance Method of an Arbitrary Object of a Particular Type) is that when we pass a method reference, in this case to the counter method, the instance of anonymous class which implements MyFunc interface is created. Then, inside this anonymous class, we override func method which is passed two parameters. And then inside the func method, lessThanTemp method is called like this:
v1.lessThanTemp(v2);
So for me this concept looks something like this:
public class Demo {
public static void main(String[] args) {
AnonymousClass an = new AnonymousClass();
System.out.println(an.apply(new SomeClass(3), 4));
}
}
interface SomeInterface {
int apply(SomeClass obj, int n);
}
class SomeClass {
private int n;
SomeClass(int n) {
this.n = n;
}
int add(int n) {
return this.n + n;
}
}
class AnonymousClass implements SomeInterface {
#Override
public int apply(SomeClass o, int n) {
return o.add(n);
}
}

Java: How to design collection using a superclass

This is a practical question, but I am not sure if it has a practical answer. If you have a superclass with let's say 10 subclasses, what is the most simple way to put those 10 subclasses in a collection? Right now (this may be bad design), I have put them in a static collection field in the superclass.
The motivation for this question, however, came because I had obtained the identity of one of the fields of one of the subclasses, but I needed a reference to a different field in the same subclass.
For instance, let's say the subclass has the following fields:
public class SampleSubClass extends SampleSuperClass{
...
private Object1 o_1;
private Object2 o_2;
private Object3 o_3;
...
}
Somewhere else in the program, I have only the identity of o_2, and I wanted to get at o_3.
In theory, there might be an easier way than having to put all of the instances of SampleClass in a collection somewhere. For instance, perhaps in my dreams, there is a software language out there, where the superclass DOES carry information about its subclasses, and the superclass serves as a collection in and of itself.
But nevermind that. To me now, it seems like a good way to put the collection somewhere in the program, is to use a hashmap/hashtable, and to use it as a static member of the superclass.
Please tell me there is a better way. Is there any way to reference field A in an object by having only a reference to field B in an object?
For instance, say I have an ActionPerformed method, it has a source object that is contained in the ActionEvent object parameter. How would I find the instance of the class that owned/contained that source object? What is the best way to design this?
There is no native way to find the owner of a field given the object the field references. The JVM records the number of references pointing to each object so it can do garbage collection, but it doesn't keep track of the owners of the references.
You can store the values of all the fields in a Map which maps them to their owners:
import java.util.*;
public class Super
{
static Map<Object, Super> owners = new IdentityHashMap<Object, Super>();
// IdentityHashMap will not work with primitives due to autoboxing,
// but HashMap requires all field values to have sensible implementations
// of hashCode() and equals().
/** Gets the owner associated with a field. */
public static Object getOwner(Object field)
{
return owners.get(field);
}
/** Establishes ownership over a field. */
protected void own(Object field)
{
owners.put(field, this);
}
/** Removes an ownership, but only if this is the owner. */
protected void disown(Object field)
{
if (owners.get(field) == this) owners.remove(field);
}
/** Shorthand for disown(oldField); own(newField). */
protected <T> T change(T oldField, T newField)
{
disown(oldField);
own(newField);
return newField;
}
}
public class SubA extends Super
{
protected String s;
protected Integer i;
public SubA(String aString, Integer anInt) { setS(aString); setI(anInt); }
public void setS(String aString) { s = change(s, aString); }
public void setI(Integer anInt) { i = change(i, anInt); }
public String toString() { return "SubA(" + s + "," + i + ")"; }
}
public class SubB extends Super
{
protected Object o;
public SubB(Object anObject) { setO(anObject); }
public void setO(Object anObject) { o = change(o, anObject); }
public String toString() { return "SubB(" + o + ")"; }
}
public class Demo
{
public static void main(String[] args)
{
String s1 = "String1", s2 = "String2", s3 = "String3";
Integer i1 = 111, i2 = 222;
Object o1 = new Object(), o2 = new Object();
SubA a1 = new SubA(s1, i1), a2 = new SubA(s2, i2);
SubB b = new SubB(o1);
p("s1 owner = %s", Super.getOwner(s1)); // SubA(String1,111)
p("s2 owner = %s", Super.getOwner(s2)); // SubB(String2,222)
p("s3 owner = %s", Super.getOwner(s3)); // null
p("i1 owner = %s", Super.getOwner(i1)); // SubA(String1,111)
p("i2 owner = %s", Super.getOwner(i2)); // SubA(String2,222)
p("o1 owner = %s", Super.getOwner(o1)); // SubB(java.lang.Object#...)
p("o2 owner = %s", Super.getOwner(o2)); // null
p("s1 -> s3, o1 -> o2");
a1.setS(s3);
b.setO(o2);
p("s1 owner = %s", Super.getOwner(s1)); // null
p("s3 owner = %s", Super.getOwner(s3)); // SubA(String3,111)
p("o1 owner = %s", Super.getOwner(o1)); // null
p("o2 owner = %s", Super.getOwner(o2)); // SubB(java.lang.Object#...)
}
static void p(String fmt, Object... args)
{
System.out.format(fmt, args);
System.out.println();
}
}
Or you could make the field values themselves maintain a reference to their owner, either through inheritance or using a wrapper class:
public class OwnableObject
{
protected Object owner;
public OwnableObject(Object anOwner) { owner = anOwner; }
public Object getOwner() { return owner; }
public void setOwner(Object anOwner) { owner = anOwner; }
}
public class MyString extends OwnableObject
{
protected String str = null;
public MyString(Object anOwner) { super(anOwner); }
public String toString() { return str; }
public void set(String aString) { str = aString; }
}
public class FieldWrapper<E> extends OwnableObject
{
protected E value = null;
public FieldWrapper(Object anOwner) { super(anOwner); }
public E getValue() { return value; }
public void setValue(E aValue) { value = aValue; }
}
public class Demo
{
protected MyString s = new MyString(this);
protected FieldWrapper<Integer> i = new FieldWrapper<Integer>(this);
public void setS(String aString) { s.set(aString); }
public void setI(int anInt) { i.setValue(anInt); }
public String toString() { return "Demo(" + s + "," + i.getValue() + ")"; }
public static void main(String[] args)
{
Demo d1 = new Demo();
Demo d2 = new Demo();
MyString f1 = d1.s;
FieldWrapper<Integer> f2 = d1.i;
OwnableObject f3 = d2.s;
OwnableObject f4 = d2.i;
d1.setS("one");
d2.setS("two");
d1.setI(1000);
d2.setI(2000);
p("f1 = %s, owner = %s", f1, f1.getOwner());
p("f2 = %d, owner = %s", f2.getValue(), f2.getOwner());
p("f3 = %s, owner = %s", f3, f3.getOwner());
p("f4 = %s, owner = %s", f4, f4.getOwner());
}
static void p(String fmt, Object... args)
{
System.out.format(fmt, args);
System.out.println();
}
}
Answering your direct question: how to easily define a collection containing a given set of classes?
public class ClassA {
private final List<Class<? extends a>> knownSubclasses = Arrays.asList(ClassB.class, ClassC.class);
};
class ClassB extends ClassA {}
class ClassC extends ClassA {}
Answering your motivation: how to access a field in a subclass without declaring it for the super class?
public class SomeSuperclass {
protected Object3 getObject3() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
}
public class SomeSubclass extends SomeSuperclass {
private final Object3 object3 = null;
#Override
protected Object3 getObject3() { return object3; }
}
Maybe recognize instances having an object3 by the use of interfaces
public interface MyClassWithObject3 { Object3 getObject3(); }
...
void someOperation(SomeSuperclass that) {
if (that instanceof MyClassWithObject3) { ... }
}
You could also use named properties
void someOperation(SomeSuperClass that) {
Object3 object3 = that.getProperty("object3");
}

Categories