2016-02-20 19 views
5

Badałem transformację obiektu z jednego rodzaju klasy na inny przy użyciu języka Java 8. Mam pakiet klas XxX generowanych przez jaxb. Klasy nie mają wystarczająco przyjaznej struktury, ponieważ mapują strukturę XML bardziej niż strukturę obiektu biznesowego. Nie chcę edytować wygenerowanych klas, ponieważ lubię je regenerować za każdym razem, gdy zmienia się schemat bez martwienia się o zachowywanie dostosowań.Przekształcanie klas za pomocą Javy 8

Mam coś podobnego schematu:

<xs:element name="farm"> 
    <xs:sequence> 
     <xs:element ref="animal" minOccurs="0" maxOccurs="unbounded"/> 
    </xs:sequence> 
</xs:element> 

<xs:element name="animal"> 
    <xs:complexType> 
     <xs:sequence> 
      <xs:element ref="goat"/> 
      <xs:element ref="sheep"/> 
     </xs:sequence> 
    <xs:complexType> 
</xs:element> 

<xs:element name="goat"> 
    <xs:complexType> 
     <xs:sequence> 
      goat fields 
     </xs:sequence> 
    <xs:complexType> 
</xs:element> 

<xs:element name="sheep"> 
    <xs:complexType> 
     <xs:sequence> 
      sheep fields 
     </xs:sequence> 
    <xs:complexType> 
</xs:element> 

Generuje to coś jak Java:

class Farm 
    public List<Animal> getAnimals() 

class Animal 

    public Goat getGoat() 
     public String getGoatField() 

    public Sheep getSheep() 
     public String getSheepField() 

getGoat i getSheep może zwrócić null, ale nie mogą one być zarówno wartość null. Podobnie przynajmniej jeden z nich musi mieć wartość zerową. Jest to wymuszane przez reguły biznesowe i ograniczenia bazy danych, ale nie w xml (chociaż jeśli ktoś ma sugestię strukturyzowania xml bardziej jak pożądane VO, ja jestem tylko uszami)

Chciałbym przekształcić tę klasę w

class FarmWrapper 
    public ArrayList<AnimalVO> getAnimals() 
    //optional features tbd 
    //possibly public ArrayList<GoatVO> getGoats() 
    //possibly public ArrayList<SheepVO> getSheep() 

class GoatVO extends AnimalVO 

class SheepVO extends AnimalVO 

Mój pomysł był aby zrobić coś takiego:

herd.stream() 
.filter(Objects::nonNull) 
.map(a -> { 
    Optional<AnimalVO> goatVO = Optional.ofNullable(a.getGoat()) 
      .map(g -> new GoatVO(g.getGoatField())); 
    Optional<AnimalVO> sheepVO = Optional.ofNullable(a.getSheep()) 
      .map(s -> new SheepVO(s.getSheepField())); 
    return goatVO.orElse(sheepVO.get()); 
}) 
.collect(Collectors.toList()); 

teraz byłem karmienie go z listy, a gdy tylko napotka null owce, to rzuca NoSuchElementException.

Chyba kilka pytań:

  1. Czy to podejście warto podzielić moją listę do klas, które używają dziedziczenia?
  2. Jaki jest najlepszy sposób na wykorzystanie Opcjonalnie w celu ochrony przed przychodzących wartości potencjalnie NULL, gdy nie można zmienić klas przekazując Ci null
  3. Co mi brakuje z goatVO.orElse(sheepVO.get()) pracuje tak długo, jak goatVO zawiera wartość null, a następnie rzuca NoSuchElementException gdy jest sheepVO zawiera wartość null

Co naprawdę robię, to pracuję z wygenerowanym kodem jaxb i próbuję wziąć wygenerowane klasy i sprawić, że będzie on bardziej przyjazny. Tradycyjnie projekt wykorzystywał klasę wrapper, która przekształca wygenerowane klasy w VO poprzez znaczną ilość kontroli zerowych i int do manipulacji typu BigInteger.

Edycja wygenerowanych klas (kozie, owcze, zwierząt) to nie rozrusznik, ponieważ chciałbym, aby zachować zdolność do regeneracji bez martwienia

+2

Dlaczego potrzebujesz 'GoatVO' lub' SheepVO' zamiast tylko 'Kóz' i' Owca'? –

+2

Twoje ostatnie pytanie jest podobne do tego: http: // stackoverflow.com/questions/31657306/java-8s-orelse-not-working-as-expected/31657522 # 31657522 – Alex

+0

Edytowałem pytanie, aby dodać więcej informacji o tym, co próbuję wykonać i jakie są moje ograniczenia: – monknomo

Odpowiedz

3

myślę, że można zrobić swoją pracę kodu z kilku korektach:

List<AnimalVO> list = herd.stream() 
    .filter(Objects::nonNull) 
    .map(a -> Optional.ofNullable(a.getGoat()) 
     .map(Goat::getGoatField) 
     .<AnimalVO>map(GoatVO::new) 
     .orElseGet(() -> new SheepVO(a.getSheep().getSheepField()))) 
    .collect(Collectors.toList()); 

Uwaga: Wolę metody referencje ponad lambdas, więc mam włączony do korzystania z nich.

Uwaga wskazówki do kompilatora w <AnimalVO>map(GoatVO::new). Jest to konieczne, aby kompilator wiedział, że typ, do którego mapujesz, jest zawsze AnimalVO, w przeciwnym razie oznacza, że ​​pierwszy Optional zwraca GoatVO i daje błąd kompilacji w drugim Optional, który zwraca SheepVO (i SheepVO nie jest potomek GoatVO).

Należy również pamiętać, że używam metody orElseGet() zamiast orElse(). orElseGet() otrzymuje wartość domyślną zamiast domyślnej wartości. Oznacza to, że wartość domyślna jest wybierana leniwie, tylko wtedy, gdy nie ma wartości pierwszego Optional.


EDIT: Jeśli miał więcej zwierząt w swoim gospodarstwie, czyli w odstępie od Goat i Sheep, teraz masz Cow, to w jaki sposób można to zrobić:

List<AnimalVO> list = herd.stream() 
    .filter(Objects::nonNull) 
    .map(a -> Optional.ofNullable(a.getGoat()) 
     .map(Goat::getGoatField) 
     .<AnimalVO>map(GoatVO::new) 
     .orElseGet(() -> Optional.ofNullable(a.getSheep()) 
      .map(Sheep::getSheepField) 
      .<AnimalVO>map(SheepVO::new) 
      .orElseGet(() -> new CowVO(a.getCow().getCowField())))) 
    .collect(Collectors.toList()); 
+0

Zastanawiałem się, jak wykonywać rzuty klasowe w lambdach, składnia uniknęła mnie. – monknomo

+2

Jeśli 'AnimalVO.class :: cast' wygląda zbyt dziwnie lub nieznośnie, możesz raczej użyć zwykłej obsady w wyrażeniu lambda' x - > (AnimalVO) x'. Ale tutaj obsada nie jest konieczna. Po prostu wywnioskowany typ '.map (GoatVO :: new)' jest zbyt wąski, więc możesz powiedzieć '. map (GoatVO :: new) 'zamiast tego, wyraźnie określając zamierzony typ. Wówczas nie jest wymagana kolejna obsada. – Holger

+1

@Holger True, rzutowanie jest niepotrzebne. Będę edytować, dzięki. –

1

myślę, że masz to coś złego dziedziczenia ... Dlaczego nie wystarczy napisać

abstract class Animal {abstract public AnimalVO createVO();} 
class Sheep extends Animal {public SheepVO createVO(){...}} 
class Goat extends Animal {public GoatVO createVO(){...}} 

Następnie, jeśli chcesz cały vos dla zwierząt po prostu

List<Animal> animals = ...; 
List<AnimalVO> vos = animals.stream().map(Animal::createVO).collect(toList()); 

Jeśli możesz wyjaśnić co nieco chcesz osiągnąć, możemy spojrzeć na , jak, aby go osiągnąć.

+0

Byłoby lepiej, ale próbuję opakować wygenerowany kod z czymś przyjaznym – monknomo

3
  1. W zasadzie można to zrobić tak, mimo spadków model byłby lepszy (Sheep extends Animal, Goat extends Animal)
  2. Jeśli chcesz chronić przed potencjalnymi null, można zrobić something = optionalValue.orElse(defaultValue) która będzie zwracać wartość określoną przez Optional jeśli wartość nie jest null lub defaultValue jeżeli wartość w Optional jest null
  3. Linia goatVO.orElse(sheepVO.get()) rzuca NoSuchElementException ponieważ sheepVO zawiera null. Należy pamiętać, że sheepVO.get() oblicza się niezależnie od wartości w goatVO.
+0

Uzgodnione w punkcie 1, ale mam do czynienia z wygenerowanym kodem, który wiernie odtwarza strukturę dokumentu XML. Punkt 3 oświetlał – monknomo

2

return goatVO.orElseGet(sheepVO::get);

Kod jest zachłannie próbuje opcjonalnie odłączyć sheepVO, nawet jeśli jest obecny goatVO. Powoduje to, że drugi dereferencja jest leniwy, tak że występuje tylko wtedy, gdy brak jest goatVO.

RE: Dwa pozostałe pytania, myślę, że konwersja do gwarantowanego interfejsu nie ma nic wspólnego jest świetna. To powiedziawszy, jest to prawdopodobnie pomocne tylko wtedy, gdy twój AnimalVO jest rzeczywiście użytecznym interfejsem (np. Twój kod nie dba o to, z jakim typem zwierząt pracuje). Jeśli twój drugi kod nie obchodzi Cię, możesz potrzebować czegoś takiego, jak visitor pattern, aby umożliwić twoim konsumentom pracę z poszczególnymi typami.

Lepiej, nie idź zbyt funkcjonalnie i po prostu odbierz dane wejściowe na dwie listy oczekujących: goats i sheeps i zwróć je; która z tych funkcji najlepiej pasuje do Twojego przypadku użycia, zależy w dużej mierze od tego, w jaki sposób te typy są rzeczywiście używane, więc to prawdopodobnie zależy od Ciebie.

+0

Eh, mam całą "farmę". Czasami zależy mi na konkretnym rodzaju zwierząt, ale czasami nie. Zależy od kontekstu. To wciąż zabawka, aby zbadać, co chcę zrobić z prawdziwą okazją – monknomo

Powiązane problemy