Thierryxc 的博客

每天进步一点点


  • 首页

  • 归档

  • 标签

Java图形用户接口

发表于 2017-08-09

JFrame

       JFrame是个代表屏幕上window的对象。可以把button、checkbox、text字段等接口放在window上面。标准的menu也可以加到上面。

import javax.swing.*;
public class SimpleGui1 {

    public static void main(String[] args) {
        JFrame frame = new JFrame(); //创建frame
        JButton button = new JButton("click me");  //创建button

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //这一行程序会在window关闭时结束程序

        frame.getContentPane().add(button);

        frame.setSize(300, 300); //设定frame大小

        frame.setVisible(true);  //显示frame
    }

}

实现按钮功能

实现按钮功能:

  • 按钮要知道它的作用。
  • 按钮要在按键事件发生时调用执行功能的方法。

取得用户的事件

       在Java中,取得处理用户操作事件的过程称为even-handling。Java中有许多不同的事件类型,大多数都与GUI上用户的操作有关。如果用户按下了按钮,就会产生事件。

监听

       如果想要知道按钮的事件,就要监听事件的接口。
       监听接口是介于监听和事件源间的桥梁。

       Swing的GUI组件是事件的来源。以Java的术语说,事件来源是个可以将用户操作(点击鼠标、关闭窗口等)转换成事件的对象。对Java而言,事件几乎都是以对象来表示(事件类对象)。
       事件源(例如按钮)会在用户做出相关动作时(按下按钮)产生事件对象。你的程序在大多数情况下是事件的接受方而不是创建方。也就是说,你会花较多的时间当监听者而不是事件来源。
       每个事件类型都有相对应的监听者接口,想要接收MuoseEvent的话就实现Mouse Listener这个接口。记得接口的规则:要实现接口就得声明这件事,这代表你必须把接口中所有方法都实现出来。

监听和事件源的沟通

监听

       如果类想要知道按钮的ActionEvent,就要实现ActionListener这个接口。按钮需要知道你关注的部分,因此要通过调用addActionListener(this)并传入ActionListener的引用(下面的例子里就是你自己的这个程序,所以用this)来向按钮注册。按钮会在该事件发生时调用该接口上的方法。作为一个ActionListener,编译器会确保你实现此接口的actionPerformed()。

事件源

       按钮是ActionEvent的来源,因此它必须要知道有哪些对象是需要事件通知的。此按钮有个addActionListener()方法可以提供对事件有兴趣的对象(listener)一种表达此兴趣的方法。
       当按钮的addActionListener()方法被调用是(因为某个listener的调用),它的参数会被按钮存到清单中。当用户按下按钮时,按钮会通过调用清单上的每个监听的actionPerformed()来启动事件。

取得按钮的ActionEvent

  1. 实现ActionListener这个接口。
  2. 向按钮注册(告诉它你要监听事件)。
  3. 定义事件处理方法(实现接口的方法)。

import javax.swing.*;
import java.awt.event.*;
public class SimpleGui1B implements ActionListener {
    JButton button;

    public static void main(String[] args) {
        SimpleGui1B gui = new SimpleGui1B();
        gui.go();
    }

    public void go() {
        JFrame frame = new JFrame();
        button = new JButton("Click me");

        button.addActionListener(this); //向按钮注册

        frame.getContentPane().add(button);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(300, 300);
        frame.setVisible(true);
    }

    public void actionPerformed(ActionEvent event) { //实现接口中的方法,真正处理事件的方法
        button.setText("I've been clicked");
    }
}

图形

在GUI上加东西的3种方法:

  1. 在frame上放置widget:
    加上按钮、窗体、radio button 等。

  2. 在widget上绘制2D图形:
    使用graphics对象来绘制图形。

  3. 在widget上放置JPEG图

创建绘图组件

       如果要在屏幕上放上自己的图形,最好的方式是自己创建出有绘图功能的widget。把它放在frame上,就像按钮或其他widget一样,不同之处是它会按照你所要的方式绘制。

创建Jpanel的子类并覆盖掉paintComponent()这个方法

       所有绘图程序代码都在paintComponent()里面。当你的panel所处的frame显示的时候,paintComponent()就会被调用。用户不能自己调用这个方法!它的参数是个跟实际屏幕有关的Graphics对象,用户无法取得这个对象,必须交由系统来交给你。然而,还是可以调用repaint()类要求系统重新绘制显示装置,然后产生paintComponent()调用。

import java.awt.*;
import javax.swing.*;
public class MyDrawPanel extends JPanel {//创建JPanel的子类

    public void paintComponent(Graphics g) {//只能由系统调用
        g.setColor(Color.blue);

        g.fillRect(20, 50, 100, 100);
    }
        public static void main (String[] args) {
            JFrame frame = new JFrame(); //创建frame
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            MyDrawPanel s = new MyDrawPanel();
            frame.getContentPane().add(s);

            frame.setSize(300, 300);
            frame.setVisible(true);                
        }
}

其他在paintComponent()中可以做的事情

显示JPEG

import java.awt.*;
import javax.swing.*;
public class MyDrawPanel extends JPanel {//创建JPanel的子类

    public void paintComponent(Graphics g) {//只能由系统调用
        Image image = new ImageIcon("224888289124380709.jpg").getImage();//注意这里的根路径是project目录

        g.drawImage(image, 3, 4, this);
    }
        public static void main (String[] args) {
            JFrame frame = new JFrame(); //创建frame
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            MyDrawPanel s = new MyDrawPanel();
            frame.getContentPane().add(s);

            frame.setSize(300, 300);
            frame.setVisible(true);                
        }
}

在黑色背景上画随机色彩的圆圈

import java.awt.*;
import javax.swing.*;
public class MyDrawPanel extends JPanel {//创建JPanel的子类

    public void paintComponent(Graphics g) {//只能由系统调用
        g.fillRect(0, 0, this.getWidth(), this.getHeight());

        int red = (int) (Math.random() * 255);
        int green = (int) (Math.random() * 255);
        int blue = (int) (Math.random() * 255);

        Color randomColor = new Color(red, green ,blue); //随机颜色
        g.setColor(randomColor); //设定画的颜色
        g.fillOval(400, 400, 200, 200);
    }
        public static void main (String[] args) {
            JFrame frame = new JFrame(); //创建frame
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            MyDrawPanel s = new MyDrawPanel();
            frame.getContentPane().add(s);

            frame.setSize(300, 300);
            frame.setVisible(true);                
        }
}

使用Graphics2D对象

paintComponent()的参数被声明为Graphics类型:

public void paintComponent(Graphics g)

因此参数g是个Graphics对象,由于多态,g也有可能是Graphics的子类。事实上,由g参数所引用的对象实际上是个Graphics2D的实例。

如果要调用Graphics2D类的方法,就不能直接使用g参数。要将其转换成Graphics2D变量:

Graphics2D g2d = (Graphics2D) g; 

Graphics2D的方法更多:

2d

import java.awt.*;
import javax.swing.*;
public class MyDrawPanel extends JPanel {//创建JPanel的子类

    public void paintComponent(Graphics g) {//只能由系统调用
        Graphics2D g2d = (Graphics2D) g;

        int red = (int) (Math.random() * 255);
        int green = (int) (Math.random() * 255);
        int blue = (int) (Math.random() * 255);

        Color startColor = new Color(red, green ,blue);

        red = (int) (Math.random() * 255);
        green = (int) (Math.random() * 255);
        blue = (int) (Math.random() * 255);

        Color endColor = new Color(red, green ,blue);

        GradientPaint gradient = new GradientPaint(400,400, startColor,600,600, endColor);//两点之间坐标渐变
        g2d.setPaint(gradient);
        g2d.fillOval(400, 400, 200, 200);
    }
        public static void main (String[] args) {
            JFrame frame = new JFrame(); //创建frame
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            MyDrawPanel s = new MyDrawPanel();
            frame.getContentPane().add(s);

            frame.setSize(800, 800);
            frame.setVisible(true);                
        }
}

在获得事件时绘制图形

GUI布局:

frame默认有5个区域可以安置widget:

位置

按下按钮圆圈就会改变颜色

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class MySimpleGui3C implements ActionListener {
    JFrame frame;
    public static void main(String[] args) {
        MySimpleGui3C gui = new MySimpleGui3C();
        gui.go();
    }

    public void go() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JButton button = new JButton("change colors");
        button.addActionListener(this);

        MyDrawPanel s = new MyDrawPanel();
        frame.getContentPane().add(BorderLayout.SOUTH, button);//两个参数的add方法可以指定位置
        frame.getContentPane().add(BorderLayout.CENTER,s);

        frame.setSize(800, 800);
        frame.setVisible(true);        


    }

    public void actionPerformed(ActionEvent event) {
        frame.repaint();
    }

} 

内部类

一个类可以嵌套在另一个类的内部。内部类可以使用外部所有的方法与变量,就算是private的也一样。

利用内部类实现两个按钮的程序

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class TwoButton {
    JFrame frame;
    JLabel label; //必须放在方法以外,否则是局部变量不能用
    public static void main(String[] args) {
        TwoButton gui = new TwoButton();
        gui.go();
    }

    public void go() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JButton labelButton = new JButton("change label");
        labelButton.addActionListener(new LabelListener());

        JButton colorButton = new JButton("change color");
        colorButton.addActionListener(new ColorListener());

        label = new JLabel("I'm a label");
        MyDrawPanel drawPanel = new MyDrawPanel();

        frame.getContentPane().add(BorderLayout.SOUTH, colorButton);
        frame.getContentPane().add(BorderLayout.EAST, labelButton);
        frame.getContentPane().add(BorderLayout.CENTER, drawPanel);
        frame.getContentPane().add(BorderLayout.WEST, label);

        frame.setSize(800, 800);
        frame.setVisible(true);
    }

    class LabelListener implements ActionListener {//内部类,可以调用label
        public void actionPerformed(ActionEvent event) {
            label.setText("Ouch!");
        }
    }

    class ColorListener implements ActionListener {//内部类,可以调用frame
        public void actionPerformed(ActionEvent event) {
            frame.repaint();
        }
    }

}

以内部类执行动画效果

import javax.swing.*;
import java.awt.*;

public class SimpleAnimation {
    int x = 70;
    int y = 70;

    public static void main(String[] args) {
        SimpleAnimation gui = new SimpleAnimation();
        gui.go();
    }

    public void go() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        MyDrawPanel drawPanel = new MyDrawPanel();

        frame.getContentPane().add(drawPanel);
        frame.setSize(800, 800);
        frame.setVisible(true);

        for (int i = 0; i < 130; i++) {
            x++;//递增坐标值
            y++;//递增坐标值
            drawPanel.repaint();//重新绘制

            try {
                Thread.sleep(50);//加上延迟放缓过程
            } catch(Exception ex) { }
        }
    }

    class MyDrawPanel extends JPanel {
        public void paintComponent(Graphics g) {
            //去除痕迹
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, this.getWidth(), this.getHeight());

            g.setColor(Color.green);
            g.fillOval(x, y, 40, 40);//x,y是椭圆左上角坐标,40,40是高度和宽度
        }
    }

}

这样使用内部类可以让外部类实现无法继承的子类的方法。

Java异常处理

发表于 2017-08-09

异常处理

       Java的异常处理(excption-handling)机制是个简洁、轻量化的执行期间例外状况处理方式。它让你能够将处理错误状况的程序代码摆在一个容易阅读的位置。这要依赖你已经知道所调用的方法是有风险的(也就是说方法可能会产生异常),因此你可以编写出处理此可能性的程序代码。如果你知道调用某个方法可能会有异常状况,你就可以预先准备好对问题的处理程序,甚或是从错误中恢复。
       知道某个方法是否会抛出异常的方式是看该方法的声明中有没有throws语句。

try/catch块

try/catch块告诉编译器你确实知道调用的方法有风险,并且也已经准备好处理它。

流程控制

  • try中内容成功,不执行catch;
  • try中不成功,跳过try中剩下部分,直接执行catch;

finally:无论如何都要执行的部分

  • 如果try块失败了:抛出异常,流程马上转移到catch块。catch完成后执行finally块。
  • 如果try块成功:跳过catch块,执行finally块。
  • 若try或catch块有return指令,finally块还是会执行,流程会调到finally然后再回到return指令。

异常

异常是一种Exception类型的对象。
因为它是对象,所以catch住的也是对象。下面的程序代码中catch的参数是Exception类型的ex引用变量:

try {
    //危险动作

} catch(Exception ex) {
    //尝试恢复

}

写在catch块中的程序必定与所抛出的异常有关。

抛出异常

当你的程序代码调用有风险的方法时(也就是声明有异常的方法),就是该方法抛出异常。

有风险、会抛出异常的程序代码:

public void takeRisk() throws BadException { //必须要声明它会抛出BadException
    if (abandonAllHope) {
        throw new BadException(); //创建Exception对象并抛出
    }
}

调用该方法的程序代码:

public void crossFingers() {
    try {
        anObject.takeRisk();
    } catch (BadException ex) {
        System.out.println("Aaargh!");
        ex.printStackTrace(); //调用此方法列出有用的信息
        }
}

方法可以抓住其他方法所抛出的异常。异常总是丢回给调用方。
会抛出异常的方法必须要声明它有可能会这么做。

抛出一个以上异常

方法可以抛出多个异常,但在该方法的声明里必须要含有全部可能的检查异常(若两个或两个以上的异常有共同的父类时,可以只声明该父类):

public class Laundry {
    public void doLaundry() throws PantsException, LingerieException {
        //有可能抛出两个异常的程序代码

    }
}

public class Foo {
    public void go() {
        Laundry laundry = new Laundry();
        try {
            laundry.doLaundry();
        } catch(PantsException pex) {
            //恢复程序代码
        } catch(LingerieException lex) {
            //恢复程序代码
        }
    }
}

异常也是多态的

如果处理各子类型异常方式不同,要单独编写,如果相同,则可以编写一个父类型的处理方式。

在处理多态问题时要把子类异常写在前面,因为一旦程序catch了父类异常,就不会再管其他子类异常。

不想处理异常

方法:继续抛出异常

duck

小结:两种处理异常的方式

小结

异常处理规则

  1. catch与finally不能没有try;
  2. try与catch之间不能有程序;
  3. try一定要有catch或finally;
  4. 只带有finally的try必须要声明异常;

Java日期和时间

发表于 2017-08-08

日期和时间

取得当前的日期用Date

import java.util.Date;
    public class DateTest {

        public static void main(String[] args) {
            //完整的日期与时间:%tc
            String s = String.format("%tc", new Date());
            System.out.println(s);

            //只有时间:%tr
            s = String.format("%tr", new Date());
            System.out.println(s);

            //周、月、日:%tA %tB %td
            Date today = new Date();
            s = String.format("%tA %tB %td", today, today, today);
            System.out.println(s);

            //不用重复给参数
            s = String.format("%tA %<tB %<td", today);
            System.out.println(s);
        }

}

输出:

Date

操作日期用Calendar

取得继承过Calendar的对象

Calendar的实例无法取得,但是可以取得它的具体子类的实例:

Calendar cal = Calendar.getInstance();

运用Calendar对象

几个关键概念:

  • 字段会保存状态:Calendar对象使用许多字段来表示某些事物的最终状态,也就是日期和时间。
  • 日期和时间可以运算:Calendar的方法能够让你对不同的字段做加法或减法的运算,比如说对month字段加一个月。
  • 日期与时间可以用millisecond来表示:Calendar可以让你将日期转换成微秒的表示法,或将微秒转换成日期。(相对于1970.1.1的微秒数),因此可以执行精确的相对计算。

运用Calendar对象:

import java.util.Calendar;
public class CalendarTest {

    public static void main(String[] args) {
        Calendar c = Calendar.getInstance();
        c.set(2017,8,8,16,27);
        long day1 = c.getTimeInMillis();
        day1 += 1000 * 60 * 60;
        c.setTimeInMillis(day1);
        System.out.println("New hour " + c.get(c.HOUR_OF_DAY));
        c.add(c.DATE, 35);
        System.out.println("add 35 days " + c.getTime());
        c.roll(c.DATE, 35);
        System.out.println("roll 35 days " + c.getTime());    
        c.set(c.DATE, 1);
        System.out.println("set to 1 " + c.getTime());

    }

}

输出:

Calendar

常用的Calendar方法、字段

Java静态

发表于 2017-08-08

静态方法

Java是面向对象的,但若处于某种特殊情况下(通常是实用方法),则不需要类的实例。static这个关键词可以标记出不需类实例的方法。一个静态的方法就是不需要实例变量、不需要对象的行为。

带有静态方法的含义

带有静态方法的类通常不打算要被初始化。
想要不让类被初始化可以有两种途径:

  • 用abstract标记类。抽象的类不能被初始化。
  • 用私有的构造函数来限制非抽象类被初始化(Math防止被初始化的方法)。

Math类

Math方法不需要创建Math实例就可使用,我们用的是类本身。

Math

静态变量

静态变量的值对所有实例来说都相同,静态变量是被同类的所有实例共享的变量。
静态变量会在该类的任何静态方法执行前就初始化。(默认值与实例变量相同)


public class Duck {
    private int size;
    public static int duckCount = 0;

    public Duck() {
        duckCount++;
        size = 20;
    }


    public static void main(String[] args) {
        System.out.println(Duck.duckCount);//实例创建前
        Duck d1 = new Duck();

        //类和类的实例共享静态变量

        System.out.println(d1.duckCount);
        Duck d2 = new Duck();
        System.out.println(Duck.duckCount);
        Duck d3= new Duck();
        System.out.println(d2.duckCount);
    }
}

输出:

0
1
2
3

静态的final变量是常量

一个被标记为final的变量代表它一旦被初始化之后就不会改动,也就是说类加载之后静态final变量就一直维持原值。以Math.PI为例:

public static final double PI = 3.141592653589793;
  • 此变量被标记为public,因此可供各方读取。
  • 此变量被标记为static,不需要Math实例。
  • 此变量被标记为final,其值不可改变。

静态final变量的初始化:

静态final变量必须人工初始化。

声明的时候

public class Foo {
    public static final int FOO_X = 20;
}

在静态初始化程序中

静态初始化程序是一段在加载类时会执行的程序代码,它会在其它程序可以调用该类之前就执行,所以很适合放静态final变量的起始程序。

public class Foo {
    public static final int FOO_X;
    static {//这段程序在类被加载时执行
        FOO_X = 20;
    }
}

非静态final变量

  • final的变量代表不能改变它的值。
  • final的方法不能被覆盖。
  • final的类不能被继承。

Java构造函数

发表于 2017-08-07

栈与堆

  • 栈与堆是两种Java使用的内存空间;
  • 实例变量是声明在类中方法之外的地方;
  • 局部变量声明在方法或方法的参数上;
  • 所有局部变量都存在于栈上相对应的堆栈块中;
  • 对象引用变量与primitive主数据类型变量都放在栈上;
  • 不管是实例变量或局部变量,对象本身都会在堆上;

构造函数

  • 构造函数带有你在初始化对象时会执行的程序代码。也就是新建一个对象时就会被执行。
  • 就算没有自己写构造函数,编译器也会帮你写一个(无参数,无内容的)。

public class Duck {
    int size;

    public Duck() { //没有返回类型
        size = 27; //使用默认值
    }

    public Duck(int duckSize) {
        size = duckSize;//使用参数设定
    }

    public static void main(String[] args) {
        Duck d1 = new Duck();
        Duck d2 = new Duck(24);

        System.out.println("The size of d1 is " + d1.size);
        System.out.println("The size of d2 is " + d2.size);

    }
}

输出:

duck

这个构造函数是重载的。

重载构造函数的意思是代表你有一个以上的构造函数且参数都不相同。(编译器只关注参数的类型和顺序,参数的名字不重要)

要点

  • 实例变量保存在所属的对象中,位于堆上。
  • 如果实例变量是个对对象的引用,则引用与对象都在堆上。
  • 构造函数是在新建对象时执行的程序代码。
  • 构造函数必须与类同名且没有返回类型。
  • 可以用构造函数来初始化被创建对象的状态。
  • 如果不写构造函数,编译器会自己创建一个,默认的构造函数没有参数。
  • 实例变量有默认值,原始的是0/0.0/false,引用的默认值是null。

父类和继承与构造函数的关系

在创建新对象时,所有继承下来的构造函数都会执行。构造函数在执行的时候,第一件事是去执行他的父类的构造函数,一直连锁反映到Object类为止。

无参数的父类构造函数调用


public class Animal {
    public Animal() {
        System.out.println("Making an animal.");
    }
}

public class Hippo extends Animal {
    public Hippo() {
        System.out.println("Making an Hippo.");
    }
}

public class TestHippo {
    public static void main(String[] args) {
        Hippo h = new Hippo();
    }
}

super

上面的例子是调用无参数的父类构造函数,可以交给编译器自行完成。

有参数的父类构造函数调用


public  abstract class Animal {
    private String name;

    public String getName() {
        return name;
    }
    public Animal(String theName) {
        name = theName;
    }
}

public class Hippo extends Animal {
    public Hippo(String name) {//这个构造函数要求名称
        super(name);//传给Animal的构造函数

    }
}

public class TestHippo {

    public static void main(String[] args) {
        Hippo h = new Hippo("Buffy");
        System.out.println(h.getName());
    }

}

输出:

Buffy

this()函数

  • 使用this()来从某个构造函数中调用同一个类的另外一个构造函数。
  • this()只能用在构造函数中,且必须是第一行语句。
  • super()与this()不能同时用。每个构造函数可以选择调用super()或this(),但不能同时调用。

Java继承、接口与多态

发表于 2017-08-04

继承

要点

  • 子类是extends父类出来的。
  • 子类会继承父类所有public类型的实例变量和方法,但不会继承父类任何的private类型的实例变量和方法。
  • 继承下来的方法可以被覆盖掉,但实例变量不能被覆盖。即是某个方法在子类中被覆盖过,调用时会使用覆盖过的版本。如果覆盖后想调用父类的方法,就要用super.method()。

继承的意义

  • 通过提取出类间共同的抽象性,可以排除重复的程序代码而将这个部分放在父类中。这样如果有共同的部分需要改动,就只会有一个地方改动,所有子类共享。
  • 继承可以确保某个父型之下的所有类都会有父型所持有的全部(可遗传的)方法,即通过继承来定义相关类间的共同协议。

多态

回顾:一般声明引用和创建对象的方法

多态1

多态的运用

多态2

运用多态时,引用类型可以是实际对象的父类。

在方法中,参数的引用类型也可以是实际参数的父类。

综上所述,通过多态,可以编写出引进新型子类也不必修改的程序。

覆盖的规则

要覆盖父类的方法时:

  • 参数必须要一样,且返回类型必须要兼容
  • 不能降低方法的存取权限

方法的重载

重载只是刚好有相同名字的不同方法,与继承和多态无关。

接口和多态

抽象类和抽象方法

声明抽象类

abstarct public class Canine extends Animal
{
    public void roam() { }
}

public class MakeCanine {
    public void go() {
        Canine c;  //这是可以的,使用抽象类型来声明为引用类型给多态使用
        c = new dog();
        c = new Canine();//编译器不通过:无法创建出抽象类的实例
        c.roam() 
    }
}

抽象类除了被继承过之外,没有用途、没有值、没有目的。

抽象的方法

可以将方法标记为abstract,抽象的类表明该类一定被继承过,抽象的方法一定要被覆盖过。

抽象的方法没有实体。

public abstract void eat();

如果在一个类中声明了任何一个抽象的方法,就必须将该类也标记成抽象的。

Object类

Object类是所有类的源头,它是所有类的父类。

Object类中带有的部分方法

Object方法

使用Object多态

ArrayList<Object> myDogArrayList = new ArrayList<Object>();
Dog aDog = new Dog();
myDogArrayList.add(aDog);

Dog d = myDogArrayList.get(0) //无法通过编译!!!不管add进去的值是什么类型的,从ArrayList<Object>取出的值引用类型均为Object!!!  

接口(类似于纯抽象类)

接口的定义

public interface Pet {
    public abstract void beFriendly();
    public abstract void Play();
}

接口的实现

public class Dog extends Canine implement Pet {
    public void beFriendly() {……} //必须实现出Pet的方法
    public  void Play(){……} //必须实现出Pet的方法

    ……
}

接口的意义

接口

意义还是在于多态,用接口取代具体的子类或抽象的父类作为参数或返回类型,则可以传入任何实现该接口的东西,使用接口可以继承超过一个以上的来源。

Java编写程序(2)

发表于 2017-08-03

问题回顾

bug

在简单版本的程序中,玩家只要猜中任意一格,计数器就+1,而没有考虑是否这一格是否被猜中过。

使用ArrayList

arraylist

游戏的完全版

需要改变的类

  • DotCom类要增加名称变量来区别不同的网站;
  • 游戏的类(DotComBust):要创建三个DotCom并指定他们的名称、将DotCom放在方阵上(GameHelper实现)、每次猜测检查3个DotCom、击沉3个DotCom后才结束游戏、脱离main();
  • 辅助性类;

DotCom类

import java.util.*;
public class DotCom {
    private ArrayList<String> locationCells; //保存位置的ArrayList
    private String name;

    public void setLocationCells(ArrayList<String> loc) { //更新位置的方法
        locationCells = loc;
    }

    public void setName(String n) {
        name = n;
    }

    public String checkYourself(String userInput) {
        String result = "miss";
        int index = locationCells.indexOf(userInput);//把玩家输入坐标的下标赋给index,没找到返回-1
        if (index >= 0) {
            locationCells.remove(index);


            if (locationCells.isEmpty()) {
                result = "kill";
                System.out.println("Oh! You sunk " + name);
            } else {
                result = "hit";
             }    
        }    
        return result;
    }
}

DotComBust类的伪码

声明GameHelper实例helper;
声明一个ArrayList实例dotComList来放置三个网站;
声明一个int类型的变量NumOfGuess来记录玩家猜测次数,初始化为0;

声明setUpGame()方法:创建并初始化三个DotCom实例,并分别取名,放置到方阵上,给玩家一个简短的介绍;
声明startPlaying()方法:重复询问并获取玩家猜测,调用checkUserGuess()方法,直到所有DotCom被移除;
声明checkUserGuess()方法:遍历所有DotCom实例,并让他们调用自身checkYourself()方法;
声明finishGame()方法:基于猜测次数输出玩家成绩;

void setUpGame()
    创建三个DotCom实例;
    分别命名;
    把它们加入dotComList;
    遍历dotComList:
        调用placeDotCom()方法(helper实例中),初始化三个网站位置;
        用DotCom的setLocationCells()设定位置;

void start playing()
    while (dotComList中还有元素):
        调用helper中的getUserInput()方法获取玩家猜测;
        调用checkUserGuess()方法验证猜测;

void checkUserGuess()
    numOfGuess++;
    声明并设置局部变量result为"miss";
    遍历dotComList:
        调用checkYourself()方法来验证玩家猜测;
        在需要的情况下改变result为"hit"或"kill";
        if (result == "kill")
                从dotComList移除该实例;
    显示result值;

void finishGame()
    显示gameover
    if 猜测数少:
        祝贺的信息
    else:
        继续努力的信息

编写测试方法的代码

最重要的方法是checkUserGuess():初始化一个网站,假定网站位置和用户猜测来测试方法。
其次是初始化的setUpGame():可以打印出设置的信息来查看(在DotCom类中增加方法)。
剩余两个方法实现比较简单,个人认为不需要专门的测试。


下面是自己写的测试代码:

import java.util.*;
public class MethodTestDrive {

    public static void main(String[] args) {
        System.out.println("Testing setUpGame()...");
            DotComBust game1 = new DotComBust();
        game1.setUpGames();
        for (DotCom x : game1.dotComsList) {
            System.out.println(x.getName() + " postion: " + x.getLocationCells());
        }
        System.out.println("Testing chekUserGuess()...");
        DotComBust game2 = new DotComBust();
        DotCom d1 = new DotCom();
        d1.setName("163.com");
        ArrayList<String> pos = new ArrayList<String>();
        pos.add("C5");
        pos.add("C6");
        pos.add("C7");
        d1.setLocationCells(pos);
        game2.dotComsList.add(d1);
        System.out.println("Result should be \"miss\" \"hit\" \"hit\" \"kill\" ");
        game2.checkUserGuess("C4");
        game2.checkUserGuess("C5");
        game2.checkUserGuess("C6");
        game2.checkUserGuess("C7");
        System.out.println("Number of guesses: " + game2.numOfGuesses);

    }

}

类的实现

import java.util.*;

public class DotComBust {
    private GameHelper helper = new GameHelper();
    private ArrayList<DotCom> dotComsList = new ArrayList<DotCom>();
    private int numOfGuesses = 0;

    private void setUpGames() {
        DotCom one = new DotCom();
        one.setName("Pets.com");
        DotCom two = new DotCom();
        two.setName("qq.com");
        DotCom three = new DotCom();
        three.setName("163.com");
        dotComsList.add(one);
        dotComsList.add(two);
        dotComsList.add(three);

        System.out.println("共有三个网站,尝试用最少的次数打掉他们!");

        for (DotCom dotComToSet : dotComsList) {
            ArrayList<String> newLocation = helper.placeDotCom(3);

            dotComToSet.setLocationCells(newLocation);
        }
    }

    private void startPlaying() {
        while (!dotComsList.isEmpty()) {
            String userGuess = helper.getUserInput("Enter a guess");
            checkUserGuess(userGuess);
        }

        finishGame();
    }

    private void checkUserGuess(String userGuess) {
        numOfGuesses++;
        String result = "miss";
        for (DotCom dotComToTest : dotComsList) {
            result = dotComToTest.checkYourself(userGuess);
            if (result.equals("hit")) {
                break;
            }
            if (result.equals("kill")) {
                dotComsList.remove(dotComToTest);
                break;
            }

        }
        System.out.println(result);
    }    

    private void finishGame() {
        System.out.println("你打掉了所有网站!");
        if (numOfGuesses <= 18) {
            System.out.println("你只用了" + numOfGuesses + "次!" );
        } else {
            System.out.println("你用了" + numOfGuesses + "次!次数太多了" );
        }

    }

    public static void main(String[] args) {
        DotComBust game = new DotComBust();
        game.setUpGames();
        game.startPlaying();
    }
}
  • 程序中除了main都是private,很好地保护了数据。

GameHelper

import java.io.*;
import java.util.*;

public class GameHelper {

  private static final String alphabet = "abcdefg";
  private int gridLength = 7;
  private int gridSize = 49;
  private int [] grid = new int[gridSize];
  private int comCount = 0;


  public String getUserInput(String prompt) {
     String inputLine = null;
     System.out.print(prompt + "  ");
     try {
       BufferedReader is = new BufferedReader(
     new InputStreamReader(System.in));
       inputLine = is.readLine();
       if (inputLine.length() == 0 )  return null; 
     } catch (IOException e) {
       System.out.println("IOException: " + e);
     }
     return inputLine.toLowerCase();
  }



  public ArrayList<String> placeDotCom(int comSize) {                 // line 19
    ArrayList<String> alphaCells = new ArrayList<String>();
    String [] alphacoords = new String [comSize];      // holds 'f6' type coords
    String temp = null;                                // temporary String for concat
    int [] coords = new int[comSize];                  // current candidate coords
    int attempts = 0;                                  // current attempts counter
    boolean success = false;                           // flag = found a good location ?
    int location = 0;                                  // current starting location

    comCount++;                                        // nth dot com to place
    int incr = 1;                                      // set horizontal increment
    if ((comCount % 2) == 1) {                         // if odd dot com  (place     vertically)
          incr = gridLength;                               // set vertical increment
    }

    while ( !success & attempts++ < 200 ) {             // main search loop  (32)
        location = (int) (Math.random() * gridSize);      // get random starting point
        //System.out.print(" try " + location);
        int x = 0;                                        // nth position in dotcom to place
        success = true;                                 // assume success
        while (success && x < comSize) {                // look for adjacent unused spots
          if (grid[location] == 0) {                    // if not already used
             coords[x++] = location;                    // save location
             location += incr;                          // try 'next' adjacent
             if (location >= gridSize){                 // out of bounds - 'bottom'
               success = false;                         // failure
             }
             if (x>0 & (location % gridLength == 0)) {  // out of bounds - right edge
               success = false;                         // failure
             }
          } else {                                      // found already used location
              // System.out.print(" used " + location);  
              success = false;                          // failure
          }
        }
    }                                                   // end while

    int x = 0;                                          // turn good location into     alpha coords
    int row = 0;
    int column = 0;
    // System.out.println("\n");
    while (x < comSize) {
      grid[coords[x]] = 1;                              // mark master grid pts. as     'used'
      row = (int) (coords[x] / gridLength);             // get row value
      column = coords[x] % gridLength;                  // get numeric column value
      temp = String.valueOf(alphabet.charAt(column));   // convert to alpha

      alphaCells.add(temp.concat(Integer.toString(row)));
      x++;

      // System.out.print("  coord "+x+" = " + alphaCells.get(x-1));

    }
    // System.out.println("\n");

    return alphaCells;
  }
}

Java编写程序(1)

发表于 2017-08-03

编写一个程序的流程

程序概述

棋盘类战舰游戏,猜测对方战舰的坐标,然后轮流开炮攻击,命中数发就可以打沉战舰。
用网站名代替战舰:

  • 游戏目标:以最少的猜测次数打掉计算机所安排的网站。
  • 初始设置:计算机在虚拟的7*7方格上安排3个网站。安排完成后,游戏要求玩家开始猜坐标。
  • 进行游戏:玩家输入坐标,计算机反馈”miss”(未命中)、”hit”(命中)或”kill”(击沉)等回应。当玩家打掉所有网站时,游戏计算分数并结束。

游戏示意

设计流程

高层设计

首先要了解游戏流程:

游戏流程

流程图

了解了游戏流程后,下面要设想需要哪些对象。要用面向对象的方式来思考;专注于程序中出现的事物而不是过程。

简单的开始

至少需要两个类:Game类和DotCom类。
先从一个简单版本开始开发:只使用横列,只设置一个网站。
简单版也需要具有完全版的基本功能,是开发完全版的踏脚石。

开发类

方法:

  • 找出类应该做的事;
  • 列出实例变量和方法;
  • 编写方法的伪码;
  • 编写方法的测试用程序;
  • 实现类;
  • 测试方法;
  • 除错或重新设计。

编写伪码

伪码是用近似编程的语言对程序进行描述,Java伪码大致有三个部分:

  1. 实例变量的声明;
  2. 方法的声明:
  3. 方法的逻辑(最重要)
SimpleDotCom类的伪码
声明 一个int数组loctionCells来存储位置信息。
声明 一个int类型的数字变量numOfHits来记录打中的数目,初始化为0。

声明 checkYourself()方法,接收一个String类型输入(玩家猜测),与网站坐标比对后返回"hit","miss"或"kill"。
声明 setLocationCells()方法来获得网站的位置信息(接收一个i内含3个元素的int类型数组)

String checkYourself(String userGuess)
    接受玩家输入(字符串形式)
    将字符串转换成int
    遍历loctionCells存储的坐标
        比较玩家的猜测和loctionCells存储的坐标
        if 两者相同
            numOfHits++
            观察玩家的猜测是否是loctionCells中最后一个坐标元素
            if numOfHits是3,返回"kill",跳出循环。
            else 返回"hit"
        else
            没猜中,返回"miss"

void setLocationCells(int[] cellLocations)
    接受一个int类型数组作为输入
    将实例变量loctionCells设为输入的数组

编写测试方法用的程序代码

目的:更容易更快的写出程序代码。

对于SimpleDotCom类来说,主要需要测试的就是checkYourself()这个方法,这就需要能创建并初始化一个SimpleDotCom类的对象,然后给它一个初始坐标(setLocationCells()),列出结果来观察是否正确。

SimpleDotCom的测试码
public class SimpleDotComTestDrive {

    public static void main(String[] args) {
        SimpleDotCom dot = new SimpleDotCom();//初始化一个对象

        int[] locations = {2,3,4};
        dot.setLocationCells(locations);//调用setter

        String userGuess = "2";//假的猜测
        String result = dot.checkYourself(userGuess);//调用被测方法并传入假的数据    
    }    

}  

实现类

public class SimpleDotCom {
    int[] locationCells;
    int numOfHits = 0;

    public void setLocationCells(int[] locs) {
        locationCells = locs;
    }

    public String checkYourself(String stringGuess) {
        int guess = Integer.parseInt(stringGuess);//字符串变为int
        String result = "miss";
        for (int cell : locationCells) {
            if (guess == cell) {
                result = "hit";
                numOfHits++;
                break;
            }
        }

        if (numOfHits == locationCells.length) {
            result = "kill";
        }
        System.out.println(result);
        return result;
    }
}

编写SimpleDotComGame类的伪码

public static void main (String[] args)
    声明int类型变量numOfGuess来储存玩家猜测次数,初始化为0
    初始化一个SimpleDotCom实例
    获取0-4的一个随机数来作为网站的第一个位置坐标
    声明一个3元素int数组来存放生成的随机位置(由上面获得的随机数递增)
    将位置用setter赋给SimpleDotCom实例
    声明一个boolean变量isAlive来控制游戏进程,初始化为true
    while (isAlive == true):
        获取用户输入
        调用checkYourself()方法
        numOfGuess++
        if (result是"kill")
                isAlive设为false
                打印numOfGuess

游戏的main()方法

public class SimpleDotComGame {

    public static void main(String[] args) {
        int numOfGuess = 0;

        GameHelper helper = new GameHelper();

        SimpleDotCom theDotCom = new SimpleDotCom();
        int randomNum = (int) (Math.random() * 5);

        int[] locations = {randomNum, randomNum+1, randomNum+2};
        theDotCom.setLocationCells(locations);
        boolean isAlive = true;

        while (isAlive == true) {
            String guess = helper.getUserInput("enter a number");
            String result = theDotCom.checkYourself(guess);
            numOfGuess++;
            if (result.equals("kill")) {
                isAlive = false;
                System.out.println("You took " + numOfGuess + " guesses");
            }
        }

    }

}

GameHelper

import java.io.*;
public class GameHelper {
    public String getUserInput(String prompt) {
        String inputline = null;
        System.out.print(prompt + "  ");
        try {
            BufferedReader is = new BufferedReader(new InputStreamReader(System.in));
                inputline = is.readLine();
            if (inputline.length() ==  0)  return null;
        } catch (IOException e) {
            System.out.println("IOException: " + e);
        }
        return inputline;
    }
}

一个bug(下篇文章处理)

bug

新知识点

  • 使用Interger.parseInt()来取得Ttring的整数值;
  • str.equal(str2)来比较两个字符串;

Java封装

发表于 2017-08-02

Java封装

为什么要封装?

数据如果不封装会暴露实例变量,导致泄露资料,或被人恶意修改。
暴露的意思是可通过圆点运算符来存取,例如:

dog.size = 80;  

别人可以把值修改为任意值,不安全。

保护数据的方法:数据隐藏

使用public和private这两个存取修饰符。
封装的基本原则:

  1. 将实例变量标记为私有(private);
  2. 提供公有(public)的方法来控制存取动作。
//GoodDog.java  封装
public class GoodDog {
    private int size; //不能通过点运算符修改

    public int getSize() {
        return size;
    }

    public void setSize(int s) {
        size = s;
    }

    void bark() {
        if (size > 60) {
            System.out.println("Woof!Woof!");
        } else if (size > 14) {
            System.out.println("Ruff!Ruff!");
        } else {
            System.out.println("Yip!Yip!");
        }
    }

}

public class GoodDogTest {

    public static void main(String[] args) {
        GoodDog one = new GoodDog();
        one.setSize(70);

        GoodDog two = new GoodDog();
        two.setSize(8);

        System.out.println("Dog one: " + one.getSize());
        System.out.println("Dog two: " + two.getSize());

        one.bark();
        two.bark();
    }

}    

Java对象引用

发表于 2017-08-02

Java对象引用

对象引用

public class Dog {
    String name;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Dog dog1 = new Dog();
        dog1.bark();
        dog1.name = "Bart";

        //创建Dog数组
        Dog[] myDogs = new Dog[3];

        myDogs[0] = new Dog();
        myDogs[1] = new Dog();
        myDogs[2] = dog1;

        myDogs[0].name = "Fred";
        myDogs[1].name = "Marge";

        System.out.print("Last dog's name is ");
        System.out.println(myDogs[2].name);

        int x = 0;
        while (x < myDogs.length) {
            myDogs[x].bark();
            x = x + 1;
        }

        dog1.name = "Bob";
        dog1.bark();
        myDogs[2].bark();

    }

    public void bark() {
        System.out.println(name + " says ruff!");
    }

}    

程序运行结果:
运行结果

  • 对象引用类似于指针,声明的类型不可改变;
  • myDogs[2] = dog1;两个变量指向同一个Dog对象,改变其中一个,另一个的值也会变;
123
Xu Chen

Xu Chen

programming self-learner

29 日志
7 标签
© 2017 Xu Chen
由 Hexo 强力驱动
主题 - NexT.Pisces