Hiển thị các bài đăng có nhãn Java. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn Java. Hiển thị tất cả bài đăng

Socket in java

http://www.devarticles.com/c/a/Java/Socket-Programming-in-Java/1/

Lập trình TCP Socket - java

Mới học java, lại thích lập trình mạng, tìm được bài viết này thấy hay nên mình post lên đây sợ lỡ khi nào quên mất thì uổng ../
-----------------------

JAVA: Lập trình Socket với TCP
Người soạn: Nimbus
Tài liệu tham khảo:
1. Giáo trình Lập trình Hướng đối tượng JAVA - Ngọc Anh Thư Press - NXB Thống Kê
2. Computer Networking - By James F. Kurose and Keith W. Ross - Addison Wesley
3. Website http://www.planet-source-code.com
4. JAVA Lập trình mạng - Nguyễn Phương Lan và Hoàng Đức Hải - NXB Giáo Dục 1.1. Lập trình Socket với TCP

1.1.1. Giới thiệu
Trong phần này chúng ta sẽ tìm hiểu về sự phát triển các ứng dụng mạng. Ta đã biết lõi (core) của một ứng dụng mạng bao gồm một cặp chương trình – một chương trình máy khách (client program) và một chương trình máy chủ (server program). Khi cả hai chương trình được thi hành, tiến trình (process) máy khách và máy chủ được tạo ra, và hai tiến trình này liên lạc với nhau bằng cách đọc từ và ghi đến những socket. Khi tạo ra một ứng dụng mạng, nhiệm vụ của chúng ta là viết mã cho cả chương trình máy khách và máy chủ.
Có hai loại ứng dụng client-server. Một loại là những ứng dụng client-server mà nó được hiện thực trên những giao thức chuẩn đã được định nghĩa trong các RFC (Request for Comments). Với những ứng dụng kiểu này thì chương trình máy chủ và máy khách đều phải phù hợp với những quy tắc đã được nêu ra trong RFC. Ví dụ, một chương trình máy khách có thể hiện thực một chương trình FTP client, được định nghĩa trong [RFC 959], và chương trình máy chủ thì hiện thực một FTP server, cũng phải được định nghĩa trong [RFC 959]

Loại còn lại của một ứng dụng client-server là một ứng dụng server-client giữ độc quyền (proprietary). Trường hợp này ứng với ứng dụng ta thực hiện trong bài tập này. Trong loại ứng dụng này thì chương trình máy khách lẫn máy chủ đều không cần phải phù hợp với bất cứ RFC nào. Một nhà phát triển đơn lẻ có thể tạo ra cả hai chương trình client và server, và nhà phát triển này hoàn toàn điều khiển tất cả những gì có trong mã lập trình. Nhưng bởi vì mã lập trình không được hiện thực trên giao thức chuẩn, nên những nhà phát triển độc lập khác sẽ không thể nào phát triển mã để có thể tương thích với ứng dụng này. Khi phát triển một ứng dụng kiểu này, những nhà phát triển phải chú ý để không sử dụng những cổng thông dụng đã được định nghĩa trong các RFC.
Trong phần kế tiếp ta sẽ nghiên cứu những điểm mấu chốt để phát triển một ứng dụng client-server giữ độc quyền. Trong quá trình phát triển, một trong những quyết định quan trọng mà nhà phát triển cần phải làm là ứng dụng sẽ chạy trên TCP hay trên UDP. TCP thì hướng kết nối (connection-oriented) và cung cấp những kênh luồng truyền dữ liệu đáng tin mà qua đó dữ liệu sẽ được truyền giữa hai hệ thống cuối. UDP là một giao thức không kết nối và gởi đi những gói dữ liệu độc lập từ hệ thống cuối này đến những hệ thống còn lại, mà không hề bảo đảm dữ liệu có được nhận hay không. Trong khuôn khổ bài tập này thì ta chỉ nghiên cứu một ứng dụng nhỏ chạy trên TCP.
Ứng dụng nhỏ này được viết bằng ngôn ngữ Java, ta chọn Java vì những lý do sau đây. Thứ nhất, những ứng dụng mạng kiểu này sẽ gọn gàng hơn khi viết bằng Java; với Java sẽ có ít dòng mã hơn, và mỗi dòng có thể dễ dàng giải thích với cả những người lập trình mới bắt đầu. Thứ hai, những chương trình client-server lập trình bằng Java đã gia tăng ngày càng thông dụng, và nó có thể trở thành tiêu chuẩn cho lập trình mạng trong vài năm tới. Java là một ngôn ngữ độc lập nền tảng, nó có cơ chể bẫy lỗi (điều quản các ngoại lệ) cường tráng mà có thể giải quyết hầu nó các lỗi xảy ra trong quá trình xuất/nhập và những hoạt động mạng, và khả năng phân luồng (thread) mạnh cung cấp những phương pháp đơn giản để hiện thực những server mạnh mẽ.

1.1.2. Lập trình Socket với TCP

Ta đã biết rằng những tiến trình chạy trên những máy khác nhau có thể liên lạc với nhay bằng cách gởi những thông điệp đến socket. Chúng ta có thể nói một cách đơn giản rằng mỗi một tiến trình thì tương tự như một căn nhà và socket của tiến trình đó thì tương tự như là cánh cửa nhà. Như đã thể hiện trên hình vẽ bên dưới, socket là cánh cửa giữa tiến trình ứng dụng (Application Process) và TCP. Những nhà phát triển ứng dụng có thể kiểm soát mọi thứ bên phía lớp Application của Socket; tuy nhiên, có rất ít kiểm soát bên phía lớp Transport.
Bây giờ ta hãy xét kỉ hơn sự tương tác giữa chương trình client và chương trình server. Máy khách client phải có nhiệm vụ khởi tạo sự giao tiếp với server. Để server có thể trả lời lại sự giao tiếp ban đầu này thì server cũng phải luôn luôn sẳn sàng. Điều này bao gồm 2 yếu tố. Đầu tiên, chương trình bên server phải đang hoạt động, nó phải đang chạy như một tiến trình trước khi máy khách tiến hành sự giao tiếp đầu tiên. Thứ hai, chương trình bên server phải có sẳn một số cổng (port) để có thể tiếp đón (welcome) những giao tiếp khởi tạo từ bên phía client. Sử dụng sự tương tự như căn nhà/cánh cửa đối với process/socket như đã nói ở trên thì ta có thể xem như sự khởi tạo giao tiếp đầu tiên bên phía client như là một sự gõ cửa.
Khi một tiến trình (process) bên server đang chạy, một tiến trình bên client có thể khởi tạo một TCP connection. Điều này được thực bên chương trình máy client bằng cách tạo ra một đối tượng socket (ta gọi nó là clientSocket). Khi một máy khách tạo ra một đối tượng socket thì nó sẽ chỉ đến địa chỉ của tiến trình máy chủ, theo cách gọi tên, thì đó là địa chỉ IP của máy server. Và cùng lúc tạo ra đối tượng clientSocket thì TCP ở client khởi tạo một three-way handshake (bài tập mạng TCP/IP sẽ giới thiệu rõ hơn về cách thức này) và thiết lập một TCP connection với server. Three-way handshake này thì hoàn toàn ẩn giấu với cả chương trình server lẫn chương trình client.
Trong suốt quá trình three-way handshake, tiến trình bên máy khách sẽ “gõ vào cổng” của tiến trình máy chủ. Khi server nhận được sự gõ cửa này, nó sẽ tạo ra một cánh cửa mới (thật ra là một socket mới) để mà phục vụ cho từng client chuyên biệt. Trong bài tập này, cánh cửa chào đón chính là một đối tượng ServerSocket mà ta gọi đó là welcomeSocket. Khi một máy khách gõ cửa, chương trình sẽ viện dẫn phương thức accept() của welcomeSocket, phương thức này sẽ tạo ra một socket khác cho client. Vào cuối quá trình handshaking này thì một kết nối TCP sẽ được thiết lập giữa socket của client và một socket mới của server. Từ đây trở về sau, ta sẽ gọi new socket này bằng tên gọi là “connection socket”

TCP connection đuợc xem như là một ống dẫn ảo giữa client’s socket và server's connection socket. Lúc này, tiến trình bên máy khách có thể gửi những byte tuỳ ý đến socket của nó, TCP bảo đảm rằng tiến trình bên server sẽ nhận (thông qua connection socket) mỗi byte theo trình tự đã gửi. Hơn thế nữa, ngược lại tiến trình bên máy khách cũng có thể nhận byte từ socket của nó và tiến trình bên server cũng có thể gửi byte đến connection socket của nó. Điều này được mô tả ở hình bên dưới.

Bởi vì socket đóng một vai trò quan trọng trong những ứng dụng client-server, cho nên những ứng dụng client-server thường được gọi là socket programming. Trước khi đi đến ứng dụng bài tập cụ thể, ta sẽ nói về khái niệm luồng (stream). Theo nghĩa đen luồng là một dòng lưu chuyển. Theo nghĩa kỹ thuật, một luồng là một lộ trình qua đó dữ liệu di chuyển đi vào hay đi ra một tiến trình (process). Mỗi một luồng thì có thể là một luồng vào (input stream) hay là một luồng xuất (output stream) cho một tiến trình. Nếu một luồng là luồng vào thì nó sẽ đính kèm với những nguồn vào của một tiến trình, ví dụ như là một công cụ nhập chuẩn (keyboard) hoặc một socket mà dữ liệu đi vào nó từ network. Nếu một luồng là luồng xuất, thì nó sẽ được đính kèm với một vài nguồn xuất của tiến trình, ví dụ như là một công cụ xuất chuẩn (màn hình) hoặc một socket mà đi ra từ nó dữ liệu sẽ đi vào network.

=======================================

1.2. Chương trình ví dụ 1: Giao nhận file bằng IP
1.2.1. Cách thức hoạt động
Chương trình gồm một file duy nhất có tên là FileTransfer.java tức là nó chỉ có một mã nguồn cho cả hai bên server và client. Sau khi biên dịch file .java này thì ta sẽ chạy nó bằng lệnh java FileTransfer. Tiếp theo chương trình sẽ yêu cầu người dùng chọn lựa để computer trở thành client hoặc server, được thể hiện như trên hình vẽ. Ta nhập vào 1 nếu để chọn là client và nhập vào 2 nếu muốn máy tính trở thành server.

Chạy chương trình ở server mode
Tuỳ chọn 2 phải được chọn trước ở máy tính đã được chỉ định chạy server mode, sau đó máy tính này sẽ chờ đợi các kết nối đến nó. Ta phải nhập tiếp port number, ứng với server mode này thì ta có thể chọn bất cứ port number nào lớn hơn 1024, vì những port number dưới 1024 đã bị giữ trước và sử dụng bởi hệ thống (well-known ports). Chương trình này cũng có thể thực hiện giao nhận file giữa một server và nhiều máy con đồng thời trong một hệ thống mạng nên nó sẽ yêu cầu ta nhập vào số lượng máy client lớn nhất có thể kết nối đến server này.

Chạy chương trình ở client mode
Tương tự cho phía bên client, chương trình sẽ yêu cầu nhập vào địa chỉ của server (host address), ta có thể nhập địa chỉ IP hay nhập vào tên của máy chạy server mode đều được (trong trường hợp trên hình bên dưới thì tên máy chạy server mode có địa chỉ IP trong mạng LAN là 192.168.0.144). Tiếp tục ta sẽ nhập port number, client phải cùng đồng ý chọn nhập port number như bên server như bên server đã chọn thì mới có thể giao nhận file được.

Giao diện giao nhận file
Sao khi chọn xong bước này thì chương trình ở cả hai máy sẽ hiển thị một cửa sổ dạng GUI để giúp cho việc giao nhận file được thực hiện dễ dàng.

Cả hai bên server và client đều có thể gửi file bằng cách click vào nút Browse để chọn file và sau đó click vào nút Send để gửi file đi.
Sau khi click Send để gửi đi thì chương trình bên máy nhận sẽ hiển thị một hộp thoại để hỏi người dùng có chấp nhận nhận file hay không. Theo mô tả trên hình vẽ thì máy sieutoc.mshome.net (máy chạy server mode có địa chỉ IP 192.168.0.144 như hình ở trên) đã gửi một file có tên là TestTransferFile.txt cho máy khác.

Sau khi chọn nhận file xong thì chương trình sẽ tiếp tục hiện một hộp thoại khác nửa để thông báo rằng file đã được gửi hay được nhận. File sau khi nhận sẽ được chứa trong cùng thư mục chứa file chương trình.


1.2.2. Hiện thực ứng dụng bằng mã lập trình Java:

Code:
//************************************************** ************************
// Name: File transfer application
// Description: transfer files on a network
// Source: http://www.planet-source-code.com
// File Name: FileTransfer.java
//************************************************** ************************

import java.io.*;
import java.net.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;



public class FileTransfer extends Frame {
public static String strHostAddress = "";
public static int intPortNumber = 0, intMaxClients = 0;
public static Vector vecConnectionSockets = null;
public static FileTransfer objFileTransfer;
public static String strFileName = "",strFilePath = "";
public static int intCheckChoice = 0;
public static Socket clientSocket = null;
public static ObjectOutputStream outToServer = null;
public static ObjectInputStream inFromServer = null;


public static void main (String [] args) throws IOException {
BufferedReader stdin = new BufferedReader(new InputStreamReader (System.in));
System.out.println("\nSelect Type for this Computer:");
System.out.println(" 1. Connect to host (i.e This computer is a client)");
System.out.println(" 2. Wait for connections (i.e This computer is a server)");
System.out.println();
System.out.print("Please make a choice: ");
System.out.flush();
int intChoice = Integer.parseInt(stdin.readLine());


if (intChoice == 1) { // This computer is a client
System.out.print("Please type in the host address: ");
System.out.flush();
strHostAddress = stdin.readLine();
System.out.print("Please type in the host port number: ");
System.out.flush();
intPortNumber = Integer.parseInt(stdin.readLine());
}


if (intChoice == 2) { //This computer is a server
System.out.print("Give the port number to listen for requests: ");
System.out.flush();
intPortNumber = Integer.parseInt(stdin.readLine());
System.out.print("Give the maximum number of clients for this server: ");
System.out.flush();
intMaxClients = Integer.parseInt(stdin.readLine());
}
objFileTransfer = new FileTransfer(intChoice);
}
public Label lblSelectFile;
public Label lblTitle;
public Label lblStudentName;
public TextField tfFile;
public Button btnBrowse;
public Button btnSend;
public Button btnReset;


public FileTransfer (int c) {
setTitle("Java - File Transfer");
setSize(300 , 300);
setLayout(null);
addWindowListener(new WindowAdapter () { public void windowClosing (WindowEvent e) { System.exit(0); } } );

lblTitle = new Label("FILE TRANSFER BY IP");
add(lblTitle);
lblTitle.setBounds(80,30,300,30);

lblSelectFile = new Label("Select File:");
add(lblSelectFile);
lblSelectFile.setBounds(15,87,100,20);

lblStudentName = new Label("Thanh Thien - Thanh Tuan");
add(lblStudentName);
lblStudentName.setBounds(130,270,200,20);


tfFile = new TextField("");
add(tfFile);
tfFile.setBounds(13,114,200,20);

btnBrowse = new Button("Browse");
btnBrowse.addActionListener(new buttonListener());
add(btnBrowse);

btnBrowse.setBounds(223,113,50,20);
btnSend = new Button("Send");
btnSend.addActionListener(new buttonListener());
add(btnSend);
btnSend.setBounds(64,168,50,20);

btnReset = new Button("Reset");
btnReset.addActionListener(new buttonListener());
add(btnReset);
btnReset.setBounds(120,168,50,20);

show();


if (c == 1) { // Client receives file
intCheckChoice = 1;


try {
clientSocket = new Socket (strHostAddress,intPortNumber);
outToServer = new ObjectOutputStream(clientSocket.getOutputStream()) ;
outToServer.flush();
inFromServer = new ObjectInputStream(clientSocket.getInputStream());
int intFlag = 0;


while (true) {
Object objRecieved = inFromServer.readObject();


switch (intFlag) {
case 0:

if (objRecieved.equals("IsFileTransfered")) {
intFlag++;
}
break;
case 1:
strFileName = (String) objRecieved;
int intOption = JOptionPane.showConfirmDialog(this,clientSocket.ge tInetAddress().getHostName()+" is sending you "+strFileName+"!\nDo you want to recieve it?","Recieve Confirm",JOptionPane.YES_NO_OPTION,JOptionPane.QUE STION_MESSAGE);

if (intOption == JOptionPane.YES_OPTION) {
intFlag++;

} else {
intFlag = 0;
}
break;
case 2:
byte[] arrByteOfReceivedFile = (byte[])objRecieved;
FileOutputStream outToHardDisk = new FileOutputStream(strFileName);
outToHardDisk.write(arrByteOfReceivedFile);
intFlag = 0;
JOptionPane.showMessageDialog(this,"File Recieved!","Confirmation",JOptionPane.INFORMATION_ MESSAGE);
break;
}
Thread.yield();
}
} catch (Exception e) {System.out.println(e);}
}


if (c == 2) { // Server receives file
vecConnectionSockets = new Vector();
intCheckChoice = 2;

try {
ServerSocket welcomeSocket = new ServerSocket(intPortNumber,intMaxClients);

while (true) {
vecConnectionSockets.addElement(new ThreadedConnectionSocket(welcomeSocket.accept()));
Thread.yield();
}
} catch (IOException ioe) {System.out.println(ioe);}
}
}


public static String showDialog () {
FileDialog fd = new FileDialog(new Frame(),"Select File...",FileDialog.LOAD);
fd.show();
return fd.getDirectory()+fd.getFile();
}


private class buttonListener implements ActionListener {


public void actionPerformed (ActionEvent ae) {
byte[] arrByteOfSentFile = null;


if (ae.getSource() == btnBrowse) {
strFilePath = showDialog();
tfFile.setText(strFilePath);
int intIndex = strFilePath.lastIndexOf("\\");
strFileName = strFilePath.substring(intIndex+1);
}


if (ae.getSource() == btnSend) {

try {
FileInputStream inFromHardDisk = new FileInputStream (strFilePath);
int size = inFromHardDisk.available();
arrByteOfSentFile = new byte[size];
inFromHardDisk.read(arrByteOfSentFile,0,size);

if (intCheckChoice == 2) {// Send file from server


for (int i=0;i");
return;
}

// Tạo đối tượng liên kết với máy chủ (thực tế chỉ là liên kết với clientSocket)
ClientConnect client = new ClientConnect(args[0],3456);

// Nhận dữ liệu từ bàn phím và gửi đến máy chủ xử lý
client.requestServer();

// Cắt đứt kết nối với đối tượng liên kết
client.shutdown();
}
}

// Thiết kế lớp liên kết với máy chủ thông qua socket và nhận dữ liệu từ bàn phím
class ClientConnect {

// Lưu giữ đối tượng kết nối
Socket clientSocket;

// Luồng dùng gửi dữ liệu đến clientSocket
DataOutputStream outToServer;

// Luồng dùng đọc dữ liệu do máy chủ gửi về
BufferedReader inFromServer;

/*
Phương thức khởi dựng tạo kết nối với máy chủ theo địa chỉ destination và cổng port
*/
public ClientConnect(String destination,int port){
try{

// Tạo socket kết nối phía client
clientSocket = new Socket(destination,port);

// Lấy vào luồng nhập dùng đọc dữ liệu do máy chủ gửi về
inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

// Lấy về luồng xuất dùng gửi dữ liệu đến socket từ đó chuyển đến máy chủ
outToServer= new DataOutputStream( clientSocket.getOutputStream());
System.out.println("Connected to server at port 3456.");
} catch (Exception e) {

// Nếu gặp lỗi thì in lỗi ra màn hình
System.out.println(e);
}
}

/*
Phương thức này sẽ nhận chuỗi dữ liệu từ bàn phím và gửi đến máy chủ xử lý
*/
public void requestServer(){

// Tạo luồng để đọc dữ liệu từ bàn phím
BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));

boolean finished=false;
do {
try{

// Tạo menu lựa chọn. S: gửi dữ liệu, R: nhận dữ liệu, Q: chấm dứt
System.out.print("Send, receive, or quit (S/R/Q): ");
System.out.flush();

String line = inFromUser.readLine();

if(line.length()>0){
line=line.toUpperCase();
switch (line.charAt(0)){

// Nếu người dùng chọn S, dữ liệu nhập vào sẽ được gửi đến clientSocket, từ đó chuyển qua máy chủ
case 'S':
String sendLine = inFromUser.readLine();
outToServer.writeBytes(sendLine);
outToServer.write(13);
outToServer.write(10);
outToServer.flush();
break;

// Nếu người dùng chọn R, dữ liệu do máy chủ xử lý sẽ được nhận về và in ra
case 'R':
int inByte;
System.out.print(">>>>");
while ((inByte = inFromServer.read()) != '\n')
System.out.write(inByte);
System.out.println();
break;

// Nếu người dùng chọn Q, chương trình sẽ chấm dứt
case 'Q':
finished=true;
break;

default:
break;
}
}
}catch (Exception e){

// Nếu lỗi xuất nhập thì in lỗi ra màn hình
System.out.println(e);
}
} while(!finished);
}

/*
Phương thức này được dùng để đóng kết nối từ máy khách với máy chủ
*/
public void shutdown(){
try{
clientSocket.close();
}catch (IOException ex){
System.out.println("IO error closing socket");
}
}
}
1.3.3. EchoServer.java
Chương trình EchoServer cũng được giải thích tương tự như chương trình EchoClient. Phần ghi chú và giải thích được ghi kèm trong mã nguồn của chương trình
Code:
import java.net.*;
import java.io.*;

public class EchoServer {

public static void main(String args[]){
try{

/*
Tạo đối tượng ServerSocket dùng để lắng nghe kết nối từ các máy khách gửi đến cổng 3456
*/
ServerSocket welcomeSocket = new ServerSocket(3456);
int localPort = welcomeSocket.getLocalPort();
System.out.println("Echo Server is listening on port "+localPort+".");

//Chờ đợi kết nối từ máy khách
/*
Lưu ý: Đến đây chương trình sẽ dừng lại và chờ đợi cho đến khi có kết nối xảy ra
*/
Socket connectionSocket = welcomeSocket.accept();
/*
Có kết nối xảy ra. Lấy các thông tin từ máy khách và cho in ra màn hình
*/

String destName = connectionSocket.getInetAddress().getHostName();
int destPort = connectionSocket.getPort();
System.out.println("Accepted connection to "+destName +" on port "+destPort+".");

//Lấy về luồng nhập để đọc dữ liệu từ connectionSocket, do máy khách gửi đến
/*
Lưu ý: Ta đưa luồng nhập vào bộ lọc luồng để chuyển đổi thành luồng BufferReader có khả năng đọc được một chuỗi ký tự bằng phương thức readLine()
*/
BufferedReader inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream( )));

// Lấy về luồng xuất để ghi dữ liệu vào connectionSocket, từ đó chuyển đến máy khách
DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream( ));

// Chờ đọc vào xử lý dữ liệu do máy khách gửi đến

boolean finished = false;
do {

// Đọc một chuỗi kí tự từ connectionSocket do máy khách gửi đến
String inLine = inFromClient.readLine();

// In chuỗi nhận được ra màn hình
System.out.println("Received: "+inLine);

// So sánh chuỗi gửi đến. Nếu là quit thì chấm dứt
if(inLine.equalsIgnoreCase("quit")) finished=true;

// Xử lý đảo ngược chuỗi
String outLine=new EchoString(inLine.trim()).getString();

// Gửi trả kết quả về connectionSocket từ đó trả về máy khách
for(int i=0;i>>>! revreS olleH Received: Hello Server !Sent: ! revreS olleH
Send, receive, or quit (S/R/Q): s123456789Send, receive, or quit (S/R/Q): r>>>>987654321 Received: 123456789Sent: 987654321
Send, receive, or quit (S/R/Q): SHow are you?Send, receive, or quit (S/R/Q): r>>>>?uoy era woH Received: How are you?Sent: ?uoy era woH
Send, receive, or quit (S/R/Q): q


1.4. Tài liệu tham khảo:
1. Giáo trình Lập trình Hướng đối tượng JAVA - Ngọc Anh Thư Press - NXB Thống Kê
2. Computer Networking - By James F. Kurose and Keith W. Ross - Addison Wesley
3. Website http://www.planet-source-code.com
4. JAVA Lập trình mạng - Nguyễn Phương Lan và Hoàng Đức Hải - NXB Giáo Dục

(sưu tầm)

Đuôi đệ quy

http://vi.how-to.mobi/index.php?id=33923

Link : code java example

http://www.javadb.com/

The final keyword

The final keyword

The final keyword has slightly different meanings depending on the context, but in general it says “This cannot be changed.” You might want to prevent changes for two reasons: design or efficiency. Because these two reasons are quite different, it’s possible to misuse the final keyword.
The following sections discuss the three places where final can be used: for data, methods and for a class.

Final data

Many programming languages have a way to tell the compiler that a piece of data is “constant.” A constant is useful for two reasons:
  1. It can be a compile-time constant that won’t ever change.
  2. It can be a value initialized at run-time that you don’t want changed.
In the case of a compile-time constant the compiler is allowed to “fold” the constant value into any calculations in which it’s used; that is, the calculation can be performed at compile time, eliminating some run-time overhead. In Java, these sorts of constants must be primitives and are expressed using the final keyword. A value must be given at the time of definition of such a constant.
A field that is both static and final has only one piece of storage that cannot be changed.
When using final with object handles rather than primitives the meaning gets a bit confusing. With a primitive, final makes the value a constant, but with an object handle, final makes the handle a constant. The handle must be initialized to an object at the point of declaration, and the handle can never be changed to point to another object. However, the object can be modified; Java does not provide a way to make any arbitrary object a constant. (You can, however, write your class so that objects have the effect of being constant.) This restriction includes arrays, which are also objects.
Here’s an example that demonstrates final fields:
//: FinalData.java
// The effect of final on fields

class Value {
int i = 1;
}

public class FinalData {
// Can be compile-time constants
final int i1 = 9;
static final int I2 = 99;
// Typical public constant:
public static final int I3 = 39;
// Cannot be compile-time constants:
final int i4 = (int)(Math.random()*20);
static final int i5 = (int)(Math.random()*20);

Value v1 = new Value();
final Value v2 = new Value();
static final Value v3 = new Value();
//! final Value v4; // Pre-Java 1.1 Error:
// no initializer
// Arrays:
final int[] a = { 1, 2, 3, 4, 5, 6 };

public void print(String id) {
System.out.println(
id + ": " + "i4 = " + i4 +
", i5 = " + i5);
}
public static void main(String[] args) {
FinalData fd1 = new FinalData();
//! fd1.i1++; // Error: can't change value
fd1.v2.i++; // Object isn't constant!
fd1.v1 = new Value(); // OK -- not final
for(int i = 0; i < fd1.a.length; i++)
fd1.a[i]++; // Object isn't constant!
//! fd1.v2 = new Value(); // Error: Can't
//! fd1.v3 = new Value(); // change handle
//! fd1.a = new int[3];

fd1.print("fd1");
System.out.println("Creating new FinalData");
FinalData fd2 = new FinalData();
fd1.print("fd1");
fd2.print("fd2");
}
} ///:~
Since i1 and I2 are final primitives with compile-time values, they can both be used as compile-time constants and are not different in any important way. I3 is the more typical way you’ll see such constants defined: public so they’re usable outside the package, static to emphasize that there’s only one, and final to say that it’s a constant. Note that final static primitives with constant initial values (that is, compile-time constants) are named with all capitals by convention. Also note that i5 cannot be known at compile time, so it is not capitalized.
Just because something is final doesn’t mean that its value is known at compile-time. This is demonstrated by initializing i4 and i5 at run-time using randomly generated numbers. This portion of the example also shows the difference between making a final value static or non- static. This difference shows up only when the values are initialized at run-time, since the compile-time values are treated the same by the compiler. (And presumably optimized out of existence.) The difference is shown in the output from one run:
fd1: i4 = 15, i5 = 9
Creating new FinalData
fd1: i4 = 15, i5 = 9
fd2: i4 = 10, i5 = 9
Note that the values of i4 for fd1 and fd2 are unique, but the value for i5 is not changed by creating the second FinalData object. That’s because it’s static and is initialized once upon loading and not each time a new object is created.
The variables v1 through v4 demonstrate the meaning of a final handle. As you can see in main( ), just because v2 is final doesn’t mean that you can’t change its value. However, you cannot re-bind v2 to a new object, precisely because it’s final. That’s what final means for a handle. You can also see the same meaning holds true for an array, which is just another kind of handle. (There is know way that I know of to make the array handles themselves final.) Making handles final seems less useful than making primitives final.

Blank finals

Java 1.1 allows the creation of blank finals , which are fields that are declared as final but are not given an initialization value. In all cases, the blank final must be initialized before it is used, and the compiler ensures this. However, blank finals provide much more flexibility in the use of the final keyword since, for example, a final field inside a class can now be different for each object and yet it retains its immutable quality. Here’s an example:
//: BlankFinal.java
// "Blank" final data members

class Poppet { }

class BlankFinal {
final int i = 0; // Initialized final
final int j; // Blank final
final Poppet p; // Blank final handle
// Blank finals MUST be initialized
// in the constructor:
BlankFinal() {
j = 1; // Initialize blank final
p = new Poppet();
}
BlankFinal(int x) {
j = x; // Initialize blank final
p = new Poppet();
}
public static void main(String[] args) {
BlankFinal bf = new BlankFinal();
}
} ///:~
You’re forced to perform assignments to finals either with an expression at the point of definition of the field or in every constructor. This way it’s guaranteed that the final field is always initialized before use.

Final arguments

Java 1.1 allows you to make arguments final by declaring them as such in the argument list. This means that inside the method you cannot change what the argument handle points to:
//: FinalArguments.java
// Using "final" with method arguments

class Gizmo {
public void spin() {}
}

public class FinalArguments {
void with(final Gizmo g) {
//! g = new Gizmo(); // Illegal -- g is final
g.spin();
}
void without(Gizmo g) {
g = new Gizmo(); // OK -- g not final
g.spin();
}
// void f(final int i) { i++; } // Can't change
// You can only read from a final primitive:
int g(final int i) { return i + 1; }
public static void main(String[] args) {
FinalArguments bf = new FinalArguments();
bf.without(null);
bf.with(null);
}
} ///:~
Note that you can still assign a null handle to an argument that’s final without the compiler catching it, just like you can with a non-final argument.
The methods f( ) and g( ) show what happens when primitive arguments are final: you can only read the argument, but you can't change it.

Final methods

There are two reasons for final methods. The first is to put a “lock” on the method to prevent any inheriting class from changing its meaning. This is done for design reasons when you want to make sure that a method’s behavior is retained during inheritance and cannot be overridden.
The second reason for final methods is efficiency. If you make a method final, you are allowing the compiler to turn any calls to that method into inline calls. When the compiler sees a final method call it can (at its discretion) skip the normal approach of inserting code to perform the method call mechanism (push arguments on the stack, hop over to the method code and execute it, hop back and clean off the stack arguments, and deal with the return value) and instead replace the method call with a copy of the actual code in the method body. This eliminates the overhead of the method call. Of course, if a method is big, then your code begins to bloat and you probably won’t see any performance gains from inlining since any improvements will be dwarfed by the amount of time spent inside the method. It is implied that the Java compiler is able to detect these situations and choose wisely whether to inline a final method. However, it’s better to not trust that the compiler is able to do this and make a method final only if it’s quite small or if you want to explicitly prevent overriding.
Any private methods in a class are implicitly final. Because you can’t access a private method, you can’t override it (the compiler gives an error message if you try). You can add the final specifier to a private method but it doesn’t give that method any extra meaning.

Final classes

When you say that an entire class is final (by preceding its definition with the final keyword), you state that you don’t want to inherit from this class or allow anyone else to do so. In other words, for some reason the design of your class is such that there is never a need to make any changes, or for safety or security reasons you don’t want subclassing. Alternatively, you might be dealing with an efficiency issue and you want to make sure that any activity involved with objects of this class is as efficient as possible.
//: Jurassic.java
// Making an entire class final

class SmallBrain {}

final class Dinosaur {
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f() {}
}

//! class Further extends Dinosaur {}
// error: Cannot extend final class 'Dinosaur'

public class Jurassic {
public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;
n.j++;
}
} ///:~
Note that the data members can be final or not, as you choose. The same rules apply to final for data members regardless of whether the class is defined as final. Defining the class as final simply prevents inheritance – nothing more. However, because it prevents inheritance all methods in a final class are implicitly final, since there’s no way to override them. So the compiler has the same efficiency options as it does if you explicitly declare a method final.
You can add the final specifier to a method in a final class, but it doesn’t add any meaning.

Final caution

It can seem to be sensible to make a method final while you’re designing a class. You might feel that efficiency is very important when using your class and that no one could possibly want to override your methods anyway. Sometimes this is true.
But be careful with your assumptions. In general, it’s difficult to anticipate how a class can be reused, especially a general-purpose class. If you define a method as final you might prevent the possibility of reusing your class through inheritance in some other programmer’s project simply because you couldn’t imagine it being used that way.
The standard Java library is a good example of this. In particular, the Vector class is commonly used and might be even more useful if, in the name of efficiency, all the methods hadn’t been made final. It’s easily conceivable that you might want to inherit and override with such a fundamentally useful class, but the designers somehow decided this wasn’t appropriate. This is ironic for two reasons. First, Stack is inherited from Vector, which says that a Stack is a Vector, which isn’t really true. Second, many of the most important methods of Vector, such as addElement( ) and elementAt( ) are synchronized, which as you will see in Chapter 14 incurs a significant performance overhead that probably wipes out any gains provided by final. This lends credence to the theory that programmers are consistently bad at guessing where optimizations should occur. It’s just too bad that such a clumsy design made it into the standard library where we must all cope with it.
It’s also interesting to note that Hashtable, another important standard library class, does not have any final methods. As mentioned elsewhere in this book, it’s quite obvious that some classes were designed by completely different people than others. (Notice the brevity of the method names in Hashtable compared to those in Vector.) This is precisely the sort of thing that should not be obvious to consumers of a class library. When things are inconsistent it just makes more work for the user. Yet another paean to the value of design and code walkthroughs.

Từ khóa 'this'

- Từ khóa this được dùng để truyền giá trị từ một tham số vào fields
vd :
public class cha
{
___// fields
___int ia;
___//method
___void tongTinh(int ia)
___{
_____ia = 10;
_____this.ia =ia
___}
}


- Từ khóa this còn được dùng để gọi một struct từ một struct
vd :
public class cha
{
___public cha(int tuoi,string ten)
___{}
___public cha(int tuoi1)
___{
_____// call struct cha(int tuoi,string ten) với t= tuoi1 và s = ten
_____this(t,s)
___}
}

Phân biệt "truyền tham trị" và "truyền tham chiếu"

- Giả sữ ta có một hàm
void tinh(int i) {} // ở đây, i là tham số hình thức

Nếu ta gọi nó trong hàm main ()

tinh(n); // ở đây n được gọi là tham số thực sự,
______// việc n thay thế cho x được gọi là truyền tham số
Truyền tham trị : giá trị của tham số được gửi đến cho hàm, hàm chỉ tác động đến bản sao của tham số gốc, do đó hàm chỉ làm thay đổi giá trị của tham số bản sao mà không ảnh hưởng đến tham số gốc.
Vd :

public class zet
{
___public static void main (String args[])
___{
_____int so = 10;
_____tinh(so);
_____System.out.println("So hien ra la : " + so);
___}

___public static int tinh(int i)
___{
_____int so1 = i *3;
___}
}

=> So hien ra la : 10


Truyền tham chiếu : hàm sẽ tác động lên địa chỉ chứa tham số đưa vào trong hàm, do đó hàm sẽ làm thay đổi tham số đó.
(Trong C++ : tinh(int &so)

Method : Static và non-static

Method : Static và non-static

Một Method trong một class là static thì nó có thể được gọi ở mà không cần khởi tạo.
Ví dụ :

public class Bai2
{
___public static void main (String args[])
___{
_____int so1 = 10 ;
_____System.out.println("So la : " + so1);
_____System.out.println("So la : " + tinh(so1) );
___}

___static int tinh(int so)
___{
_____int so2 ;
_____so2 = so *3;
_____return so2;
___}
}

=>
So la : 10
So la : 30


// không cần phải khởi tạo kiểu như :
// Bai2 b2 = new Bai2();
// b2.tinh(so1);

--------------------------

Trong Class, nếu ta viết
static
{
__int ia;
__ia = ia * 5;
}
thì khi đó hàm này chỉ chạy một lần trong suốt quá trình .

Mỗi Class có một hàm main !

Mỗi class có một hàm main riêng, nhưng chỉ có những class nào public (nghĩa là class chính - trùng tên với tên *.java) thì hàm main trong đó mới hoạt động.
Ta lợi dụng điều này để thử nghiệm các thành phần của nội bộ lớp phụ.

Vd :
public class Lopchinh()
{
___// đây mới là hàm main chính - chạy được
___public static void main(String args[])
___{
_____Lopphu[] lp1 = new Lopphu[3];
_____lp1[0] = new Lopphu("Man",21);
_____lp1[1] = new Lopphu("Duy",19);
_____lp1[2] = new Lopphu("Huy",17);
_____lp1[3] = new Lopphu("Nguyen",7);

_____// in tất cả các thông tin trong mảng bằng vòng lặp ngắn gọn sau đây :))
_____for (Lopphu e : lp1) // nghĩa là e từ 0 tới 3
_____{
_______System.out.printf("Ten : " + e.Rten() + "Tuoi : " + e.Rtuoi() );

___}
}





class Lopphu()
{

___//fields
___int tuoi;
___string ten;

___//struct
___public Lopphu(string ten, int tuoi)
___{
_____this.ten = ten;
_____this.tuoi = tuoi;
___}

___// 2 method
___public Rten()
___{
_____return ten;
___}

___public Rtuoi()
___{
_____return tuoi;
___}

___//hàm main này chỉ mang tính thử nghiệm, không có tác dụng gì !!!
___public static void main(String args[])
___{
_____Lopphu lp = new Lopphu("Man",20)
____System.out.printf(lp.Rten() + lp.Rtuoi() );
___}
}

Định nghĩa một Method (Defining methods)

Đây là một đoạn mã ví dụ về Defining Methods
public double soTong (double so1, double so2)
{
___
}
Một đoạn mã của Method sẽ gồm có các thành phần sau đây :
1. Các modifiers như : public, private
2. Kiểu giá trị trả về
3. Tên của Method, người ta đặt tên theo quy ước sau :
___+ [danh từ - viết chữ thường][tính từ - viết chữ hoa đầu] ví dụ : runFast, isEmpty,...
4. Trong dấu () là các tham số truyền vào
5. Một danh sách các ngoại lệ
6. Thân hàm trong cặp dấu {}

Overloading Methods

- Một phương thức có các tham số và có kiểu giá trị trả về khác nhau, nhưng có cùng chung một cái tên thì ta gọi đó là Overloading Methods
vd :
public class Danhsach
{
___string In_ra (string chuoi) {} ;
___int In_ra(int so){} ;
}

* Một Method hay một Struct cũng đều có nhiều tham số, người ta thường dùng
varargs khi chưa biết có bao nhiêu tham số cần dùng...

Khai báo một lớp (Declaring Classes)

( Chú ý rằng trong một file source zet.java thì chỉ có một class được public - đó là class trùng với tên file )

Một lớp trong java thường có dạng :
class zet
{
___Struct, Fields, Methods
}

Ta có thể khai báo thêm một lớp cha của nó với từ khóa extends
class zet extends cha_zet implements giao_dien
//có nghĩa là subclass zet kế thừa từ
//superclass cha_zet và thực hiện các
//giao diện trong interface giao_dien
{
___Structs, Fields, Methods
}

Bạn cũng có thể bổ xung các modifiers cho các class chẳng hạn như public, private
Nói chung, trong khai báo một class có một số điều cần lưu ý :
__ + Thêm vào các modifiers để tiện việc sử dụng sau này
__ + Tên lớp phải viết bằng chữ hoa
__ + Một lớp chỉ có thể extends từ 1 lớp khác
__ + Một lớp có thể implements một hoặc nhiều interface

Class

(Đây là bài dịch từ java.sun.com)

Một lớp trong Java có thể chứa : Struct, Methods, Fields. Xét ví dụ dưới đây :

public class xedap()
{
___// lớp xedap có 3 fields
___public int tocdo;
___public string kieudang;
___public int maso;

___//lớp xedap có thể có 1 Struct
___public xedap (int tocdo1, string kieudang1, int maso1)
______{
_________tocdo = tocdo1;
_________kieudang = kieudang1;
_________maso = maso1;
______}

___//lớp xedap có thể có vài method
___public chaytoi (int tocdotoi)
___{
______tocdo = tocdotoi;
___}
___public chaylui (int tocdolui)
___{
______tocdo = tocdolui;
___}

}


//một lớp mới xedap_dien sẽ kế thừa từ lớp xedap

public class xedap_dien extends xedap
{

___public int bienso;
___public dieukhienchung (int bienso1, int tocdo, int maso, string kieudang)
___{
______bienso = bienso1;
___}

}


Tạo một Struct cho Class của bạn
(Providing Constructors for Your Classes)
- Trong một class có thể có một hoặc nhiều Struct, về cú pháp lệnh thì nó cũng giống như Methods, nhưng tên của Struct thì trùng với tên của Class chứa nó.
- Khi cần sử dụng Struct ở các lớp khác thì ta dùng từ khóa new
____Xedap xedap_leonui = new Xedap([danh sách các đối số]);
- Bạn không cần phải khai báo Struct trong class, nhưng khi không khai báo thì mặc nhiên nó sẽ tạo ra một Struct không có đối số



Welcome to my blog !