疯狂Java讲义(七)----第一部分

 本章要点:

        Oracle为Java提供了丰富的基础类库,Java 8提供了4000多个基础类(包括下一章将要介绍的集合框架),通过这些基础类库可以提高开发效率,降低开发难度。对于合格的Java程序员而言,至少要熟悉Java SE中 70%以上的类(当然本书并不是让读者去背诵Java API文档),但在反复查阅API文档的过程中,会自动记住大部分类的功能、方法,因此程序员一定要多练,多敲代码
        Java 提供了String、StringBuffer和StringBuilder 来处理字符串,它们之间存在少许差别,本章会详细介绍它们之间的差别,以及如何选择合适的字符串类。Java还提供了Date和Calendar 来处理日期、时间,其中 Date是一个已经过时的API,通常推荐使用Calendar来处理日期、时间。
        正则表达式是一个强大的文本处理工具,通过正则表达式可以对文本内容进行查找、替换、分割等操作。从JDK 1.4以后,Java也增加了对正则表达式的支持,包括新增的Pattern和 Matcher两个类,并改写了String类,让String类增加了正则表达式支持,增加了正则表达式功能后的String类更加强大。
        Java还提供了非常简单的国际化支持,Java 使用Locale对象封装一个国家、语言环境,再使用ResourceBundle根据Locale加载语言资源包,当ResourceBundle加载了指定Locale对应的语言资源文件后,ResourceBundle对象就可调用getString()方法来取出指定key所对应的消息字符串。

1. 运行Java程序的参数

        下面详细讲解 main)方法为什么采用这个方法签名。

  • public修饰符: Java类由JVM调用,为了让JVM可以自由调用这个main()方法,所以使用public修饰符把这个方法暴露出来。
  • static修饰符:JVM调用这个主方法时,不会先创建该主类的对象,然后通过对象来调用该主方法。JVM直接通过该类来调用主方法,因此使用static修饰该主方法。
  • void返回值:因为主方法被JVM调用,该方法的返回值将返回给JVM,这没有任何意义,因此main()方法没有返回值。

        上面方法中还包括一个字符串数组形参,根据方法调用的规则:谁调用方法,谁负责为形参赋值。也就是说,main()方法由JVM调用,即 args形参应该由JVM负责赋值。但JVM怎么知道如何为args数组赋值呢?先看下面程序。

        上面程序几乎是最简单的“HelloWorld"程序,只是这个程序增加了输出args数组的长度,遍历args数组元素的代码。使用java ArgsTest命令运行上面程序,看到程序仅仅输出一个0,这表明args数组是一个长度为0的数组——这是合理的。因为计算机是没有思考能力的,它只能忠实地执行用户交给它的任务,既然程序没有给args数组设定参数值,那么JVM就不知道args数组的元素,所以JVM将args数组设置成一个长度为0的数组。

 从图7.1中可以看出,如果运行Java程序时在类名后紧跟一个或多个字符串(多个字符串之间以空格隔开),JVM就会把这些字符串依次赋给args数组元素。运行Java程序时的参数与args数组之间的对应关系如图7.2所示。

如果某参数本身包含了空格,则应该将该参数用双引号("")括起来,否则JVM 会把这个空格当成参数分隔符,而不是当成参数本身。例如,采用如下命令来运行上面程序:
 看到args数组的长度是1,只有一个数组元素,其值是Java Spring。

2. 使用Scanner 获取键盘输入

        使用Scanner类可以很方便地获取用户的键盘输入, Scanner是一个基于正则表达式的文本扫描器,它可以从文件、输入流、字符串中解析出基本类型值和字符串值。Scanner类提供了多个构造器,不同的构造器可以接收文件、输入流、字符串作为数据源,用于从文件、输入流、字符串中解析数据。


Scanner主要提供了两个方法来扫描输入。

  • hasNextXxx():是否还有下一个输入项,其中Xxx可以是Int、Long等代表基本数据类型的字符串。如果只是判断是否包含下一个字符串,则直接使用hasNext()。
  • nextXxx():获取下一个输入项。Xxx的含义与前一个方法中的Xxx相同。

       在默认情况下,Scanner使用空白(包括空格、Tab空白、回车)作为多个输入项之间的分隔符。下面程序使用Scanner来获得用户的键盘输入。

        运行上面程序,程序通过Scanner 不断从键盘读取键盘输入,每次读到键盘输入后,直接将输入内容打印在控制台。上面程序的运行效果如图7.3所示。
        如果希望改变 Scanner的分隔符(不使用空白作为分隔符),例如,程序需要每次读取一行,不管这一行中是否包含空格,Scanner都把它当成一个输入项。在这种需求下,可以把Scanner的分隔符设置为回车符,不再使用默认的空白作为分隔符

        Scanner的读取操作可能被阻塞(当前执行顺序流暂停)来等待信息的输入。如果输入源没有结束,Scanner 又读不到更多输入项时(尤其在键盘输入时比较常见),Scanner 的 hasNext()和next()方法都有可能阻塞,hasNext()方法是否阻塞与和其相关的next()方法是否阻塞无关
        为Scanner 设置分隔符使用useDelimiter(String pattern)方法即可,该方法的参数应该是一个正则表达式。只要把上面程序中粗体字代码行的注释去掉,该程序就会把键盘的每行输入当成一个输入项,不会以空格、Tab空白等作为分隔符。
        事实上,Scanner提供了两个简单的方法来逐行读取。

  • boolean hasNextLine():返回输入源中是否还有下一行。
  • String nextLine():返回输入源中下一行的字符串。

Scanner不仅可以获取字符串输入项,也可以获取任何基本类型的输入项,如下程序所示。

        注意上面程序中粗体字代码部分,正如通过hasNextLong()和 nextLong()两个方法,Scanner可以直接从输入流中获得 long型整数输入项。与此类似的是,如果需要获取其他基本类型的输入项,则可以使用相应的方法。

 Scanner不仅能读取用户的键盘输入,还可以读取文件输入。只要在创建Scanner对象时传入一个File对象作为参数,就可以让Scanner读取该文件的内容。例如如下程序。

        上面程序创建Scanner对象时传入一个File对象作为参数(如粗体字代码所示),这表明该程序将会读取ScannerFileTest.java文件中的内容。上面程序使用了hasNextLine()和nextLine()两个方法来读取文件内容(如粗体字代码所示),这表明该程序将逐行读取ScannerFileTest.java文件的内容。
        因为上面程序涉及文件输入,可能引发文件IO相关异常,故主程序声明throws Exception表明main方法不处理任何异常

3. System类

        System类代表当前Java程序的运行平台程序不能创建System类的对象,System类提供了一些类变量和类方法,允许直接通过System类来调用这些类变量和类方法。
        System类提供了代表标准输入、标准输出和错误输出的类变量,并提供了一些静态方法用于访问环境变量、系统属性的方法,还提供了加载文件和动态链接库的方法。下面程序通过System类来访问操作的环境变量和系统属性。

        上面程序通过调用System类的getenv()、getProperties()、getProperty()等方法来访问程序所在平台的环境变量和系统属性,程序运行的结果会输出操作系统所有的环境变量值,并输出JAVA HOME 环境变量,以及 os.name系统属性的值,运行结果如图7.4所示。该程序运行结束后还会在当前路径下生成一个props.txt文件,该文件中记录了当前平台的所有系统属性。

        System 类还有两个获取系统当前时间的方法: currentTimeMillis()和 nanoTime(),它们都返回一个long型整数实际上它们都返回当前时间与UTC 1970年1月1日午夜的时间差,前者以毫秒作为单位,后者以纳秒作为单位。必须指出的是,这两个方法返回的时间粒度取决于底层操作系统,可能所在的操作系统根本不支持以毫秒、纳秒作为计时单位。例如,许多操作系统以几十毫秒为单位测量时间,currentTimeMillis()方法不可能返回精确的毫秒数;而nanoTime()方法很少用,因为大部分操作系统都不支持使用纳秒作为计时单位
        除此之外,System类的in、out和 err 分别代表系统的标准输入(通常是键盘)、标准输出(通常是显示器)和错误输出流,并提供了setIn()、setOut()和 setErr()方法来改变系统的标准输入、标准输出和标准错误输出流

       System类还提供了一个identityHashCode(Object x)方法,该方法返回指定对象的精确hashCode值,也就是根据该对象的地址计算得到的 hashCode值。当某个类的 hashCode()方法被重写后,该类实例的hashCode()方法就不能唯一地标识该对象;但通过 identityHashCode()方法返回的 hashCode值,依然是根据该对象的地址计算得到的 hashCode值。所以,如果两个对象的 identityHashCode值相同,则两个对象绝对是同一个对象。如下程序所示。

        通过identityHashCode(Object x)方法可以获得对象的 identityHashCode值,这个特殊的identityHashCode值可以唯一地标识该对象。因为 identityHashCode值是根据对象的地址计算得到的,所以任何两个对象的identityHashCode值总是不相等

4.  Runtime类与Java 9的ProcessHandle

        Runtime类代表Java程序的运行时环境每个Java程序都有一个与之对应的Runtime实例,应用程序通过该对象与其运行时环境相连。应用程序不能创建自己的Runtime实例,但可以通过getRuntime()方法获取与之关联的 Runtime对象
        与System类似的是,Runtime类也提供了gc()方法和 runFinalization()方法来通知系统进行垃圾回收、清理系统资源,并提供了load(String filename)和 loadLibrary(String libname)方法来加载文件和动态链接库
        Runtime类代表Java程序的运行时环境,可以访问JVM的相关信息,如处理器数量、内存信息等。如下程序所示。

        上面程序中粗体字代码就是Runtime类提供的访问JVM相关信息的方法。除此之外,Runtime类还有一个功能——它可以直接单独启动一个进程来运行操作系统的命令,如下程序所示。

        上面程序中粗体字代码将启动 Windows系统里的“记事本”程序。Runtime 提供了一系列exec()方法来运行操作系统命令,关于它们之间的细微差别,请读者自行查阅API文档。
        通过exec启动平台上的命令之后,它就变成了一个进程,Java使用Process来代表进程。Java 9还新增了一个ProcessHandle接口,通过该接口可获取进程的ID、父进程和后代进程;通过该接口的onExit()方法可在进程结束时完成某些行为
        ProcessHandle还提供了一个 ProcessHandle.Info类,用于获取进程的命令、参数、启动时间、累计运行时间、用户等信息。下面程序示范了通过ProcessHandle获取进程的相关信息。

        上面程序比较简单,就是通过粗体字代码获取 Process对象的ProcessHandle对象,接下来即可通过ProcessHandle对象来获取进程相关信息。

5. Object类

        Object类是所有类、数组、枚举类的父类,也就是说,Java 允许把任何类型的对象赋给Object类型的变量。当定义一个类时没有使用extends关键字为它显式指定父类,则该类默认继承Object父类
        因为所有的Java类都是Object类的子类,所以任何Java对象都可以调用Object类的方法。Object类提供了如下几个常用方法。

  • boolean equals(Object obj):判断指定对象与该对象是否相等。此处相等的标准是,两个对象是同一个对象,因此该equals()方法通常没有太大的实用价值。(但可以重写!!)
  • protected void finalize():当系统中没有引用变量引用到该对象时,垃圾回收器调用此方法来清理该对象的资源
  • Class<?>getClass():返回该对象的运行时类,该方法在本书第18章还有更详细的介绍。
  • int hashCode():返回该对象的hashCode值。在默认情况下,Object类的 hashCode()方法根据该对象的地址来计算(即与System.identityHashCode(Object x)方法的计算结果相同)。但很多类都重写了Object类的hashCode()方法,不再根据地址来计算其hashCode()方法值。
  • String toString():返回该对象的字符串表示,当程序使用System.out.println()方法输出一个对象,或者把某个对象和字符串进行连接运算时,系统会自动调用该对象的toString()方法返回该对象的字符串表示Object类的 toString()方法返回“运行时类名@十六进制 hashCode值”格式的字符串,但很多类都重写了Object类的 toString()方法,用于返回可以表述该对象信息的字符串

        除此之外,Object类还提供了wait()、notify()、notifyAll()几个方法,通过这几个方法可以控制线程的暂停和运行。本书将在第16章介绍这几个方法的详细用法。
        Java还提供了一个protected修饰的clone()方法,该方法用于帮助其他对象来实现“自我克隆”,所谓“自我克隆”就是得到一个当前对象的副本,而且二者之间完全隔离。由于Object类提供的clone()方法使用了protected修饰,因此该方法只能被子类重写或调用

自定义类实现“克隆”的步骤如下。

  1. 自定义类实现 Cloneable接口。这是一个标记性的接口,实现该接口的对象可以实现“自我克隆”,接口里没有定义任何方法。
  2. 自定义类实现自己的clone()方法。
  3. 实现clone()方法时通过super.clone();调用Object实现的clone()方法来得到该对象的副本,并返回该副本。如下程序示范了如何实现“自我克隆”。

        上面程序让User类实现了Cloneable接口,而且实现了clone()方法,因此User对象就可实现“自我克隆”——克隆出来的对象只是原有对象的副本。程序在①号粗体字代码处判断原有的User 对象与克隆出来的User对象是否相同,程序返回false

        Object类提供的Clone机制只对对象里各实例变量进行“简单复制”,如果实例变量的类型是引用类型,Object的 Clone机制也只是简单地复制这个引用变量,这样原有对象的引用类型的实例变量与克隆对象的引用类型的实例变量依然指向内存中的同一个实例,所以上面程序在②号代码处输出true。上面程序“克隆”出来的ul、u2所指向的对象在内存中的存储示意图如图7.5所示。


        Object类提供的clone()方法不仅能简单地处理“复制”对象的问题,而且这种“自我克隆”机制十分高效。比如clone 一个包含100个元素的int[]数组,用系统默认的clone方法比静态copy方法快近2倍。
        需要指出的是,Object 类的clone()方法虽然简单、易用,但它只是一种“浅克隆”一它只克隆该对象的所有成员变量值,不会对引用类型的成员变量值所引用的对象进行克隆。如果开发者需要对对象进行“深克隆”,则需要开发者自己进行“递归”克隆,保证所有引用类型的成员变量值所引用的对象都被复制了

6. Java 7新增的Objects

        Java 7新增了一个Objects 工具类,它提供了一些工具方法来操作对象,这些工具方法大多是“空指针”安全的。比如你不能确定一个引用变量是否为null,如果贸然地调用该变量的 toString()方法,则可能引发NullPointerExcetpion异常;但如果使用Objects类提供的 toString(Object o)方法,就不会引发空指针异常,当o为null 时,程序将返回一个"null"字符串。

        上面程序还示范了Objects提供的 requireNonNull()方法当传入的参数不为null时,该方法返回参数本身;否则将会引发 NullPointerException异常。该方法主要用来对方法形参进行输入校验,例如如下代码:

 7. Java 9改进的String、StringBuffer和StringBuilder类

        字符串就是一连串的字符序列,Java提供了String、StringBuffer和StringBuilder三个类来封装字符串,并提供了一系列方法来操作字符串对象
        String类是不可变类,即一旦一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。
        StringBuffer对象则代表一个字符序列可变的字符串,当一个StringBuffer被创建以后,通过StringBuffer提供的 append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的 toString()方法将其转换为一个String对象。
        StringBuilder类是JDK 1.5新增的类,它也代表可变字符串对象。实际上, StringBuilder和StringBuffer基本相似,两个类的构造器和方法也基本相同。不同的是,StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高。因此在通常情况下,如果需要创建一个内容可变的字符串对象,则应该优先考虑使用StringBuilder类。

        Java9改进了字符串(包括String、StringBuffer、StringBuilder)的实现。在Java9以前字符串采用char[]数组来保存字符,因此字符串的每个字符占2字节;而Java 9的字符串采用 byte[]数组再加一个encoding-flag字段来保存字符,因此字符串的每个字符只占1字节。所以Java 9的字符串更加节省空间,但字符串的功能方法没有受到任何影响。

 String类提供了大量构造器来创建String对象,其中如下几个有特殊用途。

  • String():创建一个包含0个字符串序列的String对象(并不是返回null)。
  • String(byte[ ] bytes, Charset charset):使用指定的字符集将指定的byte[]数组解码成一个新的String对象。
  • String(byte[] bytes, int offset, int length):使用平台的默认字符集将指定的 byte[]数组从offset开始、长度为length的子数组解码成一个新的String对象。
  • String(byte[] bytes, int offset, int length, String charsetName):使用指定的字符集将指定的 byte]数组从offset开始、长度为length 的子数组解码成一个新的String对象。
  • String(byte[] bytes,String charsetName):使用指定的字符集将指定的byte[]数组解码成一个新的String对象。
  • String(char[]value, int offset, int count):将指定的字符数组从offset开始、长度为count的字符元素连缀成字符串。
  • String(String original):根据字符串直接量来创建一个String 对象。也就是说,新创建的String对象是该参数字符串的副本。
  • String(StringBuffer buffer):根据StringBuffer对象来创建对应的String对象。
  • String(StringBuilder builder):根据StringBuilder对象来创建对应的String对象。

String 类也提供了大量方法来操作字符串对象,下面详细介绍这些常用方法。

  • char charAt(int index):获取字符串中指定位置的字符。其中,参数 index 指的是字符串的序数,字符串的序数从0开始到length()-1。如下代码所示。

  • int compareTo(String anotherString):比较两个字符串的大小。如果两个字符串的字符序列相等,则返回0;不相等时,从两个字符串第0个字符开始比较,返回第一个不相等的字符差。另一种情况,较长字符串的前面部分恰巧是较短的字符串,则返回它们的长度差

  • String concat(String str):将该String对象与str连接在一起。与Java提供的字符串连接运算符“+”的功能相同。
  • boolean contentEquals(StringBuffer sb):将该String对象与StringBuffer对象sb进行比较,当它们包含的字符序列相同时返回true
  • static String copyValueOf(char[]data):将字符数组连缀成字符串,与String(char[]content)构造器的功能相同。
  • static String copyValueOf(char[] data, int offset, int count):将char数组的子数组中的元素连缀成字符串,与String(char[] value, int offset, int count)构造器的功能相同。
  • boolean endsWith(String suffix):返回该String 对象是否以 suffix结尾

  • boolean equals(Object anObject):将该字符串与指定对象比较,如果二者包含的字符序列相等,则返回true;否则返回false。
  • boolean equalsIgnoreCase(String str):与前一个方法基本相似,只是忽略字符的大小写
  • byte[] getBytes():将该String对象转换成byte数组
  • void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin):该方法将字符串中从srcBegin开始,到srcEnd结束的字符复制到dst字符数组中,其中 dstBegin为目标字符数组的起始复制位置。

  • int indexOf(int ch):接收传入的参数是ascii码 找出ch字符在该字符串中第一次出现的位置
  • int indexOf(int ch, int fromIndex):找出 ch字符在该字符串中从fromIndex开始后第一次出现的位置
  • int indexOf(String str):找出str子字符串在该字符串中第一次出现的位置。
  • int indexOf(String str,int fromIndex):找出 str子字符串在该字符串中从fromIndex开始后第一次出现的位置。

都是从0开始:

  • int lastIndexOf(int ch);找出ch字符在该字符串中最后一次出现的位置。
  • int lastIndexOf(int ch, int fromIndex):找出ch字符在该字符串中从fromIndex开始后最后一次出现的位置。
  • int lastIndexOf(String str);找出str 子字符串在该字符串中最后一次出现的位置。
  • int lastIndexOf(String str, int fromIndex):找出str 子字符串在该字符串中从fromIndex开始后最后一次出现的位置。
  • int length():返回当前字符串长度。
  • String replace(char oldChar, char newChar):将字符串中的第一个 oldChar替换成   newChar.
  • boolean startsWith(String prefix):该String对象是否以prefix开始。
  • boolean startsWith(String prefix, int toffset):该String对象从toffset位置算起,是否以prefix开始。

  • String substring(int beginIndex):获取从beginIndex位置开始到结束的子字符串。
  • String substring(int beginIndex, int endIndex):获取从beginIndex位置开始到endIndex位置的子字符串。
  • char[] toCharArray():将该String对象转换成char数组。
  • String toLowerCase():将字符串转换成小写。
  • String toUpperCase():将字符串转换成大写。

  •  static String valueOf(X x):一系列用 于将基本类型值转换为String对象的方法。

String类是不可变的,String 的实例一旦生成就不会再改变了,例如如下代码。

        上面程序除使用了3个字符串直接量之外,还会额外生成2个字符串直接量——"java" 和"struts"连接生成的"javastruts",接着"javastruts"与"spring"连接生成的"javatrutspring",程序中的str1依次指向3个不同的字符串对象。
        因为String是不可变的,所以会额外产生很多临时变量,使用StringBuffer 或StringBuilder就可以避免这个问题。

        StringBuilder提供了一系列插入、追加、改变该字符串里包含的字符序列的方法。而StringBuffer与其用法完全相同,只是StringBuffer是线程安全的。
        StringBuilder、StringBuffer 有两个属性: length 和capacity,其中length 属性表示其包含的字符序列的长度。与String对象的length不同的是,StringBuilder、 StringBuffer 的length 是可以改变的,可以通过length()、 setLength(int len)方法来访问和修改其字符序列的长度capacity 属性表示StringBuilder的容量,capacity 通常比length大,程序通常无须关心capacity属性。如下程序示范了StringBuilder 类的用法。

         上面程序中粗体 字部分示范了StringBuilder 类的追加、插入、替换、删除等操作,这些操作改变了StringBuilder里的字符序列,这就是StringBuilder与String之间最大的区别: StringBuilder 的字符序列是可变的。从程序看到StringBuilder 的length()方法返回其字符序列的长度,而capacity()返 回值则比length()返回值大。

散称知识点:

  • Java程序在不同操作系统上运行时,可能需要取得平台相关的属性,或者调用平台命令来完成特定功能。Java提供了System类和Runtime类来与程序的运行平台进行交互
     

猜你喜欢

转载自blog.csdn.net/indeedes/article/details/120941901
今日推荐