Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weirdness Using Google Guava Collections2.transform

I am not entirely sure what title to put on this problem to get the right minds. This seems like a Java slight of hand but it's only occuring using the Guava Collections2.transform. The transformer is providing a completely different instance of my object during iteration on 'results' then what is contained when 'results' is finally return. And thus, the 'setDateStamp()' doesn't actually appear to work because it's being set on instances that seem to just appear and vanish like a ghost.

When I implement what the logical equivalent of the Collections2.transform() method is doing (commented out code) I get the results I expect. I have stepped through the google code, breakpoints and all, and no where is a new instance being created through any method other than my underlying Function.

I get what their implementation is doing: transform-as-needed. Not complicated. So why the hell doesn't this work?

Here is the code in question along with some debug

@Component
public class SurveyResultToQuestionResults implements Function<SurveyResult, Collection<QuestionResult>> {

@Autowired
private QuestionResultDtoToDomain dtoToDomain;

@Override
public Collection<QuestionResult> apply(@Nullable SurveyResult input) {
    Collection<QuestionResult> results = new HashSet<QuestionResult>();
    if (input != null) {
           // substitute this
//            for (QuestionResultDto dto : input.getResults()) {
//                QuestionResult result = dtoToDomain.apply(dto);
//                results.add(result);
//            }
        // for this
        results = Collections2.transform(input.getResults(), dtoToDomain);
        for (QuestionResult result : results) {
            long time = input.getSurveyTime().getTime();
            Timestamp dateStamp = new Timestamp(time);
            result.setDateStamp(dateStamp);
        }

    }
    return results;
    }
}

next class

@Component
public class QuestionResultDtoToDomain implements Function<QuestionResultDto, QuestionResult> {

@Override
public QuestionResult apply(@Nullable QuestionResultDto input) {
    QuestionResult result = null;
    if (input != null)
        result = new QuestionResult(input.getAnswerOriginId(),input.getAnswer(),input.getQuestionId());
    return result;
}

}

And a test

@RunWith(MockitoJUnitRunner.class)
public class SurveyTransformerTest {

    @Spy
    private QuestionResultDtoToDomain dtoToDomain = new QuestionResultDtoToDomain();

    @InjectMocks
    private SurveyResultToQuestionResults surveyResultToQuestionResults = new SurveyResultToQuestionResults();        
    @Test
    public void testSurveyToQuestionResults() throws Exception {
        Set<QuestionResultDto> answers = new HashSet<QuestionResultDto>();
        answers.add(new QuestionResultDto(17L,"question 2 answer"));
        answers.add(new QuestionResultDto(18L,"question 3 answer"));
        answers.add(new QuestionResultDto(19L,"question 4 answer"));
        SurveyResult result = new SurveyResult(10L,16L,new Date(),answers);
        Collection<QuestionResult> qresults = surveyResultToQuestionResults.apply (result);
        System.out.println(qresults);       
        for (QuestionResult qresult : qresults) {
            assertNotNull(qresult.getDateStamp());
        }

    }
}


Debug:
Bad implementation
[QuestionResult{questionResultId=null, answer='question 4 answer', dateStamp=null}, QuestionResult{questionResultId=null, answer='question 2 answer', dateStamp=null}, QuestionResult{questionResultId=null, answer='question 3 answer', dateStamp=null}]

Good implementation:
[QuestionResult{questionResultId=null, answer='question 4 answer', dateStamp=2012-05-17 00:02:18.615}, QuestionResult{questionResultId=null, answer='question 3 answer', dateStamp=2012-05-17 00:02:18.615}, QuestionResult{questionResultId=null, answer='question 2 answer', dateStamp=2012-05-17 00:02:18.615}]
like image 235
Christian Bongiorno Avatar asked Dec 17 '22 00:12

Christian Bongiorno


1 Answers

You're surprised that the writes on the new objects aren't writing through to the backing collection?

Collections.transform doesn't just do the transform "as needed" -- it doesn't store anything at all. That's what "view" means in its documentation. Any time you walk through a Collections2.transformed collection, it applies the function again, freshly. Once that for loop in the apply method is done with result, that object is gone; never seen again.

If you want to do what you're doing, make an explicit copy of the transformed collection in e.g. an ArrayList.

like image 191
Louis Wasserman Avatar answered Dec 29 '22 14:12

Louis Wasserman