[Work/Class/Java with Processing/3_ProcessingClass]

アート系学生のためのJAVA with Processing - その3-3 AbstractクラスとInterface

Abstractクラス

概要

Abstractクラスとは「抽象」クラスという意味である.

前項で継承を学んだが,例えば,一つのクラスから基本機能は共有したいが,異なった挙動を持つ2つのクラスのオブジェクトを2つ枝分かれさせたい,という状況があるとする.例えばボールなら,(1)色なしで跳ね返るボールと,(2)色付きではね返られないボール,の2種類を考える.この場合ボールというクラスを一つ用意し,継承するクラス(色無し跳ね返り有り,色付き跳ね返り無し)を2種類用意すれば良い.

ところが大元となるボールクラスは,実際には画面に表示する必要はない場合が考えられる.共通する要素は,(1)x,y座標,(2)座標を更新する関数,(3)描画を指定する関数だが,(2)の座標を更新する関数と(3)の描画を指定する関数はそれぞれのクラスで内容が異なる.というわけで実際には(1)のx,y座標のみのボールクラスは使われない.

この様な場合,Abstractクラスを用いるとすっきりまとまる.

Abstractクラスは,簡単に言えばオブジェクトインスタンスは生成できない,継承の対象となるだけのクラスである.

この場合,(1)x,y座標を保持する変数,(2)座標を更新する関数の名前(と型),(3)描画を指定する関数の名前(と型)をAbstractクラスにのみ記述し,追加で必要となる変数や,(2)や(3)の関数の処理内容は継承したクラス(色なし跳ね返り有り,色付き跳ね返り無し)で記述する.

最大のメリットは,Abstractクラスの配列を用意すれば,その中にそれを継承したクラスのオブジェクトインスタンスを全て格納できることである.

コード実例では,AbstractBallというAbstractクラスを用意し,実際に表示を行うBounceBallクラスとColoredBallクラスで継承している.それを取りまとめて各オブジェクトインスタンスの関数を実行するための配列はAbstractBallクラスの配列として宣言しており,その中に両方の子クラスのオブジェクトインスタンスが格納される.

つまり「子クラスは親クラスの配列に入れられる」ことを最大利用している.

コード

// Processing_AbstractClass.pde
// Abstractクラスの配列を用意する.
AbstractBall ballArray[];

void setup(){
  size(640, 480);
  colorMode(RGB, 255);
  frameRate(10);
  
  // Abstractクラスの配列を実際に生成
  ballArray = new AbstractBall[20];

  // 0~9まではBounceBallクラスのオブジェクトインスタンスを生成して格納
  for(int i=0; i<10; i++)
    ballArray[i] = new BounceBall(width, height);
  
  // 10~19まではColoredBallクラスのオブジェクトインスタンスを生成して格納
  for(int i=10; i<20; i++)
    ballArray[i] = new ColoredBall(width, height);
}

void draw(){
  fill(255, 255, 255, 60);
  rect(0, 0, width, height);
  
  for(int i=0; i<20; i++){
    // positionUpdate関数とdrawBall関数は,
    // AbstractBallクラス内でAbstract関数として宣言されているので,
    // 継承する子クラスはそれらの関数を必ず書かなければならない(=必ず存在する)
    // ので,10ずつ分けて処理する必要がなく,一気に20個分処理できる.
    ballArray[i].positionUpdate();
    ballArray[i].drawBall();
  }
}
// AbstractBall.pde
abstract class AbstractBall{
 int xPos, yPos; //x,y座標の変数.子クラスに受け継がれる.
 int windowWidth, windowHeight; //Windowのサイズを格納する変数
 
 AbstractBall(int windowWidth, int windowHeight){
   // Constructor コンストラクタ
   this.windowWidth = windowWidth;
   this.windowHeight = windowHeight;
   
   xPos = (int)random(windowWidth);
   yPos = (int)random(windowHeight);
 }
 
 // 頭にabstractキーワードが付いている関数は,
 // 子クラスで必ず処理内容を書かなければならない
 abstract void positionUpdate();
 abstract void drawBall();
 // が,Abstractクラス内では処理を書く必要がない.
}
// BounceBall.pde
class BounceBall extends AbstractBall{
  // AbstractBallを継承したクラス.色無し跳ね返り有り
  int xDirection, yDirection, xSpeed, ySpeed;

  // x,y座標は親クラスであるAbstractBallクラスに含まれているので書く必要はない
  // (そのまま使える)

  BounceBall(int windowWidth, int windowHeight){
   // Constructor コンストラクタ
   // Parent Class Constructor 親クラスのコンストラクタをsuper()で実行する
   super(windowWidth, windowHeight);

   // Then write initialize process for this child class only
   // その後,親クラスにはなく,このクラスに必要な初期化要素を書いていく
   if(random(100) > 50)
     xDirection = 1;
   else
     xDirection = -1;
     
   if(random(100) > 50)
     yDirection = 1;
   else
     yDirection = 1;
     
   xSpeed = (int)random(10) + 5;
   ySpeed = (int)random(10) + 5;
  }
  
  @Override
  void positionUpdate(){
    // positionUpdate関数は親クラスには処理内容は書かれていないので,
    // 子クラスに全部書く
    xPos += xDirection * xSpeed;
    yPos += yDirection * ySpeed;
    
    if(xPos > windowWidth)
      xDirection = -1;
    else if(xPos < 0)
      xDirection = 1;
      
    if(yPos > windowHeight)
      yDirection = -1;
    else if(yPos < 0)
      yDirection = 1;
  }
  
  @Override
  void drawBall(){
    // drawBall関数も同様
    fill(60, 60, 60);
    ellipse(xPos, yPos, 20, 20);
  }
  
}
// ColoredBall.pde
class ColoredBall extends AbstractBall{
  // AbstractBallを継承したもう一つのクラス.色有り跳ね返り無し.

  int rColor, gColor, bColor;
  //RGBを保持する変数は親クラスにはないので,宣言する

  ColoredBall(int windowWidth, int windowHeight){
    // Constrctor コンストラクタ
    super(windowWidth, windowHeight);

    // 親クラスのコンストラクタを実行した後,このクラス独自の初期化処理を書く
    rColor = (int)random(100) + 155;
    gColor = (int)random(100) + 155;
    bColor = (int)random(100) + 155;
  }
  
  @Override
  void positionUpdate(){
    // このクラスのオブジェクトは左下に流れるだけ(単調増加)
    xPos += 2;
    yPos += 2;
  }
  
  @Override
  void drawBall(){
     // fillにこのクラス独自のRGBを保持する変数を入れる
     fill(rColor, gColor, bColor);
     ellipse(xPos, yPos, 20, 20);
  }
}
Processing_AbstractClass.pdeの実行結果.色がなく跳ね返るBounceBallクラスオブジェクトと,色が付いて左下へ流れるだけのColoredBallクラスオブジェクトが同居している.

色がなく跳ね返るBounceBallクラスオブジェクトと,色が付いて左下へ流れるだけのColoredBallクラスオブジェクトが同居している.

Interface

概要

InterfaceとはAbstractクラスから,変数やコンストラクタ,子クラスで宣言することなく使える処理実体を持つ関数を除いたものである.つまりabstractキーワード付きの関数の名前(と型)のみが定義されたクラスのことである.

Javaでは,継承できる(親とすることができる)クラスは一つのみである.これを単一継承という.C++など他の言語では,複数の親クラスを持つことができ,その全てを継承する多重継承を行うことができるが,Javaではシンプルさを保つために多重継承は禁止されている.

しかし複数のクラスの機能を備えたクラスを設計したいことがある.そこでJavaではInterfaceというabstract関数のみで構成されたAbstractクラスの仕組みを用意し,これは複数継承することができる.

クラスの継承はextendsキーワードで行うが,Interfaceはimplementsキーワードで継承を行う.implementsとは「実装する」という意味である.Interfaceを複数継承する時にはカンマで区切って指定する.

処理の内容も書かれていない(「実装する」クラスで全て書く必要がある)abstract関数のみのクラスであるInterfaceが何の役に立つかというと,そのInterfaceを「実装した」クラスには,その関数の処理内容が必ず書かれていて,実行可能であることが保障されることである.

Abstractクラスの時も「その関数の処理実態がそのクラスには必ず存在する」ことを前提に,大元のスケッチのdraw()関数の内容を書くことができた.これが保障されるという意味である.

逆に言えば,Interfaceに存在するのに,それをimplementsしているクラスで処理内容が書かれていない(@Overrideキーワードで処理実態が書かれていない)とエラーになり,コンパイルできない.

コード

// Processing_Interface.pde
ImplementedBall ballArray[];
void setup(){
  size(640, 480);
  colorMode(RGB, 255);
  frameRate(10);
  
  ballArray = new ImplementedBall[20];
  for(int i=0; i<20; i++)
    ballArray[i] = new ImplementedBall(width, height);
}

void draw(){
  fill(255, 255, 255, 60);
  rect(0, 0, width, height);
  
  for(int i=0; i<20; i++){
    ballArray[i].positionUpdate();
    ballArray[i].bounceCheck(); 
    // ImplementedBall Class is certain to implement bounceCheck() Function
    // Thus we entrust Ball Object Instance to execute bounceCheck() Function 
    ballArray[i].drawBall();
  }
}
// AbstractBall.pde
abstract class AbstractBall{
 int xPos, yPos; 
 int windowWidth, windowHeight;
 
 AbstractBall(int windowWidth, int windowHeight){
   // Constructor
   this.windowWidth = windowWidth;
   this.windowHeight = windowHeight;
   
   xPos = (int)random(windowWidth);
   yPos = (int)random(windowHeight);
 }
 
 // Abstract Methods
 abstract void positionUpdate();
 abstract void drawBall();
}
// BounceBallChecker.pde
interface BounceBallChecker{
  void bounceCheck();
}
// ImplementedBall.pde
class ImplementedBall extends AbstractBall implements BounceBallChecker{
  int xDirection, yDirection;
  int xSpeed, ySpeed;
  
  ImplementedBall(int windowWidth, int windowHeight){
    super(windowWidth, windowHeight); 
    if(random(100) > 50)
      xDirection = 1;
    else
      xDirection = -1;
      
    if(random(100) > 50)
      yDirection = 1;
    else
      yDirection = -1;
      
    xSpeed = (int)random(10) + 5;
    ySpeed = (int)random(10) + 5;
  }

  @Override // From AbstractBall
  void positionUpdate(){
    xPos += xDirection * xSpeed;
    yPos += yDirection * ySpeed;
  }

  @Override // From AbstractBall
  void drawBall(){
    fill(60, 60, 60);
    ellipse(xPos, yPos, 20, 20);
  }
  
  @Override // From BounceBallChecker
  void bounceCheck(){
    if(xPos > windowWidth)
      xDirection = -1;
    else if(xPos < 0)
      xDirection = 1;
    
    if(yPos > windowHeight)
      yDirection = -1;
    else if(yPos < 0)
      yDirection = 1;
  }
}