1. 程式人生 > >201771010101 白瑪次仁 《2018面向對象程序設計(Java)》第十六周學習總結

201771010101 白瑪次仁 《2018面向對象程序設計(Java)》第十六周學習總結

eth flow tcl mac delay buffered pub erro utm

實驗十六 線程技術

實驗時間 2017-12-8

1.學習總結:

1.程序 是一段靜態的代碼,它應用程序執行藍 是一段靜態的代碼,它應用程序執行藍 本。
2.進程 是程序的一次動態執行,它對應了從代碼加 是程序的一次動態執行,,它對應了從代碼加 載、執行至完畢的一個整過程。

3.操作系統為每個進程分配一段獨立的內存空間和 系統資源,包括:代碼數據以及堆棧等。每 系統資源,包括:代碼數據以及堆棧等。每 一個進程的內部數據和狀態都是完全獨立。
4.多任務操作系統 中,進程切換對 CPU 資源消耗較 大。多線程 是進程執行過中產生的多條線索。 線程是比進執行更小的單位。線程不能獨立存在,必須於進 中同一程的各線間共享進空數據。 程的各線共享進空數據。每個線程有它自身的產生、存在和消亡過, 是一個動態的概念。
5.Java 實現多線程有兩種途徑:
1.創建 Thread 類的子

2.在程序中定義實現 Runnable 接口的類

6.首先需從 Thread 類派生出一個子, 在該子類中 重寫 run()方法。

首先設計一個實現 Runnable 接口的類; 接口的類; 接口的類; 接口的類; 接口的類;
7.? 用Runnable() 接口實現線程

1.首先設計一個實現 Runnable 接口的類

2.然後在類中根據需要重寫 run 方法;

3再創建該類對象,以此為參數立 再創建該類對象,以此為參數立 Thread類的對象;

4.調用 Thread 類對象的 start 方法啟動線程,將 CPU 執行權轉交到 run 方法。
8.當線程的 run 方法執行體中最後 一條語句方法執行體中最後 一條語句, 或者出現了在 run 方法中沒有捕獲的異常時 ,線 程將終止 ,讓出 CPU 使用權 。

9.利用各線程的狀態變換,可以控制個輪流 利用各線程的狀態變換, 使用 CPU ,體現多線程的 並行性特征。

10.線程的終止:當線程的run方法執行方法體中最後一條語句後, 或者出現了在run方法中沒有捕獲的異常時,線 程將終止,讓出CPU使用權。調用interrupt()方法也可終止線程。

11.線程的狀態:利用各線程的狀態變換,可以控制各個線程輪流 使用CPU,體現多線程的並行性特征。線程有如下7種狀態: New (新建) 、Runnable (可運行) 、Running(運行) 、Blocked (被阻塞) 、Waiting (等待) 、Timed waiting (計時等待) 、Terminated (被終止)

12.調用setPriority(int a)重置當前線程的優先級, a 取值可以是前述的三個靜態量。

2、實驗目的與要求

(1) 掌握線程概念;

(2) 掌握線程創建的兩種技術;

(3) 理解和掌握線程的優先級屬性及調度方法;

(4) 掌握線程同步的概念及實現技術;

3、實驗內容和步驟

實驗1:測試程序並進行代碼註釋。

測試程序1:

l 在elipse IDE中調試運行ThreadTest,結合程序運行結果理解程序;

l 掌握線程概念;

l 掌握用Thread的擴展類實現線程的方法;

l 利用Runnable接口改造程序,掌握用Runnable接口創建線程的方法。

class Lefthand extends Thread { 
 2    public void run()
 3    {
 4        for(int i=0;i<=5;i++)
 5        {  System.out.println("You are Students!");
 6            try{   sleep(500);   }
 7            catch(InterruptedException e)
 8            { System.out.println("Lefthand error.");}    
 9        } 
10   } 
11 }
12 class Righthand extends Thread {
13     public void run()
14     {
15          for(int i=0;i<=5;i++)
16          {   System.out.println("I am a Teacher!");
17              try{  sleep(300);  }
18              catch(InterruptedException e)
19              { System.out.println("Righthand error.");}
20          }
21     }
22 }
23 public class ThreadTest 
24 {
25      static Lefthand left;
26      static Righthand right;
27      public static void main(String[] args)
28      {     left=new Lefthand();
29            right=new Righthand();
30            left.start();
31            right.start();
32      }
33 }

package gfhc;
class Lefthand implements Runnable{ 
       public void run()
       {
           for(int i=0;i<=5;i++)
           {  System.out.println("You are Students!");
               try{Thread.sleep(500);   }
               catch(InterruptedException e)
               { System.out.println("Lefthand error.");}    
           } 
      }

    
    
    }
    class Righthand implements Runnable {
        public void run()
        {
             for(int i=0;i<=5;i++)
             {   System.out.println("I am a Teacher!");
                 try{  Thread.sleep(300);  }
                 catch(InterruptedException e)
                 { System.out.println("Righthand error.");}
             }
             Lefthand left=new Lefthand ();
             Righthand right=new Righthand ();
        }

        
        }
    
    public class fgyukg 
    {
         static Lefthand left;
         static Righthand right;
         public static void main(String[] args)
         {    // left=new Lefthand();
               //right=new Righthand();
              // left.start ();
              // right.start();
             Runnable Lefthand =new Lefthand ();
             Thread t=new Thread(Lefthand );
             t.start();
             Runnable Righthand =new Righthand ();
             Thread t1=new Thread(Righthand );
             t1.start();
         }
    }

技術分享圖片

測試程序2

l 在Elipse環境下調試教材625頁程序14-1、14-2 14-3,結合程序運行結果理解程序;

package bounce;

import java.awt.geom.*;

/**
 * A ball that moves and bounces off the edges of a rectangle
 * @version 1.33 2007-05-17
 * @author Cay Horstmann
 */
public class Ball
{
   private static final int XSIZE = 15;
   private static final int YSIZE = 15;
   private double x = 0;
   private double y = 0;
   private double dx = 1;
   private double dy = 1;

   /**
    * Moves the ball to the next position, reversing direction if it hits one of the edges
    */
   public void move(Rectangle2D bounds)//窗口的
   {
      x += dx;
      y += dy;
      if (x < bounds.getMinX())
      {
         x = bounds.getMinX();
         dx = -dx;
      }
      if (x + XSIZE >= bounds.getMaxX())
      {
         x = bounds.getMaxX() - XSIZE;
         dx = -dx;
      }
      if (y < bounds.getMinY())
      {
         y = bounds.getMinY();
         dy = -dy;
      }
      if (y + YSIZE >= bounds.getMaxY())
      {
         y = bounds.getMaxY() - YSIZE;
         dy = -dy;
      }
   }

   /**
    * Gets the shape of the ball at its current position.
    */
   public Ellipse2D getShape()
   {
      return new Ellipse2D.Double(x, y, XSIZE, YSIZE);
   }
}
package bounce;

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

/**
 * The component that draws the balls.
 * @version 1.34 2012-01-26
 * @author Cay Horstmann
 */
public class BallComponent extends JPanel
{
   private static final int DEFAULT_WIDTH = 450;
   private static final int DEFAULT_HEIGHT = 350;

   private java.util.List<Ball> balls = new ArrayList<>();

   /**
    * Add a ball to the component.
    * @param b the ball to add
    */
   public void add(Ball b)
   {
      balls.add(b);
   }

   public void paintComponent(Graphics g)
   {
      super.paintComponent(g); // erase background
      Graphics2D g2 = (Graphics2D) g;
      for (Ball b : balls)
      {
         g2.fill(b.getShape());
      }
   }
   
   public Dimension getPreferredSize() { return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT); }
}
package bounce;

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

/**
 * Shows an animated bouncing ball.
 * @version 1.34 2015-06-21
 * @author Cay Horstmann
 */
public class Bounce
{
   public static void main(String[] args)
   {
      EventQueue.invokeLater(() -> {
         JFrame frame = new BounceFrame();
         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         frame.setVisible(true);
      });
   }
}

/**
 * The frame with ball component and buttons.
 */
class BounceFrame extends JFrame
{
   private BallComponent comp;
   public static final int STEPS = 1000;
   public static final int DELAY = 3;

   /**
    * Constructs the frame with the component for showing the bouncing ball and
    * Start and Close buttons
    */
   public BounceFrame()
   //BounceFrame類的addBall方法中有調用comp.paint(comp.getGraphics())
   {
      setTitle("Bounce");
      comp = new BallComponent();
      add(comp, BorderLayout.CENTER);
      JPanel buttonPanel = new JPanel();
      addButton(buttonPanel, "Start", event -> addBall());
      addButton(buttonPanel, "Close", event -> System.exit(0));
      add(buttonPanel, BorderLayout.SOUTH);//將buttonPanel組件整體放在南端
      pack();
   }

   /**
    * Adds a button to a container.
    * @param c the container
    * @param title the button title
    * @param listener the action listener for the button
    */
   public void addButton(Container c, String title, ActionListener listener)
   {
      JButton button = new JButton(title);
      c.add(button);
      button.addActionListener(listener);
   }

   /**
    * Adds a bouncing ball to the panel and makes it bounce 1,000 times.
    */
   public void addBall()//Start按鈕的處理程序將調用addBall
   {
      try
      {
         Ball ball = new Ball();
         comp.add(ball);

         for (int i = 1; i <= STEPS; i++)
         {
            ball.move(comp.getBounds());/調用move方法
            comp.paint(comp.getGraphics());
            Thread.sleep(DELAY);//Sleep是  Thread累靜態方法,用於暫停當前線程的活動。
         }
      }
      catch (InterruptedException e)
      //InterruptedException 只是在發生異常時簡單地終止彈跳。
      {
      }
   }
}

技術分享圖片

l 在Elipse環境下調試教材631頁程序14-4,結合程序運行結果理解程序;

 
package bounceThread;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

/**
 * Shows animated bouncing balls.
 * @version 1.34 2015-06-21
 * @author Cay Horstmann
 */
public class BounceThread
{
   public static void main(String[] args)
   {
      EventQueue.invokeLater(() -> {
         JFrame frame = new BounceFrame();
         frame.setTitle("BounceThread");
         frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         frame.setVisible(true);
      });
   }
}

/**
 * The frame with panel and buttons.
 */
class BounceFrame extends JFrame
{
   private BallComponent comp;
   public static final int STEPS = 1000;
   public static final int DELAY = 5;


   /**
    * Constructs the frame with the component for showing the bouncing ball and
    * Start and Close buttons
    */
   public BounceFrame()
   {
      comp = new BallComponent();
      add(comp, BorderLayout.CENTER);
      JPanel buttonPanel = new JPanel();
      addButton(buttonPanel, "Start", event -> addBall());
      addButton(buttonPanel, "Close", event -> System.exit(0));
      add(buttonPanel, BorderLayout.SOUTH);
      pack();
   }

   /**
    * Adds a button to a container.
    * @param c the container
    * @param title the button title
    * @param listener the action listener for the button
    */
   public void addButton(Container c, String title, ActionListener listener)
   {
      JButton button = new JButton(title);
      c.add(button);
      button.addActionListener(listener);
   }

   /**
    * Adds a bouncing ball to the canvas and starts a thread to make it bounce
    */
   public void addBall()
   {
      Ball ball = new Ball();
      comp.add(ball);
//引用實現了Runnable的方法 Runnable r
= () -> { try { for (int i = 1; i <= STEPS; i++) { ball.move(comp.getBounds()); comp.repaint(); Thread.sleep(DELAY); } } catch (InterruptedException e) { } }; Thread t = new Thread(r);//用Runnable創建一個Thread對象
t
.start();//啟動線程
} }

技術分享圖片

l 對比兩個程序,理解線程的概念和用途;

l 掌握線程創建的兩種技術。

測試程序3:分析以下程序運行結果並理解程序。

class Race extends Thread {

public static void main(String args[]) {

Race[] runner=new Race[4];

for(int i=0;i<4;i++) runner[i]=new Race( );

for(int i=0;i<4;i++) runner[i].start( );

runner[1].setPriority(MIN_PRIORITY);

runner[3].setPriority(MAX_PRIORITY);}

public void run( ) {

for(int i=0; i<1000000; i++);

System.out.println(getName()+"線程的優先級是"+getPriority()+"已計算完畢!");

}

}

class Race extends Thread {
public static void main(String args[]) {
Race[] runner = new Race[4];
for (int i = 0; i < 4; i++)
runner[i] = new Race();
for (int i = 0; i < 4; i++)
runner[i].start();
runner[1].setPriority(MIN_PRIORITY);
runner[3].setPriority(MAX_PRIORITY);
}

public void run() {
for (int i = 0; i < 1000000; i++);//用來延時
System.out.println(getName() + "線程的優先級是" + getPriority() + "已計算完!");
}
}

技術分享圖片

測試程序4

l 教材642頁程序模擬一個有若幹賬戶的銀行,隨機地生成在這些賬戶之間轉移錢款的交易。每一個賬戶有一個線程。在每一筆交易中,會從線程所服務的賬戶中隨機轉移一定數目的錢款到另一個隨機賬戶。

l 在Elipse環境下調試教材642頁程序14-5、14-6,結合程序運行結果理解程序;

package unsynch;

import java.util.*;

/**
 * A bank with a number of bank accounts.
 * @version 1.30 2004-08-01
 * @author Cay Horstmann
 */
public class Bank
{
   private final double[] accounts;

   /**
    * Constructs the bank.
    * @param n the number of accounts
    * @param initialBalance the initial balance for each account
    */
   public Bank(int n, double initialBalance)
   {
      accounts = new double[n];
      Arrays.fill(accounts, initialBalance);//將指定的 double 值分配給指定 double 型數組的每個元素
   }

   /**
    * Transfers money from one account to another.
    * @param from the account to transfer from
    * @param to the account to transfer to
    * @param amount the amount to transfer
    */
   public void transfer(int from, int to, double amount)
   {
      if (accounts[from] < amount) return;
      System.out.print(Thread.currentThread());
      accounts[from] -= amount;
      System.out.printf(" %10.2f from %d to %d", amount, from, to);
      accounts[to] += amount;
      System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
   }

   /**
    * Gets the sum of all account balances.
    * @return the total balance
    */
   public double getTotalBalance()
   {
      double sum = 0;

      for (double a : accounts)
         sum += a;

      return sum;
   }

   /**
    * Gets the number of accounts in the bank.
    * @return the number of accounts
    */
   public int size()
   {
      return accounts.length;
   }
}
package unsynch;

/**
 * This program shows data corruption when multiple threads access a data structure.
 * @version 1.31 2015-06-21
 * @author Cay Horstmann
 */
public class UnsynchBankTest
{
   public static final int NACCOUNTS = 100;
   public static final double INITIAL_BALANCE = 1000;
   public static final double MAX_AMOUNT = 1000;
   public static final int DELAY = 10;
   
   public static void main(String[] args)
   {
      Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE);
      for (int i = 0; i < NACCOUNTS; i++)
      {
         int fromAccount = i;
         Runnable r = () -> {
            try
            {
               while (true)
               {
                  int toAccount = (int) (bank.size() * Math.random());
                  double amount = MAX_AMOUNT * Math.random();
                  bank.transfer(fromAccount, toAccount, amount);
                  Thread.sleep((int) (DELAY * Math.random()));
               }
            }
            catch (InterruptedException e)
            {
            }            
         };
         Thread t = new Thread(r);
         t.start();
      }
   }
}

技術分享圖片

綜合編程練習

編程練習1

  1. 設計一個用戶信息采集程序,要求如下:

(1) 用戶信息輸入界面如下圖所示:

(2) 用戶點擊提交按鈕時,用戶輸入信息顯示控制臺界面;

(3) 用戶點擊重置按鈕後,清空用戶已輸入信息;

(4) 點擊窗口關閉,程序退出。

import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Window;

public class WinCenter {
    public static void center(Window win){
        Toolkit tkit = Toolkit.getDefaultToolkit();
        Dimension sSize = tkit.getScreenSize();
        Dimension wSize = win.getSize();
        if(wSize.height > sSize.height){
            wSize.height = sSize.height;
        }
        if(wSize.width > sSize.width){
            wSize.width = sSize.width;
        }
        win.setLocation((sSize.width - wSize.width)/ 2, (sSize.height - wSize.height)/ 2);
    }
}

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;

public class DemoJFrame extends JFrame {
    private JPanel jPanel1;
    private JPanel jPanel2;
    private JPanel jPanel3;
    private JPanel jPanel4;
    private JTextField fieldname;
    private JComboBox comboBox;
    private JTextField fieldadress;
    private ButtonGroup bg;
    private JRadioButton male;
    private JRadioButton female;
    private JCheckBox read;
    private JCheckBox movie;
    private JCheckBox learn;

    public DemoJFrame() {
        // 設置窗口大小
        this.setSize(800, 400);
        // 設置可見性
        this.setVisible(true);
        // 設置標題
        this.setTitle("編程練習一");
        // 設置關閉操作
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        // 設置窗口居中
        WinCenter.center(this);
        // 創建四個面板對象
        jPanel1 = new JPanel();
        setJPanel1(jPanel1);
        jPanel2 = new JPanel();
        setJPanel2(jPanel2);
        jPanel3 = new JPanel();
        setJPanel3(jPanel3);
        jPanel4 = new JPanel();
        setJPanel4(jPanel4);
        // 設置容器的為流布局
        FlowLayout flowLayout = new FlowLayout();//構造一個新的 FlowLayout,它是居中對齊的
        this.setLayout(flowLayout);
        // 將四個面板添加到容器中
        this.add(jPanel1);
        this.add(jPanel2);
        this.add(jPanel3);
        this.add(jPanel4);

    }

    /*
     * 設置面一
     */
    private void setJPanel1(JPanel jPanel) {
        // TODO 自動生成的方法存根
        jPanel.setPreferredSize(new Dimension(700, 45));
        // 給面板的布局設置為網格布局 一行4列
        jPanel.setLayout(new GridLayout(1, 4));
        JLabel name = new JLabel("姓名:");
        name.setSize(100, 50);
        fieldname = new JTextField("");
        fieldname.setSize(80, 20);
        JLabel study = new JLabel("學歷:");
        comboBox = new JComboBox();
        comboBox.addItem("初中");
        comboBox.addItem("高中");
        comboBox.addItem("本科");
        jPanel.add(name);
        jPanel.add(fieldname);
        jPanel.add(study);
        jPanel.add(comboBox);

    }

    /*
     * 設置面板二
     */
    private void setJPanel2(JPanel jPanel) {
        // TODO 自動生成的方法存根
        jPanel.setPreferredSize(new Dimension(700, 50));
        // 給面板的布局設置為網格布局 一行4列
        jPanel.setLayout(new GridLayout(1, 4));
        JLabel name = new JLabel("地址:");
        fieldadress = new JTextField();
        fieldadress.setPreferredSize(new Dimension(150, 50));
        JLabel study = new JLabel("愛好:");
        JPanel selectBox = new JPanel();
        selectBox.setBorder(BorderFactory.createTitledBorder(""));//創建一個新標題邊框
        selectBox.setLayout(new GridLayout(3, 1));//創建具有指定行數和列數的網格布局。給布局中的所有組件分配相等的大小。
        read = new JCheckBox("讀書");
        movie = new JCheckBox("看電影");
        learn = new JCheckBox("學習");
        selectBox.add(read);
        selectBox.add(movie);
        selectBox.add(learn);
        jPanel.add(name);
        jPanel.add(fieldadress);
        jPanel.add(study);
        jPanel.add(selectBox);
    }

    /*
     * 設置面板三
     */
    private void setJPanel3(JPanel jPanel) {
        // TODO 自動生成的方法存根
        jPanel.setPreferredSize(new Dimension(700, 150));
        FlowLayout flowLayout = new FlowLayout(FlowLayout.LEFT);
        jPanel.setLayout(flowLayout);
        JLabel sex = new JLabel("性別:");
        JPanel selectBox = new JPanel();
        selectBox.setBorder(BorderFactory.createTitledBorder(""));
        selectBox.setLayout(new GridLayout(2, 1));
        bg = new ButtonGroup();
        male = new JRadioButton("男");
        female = new JRadioButton("女");
        bg.add(male);
        bg.add(female);
        selectBox.add(male);
        selectBox.add(female);
        jPanel.add(sex);
        jPanel.add(selectBox);

    }

    /*
     * 設置面板四
     */
    private void setJPanel4(JPanel jPanel) {
        // TODO 自動生成的方法存根
        jPanel.setPreferredSize(new Dimension(700, 150));
        FlowLayout flowLayout = new FlowLayout(FlowLayout.CENTER, 50, 10);
        jPanel.setLayout(flowLayout);
        jPanel.setLayout(flowLayout);
        JButton sublite = new JButton("提交");
        JButton reset = new JButton("重置");
        sublite.addActionListener((e) -> valiData());
        reset.addActionListener((e) -> Reset());//給提交和重置按鈕創建監聽器
        jPanel.add(sublite);
        jPanel.add(reset);
    }

    /*
     * 提交數據
     */
    private void valiData() {
        // TODO 自動生成的方法存根
        // 拿到數據
        String name = fieldname.getText().toString().trim();
        String xueli = comboBox.getSelectedItem().toString().trim();
        String address = fieldadress.getText().toString().trim();
        System.out.println(name);
        System.out.println(xueli);
        String hobbystring="";
        if (read.isSelected()) {
            hobbystring+="讀書   ";
        }
        if (movie.isSelected()) {
            hobbystring+="看電影   ";
        }
        if (learn.isSelected()) {
            hobbystring+="學習  ";
        }
        System.out.println(address);
        if (male.isSelected()) {
            System.out.println("男");
        }
        if (female.isSelected()) {
            System.out.println("女");
        }
        System.out.println(hobbystring);
    }

    /*
     * 重置
     */
    private void Reset() {
        // TODO 自動生成的方法存根
        fieldadress.setText(null);
        fieldname.setText(null);
        comboBox.setSelectedIndex(0);
        read.setSelected(false);
        movie.setSelected(false);
        learn.setSelected(false);
        bg.clearSelection();
    }
}

import java.awt.EventQueue;

import javax.swing.JFrame;

public class Main {
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            DemoJFrame page = new DemoJFrame();
        });
    }
}

技術分享圖片

2.創建兩個線程,每個線程按順序輸出5次“你好”,每個“你好”要標明來自哪個線程及其順序號。

package project2;

class Lefthand extends Thread { 

       public void run()

       {

           for(int i=0;i<=4;i++)

           {  System.out.println("1.你好");

               try{   sleep(500);   }

               catch(InterruptedException e)

               { System.out.println("Lefthand error.");}    

           } 

      } 

    }

    class Righthand extends Thread {

        public void run()

        {

             for(int i=0;i<=4;i++)

             {   System.out.println("2.你好");

                 try{  sleep(300);  }

                 catch(InterruptedException e)

                 { System.out.println("Righthand error.");}

             }

        }

    }

    public class ThreadText 

    {

         static Lefthand left;

         static Righthand right;

         public static void main(String[] args)

         {     left=new Lefthand();

               right=new Righthand();

               left.start();

               right.start();

         }

    }

技術分享圖片

實驗總結:這次實驗運行多個程序的能力。例:操作系統將CPU的時間片分配給每一個進程,給人並行處理的感覺。

重要比較了多線程和多進程有哪些區別等,好多知識的收獲。

201771010101 白瑪次仁 《2018面向對象程序設計(Java)》第十六周學習總結