|
视频课堂:【免费】JAVA面向对象高级编程-1-数组的学习-张晨光的在线视频教程-CSDN程序员研修院
Java程序通过流来完成输入/输出。流是生产或消费信息的抽象。流通过Java的输入/输出系统与物理设备链接。尽管与它们链接的物理设备不尽相同,所有流的行为具有同样的方式。这样,相同的输入/输出类和方法适用于所有类型的外部设备。这意味着一个输入流能够抽象多种不同类型的输入:从磁盘文件,从键盘或从网络套接字。同样,一个输出流可以输出到控制台,磁盘文件或相连的网络。流是处理输入/输出的一个洁净的方法,例如它不需要代码理解键盘和网络的不同。 Java中流的实现是在java.io包定义的类层次结构内部的。
注意:如果你熟悉C/C++,你已经对流的概念很熟悉了。JAVA中流的实现跟C/C++中有些相似。
|
Java 2 定义了两种类型的流:字节类和字符类。字节流(byte stream)为处理字节的输入和输出提供了方便的方法。例如使用字节流读取或书写二进制数据。字符流(character stream)为字符的输入和输出处理提供了方便。它们采用了统一的编码标准,因而可以国际化。当然,在某些场合,字符流比字节流更有效。
Java的原始版本(Java 1.0)不包括字符流,因此所有的输入和输出都是以字节为单位的。Java 1.1中加入了字符流,某些字节形式的类和方法不受欢迎。这也是为什么没用字符流的老代码在适当的地方需要更新的原因。
需要声明:在最底层,所有的输入/输出都是字节形式的。基于字符的流只为处理字符提供方便有效的方法。下面是对字节流和字符流的概述。
字节流类
字节流由两个类层次结构定义。在顶层有两个抽象类:InputStream 和 OutputStream。
每个抽象类都有多个具体的子类,这些子类对不同的外设进行处理,例如磁盘文件,网络连接,甚至是内存缓冲区。字节流类显示于表9-1中。本章的后面将讨论一些这样的类。其他的类的描述在第2部分。记住,要使用流类,必须导入Java.io包。
表 9-1 字节流类
抽象类InputStream和 OutputStream定义了实现其他流类的关键方法。 最重要的两种方法是read()和write(),它们分别对数据的字节进行读写。两种方法都在InputStream 和OutputStream中被定义为抽象方法。它们被派生的流类重载。
字符流类
字符流类由两个类层次结构定义。顶层有两个抽象类:Reader和Writer。这些抽象类处理统一编码的字符流。Java中这些类含有多个具体的子类。字符流类如表9-2所示。
表 9-2 字符流的输入/输出类
抽象类Reader和Writer定义了几个实现其他流类的关键方法。 其中两个最重要的是read()和write(),它们分别进行字符数据的读和写。这些方法被派生流类重载。
|
所有的Java程序自动导入java.lang包。该包定义了一个名为System的类,该类封装了运行时环境的多个方面。例如,使用它的某些方法,你能获得当前时间和与系统有关的不同属性。System 同时包含三个预定义的流变量,in,out和err。这些成员在System中是被定义成public 和static型的,这意味着它们可以不引用特定的System对象而被用于程序的其他部分。
System.out是标准的输出流。默认情况下,它是一个控制台。System.in是标准输入,默认情况下,它指的是键盘。System.err指的是标准错误流,它默认是控制台。然而,这些流可以重定向到任何兼容的输入/输出设备。
System.in 是inputStream的对象;System.out和System.err是PrintStream的对象。它们都是字节流,尽管它们用来读写外设的字符。如果愿意,你可以用基于字符的流来包装它们。
前面的章节在例题中已经用到过System.out。你可以以同样的方式使用System.err。下面对此进行解释,你会看到使用System.in有一点复杂。
|
在Java 1.0中,完成控制台输入的惟一途径是字节流,使用该方法的老代码依然存在。今天,运用字节流读取控制台输入在技术上仍是可行的,但这样做需要用到不被赞成的方法,这种做法不值得推荐。Java 2中读取控制台输入的首选方法是字符流,它使程序容易符合国际标准并且易于维护。
注意: Java没有像标准C的函数scanf()或C++输入操作符那样的统一的控制台输入方法。
Java中,控制台输入由从System.in读取数据来完成。为获得属于控制台的字符流,在BufferedReader对象中包装System.in。BufferedReader 支持缓冲输入流。它最常见的构造函数如下:
BufferedReader(Reader inputReader)
这里,inputReader是链接被创建的BufferedReader实例的流。Reader是一个抽象类。它的一个具体的子类是InputStreamReader,该子类把字节转换成字符。为获得链接System.in的一个InputStreamReader的对象,用下面的构造函数:
InputStreamReader(InputStreaminputStream) 因为System .in引用了InputStream 类型的对象,它可以用于inputStream。综上所述,下面的一行代码创建了与键盘相连的BufferedReader对象。
BufferedReader br = newBufferedReader(new InputStreamReader(System.in));
当该语句执行后,br是通过System.in生成的链接控制台的字符流。
读取字符
从BufferedReader读取字符,用read()。我们所用的read()版本如下:
int read( ) throws IOException
该方法每次执行都从输入流读取一个字符然后以整型返回。当遇到流的末尾时它返回-1。你可以看到,它要引发一个IOException异常。
下面的例子程序演示了read()方法,从控制台读取字符直到用户键入“q”:
// Use a BufferedReader to readcharacters from the console. import java.io.*; class BRRead { public static void main(String args[]) throws IOException { char c; BufferedReader br = new BufferedReader(newInputStreamReader(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
3
a
b
c
q
程序的输出看起来与预想的略有不同,因为System.in在默认情况下是以行来缓冲的。
这意味着在你键入ENTER以前实际上是没有输入的。你能猜想,这不能充分体现交互式控制台输入条件下read()的独特价值。
|
从键盘读取字符串,使用readLine()。它是BufferedReader 类的成员。它的通常形式如下:
String readLine( ) throwsIOException
它返回一个String对象。
下面的例子阐述了BufferedReader类和readLine()方法; 程序读取和显示文本的行直到键入“stop”:
// Read a string from consoleusing a BufferedReader. import java.io.*; class BRReadLines { public static void main(String args[]) throwsIOException { // create a BufferedReader using System.in BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String str; System.out.println("Enter lines oftext."); System.out.println("Enter 'stop' toquit."); do { str = br.readLine(); System.out.println(str); } while(!str.equals("stop")); } }
下面的例题生成了一个小文本编辑器。它创建了一个String对象的数组,然后依行读取文本,把文本每一行存入数组。它将读取到100行或直到你按“stop”才停止。该例运用一个BufferedReader类来从控制台读取数据。
// Atiny editor. importjava.io.*; classTinyEdit { 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 oftext."); System.out.println("Enter 'stop' toquit."); for(int i=0; i<100; i++) { str[i] = br.readLine(); if(str[i].equals("stop")) break; } System.out.println("\nHere is yourfile:"); // display the lines for(int i=0; i<100; i++) { if(str[i].equals("stop"))break; System.out.println(str[i]); } } }
下面是输出部分:
Enterlines of text.
Enter‘stop’ to quit.
This isline one.
This isline two.
Javamakes working with strings easy.
Justcreate String objects.
stop
Here isyour file:
This isline one.
This isline two.
Javamakes working with strings easy.
Justcreate String objects.
|
控制台输出由前面描述过的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( ) 更容易用。
|
尽管Java允许用System.out向控制台写数据,但建议仅用在调试程序时或在例题中,这在本书中得到充分体现。对于实际的程序,Java推荐的向控制台写数据的方法是用PrintWriter流。PrintWriter是基于字符的类。用基于字符类向控制台写数据使程序更为国际化。
PrintWriter定义了多个构造函数,我们所用到的一个如下:
PrintWriter(OutputStreamoutputStream, boolean flushOnNewline)
这里,outputStream是OutputStream类的对象,flushOnNewline控制Java是否在println( ) 方法被调用时刷新输出流。如果flushOnNewline为true,刷新自动发生,若为false,则不发生。
PrintWriter支持所有类型(包括Object)的print( )和println( )方法,这样,你就可以像用System.out那样用这些方法。 如果遇到不同类型的情况, PrintWriter方法调用对象的toString( )方法并打印结果。
用PrintWriter向外设写数据,指定输出流为System.out并在每一新行后刷新流。例如这行代码创建了与控制台输出相连的PrintWriter类。
PrintWriter pw = newPrintWriter(System.out, true);
下面的应用程序说明了用PrintWriter处理控制台输出的方法:
// Demonstrate PrintWriter import java.io.*; public class PrintWriterDemo { public static void main(String args[]) { PrintWriter pw = newPrintWriter(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 astring
-7
4.5E-7
记住,在你学习Java或调试程序时用System.out向控制台写简单文本输出是没有错的。但是使用PrintWriter使实际的应用程序更容易国际化。因为在本书所示的例题程序中用PrintWriter并没有多大的优势,所以我们继续用System.out来向控制台输出。
|
Java为你提供了一系列的读写文件的类和方法。在Java中,所有的文件都是字节形式的。
Java提供从文件读写字节的方法。而且,Java允许在字符形式的对象中使用字节文件流。这项技术在第2部分描述。本章说明基本的文件输入/输出。
两个最常用的流类是FileInputStream和FileOutputStream,它们生成与文件链接的字节流。为打开文件,你只需创建这些类中某一个类的一个对象,在构造函数中以参数形式指定文件的名称。这两个类都支持其他形式的重载构造函数。下面是我们将要用到的形式:
FileInputStream(String fileName)throws FileNotFoundException
FileOutputStream(String fileName)throws FileNotFoundException
这里,fileName指定需要打开的文件名。当你创建了一个输入流而文件不存在时,引发FileNotFoundException异常。对于输出流,如果文件不能生成,则引发FileNotFoundException异常。如果一个输出文件被打开,所有原先存在的同名的文件被破坏。
注意:在早期的Java版本中,当输出文件不能创建时FileOutputStream()引发一
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 NotFound"); return; } catch(ArrayIndexOutOfBoundsException e) { System.out.println("Usage: ShowFileFile"); return; } // read characters until EOF isencountered do { i = fin.read(); if(i != -1) System.out.print((char)i); } while(i != -1); fin.close(); } }
向文件中写数据,需用FileOutputStream定义的write()方法。它的最简单形式如下:
void write(int byteval) throwsIOException
该方法按照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 NotFound"); return; } // open output file try { fout = new FileOutputStream(args[1]); } catch(FileNotFoundException e) { System.out.println("Error OpeningOutput File"); return; } } catch(ArrayIndexOutOfBoundsException e) { System.out.println("Usage: CopyFileFrom To"); return; } // Copy File try { do { i = fin.read(); if(i != -1) fout.write(i); } while(i != -1); } catch(IOException e) { System.out.println("FileError"); } fin.close(); fout.close(); } }
注意本程序中和前面ShowFile程序中处理潜在输入/输出错误的方法。不像其他的计算机语言,包括C和C++,这些语言用错误代码报告文件错误,而Java用异常处理机制。这样不仅是文件处理更为简洁,而且使Java正在执行输入时容易区分是文件出错还是EOF条件问题。在C/C++中,很多输入函数在出错时和到达文件结尾时返回相同的值(也就是说,在C/C++中,EOF情况与输入错误情况映射相同)。这通常意味着程序员必须还要编写特殊程序语句来判定究竟是哪种事件发生。Java中,错误通过异常引发,而不是通过read( )的返回值。这样,当read( )返回-1时,它仅表示一点:遇到了文件的结尾。
|
本书中前面所有的例子都是Java应用程序。然而,应用程序只是Java程序的一种。另一种类型的程序是applet(小应用程序)。如第1章提到的,小应用程序(applet)是访问internet服务器,在internet上传播的,自动安装的,作为部分Web文档运行的小应用程序。当小应用程序到达客户端,它被限制访问资源,以使它能够在不受病毒威胁和破坏数据完整性的情况下生成一个二进制的多媒体用户界面以及完成复杂的计算。
用到applet包时,很多关于创建和使用小应用程序的问题会在第2部分见到。然而,有关创建小应用程序的基础问题在这里描述,因为小应用程序与以前所用的程序具有不同的结构。你将看到,小应用程序在几处关键地方与应用程序不同。
让我们从下面的简单小应用程序开始:
importjava.awt.*; importjava.applet.*; public class SimpleApplet extendsApplet { public void paint(Graphics g) { g.drawString("ASimple 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(),该方法从指定的X,Y坐标处输出一个字符串。它有下面的常用形式:
void drawString(String message, int x, inty)
这里message是以x,y为输出起点的字符串。在Java窗口中,左上角的位置为0,0。在小应用程序中DrawString()的调用使得在坐标20,20处开始显示消息“A Simple Applet”。
注意小应用程序没有main()方法,不像Java应用程序,小应用程序不以main()为程序起始。实际上,大多数小应用程序甚至不含main()方法。相反,当小应用程序类名被传输到小应用程序阅读器(appletview)或网络浏览器时它开始执行。在你键入SimpleApplet的源代码后,用你以前编译程序的方法编译该程序。但是,运行SimpleApplet包含一个完全不同的过程。实际上,有两种方法可以运行小应用程序。
· 在一个兼容Java的网络浏览器,例如Netscape Navigator中执行小应用程序
· 使用小应用程序阅读器,例如标准JDK工具,小应用程序阅览器(appletviewer)。
一个小应用程序阅读器在窗口中执行小应用程序。 这是检测小应用程序最快和最简单的方法。
上述方法在下面都有阐述。
为在一个网络浏览器中执行小应用程序,需要编写包含适当APPLET标记的简短的HTML文档。下面是执行SimpleApplet的HTML文件:
<appletcode="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源文件如下:
importjava.awt.*; importjava.applet.*; /* */ publicclass SimpleApplet extends Applet { public void paint(Graphics g) { g.drawString("A Simple Applet",20, 20); } }
总的来说,你可以使用下面三步来应用小应用程序:
1. 编写Java源程序。
2. 编译程序。
3. 执行小应用程序阅览器,指定小应用程序源文件名称。小应用程序阅览器将在注释中遇到APPLET标记并执行小应用程序。
SimpleApplet生成的窗口,在小应用程序阅览器中显示。该窗口如下面插图:
关于小应用程序的专题在本书后面有更详尽的讨论,下面是需要记住的关键点:
· 小应用程序不一定包含 main( ) 方法。
· 小应用程序必须在小应用程序阅读器或兼容JAVA的浏览器中运行。
· 用户输入/输出不是由Java的输入/输出流类来完成的。相反,小应用程序运用 AWT提供的界面。
|
Java定义了两类有趣的修饰符:transient和volatile,这些修饰符用来处理特殊的情况。
如果用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++中的类似。
|
有时,在运行时间内知道对象类型是很有用的。例如,你有一个执行线程生成各种类型的对象,其他线程处理这些对象。这种情况下,让处理线程在接受对象时知道每一个对象的类型是大有裨益的。另一种在运行时间内知道对象的类型是很有用的情形是强制类型转换。Java中非法强制类型转换导致运行时错误。很多非法的强制类型转换在编译时发生。然而包括类层次结构的强制类型转换可能产生仅能在运行时间里被察觉的非法强制类型转换。例如,一个名为A的父类能生成两个子类B和C。这样,在强制B对象转换为类型A或强制C对象转换为类型A都是合法的,但强制B对象转换为C对象(或相反)都是不合法的。因为类型A的一个对象可以引用B或C。但是你怎么知道,在运行时,在强制转换为C之前哪类对象被引用?它可能是A,B或C的一个对象。如果它是B的对象,一个运行时异常被引发。Java 提供运行时运算符instanceof来解决这个问题。
instanceof运算符具有下面的一般形式:
object instanceof type
这里,object是类的实例,而type是类的类型。如果object是指定的类型或者可以被强制转换成指定类型,instanceof将它评估成true,若不是,则结果为false。这样,instanceof是你的程序获得对象运行时类型信息的方法。
下面的程序说明了instanceof的应用:
// Demonstrate instanceofoperator. 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 ofA"); if(b instanceof B) System.out.println("b is instance ofB"); if(c instanceof C) System.out.println("c is instance ofC"); if(c instanceof A) System.out.println("c can be cast toA"); if(a instanceof C) System.out.println("a can be cast toC"); System.out.println(); // compare types of derived types A ob; ob = d; // A reference to d System.out.println("ob now refers tod"); if(ob instanceof D) System.out.println("ob is instanceof D"); System.out.println(); ob = c; // A reference to c System.out.println("obnow refers to c"); if(obinstanceof D) System.out.println("ob can be castto D"); else System.out.println("ob cannot becast to D"); if(ob instanceof A) System.out.println("ob can be castto A"); System.out.println(); // all objects can be cast to Object if(a instanceof Object) System.out.println("a may be cast toObject"); if(b instanceof Object) System.out.println("b may be cast toObject"); if(c instanceof Object) System.out.println("c may be cast toObject"); if(d instanceof Object) System.out.println("d may be cast toObject"); } }
程序输出如下:
a isinstance of A
b isinstance of B
c isinstance of C
c canbe cast to A
ob nowrefers to d
ob isinstance of D
ob nowrefers to c
obcannot be cast to D
ob canbe cast to A
a maybe cast to Object
b maybe cast to Object
c maybe cast to Object
d maybe cast to Object
多数程序不需要instanceof运算符,因为,一般来说,你知道你正在使用的对象类型。
但是,在你编写对复杂类层次结构对象进行操作的通用程序时它是非常有用的。
|
Java 2向Java语言增加了一个新的关键字strictfp。与Java 2同时产生的浮点运算计算模型很轻松的使某些处理器可以以较快速度进行浮点运算例如奔腾处理器。特别指明,在计算过程中,一个不需要切断某些中介值的新的模型产生了。用strictfp来修饰类或方法,可以确保浮点运算(以及所有切断)正如它们在早期Java版本中那样准确。切断只影响某些操作的指数。当一个类被strictfp修饰,所有该类的方法都自动被strictfp修饰。举例来说,下面的程序段告诉Java在MyClass中定义的所有方法都用原始浮点运算模型来计算:
strictfp class MyClass { //...
坦白地说,很多程序员从未用过strictfp,因为它只对非常少的问题有影响。
|
尽管这种情况极少发生,你也许希望调用不是用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 anative 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.ibefore the native method:" + ob.i); ob.test(); // call a native method System.out.println("This is ob.i afterthe 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(Stringfilename)
这里,filename是指定保存该库文件名的字符串。在Windows环境下,该文件的扩展名为.DLL。
写完程序后,编译它生成NativeDemo.class。然后,你必须用javah.exe生成一个文件:
NativeDemo.h(javah.exe包含在JDK中)。在执行test( )时你要包含NativeDemo.h。为生成NativeDemo.h,用下面的命令:
javah -jni NativeDemo 该命令生成名为NativeDemo.h的头文件。该文件必须包含在实现test()的C文件中。该命令的输出结果如下:
/* DO NOT EDIT THIS FILE - it ismachine generated */ #include /* Header for class NativeDemo */ #ifndef _Included_NativeDemo #define _Included_NativeDemo #ifdef _ _cplusplus extern "C" { #endif /* * Class: NativeDemo * Method: test * Signature: ()V */ JNIEXPORT void JNICALLJava_NativeDemo_test (JNIEnv *, jobject); #ifdef _ _cplusplus } #endif #endif
请特别注意下面一行,该行定义了所要创建的test( )函数的原型:
JNIEXPORT void JNICALLJava_NativeDemo_test(JNIEnv *, jobject);
注意函数的名称是Java_NativeDemo_test()。调用本机函数你必须用这样的名字。也就是说,不是生成一个名为test( )的C函数,而是创建一个名为Java_NativeDemo_test()函数。
加入前缀NativeDemo是因为它把test( )方法作为NativeDemo类的一部分。记住,其他类可以定义它们自己的与NativeDemo定义的完全不同的本地test( )方法。 前缀中包括类名的方法解决了区分不同版本的问题。作为一个常规方法,给本机函数取名,前缀中必须包括声明它们的类名。
生成了必备的头文件后,可以编写test( )执行文件并把它存在一个名为NativeDemo.c的文件中:
/* This file contains the Cversion of the test() method. */ #include #include "NativeDemo.h" #include JNIEXPORT void JNICALLJava_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 fieldid.\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 nativemethod: 10
Starting the native method.
i = 10
Ending the native method.
This is ob.i after the nativemethod: 20
注意:使用native的特殊环境是依赖于实现和环境的。而且,与JAVA代码接口的指定方式必须改变。你必须仔细考虑完成你Java开发系统文件的本机方法。
|
本机方法看起来提供巨大承诺,因为它们使你有权使用已经存在的库程序,而且使快速执行成为可能。但是本机方法同样引入了两个重大问题:
· 潜在的安全隐患 因为本机方法执行实际的机器代码,它有权使用主机系统的任何资源。也就是说,本机代码不受Java执行环境的限制。例如,它可能允许病毒入侵。因为这个原因,小应用程序不能使用本机方法。同样,DLL文件的加载被限制,它们的加载必须经过安全管理器的同意。
· 丧失了可移植性 因为本机代码是包含在DLL文件中的,它必须存在于执行Java程序的机器上。而且,因为每一个本机方法都依赖于CPU和操作系统,每一个DLL文件在本质上都是不可可移植性的。这样,一个运用本机方法的Java程序只能在一个已经安装了可兼容的DLL的机器上运行。
本机方法的使用是受限的,因为它使Java程序丧失了可移植性且造成重大安全隐患。
|
1. 对比控制台读写和文件读写,以及键盘输入字符的原理;思考计算机是怎样存储并显示文件和字符的?
2. 思考水库中的水是怎么流入家庭的水管里的;这种流水和本章中的流的概念有何异同?
|
在本章中,我们主要学习了:
u 流的概念;
u 读写文件及读写控制台;
u 其他与流处理相关的关键字
|
英文 全文 中文
Applet Applet 小应用程序言
AWT Abstract Window Toolkit 抽象窗口工具包
IOException IOException IO异常
InputStream InputStream 输入流
OutputStream OutputStream 输出流
BufferedReader BufferedReader 缓冲输入流
Graphics Graphics 图形类型
Transient Transient 修饰符,瞬态的 短暂的
Volatile Volatile 修饰符,易变的, 反复无常的
Instanceof Instanceof 二元操作符,测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。
Strictfp Strict Float Point 精确浮点
|
包龙兴到街上算命,算命的老先生只问了他的生辰八字;就能知道他的性格、脾气等简单信息;试着使用本章所学知识实现本案例?