Comparable.compareTo(...)

0

Potrzebuję tak zmienić metodę ComparableObject.compareTo(ComparableObject comparableObject):int by a.compareTo(b)==0 wtedy i tylko wtedy gdy a==b

public final class ComparableObject implements Comparable<ComparableObject>{
 public final int value;
 public ComparableObject(final int value){
  this.value=value;
 }
 public int compareTo(ComparableObject comparableObject){
  return comparableObject.value-this.value;
 }
 public boolean equals(Object object){
  return this==object;
 }
}

Nie wiem na jakiej podstawie temat został przeniesiony do kosza - na forum nie było wyjaśnione co zrobić w takim przypadku, nie ma tego w FAQ, nie jest to chyba SPAM no i w końcu nie wyobrażam sobie by łamał on regulamin ;]

0

A nie wystarczy Ci klasa Integer?
Przecież ona robi dokładnie to samo.

Natomiast w kodzie zmieniłbym equals na:

 public boolean equals(Object object){
  return (this.value == ((ComparableObject)object).value);
 }
0

Sęk w tym, że to co napisałem, to nie jest reimplementacja istniejącej już klasy Integer tylko przykładowa klasa pokazująca dokładnie w czym problem. Ale jak chcesz żeby było jaśniej mogę tu wkleić mającą prawie 300 linijek kodu klasę, podczas której pisania napotkałem ten problem ;]

Metoda equals() ma być dokładnie taka gdyż, jak już napisałem, chcę by równe były tylko i wyłącznie te same obiekty ;]

...może przydałby się mały kurs czytania ze zrozumieniem :)

0
 public int compareTo(ComparableObject comparableObject){
 if (this ==  comparableObject) return 0;
 return 1;
 }

Ale po co to?
Przecież tego i tak nie możesz wykorzystać np. do sortowania.

0

Oj... troszkę zamieszałeś. Chodzi oto żeby

  • metoda equals() działała jak operator ==
  • metoda compareTo działała jak operator == co w tym przypadku znaczy zwracała 0 dla obiektów równych w sensie ==
public class ComparableObj implements Comparable<ComparableObj> {

	public int compareTo(ComparableObj arg0) {
		if (this.equals(arg0))
			return 0;
		else
			return 1;
	}

	@Override
	public boolean equals(Object obj) {
		return this == obj;
	}

	/*
	 * a to tardycyjnie piszemy jawnie jak mamy własne equals
	 */
	@Override
	public int hashCode() {
		return super.hashCode();
	}
}
0

no nie do końca ;]
...jak widać w przykładowej klasie, tam jest jeszcze jakieś pole ComparableObject.value (w rzeczywistości jest to troszkę bardziej skomplikowane, ale powiedzmy, że dało by się obliczyć takie value dla interesującej mnie naprawdę klasy więc nie będę pisał jak to liczę tylko zakładamy, że mam je po prostu podane), i jeśli wartości tego pola są różne dla 2 instancji tej klasy to mają one zostać posortowane właśnie według jego wartości.
Poza tym poprawnie zaimplementowana metoda Comparable.compareTo(...) zgodnie z dokumentacją sun'a musi spełniać następujące aksjomaty:

sgn(x.compareTo(y))==-sgn(y.compareTo(x))//#1
(!(x.compareTo(y)>0 && y.compareTo(z)>0))||x.compareTo(z)>0//#2
x.compareTo(y)!=0||sgn(x.compareTo(z))==sgn(y.compareTo(z))//#3

no i to co ja bym chciał:

(x.compareTo(y)==0)==(x==y)//#4

Jest oczywiste, ze zaproponowana przez was metoda nie spełnia #1 ;]

0

To zagadkowe value jest typu int ?
Jeśli value jest różne dla różnych obiektów, to można tak:

public int compareTo(ComparableObject co)
{
     if(this==co)
     {
           return 0;
     }
     else
     {
           return this.value-co.value;
     }
}

Jeśli value może być takie samo dla różnych obiektów, to trzeba trochę skomplikować.
Jedna z możliwości: Dodajemy w klasie ComparableObject nowe pole powiedzmy id. Korzystając
z jakiegoś pola static piszemy konstruktory tab by wartość pola id była niepowtarzalna.

public int compareTo(ComparableObject co)
{
     if(this==co)
     {
           return 0;
     }
     if(this.value!=co.value)
     {
           return this.value-co.value;
     }
     return this.id-co.id;
}
0

ComparableObject.value oczywiście może być nieróżne dla różnych instancji. Gdyby było różne nie byłoby problemu ;]

Prawdę mówiąc myślałem nad rozwiązaniem z tym ID już troszkę wcześniej. Ma ono jedną małą wadę. Otóż jeśli klasa implementowałaby również interface Serializable mogłyby się spotkać dwie różne jej instancje które miałyby te same wartości ID oraz value.

Co do typu ComparableObject.value - jest on raczej mało istotny. Ważne jest, że wyznacza on kolejność, lecz zdarzają się instancje których nie można względem niego posortować (tzn a.value==b.value, lecz a!=b)

Gdybym pisał w C to użyłbym adresu obiektu w pamięci i byłoby po kłopocie [glowa]

0

O adresie też myślałem, jeśli twoja klasa (ani żadna klasa leżąca między Object a twoją klasą) nie nadpisuje metody toString(), to metoda toString() zwróci coś takiego: MojaKlasa@acfe55. Możesz wyciąć ostatnie znaki i zamienić ja na liczbę.

0

Z tego co mówił prowadzący na wykładzie z Programowania Obiektowego to tam w Object.toString() jest tylko hash z adresu, a nie adres. Więc może się zdarzyć, że dwie różne instancje będą miały tam to samo ;]
Więc to nadal nie rozwiązuje problemu [glowa]

0

Sprawdziłem, prowadzący ma rację. Pozostaje zatem własne pole, powiedzmy identyfikator.
Konstuktory pilnują jednoznaczności w nowo tworzonych instancjach. Dla obiektów wczytanych z pliku (po serializacji) lub ściągniętych ze świata trzeba zawartość pola aktualizować.

0

Czyli np:

public class ComparableObject implements Comparable<ComparableObject>,Serializable{
 private static final long serialVersionUID=1827558935228038813L;
 private static long nextID=0;
 protected final long ID;
 public final int value;
 public ComparableObject(final int value){
  this.value=value;
  this.ID=ComparableObject.nextID++;
  //...
 }
 public int compareTo(ComparableObject comparableObject){
  if(this==comparableObject)return 0;
  if(comparableObject.value!=this.value)return comparableObject.value-this.value;
  return comparableObject.ID>this.ID?1:(comparableObject.ID==this.ID?0:-1);
 }
 public boolean equals(Object object){
  return this==object;
 }
 public int hashCode(){
  return (new Random(this.ID)).nextInt();
 }
 //...
}

No ale jak teraz wykombinować by działała serializacja i deserializacja [glowa]

0

Nie rozumiem w czym problem, pole ID ma jest czysto pomocnicze. Służy do tego by arbitralnie odróżniać instancje z tym samym value. Poprzedzasz zatem deklarację pola słowem kluczowym transient (żeby się nie "serializowało"), po deserializacji robisz coś takiego:
OdczytanyComaparableObject.ID=ComparableObject.nextID++;

0
public class ComparableObject implements Comparable<ComparableObject>,Serializable{
 private static final long serialVersionUID=1827558935228038813L;
 private static long nextID=0;
 protected transient final long ID;
 public final int value;
 public ComparableObject(final int value){
  this.value=value;
  this.ID=ComparableObject.nextID++;
  //...
 }
 public int compareTo(ComparableObject comparableObject){
  if(this==comparableObject)return 0;
  if(comparableObject.value!=this.value)return comparableObject.value-this.value;
  return comparableObject.ID>this.ID?1:(comparableObject.ID==this.ID?0:-1);
 }
 public boolean equals(Object object){
  return this==object;
 }
 public int hashCode(){
  return (new Random(this.ID)).nextInt();
 }
 //...
}

Ale gdzie wstawić to OdczytanyComaparableObject.ID=ComparableObject.nextID++; ;]

0

Jak czytasz obiekt (np z pliku). to masz taki mniej więcej fragment:

FileInputStream f=new FileInputStream("Abrakadabra");
input=new ObjectInputStream(f);
ComparableObject co=(ComparableObject) input.readObject();
// TUTAJ
co.ID=...;
0

No niezbyt ;]
Nie mogę założyć, że odczytywać będę zawsze tylko i wyłącznie jedną instancję danej klasy...
Wręcz prawie na pewno będzie ona tylko polem większej struktury.
Poza tym każda klasa powinna sama zarządzać swoimi wartościami (encapsulacja). Czyli nie mogę wymagać od użytkownika by za każdym razem po odczytaniu aktualizował ID.

Pomijam już fakt, że powyższy kod się nie skompiluje :P - pole ComparableObject.ID jest private final ;]

0

Przecież kod był przykładowy. Jak się nie kompiluje, to (napisz settera albo zmień pole na public) i usuń modyfikator final.
Argument o enkapsulacji jest poważniejszy. Trzeba w innym miejscu nadawać wartość pola ID obiektom deserializowanym

 private static long nextID=1;
 protected transient long ID;
....
 public ComparableObject(final int value)
{
    this.value=value;
    this.ID=ComparableObject.nextID++;
  //...
 }

Wtedy każdy konstruowany obiekt ma ID>=1, natomiast w deserializowanych ID=0.
Wewnątrz metody compareTo() można umieścić uwaga kod jest przykładowy może się nie skompilować cos takiego:

if(this.ID==0)  
{
    this.nadajID();
}
0

Trochę jak widze do przodu poszła dyskusja. Ok, na początek pytanie czy jeżeli obiekty mają takie samo value i są to różne obiekty to czy naprawdę ważna jest ich kolejność przy sortowaniu? Jeżeli nie, a jednocześnie nie chcesz, aby były oznaczane jako równe przez compareTo, to samo compareTo może zawierać fragment:

public int compareTo(ComparableObject co){
//...

   if(this != co && this.value.equals(co.value)){
      if(Math.random()>0.5)
         return 1;
      else 
         return -1;
   }
}

Metoda z ID będzie się sprawdzać o ile obiekty po wczytaniu będą w jakiś magiczny sposób otrzymywały identyfikatory w takiej samej kolejności jak przed zapisaniem (serializacją). W przeciwnym wypadku powstanie sytuacja gdzie serializacja i zapis obiektów będą prowadziły do zamiany ich kolejności. Spowodowane jest to przez transistent pola ID. W takiej sytuacji wracamy do sytuacji, w której kolejność nie jest istotna. Metoda przedstawiona powyżej może okazać się zła jeżeli zależy nam na tym by dane były w trakcie działania aplikacji sortowane zawsze w tej samej kolejności, ale tylko w ramach danego uruchomienia programu. Można więc nadawać ID w taki sposób:

this.ID = Math.random() * (new Date()).getTime();

każdy identyfikator jest unikatowy, a jednocześnie nie powtarza się. Wywołanie jest w konstruktorze.

Jeżeli chcemy, aby zawsze ID było takie samo to należy serializować pole ID. Pytanie jeszcze jak dobrać się do ID obiektu w sposób bezpieczny. Tworzenie publicznego settera nie ma sensu ponieważ w taki sposób każdy fragment programu będzie mógł namieszać w ID. Należy zatem użyć modyfikatora domyślnego w ten sposób do metody uzyskają dostę tylko obiekty z klas znajdujących się w jednym pakiecie z naszą klasą. Można też zastosować refleksję w celu wybrania prywatnego pola, ale jest to moim zdaniem armata na muchę.

0

@Koziołek, autor problemu wymaga by metoda compareTo() była zgodna z zaleceniami firmy Sun (które na mój gust są bardzo rozsądne). W szczególności winna byc przechodnia x<=y oraz y<=z implikuje, że x<=z.
Metoda z losowanym ID tego nie gwarantuje.
Swoją drogą problem jest ciekawy, ale nie mogę nijak wymyslić po czorta komuś metoda compareTo() zgodna z wymaganiami Seba.

0

@bogdans, metoda losowa jest rzeczywiście wtedy zła, ale tylko w przypadku BEZ ID. Jeżeli masz ID to jest ono nadawane w momencie tworzenia obiektu i później już nie może być zmienione.

Jak rozumiem problem na obecnym etapie nie polega na samym porównaniu ID, ale na takim ich generowaniu by dwa różne obiekty miały zawsze różne ID. Rozwiązaniem jest takie wybranie funkcji generującej ID, aby zminimalizować szansę powtórzenia się wyniku.

0

Tez tak rozumiem problem. Dobrym generatorem niepowtarzalnego (prawie) ID (typu long) jest taki:
(aktualny_czas<<16)+liczba_losowa_z_zakresu_0_(2^16)-1

0

Wiesz... jeśli chciałbym mieć prawie nie powtarzalne ID to przecież zamiast mniej lub bardziej sprytnego losowania mógłbym użyć klasy java.util.UUID ;]

Chodzi o to, by w danym uruchomieniu aplikacji dało się uszeregować wszystkie istniejące w pamięci instancje danej klasy, mimo identycznej treści. Po serializacji i deserializacji kolejność dla obiektów o identycznej treści może się zmienić.
A to przykład gdzie coś takiego może się przydać:
Mam np pakiet do budowy sieci:

package sebo.net;
 public class Node implements SortedSet<Edge>, Comparable<Node>{
  private final SortedSet<Edge> edges=new TreeSet<Edge>();
  public final transient long ID;
  private static long nextID=1L;
  public Node(){
   this.ID=Node.nextID++;
  }
  public int compareTo(Node node){
   if(this==node)return 0;
   final int r=this.edges.size()-node.edges.size();
   if(r!=0)return r;
   if(this.ID<node.ID)return 1;
   return -1;
  }
  public boolean equals(Object object){
   return this==object;
  }
  public int hashCode(){
   return super.hashCode();
  }
  //...
 }
 public class Edge implements Comparable<Edge>,Serializable{
  public final Node from;
  public final Node to;
  public final int speed;
  public final transient long ID;
  private static long nextID=1L;
  public Edge(final Node from,final Node to){
   this.from=from;
   this.to=to;
   this.ID=Edge.nextID++;
  }
  public boolean equals(Object object){
   return this==object;
  }
  public int hashCode(){
   return super.hashCode();
  }
  public int compareTo(Edge edge){
   if(this==node)return 0;
   if(this.speed!=edge.speed)return this.speed-edge.speed;
   if(this.ID<edge.ID)return 1;
   return -1;
  }
  //...
 }
 public class Network implements SortedSet<Node>,Serializable{
  //...
 }

No i w tym przykładzie oczywiście może się zdarzyć, że mamy dwie krawędzie o takiej samej szybkości pomiędzy tymi samymi wierzchołkami sieci, ale jednak mimo wszystko chcielibyśmy przechowywać w pamięci je obydwie. Tak samo może się zdarzyć, że mamy dwa takie same wierzchołki, lecz nie są to te same wierzchołki więc chcielibyśmy pamiętać obydwa. Musi być możliwość jakiegoś uszeregowania tych obiektów, ale kolejność ta (dla takich samych obiektów) nie jest dla nas istotna ;]

0

Jesli uporządkowanie może sie zmienić po serializacji i deserializacji, to odpowiedź jest w poście z 17.12 godz. 8.04

0

Nie rozumiem argumentów uzasadniających potrzebę takiej "dziwnej" metody compareTo(), jeśli kolejność tych krawędzi jest nieistotna, to dlaczego compareTo() nie może zwrócić zera ?

0
bogdans napisał(a)

Nie rozumiem argumentów uzasadniających potrzebę takiej "dziwnej" metody compareTo(), jeśli kolejność tych krawędzi jest nieistotna, to dlaczego compareTo() nie może zwrócić zera ?

To wrzuć sobie do TreeSet dwa obiekty dla których compareTo() zwraca 0 ;]

0

@Sebo, niezły argument. Jest jeszcze jedno podejście. Możesz zarządzać tworzeniem obiektów nie bezpośrednio, ale poprzez fabrykę. W ten sposób metoda generowania ID będzie niezależna od klasy obiektu.

0

Ale właśnie o to chodzi, żeby nie robić dodatkowych fabryk gdyż one będą tylko przeszkadzały podczas serializacji ;]

0

Dlaczego złe jest takie rozwiązanie (trzeba dodac synchronizacje) ?

public class ComparableObject implements Comparable<ComparableObject>,Serializable
{
   ...
   private static long nextID=1;
   protected transient long ID;
   public final int value;
   public ComparableObject(final int value)
   {
      this.value=value;
      this.ID=ComparableObject.nextID++;
      //...
    }
    public int compareTo(ComparableObject co)
   {
       if(this==co) return 0;
       if(co.value!=this.value)return co.value-this.value;
       if(this.ID==0)
       {
            this.ID=ComparableObject.nextID++;
       } 
       if(co.ID==0)
       {
            co.ID=ComparableObject.nextID++;
       } 
       return co.ID-this.ID;
   }
 //...
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1