プロ2 > 資料 > 第5章
第5回:多相性とその活用
目標
- インタフェースが理解できる.
- 継承と抽象クラスが理解できる.
5.1 インタフェースと多相性
- インタフェースは複数のクラスをまとめて扱うための仕組みである.
- インタフェースによりそのクラスが持つべきメソッドを定義する.メソッドの動作内容はクラスごとに異なってもよい(多相性).
- インタフェースで定義したメソッドをクラスの中で定義しないとエラーが発生する.
- 以下の例では,さまざまな図形クラスをFigureインタフェースに従わせることによって,異なる図形でもFigureクラスとして扱うことができ,同じメソッド(draw, hit, moveTo))が利用できるようになる.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
public class Sample51 extends JPanel {
ArrayList<Figure> figs = new ArrayList<Figure>();
Figure sel = null;
public Sample51() {
setOpaque(false);
figs.add(new Circle(Color.PINK, 200, 100, 40));
figs.add(new Circle(Color.GREEN, 220, 80, 30));
figs.add(new Rect(Color.YELLOW, 240, 60, 30, 40));
figs.add(new Rect(Color.BLUE, 260, 40, 80, 40));
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent evt) {
sel = pick(evt.getX(), evt.getY());
}
});
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent evt) {
if (sel == null) {
return;
}
sel.moveTo(evt.getX(), evt.getY());
repaint();
}
});
}
private Figure pick(int x, int y) {
Figure p = null;
for (Figure f : figs) {
if (f.hit(x, y)) {
p = f;
}
}
return p;
}
public void paintComponent(Graphics g) {
for (Figure f : figs) {
f.draw(g);
}
}
public static void main(String[] args) {
JFrame app = new JFrame();
app.add(new Sample51());
app.setSize(400, 300);
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setVisible(true);
}
interface Figure {
public void draw(Graphics g);
public boolean hit(int x, int y);
public void moveTo(int x, int y);
}
static class Circle implements Figure {
Color col;
int xpos, ypos, rad;
public Circle(Color c, int x, int y, int r) {
col = c;
xpos = x;
ypos = y;
rad = r;
}
public boolean hit(int x, int y) {
return (xpos - x) * (xpos - x) + (ypos - y) * (ypos - y) <= rad
* rad;
}
public void moveTo(int x, int y) {
xpos = x;
ypos = y;
}
public void draw(Graphics g) {
g.setColor(col);
g.fillOval(xpos - rad, ypos - rad, rad * 2, rad * 2);
}
}
static class Rect implements Figure {
Color col;
int xpos, ypos, width, height;
public Rect(Color c, int x, int y, int w, int h) {
col = c;
xpos = x;
ypos = y;
width = w;
height = h;
}
public boolean hit(int x, int y) {
return xpos - width / 2 <= x && x <= xpos + width / 2
&& ypos - height / 2 <= y && y <= ypos + height / 2;
}
public void moveTo(int x, int y) {
xpos = x;
ypos = y;
}
public void draw(Graphics g) {
g.setColor(col);
g.fillRect(xpos - width / 2, ypos - height / 2, width, height);
}
}
}
- ArrayListは複数の要素をリスト形式で格納する(配列のような)構造体.配列と異なり格納できる要素の数をあらかじめ指定する必要がない.要素の挿入にはaddメソッドを用いる. 要素の削除にはremoveメソッドを用いる.
- 以下の例では,クリックした図形を削除し,再び挿入することで,手前に表示している.また何もないところをクリックすると円が生成される.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
public class Ex51ab extends JPanel {
ArrayList<Figure> figs = new ArrayList<Figure>();
Figure sel = null;
public Ex51ab() {
setOpaque(false);
figs.add(new Circle(Color.PINK, 200, 100, 40));
figs.add(new Circle(Color.GREEN, 220, 80, 30));
figs.add(new Rect(Color.YELLOW, 240, 60, 30, 40));
figs.add(new Rect(Color.BLUE, 260, 40, 80, 40));
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent evt) {
sel = pick(evt.getX(), evt.getY());
if (sel != null) {
figs.remove(sel);
figs.add(sel);
repaint();
} else {
Color c = Color.getHSBColor((float) Math.random(), 1f, 1f);
sel = new Circle(c, evt.getX(), evt.getY(), 30);
figs.add(sel);
repaint();
}
}
});
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent evt) {
if (sel == null) {
return;
}
sel.moveTo(evt.getX(), evt.getY());
repaint();
}
});
}
private Figure pick(int x, int y) {
Figure p = null;
for (Figure f : figs) {
if (f.hit(x, y)) {
p = f;
}
}
return p;
}
public void paintComponent(Graphics g) {
for (Figure f : figs) {
f.draw(g);
}
}
public static void main(String[] args) {
JFrame app = new JFrame();
app.add(new Ex51ab());
app.setSize(400, 300);
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setVisible(true);
}
interface Figure {
public void draw(Graphics g);
public boolean hit(int x, int y);
public void moveTo(int x, int y);
}
static class Circle implements Figure {
Color col;
int xpos, ypos, rad;
public Circle(Color c, int x, int y, int r) {
col = c;
xpos = x;
ypos = y;
rad = r;
}
public boolean hit(int x, int y) {
return (xpos - x) * (xpos - x) + (ypos - y) * (ypos - y) <= rad
* rad;
}
public void moveTo(int x, int y) {
xpos = x;
ypos = y;
}
public void draw(Graphics g) {
g.setColor(col);
g.fillOval(xpos - rad, ypos - rad, rad * 2, rad * 2);
}
}
static class Rect implements Figure {
Color col;
int xpos, ypos, width, height;
public Rect(Color c, int x, int y, int w, int h) {
col = c;
xpos = x;
ypos = y;
width = w;
height = h;
}
public boolean hit(int x, int y) {
return xpos - width / 2 <= x && x <= xpos + width / 2
&& ypos - height / 2 <= y && y <= ypos + height / 2;
}
public void moveTo(int x, int y) {
xpos = x;
ypos = y;
}
public void draw(Graphics g) {
g.setColor(col);
g.fillRect(xpos - width / 2, ypos - height / 2, width, height);
}
}
}
5.2 継承によるくくり出しと抽象クラス
- 継承を使うとコードの共通化が行え,コードの記述量が少なくなる.また冗長なコードがなくなるので,バグが少なくなる.
- 複数のクラスの土台となることが目的で,インスタンスを生成しないクラスを抽象クラスと呼ぶ.抽象クラスを継承して作るクラスを具象クラスと呼ぶ.
- 抽象クラスとしてSimpleFigureが作成されており,その中でコンストラクタとmoveToメソッドが定義されている.hitとdrawメソッドは具象クラスで定義するようになっている.
- SimpleFigureを継承するクラスとして,CircleとRectが作成されている.
- superは親クラスを指す記述である.それがメソッドとなる場合は,親クラスのコンストラクタを表す.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
public class Sample52 extends JPanel {
ArrayList<Figure> figs = new ArrayList<Figure>();
Figure sel = null;
public Sample52() {
setOpaque(false);
figs.add(new Circle(Color.PINK, 200, 100, 40));
figs.add(new Circle(Color.GREEN, 220, 80, 30));
figs.add(new Rect(Color.YELLOW, 240, 60, 30, 40));
figs.add(new Rect(Color.BLUE, 260, 40, 80, 40));
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent evt) {
sel = pick(evt.getX(), evt.getY());
}
});
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent evt) {
if (sel == null) {
return;
}
sel.moveTo(evt.getX(), evt.getY());
repaint();
}
});
}
private Figure pick(int x, int y) {
Figure p = null;
for (Figure f : figs) {
if (f.hit(x, y)) {
p = f;
}
}
return p;
}
public void paintComponent(Graphics g) {
for (Figure f : figs) {
f.draw(g);
}
}
public static void main(String[] args) {
JFrame app = new JFrame();
app.add(new Sample52());
app.setSize(400, 300);
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setVisible(true);
}
interface Figure {
public void draw(Graphics g);
public boolean hit(int x, int y);
public void moveTo(int x, int y);
}
static abstract class SimpleFigure implements Figure {
Color col;
int xpos, ypos;
public SimpleFigure(Color c, int x, int y) {
col = c;
xpos = x;
ypos = y;
}
public void moveTo(int x, int y) {
xpos = x;
ypos = y;
}
public abstract boolean hit(int x, int y);
public abstract void draw(Graphics g);
}
static class Circle extends SimpleFigure {
int rad;
public Circle(Color c, int x, int y, int r) {
super(c, x, y);
rad = r;
}
public boolean hit(int x, int y) {
return (xpos - x) * (xpos - x) + (ypos - y) * (ypos - y) <= rad
* rad;
}
public void draw(Graphics g) {
g.setColor(col);
g.fillOval(xpos - rad, ypos - rad, rad * 2, rad * 2);
}
}
static class Rect extends SimpleFigure {
int width, height;
public Rect(Color c, int x, int y, int w, int h) {
super(c, x, y);
width = w;
height = h;
}
public boolean hit(int x, int y) {
return xpos - width / 2 <= x && x <= xpos + width / 2
&& ypos - height / 2 <= y && y <= ypos + height / 2;
}
public void draw(Graphics g) {
g.setColor(col);
g.fillRect(xpos - width / 2, ypos - height / 2, width, height);
}
}
}
5.3 型の判定と行き来
- 親クラスの変数に子クラスのオブジェクトを代入することができる.
- 親クラスの変数に入っているオブジェクトを子クラスの変数に代入するには(ダウン)キャストする.
- 型の判定にはinstanceof演算子が使える.
- 描画するオブジェクトはすべてArrayListに入っている.オブジェクトの型(マス目かどうか)判定にinstanceof演算子を用いている.
- フレーム上に文字を表示するにはdrawStringメソッドを用いる.setFontメソッドによってフォントを設定できる.
- インタフェースFigureと抽象クラスSimpleFigureは5.2のものとは異なっていることに注意.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Sample53a extends JPanel {
ArrayList<Figure> figs = new ArrayList<Figure>();
boolean turn = true;
public Sample53a() {
for (int i = 0; i < 9; ++i) {
int r = i / 3, c = i % 3;
figs.add(new Rect(Color.PINK, 80 + r * 60, 40 + c * 60, 56, 56));
}
figs.add(new Text(300, 100, "の手番", new Font("serif", Font.BOLD, 20)));
figs.add(new Batsu(300, 40, 24));
setOpaque(false);
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent evt) {
Rect r = pick(evt.getX(), evt.getY());
if (r == null) {
return;
}
figs.remove(figs.size() - 1);
if (turn) {
figs.add(new Batsu(r.getX(), r.getY(), 24));
figs.add(new Maru(300, 40, 24));
} else {
figs.add(new Maru(r.getX(), r.getY(), 24));
figs.add(new Batsu(300, 40, 24));
}
turn = !turn;
repaint();
}
});
}
public Rect pick(int x, int y) {
Rect r = null;
for (Figure f : figs) {
if (f instanceof Rect && ((Rect) f).hit(x, y)) {
r = (Rect) f;
}
}
return r;
}
public void paintComponent(Graphics g) {
for (Figure f : figs) {
f.draw(g);
}
}
public static void main(String[] args) {
JFrame app = new JFrame();
app.add(new Sample53a());
app.setSize(400, 300);
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setVisible(true);
}
interface Figure {
public void draw(Graphics g);
}
static abstract class SimpleFigure implements Figure {
int xpos, ypos;
public SimpleFigure(int x, int y) {
xpos = x;
ypos = y;
}
public void moveTo(int x, int y) {
xpos = x;
ypos = y;
}
public void draw(Graphics g) {
g.setColor(Color.BLACK);
((Graphics2D) g).setStroke(new BasicStroke(4));
}
}
static class Maru extends SimpleFigure {
int size;
public Maru(int x, int y, int s) {
super(x, y);
size = s;
}
public void draw(Graphics g) {
super.draw(g);
g.drawOval(xpos - size, ypos - size, 2 * size, 2 * size);
}
}
static class Batsu extends SimpleFigure {
int size;
public Batsu(int x, int y, int s) {
super(x, y);
size = s;
}
public void draw(Graphics g) {
super.draw(g);
g.drawLine(xpos - size, ypos - size, xpos + size, ypos + size);
g.drawLine(xpos - size, ypos + size, xpos + size, ypos - size);
}
}
static class Rect extends SimpleFigure {
Color col;
int width, height;
public Rect(Color c, int x, int y, int w, int h) {
super(x, y);
col = c;
width = w;
height = h;
}
public boolean hit(int x, int y) {
return xpos - width / 2 <= x && x <= xpos + width / 2
&& ypos - height / 2 <= y && y <= ypos + height / 2;
}
public int getX() {
return xpos;
}
public int getY() {
return ypos;
}
public void draw(Graphics g) {
g.setColor(col);
g.fillRect(xpos - width / 2, ypos - height / 2, width, height);
}
}
static class Text extends SimpleFigure {
String txt;
Font fn;
public Text(int x, int y, String t, Font f){
super(x,y);
txt = t; fn = f;
}
public void setText(String t) { txt = t; }
public void draw(Graphics g) {
super.draw(g);
g.setFont(fn);
g.drawString(txt, xpos, ypos);
}
}
}
補足:論理型変数
- Javaでは真(true)または偽(false)の値をとる論理型(boolean)変数が定義できる.
- 以下の二つのメソッドは同じ意味である.
public boolean eq1(int x) {
if(x>10) return true;
else return false;
}
public boolean eq2(int x) {
return x>10;
}
ykitamura@kwansei.ac.jp