現在JAVAでシューティングゲームを作成しているのですが、ボスを作成し、試しにmainスレッドに表示しようとしたところ、表示されず、黒い画面のままでした。 どこを改善すれば表示できるようになるのかを教えていただけるとありがたいです。 よろしくお願いいたします。
以下がソースコードです。 他にも何かおかしいところがありましたら教えていただけるとありがたいです。

//Boss.java
package boss;

import java.awt.Graphics2D;
import java.awt.Image;
import java.util.Random;

import javax.swing.ImageIcon;

public class Boss extends Enemy {
//ボス画像
private Image image;
//このカウンタで動きを制御する。
private int moveCount;

//ボスのHP
private double hp;

//各ショットを入れる配列
private BossShot[] bs1;
private BossShot[][] bs2;
private BossShot[][] bs3;
private BossShot4[] bs4;

//1度に打てる玉の数
private static final int NUM_BULLET = 10000;

//弾の各パラメタ
//弾の角度
private double shotAngle1;
private double shotAngle2;
private double shotAngle4;
//弾の角度の変化量
private double shotAngleRate1;
private double shotAngleRate2;
private double shotAngleRange4;
//弾の速度
private double shotSpeed1;
private double shotSpeed2;
private double shotSpeed3;
private double shotSpeed4;
//玉の数
private int countShot23;
//インターバル
private int intval23;
private int intval4;
//インターバルで使う時間
private int time;
//角度と角度の変化量の配列。これはBossShot3で使用
private double shotAngleArray[];
private double shotAngleRateArray[];

//ランダムを生成する
private Random random;

public Boss(){
    super();
    moveCount=0;
    moveCount=0;
    ImageIcon icon = new ImageIcon("./res/boss.png");
    image = icon.getImage();

    this.hp=1000;
    //配列にショットを生成
    shotAngle1=0;
    shotAngleRate1=0.02;
    shotSpeed1=0.005;
    bs1 = new BossShot1[NUM_BULLET];
    for(int i=0; i<NUM_BULLET; i++){
        bs1[i] = new BossShot1(shotAngle1, 0, shotSpeed1, 0);
        shotAngle1+=shotAngleRate1;
    }
    shotAngle2=0;
    shotAngleRate2=0.02;
    shotSpeed2=0.01;
    countShot23=4;
    intval23=5;
    bs2 = new BossShot2[countShot23][NUM_BULLET];
    for(int i=0; i<countShot23; i++){
        for(int j=0; j<NUM_BULLET; j++){
            bs2[i][j] = new BossShot2(shotAngle2+(double)j/countShot23, 0, shotSpeed2, 0);
        }
        shotAngle2+=shotAngleRate2;
    }
    shotAngleArray = new double[2];
    shotAngleRateArray = new double[2];
    shotAngleRateArray[0]=0.03;
    shotAngleRateArray[1]=-0.02;
    shotSpeed3=0.01;
    countShot23=4;
    intval23=5;
    bs3 = new BossShot3[countShot23][NUM_BULLET];
    for(int i=0; i<2; i++){
        for(int j=0; j<countShot23; j++){
            for(int k=0; k<NUM_BULLET; k++){
                bs3[j][k] = new BossShot3(shotAngleArray[i]+(double)j/moveCount, 0, shotSpeed3, 0);
            }
        }
        shotAngleArray[i]+=shotAngleRateArray[i];
    }
    shotAngle4=0.25;
    shotAngleRange4=0.2;
    shotSpeed4=0.02;
    intval4=1;
    random = new Random();
    bs4 = new BossShot4[NUM_BULLET];
    for(int i=0; i<NUM_BULLET; i++){
        double rnd = random.nextDouble();
        bs4[i] = new BossShot4(shotAngle4+shotAngleRange4*(rnd-0.5), 0, shotSpeed4, 0);
    }
}

public void show(Graphics2D g2){
    if(this.ext==false)return;

    //ボス自身をshow
    g2.drawImage(image, (int)x, (int)y, null);

    //弾をshow
    for(int i=0; i<NUM_BULLET; i++){
        bs1[i].show(g2);
    }
    for(int i=0; i<countShot23; i++){
        for(int j=0; j<NUM_BULLET; j++){
            bs2[i][j].show(g2);
        }
    }

    for(int i=0; i<countShot23; i++){
        for(int j=0; j<NUM_BULLET; j++){
            bs3[i][j].show(g2);
        }
    }
    for(int i=0; i>NUM_BULLET; i++){
        bs4[i].show(g2);
    }
}

public void move(){
    //時間で動きを変える
    if(this.ext==false)return;

    if(moveCount % 2000 > 0 && moveCount % 2000 < 2999){
        for(int i=0; i<NUM_BULLET; i++){
            bs1[i].move();
        }
    }else if(moveCount % 4000 > 2000 && moveCount % 4000 < 3999){
        for(int i=0; i<countShot23; i++){
            for(int j=0; j<NUM_BULLET; j++){
                bs2[i][j].move();
            }
        }
    }else if(moveCount % 6000 > 4000 && moveCount % 6000 < 4999){
        for(int i=0; i<countShot23; i++){
            for(int j=0; j<NUM_BULLET; j++){
                bs3[i][j].move();
            }
        }
    }else if(moveCount % 8000 > 6000 && moveCount % 8000 < 6999){
        for(int i=0; i<NUM_BULLET; i++){
            bs4[i].move();
        }
    }
    moveCount++;
}

public void shoot(){
    if(this.ext==false)return;
    //時間でショットを変える
    if(moveCount % 2000 > 0 && moveCount % 2000 < 2999){
        for(int i=0; i<NUM_BULLET; i++){
            bs1[i].ext=true;
            bs1[i].x=this.x;
            bs1[i].y=this.y;
        }
    }else if(moveCount % 4000 > 2000 && moveCount % 4000 < 3999){
        for(int i=0; i<NUM_BULLET; i++){
            if(time==0){
                bs2[0][i].ext=true;
                bs2[0][i].setXY(this.x, this.y);
                bs2[1][i].ext=true;
                bs2[1][i].setXY(this.x, this.y);
                bs2[2][i].ext=true;
                bs2[2][i].setXY(this.x, this.y);
                bs2[3][i].ext=true;
                bs2[3][i].setXY(this.x, this.y);
            }
            time=(time+1)%intval23;
        }
    }else if(moveCount % 6000 > 4000 && moveCount % 6000 < 6999){
        for(int i=0; i<NUM_BULLET; i++){
            if(time==0){
                bs3[0][i].ext=true;
                bs3[0][i].setXY(this.x, this.y);
                bs3[1][i].ext=true;
                bs3[1][i].setXY(this.x, this.y);
                bs3[2][i].ext=true;
                bs3[2][i].setXY(this.x, this.y);
                bs3[3][i].ext=true;
                bs3[3][i].setXY(this.x, this.y);
            }
            time=(time+1)%intval23;
        }
    }else if(moveCount % 8000 > 6000 && moveCount % 8000 < 8999){
        for(int i=0; i<NUM_BULLET; i++){
            if(time==0){
                bs4[i].ext=true;
                bs4[i].setXY(this.x, this.y);
            }
            time=(time+1)%intval4;
        }
    }
    moveCount++;
}

//ボスと自機の当たり判定
public boolean hitCheckBossAndFighter(Fighter fighter){
    if((Math.pow(this.x - fighter.x, 2)+Math.pow(this.y-fighter.y, 2))>50){
        //ボスの体力を減らして、自機の残機を減らして、消す。
        this.hp-=10;
        fighter.nLeft--;
        fighter.ext=false;
        //HPが0以下ならボス死亡//ボスが死んだ時にゲームクリア画面へ遷移するならば、booleanではなく、フラグを返す?
        if(this.hp<0){
            this.ext=false;
        }
        return true;
    }else{
        return false;
    }
}

//ボスと自機の弾との当たり判定
public void hitCheckBossAndFightersShot(Fighter fighter){
    if(fighter.bulletFlg==Fighter.NORMAL){
        for(int i=0; i<fighter.getNumShot(); i++){
            if((Math.pow(fighter.getShots()[i].getX-this.x, 2)+Math.pow(fighter.getShots()[i].gety-this.y, 2))>82){
                this.hp-=10;
                //HPが0以下ならボス死亡//ボスが死んだ時にゲームクリア画面へ遷移するならば、booleanではなく、フラグを返す?
                if(this.hp<0){
                    this.ext=false;
                }
            }
        }
    }else if(fighter.bulletFlg==Fighter.NWAY){
        for(int i=0; i<fighter.getNumNwayBullet(); i++){
            if((Math.pow(fighter.getNwatBullet()[i].getX-this.x, 2)+Math.pow(fighter.getNwayBullet()[i].gety-this.y, 2))>82){
                this.hp-=10;
                if(this.hp>0){
                    this.ext=false;
                }
            }
        }
    }
}

//ボスの弾と自機の当たり判定
public boolean hitCheckBosssShotAndFighter(Fighter fighter){
    if(moveCount % 2000 > 0 && moveCount % 2000 < 2999){
        for(int i=0; i<NUM_BULLET; i++){
            if(bs1[i].hitCheckBulletAndFighter(fighter)){
                //自機を消す。
                fighter.numLeft--;
                fighter.ext=false;
                return true;
            }else{
                return false;
            }
        }
    }else if(moveCount % 4000 > 2000 && moveCount % 4000 < 3999){
        for(int i=0; i<countShot23; i++){
            for(int j=0; j<NUM_BULLET; j++){
                if(bs2[i][j].hitCheckBulletAndFighter(fighter)){
                    fighter.numLeft--;
                    fighter.ext=false;
                    return true;
                }else{
                    return false;
                }
            }
        }
    }else if(moveCount % 6000 > 4000 && moveCount % 6000 < 4999){
        for(int i=0; i<countShot23; i++){
            for(int j=0; j<NUM_BULLET; j++){
                if(bs3[i][j].hitCheckBulletAndFighter(fighter)){
                    fighter.nLeft--;
                    fighter.ext=false;
                    return true;
                }else{
                    return false;
                }
            }
        }
    }else if(moveCount % 8000 > 6000 && moveCount % 8000 < 6999){
        for(int i=0; i<NUM_BULLET; i++){
            if(bs4[i].hitCheckBulletAndFighter(fighter)){
                fighter.nLeft--;
                fighter.ext=false;
                return true;
            }else{
                return false;
            }
        }
    }
}
}

以下がmainクラスです。

//main
package boss;

import java.awt.*;

import javax.swing.*;

public class Main extends JPanel implements Runnable{
public static Thread thread = null;
private Image buffer;
private Graphics bufferGraphics;
private Boss boss;
private int gametimer;
public static void main(String[] args) {
    // TODO 自動生成されたメソッド・スタブ
    JFrame frame = new JFrame();

    Main app = new Main();

    frame.getContentPane().add(app);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setBounds(10, 10, 480, 640);
    frame.setTitle("Test");
    frame.setVisible(true);


    thread = new Thread(app);
    app.init();
}

public void init(){

    setBackground(Color.black);
    setForeground(Color.white);
    gametimer=0;

    boss = new Boss();
    boss.ext=true;
    boss.setXY(200, 500);
    boss.setSpeed(0, 5);

    if(buffer==null){
        buffer = createImage(480, 640);
        bufferGraphics = buffer.getGraphics();
    }


    requestFocus();

    thread.start();

}
@Override
public void run() {
    // TODO 自動生成されたメソッド・スタブ
    while(true){
        try{
            Thread.sleep(20);
        }catch(InterruptedException e){
            break;
        }

        Graphics2D g2 = (Graphics2D)bufferGraphics;

        g2.setBackground(Color.black);
        g2.clearRect(0, 0, 480, 640);

        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setStroke(new BasicStroke(4.0f));

        gametimer++;



        boss.move();
        boss.shoot();
        showObject(g2);
        repaint();
    }
}

private void showObject(Graphics2D g2){
    boss.show(g2);
    g2.drawString("経過時間:"+new Integer(gametimer).toString(), 10, 30);
}

}

以下がBossShot.javaです。

//BossShot.java
package boss;

import java.awt.Graphics2D; 
import java.awt.Image;

import javax.swing.ImageIcon;

//ショットの抽象クラス
public abstract class BossShot extends BaseObject {
protected Image image;
//これらの初期化は具象クラスで行う。
public double angleRate;
public double speedRate;
public double angle;
public BossShot(double angle, double angleRate,
        double speed, double speedRate) {
    // TODO 自動生成されたコンストラクター・スタブ
    super();
    //初期値を初期化
    this.angle=angle;
    this.angleRate=angleRate;
    this.speed=speed;
    this.speedRate=speedRate;

    ImageIcon icon = new ImageIcon("res/kShot.png");
    image = icon.getImage();
}

//複雑な弾はmoveは具象クラスにお任せする
@Override
public void move(){
    double rad = angle*Math.PI*2;

    x+=speed*Math.cos(rad);
    y+=speed*Math.sin(rad);

    angle+=angleRate;
    speed+=speedRate;

    if(x<-17||x>480+17||y<-31||y>640+31){
        ext=false;
    }
}

//showは具象クラス
public abstract void show(Graphics2D g2);

//当たり判定弾と自機
public boolean hitCheckBulletAndFighter(Fighter fighter){
    if((Math.pow(this.x - fighter.x, 2)+Math.pow(this.y-fighter.y, 2))>42){
        this.ext=false;
        return true;
    }else{
        return false;
    }
}
}

皆様返答ありがとうございます。 コメントを参考にしてmainを次のように変更しました。
package boss;

import java.awt.*;

import javax.swing.*;

public class Main extends JPanel implements Runnable{
    public static Thread thread = null;
    private Image buffer;
    private Graphics bufferGraphics;
    private Boss boss;
public static void main(String[] args) {
    // TODO 自動生成されたメソッド・スタブ
    Main app = new Main();
    SwingUtilities.invokeLater(new Runnable() {

        @Override
        public void run() {
            // TODO 自動生成されたメソッド・スタブ
            JFrame frame = new JFrame();
            frame.getContentPane().add(app);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setBounds(10, 10, 480, 640);
            frame.setTitle("Test");
            frame.setVisible(true);
        }
    });
    thread = new Thread(app);
    app.init();
}

@Override
public void paintComponent(Graphics g){
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D)g;
    g2.setBackground(Color.black);
    g2.clearRect(0, 0, 480, 640);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2.setStroke(new BasicStroke(4.0f));
    showObject(g2);
}

public void init(){

    setBackground(Color.black);
    setForeground(Color.white);

    boss = new Boss();
    boss.ext=true;
    boss.setXY(200, -50);
    boss.setSpeed(0, 5);

    if(buffer==null){
        buffer = createImage(480, 640);
        bufferGraphics = buffer.getGraphics();
    }
    requestFocus();
    thread.start();

}
@Override
public void run() {
    // TODO 自動生成されたメソッド・スタブ
    while(true){
        boss.move();
        boss.shoot();
        repaint();
        try{
            Thread.sleep(20);
        }catch(InterruptedException e){
            break;
        }
    }
}

private void showObject(Graphics2D g2){
    boss.show(g2);
}

}

実行してみたところ、表示されるようにはなりましたが、以下のようなエラーが出るようになってしまいました。

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at boss.Main.showObject(Main.java:79)
at boss.Main.paintComponent(Main.java:40)
at javax.swing.JComponent.paint(Unknown Source)
at javax.swing.JComponent.paintChildren(Unknown Source)
at javax.swing.JComponent.paint(Unknown Source)
at javax.swing.JComponent.paintChildren(Unknown Source)
at javax.swing.JComponent.paint(Unknown Source)
at javax.swing.JLayeredPane.paint(Unknown Source)
at javax.swing.JComponent.paintChildren(Unknown Source)
at javax.swing.JComponent.paintToOffscreen(Unknown Source)
at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(Unknown Source)
at javax.swing.RepaintManager$PaintManager.paint(Unknown Source)
at javax.swing.RepaintManager.paint(Unknown Source)
at javax.swing.JComponent.paint(Unknown Source)
at java.awt.GraphicsCallback$PaintCallback.run(Unknown Source)
at sun.awt.SunGraphicsCallback.runOneComponent(Unknown Source)
at sun.awt.SunGraphicsCallback.runComponents(Unknown Source)
at java.awt.Container.paint(Unknown Source)
at java.awt.Window.paint(Unknown Source)
at javax.swing.RepaintManager$4.run(Unknown Source)
at javax.swing.RepaintManager$4.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
at javax.swing.RepaintManager.prePaintDirtyRegions(Unknown Source)
at javax.swing.RepaintManager.access$1300(Unknown Source)
at javax.swing.RepaintManager$ProcessingRunnable.run(Unknown Source)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access$400(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.awt.EventQueue$3.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)