I am looking for an algorithm in Java that creates an object thats attributes are set to the first not-null value of a string of objects. Consider this array (I will use JSON syntax to represent the array for the sake of simplicity):
{
"objects": [
{
"id": 1,
"val1": null,
"val2": null,
"val3": 2.0
},
{
"id": 2,
"val1": null,
"val2": 3.8,
"val3": 6.0
},
{
"id": 3,
"val1": 1.98,
"val2": 1.8,
"val3": 9.0
}
]
}
In the end, I want one object that looks like this:
{
"id": 1,
"val1": 1.98,
"val2": 3.8,
"val3": 2.0
}
Where val1 comes from the third object, val2 from the secound and val3 and id from the first, because these are the first objects found where the attribute isn't null.
What I have done so far in Java and what works really fine is this:
// Java code that proceeds a deserialized representation of the
// above mentioned array
int k = 0;
while (bs.getVal1() == null) {
k++;
bs.setVal1(objectArray.get(k).getVal1());
}
However, I am not satisfied, because I would have to write this code four times (getId, getVal1, getVal2, getVal3). I am sure there must be a rather generic approach. Any Java guru who could give a Java beginner an advice?
Before getting to your actual question, here's a better way of writing your existing loop: (replace Object with whatever the actual type is)
for (Object o : objectArray) {
Double d = o.getVal1();
if (d != null) {
bs.setVal1(d);
break;
}
}
Considering the way your objects are laid out now, there isn't a better way to do it. But you can do better if you change the structure of your objects.
One way is to put your different value fields (val1, val2, ...) into an array:
Double val[] = new Double[3]; // for 3 val fields
public Double getVal(int index) {
return val[index];
}
public void setVal(int index, Double value) {
val[index] = value;
}
Then you can simply access the fields by their index and set all fields of bs in one iteration of the object array:
for (Object o : objectArray) {
for (int i = 0; i < 3; i++) {
Double d = o.getVal(i);
if (d != null) {
bs.setVal(i, d);
break;
}
}
}
+1 - Code repetition is a problem that can sometimes be hard to overcome in Java.
One solution is to create an Iterable class which allows you to iterate over the values in one of those objects as if they were in an array. This takes away some of the repetition from your code without sacraficing the legibility benefits of named variables.
Note that In my code below, I've created a separate iterable class, but you could also simply make the POD class iterable (which one of these is the best option for you depends on details you didn't cover with your example code):
(warning - not tested yet)
import java.util.Iterator;
public class Main {
static class POD{
Integer id; Double val1; Double val2; Double val3;
public POD(Integer i, Double v1, Double v2, Double v3){
id=i; val1=v1; val2=v2; val3=v3;
}
public POD(){ }
}
static class PODFields implements Iterable<Number>{
private POD pod;
public PODFields(POD pod){
this.pod=pod;
}
public PODFieldsIterator iterator() {
return new PODFieldsIterator(pod);
}
}
static class PODFieldsIterator implements Iterator<Number>{
int cur=0;
POD pod;
public PODFieldsIterator(POD pod) {
this.pod=pod;
}
public boolean hasNext() { return cur<4; }
public Number next() {
switch(cur++){
case 0:
return pod.id;
case 1:
return pod.val1;
case 2:
return pod.val2;
case 3:
return pod.val3;
}
return null;//(there are better ways to handle this case, but whatever)
}
public void remove() { throw new UnsupportedOperationException("You cannot remove a POD field."); }
}
public static void main(String[] args) {
POD [] objectArray = {new POD(1,null,null,2.0),
new POD(1,null,null,2.0),
new POD(1,null,null,2.0),
new POD(1,null,null,2.0)};
POD finalObject=new POD();
for (POD cur : objectArray){
PODFieldsIterator curFields = new PODFields(cur).iterator();
for (Number finalValue : new PODFields(finalObject)){
Number curValue = curFields.next();
if (finalValue==null)
finalValue=curValue;
}
}
for (Number finalValue : new PODFields(finalObject))
System.out.println(finalValue);
}
}
Edit: Oops - looks like I forgot Numbers are immutable. I suppose you could overcome this by having the iterator return functors or something, but that's possibly going a bit overboard.
Whenever you want to eliminate code duplication, one of the first things you look for is whether you can extract a reusable method. Reflection helps you call arbitrary methods in a reusable way. Its not the prettiest thing in the world, but this method works for you:
#SuppressWarnings("unchecked")
public <T> T firstNonNull(String methodName, TestObject... objs) {
try {
Method m = TestObject.class.getMethod(methodName, (Class[])null);
for (TestObject testObj : objs) {
T retVal = (T)m.invoke(testObj, (Object[])null);
if (retVal != null) return retVal;
}
return null;
} catch (Exception e) {
//log, at a minimum
return null;
}
}
Testing with a class like this:
public class TestObject {
Integer id;
String val1;
Map<String, Boolean> val2;
public int getId() {
return id;
}
public String getVal1() {
return val1;
}
public Map<String, Boolean> getVal2() {
return val2;
}
}
This JUnit test demonstrates its usage:
#org.junit.Test
public void testFirstNonNull() {
TestObject t1 = new TestObject();
t1.id = 1;
t1.val1 = "Hello";
t1.val2 = null;
TestObject t2 = new TestObject();
Map<String, Boolean> map = new HashMap<String, Boolean>();
t2.id = null;
t2.val1 = "World";
t2.val2 = map;
TestObject result = new TestObject();
result.id = firstNonNull("getId", t1, t2);
result.val1 = firstNonNull("getVal1", t1, t2);
result.val2 = firstNonNull("getVal2", t1, t2);
Assert.assertEquals(result.id, (Integer)1);
Assert.assertEquals(result.val1, "Hello");
Assert.assertSame(result.val2, map);
}
Related
I have a collection of Java objects where I want to run a single function across multiple values I might find in some of the object's member variables. I'm looking for a nice way to pass in which getter should be used so I can have one method do all that work. I was thinking about something like a Supplier, but that would mean I have to have one per instance of the class. Here's an example of what I'm trying to do (only I would like to do this without the if statement or with potentially n getters a switch statement:
import java.util.ArrayList;
import java.util.List;
public class TestSupplier {
private int varA;
private int varB;
public TestSupplier(int varA, int varB) {
this.varA = varA;
this.varB = varB;
}
public int getA() {
return this.varA;
}
public int getB() {
return this.varB;
}
public static void main(String[] args) {
List<TestSupplier> testList = new ArrayList<>();
testList.add(new TestSupplier(1, 11));
testList.add(new TestSupplier(2, 22));
// Can I pass something like a generic supplier instead of a bool?
TestSupplier.someCollectorFunction(testList, true);
TestSupplier.someCollectorFunction(testList, false);
}
public static void someCollectorFunction(List<TestSupplier> list, boolean isA /* what if I want more than one getter*/) {
int sum = 0;
for (TestSupplier obj: list) {
// This is where I wish I could have a generic supplier or something
if (isA) {
sum = sum + obj.getA();
}
else {
sum = sum + obj.getB();
}
}
System.out.println("I have a sum: " + sum);
}
}
Is there something is Java's functional API that would let me do this?
It sounds like what you want is
ToIntFunction<TestSupplier> fn = isA ? TestSupplier::getA : TestSupplier::getB;
for (TestSupplier obj: list) {
sum += fn.applyAsInt(obj);
}
It's up to you whether you consider that an improvement.
You could also pass in the ToIntFunction instead of the boolean, passing in TestSupplier::getA instead of true etc.
I want to implement storing of enabled or disabled features into database row. When some String value is received from them the network I would like to compare it into ENUM.
ENUM:
public enum TerminalConfigurationFeatureBitString {
Authorize("authorize", 0), // index 0 in bit string
Authorize3d("authorize3d", 1), // index 1 in bit String
Sale("sale", 2), // index 2 in bit String
Sale3d("sale3d", 3), // index 3 in bit String
}
Map<TerminalConfigurationFeatureBitString, Boolean> featureMaps =
config.initFromDatabaseValue(optsFromDatabase);
featureMaps.get(transaction.transactionType);
The best way is to use featureMaps.get(TerminalConfigurationFeatureBitString.Sale);
But I don't know the incoming string what would be.
Now I get warning Unlikely argument type String for get(Object) on a Map<TerminalConfigurationFeatureBitString,Boolean>
Is there any other way to make a query into the ENUM without knowing the key?
In cases like these, I often find myself adding a static method getByX which does a lookup based upon a property of the enum:
public enum BitString {
//...
public static Optional<BitString> getByTransactionType(String transactionType)
{
return Arrays.stream(values())
.filter(x -> x.transactionType.equals(transactionType))
.findFirst();
}
}
Usage:
enum TransactionStatus
{
ENABLED, NOT_ENABLED, NOT_SUPPORTED
}
TransactionStatus status = BitString.getBygetByTransactionType(transaction.transactionType)
.map(bitString -> featureMaps.get(bitString))
.map(enabled -> enabled ? TransactionStatus.ENABLED : TransactionStatus.NOT_ENABLED)
.orElse(TransactionStatus.NOT_SUPPORTED);
Similar to #Michael's answer, you can just generate a static lookup map inside your enum which maps an enums transaction type to the actual enum:
private static final Map<String, TerminalConfigurationFeatureBitString> TRANSACTION_TYPE_TO_ENUM =
Arrays.stream(values()).collect(Collectors.toMap(
TerminalConfigurationFeatureBitString::getTransactionType,
Function.identity()
);
And then have a lookup method, also inside the enum:
public static TerminalConfigurationFeatureBitString getByTransactionType(String transactionType) {
TerminalConfigurationFeatureBitString bitString = TRANSACTION_TYPE_TO_ENUM.get(transactionType);
if(bitString == null) throw new NoSuchElementException(transactionType);
return bitString;
}
This in a way more performant than the mentioned answer, because the Map is created the first time the enum is loaded (So when it is the first time referenced). And thus the iteration happens only once. Also Maps have a rather fast lookup time so you could say that getting an enum this way works O(1) (when ignoring the initial computation time of O(n))
You can extend your enum with extra static method which will try to convert given String on enum item:
enum TerminalConfigurationFeatureBitString {
Authorize("authorize", 0), // index 0 in bit string
Authorize3d("authorize3d", 1), // index 1 in bit String
Sale("sale", 2), // index 2 in bit String
Sale3d("sale3d", 3); // index 3 in bit String
private final String value;
private final int index;
TerminalConfigurationFeatureBitString(String value, int index) {
this.value = value;
this.index = index;
}
public String getValue() {
return value;
}
public int getIndex() {
return index;
}
public static Optional<TerminalConfigurationFeatureBitString> fromValue(String value) {
for (TerminalConfigurationFeatureBitString item : values()) {
if (item.value.equals(value)) {
return Optional.of(item);
}
}
return Optional.empty();
}
}
In case option is not found, return Optional.empty(). If feature is not present it means String representation does not represent any feature. Usage:
public void test() {
EnumMap<TerminalConfigurationFeatureBitString, Boolean> featureMaps = new EnumMap<>(
TerminalConfigurationFeatureBitString.class);
Optional<TerminalConfigurationFeatureBitString> feature = TerminalConfigurationFeatureBitString.fromValue("authorize");
if (!feature.isPresent()) {
System.out.println("Feature is not foudn!");
} else {
Boolean authorize = featureMaps.get(feature.get());
if (authorize != null && authorize) {
System.out.println("Feature is enabled!");
} else {
System.out.println("Feature is disabled!");
}
}
}
I'm trying to find number of words in given string. Below is sequential algorithm for it which works fine.
public int getWordcount() {
boolean lastSpace = true;
int result = 0;
for(char c : str.toCharArray()){
if(Character.isWhitespace(c)){
lastSpace = true;
}else{
if(lastSpace){
lastSpace = false;
++result;
}
}
}
return result;
}
But, when i tried to 'parallelize' this with Stream.collect(supplier, accumulator, combiner) method, i am getting wordCount = 0. I am using an immutable class (WordCountState) just to maintain the state of word count.
Code :
public class WordCounter {
private final String str = "Java8 parallelism helps if you know how to use it properly.";
public int getWordCountInParallel() {
Stream<Character> charStream = IntStream.range(0, str.length())
.mapToObj(i -> str.charAt(i));
WordCountState finalState = charStream.parallel()
.collect(WordCountState::new,
WordCountState::accumulate,
WordCountState::combine);
return finalState.getCounter();
}
}
public class WordCountState {
private final boolean lastSpace;
private final int counter;
private static int numberOfInstances = 0;
public WordCountState(){
this.lastSpace = true;
this.counter = 0;
//numberOfInstances++;
}
public WordCountState(boolean lastSpace, int counter){
this.lastSpace = lastSpace;
this.counter = counter;
//numberOfInstances++;
}
//accumulator
public WordCountState accumulate(Character c) {
if(Character.isWhitespace(c)){
return lastSpace ? this : new WordCountState(true, counter);
}else{
return lastSpace ? new WordCountState(false, counter + 1) : this;
}
}
//combiner
public WordCountState combine(WordCountState wordCountState) {
//System.out.println("Returning new obj with count : " + (counter + wordCountState.getCounter()));
return new WordCountState(this.isLastSpace(),
(counter + wordCountState.getCounter()));
}
I've observed two issues with above code :
1. Number of objects (WordCountState) created are greater than number of characters in the string.
2. Result is always 0.
3. As per accumulator/consumer documentation, shouldn't the accumulator return void? Even though my accumulator method is returning an object, compiler doesn't complain.
Any clue where i might have gone off track?
UPDATE :
Used solution as below -
public int getWordCountInParallel() {
Stream<Character> charStream = IntStream.range(0, str.length())
.mapToObj(i -> str.charAt(i));
WordCountState finalState = charStream.parallel()
.reduce(new WordCountState(),
WordCountState::accumulate,
WordCountState::combine);
return finalState.getCounter();
}
You can always invoke a method and ignore its return value, so it’s logical to allow the same when using method references. Therefore, it’s no problem creating a method reference to a non-void method when a consumer is required, as long as the parameters match.
What you have created with your immutable WordCountState class, is a reduction operation, i.e. it would support a use case like
Stream<Character> charStream = IntStream.range(0, str.length())
.mapToObj(i -> str.charAt(i));
WordCountState finalState = charStream.parallel()
.map(ch -> new WordCountState().accumulate(ch))
.reduce(new WordCountState(), WordCountState::combine);
whereas the collect method supports the mutable reduction, where a container instance (may be identical to the result) gets modified.
There is still a logical error in your solution as each WordCountState instance starts with assuming to have a preceding space character, without knowing the actual situation and no attempt to fix this in the combiner.
A way to fix and simplify this, still using reduction, would be:
public int getWordCountInParallel() {
return str.codePoints().parallel()
.mapToObj(WordCountState::new)
.reduce(WordCountState::new)
.map(WordCountState::getResult).orElse(0);
}
public class WordCountState {
private final boolean firstSpace, lastSpace;
private final int counter;
public WordCountState(int character){
firstSpace = lastSpace = Character.isWhitespace(character);
this.counter = 0;
}
public WordCountState(WordCountState a, WordCountState b) {
this.firstSpace = a.firstSpace;
this.lastSpace = b.lastSpace;
this.counter = a.counter + b.counter + (a.lastSpace && !b.firstSpace? 1: 0);
}
public int getResult() {
return counter+(firstSpace? 0: 1);
}
}
If you are worrying about the number of WordCountState instances, note how many Character instances this solution does not create, compared to your initial approach.
However, this task is indeed suitable for mutable reduction, if you rewrite your WordCountState to a mutable result container:
public int getWordCountInParallel() {
return str.codePoints().parallel()
.collect(WordCountState::new, WordCountState::accumulate, WordCountState::combine)
.getResult();
}
public class WordCountState {
private boolean firstSpace, lastSpace=true, initial=true;
private int counter;
public void accumulate(int character) {
boolean white=Character.isWhitespace(character);
if(lastSpace && !white) counter++;
lastSpace=white;
if(initial) {
firstSpace=white;
initial=false;
}
}
public void combine(WordCountState b) {
if(initial) {
this.initial=b.initial;
this.counter=b.counter;
this.firstSpace=b.firstSpace;
this.lastSpace=b.lastSpace;
}
else if(!b.initial) {
this.counter += b.counter;
if(!lastSpace && !b.firstSpace) counter--;
this.lastSpace = b.lastSpace;
}
}
public int getResult() {
return counter;
}
}
Note how using int to represent unicode characters consistently, allows to use the codePoint() stream of a CharSequence, which is not only simpler, but also handles characters outside the Basic Multilingual Plane and is potentially more efficient, as it doesn’t need boxing to Character instances.
When you implemented stream().collect(supplier, accumulator, combiner) they do return void (combiner and accumulator). The problem is that this:
collect(WordCountState::new,
WordCountState::accumulate,
WordCountState::combine)
In your case actually means (just the accumulator, but same goes for the combiner):
(wordCounter, character) -> {
WordCountState state = wc.accumulate(c);
return;
}
And this is not trivial to get indeed. Let's say we have two methods:
public void accumulate(Character c) {
if (!Character.isWhitespace(c)) {
counter++;
}
}
public WordCountState accumulate2(Character c) {
if (Character.isWhitespace(c)) {
return lastSpace ? this : new WordCountState(true, counter);
} else {
return lastSpace ? new WordCountState(false, counter + 1) : this;
}
}
For the them the below code will work just fine, BUT only for a method reference, not for lambda expressions.
BiConsumer<WordCountState, Character> cons = WordCountState::accumulate;
BiConsumer<WordCountState, Character> cons2 = WordCountState::accumulate2;
You can imagine it slightly different, via an class that implementes BiConsumer for example:
BiConsumer<WordCountState, Character> clazz = new BiConsumer<WordCountState, Character>() {
#Override
public void accept(WordCountState state, Character character) {
WordCountState newState = state.accumulate2(character);
return;
}
};
As such your combine and accumulate methods needs to change to:
public void combine(WordCountState wordCountState) {
counter = counter + wordCountState.getCounter();
}
public void accumulate(Character c) {
if (!Character.isWhitespace(c)) {
counter++;
}
}
First of all, would it not be easier to just use something like input.split("\\s+").length to get the word count?
In case this is an exercise in streams and collectors, let's discuss your implementation. The biggest mistake was pointed out by you already: Your accumulator and combiner should not return new instances. The signature of collect tells you that it expects BiConsumer, which do not return anything. Because you create new object in the accumulator, you never increase the count of the WordCountState objects your collector actually uses. And by creating a new object in the combiner you would discard any progress you would have made. This is also why you create more objects than characters in your input: one per character, and then some for the return values.
See this adapted implementation:
public static class WordCountState
{
private boolean lastSpace = true;
private int counter = 0;
public void accumulate(Character character)
{
if (!Character.isWhitespace(character))
{
if (lastSpace)
{
counter++;
}
lastSpace = false;
}
else
{
lastSpace = true;
}
}
public void combine(WordCountState wordCountState)
{
counter += wordCountState.counter;
}
}
Here, we do not create new objects in every step, but change the state of the ones we have. I think you tried to create new objects because your Elvis operators forced you to return something and/or you couldn't change the instance fields as they are final. They do not need to be final, though, and you can easily change them.
Running this adapted implementation sequentially now works fine, as we nicely look at the chars one by one and end up with 11 words.
In parallel, though, it fails. It seems it creates a new WordCountState for every char, but does not count all of them, and ends up at 29 (at least for me). This shows a basic flaw with your algorithm: Splitting on every character doesn't work in parallel. Imagine the input abc abc, which should result in 2. If you do it in parallel and do not specify how to split the input, you might end up with these chunks: ab, c a, bc, which would add up to 4.
The problem is that by parallelizing between characters (i.e. in the middle of words), you make your separate WordCountStates dependent on each other (because they would need to know which one come before them and whether it ended with a whitespace char). This defeats the parallelism and results in errors.
Aside from all that, it might be easier to implement the Collector interface instead of providing the three methods:
public static class WordCountCollector
implements Collector<Character, SimpleEntry<AtomicInteger, Boolean>, Integer>
{
#Override
public Supplier<SimpleEntry<AtomicInteger, Boolean>> supplier()
{
return () -> new SimpleEntry<>(new AtomicInteger(0), true);
}
#Override
public BiConsumer<SimpleEntry<AtomicInteger, Boolean>, Character> accumulator()
{
return (count, character) -> {
if (!Character.isWhitespace(character))
{
if (count.getValue())
{
String before = count.getKey().get() + " -> ";
count.getKey().incrementAndGet();
System.out.println(before + count.getKey().get());
}
count.setValue(false);
}
else
{
count.setValue(true);
}
};
}
#Override
public BinaryOperator<SimpleEntry<AtomicInteger, Boolean>> combiner()
{
return (c1, c2) -> new SimpleEntry<>(new AtomicInteger(c1.getKey().get() + c2.getKey().get()), false);
}
#Override
public Function<SimpleEntry<AtomicInteger, Boolean>, Integer> finisher()
{
return count -> count.getKey().get();
}
#Override
public Set<java.util.stream.Collector.Characteristics> characteristics()
{
return new HashSet<>(Arrays.asList(Characteristics.CONCURRENT, Characteristics.UNORDERED));
}
}
We use a pair (SimpleEntry) to keep the count and the knowledge about the last space. This way, we do not need to implement the state in the collector itself or write a param object for it. You can use this collector like this:
return charStream.parallel().collect(new WordCountCollector());
This collector parallelizes nicer than the initial implementation, but still varies in results (mostly between 14 and 16) because of the mentioned weaknesses in your approach.
I got a problem where I am not allowed to use switch/case or if/else queries.
I got a config file I read which is this:
650;0;1.5;month
614;0;2.88;year
466;0;2.48;week
716;0;4.6;half-year
718;0;2.6;quarter
I am splitting those Strings at the ";", so it is saved in an array. The problem I have, that I need to do other things in the code for each time given in that array ar[3], so if it is a month I need other calculations then when it is a full year.
But I am not allowed to do this with Switch/case or If/Else, now I am getting confused.
If (ar[3] = month){
do this;
else if (ar[3] = year) {
do this;
}
How am I doing this object oriented? Thanks for every help :)
Polymorphism by Inheritance is your friend
It seems like you need some sort of inheritance structure based on the time period in ar[3]. The special do this method could be coded for each case. That way you get the ability to do something different for each case. You just need a way to instantiate the correct subtype in the first place. There are a number of ways you could approach this.
The Conditional Operator
The most direct approach IMHO is the conditional operator, ?:.
So the code would look something like this:
MyClass x = ar[3].equals("month") ? new MyClassMonth() :
(ar[3].equals("year") ? new MyClassYear() :
(ar[3].equals("week") ? new MyClassWeek() :
(ar[3].equals("half-year") ? new MyClassHalfyear() :
new MyClassQuarter())));
x.doSomething();
The nested conditional expressions give you the ability to select the right class, and the inheritance gives you the polymorphic behavior you want.
But you mentioned in comment that you also can't use ?:. What next?
A Map of Stateless Objects
Suppose you wrote MyClassMonth in a way that nothing in it depended on any remembered state, i.e. the doSomething() method has no side effects. Then you could create a Map<String, MyClass> to store one instance of each subclass, then pull the relevant one out of the map when you needed to invoke.
You'd initialize the map like this:
final Map<String, MyClass> themap = new HashMap<>();
{
themap.add("month", new MyClassMonth());
themap.add("year", new MyClassYear());
themap.add("week", new MyClassWeek());
themap.add("half-year", new MyClassHalfyear());
themap.add("quarter", new MyClassQuarter());
}
And invoke doSomething() with ar as argument:
MyClass x = themap.get(ar[3]);
if (x != null)
x.doSomething(ar);
Other Options
There are other ways to do this. Sticking with the Map concept, you could store class literals in the Map instead of instances, then instantiate them reflectively. You could also keep a lambda in the Map and invoke it.
Enums
#OldCurmudgeon suggested using enums. If you put those enums into the Map and add a lambda to the enum, you can grab the enum and invoke the lambda. That would work and has a certain appeal, but it seems unnecessary. You'd be better off just invoking the lambda directly.
You could use an enum as a command factory pattern and implement the choice with a Map lookup.
// Lookups for teh period.
static final Map<String, Period> lookup = new HashMap<>();
enum Period {
Month("month") {
#Override
void process(int x, int y, double v) {
// Processing for "month" records here.
System.out.println(this + "-process(" + x + "," + y + "," + v + ")");
}
},
Year("year") {
#Override
void process(int x, int y, double v) {
// Processing for "year" records here.
System.out.println(this + "-process(" + x + "," + y + "," + v + ")");
}
},
Quarter("quarter") {
#Override
void process(int x, int y, double v) {
// Processing for "quarter" records here.
System.out.println(this + "-process(" + x + "," + y + "," + v + ")");
}
},
HalfYear("half-year") {
#Override
void process(int x, int y, double v) {
// Processing for "half-year" records here.
System.out.println(this + "-process(" + x + "," + y + "," + v + ")");
}
};
Period(String inData) {
// Record me in the map.
lookup.put(inData, this);
}
abstract void process(int x, int y, double v);
static void process(String data) {
String[] parts = data.split(";");
Period p = lookup.get(parts[3]);
if (p != null) {
p.process(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Double.parseDouble(parts[2]));
}
}
}
public void test() {
String[] test = {"650;0;1.5;month",
"614;0;2.88;year",
"466;0;2.48;week",
"716;0;4.6;half-year",
"718;0;2.6;quarter",};
for (String s : test) {
Period.process(s);
}
}
correctly prints:
Month-process(650,0,1.5)
Year-process(614,0,2.88)
HalfYear-process(716,0,4.6)
Quarter-process(718,0,2.6)
Note that there is one if in there but that is only defensive to avoid bad data - it is not part of the lookup mechanism.
Something like this:
public interface Calculator {
double calculate(int p1, int p2, double p3);
}
public class YearCalculator implements Calculator {
public double calculate(int p1, int p2, double p3) {
double value = 0.0;
// do year calculations
return value;
}
}
public class CalculatorFactory {
public Calculator getInstance(String type) {
Calculator calculator = null;
if (type != null) {
} else {
throw new IllegalArgumentException("calculator type cannot be null");
if ("year".equalsIgnoreCase(type)) {
} else {
System.out.println(String.format("No such type: %s", type));
}
}
return calculator;
}
}
You have to have if/else logic in the factory, but not when you're parsing the text.
Your processing code:
CalculatorFactory factory = new CalculatorFactory();
// contents is a List of Strings from your input file.
for (String line : contents) {
String [] tokens = line.split(";");
Calculator calculator = factory.getInstance(tokens[3]);
double value = calculator.calculate(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1]), Double.parseDouble(tokens[2]));
}
Building upon the suggestion given by Codebender as an alternative solution:
You need 5 classes, one for each case, with a common interface but different implementations.
Your interface may look something like this:
public interface MyCalculator {
public double calculate(double a, double b, double c);
}
Then you will need to implement your 5 classes similar to this. You will need a different class with a different implementation for calculate for month, year, week, half-year and quarter:
public class MyMonthCalculator implements MyCalculator {
#Override
public double calculate(double a, double b, double c) {
// Do your calculations here then return
}
}
Then, before your parsing logic, you can add the five classes to a Map.
map.put("month", new MyMonthCalculator());
// Repeat for year, week, half-year and quarter
To actually perform a calculation:
double result = map.get(ar[3]).calculate(Double.parseDouble(ar[0]), Double.parseDouble(ar[1]), Double.parseDouble(ar[2]));
You can simulate if or case with arrays of options. Only problem here would be finding index of our element in such array. We can't use if and case but I assume that while is an option.
So your code can be similar to something like:
String[] options = { "foo", "bar", "baz" };
Runnable[] action = { new Runnable() {
#Override
public void run() {
System.out.println("handling foo");
}
}, new Runnable() {
#Override
public void run() {
System.out.println("handling bar");
}
}, new Runnable() {
#Override
public void run() {
System.out.println("handling baz");
}
} };
String choice = "bar";
int matched = 0;
int i = -1;
while (matched != 1) {
i++;
matched = boolToInt(options[i].equals(choice));
}
action[i].run();
I used method like this to convert boolean to integer where 1=true, 0=false
public static int boolToInt(Boolean b) {
return 5 - b.toString().length();
}
Instead Runnable you can provide your own interface.
How do I print the content of an object recursively?
You can print it recursively by overriding toString in all your classes.
If you want to have a method like printObjectRecursively(Object o) you need to dive into reflection, fetch the fields, print their name and content recursively using printObjectRecursively(someField).
Example:
public class Test {
public static void main(String[] args) {
A a = new A();
System.out.println(a);
}
}
class A {
int i = 5;
B obj = new B();
String str = "hello";
public String toString() {
return String.format("A: [i: %d, obj: %s, str: %s]", i, obj, str);
}
}
class B {
int j = 17;
public String toString() {
return String.format("B: [j: %d]", j);
}
}
Prints:
A: [i: 5, obj: B: [j: 17], str: hello]
A reflection-based recursive print method could be written something like this
private static final List LEAVES = Arrays.asList(
Boolean.class, Character.class, Byte.class, Short.class,
Integer.class, Long.class, Float.class, Double.class, Void.class,
String.class);
public static String toStringRecursive(Object o) throws Exception {
if (o == null)
return "null";
if (LEAVES.contains(o.getClass()))
return o.toString();
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getSimpleName()).append(": [");
for (Field f : o.getClass().getDeclaredFields()) {
if (Modifier.isStatic(f.getModifiers()))
continue;
f.setAccessible(true);
sb.append(f.getName()).append(": ");
sb.append(toStringRecursive(f.get(o))).append(" ");
}
sb.append("]");
return sb.toString();
}
You can use:
ToStringBuilder.reflectionToString(this);
Apache Common Lang contains ToStringBuilder class. You can define different style with ToStringStyle object.
I've had great success doing this on a casual basis using XStream to dump JSON representations of objects. It recurses down objects and just seems to do what you want it to do most of the time. And it's super lightweight. Example:
private static final XStream jsonXStream =
new XStream(new JsonHierarchicalStreamDriver());
public static String toDebugString(Object object) {
return jsonXStream.toXML(object);
// ignore "toXML" name, it's going to be JSON.
}
You should implement the toString method for your classes - it will print the information about the class members - usually using their toString methods. |Then you jut iterate through the collection and call toString of each item
Do you really need print this informations out? Maybe watch during debbuging will be enough?
You are looking for something similar to PHP's var_dump, see if this question is of any help: What is the Java equivalent of PHP var_dump?
Also, have a look at reflection: http://java.sun.com/developer/technicalArticles/ALT/Reflection/
You can override the toString method.
Example:
class foo
{
int i,j;
String toString()
{
StringBuilder b=new StringBuilder();
return b.append(i).append(j).toString();
}
}
Use one of the serialization libraries like Jackson (JSON).
This dumps everything in plain text. If you use a Javascript capable editor to prettify the content, with a bit of luck, you might actually make some sense out of it.
If a lot of the objects are not serializable, you might end up with a lot of readable gibberish which will not help anything, unfortunately,
YMMV
In some cases it is easy to use Gson to print object. But sometimes gson does not work.
Here I just improve aioobe' answer for this case:
don't print printed objects (protect from infinite cycles)
print collections
don't go deeply on few basic types, like BigInteger
private static final Set<Object> looked = new HashSet<>();
private static final List LEAVES = Arrays.asList(
Boolean.class, Character.class, Byte.class, Short.class,
Integer.class, Long.class, Float.class, Double.class, Void.class,
String.class, java.math.BigInteger.class, java.math.BigDecimal.class);
public static void toStringRecursive(Object o, StringBuilder sb) throws Exception {
if (o == null) {
sb.append("null");
return;
}
if (looked.contains(o)) return;
looked.add(o);
if (LEAVES.contains(o.getClass())) {
sb.append(o);
return;
}
sb.append(o.getClass().getSimpleName()).append(": [");
if (o.getClass().isArray()) {
for (Object x : (Object[]) o) {
toStringRecursive(x, sb);
}
} else if (Iterable.class.isAssignableFrom(o.getClass())) {
for (Object x : (Iterable) o) {
toStringRecursive(x, sb);
}
} else if (Map.class.isAssignableFrom(o.getClass())) {
for (Object entry : ((Map)o).entrySet()) {
toStringRecursive(entry, sb);
}
} else {
for (Field f : o.getClass().getDeclaredFields()) {
if (Modifier.isStatic(f.getModifiers()))
continue;
f.setAccessible(true);
sb.append(f.getName()).append(": ");
toStringRecursive(f.get(o), sb);
sb.append(" ");
}
}
sb.append("]\n");
}