Java语言是目前最受欢迎的程序语言之一,与传统的程序语言相比(比如C、C++),Java语言简单易学,使用它编程时间短、功能强,人们接受起来也更快、更简便。Java主要具有以下三个特点:
第一, 跨平台,不管 Windows还是Unix或者其他平台,它都可以使用;
第二, 面向对象;
第三, 动态域的控制。
1.1 初识Java语言
像其它编程语言一样,Java编程语言也被用来创建 应用程序。那,首先我们还是从一个最普遍的小 应用程序范例来切入——在屏幕上显示字串“Hello World!”。下列代码给出了这个Java应用程序。
1.1.1 HelloWorldApp
- 1.//
- 2.// Sample HelloWorld application
- 3.//
- 4.public class HelloWorldApp{
- 5.public static void main (String args[]) {
- 6.System.out.println ("Hello World!");
- 7.}
- 8.}
以上程序行是在你的屏幕上打印“Hello World!”所需的最少组件。接下来,我们就针对上面给出的这段程序来简单解释一下,并且来实际编译、运行它。
1.1.2 描述HelloWorldApp
第1-3行
程序中的1-3行是注释行
1 //
2 // Sample HelloWorld application
3 //
第4行
第4行声明类名为HelloWorldApp。类名(Classname)是在源 文件中指明的,它可在与源代码相同的目录上创建一个classname.class文件。在本例题中,编译器创建了一个称为HelloWorldApp.class的文件, 它包含了公共类HelloWorldApp的编译代码。
4 public class HelloWorldApp{
第5行
第5行是程序执行的起始点。Java 技术解释器必须发现这一严格定义的点,否则将拒绝运行程序。
其它程序语言(特别是C和C++)也采用main ()声明作为程序执行的起始点。此声明的不同部分将在本课程的后几部分介绍。
如果在程序的命令行中给出了任何自变量,它们将被传递给main()方法中被称作args的String数组。在本例题中,未使用自变量。
5 public static void main (String args[]) {
- public-方法main()可被任何程序访问,包括Java技术解释器。
- static- 是一个告知编译器main()是用于类HelloWorldApp中的函数的关键字。为使main()在程序做其它事之前就开始运行,这一关键字是必要的。
- void- 表明main()不返回任何信息。这一点是重要的,因为Java编程语言要进行谨慎的类型检查,包括检查调用的方法确实返回了这些方法所声明的类型。
- String args [] - 是一个String数组的声明,它将包含位于类名之后的命令行中的自变量。
java HelloWorldApp args[0]args[1]····
第6行
第6行声明如何使用类名、对象名和方法调用。它使用由System类的out成员引用的PrintStreamout对象的println()方法,将字串“Hello World!”打印到标准输出上。
6 System.out.println (“Hello World!”);
在这个例子中,println()方法被输入了一个字串自变量并将其写在了标准输出流上。
第7-8行
本程序的7-8行分别是方法main()和类HelloWorldApp的下括号。
7 }
8 }
1.1.3 编译并运行HelloWorldApp
编译
当你创建了HelloWorldApp.java源文件后,用下列程序行进行编译:
c:\student\javac HelloWorldApp.java
如果编译器未返回任何提示信息,新文件HelloWorldApp.class则被 存储在与源文件相同的目录中,除非另有指定。
运行
为运行你的HelloWorldApp应用程序,需使用Java解释器和位于bin目录下的java:
c:\student\ java HelloWorldApp
Hello World!
1.1.4 严格的约定
通过上面实际编写、编译和运行这个简单的例子,我们会发现Java是一种限制很严格的语言。我们在编写Java程序的时候,一定要严格遵从它的约定,以后你会发现这些约定对维护和组织程序,使你养成一种优良的编程风格都很有帮助。
->; 源文件的类型
在Java中,一个源程序文件被称为一个编译单元。它是一个包含一个或多个类定义的文本文件。Java编译器要求源程序文件必须使用.java文件扩展名。这里可以注意到这个文件的扩展名是4个字符,所以,你的 操作系统必须要有支持长文件名的能力。
->; 源文件的命名
如果.java文件包括一个公共类,那么它必须使用与那个公共类相同的文件名。例如在前例中的类的定义是
public class HelloWorldapp
源文件名则必须是HelloWorldapp.java
->; 类的数量
在源文件中,可以定义多个类,但每次只能定义一个公共类。
->; 标识符
Java中使用的标识符可以是大写和小写字母、数字、下划线(_)、美元符号($)的任意组合。以下是一些有效的标识符:
Year99 count this_is_ok $some
以下是一些无效的标识符:
99year hello# not/ok
这里需要提醒大家注意的是,Java是一种大小写敏感的语言,所以大家在书写代码的时候一定要注意大小写的运用。
1.2 结构与构件
现在,我们从一个典型的Java源文件来了解一下Java程序涉及到的一些重要的构件,以及这些构件的结构。
- 1 package trades;
- 2 import java.util.Vector;
- 3 public class Quote extends Thread implements Tradable {
- 4 // Code goes in here
- 5 }
那我们从上面的例子中可以发现源文件的结构主要包含三个要素:
->; 一个包声明(可选)
程序的第1行就是声明了一个包(package),把该程序放在这个包中间。
->; 任意数量的包引入语句(可选)
程序的第2行是要求该程序引入 系统提供的一个包java.util.Vector。
->; 任意数量的类的声明
程序的3-5行则是声明了一个public的类Quote,它是从它的超类Thread扩展得到,并且实现Tradable的接口。
该三要素必须以上述顺序出现。即,任何包引入语句出现在所有类定义之前;如果使用包声明,则包声明必须出现在类和引入语句之前。
结合上面的例子,我们接下来简单的讨论一下涉及到的这些构件,包package、包引入import、类class、方法method和变量variable。
1.2.1 包package
包实际上就是一个组织类class和接口interface的集合。通过包的这种机制,来把类名空间划分成更多易于管理的块。这样做其主要目的就是可以避免类名的冲突,在不同的包里的类,其名字相同也不会发生冲突。包的主要功能如下:
->; 依照功能来组织和划分类。例如:java.awt包含了构成抽象窗口工具包(AWT)的类,这个包被用来构建和管理应用程序的图形 用户界面。
->; 有助于定义变量和方法的访问控制。
->; 通过这种包和包引入的机制,也可以保持程序的相对独立性,有利于模块化程序设计的实现。
声明包的定义语句必须放在源文件的顶端。如果你没有显式的声明一个包,那么Java会把你的类放入缺省包(default package)里面,这个包没有名字。
Java使用文件系统目录来存储包,目录名必须和包严格的匹配。另外,这里还要简单谈论一下类路径(CLASSPATH)环境变量的问题。Java编译器使用参考的包层次的根是由类路径决定的。例如,你可以在DOS系统环境下使用如下的命令来指定类路径:
set CLASSPATH=C:\Mysource;C:\Mypackages
如果我们在源文件类的声明前有以下语句:
Package trades.quotes
那么,这个源文件就放在下面这个目录里面:
c:\Mypackages\trades\quotes
最后,需要提醒的是,如果你定义的类已经指定到了某个包中,那么在运行的时候你也需要指定相应的包名和类名一起使用。
同学们可以结合实验二来加深对包概念的理解。
1.2.2 类class
类是面向程序设计中最小的抽象单元,它就像是一个数据和代码的容器。它也是Java里面最重要的一个要件。它通过实例化转变为对象,在实例化的时候自动调用构造函数来初始化对象。
对于类的声明比较复杂,它可以使用很多的修饰符,我们以下面的例子来简单谈论一下这些修饰符。
public abstract class Myclass extends Parent implements A,B {}
首先我们看到的public修饰符,是定义的类的访问控制属性,这个我们在下一章会有详尽的介绍。
然后我们看到的abstract修饰符是表示该类是个抽象类,需要其他类来扩展。这里再介绍一个final修饰符,它与abstract修饰符的意义正好相反,它表示该类不能扩展。所以abstract和final不能在声明类的时候同时使用。
在Java中所有的类都扩展一个超类,如果我们没有指定一个超类,那么系统会把Object类作为它要扩展的超类,Object类就是所有类的超类。这里需要注意的是,在C++中我们使用基类(base class)和继承类(derived class)的称谓,而在Java中我们一般称为超类(super class)和子类(subclass)。
另外,需要提一下的是一个特殊的关键字this,它用来引用正在被调用方法的所属当前对象。
1.2.3 接口interface
接口是一个非功能性、抽象的类,它包含常量和没有功能性的方法声明。也就是说,用interface,你可以指定一个类必须做什么,而不是规定它如何去做。接口在语句的构成上与类十分相似,但是它们缺少实例变量,而且它们定义的方法是不含有方法体的。
接口中声明的变量,一般是final和static型的,意思是它们的值不能通过实现类而改变,也就是说它们被看作是常量,而且它们还必须以常量值初始化。
对于接口来说,它们定义以后都是需要类来实现implement的。也就是说,使用具体的类来实现上面的“如何做”。一个类可以实现多个接口,一个接口也可以被多个类来实现。
另外,接口也可以扩展其他接口来实现继承。如:
interface MyInterface extends YourInterface, Runnable {}
1.2.4 方法method
方法声明的是被调用执行的代码,它可以使用传递的参数并且返回一定类型的返回值。对于方法的修饰符也比较复杂和繁多。我们仍然以一个例子来简单谈论以下其中的一些修饰符。
public static final synchronized double getPrice ( final int itemCode, Color itemColor ) throw Exception { /* code here */}
首先,Public是定义的方法的访问控制属性,这个我们在下一章会有详细介绍。
然后,static、final和synchronized是特殊的修饰符。同样需要注意的是其中final和abstract修饰符不能同时使用的。
最后,我们可以看到该方法getPrice返回一个double数据类型的返回值,而且里面定义了两个参数(itemCode和itemColor)。
另外,我们还注意它通过throw修饰符,表示它会抛出一个Exception的异常,关于异常我们在后面的章节会介绍到。
接下来我们会介绍两种比较特殊的方法。
->; Main方法
Main()方法是Java程序的入口起点,它和其他方法的定义类似,只是它包含一个字符串数组用来传递命令行方式执行程序时所跟的参数。如下的例子它把命令行方式所跟的参数全部显示出来:
- public class MainTest {
- public static void main ( String [] args ) {
- for ( int i = 0 ; i < args.lenth; i++ ) {
- System.out.println(“Argument ” + i + “: ” + args[i] );
- }
- }
- }
c:\java project\Main>; java MainTest Philip K Dick
Argumet 0: Philip
Argumet 1: K
Argumet 2: Dick
这里需要注意的是,如果Main()方法没有使用static修饰符,那么编译不会出错,但是如果你试图执行该程序将会报错,提示main()方法不存在。这是因为你如果这样使用命令行的形式直接执行该程序,MainTest类并没有实例化,所以其main()方法也不会存在,而使用static修饰符则表示该方法是静态的,不需要实例化即可使用。
->; 构造函数Constructor Method
当类被实例化的时候,第一个被调用的方法就是构造函数。构造函数的主要作用就是初始化变量。如果没有定义构造函数,那么Java会使用其超类的默认构造函数。
构造函数与其他方法相比,主要具有以下的特点:
->; 构造函数的名字和其类名相同。
->; 没有返回值。
->; 构造函数不能像其他超类的方法那样被继承。
->; 不能使用final、abstract、synchronized、native或者static修饰符。
有时候,我们在编写构造函数的时候,可能需要首先调用其超类的构造函数,这里我们使用super的关键字,实际上它同this关键字的作用类似,只是它指的是其超类。如下的例子:
- class DataServer extends Server {
- public String serverName;
- public DataServer ( int startCode ) {
- super ( startCode );
- serverName = “Customer Service”;
- }
- }
需要注意的是,调用超类构造函数super()的语句必须放在其构造函数定义的前面,否则编译器会报错。
1.2.5 变量variable
变量是Java程序的一个基本存储单元。变量由一个标识符、类型及一个可选初始值的组合定义。此外,如同定义方法一样,它也有各种的修饰符。比如访问控制属性的修饰符public、private、protected和final、static等。这里需要注意的是,变量不能使用synchronized、abstract和native修饰符。
在定义变量的时候需要指明其类型,除了常用的基本类型(比如int型、Boolean型等)以外,也可以使用对象类型。
1.2.6 引入语句import
使用import引入语句是允许你选择某个包中的某个类或者所有类,使之能在你当前的代码中能方便的使用。例如:
import java.util.Dictionary; //引入java.util包中的Dictionary类
import javax.swing.*; //引入javax.swing包中的所有的类
当然如果你不使用引入语句也可以使用其他包中的其他的类,只要你指定引用对象的全名。比如:
java.util.Date now = new java.util.Date();
另外,java.lang包是会被自动引入到源程序中的。(待续)
| |
__________________________________
~M.Meng's Cabin Before posting did you try: [ J2SE Javadocs | J2EE Javadocs | Google ] | |
状态:...当前离线... |
|
__________________________________
~M.Meng's Cabin Before posting did you try: [ J2SE Javadocs | J2EE Javadocs | Google ] | |
状态:...当前离线... |
|
__________________________________
~M.Meng's Cabin Before posting did you try: [ J2SE Javadocs | J2EE Javadocs | Google ] |
第四次:JAVA程序控制语句 4.程序控制语句 我们在使用任何编程语言都使用过程序控制语句,Java语言的程序控制语句基本与C语言完全相同,它主要分为以下几种:选择、循环和跳转。 4.1 选择语句 4.1.1 if语句 if语句是最基本的条件判断语句,用于根据条件来控制程序的执行路径。其完整格式如下: if (condition) statement1; else statement2; if和else的对象statement可以是单个语句,也可以是个程序块。else子句是可选的。 另外if-else可以嵌套。所以这里需要提醒大家注意的是,如果你没有使用{}来保证每个程序块是具体所属那个if或者else子句,那么一定要注意if和else的匹配关系。一个else子句总是对应着它的同一个块中的最近的if语句,而且该语句没有于其他的else语句相关联。 4.1.2 switch语句 switch语句是Java的多路分支语句。它提供了一种基于一个表达式的值来使程序执行不同部分的简单方法。它比使用一系列if-else-if语句效率显得更高,也更方便。其通用格式如下: switch ( expression ) { case value1: // statement sequence break; case value2: // statement sequence break; . . . case valueN: // statement sequence break; default: // default statement sequence } 表达式expression必须为byte、short、int或char类型。每个case子句后的值必须是于表达式类型兼容的特定的一个常量(注意必须是常量,而不是变量)。Default子句是可选的。重复的default子句或者重复的case值是不允许的。 4.2 循环语句 4.2.1 while循环 while语句是Java最基本的循环语句。当它的控制表达式为真的时候,while语句重复执行单个语句或语句块。通用格式如下: while ( condition ) { // body of loop } 注意的是,如果循环体只有单个语句,可以不使用{}。后面的do-while、for也类似。 同学们可以结合实验一来加深对while循环的理解。 4.2.2 do-while循环 与while循环类似,只是循环控制表达式是在循环体的尾部进行测试。这意味着即使表达式为假,循环体也至少要被执行一次。通用格式如下: do { // body of loop } while ( condition ) ; 4.2.3 for循环 for循环是一个功能强大而且形式灵活的结构。通用格式如下: for ( initialization ; condition ; iteration ) { // body } 它与C语言完全相同,按它分为的三段,for循环执行的过程也可以看作是三步。首先循环启动时先执行初始化initialization部分,通常这里是设置循环控制变量值的一个表达式,作为控制循环的计数器。然后,计算条件condition的值,来判断是否满足该表达式,执行循环体。最后执行循环体的反复部分iteration,一般来说这里通常是增加或者减少循环控制变量的一个表达式。 需要注意的是,对于initialization和iteration两个部分而言,可以使用多个变量表达式,中间用逗号隔开。另外,for循环也可以不含任何的部分,这样做就是一个死循环。总之,对于for循环语句的使用相当的灵活,大家可以在具体的应用中感受到。 4.3 跳转语句 4.3.1 break语句 break语句有三种作用。首先,可以在switch语句中使用,用来终止一个子句序列,跳出switch语句。另外,它能用来退出一个循环,这种使用与C语言也完全一致。第三种,它能作为一种“先进”的goto语句来使用。这是Java的一个特点,我们特别讨论一下。 我们知道,对于一个比较优秀的语言来讲,都限定使用goto语句。Java语言虽然把goto作为了保留关键字,但并不使用这个语句。它提供了break语句来扩展的实现它的一些功能。通过使用带有标签的break语句,来指定执行从何处重新开始。标签break语句的通用格式如下: break label ; 这里,标签label是标识代码块的标签。当这种形式的break执行时,控制被传递处指定的代码块。但需要注意的是,如果一个标签不在包围break的块中定义,你就不能break到该标签。一般来说,在实际应用中,带标签的break语句可以用来跳转到多重循环体之外。 4.3.2 continue语句 用法与C语言类似,用来强迫一个循环提早反复。也就是说,你可以使用continue来继续运行循环,但忽略这次重复剩余的循环体语句。 |
同大多数的编程语言一样,Java语言也包含了许多的运算符。如果大家学习过C或者C++,会发现下面介绍的各种Java的运算符都与之类似。
5.1 try-catch-finally
首先我们介绍一下异常的一些基本概念。Java异常是一个描述在代码段中发生的异常(也就是出错)情况的对象。当异常情况发生,一个代表该异常的对象被创建并在导致该错误的方法中被引发throw。另外,你需要定义一段代码来处理这一异常,首先它需要捕获catch被引发的异常。
如果我们不提供任何异常处理程序,那么异常会被Java运行时系统的默认处理程序捕获。任何不是被你程序捕获的异常最终都会被该默认处理程序处理。默认处理程序会显示一个描述异常的字符串,打印异常发生处的堆栈轨迹并终止程序。
堆栈轨迹实际显示了导致错误产生的方法调用序列。Java在运行的时候会维护一个堆栈来保存程序运行调用的方法序列。实际上,异常发生以后就是按照这个堆栈的方法序列依次呈递给这些方法,如果这些方法里面有异常处理程序就可以捕获它加以处理,否则会一直呈递到系统默认的处理程序。
我们通过下面的例子能更容易理解这个调用方法堆栈和异常的呈递机制。
- Class Exc {
- static void subroutine () {
- int d = 0 ;
- int a = 10 / d ;
- }
- public static void main ( String [ ] args ) {
- Exc.subroutine ();
- }
- }
运行结果:
java.lang.ArithmeticException: / by zero
at Exc.subroutine(Exc.java:4)
at Exc.main(Exc.java:7)
尽管由Java运行时系统提供的默认异常处理程序对程序的调试很有用,但通常还是需要自己来处理异常。因为,这样做一可以修正错误,二来可以防止程序的自动终止。
接下来,我们来看一下异常处理块的通用格式:
try {
// block of code to monitor for errors
}
catch ( ExceptionType1 exOb ) {
// exception handler for ExceptionType1
}
catch ( ExceptionType2 exOb ) {
// exception handler for ExceptionType2
}
// …
finally {
// block of code to be executed before try block ends
}
☆ try子句
用来监控可能产生错误的代码
☆ catch子句
用来捕获你指定的异常类型,并进行错误处理。它必须紧跟try块,并且可以定义多个catch块。
☆ finally子句
用来处理善后清理的工作,因为无论异常是否引发,它里面的代码都会执行,甚至你在try块中定义了return子句,它也一样会被执行。这是可选的。
5.2 Exception类
前面我们讨论的所有异常都来自于Exception类。
在Java编程语言中,异常类可以大致分为三种。Java.lang.Throwable类充当所有对象的父类。它有Error和Exception两个基本子类,如下图所示:
Throwable类不能使用,而使用子类异常中的一个来描述任何特殊异常。
☆ Error表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。
☆ RuntimeException表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。比如,如果数组索引扩展不超出数组界限,那么,ArrayIndexOutOfBoundsException异常从不会抛出。比如,这也适用于取消引用一个空值对象变量。因为一个正确设计和实现的程序从不出现这种异常,通常对它不做处理。这会导致一个运行时信息,应确保能采取措施更正问题,而不是将它藏到谁也不注意的地方。
☆ 其它异常表示一种运行时的困难,它通常由环境效果引起,可以进行处理。例子包括文件未找到或无效URL异常(用户打了一个错误的URL),如果用户误打了什么东西,两者都容易出现。这两者都可能因为用户错误而出现,这就鼓励程序员去处理它们。
在捕获异常的时候,一定要注意异常类型的匹配。只有catch块中定义的捕获异常的类型匹配引发的异常的类型或者其超类,才捕获并处理。所以,一定要注意你处理的异常类型的继承关系。另外,对于多个catch块来说,如果你定义的带捕获处理的异常有子类和相应的其超类,一定要注意放置的顺序,捕获超类的catch块一定要放置捕获其子类的catch块后面。
5.3 throws子句
如果一个方法可以导致一个异常但不处理它,它必须指定这种行为以使方法的调用者可以保护它们自己而不发生异常。你可以在方法声明中使用throws子句来实现这一目的。一个throws子句列举了一个方法可能引发的所有异常类型。这对于除了Error或者RuntimeException及它们的子类以外类型的所有异常是必要的。一个方法可以引发的所有其他类型的异常必须在throws子句中声明。
针对上面的情况,这里简单介绍一下异常的检查类型的概念。RuntimeException异常类及其子类就是非检查类型,Error类虽然不属于Exception类也是非检查类型的。对于非检查类型的异常,即使你在方法声明中没有指定它为可能引发的异常类型,它也会自动正确的被引发,系统保证它们的运行而不会出错。而除此以外的其他异常类型都是检查的,也就是如果你要引发它就必须在方法声明中指定,而且还必须使用catch块来捕获处理。
另外,对于throws子句还需要提一下的是其方法的重载问题。对于子类重载超类方法的这种覆盖重载override来说,要注意子类方法所引发的异常不能比超类的多,你引发的异常类型可以是超类引发异常类型的子类或者根本不引发任何异常,反正不可以超出其超类的范围。
5.4 引发和创建自定义异常
5.4.1 throw子句
前面我们都是获取的Java运行时系统引发的异常,然而,我们也可以在程序中用throw语句来引发明确的异常。Throw子句通用格式如下:
throw ThrowableInstance ;
ThrowableInstance一定是Throwable类类型或者其子类的一个对象,所以这里又涉及到了一个实例化异常类的问题。可以使用new来实例化异常类,需要注意的是,异常的有两个构造函数:一个没有参数,一个带有一个字符串参数。使用后者时,参数指定的是一个描述异常的字符串。如下:
throw new NullpointerException (“demo”) ;
5.4.2 创建自定义异常类
尽管Java内置异常可以处理大多数常见错误,你还是可以建立你自己的异常类型来满足你的特殊要求。建立的方法很简单,只要定义一个Exception 的一个子类就可以了。
第六次 垃圾回收机制
这里讨论Java的垃圾回收机制,也就是Java管理使用内存的机制。当对象在内存中创建以后,如果你的程序使用完毕了该对象,通常都应该回收这个对象所占用的内存空间。在C 和C++语言中,一般都靠程序员在代码中手工编写代码来处理这个过程。而Java语言则通过它的JVM(Java虚拟机)使用一种称作mark-sweep的垃圾回收机制来自动处理这一过程。
6.1 垃圾回收机制
内存空间是计算机最基本、最重要的资源。对内存空间的耗费最主要的就是创建对象。当我们不再需要使用创建的对象时,就需要释放分配给对象的内存,回收资源。Java使用一种称为“堆heap”的结构来管理内存,它使用垃圾回收机制来保证始终有足够的内存分配给创建的对象。这一内存管理机制通常是系统自动进行,不受程序员干预的。这也就减轻了程序员的工作负担。
在回收内存空间中,系统主要使用一种称作标记-清理(mark-sweep)的机制。首先,系统遍历所有对象的引用指针,如果发现有对象没有被引用,那么就把它标记为未使用。然后,系统把所有的凡是标记了未使用状态的对象进行清理回收。即使是对于那种两个未使用的对象互相引用的情况,系统通过的遍历算法也可以发现,所以这一机制是十分有效的。
另外需要注意的是,回收处理是周期性的运行,它由系统自行控制。它可能延迟任意长的时间来运行回收器,也可能在任意时候运行。所以这就可能在运行的时候出现一些问题,比如程序会暂停或者运行速度减慢。
针对这样的回收机制,我们在程序中可以显式的使用一些方法来告诉系统回收我们不需要的对象,提高回收执行的效率。比如,把不再需要使用的对象置为null,或者赋给其他对象类型变量,使其不引用指向原对象。
6.2 手工干预回收
对于回收机制,都是由系统自行完成,通常我们并不需要关心。但有时候也可以使用一些手段来手工干预内存空间的回收。
Java提供一个java.lang包中的Runtime类来做这样的工作。我们使用Runtime类的gc()方法来运行垃圾回收器,强制内存空间回收。需要注意的是使用这个方法只是给系统发出一个要求,而不是命令,所以并不能保证一定完成你指定的工作,达到你满意的效果。 另外还需要注意的是Runtime类没有构造函数,你需要使用其getRuntime()来实例化它。
有时候当撤消一个对象的时候,需要完成一些操作,来回收一些非Java的资源。对处理这样的情况,Java提供了一种被称为收尾(finalization)的机制,你可以采用与C语言类似的方式来手工回收一些资源。
要实现这一机制,只需要在你需要回收的类中定义finalize()方法即可。Java回收该类的一个对象时就会调用这个方法。在finalize()方法中你需要指定一个对象被撤消前必须执行的操作。
finalize()方法的通用格式如下:
protected void finalize ()
{
// finalization code here
}
第七次 重载
重载对于面向对象编程来说是一个难点,相对C++来说,Java这个方面较为简单。这里我们主要讨论方法的两种形式的重载和构造函数的重载。通过本次学习,大家会对Java面向对象编程的思想有更一步的理解。
7.1 方法的重载
通过方法重载可以实现多态,多态性是面向对象编程的一个重要的特点。Java语言中对方法的重载分为两类,我们在下面依次具体介绍。
7.1.1 Overload名称重载
指同一个类中的多个方法可以享用相同的名字,只要它们声明的参数不同。这种不同体现在或者参数的个数不同,或者其参数类型不同。编译器根据参数的个数和类型来决定当前所使用的方法。需要注意的是多个方法必须返回相同的数据类型,它们的不同只在于参数。
我们可以通过如下的例子来了解名称重载。
- public class Mytest {
- public void printNumbers ( int a, int b ) {
- System.out.println(a);
- System.out.println(b);
- }
- public void printNumbers ( int a, int b, int c ) {
- System.out.println(a);
- System.out.println(b);
- System.out.println(c);
- }
7.1.2 Override覆盖重载
在类层次结构中,如果子类中的一个方法与它超类中的方法用相同的方法名、返回类型和参数表,就称子类中的方法覆盖重载超类中的方法。从子类中调用重载方法时,它总是引用子类定义的方法,而超类中定义的方法将被隐藏。也就是说,通过覆盖重载,子类修改了从超类继承下来的行为。
需要注意的是方法覆盖仅载两个方法的名称和类型都相同时才发生。如果仅有名称相同,那两个方法只是覆盖。
另外使用方法覆盖还需要注意要保证以下几点:
☆ 覆盖方法的返回类型必须与它所覆盖的方法相同。
☆ 覆盖方法不能比它所覆盖的方法访问控制范围差。
☆ 覆盖方法不能比它所覆盖的方法抛出更多的异常。
☆ synchronized修饰符对超类和子类的作用是独立的。
我们可以通过如下的例子来了解覆盖重载。
Myparent.java
- public class Myparent {
- public void printClassName () {
- System.out.println ( “Myparent” );
- }
- }
Mychild.java
- public class Mychild {
- public void printClassName () {
- System.out.println ( “Mychild” );
- }
- }
7.2 构造函数的重载
构造函数和其他的方法一样也可以被重载,由于它的特殊性,所以它的重载也与其他的方法重载有些不同。主要包括以下几个方面。
☆ 使用this调用自身的其余构造函数实现构造函数的名称覆盖。如下的示例:
- public class Myclass {
- String title ;
-
- //Calling constructor
- public Myclass () {
- this ( “Default Title ” );
- }
-
- //Called constructor
- public Myclass ( String t ) {
- title = t ;
- }
- }
☆ 使用super调用超类的构造函数。需要注意的是,如果不显式的使用super调用超类的构造函数,系统会调用超类的不带参数的缺省构造函数。另外要使用this()或者super()则必须把它放在其构造函数的首行。如下的示例:
- public class MySuper {
- //first constructor
- public MySuper () {
- System.out.println (“Super1”) ;
- }
- //second constructor
- public MySuper ( String title ) {
- System.out.println (“Super2”) ;
- }
- }
- public class MySub extends MySuper {
- //first constructor
- public MySub () {
- super(“the title”)
- System.out.println (“Sub1”) ;
- }
- //second constructor
- public MySub ( String title ) {
- System.out.println (“Sub2”) ;
- }
- }
7.3 所属Is和拥有Has关系
这里主要是提醒大家明白了一个我们在平时程序设计中经常混淆的概念,即所属Is和拥有Has关系的概念和区别。我们需要注意的是只有对于所属的这种关系,我们才可以使用子类和超类的程序设计思想
第八次 内部类
内部类就是定义在其他类、方法、甚至表达式中的类,它的特点与一般的类并没有什么大的不同。Java语言有四种类型的内部类,下面分别介绍。
8.1 静态Static内部类
静态内部类是定义在类内部的类,所以也称为嵌套类,并且使用static修饰符来声明。需要注意的是凡是内部类,其名字都不能和封装它的类名字相同。对于静态内部类来说,它只能访问其封装类中的静态成员(包括方法和变量)。
静态内部类示例如下:
- package mypackage;
- public class EnclosiongClass {
- private static int staticvar = 0;
- public int instancevar = 0 ;
-
- public static class StaticInnerClass {
- }
- }
8.2 成员Member内部类
成员内部类与静态内部类相似,也是定义在类内部这一级,只是它不使用static修饰符来声明。所以它和类中的其他实例变量一样,都可以看作是其封装类中的成员。它可以访问其封装类中的任何成员。需要注意的是,在成员内部类中不能定义有static变量,但是可以使用局部变量。
由于成员内部类是非static的,所以在创建内部类的实例时,必须有一个其封装类的实例引用。可见下面的例子:
- public class EnclosingClass {
- private int instVar = 1;
- public class MemberClass {
- public void innerMethod {
- // it is okay to do something with instVar here
- }
- }
- public MemberClass createMember () {
- return this.new MemberClass ();
- }
- }
这里是在封装类内部创建内部类的实例,实际这个this可以不显式使用,系统会自行隐式使用来引用封装类的实例。如果对于在封装类外部来创建内部类的实例,依然必须要有一个封装类的实例引用。其语法有两种,可分别见下面的两个示例:
EnclosingClass ec = new EnclosingClass ();
EnclosingClass.MemberClass mc = ec.new MemberClass();
或者
EnclosingClass.MemberClass mc = new EnclosingClass ().new MemberClass ();
另外还需要提一下的是this和super在内部类中使用所代表的意义,this是指的该内部类的实例引用,而super是指的其封装类的实例引用。所以大家在使用这两个关键字的时候一定要注意它所处的位置。
8.3 局部Local内部类
局部内部类是定义在方法体或更小的语句块中的类。它的使用如同方法体中的局部变量,所以你不可以为它声明访问控制修饰符。同成员内部类相同,也不可以含有static成员。这里需要注意的是,在局部内部类和后面将介绍的匿名内部类中可以引用方法中声明的变量,但这些变量必须是final的。
8.4 匿名Anonymous内部类
在某些时候,你可能需要定义一个类,但在程序中却从来不会定义它的对象,只是用来把这个类的对作为自变量直接传递给方法,这时就使用匿名类。如下的例子:
pickButton.AddActionListener ( new ActionListener ( )
{
//Code to define the class that implements
//the ActionListener interface
}
)
匿名类总是用来扩展一个现有的类,或者实现一个接口。它没有名字,所以也没有构造函数,并且也没有任何修饰符来声明它。
一般来说,匿名类经常用于AWT和Swing中的事件处理。
第九次 线程
Java语言中一个十分重要的特点就是支持多线程编程。多线程程序包含两条或两条以上并发运行的部分。程序中每个这样的部分都叫做一个线程thread,每个线程都有独立的执行路径。因此,多线程是多任务处理的一个特殊形式。
9.1 创建进程
通常,通过实例化一个Thread对象来创建一个线程,Java对此定义了两种方式。下面我们分别介绍一下。
☆ 实现Runnable接口
创建线程的最简单的方法是创建一个实现Runnable接口的类。创建过程大致分以下的几步。
首先,你通过实现Runnable接口的run()方法来创建每一个对象的线程。run()方法能够像主线程那样调用其他方法,引用其他类,声明变量。唯一不同的是,run()在程序中确定另一个并发的线程执行入口。当run()方法返回时,该线程即结束。
其次,你要在你创建的实现Runnable接口的类中,实例化一个Thread类的对象。
最后,实例化了Thread类创建了一个线程后,线程并没有运行。你要运行你创建的这个线程还要调用它的start()方法。实际上,start()执行的就是一个对run()的调用。
☆ 扩展Thread类
创建线程的另一个方法是创建一个新类来扩展继承Thread类,然后再创建这个类的实例。当这个子类继承Thread类时,它必须重载run()方法,这个run()方法就是新线程的入口。另外它同样也必须调用start()方法去启动新线程的执行。
9.2 线程的同步
当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一时刻仅被一个线程占用。这个过程就成为同步(synchronization)。
与其他语言不同的是,Java提供了一种语言上对同步的支持,极大的简化了管理线程同步的复杂性。有两种方法来同步化代码,两者都包括了synchronized关键字的运用,下面分别说明。
☆ 使用同步方法
在多线程的情况下,你如果有一个或多个方法操纵对象的内部状态,都必须使用synchronized关键字来声明方法为同步方法,防止状态出现竞争。一旦线程进入实例的同步方法,就没有其他线程可以进入相同实例的同步方法。
☆ 使用同步语句
当我们在类的定义中没有用到同步方法的时候,你可能也需要对一些操纵对象内部状态的代码同步化。这时你可以使用synchronized来声明一个同步语句块。
下面给出了synchronized语句的普通格式:
synchronized(object) {
// statement to be synchronized
}
9.3 线程间通信
对于多线程的管理,一般采用一种轮询的方式。轮询通常由重复监测条件的循环实现。一旦条件成立,就采用适当的动作。这种方式相对简单,但却很大程度上浪费了CPU的时间。Java语言则采用了另外一种机制,避免了轮询。它通过wait()、notify()和notifyAll()方法实现一个进程间通信机制。这些方法是在Object类中用final声明定义了的,所以所有的类都包含它们。它们的意义分别如下:
☆ wait():通知被调用的线程放弃管程进入睡眠直到其他线程进入相同管程并调用notify()把它唤醒。
☆ notify():恢复相同对象中第一个调用wait()的线程。
☆ notifyAll():恢复相同对象中所有调用wait()的线程。
这里需要提醒的是,这三个方法仅在synchronized方法中才能被调用。
9.4 死锁
这里,我们介绍一个死锁的概念。这是我们需要避免在多任务处理中出现的情况。
死锁发生在当两个线程对一对有依赖循环时。例如,假设一个线程进入了对象A的管程而另一个线程则进入了对象B的管程。如果A的线程试图调用Y的同步方法,它将被锁定。这时,如果B的线程希望调用A的一些同步方法,线程就会永远等待。系统陷入死锁的情况。因为为到达A,B必须释放自己的锁定以使第一个线程可以完成。
9.5 优先级问题
线程优先级被线程调度用来判断何时每个线程允许运行。理论上,优先级高的线程比优先级低的线程获得更多的CPU时间。但实际上,线程获得CPU时间通常由包括优先级在内的多个因素决定的。
我们可以使用setPriority()方法来设置线程的优先级,该方法是Thread类的成员。它的通常格式如下:
final void setPriority(int level)
level指定了对调用线程的新的优先级的设置。Level的值必须在MIN_PRIORITY 到MAX_PRIORITY之间。通常它们的值是1和10。如果要指定默认的优先级使用NORM_PRIORITY,通常值为5。
另外你还可以使用getPriority()方法来获得当前的优先级设置。