<

1. Java: nowe elementy środowiska


Zapoznamy się tu z nowymi elementami standardowej edycji Javy w wersji 1.5 (J2SE wersja 5). Więcej informacji na temat takich elementów Javy 1.5 jak typy sparametryzowane i adnotacje można znaleźć w następnych dwóch wykładach. Ciekawy pakiet java.util.concurrent omawiany jest w wykładach 8 i 9. W wykładach końcowych (11 i 14) będzie mowa o nowościach edycji "enterprise" -  J2EE w wersji 5.


1. Przegląd

W Javie w wersji 1.5 wprowadzono zmiany zarówno w składni i semantyce samego języka, jak i w zestawie pakietów. Do najważniejszych zmian należą

 Język
Pakiety
Celem zmian było przede wszystkim ułatwienie tworzenia i wdrażania programów. I w dużej mierze efekt ten osiągnięto.  Skalę ułatwień, wprowadzanych przez poszczególne elementy języka, ilustracyjnie pokazuje poniższy rysunek (na osi OY pokazano pracochłonność budowy aplikacji).

r
Źródło: Calvin Austin. Five Reasons to Move to the J2SE 5 Platform, Sun 2005

Warto dodać, że w wersji J2SE 6 (Mustang) już znajdują się nowe ekscytujące dodatki, jak:
Nie omawiamy tu jeszcze wersji 6 Javy, ponieważ wciąż jest to beta.


2. Autoboxing


Autoboxing to automatyczne przekształcanie pomiędzy typami prostymi a typami obiektów klas, opakowujących dany typ prosty.


Boxing

Automatyczne przekształcenie z typu prostego do obiektowego.
Typ prosty (i)  -> typ obiektowy (r), tak, że : r.value() == i

boolean -> Boolean
byte  -> Byte
char -> Character
short -> Short
int -> Integer
long -> Long
float -> Float
double -> Double

Unboxing

Automatyczne przekształcenie z typu obiektowego do prostego.
Typ obiektowy (r)  -> typ prosty (i), tak, że i == r.value()

Boolean   ->   boolean
Byte      ->   byte
Character ->   char
Short     ->   short
Integer   ->   int
Long      ->   long
Float     ->   float
Double    ->   double

Kiedy zachodzą te automatyczne konwersje?

Przykład.

Poniższy program:
public class AutoBoxing {

  private int intField;

  public AutoBoxing() {
    // Konwersje przy przypisaniu
    int x = 1;
    Integer a = x;
    int y = a;
    System.out.println("Konwersje przy przypisaniu: " + a + " " + y);

    // Konwersje przy wywołaniu metod
    methodInvocationConversion1(x);
    methodInvocationConversion2(a);

    // Konwersje przy zwrocie wyników  
    y = getIntFromInteger();
    a = getIntegerFromInt();
    System.out.println("Konwersje przy zwrocie wyniku: " + a + " " + y);
  }

  public AutoBoxing(Integer i) {
    intField = i;
    System.out.println("W konstruktorze bezparametrowym: " + intField);
  }
  
  void methodInvocationConversion1(Integer p) {
    System.out.println("Parametr Integer, argument int " + p);
  }

  void methodInvocationConversion2(int p) {
    System.out.println("Parametr int, argument Integer " + p);
  }

  int getIntFromInteger() {
    return new Integer(2);
  }

  Integer getIntegerFromInt() {
    return 2;
  }

  public static void main(String[] args) {
     new AutoBoxing();
     // Zauważmy, że konstruktor możemy wołać z arg. int lub Integer
     new AutoBoxing(3);
     new AutoBoxing(new Integer(3));
  }


}
da w wyniku:

Konwersje przy przypisaniu: 1 1
Parametr Integer, argument int 1
Parametr int, argument Integer 1
Konwersje przy zwrocie wyniku: 2 2
W konstruktorze bezparametrowym: 3
W konstruktorze bezparametrowym: 3


Zobacz demo tego programu (uwaga - proszę zapoznać się z informacją o uruchamianiu programów demonstracyjnych podaną na stronie początkowej kursu).




3. Wstępne pojęcie o szablonach (generics)

Szablony (generics) są językowym środkiem umożliwiającym definiowanie sparametryzowanych typów (klas) i metod. W takich definicjach używa się parametrów na oznaczenie typów danych: pól klasy, parametrów metod i konstruktorów, wyników metod.
Przy tworzeniu obiektów klas sparametryzowanych i przy wywołaniu metod formalne parametry są zastępowane faktycznymi.


Podstawowym celem wprowadzenia szablonów (generics) do języka było zapewnienie możliwości parametryzowania kolekcji, dzięki czemu:
Przykładowo, interfejs List oraz klasa ArrayList są sparametryzowane:
interface List<E>  {   // E jest parametrem oznaczającym typ danych

// ....

}    


class ArrayList<E> ... {

...

}

i możemy teraz tworzyć listy danych konkretnych typów:
List<String> lista1 = new ArrayList<String>();

List<Integee> lista2 = new ArrayList<Integer>();


Również iteratory są sparametryzowane, tak że next() zwraca wartość typu określonego przez parametr.

Składnia: parametr oznaczający typ podajemy w nawiasach < >


Zalety generics:


        lista1.add("Jan"); // Ok
        lista1.add(new Date()); // błąd w kompilacji; przedtem niewykrywalny.
        for (Iterator<String> it = lista1.iterator(); it.hasNext(); ) {
             String name = it.next();
             // ... tu coś robimy z name
        }

dotąd musieliśmy pisać: String name = (String) it.next();

        lista2.add(5);
        int n = lista2.get(1);


Zobacz multimedialną prezentację zalet stosowania generics w kolekcjach r




Jeszcze dobitniej o zaletach sparametryzowanych kolekcji przekonuje przykład zliczania wystąpień słów.
import java.util.*;
import javax.swing.*;


public class CountWords { 
  
  private String testString = "ala ma kota ala i ma psa ala";
  
  private  Map<String,Integer> map = new HashMap<String,Integer>();
  
  public CountWords() {
    String[] words = testString.split(" +");
    
    // Zliczanie słów
    for (int i = 0; i < words.length; i++) {
      int n = 0;
      try {
         n = map.get(words[i]);
      } catch(NullPointerException exc) {
          System.out.println("Pierwsze zliczenie słowa: " + words[i]);
      }
      map.put(words[i], ++n);
    }
    
    // Wyniki
    StringBuilder wynik = new StringBuilder("Wyniki:\n");
    for (String word : map.keySet()) {
      wynik.append(word).append(' ').append(map.get(word)).append('\n');
    }
    
    JOptionPane.showMessageDialog(null, wynik);
  }

  public static void main(String[] args) {
     new CountWords(); 
  }

}
Tu dzięki zastosowaniu sparametryzowanej mapy oraz autoboxingu kod jest niezwykle prosty, czytelny, krótki.

Zobacz demo programu  



Zastosowana w drugiej części programu rozszerzona instrukcja for również jest tu bardzo użyteczna. Omówimy ją teraz.



4. Rozszerzona instrukcja for

Ta instrukcja znana jest rownież jako instrukcja "for-each". Służy do przebiegania po zestawach danych (kolekcjach, tablicach, innych). Dla każdego elementu takiego zestawu wykonywane są instrukcje zawarte w ciele rozszerzonego for.



Składnia

        for ( Typ id : expr )
            stmt

gdzie:


Interfejs Iterable, który wygląda następująco:
public interface Iterable<E> {
    Iterator<E> iterator();
}
implementują klasy kolekcyjne, możemy też sami go implementować we własnych klasach czyniąc ich obiekty "iterowalnymi" za pomocą "for-each".


Działanie rozszerzonego for

Dla tablic równoważne:

    Typ[] $a = ....;
    for (int $i = 0; $i < $a.length; $i++) {
        Typ id = $a[ $i ] ;
        stmt
    }

Dla klas implementujących interfejs Iterable (w tym dla wszystkich kolekcji) równoważne:

 Iterable classObject = ....;
....
 for ( Iterator<Typ> $i = classObject.iterator(); $i.hasNext(); ) {
        Typ id = $i.next();
        stmt
 }


W poniższym przykładzie pokazano:

import java.util.*;

class FromTo implements Iterable<Calendar> {

   private Calendar from = Calendar.getInstance(),
                    to = Calendar.getInstance();


   public FromTo(Calendar f, Calendar t) {
      from.setTime(f.getTime());
      to.setTime(t.getTime());
   }

   public CalendarIterator iterator() {
     return new CalendarIterator();
   }

   private class CalendarIterator implements Iterator {
     Calendar current = Calendar.getInstance(),
              next = Calendar.getInstance();

     CalendarIterator() {
       current.setTime(from.getTime());
       current.add(Calendar.DATE, -1);
     }

     public boolean hasNext() {
       next.setTime(current.getTime());
       next.add(Calendar.DATE, 1);
       return next.compareTo(to) <= 0;
     }

     public Calendar next() {
       if (!hasNext()) throw new NoSuchElementException();
       current.add(Calendar.DATE, 1);
       return current;
     }

     public void remove() {}
   }
}


public class ForEach {


  public ForEach() {
    String[] names  = { "Ala", "Asia", "Janek" };
    List<String> list = Arrays.asList(names);

    for (String name : names)
      System.out.println(name);

    for (String name : list)   // to jest String, ale bez generics byłby Object
      System.out.println(name + " - długość " + name.length());

    Calendar from = Calendar.getInstance();
    Calendar to = Calendar.getInstance();
    to.add(Calendar.DAY_OF_MONTH, 5);
    
    FromTo timeInterval = new FromTo(from, to);

    for (Calendar day : timeInterval) {
      System.out.println(day.getTime());
    }

  }

  public static void main(String[] args) {
     new ForEach();
  }


}
Warto  zwrócić uwagę na to, że dla zastosowania for-each bez rzutowania ważne jest by klasa   implementowała interfejs Iterable z konkretnym parametrem typu - tu Iterable<Calendar>.

Program, uruchomiony 1 lipca 2005, wyprowadzi następujące wyniki:

Ala
Asia
Janek
Ala - długość 3
Asia - długość 4
Janek - długość 5
Sat Jul 01 00:02:58 CEST 2006
Sun Jul 02 00:02:58 CEST 2006
Mon Jul 03 00:02:58 CEST 2006
Tue Jul 04 00:02:58 CEST 2006
Wed Jul 05 00:02:58 CEST 2006
Thu Jul 06 00:02:58 CEST 2006


Zobacz demo działania programu  




5. Statyczne importy

Statyczny import umożliwia odwołania do składowych statycznych bez kwalifikowania ich nazwą klasy.




Import składowej statycznej:

import static TypeName.Identifier;


Import wszystkich składowych statycznych z klasy lub interfejsu:

import static TypeName.Identifier.*;



Przyklady


Przedtem (bez statycznych importów):
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class StaticImports extends JFrame {

  public StaticImports() {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    Color[] colors = { Color.RED, Color.BLUE, Color.GREEN };
    getContentPane().setLayout(new FlowLayout());
    for (int i=0; i<colors.length; i++) {
      JButton b = new JButton("Przycisk");
      b.setForeground(colors[i]);
      b.addActionListener( new ActionListener() {
           public void actionPerformed(ActionEvent e) {
             System.out.println("Akcja");
           }
        });
      getContentPane().add(b);
    }
    pack();
    show();

    try { Thread.sleep(2000); } catch(Exception exc) {}

    SwingUtilities.invokeLater( new Runnable() {
      public void run() {
        getContentPane().setBackground(Color.YELLOW);
      }
    });

    double d = Math.max(2.1, 2.3);
    System.out.println(d);
    d = Math.pow(2,7);
    System.out.println(d);


  }

  public static void main(String[] args) {
     new StaticImports();
  }


}

Teraz (ze statycznymi importami):
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import static java.awt.Color.*;
import static javax.swing.JFrame.*;
import static java.lang.System.out;
import static java.lang.Thread.sleep;
import static javax.swing.SwingUtilities.*;
import static java.lang.Math.*;


public class StaticImportsWork extends JFrame {

 public StaticImportsWork() {
   setDefaultCloseOperation(EXIT_ON_CLOSE); // łatwiej!
   Color[] colors = { RED, BLUE, GREEN }; // łatwiej!
   setLayout(new FlowLayout()); // bez contentPane !

   for (Color c : colors) { // nowe for
     JButton b = new JButton("Przycisk");
     b.setForeground(c);
     b.addActionListener( new ActionListener() {
       public void actionPerformed(ActionEvent e) {
         out.println("Akcja"); // łatwiej
       }
     });
     add(b); // bez contentPane !
   }
   pack();
   show();

   try { sleep(500); } catch(Exception exc) {} // łatwiej

   invokeLater( new Runnable() { // łatwiej
     public void run() {
       getContentPane().setBackground(YELLOW); // łatwiej
     }
   });

   // Również szczególnie użyteczne przy operacjach matematycznych
   double d = max(2.1, 2.3);
   out.println(d);
   d = pow(2,7);
   out.println(d);

 }

 public static void main(String[] args) {
   new StaticImportsWork();
 }

}


Zobacz demo programu.






6. Zmienna liczba argumentów metod

W nowej wersji Javy łatwiej (i bezpośrednio) deklarujemy i wywołujemy metody ze zmienną liczbą/typami argumentów.


Deklaracja metody ze zmienną liczbą argumentów

Zmienną liczbę argumentów oznacza się za pomocą wielokropka (...):

        typ ... parms


 gdzie typ - typ argumentów,

           parms - oznacza argumenty, których może być  0, jeden lub wiele.

Taki zapis powinien być ostatni na liście argumentów (parametrów) metody.

Np.
typWyniku nazwaMetody(typ1 parm1, typ2 parm2,  typ ... parms)
typWyniku nazwaMetody( typ ... parms)




Dostęp w metodzie do przekazanych argumentów:


        for ( typ v : parms) ...


        parms[0], parms[1] ...


        parms.length



Jak łatwo można się domyślić kompilator ukrywa przed nami zwykłą tablicę parametrów (nazwaMetody(typ[] parms).


Jak można wywoływac metodę ze zmienną liczbą argumentów

Podając argumenty  - różną liczbę przy różnych wołaniach:

metoda(a);
metoda(a, b, c);


A także podając tablicę:

String[] tab = ....
metoda(tab);


Przykład.
import java.awt.*;
import java.awt.event.*;
import java.beans.*;

import javax.swing.*;
import static java.awt.Color.*;
import static java.lang.Thread.sleep;
import static java.lang.System.out;

public class Varargs extends JFrame {

  JTextArea report = new JTextArea(25, 40);
  
  public Varargs() {
    setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    
    report.setBorder(BorderFactory.createTitledBorder("Raport"));
    add(new JScrollPane(report));
    
    JPanel p = new JPanel();

    final JLabel lab = new JLabel("Label");
    lab.setOpaque(true);
    p.add(lab);
    
    final JButton b1 = new JButton("Kolory 1");
    b1.addActionListener( new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        setColor(RED, b1);
      }
    });
    p.add(b1);
        
    final JButton b2 = new JButton("Kolory 2");
    b2.addActionListener( new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        setColor(GREEN, b2, lab);
      }
    });
    p.add(b2);

    final JButton b3 = new JButton("Kolory 3");
    b3.addActionListener( new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        setColor(YELLOW, b1, b2, b3, lab);
      }
    });
    p.add(b3);
    add(p, "North"); 
    
    pack();
    setVisible(true);
  }


  private void setColor(final Color color, final Component ... comps) {
    report.append("Liczba przekazanych komponentów: " + comps.length +'\n');
    report.append("Szerokość pierwszego komponentu: " +
                        comps[0].getWidth() + '\n');
    report.append("Szerokość ostatniego komponentu: " +
                        comps[comps.length-1].getWidth()  + '\n');
    
    for (Component c : comps) c.setBackground(color);
    met(1, 3, 5);
  }

  private void met(int ... p) {
    for(int v : p) report.append(v + "  ");
  }

  public static void main(String[] args) {
     new Varargs();
  }
  

}


Zobacz demo programu  

Wiele metod w Javie zostało zmienionych, tak, że są deklarowane jako metody ze zmienną liczbą argumentów. Dotyczy to np. formatowania komunikatów (klasa MessageFormat), a także pakietu refleksji. Dzięki temu użycie tych metod jest teraz dużo łatwiejsze.

Przykład. Program daje użytkownikowi wybór co do następstw przyciśnięcia jakiego przycisku. Co więcej, wyborów takich użytkownik może dokonywać w fazie wykonania programu.
Zestaw możliwych akcji (na przyciskach) będzie zawarty w klasie ActionsSet np.
public class ActionSet {

 public void dodaj() { show("Dodaj"); }
 public void usuń() { show("Usuń"); }
 public void zastąp() { show("Zastąp"); }
 public void szukaj() { show("Szukaj"); }
 public void otwórz() { show("Otwórz"); }

 private void show(String string) {
   JOptionPane.showMessageDialog(null, string);
 }

}
Opcje dla użytkownika będą przedstawione w menu kontekstowym, otwieranym na przycisku. Z tego menu może on wybrać (wielokrotnie i różnie w trakcie działania programu) co konkretnie ma się stać, jeśli przyciśnie ten przycisk.

Poniższy wydruk pokazuje konstrukcję programu. Warto zwrócić uwagę nie tylko na zmienną liczbę argumentow metod refleksji, ale również na ułatwienia w ustalaniu i otwieraniu menu kontekstowego. Teraz wystarczy wywołanie prostej metody setComponentPopupMenu(...) bez żmudnego przyłączania słuchaczy myszki i "ręcznego" otwierania menu w reakcji na prawy klawisz.
import java.lang.reflect.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ReflectionTest extends JFrame implements ActionListener {

  Method currAction = null; // bieżąca metoda obsługi
  Class actionClass = null; // klasa obsługi
  Object actionObject = null; // obiekt obsługi
  JPopupMenu popUp = null; // menu kontekstowe z wyborem obsługi
  JButton b;

  public ReflectionTest() {
    super("Test refleksji");
    try {
      actionClass = Class.forName("demo6.ActionSet");
      actionObject = actionClass.newInstance();
    } catch (Exception exc) {
      throw new RuntimeException("Wadliwa klasa obsługi");
    }

    popUp = new JPopupMenu();
    createMenuItems();

    b = new JButton("Użyj prawego klawisza myszki, by ustalić akcję");
    b.setFont(new Font("Dialog", Font.BOLD, 24));
    b.addActionListener(this);
    b.setComponentPopupMenu(popUp); // ułatwienie z popup!!!

    setLayout(new FlowLayout());
    add(b);
    setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    pack();
    setLocationRelativeTo(null);
    setVisible(true);
  }

  void createMenuItems() {
    Method mets[] = null;
    try {
      mets = actionClass.getMethods();
    } catch (Exception exc) {
      throw new RuntimeException("Niedostępna info o metodach klasy obsługi");
    }

    for (Method m : mets) {
      if (m.getDeclaringClass() == actionClass) {
        String name = m.getName();
        JMenuItem mi = new JMenuItem(name);
        mi.addActionListener(this);
        popUp.add(mi);
      }
    }
  }

  void setCurrentAction(String action) {
    try {
      currAction = actionClass.getMethod(action); // zm. liczba arg.!!!
      b.setText("Teraz akcją jest: " + currAction.getName() + " - kliknij!");
    } catch (Exception exc) {
      throw new RuntimeException("Nieznana metoda obsługi");
    }
  }

  public void actionPerformed(ActionEvent e) {
    Object src = e.getSource();
    if (src instanceof JMenuItem)
      setCurrentAction(((JMenuItem) src).getText());
    else {
      try {
        currAction.invoke(actionObject); // zmienna liczba arg. !!!
      } catch (Exception exc) {
        JOptionPane.showMessageDialog(null, "Akcja na przycisku nieustalona!");
      }
    }
  }

  public static void main(String args[]) {
    new ReflectionTest();
  }

}



Zobacz demo programu  



7. Enumeracje


Enumeracja, czyli wyliczenie, to zestaw stałych, zwykle krótki, takich których wartości są znane w fazie kompilacji.


W Javie, w wersji 1.5, pojawiło się nowe słowo kluczowe enum.
Wprowadzany przez nie mechanizm definiowania enumeracji ma zalety wobec rozwiązań w których wyliczenia są liczbami całkowitymi:
a przy tym zapewniona jest efektywność jak w działaniu na liczbach całkowitych

Przykład:

Uwaga: enum to tak naprawdę  klasa. Poniższe definicje - ze względu na słówko public - winny być umieszczone w trzech różnych plikach.
public enum DayOfWeek {
 PONIEDZIAŁEK, WTOREK, SRODA,
 CZWARTEK, PIĄTEK, SOBOTA, NIEDZIELA
}

public enum Weather {
 POCHMURNO, SŁONECZNIE, DESZCZOWO
}

public enum Temperature {
 ZIMNO, CIEPŁO
}
class DayWeather {

 private DayOfWeek day;
 private Weather weather;
 private Temperature temp;

 public DayWeather( DayOfWeek d, Weather w, Temperature t) {
   day = d;
   weather = w;
   temp = t;
 }

 public String toString() {
   return "W " + day + " było " + weather + " i " + temp;
 }

 public Weather getWeather() { return weather; }

}


public class Enums {

 // Przestrzenie nazw są rozdzielone
 public enum Szukaj { CIEPŁO, ZIMNO };

 public Enums() {

 // Łatwy sposób iterowania po elementach enumeracji
 for (DayOfWeek d : DayOfWeek.values()) System.out.println(d);

 Szukaj stan = Szukaj.CIEPŁO;

 // Nie można się pomylić - błąd w kompilacji:
 //DayWeather dw = new DayWeather(DayOfWeek.CZWARTEK, Weather.SŁONECZNIE,
 // stan);

 // można używać w switchu
 // przy czym ze względu na znany typ zmiennej etykiety nie muszą
 // być kwalifikowane nazwą klasy enum 

 switch(stan) {
   case ZIMNO : System.out.println("Jest zimno"); break;
   case CIEPŁO : System.out.println("Jest ciepło"); break;
 }


 DayWeather dw1 = new DayWeather(DayOfWeek.CZWARTEK, Weather.SŁONECZNIE,
 Temperature.CIEPŁO);

 // Naturalny wydruk
 System.out.println(dw1);

 DayWeather dw2 = new DayWeather(DayOfWeek.WTOREK, Weather.DESZCZOWO,
                                 Temperature.CIEPŁO);

 // Elementy enumeracji są obiektami-singletonami
 // możemy zatem używać operatora ==

 if (dw2.getWeather() == Weather.DESZCZOWO) System.out.println("Był deszcz");
 else System.out.println("nie padało");


 }

 public static void main(String[] args) {
   new Enums();
 }


}
Wynik działania programu:


PONIEDZIAŁEK
WTOREK
SRODA
CZWARTEK
PIĄTEK
SOBOTA
NIEDZIELA
Jest ciepło
W CZWARTEK było SŁONECZNIE i CIEPŁO
Był deszcz



Dodatkowe możliwości enum:


Przykłady

Przykładowe programy pokazują  jak można rozszerzyć enumerację, traktując ją niemal jak zwykłą klasę.
Najpierw dodamy konstruktor, który wiąże ze stałymi wyliczeniowymi (gazety) ich ceny.
Uwaga. Obiektów enumeracji nie można tworzyć za pomocą wyrażenia new.
package kiosk;

public enum Gazety {

 Głos(1), Polityka(4.5), Gazeta(2.5);

 Gazety(double p) { price = p; }

 public double getPrice() { return price; }

 private final double price;

}

Następny kod - przy okazji - pokazuję, że przy pisaniu nieco większych programów nalezy korzystać z komentarzy dokumentacyjnych - i jak z nich korzystać.
package kiosk;
import java.util.*;
import static kiosk.Gazety.*; // musi być nazwany pakiet!
import static java.lang.System.out;


public class Kiosk {

 private EnumMap<Gazety, Integer> map =
                                  new EnumMap<Gazety, Integer>(Gazety.class);

 public Kiosk() {
   supply(Głos, 20);
   supply(Polityka, 20);
   supply(Gazeta, 20);

   // co jest w kiosku:
   // Jak łatwo i elegancko!!!
   for (Gazety g : map.keySet())
   out.println(g + " - liczba egzemplarzy " + map.get(g));

   // Sprzedajemy trochę
   double income = 0;
   income += sale(Głos, 2);
   income += sale(Polityka, 10);
   income += sale(Gazeta, 5);
   income += sale(Głos, 2);
   // Teraz w kiosku zostało
   for (Gazety g : map.keySet())
   out.println(g + " - liczba egzemplarzy " + map.get(g));
   // a uzyskany dochód ze sprzedaży
   out.println("Dochód: " + income);
 }

   /**
   * Dostawa q sztuk gazety g
   * @param g - konkretna gazeta
   * @param q - liczba sztuk w dostawie
   */
   public void supply(Gazety g, int q) {
     if (map.containsKey(g)) q += map.get(g);
     map.put(g, q);
   }

   /**
   * sprzedaż q sztuk gazety g
   * @param g - sprzedawana gazeta
   * @param q - liczba sprzedanych sztuk
   * @return wartość transakcji
   */
   public double sale(Gazety g, int q) {
     if (!map.containsKey(g)) return 0;
     int n = map.get(g);
     if (q > n) q = n;
     map.put(g, n-q);
     return q*g.getPrice();
   }

   public static void main(String[] args) {
     Kiosk kiosk = new Kiosk();
   }

}

Wynik działania programu:

Głos - liczba egzemplarzy 20
Polityka - liczba egzemplarzy 20
Gazeta - liczba egzemplarzy 20
Głos - liczba egzemplarzy 16
Polityka - liczba egzemplarzy 10
Gazeta - liczba egzemplarzy 15
Dochód: 61.5


I bardziej zaawansowany przykład, korzystający z możliwości znaczących rozszerzeń funkcjonalności klas enum (w tym - z wiązania ze stałymi wyliczenia przedefiniowanych metod, które będą wywoływane na ich rzecz).

W nowej wersji "gazet" (konstruktor ma dwa parametry - cenę hurtową i detaliczną), z konkretnymi stałymi (gazetami) wiążemy także przedefiniowane metody toString(), tak by wydruk był jeszcze bardziej informacyjny.
package kiosk;

public enum Gazety {

  // W konstruktorze użyjemy dwóch parametrów
  // Ze stałymi wyliczenia zwiążemy przedefiniowane metody toString
  // tak, by wydruk był ładniejszy nieco

  Głos(0.75, 1) {
    public String toString() { return "Tygodnik \"Głos\""; }
  },

  Polityka(4, 4.5) {
    public String toString() { return "Tygodnik \"Polityka\""; }
  },

  Gazeta(2, 2.5) {
    public String toString() { return "\"Gazeta Wyborcza\""; }
  };

  Gazety(double wp, double rp) {
    wholesalePrice = wp;
    retailPrice = rp;
  }

  public double getRetailPrice() { return retailPrice; }
  public double getWholesalePrice() { return wholesalePrice; }

  private final double wholesalePrice;
  private final double retailPrice;

}

Wprowadzamy nową enumerację, opisującą transakcje: dostawa (SUPPLY) i sprzedaż (SALE).
package kiosk;
import java.util.*;

// Dosyć funkcjonalna enumeracja
// opisuje możliwe transakcje (dostawa, sprzedaż gazet)
// a zarazem prowwadzi rejestr magazynu i rejestr sprzedanych gazet

public enum Transaction {

   // Elementy wyliczenia (konkretne operacje)
   // Wiązemy z nimi ciała podklas, w których
   // implementujemy metodę perform,
   // która jest zadeklarowana na końcu jako abstrakcyjna
   // Uwaga: w klasach enum elementy wyliczenia zdaje się winny
   // poprzedzać wszelkie rozszerzenia funkcjonalności

   SUPPLY {
     public void perform(Gazety g, int n) {
       if (volume.containsKey(g)) n += volume.get(g);
       volume.put(g, n);
     }
   },

   SALE {
     public void perform(Gazety g, int n) {
       if (!volume.containsKey(g)) return;
       int k = volume.get(g);
       if (n > k) n = k;
       volume.put(g, k-n);
       sold.put(g, n);
     }
   };

   // Gazety - stan magazynowy
   private static final EnumMap<Gazety, Integer> volume =
                  new  EnumMap<Gazety, Integer>(Gazety.class);
   // Gazety - sprzedane
   private static final EnumMap<Gazety, Integer> sold =
                  new  EnumMap<Gazety, Integer>(Gazety.class);




   public static EnumMap<Gazety, Integer> getVolume() { return volume; }
   public static EnumMap<Gazety, Integer> getSold() { return sold; }


   // Metoda perform musi być zsdeklarowana jako abstrakcyjna
   // ona własnie jest w różny sposób implemetowana w ciałach podklas
   // przypisanych stałym wyliczenia

   public abstract void perform(Gazety g, int n);


}
No i nowa klasa Kiosk, która w pełni korzysta z dobrodziejstw obu enumeracji i w łatwy sposób uzyskuje sporą funkcjonalnośc pod względem obliczeniowym i prezentacyjnym.
package kiosk;
import java.util.*;
import static kiosk.Gazety.*;
import static kiosk.Transaction.*;
import static java.lang.System.out;


public class Kiosk {

  public Kiosk() {

    // Sprowadzamy gazety
    // możemy użyć dalej zdefiniowanej metody trans (może coś jeszcze będzie robić)
    trans(SUPPLY, Głos, 20);
    trans(SUPPLY, Polityka, 20);
    trans(SUPPLY, Gazeta, 20);

    // ale możemy też pisać tak:
    SUPPLY.perform(Gazeta, 10);
    SUPPLY.perform(Polityka, 10);

    // co jest w kiosku i ile wydano na sprowadzenie gazet:
    out.println("Po dostawie w kiosku są następujące gazety:");
    double cost = 0;
    for (Gazety g : getVolume().keySet()) {
      int n = getVolume().get(g);
      cost += g.getWholesalePrice() * n;
      out.println(g + " - liczba egzemplarzy " + n );
    }
    out.println("Wydano: " + cost);

    // Sprzedajemy trochę
    SALE.perform(Polityka, 15);
    SALE.perform(Głos, 10);
    SALE.perform(Gazeta, 20);

    // Co sprzedano i ile uzyskano?
    out.println("Sprzedano gazety: ");
    double income = 0;
    for (Gazety g : getSold().keySet()) {
      int n = getSold().get(g);
      income += g.getRetailPrice() * n;
      out.println(g + " - liczba egzemplarzy " + n );
    }
    out.println("Dochód " + income);
    out.println("Zarobek : " + (income - cost));

    // Inny sposób wyciągania danych
    out.println("Zostały do sprzedaży:");
    for(Gazety g : Gazety.values())
      out.println(g + " - liczba egzemplarzy: " + getVolume().get(g));


  }

  /**
   * Ew. można taką metodę sobie dodac
   *   Transakcja - zakup gazet do kiosku lub ich sprzedaż
   *   @param t - rodzaj transakcji (SUPPLY lub SALE)
   *   @param g - jaka gazeta
   *   @param n - ile sztuk
   */

  public void trans(Transaction t, Gazety g, int n) {
    t.perform(g, n);
  }


  public static void main(String[] args) {
    Kiosk kiosk = new Kiosk();
  }


}
Wynik działania programu:


Po dostawie w kiosku są następujące gazety:
Tygodnik "Głos" - liczba egzemplarzy 20
Tygodnik "Polityka" - liczba egzemplarzy 30
"Gazeta Wyborcza" - liczba egzemplarzy 30
Wydano: 195.0
Sprzedano gazety:
Tygodnik "Głos" - liczba egzemplarzy 10
Tygodnik "Polityka" - liczba egzemplarzy 15
"Gazeta Wyborcza" - liczba egzemplarzy 20
Dochód 127.5
Zarobek : -67.5
Zostały do sprzedaży:
Tygodnik "Głos" - liczba egzemplarzy: 10
Tygodnik "Polityka" - liczba egzemplarzy: 15
"Gazeta Wyborcza" - liczba egzemplarzy: 10



8. Formator 

Klasa java.utill.Formatter zapewnia możliwości formatowania danych.

Tworząc formator (za pomocą wywołania konstruktora) możemy określić:

File, String, OutputStream, obiekty klas implementujących interfejs Appendable, czyli:
 
Uwaga: formatory dla destynacji implementującyh interfejs Closeable (m.in. pliki, strumienie) powinny być po użyciu zamykane lub wymiatane (close() flush()), co powoduje zamknięcie lub wymiecenie buforów tych destynacji.

Formatowanie polega na wywołaniu jednej z dwóch wersji metody format (na rzecz fornatora):

 Formatter format(Locale l, String format, Object... args)
         
 Formatter format(String format, Object... args)

Łańcuch formatu zawiera dowolne ciągi znaków oraz specjalne symbole formatujące (podobne jak w printf w jezyku C). Dalej następują dane do "wstawienia" w łańcuch formatu w miejscu symboli formatu i do sformatowania podług tych symboli (zmienna liczba argumentów). Wszystko działa podobnie jak printf. Dzięki autoboxingowi nie ma problemu z formatowaniem danych typów prostych.

Ciekawe właściwości formatora to m.in.:
Dla uproszczenia dostępne są:

Proszę zapoznać się z dokumentacją klasy java.util.Formatter.





9. Skaner

Klasa java.util.Scanner pozwala na łatwy, elastyczny i o bardzo dużych możliwościach rozbiór informacji zawierającej napisy i dane typów prostych.


Możliwości:


Przykład.

import java.util.*;

class Employee {
 String name;
 double salary;

 Employee(String n, double s) {
   name = n; salary = s;
 }

 public double getSalary() { return salary; }

 public String toString() { return name + " " + salary; }

}

public class Skaner1 {

 String s1 = "1 2 3";
 String s2 = "Jan Kowalski/1200\nA. Grabowski/1500";


 public Skaner1() {
   Scanner scan1 = new Scanner(s1);
   int suma = 0;
   while (scan1.hasNextInt()) suma += scan1.nextInt();
   System.out.println("Suma = " + suma);

   List<Employee> list = new ArrayList<Employee>();
   Scanner scan2 = new Scanner(s2);
   while (scan2.hasNextLine()) {
     Scanner scan3 = new Scanner(scan2.nextLine()).useDelimiter("/");
     String name = scan3.next();
     double salary = scan3.nextDouble();
     list.add(new Employee(name, salary));
   }

   double value = 0;
   for (Employee emp : list) {
     value += emp.getSalary();
     System.out.println(emp);
   }
   System.out.println("Suma zarobków: " + value);
 }

 public static void main(String[] args) {
   Skaner1 skaner1 = new Skaner1();
 }

}

Wynik działania programu:

Suma = 6
Jan Kowalski 1200.0
A. Grabowski 1500.0
Suma zarobków: 2700.0


10. Podsumowanie

Zapoznaliśmy się z nowymi elementami języka Java w wersji 1.5.
Na szczególną uwagę zasługuje tu równoczesne zastosowanie autoboxingu, sparametryzowanych kolekcji oraz instrukcji "for-each", co znacznie upraszcza kodowanie, zmniejsza rozmiar i zwiększa czytelnośc kodu.
Godne podkreślenia jest też zastosowanie typu wyliczeniowego (enum), który w Javie ma niezwykle bogate możliwości.


11. Zadania

Zadanie 1
Napisać aplikację prezentującą operacje na kontach bankowych oraz ich stany.
Każde konto ma właściciela (osobę, przedsiębiorstwo, instytucję finansową, instytucję budżetową, instytucję samorządową).
Konta są następujących rodzajów (cokolwiek to znaczy): złote, srebrne, brązowe, normalne.
Przyjmujemy, że inicjalnie konta mają stan zero.
Mamy do dyspozycji plik wpłat i wypłat (data nr_konta zmiana_konta).
Po jego wczytaniu chcemy mieć z poziomu GUI możliwości:
Dokładniejsza przykładowa specyfikacja:
Inne podejścia (funkcjonalnie równoważne) - mile widziane.

Możliwe rozszerzenia (nieobowiązkowe):

Przy rozwiązaniu tego zadania należy zastosować możliwie najwięcej i możliwie najsensowniej nowe środki Javy 5. W szczególności należy zastosować:




Zadanie 2
Dla sensownie wybranego zestawu wyliczeniowego (enum) przeprowadzić eksperymenty symulacyjne porównujące efektywność działania:
Zbadać i opisać (wyjaśnić) budowę klas EnumSet i EnumMap.