Threads 30 January 2003 1
Threads
Threads are used to achieve parallelism within your program. This is handled completely inside the JVM and there is no extra processes created. If you are working on a single processor compu- ter, there will only be simulated paral- lelism.
Threads 30 January 2003 2
To be a Thread, your class should be a subclass of the Thread class. It contains some important methods:
start, start the thread
stop, stop the thread, deprecated.
sleep, delay for a while run, the thread
suspend, pause, deprecated destroy, cleanup
resume, restart after a suspend, deprecated
interrupt, ask the thread to stop interrupted, check if current thread is interrupted
isInterrupted, check if interrupted isAlive, check if alive
Threads 30 January 2003 3
The procedure to create a thread is
•
declare a class as a subclass to Thread.
•
implement the run method, this is the thread we are to create
•
instansiate your class
•
call the start method of your class.
Threads 30 January 2003 4
This procedure is sometimes impossi- ble to implement.
Why?
Threads 30 January 2003 5
If you want an applet to contain a thread, it should extend Thread. How- ever an applet already extends Applet and can extend no more classes.
To solve this there is an interface, Run- nable, that can be implemented.
Threads 30 January 2003 6
The procedure to use this interface is
•
Let your class implement the Runna- ble interface. This means that you should implement the run method
•
declare a Thread as an instance varia- ble in your class
•
instantiate the Thread, give this (a Runnable) as parameter to the con- structor.
•
start the Thread, using the start method.
Threads 30 January 2003 7
A simple example looks like this
public class Trad extends Thread{
String s;
int d;
public Trad() { s = "";
d = 1000;
}
public Trad(String a, int delay) { s = a;
d = delay;
}
public void run() { while (true) {
System.out.println(s);
try {
sleep(d); // a Thread method }
catch (InterruptedException e) {} // do nussing
} }
public static void main(String [] s) {
Trad t1 = new Trad("Ettan", 100);
Trad t2 = new Trad("Tvåan", 175);
Threads 30 January 2003 8
t1.start(); t2.start();
try {
sleep(10000);
}
catch (InterruptedException e) {}
t1.stop();
t2.stop();
} }
This creates two instances of the Trad class and starts their threads. This will cause two parallel executions of the run method in two different objects. It will go on for a while until stopped by the main thread.
NOTE: stop is deprecated in Java2 and
should not be used. This is because it
cannot be guaranteed to leave the locks
in a proper state. This is just for demo
purpose.
Threads 30 January 2003 9
The output of this will be
> java Trad Ettan Tvåan Ettan Tvåan Ettan Ettan Tvåan Ettan Ettan Tvåan Ettan Ettan Tvåan Ettan Tvåan Ettan Ettan .... etc
Threads 30 January 2003 10
If I’m not supposed to use stop, how do I stop a thread?
public class Trad2 extends Thread{
String s;
int d;
public Trad2() { s = "";
d = 1000;
}
public Trad2(String a, int delay) { s = a;
d = delay;
}
public void run() {
while (!interrupted()) { System.out.println(s);
try { sleep(d);
}
catch (InterruptedException e) {break;}
} }
public static void main(String [] s) { Trad2 t1 = new Trad2("Ettan", 100);
Trad2 t2 = new Trad2("Tvåan", 175);
t1.start(); t2.start();
try {
sleep(10000);
}
catch (InterruptedException e) {}
t1.interrupt();
t2.interrupt();
} }
Threads 30 January 2003 11
Here I politely ask the thread to termi- nate. It might ignore me, but here we test the flag and terminate in a control- led way when asked to.
Why do we have a break in the catch?
Because if we are interrupted while sleeping, we will end up in the catch clause.
Threads 30 January 2003 12
An applet has some additional meth- ods
init, the setup method
stop, called when we leave this page start, called when we are back
destroy, called when the browser exits
Please note the difference between
start/stop in the Thread class and
start/stop in the Applet class.
Threads 30 January 2003 13
A digital clock implemented as an applet.
import java.applet.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;
//
// A digital clock applet //
public class Digital extends Applet implements Runnable {
private TextField h,m,s;
private Calendar cal; // current time private int hour,minute,second;
// extracted data private Label a,b,c; // some labels private Thread t; // our thread
public void init() { // get current time
cal = Calendar.getInstance();
setLayout(new GridLayout(2,3));
// extract h, m, s
hour = cal.get(Calendar.HOUR_OF_DAY);
minute = cal.get(Calendar.MINUTE);
second = cal.get(Calendar.SECOND);
a = new Label("Hour");
// add labels b = new Label("Min");
c = new Label("Sec");
h = new TextField(String.valueOf(hour));
m = new TextField(String.valueOf(minute));
s = new TextField(String.valueOf(second));
add(a); add(b); add(c);
add(h); add(m); add(s);
Threads 30 January 2003 14
// Disable editing of the textfields h.setEditable(false);
m.setEditable(false);
s.setEditable(false);
// create and start a thread t = new Thread(this);
t.start();
}
public void stop() { if (t != null) { t.interrupt();
t = null;
} }
public void start() { if (t == null) { t = new Thread(this);
t.start();
} }
public void destroy() { t.destroy();
t = null;
}
public void run() {
while (!Thread.interrupted()) { try {
Thread.sleep(200);
}
catch (InterruptedException e) {break;}
// get the time cal = Calendar.getInstance();
// extract data
hour = cal.get(Calendar.HOUR_OF_DAY);
minute = cal.get(Calendar.MINUTE);
Threads 30 January 2003 15
second = cal.get(Calendar.SECOND);
// update the clock
h.setText(String.valueOf(hour));
m.setText(String.valueOf(minute));
s.setText(String.valueOf(second));
} } };
This creates and starts a thread. When the browser selects another page, the thread is stopped. When the browser returns to this page, a new thread is created and started.
Note that the methods sleep and inter- rupt are prefixed with Thread. Why?
Threads 30 January 2003 16
Parallelism causes problems with data access. If you have several actors modi- fying and reading the same data, there is no way to tell the order of these accesses. If you have an array or other structure, it might be that part of the array have been written when you are to read it.
A class that handles these problems
properly is threadsafe.
Threads 30 January 2003 17
Assume that there are two persons that use a bank account. Both try to add 1000 SEK to the account at the same time. Assume that the balance is 1500 SEK. It might look like
Person 1 Person 2 --- --- Get the balance
Get the balance Add 1000
Set new balance as 2500 SEK
Add 1000 Set new balance as 2500 SEK.
1000 SEK got lost somewhere!
Threads 30 January 2003 18
To handle this we have to make sure that only one actor at a time have access to critical data. To do that we declare a method as synchronized class Account {
private double balance;
public synchronized void
transaction(double amount) { if (amount + balance <0) return;
else balance = balance+ amount;
} ....
}
Threads 30 January 2003 19
This adds a lock to the object so that there can only be one actor executing this method at a time. The lock is appli- cable to all synchronized methods in an object.
Thus, only one synchronized method in an object can be executed at any time.
Threads 30 January 2003 20
The locking mechanism is object based, which means that all other syncronized methods in the object are locked out, but not static methods. Also it does not affect other objects of the same class.
A static method may be declared syn-
chronized, and will then be synchro-
nized over all instances of the class.
Threads 30 January 2003 21
To properly synchronize data that is accessed by several objects, all accesses should be routed through a common object that can be synchronized.
Let us study a consumer-producer situ- ation where we have different threads producing and consuming data. This adds an additional problem, there might not be any data produced when a consumer requests it.
Threads 30 January 2003 22
To handle this, two additional tools are available, wait and notify. These are declared in the Object class.
If a consumer finds that there is no data available, it puts itself into wait mode by calling wait. This also releases the locks it might have acquired.
When a producer produces some data it calls notify to generate a signal that there is data available. This will release one of the waiting threads (if there are any).
Threads 30 January 2003 23
We have a simple queue where a pro- ducer stores numbers and a consumer gets numbers. Things are properly syn- chronized and accesses are done through an object of the queue type.
We are using wait and notify to handle the “empty/full” queue problem.
Since dequeue and enqueue are both in the same object, since both are declared synchronized and since the data array is local to the object, this class is thread- safe.
Threads 30 January 2003 24
// first a simple main that starts up everything public class QTest {
public static void main(String args[]) { Q q = new Q();
new Producer(q);
new Consumer(q);
} }
// the queue class public class Q {
private final int BUF_SIZE = 5;
private int buf[] = new int[BUF_SIZE];
private int start = 0;
private int current_size = 0;
// dequeueing
public synchronized int get() { if (current_size == 0) { try {
System.out.println("Queue empty in get");
wait();
}
catch (InterruptedException e) { }
}
// get the value, adjust the start pointer int return_value = buf[start++];
start %= BUF_SIZE;
// check if the queue was full, if so // signal that there is space left now.
if ((current_size--) == BUF_SIZE) { notify();
}
Threads 30 January 2003 25
return return_value;
}
// the enqueue method
public synchronized void put(int val) { if (current_size == BUF_SIZE) { try {
System.out.println("Queue full in put");
wait();
}
catch (InterruptedException e) { }
}
current_size++;
buf[(start+current_size) % BUF_SIZE] = val;
if (current_size==1) notify();
} }
// The producer class
public class Producer implements Runnable { private Q q; // the queue public Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}
//the “thread”
public void run() { int i = 0;
while(true) {
System.out.println("put " + i);
try {
if ((i/10)%2 == 0) Thread.sleep(100);
}
catch (InterruptedException e) { }
Threads 30 January 2003 26
q.put(i++);
} } } //
public class Consumer implements Runnable { private Q q;
public Consumer(Q q) { this.q = q;
new Thread(this, "Consumer").start();
}
public void run() { int i;
while(true) {
System.out.println("get");
i = q.get();
try {
if ((i/10)%2 == 1) Thread.sleep(100);
}
catch (InterruptedException e) { }
} } }
Threads 30 January 2003 27
If you run this you will get
get
Queue empty in get put 0
put 1 get
Queue empty in get put 2
get
Queue empty in get put 3
get
Queue empty in get put 4
get
Queue empty in get put 5
get
Queue empty in get put 6
get
Queue empty in get put 7
get
Queue empty in get put 8
get
Queue empty in get put 9
get
Queue empty in get put 10 put 11 put 12 put 13
Threads 30 January 2003 28
put 14 Queue full in put put 15 Queue full in put get
put 16 Queue full in put get
put 17 Queue full in put get
put 18 Queue full in put get
put 19 Queue full in put get
put 20 Queue full in put get
put 21 Queue full in put get
get put 22 ....
Threads 30 January 2003 29
Recent versions of Java contain tools to simplify threading.
Instead of an explicit thread you can use a scheduler, that schedules your parallel stuff at prescribed times. It might look like:
Threads 30 January 2003 30
import java.util.*;
public class Trad3 extends TimerTask{ // A timertask!!
String s;
public Trad3() { s = "";
}
public Trad3(String a) { s = a;
}
public void run() { System.out.println(s);
}
public static void main(String [] s) { Timer t = new Timer(); // our timer // create our two “threads”
Trad3 t1 = new Trad3("Ettan");
Trad3 t2 = new Trad3("Tvåan");
// schedule start and interval t.schedule(t1,100,100);
t.schedule(t2,175,175);
// wait 10s try {
Thread.sleep(10000);
}
catch (InterruptedException e) {}
// kill the timer and the “threads”
t1.cancel();
t2.cancel();
t.cancel();
} }
Threads 30 January 2003 31
When working with Swing and threads some special precautions must be taken.
Any action that changes any graphical component must be performed “in the eventhandling thread” to get proper synchronization.
The only exception to this is the con- structor (or init method). You may add and change components in the con- structor but not after the call to setVisi- ble.
Threads 30 January 2003 32
What does it mean?
You should do things that affect your graph in an event handler. An event- handler is always executing in the
“eventhandling thread”.
Threads 30 January 2003 33
There are different techniques that can be used here.
You can use some kind of timers or use explicit threads.
Threads 30 January 2003 34
To see an example we can use the Timer class (not to be confused with the Timer class). This Timer class is in javax.swing and the previous one is in java.util!
The Timer class is a simple clock that we can set to go off now and then.
When it do so, it generates an event that we can use.
An EXAMPLE!
Threads 30 January 2003 35
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;
//
// A digital clock applet, testing javax.swing.Timer //
public class JDigital extends JApplet implements ActionListener {
private JTextField h,m,s; // 3 fields in the clock private Calendar cal; // current time private int hour,minute,second; // extracted data private JLabel a,b,c; // some labels // create a timer that goes off each 200 ms //this acts as a listener
// qualification of the name is needed because // we are importing util too, giving us both Timer // tasks visible.
private javax.swing.Timer t =
new javax.swing.Timer(200,this);
public void init() {
Container cont = getContentPane();
cal = Calendar.getInstance(); // get current time cont.setLayout(new GridLayout(2,3)); // gridlayout hour = cal.get(Calendar.HOUR_OF_DAY); // extract minute = cal.get(Calendar.MINUTE);
second = cal.get(Calendar.SECOND);
a = new JLabel("Hour"); // add labels b = new JLabel("Min");
c = new JLabel("Sec");
h = new JTextField(String.valueOf(hour)); // data m = new JTextField(String.valueOf(minute));
s = new JTextField(String.valueOf(second));
cont.add(a); cont.add(b); cont.add(c);
cont.add(h); cont.add(m); cont.add(s);
// Disable editing of the textfields h.setEditable(false); m.setEditable(false);
s.setEditable(false);
// start a Timer t.start();
}
Threads 30 January 2003 36
public void start() {
System.out.println("Start");
t.restart();
}
public void stop() {
System.out.println("Stop");
t.stop();
}
// Change things in here, in the “Event thread”
public void actionPerformed(ActionEvent e) { // get the time
cal = Calendar.getInstance();
// extract data
hour = cal.get(Calendar.HOUR_OF_DAY);
minute = cal.get(Calendar.MINUTE);
second = cal.get(Calendar.SECOND);
// update the clock
h.setText(String.valueOf(hour));
m.setText(String.valueOf(minute));
s.setText(String.valueOf(second));
} };
Threads 30 January 2003 37
You can also use explicit threads as we did in the awt example. Then we have some other tools available:
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;
//
// A digital clock applet // testing some swing tools //
public class JDigital2 extends JApplet implements Runnable {
private JTextField h,m,s; // 3 fields in the clock private Calendar cal; // current time private int hour,minute,second; // extracted data private JLabel a,b,c; // some labels private Thread t; // our thread
public void init() {
Container cont = getContentPane();
cal = Calendar.getInstance(); // get current time cont.setLayout(new GridLayout(2,3)); //gridlayout hour = cal.get(Calendar.HOUR_OF_DAY); // h, m, s minute = cal.get(Calendar.MINUTE);
second = cal.get(Calendar.SECOND);
a = new JLabel("Hour"); // add labels b = new JLabel("Min");
c = new JLabel("Sec");
h = new JTextField(String.valueOf(hour)); // data m = new JTextField(String.valueOf(minute));
s = new JTextField(String.valueOf(second));
cont.add(a); cont.add(b); cont.add(c);
cont.add(h); cont.add(m); cont.add(s);
// Disable editing of the textfields h.setEditable(false); m.setEditable(false);
s.setEditable(false);
// create and start a thread t = new Thread(this);
t.start();
Threads 30 January 2003 38
}
public void stop() { if (t != null) { t.interrupt();
t = null;
} }
public void start() { if (t == null) { t = new Thread(this);
t.start();
} }
public void destroy() { if (t != null) t.destroy();
t = null;
}
// now, instead of updating the graph, spawn another // thread that updates the graph in the proper way.
public void run() {
Updater r = new Updater();
while (!Thread.interrupted()) { try {
Thread.sleep(200);
}
catch (InterruptedException e) {break;}
SwingUtilities.invokeLater(r); //!!!!!
} }
// internal help class
class Updater implements Runnable { public void run() {
// get the time
cal = Calendar.getInstance();
// extract data
hour = cal.get(Calendar.HOUR_OF_DAY);
minute = cal.get(Calendar.MINUTE);
second = cal.get(Calendar.SECOND);
Threads 30 January 2003 39
// update the clock
h.setText(String.valueOf(hour));
m.setText(String.valueOf(minute));
s.setText(String.valueOf(second));
} } };
Threads 30 January 2003 40
An example with moving objects is the following:
This draws a rotating and moving pol- ygon.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class JPolyCanvas extends JPanel implements Runnable {
private Thread aktivitet;
int n, r, x0, y0;
double vinkel;
int[] x,y;
final double dv = 5*2*Math.PI/360;
double vrid = 0;
int xSteg = 4, ySteg = 3;
boolean first = true;
public JPolyCanvas() { this(7, 100);
}
public JPolyCanvas(int number, int radius) { n = number;
r = radius;
x = new int[n];
y = new int[n];
vinkel = 2*Math.PI/n;
}
public void start() { if (aktivitet == null ) { aktivitet = new Thread(this);
aktivitet.start();
} }
public void stop() { if (aktivitet != null ) { aktivitet.interrupt();
aktivitet = null;
Threads 30 January 2003 41
} }
public void destroy() { if (aktivitet != null ) { aktivitet.destroy();
} }
public void run() {
Updater r = new Updater();
while (!aktivitet.interrupted()) { try {
Thread.sleep(200);
}
catch (InterruptedException e) {break;}
SwingUtilities.invokeLater(r);
} }
class Updater implements Runnable { public void run () {
vrid = vrid + dv;
if (vrid > 2*Math.PI) vrid -= 2*Math.PI;
if (x0 - r + xSteg < 0 ||
x0 + r + xSteg > getSize().width) xSteg = -xSteg;
x0 += xSteg;
if (y0 - r + ySteg < 0 ||
y0 + r + ySteg > getSize().height) ySteg = -ySteg;
y0 += ySteg;
repaint();
} }
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (first) {
x0 = getSize().width/2;
y0 = getSize().height/2;
first = false;
}
for (int i=0; i < n; i++) {
double v = i*vinkel - vrid;
x[i] = x0 + (int)Math.round(r * Math.cos(v));
Threads 30 January 2003 42
y[i] = y0 - (int)Math.round(r * Math.sin(v));
}
g.fillPolygon(x, y, n);
} }
Threads 30 January 2003 43
To use this we can do
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.applet.*;
public class JPoly extends JApplet implements
ActionListener { JPanel p = new JPanel();
JPolyCanvas c = new JPolyCanvas(20,50);
JRadioButton c1 = new JRadioButton("Start");
public void init() {
Container cont = getContentPane();
cont.setLayout(new BorderLayout());
c1.addActionListener(this);
p.add(c1);
cont.add("South",p);
cont.add("Center",c);
setVisible(true);
}
public void actionPerformed(ActionEvent e) { if (e.getSource() == c1 &&
c1.getText().equals("Stop")) {
c.stop(); c1.setText("Start");
}
else if (e.getSource() == c1) { c.start(); c1.setText("Stop");
} }
public void stop() { c1.setText("Start");
c.stop();
}
public void start() { c1.setText("Stop");
c.start();
}
public void destroy() { c.destroy();
}
public static void main(String[] args)
Threads 30 January 2003 44
{
JPoly p = new JPoly();
} }
This will result in
Threads 30 January 2003 45
We can also do something more fun
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class JPolyMany extends JApplet { JPolyCanvas c[];
public void init() {
Container cont = getContentPane();
c = new JPolyCanvas[4];
c[0] = new JPolyCanvas(20,40);
c[1] = new JPolyCanvas(100,40);
c[2] = new JPolyCanvas(30,50);
c[3] = new JPolyCanvas(5,40);
cont.setLayout(new GridLayout(2,2));
cont.add(c[0]);
cont.add(c[1]);
cont.add(c[2]);
cont.add(c[3]);
c[0].start();
c[1].start();
c[2].start();
c[3].start();
} }
Threads 30 January 2003 46
Which will produce
Images and sounds 30 January 2003 47
Images and sounds
Images can be loaded in both Applets and standalone programs, but different procedures are used.
The Applet class have two methods for this purpose,
getCodeBase(), class file location getDocumentBase(), html file location They both return a URL-object that can be used.
Images and sounds 30 January 2003 48
To load an image (a gif or a jpeg) you use the getImage method.
Image im;
im = getImage(getCodeBase(),”filename”);
The image must be stored in the same
catalogue as the class file, or in a sub-
catalogue. You cannot use an absolute
path in the filename.
Images and sounds 30 January 2003 49
To show the image, the drawImage method can be used.
g.drawImage(im, xstart, ystart, this);
or to rescale the image
g.drawImage(im, xstart, ystart, xsize, ysize, this);
The last parameter is an ImageOb- server. This is an interface imple- mented by all Component objects.
g is a Graphics object.
Images and sounds 30 January 2003 50
The getImage is asynchronous and returns immediately and the image is loaded in the background. The obser- ver automatically monitors the load- ing process and displays the image as it is loaded.
There are tools to handle this process manually too.
Images and sounds 30 January 2003 51
A simple example
import java.applet.*;
import java.awt.*;
public class ImDemo extends Applet { Image image;
public void init() {
// get filename from the html-file String name = getParameter("imagename");
image = getImage(getCodeBase(), name);
}
public void paint(Graphics g) { g.drawImage(image,10,10,this);
} }
And the corresponding HTML-file
<html>
<head>
<title>ImDemo</title>
</head>
<body>
<applet code=ImDemo.class width=300 height=200 align=right>
<param name=imagename value=olle.gif>
</applet>
<H1> Welcome to <br>
Java</H1>
</body> </html>
Images and sounds 30 January 2003 52
The result is
To get the size of the Image there are two methods available
getWidth(ImageObserver o) getHeight(ImageObserver o) both are methods in the Image class.
The ImageObserver is usually given as
this.
Images and sounds 30 January 2003 53
If it is vital that the Image is completely loaded before using it you need to use some synchronization tools, like the MediaTracker
You can do
MediaTracker mt =
new MediaTracker(this);
to create it. Then you must set it up mt.addimage(image, id);
This will add the Image image to its supervisor list and give it the idnum- ber id. Each image should have a unique id in your program.
Images and sounds 30 January 2003 54
Then you wait for it to load try {
mt.waitForID(id);
} catch ( InterruptedException e) {}
Images and sounds 30 January 2003 55
In a program this gets
import java.applet.*;
import java.awt.*;
public class ImDemo2 extends Applet { Image image;
MediaTracker mt;
public void init() { mt = new MediaTracker(this);
String name = getParameter("imagename");
image = getImage(getCodeBase(), name);
mt.addImage(image,0);
try { mt.waitForID(0);}
catch ( InterruptedException e) {}
}
public void paint(Graphics g) { g.drawImage(image,10,10,this);
} }
Note that this makes the program less efficient and it shouldn’t be used unless you actually need to access the image for other purposes than to dis- play it.
Images and sounds 30 January 2003 56
To load an Image in a standalone pro- gram a different technique is used You still need an URL but there is no CodeBase. So the URL has to be con- structed in another way.
You do that in the following way URL u = new URL(“a URL”);
Then you can load the contents of the URL using
ImageProducer ip =
(ImageProducer) u.getContent();
This requires that it is an image you
are loading. If the URL refers some-
thing else the typecast will fail.
Images and sounds 30 January 2003 57
To get an Image from the ImagePro- ducer you do
Image im = createImage(ip);
If you have a local image file you can use an alternative approach,
Image im =
Toolkit.GetDefaultToolkit().createImage(
“filename”);
Images and sounds 30 January 2003 58
A small program that uses this is
import java.awt.*;
import java.awt.image.*;
import java.net.*;
import java.io.*;
public class BildApp extends Frame { URL u; Image bilden;
/**
* Constructor.
*/
public BildApp (String namn) { try {
u = new URL(namn);
}
catch (MalformedURLException e) {
System.out.println("Illegal URL " + namn);
} try {
bilden =
createImage((ImageProducer) u.getContent());
}
catch (IOException e) {
System.out.println("Error when reading " + namn);
}
setSize(400,400);
setTitle(u.getHost() + u.getFile());
setVisible(true);
}
public void paint(Graphics g) {
g.drawImage(bilden, 50, 50, 300, 300, this);
}
public static void main(String [] arg) { new BildApp(arg[0]);
} }
Images and sounds 30 January 2003 59
If you do
java BildApp http://www.cs.chalmers.se/~skanshol/Java_dir/hund.gif
you will get
Images and sounds 30 January 2003 60
The best way to integrate an Image into a graph is to extend a Canvas or JPanel and to load the Image into the class.
The class can then easlily added to a Container.
You have the choice if the image
should be scaled or truncated if it is too
big.
Images and sounds 30 January 2003 61
Animation is achieved by loading a number of images and displaying them as fast as possible.
A simple piece of code that do anima- tion is
Images and sounds 30 January 2003 62
import java.awt.*;
import java.awt.image.*;
import java.net.*;
import java.io.*;
public class Animator extends Frame implements Runnable { private URL u;
private Image [] bilden; // An array of images private int index = 0;
private Thread t;
private int size;
/**
* Constructor.
*/
public Animator (String[] name) { bilden = new Image[name.length];
size = name.length;
// load the images
for (int i = 0; i < name.length; i++) { try {
u = new URL(name[i]);
}
catch (MalformedURLException e) {
System.out.println("Illegal URL " + name[i]);
} try { bilden[i] =
createImage((ImageProducer) u.getContent());
}
catch (IOException e) { System.out.println(
"Error when reading " + name[i]);
} }
t = new Thread(this);
t.start();
setSize(400,400);
setTitle(u.getHost() + u.getFile());
setVisible(true);
}
// the display thread public void run() {
while (true) { try {
Images and sounds 30 January 2003 63
Thread.sleep(100);
}
catch (InterruptedException e) {}
index = (index + 1) % size;
repaint();
} }
public void paint(Graphics g) {
g.drawImage(bilden[index], 50, 50, 300, 300, this);
}
public static void main(String [] arg) { new Animator(arg);
} }
This requires that the images have the same size. We don’t use a MediaT- racker to check the loading, that might give some slowdown in the beginning of the film.
Images and sounds 30 January 2003 64
Sounds are easy to handle, the only requirement is that you have sound hardware. Java 1.2 supports the AIFF, WAW, TYPE 0 MIDI and TYPE 1 MIDI formats.
To load a soundfile into an Applet is very easy
AudioClip a =
getAudioClip(getCodeBase(),”soundfile”);
Then there are three simple methods available
play(), play the sound
stop(), stop the play
loop(), play in a loop
Images and sounds 30 January 2003 65
A simple program might be
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class SoundDemo extends Applet { AudioClip frogsound;
Image frogimage;
MediaTracker mt = new MediaTracker(this);
// draw the image and play the sound public void paint(Graphics gr) {
gr.drawImage(frogimage,50,50,this);
frogsound.play();
}
// add a mouse listener, each time a mousebutton // is pressed, play the sound
MouseListener ml = new MouseAdapter () { public void mouseClicked(MouseEvent e) {
frogsound.play();
} };
// set up things public void init() {
addMouseListener(ml);
frogsound = getAudioClip(getCodeBase(),"frog.au");
frogimage = getImage(getCodeBase(),"frog.gif");
mt.addImage(frogimage,0);
try {
mt.waitForID(0);
}
catch (InterruptedException e) {}
frogsound.play();
resize(300,400);
} }
Images and sounds 30 January 2003 66
Will give you this
Images and sounds 30 January 2003 67
To use sounds in a standalone applica- tion you need a URL. In addition you need a Java2 implementation.
URL u = new URL(“a URL”);
To load the file you do AudioClip a =
Applet.newAudioClip(u);
This is a static method in the Applet class.
Images and sounds 30 January 2003 68
An example would be
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.net.*;
import java.applet.*;
import java.io.*;
public class SoundDemo2 extends Frame { // create the things we need
private AudioClip frogsound;
private URL u1, u2;
private Image frogimage;
private MediaTracker mt = new MediaTracker(this);
// draw the image, play the sound public void paint(Graphics gr) {
gr.drawImage(frogimage,50,50,this);
frogsound.play();
}
// add a mouselistener
MouseListener ml = new MouseAdapter () { public void mouseClicked(MouseEvent e) {
frogsound.play();
} };
// constructor public SoundDemo2() { addMouseListener(ml);
// try to load the image and the sound, create URL’s try {
u1 = new URL(
"http://www.tdb.uu.se/~olle/pvk/assorted/frog.gif");
u2 = new URL(
"http://www.tdb.uu.se/~olle/pvk/assorted/frog.au");
}
catch (MalformedURLException e) { System.out.println("Illegal URL");}
Images and sounds 30 January 2003 69
// read the soundfile and the imagefile
frogsound = Applet.newAudioClip(u2);
try { frogimage =
createImage((ImageProducer) u1.getContent());
}
catch (IOException e) {
System.out.println("Error when reading the image");
}
// wait for the image to load mt.addImage(frogimage,0);
try {
mt.waitForID(0);
}
catch (InterruptedException e) {}
setSize(300,400);
setVisible(true);
}
public static void main(String [] arg) { SoundDemo2 s = new SoundDemo2();
} }
Images and sounds 30 January 2003 70
We have seen ImageObserver and ImageProducer here. There are also an ImageConsumer interface
If you are interested in modifying the image in any way you should declare an ImageConsumer, modify the data, recreate the image using an ImagePro- ducer.
Images and sounds 30 January 2003 71
One implementation of ImageCon- sumer is the PixelGrabber.
int [] arr = new int[width][height];
PixelGrabber pg =
new PixelGrabber(Image, x, y, w, h, int [] arr, offset, scanset);
pg.grabPixels();
You now can work with the array. It contains 32 bit pixels, 4x 8bit for Alpha, R, G and B
Images and sounds 30 January 2003 72
Once you are finished you can do Image im;
im = createImage
(new MemoryImageSource(
width,height, array, 0, width));
The MemoryImageSource implements
the ImageProducer Interface.
Images and sounds 30 January 2003 73
But you can also declare a BufferedIm- age.
Such an object allows you to access data directly without all the help- classes.
If you have a loaded Image your can convert it to a BufferedImage using something like
Images and sounds 30 January 2003 74
// assume the image is a loaded Image
BufferedImage bilden = new BufferedImage(image.getWidth(null), image.getHeight(null),
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = bilden.createGraphics();
g2.drawImage(image,null,null);
This creates the image, obtains its Graphics2D object and renders the Image into the BufferedImage.
Images and sounds 30 January 2003 75
You can then do
for (int i =0; i < bilden.getWidth(); i++) for (int j=0; j < bilden.getHeight(); j++)
bilden.setRGB(i, j, bilden.getRGB(i,j) & 0xFFFF00FF);
The getRGB method gives you pixel packed as 4 8-bit pixels in an int, the order is Alpha, Red, Green, Blue.
This piece of code clears out the green part of all pixels
Images and sounds 30 January 2003 76
A piece of code the blurs the image with a convolution
BufferedImage b;
float ninth = 1.0f/ 9.0f;
float [] blurKernel = { ninth, ninth, ninth, ninth, ninth, ninth,
ninth, ninth, ninth};
ConvolveOp blurOp = new ConvolveOp ( new Kernel(3, 3, blurKernel),
ConvolveOp.EDGE_NO_OP, null);
b = blurOp.filter(bilden,null);
This creates a 3x3 matrix that is used to
do a convolution kernel. We ignore the
edge pixels and have no rendering
hints. Finally the operation is done.
Images and sounds 30 January 2003 77
Another piece of code
BufferedImage b;
short [] invert = new short[256];
short [] straight = new short[256];
for (int i = 0; i < 256; i++) { invert[i] = (short)(255-i);
straight[i] = (short) i;
}
short[] [] redInvert = { invert, straight, straight};
LookupTable table = new ShortLookupTable(0,redInvert);
LookupOp redinvertOp = new LookupOp(table,null);
b = redinvertOp.filter(bilden,null);
This creates a lookup table that is used to transform each pixel. We create three of these, one for each color. The RED one inverts the 8-bits scale, the GREEN and the BLUE do nothing.
Images and sounds 30 January 2003 78
A rotation
BufferedImage b;
AffineTransform at =
AffineTransform.getRotateInstance(Math.PI/6,0,80);
RenderingHints rh = new RenderingHints(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
AffineTransformOp rotateOp = new AffineTransformOp(at,rh);
b = rotateOp.filter(bilden,null);
This creates a rotation around the point (0,80) of pi/6. We do want bilinear interpolation during the operation.
Images and sounds 30 January 2003 79
A complete code
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.net.*;
import java.io.*;
public class JBildApp extends JFrame { BufferedImage bilden;
Image image;
/**
* Constructor.
*/
public JBildApp (String namn) { // load the image
MediaTracker mt = new MediaTracker(this);
image =
Toolkit.getDefaultToolkit().getImage(namn);
// wait for it to load mt.addImage(image,0);
try { mt.waitForID(0);}
catch ( InterruptedException e) {}
// create a BufferedImage
bilden = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
// create Graphics2D
Graphics2D g2 = bilden.createGraphics();
// draw the image into the BufferedImage g2.drawImage(image,null,null);
setSize(400,400);
setVisible(true);
}
Images and sounds 30 January 2003 80
// Clear out the green part of the image public void clear_G() {
for (int i =0; i < bilden.getWidth(); i++) for (int j=0; j < bilden.getHeight(); j++)
bilden.setRGB(i, j,
bilden.getRGB(i, j) & 0xFFFF00FF);
repaint();
}
// blur the image with a convolution kernel public void convolve() {
BufferedImage b; // work image // create the matrix
float ninth = 1.0f/ 9.0f;
float [] blurKernel = { ninth, ninth, ninth, ninth, ninth, ninth, ninth, ninth, ninth};
// create the kernel
ConvolveOp blurOp = new ConvolveOp (
new Kernel(3, 3, blurKernel), ConvolveOp.EDGE_NO_OP, null);
// apply
b = blurOp.filter(bilden,null);
// swap the image bilden = b;
repaint();
}
Images and sounds 30 January 2003 81
// rotate
public void rotate() { BufferedImage b;
// create an affine transformation AffineTransform at =
AffineTransform.getRotateInstance(Math.PI/6,0,80);
// create the rendering hint RenderingHints rh = new RenderingHints(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
// create the transformation AffineTransformOp rotateOp =
new AffineTransformOp(at,rh);
// apply it
b = rotateOp.filter(bilden,null);
bilden = b;
repaint();
}
// invert the red public void invert_R() {
BufferedImage b;
short [] invert = new short[256];
short [] straight = new short[256];
// create the lookup tables for (int i = 0; i < 256; i++) { invert[i] = (short)(255-i);
straight[i] = (short) i;
}
// a common transform short[] [] redInvert =
{ invert, straight, straight};
// create the final table LookupTable table =
Images and sounds 30 January 2003 82
new ShortLookupTable(0,redInvert);
// and the transform
LookupOp redinvertOp = new LookupOp(table,null);
// apply it
b = redinvertOp.filter(bilden,null);
bilden = b;
repaint();
}
public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g;
g2.drawImage(bilden, null, 20,50);
}
public static void main(String [] arg)
throws InterruptedException { JBildApp a = new JBildApp(arg[0]);
Thread.sleep(2000);
a.invert_R();
Thread.sleep(2000);
a.convolve();
Thread.sleep(2000);
a.rotate();
Thread.sleep(2000);
a.clear_G();
} }
Images and sounds 30 January 2003 83
If you do
java JBildApp olle.gif You will end up with
Arrays, Strings and Collections 30 January 2003 84
Arrays, Strings and Collections Java has some basic data structures.
The simplest of these are String, StringBuffer and the array.
The String class is used to represent a number of characters.
It has a lot of methods, some are length(), the length of the String charAt(n), the n’th character substring(n,m), substring n..m-1 compareTo(s), compare Strings equals(s), test if equal
indexOf(c), index of the character c
trim(), remove leading/trailing spaces
valueOf(x), convert x into a String
Arrays, Strings and Collections 30 January 2003 85
Note that the test for equality is equals, not the == operator.
String s = “Nisse”;
String t = “Nisse”;
if (s.equals(t))
System.out.println(“Equal”);
else
System.out.println(“Not equal”);
if (s == t)
System.out.println(“Equal”);
else
System.out.println(“Not equal”);
The first one will print “Equal”. The second one compares the references, i. e. whether s and t points to the same string. The result of this is probably
“Not equal” but its up to compiler.
Arrays, Strings and Collections 30 January 2003 86
A String object is ReadOnly! There is no way to change the content of a String.
You may of course throw away the object and create a new object with a different content. You may also put Strings together using the +-operator but that also creates a new object.
There is also a class StringBuffer with objects that can be modified.
Some additional methods in String- Buffer are:
append(s), append to the StringBuf.
insert(pos,s), insert s at position pos
Arrays, Strings and Collections 30 January 2003 87
String and StringBuffer implement the interface Comparable, i. e. they imple- ment the method compareTo, and they can be sorted.
The sorting is based on the order of the character codes. This means that other languages than English might get incorrectly sorted.
Arrays, Strings and Collections 30 January 2003 88
To handle this, a more general ordering mechanism is available, the Collator class.
It uses the current locale and compares using the local rules. It can also be set to different strength.
In English we have:
a and b is a PRIMARY difference.
a and å is a SECONDARY difference a and A is a TERTIARY difference a and a are IDENTICAL
å is no letter in English but a variant of the letter a, a diacritical.
It can be used like this
Arrays, Strings and Collections 30 January 2003 89
Collator co = Collator.getInstance();
// TERTIARY is default if (co.compare(“åska”,”Äska”) < 0) System.out.println(“åska is before Äska”);
else if (co.compare(“åska”,”Äska”) > 0) System.out.println(“åska is after Äska”);
else
System.out.println(“equal”);
co.setStrength(Collator.SECONDARY);
if (co.compare(“åska”,”Äska”) < 0) System.out.println(“åska is before Äska”);
else if (co.compare(“åska”,”Äska”) > 0) System.out.println(“åska is after Äska”);
else
System.out.println(“equal”);
co.setStrength(Collator.PRIMARY);
if (co.compare(“åska”,”Äska”) < 0) System.out.println(“åska is before Äska”);
else if (co.compare(“åska”,”Äska”) > 0) System.out.println(“åska is after Äska”);
else
System.out.println(“equal”);
The output will be
åska is before Äska åska is before Äska equal
Arrays, Strings and Collections 30 January 2003 90
If you instead do
Collator co = Collator.getInstance(new Locale(“sv”,”SE”));
The output will be
åska is before Äska åska is before Äska åska is before Äska
Why? Because in Swedish, å and ä make up a PRIMARY difference since they are distinc letters, in English they have a SECONDARY difference.
Arrays, Strings and Collections 30 January 2003 91
If you do a lot of comparisons using the same string it might be a good idea to compute a CollationKey. It is a key that is fast to compare that reflects the order of the strings.
CollationKey k1 = co.getCollatorKey(“program”);
CollationKey k2 = co.getCollatorKey(“pâté”);
if (k1.compareTo(k2) < 0)....
Arrays, Strings and Collections 30 January 2003 92
All arrays are objects, even though the elements may not be (i. e. ints).
They have a public instance variable length, that gives the size of the array.
A declaration int [] a;
double [][] b;
just declares a reference, not an array.
Arrays, Strings and Collections 30 January 2003 93
Whereas
a = new int[100];
b = new double[10][10];
creates the array
Arrays, Strings and Collections 30 January 2003 94
A multidimensional array is really a reference to an array of references to an array. Thus you could do
b = new double[10][];
for (int i = 0; i < b.length; i++) b[i] = new int[10];
The length of b is the number of lines, the number of columns is b[0].length Note that this makes it possible to do matrices that are nonrectangular. e. g.
b = new double[10][];
for (int i =0 ; i < b.length; i++) b[i] = new int[i+1];
Arrays, Strings and Collections 30 January 2003 95
Arrays of objects can of course also be created. The array will be an array of references, not an array of objects though. Assuming that we have a class, Person, we can create an array:
Person [] parr;
parr = new Person[10];
for (int i=0; i < 10; i++) parr[i] = new Person();
Note the separate step to create the objects!
Arrays, Strings and Collections 30 January 2003 96
Arrays may be compared using the ==
operator but that just compare the ref- erence (the addresses), not the ele- ments.
The assignment operator just copy the reference, not the elements.
To copy an array, you either use arrayc-
opy or clone.
Arrays, Strings and Collections 30 January 2003 97
Arraycopy looks like
System.arraycopy(fromarray, startindex, toarray, startindex, numberofelem);
The outputarray must have been ini- tialized, i. e. must not be a nullrefer- ence and it must be large enough to hold the input data.
Arraycopy is shallow which means that it just copies the array. If you have an array of objects, the objects will not be copied, just the references.
Arrays, Strings and Collections 30 January 2003 98
Another method is clone, int [] a = new int [100];
int [] b;
b = (int []) a.clone();
The clone method returns an Object and must be typecasted back to the cor- rect type. The destination need not exist to use clone.
This is also a shallow copy.
Arrays, Strings and Collections 30 January 2003 99
If you have
int [][] a = new int [2][3];
int [][] b;
you must do
b = (int [][]) a.clone();
for (int i = 0; i < a.length; i++) b[i] = (int []) a[i].clone();
to copy the matrice deep.
Arrays, Strings and Collections 30 January 2003 100
If you have
Person [] p = new Person[10];
for (int i=0; i < 10; i++) p[i] = new Person();
Person [] q;
you must do
q = (Person []) p.clone();
for (int i = 0; i < 10; i++) q[i] = (Person) p[i].clone();
to get a deep copy.
Arrays, Strings and Collections 30 January 2003 101
A class cannot in general do cloning, to be able to do that you need to:
implement the empy interface Clonea- ble
optionally override the clone method in the Object class. If you select not to override this method, a shallow copy will be made.
If shallow copying is OK public class Person
implements Cloneable { will do.
If you want deep cloning, you should override the clone method.
public Object clone()
Arrays, Strings and Collections 30 January 2003 102
There is a package
java.util.Arrays (in Java2 only) that contains some useful methods equals(a,b), compare two arrays sort(a), sort an array
binarySearch(a,k), seach for k in a Sorting requires that the elements have a natural ordering or that they imple- ments the Comparable interface. This interface defines the methodcom- pareTo that should be implemented.
Arrays, Strings and Collections 30 January 2003 103
Other data structures
Java2 defines a number of builtin data structures. These are declared by a number of interfaces and a number of implementations of these interfaces.
The basic interface is Collection. This interface have two subinterfaces called List and Set. The Set interface have a subinterface SortedSet.
Arrays, Strings and Collections 30 January 2003 104
A List is an ordered (not neccesarily sorted) structured sequence of ele- ments. Each element has a position.
The same value can occur several times
A Set is an unordered structure where
a value occurs once (or not at all).
Arrays, Strings and Collections 30 January 2003 105
Lists are implemented by Vector, ArrayList and LinkedList.
Sets are implemented by HashSet and TreeSet (a SortedSet).
Since Collection is an interface you can implement your own structure if you like to.
Arrays, Strings and Collections 30 January 2003 106
A Collection defines some methods add(Object o)
clear() isEmpty() iterator()
remove(Object o)
It also defines a constructor that takes any Collection as parameter and that converts that Collection to the current structure.
Arrays, Strings and Collections 30 January 2003 107
An Iterator is a modernized version of Enumerator and defines methods like hasNext, next, remove.
The List interface defines some addi- tional methods (page 637 in Skans- holm), in addition it uses a List-Iterator that is more specific than theIterator.
Arrays, Strings and Collections 30 January 2003 108
There are also maps. A map is a struc- ture where access data using a key.
There are the classes TreeMap, Hashta- ble and HashMap implemented.
See Skansholm chapter 19.4
Arrays, Strings and Collections 30 January 2003 109
A final example, a program that reads words from a file and inserts them into a binary searchtree.
import java.util.*;
import java.text.*;
import java.io.*;
import extra.*;
public class TextAnalys {
// detta är ett program. övar lite, testa om ord som // åke, östen och sånt sorteras rätt
public static void main(String [] arg) { String word;
Collator co =
Collator.getInstance(new Locale("sv","SE"));
co.setStrength(Collator.PRIMARY);
TreeSet words = new TreeSet(co);
// specify sorter
// ExtendedReader is a Skansholm class ExtendedReader
r = ExtendedReader.getFileReader(arg[0]);
// read one word at a time add it to the tree while ((word = nextWord(r)) != null) words.add(word.toUpperCase());
// print all words
for (Iterator i = words.iterator(); i.hasNext();) Std.out.println(i.next());
}
public static String nextWord(ExtendedReader r) { String s = "";
int c;
// skip nonletters
while ((c = r.lookAhead()) != -1 &&
!Character.isLetter((char) c)) c = r.readChar();
Arrays, Strings and Collections 30 January 2003 110
// get the letters
while ((c = r.lookAhead()) != -1 &&
Character.isLetter((char) c)) s = s + (char) r.readChar();
if (s.length() == 0) return null;
else return s;
} }
If you run it like
java TextAnalys TextAnalys.java You will get
AADD ALLARG AT CCHAR CHARACTER CLASS CO COLLATOR DETTA ELSEETT
EXTENDEDREADER EXTRA FOR GETGETFILEREADER GETINSTANCE HASNEXT IIF IMPORT INT IOIS ISLETTER IT
Arrays, Strings and Collections 30 January 2003 111
ITERATOR JAVALENGTH LETTERS LITELOCALE LOOKAHEAD MAIN NEWNEXT NEXTWORD NONLETTERS NULLOCH OMONE ORDOUT PRIMARY PRINT PRINTLN PROGRAM PUBLIC R READREADCHAR RETURN RÄTTS SESETSTRENGTH SKANSHOLM SKIPSOM SORTER SORTERAS SPECIFY STATIC STD STRING SVSÅNT TESTA TEXT TEXTANALYS THETIME TOTOUPPERCASE TREE
Arrays, Strings and Collections 30 January 2003 112
TREESET UTILWHILE VOIDWORD WORDS ÅKEÄR ÖSTEN ÖVAR