`
fulerbakesi
  • 浏览: 559318 次
文章分类
社区版块
存档分类
最新评论

java基础教程-输入/输出、小应用程序和其他主题

 
阅读更多

12 输入/输出、小应用程序和其他主题

本章介绍Java的两个重要的包:ioappletio包支持Java的基本I/O(输入/输出)系统,包括文件的输入/输出。applet包支持applet(小应用程序)。对输入/输出和applet的支持是来源于Java的内核API库,而不是语言关键字。因为这个原因,关于这些主题的深入讨论在本书的第2部分可以见到,这些讨论验证了JavaAPI类。本章讨论这两个子系统的基础部分,这样你可以看到它们怎样融入Java语言,怎样符合Java编程和执行环境的大量内容。本章同样介绍了Java的最后的关键字:transientvolatileinstanceofnative以及strictfp

12.1 输入/输出基础

在阅读前面的第11章时你也许注意到在例题中输入/输出没有很多的应用。实际上,除了print()println(),基本没有运用输入/输出方法。原因很简单:很多实际的Java应用程序不是基于文本的控制台程序。相反,它们与用户交流依赖于抽象窗口工具集(AWT)的用于绘图的小应用程序。尽管基于文本的程序作为教学实例是很出色的,但是它们无法胜任JAVA在实际中的重要应用。同样Java对外设输入/输出的支持也是有限的,并且用起来有些笨拙——甚至是在简单的例子程序中。基于文本的控制台输入/输出对于Java程序并不是十分重要。

尽管在前面的章节中Java 没有提供与文件和网络相关强大且灵活的输入/输出支持,但但是它的输入/输出系统是紧密相连并且具有一致性的。实际上,一旦你理解了它的基本原理,输入/输出系统的其他部分就变得易于掌握了。

<chsdate w:st="on" year="1899" month="12" day="30" islunardate="False" isrocdate="False"><span style="mso-bookmark: _Toc528747348"><span lang="EN-US">12.1.1</span></span></chsdate> 流的概念

Java程序通过流来完成输入/输出。流是生产或消费信息的抽象。流通过Java的输入/输出系统与物理设备链接。尽管与它们链接的物理设备不尽相同,所有流的行为具有同样的方式。这样,相同的输入/输出类和方法适用于所有类型的外部设备。这意味着一个输入流能够抽象多种不同类型的输入:从磁盘文件,从键盘或从网络套接字。同样,一个输出流可以输出到控制台,磁盘文件或相连的网络。流是处理输入/输出的一个洁净的方法,例如它不需要代码理解键盘和网络的不同。Java中流的实现是在java.io包定义的类层次结构内部的。

注意:如果你熟悉C/C++,你已经对流的概念很熟悉了。JAVA中流的实现跟C/C++中有些相似。

<chsdate w:st="on" year="1899" month="12" day="30" islunardate="False" isrocdate="False"><span style="mso-bookmark: _Toc528747349"><span lang="EN-US">12.1.2</span></span></chsdate> 字节流和字符流

Java 2 定义了两种类型的流:字节类和字符类。字节流(byte stream)为处理字节的输入和输出提供了方便的方法。例如使用字节流读取或书写二进制数据。字符流(character stream)为字符的输入和输出处理提供了方便。它们采用了统一的编码标准,因而可以国际化。当然,在某些场合,字符流比字节流更有效。

Java的原始版本(Java 1.0)不包括字符流,因此所有的输入和输出都是以字节为单位的。Java 1.1中加入了字符流,某些字节形式的类和方法不受欢迎。这也是为什么没用字节流的老代码在适当的地方需要更新的原因。

需要声明:在最底层,所有的输入/输出都是字节形式的。基于字符的流只为处理字符提供方便有效的方法。

下面是对字节流和字符流的概述。

字节流类

字节流由两个类层次结构定义。在顶层有两个抽象类:InputStream OutputStream。每个抽象类都有多个具体的子类,这些子类对不同的外设进行处理,例如磁盘文件,网络连接,甚至是内存缓冲区。字节流类显示于表12-1中。本章的后面将讨论一些这样的类。其他的类的描述在第2部分。记住,要使用流类,必须导入Java.io包。

12-1 字节流类

流类

含义

BufferedInputStream

缓冲输入流

BufferedOutputStream

缓冲输出流

ByteArrayInputStream

从字节数组读取的输入流

ByteArrayOutputStream

向字节数组写入的输出流

DataInputStream

包含读取Java标准数据类型方法的输入流

DataOutputStream

包含编写Java 标准数据类型方法的输出流

FileInputStream

读取文件的输入流

FileOutputStream

写文件的输出流

FilterInputStream

实现 InputStream

FilterOutputStream

实现 OutputStream

InputStream

描述流输入的抽象类

OutputStream

描述流输出的抽象类

PipedInputStream

输入管道

PipedOutputStream

输出管道

PrintStream

包含print( ) println( )的输出流

PushbackInputStream

支持向输入流返回一个字节的单字节的“unget”的输入流

RandomAccessFile

支持随机文件输入/输出

SequenceInputStream

两个或两个以上顺序读取的输入流组成的输入流

抽象类InputStream OutputStream定义了实现其他流类的关键方法。最重要的两种方法是read()write(),它们分别对数据的字节进行读写。两种方法都在InputStream OutputStream中被定义为抽象方法。它们被派生的流类重载。

字符流类

字符流类由两个类层次结构定义。顶层有两个抽象类:ReaderWriter。这些抽象类处理统一编码的字符流。Java中这些类含有多个具体的子类。字符流类如表12-2所示。

12-2 字符流的输入/输出类

流类

含义

BufferedReader

缓冲输入字符流

BufferedWriter

缓冲输出字符流

CharArrayReader

从字符数组读取数据的输入流

CharArrayWriter

向字符数组写数据的输出流

FileReader

读取文件的输入流

FileWriter

写文件的输出流

FilterReader

过滤读

FilterWriter

过滤写

InputStreamReader

把字节转换成字符的输入流

LineNumberReader

计算行数的输入流

OutputStreamWriter

把字符转换成字节的输出流

PipedReader

输入管道

PipedWriter

输出管道

PrintWriter

包含print( )println( )的输出流

PushbackReader

允许字符返回到输入流的输入流

Reader

描述字符流输入的抽象类

StringReader

读取字符串的输入流

StringWriter

写字符串的输出流

Writer

描述字符流输出的抽象类

抽象类ReaderWriter定义了几个实现其他流类的关键方法。其中两个最重要的是read()write(),它们分别进行字符数据的读和写。这些方法被派生流类重载。

<chsdate w:st="on" year="1899" month="12" day="30" islunardate="False" isrocdate="False"><span style="mso-bookmark: _Toc528747350"><span lang="EN-US">12.1.3</span></span></chsdate> 预定义流

所有的Java程序自动导入java.lang包。该包定义了一个名为System的类,该类封装了运行时环境的多个方面。例如,使用它的某些方法,你能获得当前时间和与系统有关的不同属性。System 同时包含三个预定义的流变量,inouterr。这些成员在System中是被定义成public static型的,这意味着它们可以不引用特定的System对象而被用于程序的其他部分。

System.out是标准的输出流。默认情况下,它是一个控制台。System.in是标准输入,默认情况下,它指的是键盘。System.err指的是标准错误流,它默认是控制台。然而,这些流可以重定向到任何兼容的输入/输出设备。

System.in inputStream的对象;System.outSystem.errPrintStream的对象。它们都是字节流,尽管它们用来读写外设的字符。如果愿意,你可以用基于字符的流来包装它们。

前面的章节在例题中已经用到过System.out。你可以以同样的方式使用System.err。下面对此进行解释,你会看到使用System.in有一点复杂。

12.2 读取控制台输入

Java 1.0中,完成控制台输入的惟一途径是字节流,使用该方法的老代码依然存在。今天,运用字节流读取控制台输入在技术上仍是可行的,但这样做需要用到不被赞成的方法,这种做法不值得推荐。Java 2中读取控制台输入的首选方法是字符流,它使程序容易符合国际标准并且易于维护。

注意:Java没有像标准C的函数scanf()C++输入操作符那样的统一的控制台输入方法。

Java中,控制台输入由从System.in读取数据来完成。为获得属于控制台的字符流,在BufferedReader对象中包装System.inBufferedReader 支持缓冲输入流。它最常见的构造函数如下:

BufferedReader(Reader inputReader)

这里,inputReader是链接被创建的BufferedReader实例的流。Reader是一个抽象类。它的一个具体的子类是InputStreamReader,该子类把字节转换成字符。为获得链接System.in的一个InputStreamReader的对象,用下面的构造函数:

InputStreamReader(InputStream inputStream)

因为System.in引用了InputStream 类型的对象,它可以用于inputStream。综上所述,下面的一行代码

创建了与键盘相连的BufferedReader对象。

BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));

当该语句执行后,br是通过System.in生成的链接控制台的字符流。

<chsdate w:st="on" year="1899" month="12" day="30" islunardate="False" isrocdate="False"><span style="mso-bookmark: _Toc528747352"><span lang="EN-US">12.2.1</span></span></chsdate> 读取字符

BufferedReader读取字符,用read()。我们所用的read()版本如下:

int read() throws IOException

该方法每次执行都从输入流读取一个字符然后以整型返回。当遇到流的末尾时它返回-1。你可以看到,它要引发一个IOException异常。

下面的例子程序演示了read()方法,从控制台读取字符直到用户键入“q”:

// Use a BufferedReader to read characters from the console.
import java.io.*;

class BRRead {
public static void main(String args[])
throws IOException
{
char c;
BufferedReader br = new
BufferedReader(new InputStreamReader(System.in));
System.out.println("Enter characters, 'q' to quit.");


// read characters
do {
c = (char) br.read();
System.out.println(c);
} while(c != 'q');
}
}

下面是程序运行:

Enter characters, 'q' to quit.
123abcq
1
2
<chmetcnv w:st="on" unitname="a" sourcevalue="3" hasspace="False" negative="False" numbertype="1" tcsc="0">3<br></chmetcnv>a
b
c
q

程序的输出看起来与预想的略有不同,因为System.in在默认情况下是以行来缓冲的。这意味着在你键入ENTER以前实际上是没有输入的。你能猜想,这不能充分体现交互式控制台输入条件下read()的独特价值。

<chsdate w:st="on" year="1899" month="12" day="30" islunardate="False" isrocdate="False"><span style="mso-bookmark: _Toc528747353"><span lang="EN-US">12.2.2</span></span></chsdate> 读取字符串

从键盘读取字符串,使用readLine()。它是BufferedReader 类的成员。它的通常形式如下:

String readLine() throws IOException

它返回一个String对象。

下面的例子阐述了BufferedReader类和readLine()方法;程序读取和显示文本的行直到键入“stop”:

// Read a string from console using a BufferedReader.
import java.io.*;

class BRReadLines {
public static void main(String args[])

throws IOException
{
// create a BufferedReader using System.in
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
String str;

System.out.println("Enter lines of text.");
System.out.println("Enter 'stop' to quit.");
do {
str = br.readLine();
System.out.println(str);
} while(!str.equals("stop"));
}
}

下面的例题生成了一个小文本编辑器。它创建了一个String对象的数组,然后依行读取文本,把文本每一行存入数组。它将读取到100行或直到你按“stop”才停止。该例运用一个BufferedReader类来从控制台读取数据。

// A tiny editor.
import java.io.*;

class TinyEdit {
public static void main(String args[])
throws IOException
{
// create a BufferedReader using System.in
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
String str[] = new String[100];

System.out.println("Enter lines of text.");
System.out.println("Enter 'stop' to quit.");
for(int i=0; i<100; i++) {
str[i] = br.readLine();
if(str[i].equals("stop")) break;
}

System.out.println("/nHere is your file:");

// display the lines
for(int i=0; i<100; i++) {
if(str[i].equals("stop")) break;
System.out.println(str[i]);
}
}
}

下面是输出部分:

Enter lines of text.
Enter ‘stop’ to quit.
This is line one.

This is line two.
Java makes working with strings easy.
Just create String objects.
stop
Here is your file:
This is line one.
This is line two.
Java makes working with strings easy.
Just create String objects.

12.3 向控制台写输出

控制台输出由前面描述过的print() println()来完成最为简单,它们被用在本书的大多数例题中。这两种方法由PrintStream(System.out引用的对象类型)定义。尽管System.out是一个字节流,用它作为简单程序的输出是可行的。字符流输出在下节介绍。

因为PrintStream是从OutputStream派生的输出流,它同样实现低级方法write()write()可用来向控制台写数据。PrintStream 定义的write()的最简单的形式如下:

void write(int byteval)

该方法按照byteval指定的数向文件写字节。尽管byteval 定义成整数,但只有低位的8个字节被写入。下面的短例用 write()向屏幕输出字符“A”,然后是新的行。

// Demonstrate System.out.write().
class WriteDemo {
public static void main(String args[]) {
int b;

b = 'A';
System.out.write(b);
System.out.write('/n');
}
}

一般不常用write()来完成向控制台的输出(尽管这样做在某些场合非常有用),因为 print()println() 更容易用。

12.4 PrintWriter

尽管Java允许用System.out向控制台写数据,但建议仅用在调试程序时或在例题中,这在本书中得到充分体现。对于实际的程序,Java推荐向控制台写数据的方法是用PrintWriter流。PrintWriter是基于字符的类。用基于字符类向控制台写数据使程序更为国际化。

PrintWriter定义了多个构造函数,我们所用到的一个如下:

PrintWriter(OutputStream outputStream, boolean flushOnNewline)

这里,outputStreamOutputStream类的对象,flushOnNewline控制Java是否在println( )方法被调用时刷新输出流。如果flushOnNewlinetrue,刷新自动发生,若为false,则不发生。

PrintWriter支持所有类型(包括Object)的print()println()方法,这样,你就可以像用System.out那样用这些方法。如果遇到不同类型的情况,PrintWriter方法调用对象的toString()方法并打印结果。

PrintWriter向外设写数据,指定输出流为System.out并在每一新行后刷新流。例如这行代码创建了与控制台输出相连的PrintWriter类。

PrintWriter pw = new PrintWriter(System.out, true);

下面的应用程序说明了用PrintWriter处理控制台输出的方法:

// Demonstrate PrintWriter
import java.io.*;

public class PrintWriterDemo {
public static void main(String args[]) {
PrintWriter pw = new PrintWriter(System.out, true);
pw.println("This is a string");
int i = -7;
pw.println(i);
double d = 4.5e-7;
pw.println(d);
}
}

该程序的输出如下:

This is a string

-7

4.5E-7

记住,在你学习Java或调试程序时用System.out向控制台写简单文本输出是没有错的。但是使用PrintWriter使实际的应用程序更容易国际化。因为在本书所示的例题程序中用PrintWriter并没有多大的优势,所以我们继续用System.out来向控制台输出。

12.5 文件的读写

Java为你提供了一系列的读写文件的类和方法。在Java中,所有的文件都是字节形式的。Java提供从文件读写字节的方法。而且,Java允许在字符形式的对象中使用字节文件流。这项技术在第2部分描述。本章说明基本的文件输入/输出。

两个最常用的流类是FileInputStreamFileOutputStream,它们生成与文件链接的字节流。为打开文件,你只需创建这些类中某一个类的一个对象,在构造函数中以参数形式指定文件的名称。这两个类都支持其他形式的重载构造函数。下面是我们将要用到的形式:

FileInputStream(String fileName) throws FileNotFoundException
FileOutputStream(String fileName) throws FileNotFoundException

这里,fileName指定需要打开的文件名。当你创建了一个输入流而文件不存在时,引发FileNotFoundException异常。对于输出流,如果文件不能生成,则引发FileNotFound Exception异常。如果一个输出文件被打开,所有原先存在的同名的文件被破坏。

注意:在早期的Java版本中,当输出文件不能创建时FileOutputStream()引发一个IOException异常。这在Java 2中有所修改。

当你对文件的操作结束后,需要调用close()来关闭文件。该方法在FileInputStreamFileOutputStream中都有定义。如下:

void close() throws IOException

为读文件,可以使用在FileInputStream中定义的read()方法。我们用到的如下:

int read() throws IOException

该方法每次被调用,它仅从文件中读取一个字节并将该字节以整数形式返回。当读到文件尾时,read()

返回-1。该方法可以引发IOException异常。

下面的程序用read()来输入和显示文本文件的内容,该文件名以命令行形式指定。注意try/catch块处理程序运行时可能发生的两个错误——未找到指定的文件或用户忘记包括文件名了。当你用命令行时也可以用这样的方法。

/* Display a text file.

To use this program, specify the name
of the file that you want to see.
For example, to see a file called TEST.TXT,
use the following command line.

Java ShowFile TEST.TXT

*/

import java.io.*;

class ShowFile {
public static void main(String args[])
throws IOException
{
int i;
FileInputStream fin;

try {
fin = new FileInputStream(args[0]);
} catch(FileNotFoundException e) {
System.out.println("File Not Found");
return;
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("Usage: ShowFile File");
return;
}

// read characters until EOF is encountered
do {
i = fin.read();
if(i != -1) System.out.print((char) i);
} while(i != -1);

fin.close();
}
}

向文件中写数据,需用FileOutputStream定义的write()方法。它的最简单形式如下:

void write(int byteval) throws IOException

该方法按照byteval指定的数向文件写入字节。尽管byteval作为整数声明,但仅低8位字节可以写入文件。如果在写的过程中出现问题,一个IOException被引发。下面的例子用write()拷贝一个文本文件:

/* Copy a text file.

To use this program, specify the name
of the source file and the destination file.
For example, to copy a file called FIRST.TXT
to a file called SECOND.TXT, use the following
command line.

Java CopyFile FIRST.TXT SECOND.TXT
*/

import java.io.*;

class CopyFile {
public static void main(String args[])
throws IOException
{
int i;
FileInputStream fin;
FileOutputStream fout;

try {
// open input file
try {
fin = new FileInputStream(args[0]);
} catch(FileNotFoundException e) {
System.out.println("Input File Not Found");
return;
}

// open output file
try {
fout = new FileOutputStream(args[1]);
} catch(FileNotFoundException e) {
System.out.println("Error Opening Output File");
return;
}
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("Usage: CopyFile From To");
return;
}

// Copy File
try {
do {
i = fin.read();
if(i != -1) fout.write(i);
} while(i != -1);
} catch(IOException e) {
System.out.println("File Error");
}

fin.close();
fout.close();
}
}

注意本程序中和前面ShowFile程序中处理潜在输入/输出错误的方法。不像其他的计算机语言,包括CC++,这些语言用错误代码报告文件错误,而Java用异常处理机制。这样不仅是文件处理更为简洁,而且使Java正在执行输入时容易区分是文件出错还是EOF条件问题。在C/C++中,很多输入函数在出错时和到达文件结尾时返回相同的值(也就是说,在C/C++中,EOF情况与输入错误情况映射相同)。这通常意味着程序员必须还要编写特殊程序语句来判定究竟是哪种事件发生。Java中,错误通过异常引发,而不是通过read()的返回值。这样,当read()返回-1时,它仅表示一点:遇到了文件的结尾。

12.6 小应用程序基础

本书中前面所有的例子都是Java应用程序。然而,应用程序只是Java程序的一种。另一种类型的程序是applet(小应用程序)。如第1章提到的,小应用程序(applet)是访问internet服务器,在internet上传播的,自动安装的,作为部分Web文档运行的小应用程序。当小应用程序到达客户端,它被限制访问资源,

以使它能够在不受病毒威胁和破坏数据完整性的情况下生成一个二进制的多媒体用户界面以及完成复杂的计算。

用到applet包时,很多关于创建和使用小应用程序的问题会在第2部分见到。然而,有关创建小应用程序的基础问题在这里描述,因为小应用程序与以前所用的程序具有不同的结构。你将看到,小应用程序在几处关键地方与应用程序不同。

让我们从下面的简单小应用程序开始:

import java.awt.*;
import java.applet.*;

public class SimpleApplet extends Applet {
public void paint(Graphics g) {
g.drawString("A Simple Applet", 20, 20);
}
}

这个小应用程序以两个import语句开始。第一个导入抽象窗口工具集(AWT)类。小应用程序是通过AWT与用户交流的,而不是通过基于控制台的输入/输出类。AWT包含了对基于视窗的图形界面的支持。你能猜想,AWT是非常庞大和复杂的,关于它的详尽的讨论在本书的第2部分花了好几章。幸运的是,这个简单的小应用程序仅用到了AWT的一点点内容。第二个import语句导入了applet包,该包包含Applet类。每一个小应用程序都必须是Applet的子类。

程序的下面一行声明了SimpleApplet类。该类必须为public型,因为它的代码会在程序外面被引用。

SimpleApplet内部声明了paint()。该方法由AWT定义且必须被小应用程序重载。小应用程序每次重新显示输出时都要调用paint()。发生这种情况有多种原因。例如,小应用程序运行的窗口可以被另一窗口重写然后覆盖。或者,小应用程序窗口可以最小化然后恢复。paint()方法在小应用程序启动时也被调用。无论什么原因,当小应用程序需要重画输出时,paint()总被调用。paint()方法有一个Graphics类型的参数,该参数包含描绘小应用程序运行的图形环境的内容。一旦小应用程序需要输出,该内容被用到。

paint()内调用Graphics类成员drawString(),该方法从指定的XY坐标处输出一个字符串。它有下面的常用形式:

void drawString(String message, int x, int y)

这里message是以x,y为输出起点的字符串。在Java窗口中,左上角的位置为00。在小应用程序中DrawString()的调用使得在坐标2020处开始显示消息“A Simple Applet”。

注意小应用程序没有main()方法,不像Java应用程序,小应用程序不以main()为程序起始。实际上,大多数小应用程序甚至不含main()方法。相反,当小应用程序类名被传输到小应用程序阅读器(applet view)或网络浏览器时它开始执行。

在你键入SimpleApplet的源代码后,用你以前编译程序的方法编译该程序。但是,运行SimpleApplet包含一个完全不同的过程。实际上,有两种方法可以运行小应用程序。

· 在一个兼容Java的网络浏览器,例如Netscape Navigator中执行小应用程序

· 使用小应用程序阅读器,例如标准JDK工具,小应用程序阅览器(appletviewer)。一个小应用程序阅读器在窗口中执行小应用程序。这是检测小应用程序最快和最简单的方法。

接下来在下面详细阐述上述两种方法。

若在一个网络浏览器中执行小应用程序,需要编写包含适当APPLET标记的简短的HTML文档。下面是执行SimpleAppletHTML文件:

<applet code="SimpleApplet" width=200 height=60>
</applet>

width height语句指定了小应用程序用到的显示区域的尺寸(APPLET标记包括几个其他的选项,这

在第2部分有详细的描述)。创建文件后,你可以启动浏览器并加载可以执行SimpleApplet的文件。

若使用小应用程序阅读器执行SimpleApplet,你也需执行前面的HTML文件。例如前面所述的HTML文档叫做RunApp.html,则下面的命令行将运行SimpleApplet

C:/>appletviewer RunApp.html

然而,存在一个更方便的方法使测试更快的完成。仅仅在你包含APPLET标记的Java源代码的开头加入一个命令。这样做,你的代码就被一个必要的HTML语句原型证明,你只需启动含有JAVA源码文件的小应用程序阅读器就可以测试你编译过的小应用程序。如果你使用该方法,SimpleApplet源文件如下:

import java.awt.*;
import java.applet.*;
/*
<applet code="SimpleApplet" width=200 height=60>
</applet>
*/

public class SimpleApplet extends Applet {
public void paint(Graphics g) {
g.drawString("A Simple Applet", 20, 20);
}
}

总的来说,你可以使用下面三步来应用小应用程序:

1. 编写Java源程序。

2. 编译程序。

3. 执行小应用程序阅览器,指定小应用程序源文件名称。小应用程序阅览器将在注释中遇到APPLET标记并执行小应用程序。

SimpleApplet生成的窗口,在小应用程序阅览器中显示。该窗口如下面插图:

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_s1026" style="MARGIN-TOP: 6.8pt; Z-INDEX: 1; LEFT: 0px; MARGIN-LEFT: 0px; WIDTH: 171.1pt; POSITION: absolute; HEIGHT: 72.7pt; TEXT-ALIGN: left; mso-wrap-edited: f; mso-position-horizontal: center" type="#_x0000_t75" wrapcoords="-66 0 -66 21482 21600 21482 21600 0 -66 0"><imagedata o:title="" src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/03/clip_image001.wmz"><font size="3"></font></imagedata></shape>


关于小应用程序的专题在本书后面有更详尽的讨论,下面是需要记住的关键点:

· 小应用程序不一定包含 main() 方法。

· 小应用程序必须在小应用程序阅读器或兼容JAVA的浏览器中运行。

· 用户输入/输出不是由Java的输入/输出流类来完成的。相反,小应用程序运用
AWT
提供的界面。

12.7 Transientvolatile修饰符

Java定义了两类有趣的修饰符:transientvolatile,这些修饰符用来处理特殊的情况。

如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。例如:

class T {
transient int a; // will not persist
int b; // will persist
}

这里,如果T类的一个对象被写入一个持久的存储区域,a的内容不被保存,但b将被保存。

Volatile修饰符告诉编译器被volatile修饰的变量可以被程序的其他部分改变。一种这样的情形是多线程程序(参看第11章的例子)。在多线程程序里,有时两个或更多的线程共享一个相同的实例变量。考虑效率的问题,每个线程可以自己保存该共享变量的私有拷贝。实际的(或主要的)变量副本在不同的时候更新,例如当进入synchronized方法时。当这种方式运行良好时,它在时间上会是低效的。在某些情况,真正要紧的是变量主副本的值会体现当前的状态。为保证这点,仅需把变量定义成volatile型,它告诉编译器它必须总是使用volatile变量的主副本(或者至少总是保持一些私有的最新的主副本的拷贝,反之亦然),同时,对主变量的获取必须以简洁次序执行,就像执行私有拷贝一样。

注意:Java中的volatile或多或少与C/C++中的类似。

12.8 使用instanceof

有时,在运行时间内知道对象类型是很有用的。例如,你有一个执行线程生成各种类型的对象,其他线程处理这些对象。这种情况下,让处理线程在接受对象时知道每一个对象的类型是大有裨益的。另一种在运行时间内知道对象的类型是很有用的情形是强制类型转换。Java中非法强制类型转换导致运行时错误。很多非法的强制类型转换在编译时发生。然而包括类层次结构的强制类型转换可能产生仅能在运行时间里被察觉的非法强制类型转换。例如,一个名为A的父类能生成两个子类BC。这样,在强制B对象转换为类型A或强制C对象转换为类型A都是合法的,但强制B对象转换为C对象(或相反)都是不合法的。因为类型A的一个对象可以引用BC。但是你怎么知道,在运行时,在强制转换为C之前哪类对象被引用?它可能是ABC的一个对象。如果它是B的对象,一个运行时异常被引发。Java 提供运行时运算符instanceof来解决这个问题。

instanceof运算符具有下面的一般形式:

object instanceof type

这里,object是类的实例,而type是类的类型。如果object是指定的类型或者可以被强制转换成指定类型,instanceof将它评估成true,若不是,则结果为false。这样,instanceof是你的程序获得对象运行时类型信息的方法。

下面的程序说明了instanceof的应用:

// Demonstrate instanceof operator.
class A {
int i, j;
}

class B {
int i, j;
}

class C extends A {
int k;
}

class D extends A {
int k;
}

class InstanceOf {
public static void main(String args[]) {
A a = new A();
B b = new B();

C c = new C();
D d = new D();

if(a instanceof A)
System.out.println("a is instance of A");
if(b instanceof B)
System.out.println("b is instance of B");
if(c instanceof C)
System.out.println("c is instance of C");
if(c instanceof A)

System.out.println("c can be cast to A");

if(a instanceof C)
System.out.println("a can be cast to C");

System.out.println();

// compare types of derived types
A ob;

ob = d; // A reference to d
System.out.println("ob now refers to d");
if(ob instanceof D)
System.out.println("ob is instance of D");

System.out.println();

ob = c; // A reference to c
System.out.println("ob now refers to c");

if(ob instanceof D)
System.out.println("ob can be cast to D");
else
System.out.println("ob cannot be cast to D");

if(ob instanceof A)
System.out.println("ob can be cast to A");

System.out.println();

// all objects can be cast to Object
if(a instanceof Object)
System.out.println("a may be cast to Object");
if(b instanceof Object)
System.out.println("b may be cast to Object");
if(c instanceof Object)
System.out.println("c may be cast to Object");
if(d instanceof Object)
System.out.println("d may be cast to Object");
}
}

程序输出如下:

a is instance of A
b is instance of B
c is instance of C
c can be cast to A

ob now refers to d
ob is instance of D

ob now refers to c
ob cannot be cast to D

ob can be cast to A

a may be cast to Object
b may be cast to Object
c may be cast to Object
d may be cast to Object

多数程序不需要instanceof运算符,因为,一般来说,你知道你正在使用的对象类型。但是,在你编写对复杂类层次结构对象进行操作的通用程序时它是非常有用的。

12.9 strictfp

Java 2Java语言增加了一个新的关键字strictfp。与Java 2同时产生的浮点运算计算模型很轻松的使某些处理器可以以较快速度进行浮点运算例如奔腾处理器。特别指明,在计算过程中,一个不需要切断某些中介值的新的模型产生了。用strictfp来修饰类或方法,可以确保浮点运算(以及所有切断)正如它们在早期Java版本中那样准确。切断只影响某些操作的指数。当一个类被strictfp修饰,所有该类的方法都自动被strictfp修饰。

举例来说,下面的程序段告诉JavaMyClass中定义的所有方法都用原始浮点运算模型来计算:

strictfp class MyClass { //...

坦白地说,很多程序员从未用过strictfp,因为它只对非常少的问题有影响。

12.10

尽管这种情况极少发生,你也许希望调用不是用Java语言写的子程序。通常,这样的子程序是CPU的或是你所工作环境的执行代码——也就是说,本机代码。例如,你希望调用本机代码子程序来获得较快的执行时间。或者,你希望用一个专用的第三方的库,例如统计学包。然而,因为Java程序被编译为字节码,字节码由Java运行时系统解释(或动态编译),看起来在Java程序中调用本机代码子程序是不可能的。幸运的是,这个结论是错误的。Java提供了native关键字,该关键字用来声明本机代码方法。一旦声明,这些方法可以在Java程序中被调用,就像调用其他Java方法一样。

为声明一个本机方法,在该方法之前用native修饰符,但是不要定义任何方法体。例如:

public native int meth() ;

声明本机方法后,必须编写本机方法并要执行一系列复杂的步骤使它与Java代码链接。

很多本机方法是用C写的。把C代码结合到Java 程序中的机制是调用Java Native Interface (JNI)。该方法学由Java 1.1创建并在Java 2中增强。(Java 1.0是用不同的方法,该方法已经过时),关于JNI的详尽描述超出了本书的范围。但是下面的描述为多数应用程序提供了足够的信息。

注意:所需执行的精确的步骤随Java 环境和版本的不同而不同,它还依赖于所要实现的本机方法使用的语言。下面的讨论假定在Windows 95/98/NT/2000环境下。所要实现的本机方法是用C写的。

理解该过程的最简单的方法是完成一个例子。开始,输入下面的短程序,该程序使用了一个名为test()native方法。

// A simple example that uses a native method.
public class NativeDemo {
int i;
public static void main(String args[]) {
NativeDemo ob = new NativeDemo();

ob.i = 10;
System.out.println("This is ob.i before the native method:" +
ob.i);
ob.test(); // call a native method
System.out.println("This is ob.i after the native method:" +
ob.i);
}
// declare native method
public native void test() ;

// load DLL that contains static method
static {
System.loadLibrary("NativeDemo");
}
}

注意test()方法声明为native且不含方法体。简而言之这是我们用C语言实现的方法。同时注意static块。像本书前面解释过的,一个static块仅在程序开始执行时执行(更为简单的说,当它的类被加载时执行)。这种情况下,它用来加载包含本地执行方法test()的动态链接库(你不久就会看到怎样创建这个库)。

该库由loadLibrary()方法加载。loadLibrary()方法是System类的组成单元。它的一般形式为:

static void loadLibrary(String filename)

这里,filename是指定保存该库文件名的字符串。在Windows环境下,该文件的扩展名为.DLL

写完程序后,编译它生成NativeDemo.class。然后,你必须用javah.exe生成一个文件:NativeDemo.hjavah.exe包含在JDK中)。在执行test()时你要包含NativeDemo.h。为生成NativeDemo.h,用下面的命令:

javah -jni NativeDemo

该命令生成名为NativeDemo.h的头文件。该文件必须包含在实现test()C文件中。该命令的输出结果如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class NativeDemo */

#ifndef _Included_NativeDemo
#define _Included_NativeDemo
#ifdef _ _cplusplus
extern
"C" {
#endif
/*
* Class: NativeDemo
* Method: test
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_NativeDemo_test
(JNIEnv *, jobject);

#ifdef _ _cplusplus
}
#endif
#endif

请特别注意下面一行,该行定义了所要创建的test()函数的原型:

JNIEXPORT void JNICALL Java_NativeDemo_test(JNIEnv *, jobject);

注意函数的名称是Java_NativeDemo_test()。调用本机函数你必须用这样的名字。也就是说,不是生成一个名为test()C函数,而是创建一个名为Java_NativeDemo_test()函数。加入前缀NativeDemo是因为它把test()方法作为NativeDemo类的一部分。记住,其他类可以定义它们自己的与NativeDemo定义的完全

不同的本地test()方法。前缀中包括类名的方法解决了区分不同版本的问题。作为一个常规方法,给本机函数取名,前缀中必须包括声明它们的类名。

生成了必备的头文件后,可以编写test()执行文件并把它存在一个名为NativeDemo.c的文件中:

/* This file contains the C version of the
test() method.
*/

#include <jni.h>
#include "NativeDemo.h"
#include <stdio.h>


JNIEXPORT void JNICALL Java_NativeDemo_test(JNIEnv *env, jobject obj)
{
jclass cls;
jfieldID fid;
jint i;

printf("Starting the native method./n");
cls = (*env)->GetObjectClass(env, obj);
fid = (*env)->GetFieldID(env, cls, "i", "I");

if(fid == 0) {
printf("Could not get field id./n");
return;
}
i = (*env)->GetIntField(env, obj, fid);
printf("i = %d/n", i);
(*env)->SetIntField(env, obj, fid, 2*i);
printf("Ending the native method./n");
}

注意此文件包含具有接口信息的jni.h文件。该文件由你的Java 编译器提供。头文件NativeDemo.h预先已由javah创建。

该函数中,GetObjectClass()方法用来获得一个含有NativeDemo类信息的C结构。GetFieldID()方法返回一个包含该类域名“i”信息的C结构。GetIntField()检索该域原来的值。SetIntField()存储该域的一个更新值(别的处理其他数据类型的方法参看文件jni.h)。

生成NativeDemo.c文件后,必须编译它生成一个DLL文件。用微软C/C++编译器来做,使用下面的命令行:

Cl /LD NativeDemo.c

它生成了一个名为NativeDemo.dll的文件。该步骤完成,你可以执行Java 程序。该程序输出如下:

This is ob.i before the native method: 10
Starting the native method.
i = 10
Ending the native method.
This is ob.i after the native method: 20

注意:使用native的特殊环境是依赖于实现和环境的。而且,与JAVA代码接口的指定方式必须改变。你必须仔细考虑完成你Java开发系统文件的本机方法。

12.11 使用本机方法的问题

本机方法看起来提供巨大承诺,因为它们使你有权使用已经存在的库程序,而且使快速执行成为可能。但是本机方法同样引入了两个重大问题:

· 潜在的安全隐患因为本机方法执行实际的机器代码,它有权使用主机系统的任何资源。也就是说,本机代码不受Java执行环境的限制。例如,它可能允许病毒入侵。因为这个原因,小应用程序不能使用本机方法。同样,DLL文件的加载被限制,它们的加载必须经过安全管理器的同意。

· 丧失了可移植性 因为本机代码是包含在DLL文件中的,它必须存在于执行Java程序的机器上。而且,因为每一个本机方法都依赖于CPU和操作系统,每一个DLL文件在本质上都是不可可移植性的。这样,一个运用本机方法的Java程序只能在一个已经安装了可兼容的DLL的机器上运行。

本机方法的使用是受限的,因为它使Java程序丧失了可移植性且造成重大安全隐患。

分享到:
评论

相关推荐

    java基础教程.pdf

    全书共分11章:Java语言概述、Java语言基础、类与对象、继承与接口、数组与字符串、Java的异常处理机制、Java常见类库、输入输出及数据库操作、多线程、Applet程序及应用和图形用户界面设计。  本书内容实用,结构...

    Java基础入门学习教程PPT课件 JAVA语言程序设计 JAVA_03 JAVA语言基础 位运算(共21页).ppt

    JAVA_7 IO输入输出 JAVA_8 AWT图形界面编程 JFC AWT Swing JAVA_8 AWT图形界面编程 JFC Swing JAVA_9 Applet JAVA_10 线程 JAVA_11 网络程序设计 JAVA_12 JFC Swing JAVA_13 JDBC JAVA_13 JDBC编程

    Java清华教程.rar

     ■ Java语言的输入/输出处理机制和方法,常用的输入/输出方法,输入/输出处理的应用;  ■ Java语言的图形用户界面设计:AWT界面设计的基本方法,常用的组件类库,图形用户界面的事件处理模型和方法,JFC介绍,...

    JAVA清华教程 详细

     ■ Java语言的输入/输出处理机制和方法,常用的输入/输出方法,输入/输出处理的应用;  ■ Java语言的图形用户界面设计:AWT界面设计的基本方法,常用的组件类库,图形用户界面的事件处理模型和方法,JFC介绍,...

    完整版 Java高级教程 Java语言程序设计 第7章 Java注解(共10页).ppt

    完整版 Java高级教程 Java语言程序设计 第1章 输入输出流(共42页).ppt 完整版 Java高级教程 Java语言程序设计 第2章 Java多线程(共24页).ppt 完整版 Java高级教程 Java语言程序设计 第2章 哲学家的故事(共7页)...

    Java语言基础 Java网络编程技术 Java程序设计使用教程 第10章 网络通信(共53页).ppt

    【完整课程列表】 ...Java语言基础 Java网络编程技术 Java程序设计使用教程 第09章 输入输出流和文件操作(共37页).ppt Java语言基础 Java网络编程技术 Java程序设计使用教程 第10章 网络通信(共53页).ppt

    清华大学java教程

     ■ Java语言的输入/输出处理机制和方法,常用的输入/输出方法,输入/输出处理的应用;  ■ Java语言的图形用户界面设计:AWT界面设计的基本方法,常用的组件类库,图形用户界面的事件处理模型和方法,JFC介绍,...

    完整版 Java高级教程 Java语言程序设计 第8章 JDBC(共24页).ppt

    完整版 Java高级教程 Java语言程序设计 第1章 输入输出流(共42页).ppt 完整版 Java高级教程 Java语言程序设计 第2章 Java多线程(共24页).ppt 完整版 Java高级教程 Java语言程序设计 第2章 哲学家的故事(共7页)...

    Java基础入门教程 第7章 Java中的异常处理(共26页).ppt

    【完整课程列表】 ... Java基础入门教程 第1章 Java 简介...Java基础入门教程 第8章 文件流的输入输出操作(共37页).ppt Java基础入门教程 第9章 Java的高级特性(共32页).ppt jdk api 1.7.chm JDK_API_1_6_zh_CN.CHM

    Java2实用教程提取码

    Java语言不仅可以用来开发大型的应用程序,而且特别适合于在Internet上应用开发,Java已成为网络时代最重要的编程语言之一。 第1章 Java入门 第2章 标识符、关键字和数据类型 第3章 运算符、表达式和语句 第4章 类、...

    Java语言基础 Java网络编程技术 Java程序设计使用教程 全套PPT课件资源 共10个章节 含全部源代码.rar

    Java网络编程技术 Java程序设计使用教程 第08章 Applet应用程序(共34页).pptJava语言基础 Java网络编程技术 Java程序设计使用教程 第09章 输入输出流和文件操作(共37页).pptJava语言基础 Java网络编程技术 Java...

    [清华大学]JAVA教程

     ■ Java语言的输入/输出处理机制和方法,常用的输入/输出方法,输入/输出处理的应用;  ■ Java语言的图形用户界面设计:AWT界面设计的基本方法,常用的组件类库,图形用户界面的事件处理模型和方法,JFC介绍,...

    JAVA 7 程序设计.part1.rar

    接着阐述Swing的基础和高级知识、多态、注解、国际化、Java网络、JDBC、Java线程、并发工具、安全、Java Web应用程序、JavaServer Pages、Javadoc,以及应用程序部署等内容,掌握这部分内容有助于提升编程技能。

    Java语言实用教程

    本书共12章,从程序设计方法讲起,详细介绍了Java开发环境、语法知识、数组、类、对象、继承、接口等面向对象程序设计和开发的知识及应用,同时包括异常处理、输入输出方法、图形图像、多媒体、Applet等方面的内容。...

    JAVA语言程序设计教程 Java基础入门教程 杭电Java程序设计课程全套课件 共13个章节 含上机练习题和全部源代码.rar

    07 IO输入输出(共30页) 08 AWT图形界面编程 JFC AWT Swing(共56页) 08 AWT图形界面编程 JFC Swing (共22页) 09 Applet (共33页) 10 线程(共51页) 11 网络程序设计(共26页) 12 JFC Swing (共20页) 13 ...

    CoreJava基础课程课件(附源码、练习题)

    应用程序开发基础-输入和输出【】2d9e3f38327df1d553bb59590d5cacb2【】f86fd7745b3ab3ef4f50e8918e756adb【】java入门经典练习题【】Java面试宝典2011版【】[Core.Java基础教程]_杨帆_ 资源太大,传百度网盘了,...

    《Java程序设计案例教程》教学课件02Java语言基础.pptx

    模块2 Java语言基础 《Java程序设计案例教程》教学课件02Java语言基础全文共48页,当前为第1页。 学习目标 01 掌握标识符、关键字、分隔符、变量和常量的含义和使用方法。 02 了解Java注释的格式和代码书写风格。 03...

    Java Web程序设计教程

    &lt;&lt;Java Web程序设计教程.pdf&gt;&gt;人民邮电出版社的教程哦,所以,好书,你懂的!! 第1章web应用开发简介 1 1.1何为web应用 1 1.1.1web的概念及发展 1 1.1.2web应用程序 2 1.2使用java开发web应用 3 1.2.1面向对象...

    Java 中文入门学习手册合集[chm版]

    第四章 Java应用程序的基本框架 第五章 Java的类 第六章 Java图形用户接口 第七章 多线程 第八章 Java的"异常" 第九章 Java输入输出操作 java新手教程.chm JAVA_精通swing程序设计.chm JAVA_类库中文版Java ...

    Java语言程序设计实验指导书

    7 Java流 用输入输出流实现从一个文本文件中读写简单数据。 8 泛型与容器 编程实现set、list与map的简单应用。 9 图形用户界面 用图形界面工具,结合事件处理机制,实现一个可视化的计算器。 10 JDBC基础 使用JDBC...

Global site tag (gtag.js) - Google Analytics