Java学习笔记(3)——资源管理
46、包/套件/package
Java提供package机制,它就像是一个管理容器,可以将您所 定义的名称区隔管理在package下,而不会有相互冲突的发生,例如您定义了一个 dimension2d与dimension3d的package,在它们之下都有一个Point类别,但由于属于不同的package,所以这两个名称 并不会有所冲突。
Java的package被设计为与档案系统结构相对应,如果您的package设定是only.caterpillar,则该类别应该在指定目录(或jar)的onlyfun/caterpillar下可以找到,为了要能建立与package相对应的档案系统结构,您在编译时可以加入-d 参数,并指定要建立在哪一个目录之下。
下面这个程序使用"package"关键词来建立package以管理我们所定义的类别:
package onlyfun.caterpillar; public class UsePackage {public static void main(String[] args) {System.out.println("Hello! World!");} }
在编译时要使用以下的指令:
$ javac -d . UsePackage.java
在编译时使用 "-d" 参数,并指定在现行目录 "."中建立档案与系统结构,则编译完成之后,在现行目录中会出现onlyfun/caterpillar目录,而当中有一个UsePackage.class 档案,在编译完成之后,package的指定就成为class名称的一部份了,在执行时可以这么下指令:
$ java onlyfun.caterpillar.UsePackage Hello! World!
可以为类别建立package管理,举下面的例子来说:
package onlyfun.caterpillar; public class Point2D {private int x, y;public Point2D() {x = 0; y = 0;}public Point2D(int x, int y) {this.x = x; this.y = y;}public int getX() {return x;}public int getY() {return y;} }
这个类别建立在Point2D.java档案中,可以先用以下的指令来编译它:
$ javac -d . Point2D.java
之前说过,package名称为类别名称的一部份,除非您重新编译类别,否则的话无法改变这个名称,为了要使用这个类别,方法之一是使用完全描述(Fully qualified)名称,也就是完整的指出package与类别名称,例如:
UsePackage.java
public class UsePackage {public static void main(String[] args) {onlyfun.caterpillar.Point2D p1 = newonlyfun.caterpillar.Point2D(10, 20);System.out.printf("p1: (x, y) = (%d, %d)%n",p1.getX(), p1.getY());} }
当然这个方法在使用上不是很方便,您可以使用"import"关键词,告知编译器要使用的类别是位于哪一个package之下,如此可以少打一些 字,让编译器多作一些事,例如:
UsePackage.java
import onlyfun.caterpillar.Point2D; public class UsePackage {public static void main(String[] args) {Point2D p1 = new Point2D(10, 20);System.out.printf("p1: (x, y) = (%d, %d)%n",p1.getX(), p1.getY());} }
您在使用"import"时可以指定类别的完整描述,如果您会使用到某个package下的许多类别,您可以使用 '*',表示您可能使用到某个package下的某些类别,再让编译器作更多事,例如:
UsePackage.java
import onlyfun.caterpillar.*; public class UsePackage {public static void main(String[] args) {Point2D p1 = new Point2D(10, 20);System.out.printf("p1: (x, y) = (%d, %d)%n",p1.getX(), p1.getY());} }
但要注意的是,如果您import之后,出现类别名称有同名冲突时,编译器就不知道如何处理了,例如:
import java.util.Arrays; import onlyfun.caterpillar.Arrays; public class SomeClass {.... }
在这个例子中,编译器发现有两个Arrays类别,它不确定若遇到Arrays时您要使用的是java.util.Arrays,或是 onlyfun.caterpillar.Arrays,它只好回报以下讯息:
java.util.Arrays is already defined in a single-type import import onlyfun.caterpillar.Arrays; ^ 1 error
这个时候您就要考虑换一下类别名称了(如果您有权更动那些类别的话),或者是不使用"import",直接使用完整描述;在"import"时尽量不使用 '*' 也可以减少这种情况发生。
注意如果您提供的类别若不位于相同的package中,您的类别必须宣告为"public",若不宣告则类别预设只能于同一个package中被存取,例如将之前Point2D类别的public拿掉,则编译UsePackage.java档案时,会出现以下的错误:
UsePackage.java:5: caterpillar.demo.Point2D is not public in caterpillar.demo;cannot be accessed from outside package
如果定义类别成员时没有指定public、protected或private的存取修饰,则为预设(Default)权 限,成员将只能于同一个package中被直接存取,通常称之为package-friendly或package-private,且该成员将无法于子 类别中被直接存取。
要存取package的class也与CLASSPATH的设定有关,建议您也看看官方网站上的文章,您对package的了解会更深入:
- Setting the class path (Windows)
- Setting the class path (Solaris and Linux)
Java平台的classes是被储存在Java安装目录的jre/lib/下的rt.jar,另外额外的第三组件(Third- party)可以放在/jre/lib/ext/中,在之前的例子中,使用"import"就是在告知编译器我们的类别位于哪一个package下,这些类别必须设定好CLASSPATH才可以被编译器找到,预设上是jre/lib/下的rt.jar、jre/lib/ext/中相关扩充组件与现行工作目录。
47、预设建构子
当您在Java中定义一个类别,但没有定义建构子时,编译器会自动帮您产生一个预设建构子。
在继承时,如果您没有使用super()指定要使用父类别的哪个建构子,则预设会寻找无参数的建构子。
预设建构子的存取权限是跟随着类别的存取权限。
48、存取权限与修饰
在这边整理一下private、protected、public与default与类别及套件的存取关系:
存取修饰 |
同一类别 |
同一套件 |
子类别 |
全域 |
private |
OK |
|||
default |
OK |
OK |
||
protected |
OK |
OK |
OK |
|
public |
OK |
OK |
OK |
OK |
49、常数设置
有时候您会需要定义一些常数供程序使用,您可以使用接口或类别来定义,例如定义操作常数:
OpConstants.java
public interface OpConstants {public static final int TURN_LEFT = 1;public static final int TURN_RIGHT = 2;public static final int SHOOT = 3; }
常数必须是可以直接取用,并且不可被修改的,所以我们在宣告时加上 static 与 final,事实上,对于接口来说,当中的常数预设就为public、static、final,即使您没有明确指定修饰,例如下例与上例是一样的:
OpConstants.java
public interface OpConstants { int TURN_LEFT = 1; int TURN_RIGHT = 2; int SHOOT = 3; }
这意谓着如果您实作的接口中有定义常数,您不可以重新指定该常数值,例如下例是错误的…
interface ISome {int const = 10;... } public class Some implements ISome {public Some() {const = 20; // 这行是错的} }
宣告常数之后,就可以在程序中直接使用 OperateConstants.TURN_LEFT之类的名称来取代常数值,例如:
public void someMethod() {....doOp(OpConstants.TURN_RIGHT);.... } public void doOp(int op) {switch(op) {case OpConstants.TURN_LEFT:System.out.println("向左转");break;case OpConstants.TURN_RIGHT:System.out.println("向右转");break;case OpConstants.SHOOT:System.out.println("射击");break;} }
如果使用类别来宣告的话,方法也是类似,例如:
OpConstants.java
public class OpConstants {public static final int TURN_LEFT = 1;public static final int TURN_RIGHT = 2;public static final SHOOT = 3; }
对于简单的常数设置,上面的作法已经足够了,不过在 J2SE 5.0 中新增了 列举型态(Enumerated Types),使用列举型态,除常数设定的功能之外,您还可以获得像编译时期型态检查等的更多好处。
50、Static import
在 J2SE 5.0 后新增了"import static" ,它的作用与 套件 (package) 中介绍的"import"类似,都是为了让您可以省一些打字功夫,让编译器多作一点事而存在的。
"import static"是使用时的语法,国外网站上的文章或原文书中介绍这个功能时,大都用static import描述这个功能,编译器讯息也这么写,这边就还是用static import来作为原文时的描述,但为了比较彰显这个功能的作用,我称之为「import 静态成员」。
使用"import static"语法,您可以import类别或接口中的静态成员,例如来看看这个Hello! World!程序:
HelloWorld.java
import static java.lang.System.out; public class HelloWorld {public static void main(String[] args) {out.println("Hello! World!");} }
在这边您将java.lang.System类别中的out静态成员import至程序中,编译时编译器遇到out名称,就会自动展开为System.out,所以这还是编译器给的蜜糖(Compiler suger)。
再来看一个例子,Arrays 类别 中有很多的静态方法,为了使用方便,可使用"import static"将这些静态方法import至程序中,例如:
UseImportStatic.java
import static java.lang.System.out; import static java.util.Arrays.sort; public class UseImportStatic {public static void main(String[] args) {int[] array = {2, 5, 3, 1, 7, 6, 8};sort(array);for(int i : array) {out.print(i + " ");}} }
如果您想要import类别下所有的静态成员,可以使用 '*' 字符,例如:
UseImportStatic.java
import static java.lang.System.*; import static java.util.Arrays.*; public class UseImportStatic {public static void main(String[] args) {int[] array = {2, 5, 3, 1, 7, 6, 8};sort(array);for(int i : array) {out.print(i + " ");}} }
与import一样,import 静态成员(static import)这个功能是为了方便,可以让您少打一些字,您把少打的字交给编译器来判断并自动为您补上,但是您要注意名称冲突问题,有些名称冲突编译器可 能透过以下的几个方法来解决:
- 成员覆盖
如果类别中有同名的field或方法名称,则优先选用它们。 - 区域变量覆盖
如果方法中有同名的变量名或自变量名,则选用它们。 - 重载(Overload)方法 上的比对
对于被使用import static的各个静态成员,若有同名冲突,尝试透用重载机制判断,也就是透过方法名称及自变量列的比对来选择适当的方法。
如果编译器无法判断,则会回报错误,例如若您定义的sort()方法与Arrays的sort()方法冲突,且编译器也无法判别时,会出现以下的讯息:
reference to sort is ambiguous,both method sort(float[]) in onlyfun.caterpillar.Arrays andmethod sort(float[]) in java.util.Arrays match
总之,package与类别等可以用于管理一些资源,避免同名冲突发生,而"import"与"import staic"则是反其道而行,让您可以获得一些方便,如果同名冲突发生了,这种方便性的使用就有考虑的必要了。
51、异常处理
Java的例外处理藉由"try"、"catch"、"finally"三个关键词组合的语言来达到,其语法基本结构如下:
try {// 陈述句 } catch(例外型态 名称) {// 例外处理 } finally {// 一定会处理的区块 }
一个"try"所包括的区块,必须有对应的"catch"区块,它可以有多个"catch"区域,而"finally"可有可无,如果没有定义"catch"区块,则一定要有"finally"区块。
先来看个实例,了解如何使用try...catch来处理使用者输入的错误:
UseException.java
import java.io.*; public class UseException {public static void main(String[] args) {try {int input;BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));System.out.print("请输入整数: ");input = Integer.parseInt(buf.readLine());System.out.println("input x 10 = " + (input*10));}catch(IOException e) {System.out.println("I/O错误");}catch(NumberFormatException e) {System.out.println("输入格式有误");}} }
例外处理是程序在执行但发生错误并无法处理时,会丢出一个例外对象,在这个程序中,您特意 使用 BufferedReader 取得输入,当使用 BufferedReader类别时,若发生I/O错误会丢出IOException例外,这个例外您必须处理。
您试着从使用者输入取得一个整数值,由BufferedReader对象所读取到的输入是个字符串,您使用Integer类别的 parseInt()方法试着剖析该字符串为整数,如果无法剖析,则会发生错误并丢出一个NumberFormatException例外对象,当这个例外丢出后,程序会离开目前执行的位置,而如果设定的"catch"有捕捉这个例外,则会执行对应区块中的陈述句,注意当例外一但丢出,就不会再回到例外的丢出点了。
来看看这个程序的执行范例:
$ java UseException 请输入整数: 10 input x 10 = 100 $ java UseException 请输入整数: XX 输入格式有误
如果程序中设定有"finally"区块,则无论例外是否有发生,则一定会执行"finally"区块中所定义的陈述句,"finally"区块使用时机 的例子之一,就是当您开启了某个档案时,在读/写的过程中发生错误,在使用"catch"区块处理相对应的例外之后,最后在"finally"区块中定义 一些关闭档案的动作,让关闭档案的动作一定会被执行。
使用例外处理的好处是您可以将程序逻辑与错误处理分开,使得程序易于撰写、阅读与维护,由于例外处理是在程序执行时发生错误,而没有办法处理之时才产生例外对象,所以与使用判断式来避免例外的方式比起来,例外处理会有比较好的执行效能。
52、throw、throws
当程序发生错误而无法处理的时候,会丢出对应的例外对象,除此之外,在某些时刻,您可能会想要自行丢出例外,例如在例外处理结束后,再将例外丢出,让下一层例外处理区块来捕捉,若想要自行丢出例外,您可以使用"throw"关键词,并生成指定的例外对象,例如:
throw new ArithmeticException();
举个例子来说明,在Java的除法中,允许除数为浮点数0.0,所得到的是Infinity,即无穷数,如果您想要自行检验除零错误,可以自行丢出例外,最接近这个条件的是ArithmeticException,当除数为整数且为0时,就会引发这个例外,您可以如下丢出例外:
UseThrow.java
public class UseThrow {public static void main(String[] args) {double dblzero = 0.0;try {System.out.println("浮点数除以零: "+ (100 / dblzero));if(dblzero == 0)throw new ArithmeticException();}catch(ArithmeticException e) {System.out.println("发生除零例外");}} }
执行结果:
浮点数除以零: Infinity 发生除零例外
每个例外都必须有一个"catch"区块来捕捉,在巢状的try...catch时,必须注意该例外是由何者引发并由何者捕捉,例如:
UseThrow.java
public class UseThrow {public static void main(String[] args) {try {try {throw new ArrayIndexOutOfBoundsException();}catch(ArrayIndexOutOfBoundsException e) {System.out.println("ArrayIndexOutOfBoundsException/内层try-catch");}throw new ArithmeticException();}catch(ArithmeticException e) {System.out.println("发生ArithmeticException");}catch(ArrayIndexOutOfBoundsException e) {System.out.println("ArrayIndexOutOfBoundsException/外层try-catch");}} }
执行结果:
ArrayIndexOutOfBoundsException/内层try-catch 发生ArithmeticException?
在这个程序中,ArrayIndexOutOfBoundsException由内层try-catch丢出并捕捉,由于内层 已经捕捉了例外,所以外层的try-catch中之ArrayIndexOutOfBoundsException并不会捕捉到内层所丢出的例外,但如果 内层的try-catch并没有捕捉到这个例外,则外层try-catch就有机会捕捉这个例外,例如:
UseThrow.java
public class UseThrow {public static void main(String[] args) {try {try {throw new ArrayIndexOutOfBoundsException();}catch(ArithmeticException e) {System.out.println("ArrayIndexOutOfBoundsException/内层try-catch");}throw new ArithmeticException();}catch(ArithmeticException e) {System.out.println("发生ArithmeticException");}catch(ArrayIndexOutOfBoundsException e) {System.out.println("ArrayIndexOutOfBoundsException/外层try-catch");}} }
执行结果:
ArrayIndexOutOfBoundsException/外层try-catch
程序中会订定许多方法(Method),这些方法中可能会因某些错误而引发例外,但您不希望直接在这个方法中处理这些例外,而希望呼叫这个它的方法来统一处理,这时候您可以使用"throws"关键词来宣告这个方法将会丢出例外,例如:
private void arrayMethod(int[] arr)throws ArrayIndexOutOfBoundsException,ArithmeticException {// 实作 }
注意如果会丢出多种可能的例外时,中间使用逗点分隔;当有方法上使用"throws"宣告例外时,意味着呼叫该方法的呼叫者必须处理这些例外,而被呼叫方法可以保持程序逻辑的简洁,下面这个范例是"throws"的一个简单示范:
UseThrows.java
public class UseThrows {public static void main(String[] args) {try {throwsTest();}catch(ArithmeticException e) {System.out.println("捕捉例外");}}private static void throwsTest()throws ArithmeticException {System.out.println("这只是一个测试");// 程序处理过程假设发生例外throw new ArithmeticException();} }
执行结果:
这只是一个测试 捕捉例外
简单的说,您要不就在方法中直接处理例外,要不就在方法上宣告该方法会丢回例外,由呼叫它的呼叫者来处理例外,另一方面,在方法上使用 "throws"宣告可丢出的例外,也表示了您只能丢出所宣告类型的例外,其它的例外您必须在方法中处理完,或是重新包装为所宣告的例外再丢出。
如果使用继承时,在父类别的某个方法上宣告了throws某些例外,而在子类别中重新定义该方法时,您可以:
- 不处理例外(重新定义时不设定throws)
- 可仅throws父类别中被重新定义的方法上之某些例外
- 可throws被重新定义的方法上之例外之子类别
但是您不可以:
- throws出额外的例外
- throws被重新定义的方法上之例外之父类别
51、断言(Assertion)
例外是程序中非预期的错误,例外处理是在这些错误发生时所采取的措施。
有些时候,您预期程序中应该会处于何种状态,例如某些情况下某个值必然是多少,这称之为一种断言(Assertion),断言有两种情况:成立或不成立。当预期结果与实际执行相同时,断言成立,否则断言失败。
Java在JDK 1.4之后提供断言陈述,有两种使用的语法:
assert <BOOLEAN_EXPRESSION>; assert <BOOLEAN_EXPRESSION> : <DETAIL_EXPRESSION>;
boolean_expression如果为true,则什么事都不会发生,如果为false,则会发生 java.lang.AssertionError,此时若采取的是第二个语法,则会将detail_expression的结果显示出来,如果是个物 件,则呼叫它的toString()显示文字描述结果。
一个使用断言的时机是内部不变量(Internal invarant)的判断,例如在某个时间点上,或某个状况发生时,您判断某个变量必然要是某个值,举个例子来说:
AssertionDemo.java
public class AssertionDemo {public static void main(String[] args) {if(args.length > 0) {System.out.println(args[0]);}else {assert args.length == 0;System.out.println("没有输入自变量");}} }
在正常的预期中,数组长度是不会小于0的,所以一但执行至else区块,数组长度必然只有一个可能,就是等于0,您断言args.length==0结果 必然成立,else之中的程序代码也只有在断言成立的状况下才能执行,如果不成立,表示程序运行存在错误,else区块不应被执行,您要停下来检查程序的错 误,事实上断言主要的目的通常是在开发时期才使用。
另一个使用断言的时机为控制流程不变量(Control flow invariant)的判断,例如在使用switch时,假设您已经列出了所有的可能常数:
... switch(var) {case Constants.Con1:...break;case Constants.Con2:...break;case Constants.Con3:...break;default:assert false : "非定义的常数"; } ...
假设您已经在switch中列出了所有的常数,即var不该出现Constants.Con1、Constants.Con2、 Constants.Con3以外的常数,则如果发生default被执行的情况,表示程序的状态与预期不符,此时由于assert false必然断言失败。
总结就是,断言是判定程序中的某个执行点必然是某个状态,所以它不能当作像if之类的判断式使用,简单的说它不应是程序执行流程的一部份。
52、
53、
54、
55、
56、
57、
58、
59、
Java学习笔记(3)——资源管理相关推荐
- 《Java学习笔记(第8版)》学习指导
<Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...
- java学习笔记11--Annotation
java学习笔记11--Annotation Annotation:在JDK1.5之后增加的一个新特性,这种特性被称为元数据特性,在JDK1.5之后称为注释,即:使用注释的方式加入一些程序的信息. j ...
- java学习笔记13--反射机制与动态代理
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note13.html,转载请注明源地址. Java的反射机制 在Java运行时环境中,对于任意 ...
- 准备写java学习笔记
准备写java学习笔记 java int 转载于:https://blog.51cto.com/cryingcloud/1975267
- Java学习笔记--StringTokenizer的使用
2019独角兽企业重金招聘Python工程师标准>>> Java Tips: 使用Pattern.split替代String.split String.split方法很常用,用于切割 ...
- java学习笔记12--异常处理
java学习笔记系列: java学习笔记11--集合总结 java学习笔记10--泛型总结 java学习笔记9--内部类总结 java学习笔记8--接口总结 java学习笔记7--抽象类与抽象方法 j ...
- Java学习笔记(十)--控制台输入输出
输入输出 一.控制台输入 在程序运行中要获取用户的输入数据来控制程序,我们要使用到 java.util 包中的 Scanner 类.当然 Java 中还可以使用其他的输入方式,但这里主要讲解 Scan ...
- java学习笔记16--I/O流和文件
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note16.html,转载请注明源地址. IO(Input Output)流 IO流用来处理 ...
- java学习笔记15--多线程编程基础2
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note15.html,转载请注明源地址. 线程的生命周期 1.线程的生命周期 线程从产生到消亡 ...
- java学习笔记14--多线程编程基础1
本文地址:http://www.cnblogs.com/archimedes/p/java-study-note14.html,转载请注明源地址. 多线程编程基础 多进程 一个独立程序的每一次运行称为 ...
最新文章
- python拼音怎么写-Python 返回汉字的汉语拼音
- 深度学总结:CNN Decoder, Upsampling的处理
- 【深度学习】数据降维方法总结
- 一切从用户的需求与体验出发
- 7-25 朋友圈 (25 分)(详解+并查集的了解和应用)
- 流言终结者- Flutter和RN谁才是更好的跨端开发方案?
- html代码如何查看视频,Web前端
- No module named swigfaiss
- 风变编程python第一关_风变编程python学习心得
- MPQ文档布局分析[转帖]
- 【Pix4d精品教程】打开Pix4DMapper时提示Pix4DMapper.exe已停止工作的完全解决办法
- 【项目实战】Python基于RFM模型和K-Means聚类算法进行航空公司客户价值分析
- 图形处理单元(GPU)的演进
- 正则验证邮箱格式是不是正确
- 熔断机制什么意思_熔断机制是什么意思?熔断机制的作用
- 灰色预测方法预测温度matlab,灰色预测模型matlab程序
- 金手指(通达信公式 主图 源码 测试图)箱底 箱顶 短趋势线 中趋势线 压力位 支撑位
- 3Dmax_三维模型无法处理平滑解决方案
- mysql insert 阻塞_insert遭遇阻塞
- Springboot 整合Websocket+Stomp协议+RabbitMQ做消息代理 实例教程