Java学习笔记
第七章——Java基础类库
与用户互动
运行Java程序的参数
public static void main ( String[ ] args) { . . . }
public:Java类由JVM调用,为了让JVM可以自由调用这个方法,使用该修饰符把它暴露出来 static:JVM调用这个主方法时,不会创建对象,而是通过该类来调用,故使用static void:将该方法的返回值返回给JVM没有任何意义 String[] args形参:在调用该程序时,在主类名称后增加其他字符串,会被当做形参处理,如果没有,则是长度为0的数组:
public class ArgsTest {
public static void main ( String[ ] args)
{
System. out. println ( args. length) ;
for ( String tmp: args
) {
System. out. println ( tmp) ;
}
}
}
lancibe@lancibe-TM1701:~/java/test/src/chapter7$ java ArgsTest Lancibe
1
Lancibe
使用Scanner获取键盘输入
运行Java程序时传入参数只能在开始运行之前就设定几个固定的参数。对于更复杂的情形,程序需要在运行过程中获取输入。 使用Scanner类可以很方便的获取用户的键盘输入,Scanner是一个基于正则表达式的文本扫描器,他可以从文件、输入流、字符串中解析出基本类型值和字符串值。Scanner类提供了多个构造器,不同的构造器可以接受文件、输入流、字符串作为数据源,用于从文件、输入流、字符串中解析数据。 Scanner主要提供了两个方法来扫描输入。
hasNextXxx():是否还有下一个输入项,如果只是判断是否包含下一个字符串,则可以使用hasNext() nextXxx():获取下一个输入项。Xxx的含义与前一个方法中的Xxx相同。
import java. util. Scanner;
public class ScannerKeyBoardTest {
public static void main ( String[ ] args)
{
Scanner sc = new Scanner ( System. in) ;
while ( sc. hasNext ( ) )
{
System. out. println ( "键盘输入的内容是:" + sc. next ( ) ) ;
}
}
}
为Scanner设置分隔符时使用useDelimiter(String pattern)方法即可,该方法的参数应该是一个正则表达式,关于正则表达式的内容会在后面介绍。只要把上面程序中粗体字代码行的注释去掉,程序就会把键盘的每行输入当成一个输入项,不会以空格、Tab制表符等作为分隔符 事实上,Scanner提供了两个简单的方法来逐行读取
boolean hasNextLine():返回输入源中是否还有下面一行 String nextLine():返回输入源中下一行的字符串 Scanner不仅能读取用户的键盘输入,还可以读取文件输入。只要在创建Scanner对象时传入File对象作为参数,就可以让Scanner读取该文件的内容。
import java. io. File;
import java. util. Scanner;
public class ScannerFileTest {
public static void main ( String[ ] args)
throws Exception
{
Scanner sc = new Scanner ( new File ( "ScannerFileTest.java" ) ) ;
System. out. println ( "ScannerFileTest.java文件内容如下:" ) ;
while ( sc. hasNextLine ( ) )
{
System. out. println ( sc. nextLine ( ) ) ;
}
}
}
上面程序创建Scanner对象时传入了一个File对象作为参数,这表明该程序会自动读取ScannerFileTest.java文件中的内容。上面程序使用了hasNextLine()和nextLine()两个方法来读取文件内容,这表明该程序将逐行读取ScannerFileTest.java文件的内容。
系统相关
System类
System类代表当前Java程序的运行平台,程序不能创建System类的对象,该类提供了一些类变量和类方法,允许直接通过System类来调用这些类变量和类方法。 该类提供了代表标准输入,标准输出和错误输出的类变量,并提供了一些静态方法用于访问环境变量、系统属性的方法,还提供了加载文件和动态链接库的方法。下面程序通过System类来访问操作的环境变量和系统属性
import java. io. FileOutputStream;
import java. util. Map;
import java. util. Properties;
public class SystemTest {
public static void main ( String[ ] args)
throws Exception
{
Map< String, String> env = System. getenv ( ) ;
for ( String name: env. keySet ( ) )
{
System. out. println ( name + " --> " + env. get ( name) ) ;
}
System. out. println ( System. getenv ( "JAVA_HOME" ) ) ;
Properties props = System. getProperties ( ) ;
props. store ( new FileOutputStream ( "props.txt" ) , "System Properties" ) ;
System. out. println ( System. getProperty ( "os.name" ) ) ;
}
}
上面程序通过调用System类的getenv()、getProperties()、getProperty()等方法来访问程序的环境变量和系统属性,程序运行的结果会输出操作系统所有的环境变量值,并输出JAVA_HOME环境变量,以及os.name系统属性的值。 System类的in、out和err分别代表系统的标准输入(键盘)标准输出(显示器)和错误输出流,并提供了setIn()、setOut()、setErr()方法来改变系统的标准输入输出以及错误流。 System类还提供了一个identityHashCode(Object x)方法,该方法返回指定对象的精确hashCode值,也就是根据该对象的地址计算得到的hashCode值。当某个类的hashCode()方法被重写后,该类实例的hashCode()方法就不能唯一的标识该对象;但通过该方法刚回的hashCode值依然是根据该对象的地址计算而来的。所以,如果两个对象的identityHashCode值相同,则两个对象绝对是同一个对象
public class IdentityHashCodeTest {
public static void main ( String[ ] args)
{
String s1 = new String ( "Hello" ) ;
String s2 = new String ( "Hello" ) ;
System. out. println ( s1. hashCode ( ) + "----" + System. identityHashCode ( s2) ) ;
String s3 = "lancibe" ;
String s4 = "lancibe" ;
System. out. println ( System. identityHashCode ( s3) + "----" + System. identityHashCode ( s4) ) ;
}
}
lancibe@lancibe-TM1701:~/java/test/src/chapter7$ java IdentityHashCodeTest
69609650----225534817
664740647----664740647
Runtime类与Java9 的ProcessHandle
Runtime类代表Java程序的运行环境,每个Java程序都有一个与之对应的Runtime实例,应用程序通过该对象与其运行时的环境相连。应用程序不能创建自己的Runtime实例,但可以通过getRuntime()方法获取与之相关联的Runtime对象。 与System类似的是,该类也提供了gc()方法和runFinalization()方法来通知系统进行垃圾回收、清理系统资源,并提供了load(String filename)和loadLibrary(String libname)方法来加载文件和动态链接库。 Runtime类代表了Java程序的运行时环境,可以访问JVM相关信息,如处理器数量、内存信息等。
public class RuntimeTest {
public static void main ( String[ ] args)
{
Runtime rt = Runtime. getRuntime ( ) ;
System. out. println ( "处理器数量:" + rt. availableProcessors ( ) ) ;
System. out. println ( "空闲内存数:" + rt. freeMemory ( ) ) ;
System. out. println ( "总内存数:" + rt. totalMemory ( ) ) ;
System. out. println ( "可用最大内存数:" + rt. maxMemory ( ) ) ;
}
}
处理器数量:8
空闲内存数:261958072
总内存数:264241152
可用最大内存数:4169138176
上面程序中使用的就是Runtime类提供的访问JVM相关信息的方法。除此之外,Runtime类还有一个功能——可以直接单独启动一个进程来运行操作系统的命令:
public class ExecTest {
public static void main ( String[ ] args)
throws Exception
{
Runtime rt = Runtime. getRuntime ( ) ;
rt. exec ( "gnome-terminal" ) ;
}
}
将启动一个新的终端,这样的操作是十分有用而且有趣的 。通过exec启动平台上的命令之后,通过该接口可以获取进城的ID、父进程和后代进程;通过该接口的onExit()方法可以在进城结束时完成某些行为。 ProcessHandle还提供了一个ProcessHandle.Info类,用于获取进程的命令、参数、启动时间、累计运行时间、用户等信息。
import java. util. concurrent. CompletableFuture;
public class ProcessHandleTest {
public static void main ( String[ ] args)
throws Exception
{
Runtime rt = Runtime. getRuntime ( ) ;
Process p = rt. exec ( "gnome-terminal" ) ;
ProcessHandle ph = p. toHandle ( ) ;
System. out. println ( "进程是否运行:" + ph. isAlive ( ) ) ;
System. out. println ( "进程ID:" + ph. pid ( ) ) ;
System. out. println ( "父进程:" + ph. parent ( ) ) ;
ProcessHandle. Info info = ph. info ( ) ;
System. out. println ( "进程命令:" + info. command ( ) ) ;
System. out. println ( "进程参数:" + info. arguments ( ) ) ;
System. out. println ( "进程启动时间:" + info. startInstant ( ) ) ;
System. out. println ( "进程累计运行时间:" + info. totalCpuDuration ( ) ) ;
CompletableFuture< ProcessHandle> cf = ph. onExit ( ) ;
cf. thenRunAsync ( ( ) - > {
System. out. println ( "程序退出" ) ;
} ) ;
Thread. sleep ( 5000 ) ;
}
}
常用类
Object类
Object类是所有类、数组、枚举类的父类,也就是说,Java允许把任何类型的对象赋给Object类型的变量。当定义一个类时没有使用extends关键字为她显式指定父类,则该类默认继承Object类。 该类提供了如下几个常用方法:
boolean equals ( Object obj) ;
protected void finalize ( ) ;
Class< ? > getClass ( ) ;
int hashCode ( ) ;
String toString ( ) ;
Java还提供了一个protected修饰的clone()方法,该方法用于帮助其他对象来实现自我克隆,所谓的自我克隆就是得到一个当前对象的副本,而且二者之间完全隔离。由于Object类提供的clone()方法使用了protected修饰,因此该方法只能被子类重写或调用。 自定义类实现“克隆”的步骤如下:
自定义类实现Cloneable接口。这是一个标志性的接口,实现该接口的对象可以实现“自我克隆”,接口里没有定义任何方法。 自定义类实现自己的clone()方法 实现clone()方法时通过super.clone()调用Object实现的clone()方法来得到该对象的副本,并返回该副本。
class Address
{
String detail;
public Address ( String detail)
{
this . detail = detail;
}
}
class User implements Cloneable
{
int age;
Address address;
public User ( int age)
{
this . age = age;
address = new Address ( "西长安街" ) ;
}
public User clone ( )
throws CloneNotSupportedException
{
return ( User) super . clone ( ) ;
}
}
public class CloneTest {
public static void main ( String[ ] args)
throws CloneNotSupportedException
{
User u1 = new User ( 29 ) ;
User u2 = u1. clone ( ) ;
System. out. println ( u1 == u2) ;
System. out. println ( u1. address == u2. address) ;
}
}
Java7 新增的Objects类
Java7 新增了一个Objects工具类,他提供了一些工具方法来操作对象,这些工具方法大多是“空指针”安全的。比如不能确定一个引用变量是否为null,如果贸然地调用该变量的toString方法,可能会引发NullPointerException异常;但如果使用Objects类提供的toString(Object o)方法,就不会引发了,当o是null时,程序将返回一个“null”字符串。
import java. util. Objects;
public class ObjectsTest {
static ObjectsTest obj;
public static void main ( String[ ] args)
{
System. out. println ( Objects. hashCode ( obj) ) ;
System. out. println ( Objects. toString ( obj) ) ;
System. out. println ( Objects. requireNonNull ( obj, "obj参数不能是null" ) ) ;
}
}
Java9 改进的String、StringBuffer、StringBuilder类
字符串就是一连串的字符序列,Java提供了String、StringBuffer、StringBuilder三个类来封装字符串,并提供了一系列方法来操作字符串对象。 String类是不可变类,即一旦一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。 StringBuffer对象这代表一个字符序列可变的字符串,当一个StringBuffer被创建后,通过他提供的append()、insert()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的toString()方法将其转换为一个String对象。 StringBuilder类也代表可变字符串对象。实际上,这两个类基本相似,两个类的构造器和方法也基本相同。不同的是,前者是线程安全的,迩后者并没有实现线程安全功能,所以性能略高。因此在通常情况下,应优先考虑StringBuilder类
这三个类都实现了CharSequence接口,因此该接口可以认为是一个字符串的协议接口。
String类提供了大量构造器来创建String对象:
String ( ) : 创建一个包含0 个字符串序列的String对象
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) : 使用指定的字符集将指定的bytes[ ] 数组从offset开始、长度为length的子数组解码成一个新的String对象。
. . .
Math类
public class MathTest {
public static void main ( String[ ] args)
{
System. out. println ( "Math.toDegrees(1.57):" + Math. toDegrees ( 1.57 ) ) ;
System. out. println ( "Math.toRadians(90):" + Math. toRadians ( 90 ) ) ;
System. out. println ( "Math.acos(1.2):" + Math. acos ( 1.2 ) ) ;
System. out. println ( "Math.sinh(1.2):" + Math. sinh ( 1.2 ) ) ;
System. out. println ( "Math.atan2(0.1, 0.2):" + Math. atan2 ( 0.1 , 0.2 ) )
System. out. println ( "Math.floor(-1.2):" + Math. floor ( - 1.2 ) ) ;
System. out. println ( "Math.ceil(2.3):" + Math. ceil ( 2.3 ) ) ;
System. out. println ( "Math.round(3.4):" + Math. round ( 3.4 ) ) ;
System. out. println ( "Math.exp(2):" + Math. exp ( 2 ) ) ;
}
}
正则表达式
正则表达式是一个强大的字符串处理工具,可以对字符串进行查找、提取、分割、替换等操作。String类里也提供了如下几个特殊的方法:
boolean matches ( String regex) ;
String replaceAll ( String regex, String replacement) ;
String replaceFirst ( String regex, String replacement) ;
String[ ] split ( String regex) ;
上面这些特殊的方法都依赖于Java提供的正则表达式支持,除此之外,Java还提供了Pattern和Matcher这两个类专门用于提供正则表达式支持。
创建正则表达式
正则表达式就是一个用于匹配字符串的模板,可以匹配一批字符串,所以创建正则表达式就是创建一个特殊的字符串。下表时正则表达式所支持的合法字符
字符 解释 x 字符x \0mnn 八进制数0mnn所表示的字符 \xhh 十六进制0xhh \uhhhh 十六进制0xhhhh所表示的Unicode字符 \t 制表符 \n 换行符 \r 回车符 \f 换页符 \a 报警(bell)符 \e Escape符 \cx x对应的控制符,例如\cM代表Ctrl-M。x的值必须为A~Z或a~z之一
除此之外,正则表达式中还有一些特殊的字符,这些特殊字符在正则表达式中有其特殊的用途,如果需要匹配这些字符,则必须首先将这些字符转义,也就是加上反斜线。
特殊字符 说明 $ 匹配一行的结尾 ^ 匹配一行的开头 () 标记子表达式的开始和结束位置 [] 用于确定中括号表达式的开始和结束位置 {} 用于标记前面子表达式的出现频度 * 指定前面子表达式可以出现零次或多次 + 指定前面子表达式可以出现一次或多次 ? 指定前面子表达式可以出现零次或一次 . 匹配除换行符\n以外的任何单字符 \ 用于转义下一个字符,或指定八进制、十六进制字符 | 制定两项之间任选一项
注意如果需要匹配特殊字符本身,应使用\\m
的形式。 下面举例说明具体使用方法
"\u0041\\\\"
"\u0061\t"
"\\?\\["
前面的正则表达式依然只能匹配单个字符,这因为还未在正则表达式中使用“通配符”,他是可以匹配多个字符串的特殊字符。正则表达式中的“通配符”远远超出了普通通配符的功能,他被称为预定义字符
预定义字符 说明 . 可以匹配任何字符 \d 匹配0~9之间的所有数字 \D 匹配非数字 \s 匹配所有的空白字符,包括空格、制表符、回车符、换行符、换页符等 \S 匹配所有的非空白字符 \w 匹配所有的单词字符,包括0~9所有数字、26个英文字母和下划线_ \W 匹配所有的非单词字符
"c\\wt"
"\\d\\d\\d-\\d\\d\\d-\\d\\d\\d\\d"
在一些特殊的情况下,例如只想匹配a~f之间的字母,或者匹配除ab以外的所有小写字母,或者匹配中文字符,上面的预定义字符就无能为力了,此时就需要方括号表达式。
方括号表达式 说明 表示枚举 例如[abc],表示a、b、c其中任意一个字符 表示范围:- 例如[a-f],表示a~f之间的任意字符,[\\u0041-\\u0056]表示十六进制的这两个字符之间的字符,[a-cx-z]表示a~c或x~z范围内的任意字符 表示求否:^ 例如[abc]表示非a、b、c的任意字符,[ a-f]表示非a~f之间的任意字符 表示“与”运算:&& 例如[a-z&&[def]],求a~z和[def]的交集,表示d、e或f。[a-z&&[^m-p]],即[a-lq-z]。 表示“并”运算 并运算与前面的枚举类似,如[a-d[m-p]]表示[a-dm-p]。
正则表达式还支持圆括号表达式,用于将多个表达式组成一个子表达式,圆括号中可以使用或运算符(|)。例如,正则表达式`"((public)|(protected)|(private))"用于匹配Java的三个访问控制符其中之一。 除此之外,Java正则表达式还支持如下表所示的几个边界控制符
边界匹配符 说明 ^ 行的开头 $ 行的结尾 \b 单词的边界 \B 非单词的边界 \A 输入的开头 \G 前一个匹配的结尾 \Z 输入的结尾,仅用于最后的控制符 \z 输入的结尾
正则表达式还提供了数量标识符:
Greedy(贪婪模式):数量表示符默认采用贪婪模式,除非另有表示。该模式的表达式会一直匹配下去,直至无法匹配。如果发现表达式匹配的结果与预期不符,很有可能是因为使用了贪婪模式 Reluctant(勉强模式):用问号后缀(?)表示,他只会匹配最小的字符,也称为最小匹配模式 Possessive(占有模式):用加号后缀(+)表示,目前只有Java支持占有模式,通常比较少用。
贪婪模式 勉强模式 占有模式 说明 X? X?? X?+ X表达式出现零次或一次 X* X*? X*+ X表达式出现零次或多次 X+ X+? X++ X表达式出现一次或多次 X{n} X{n}? X{n}+ X表达式出现n次 X{n,} X{n,}? X{n,}+ X表达式至少出现n次 X{n,m} X{n,m}? X{n,m}+ X表达式最少出现n次,最多出现m次
public class test {
public static void main ( String[ ] args)
{
String str = "hello, java" ;
System. out. println ( str. replaceFirst ( "\\w*" , "lancibe" ) ) ;
System. out. println ( str. replaceFirst ( "\\w*?" , "lancibe" ) ) ;
}
}
lancibe@lancibe-TM1701:~/java/test/src/chapter7$ java test
lancibe, java
lancibehello, java
当从"hello, java"字符串中查找匹配"\w*"子串时,第一行使用了贪婪模式,所以直到逗号才停止,第二行使用了勉强模式,故匹配了0个字符。
使用正则表达式
一旦在程序中定义了正则表达式,就可以通过Pattern和Matcher来使用正则表达式。 Pattern对象是正则表达式编译后在内存中的表示形式,因此,正则表达式字符串必须先被编译为Pattern对象,然后再利用该Pattern对象创建对应的Matcher对象,执行匹配所涉及的状态保留在Matcher对象中,多个Matcher对象可以共享同一个Pattern对象。 因此,典型的调用顺序如下:
Pattern p = Pattern. compile ( "a*b" ) ;
Matcher m = p. matcher ( "aaaaab" ) ;
boolean b = m. matches ( ) ;
上面定义的Pattern对象可以多次重复使用。如果一个正则表达式仅需要一次使用,这可以直接使用Pattern类的静态matches()方法,此方法自动把指定字符串编译成匿名的Pattern对象,并执行匹配:
boolean p = Pattern. matches ( "a*b" , "aaaaab" ) ;
Pattern是不可变类,可供多个并发线程安全使用 Matcher类提供了如下多个常用方法
find():返回目标字符串中是否包含与Pattern匹配的子串
group():返回上一次与Pattern匹配的子串
start():返回上一次与Pattern匹配的子串在目标字符串中的开始位置
end():返回上一次与Pattern匹配的子串在目标字符串中的结束位置加一
lookingAt():放回目标字符串前面部分与Pattern是否匹配
matches():返回整个目标字符串与Pattern是否匹配
reset():将现有的Matcher对象应用于一个新的字符序列。
-下面程序示范了如何从大段字符串中找出电话号码。
import java. util. regex. Matcher;
import java. util. regex. Pattern;
public class FindGroup {
public static void main ( String[ ] args)
{
String str = "Lancibe, tel:13512341234"
+ "Lan, tel:15701234567"
+ "Xun, tel:14288886666" ;
Matcher m = Pattern. compile ( "((13\\d)|(15\\d)|(14\\d))\\d{8}" ) . matcher ( str) ;
while ( m. find ( ) )
{
System. out. println ( m. group ( ) ) ;
}
}
}
find()方法还可以传入一个int类型的参数,带int参数的find()方法将从该int索引处向下搜索。 start()和end()方法主要用于确定子串在目标字符串中的位置:
import java. util. regex. Matcher;
import java. util. regex. Pattern;
public class StartEnd {
public static void main ( String[ ] args)
{
String regStr = "Java is not very easy :(" ;
System. out. println ( "目标字符串:" + regStr) ;
Matcher m = Pattern. compile ( "\\w+" ) . matcher ( regStr) ;
while ( m. find ( ) )
{
System. out. println ( m. group ( ) + "\n子串起始位置:" + m. start ( ) + "\n子串结束位置:" + m. end ( ) ) ;
}
}
}
lancibe@lancibe-TM1701:~/java/test/src/chapter7$ java StartEnd
目标字符串:Java is not very easy :(
Java
子串起始位置:0
子串结束位置:4
is
子串起始位置:5
子串结束位置:7
not
子串起始位置:8
子串结束位置:11
very
子串起始位置:12
子串结束位置:16
easy
子串起始位置:17
子串结束位置:21
matches()和lookingAt()方法有点类似,只是matches()方法要求整个字符串和Pattern完全匹配才返回true,而lookingAt()只要求字符串以Pattern开头就会返回true。Reset()方法可以将现有的Matcher对象应用于新的字符序列:
import java. util. regex. Matcher;
import java. util. regex. Pattern;
public class MatchesTest {
public static void main ( String[ ] args)
{
String[ ] mails =
{
"lancibe@yahoo.com" ,
"lancibe0326@gmail.com" ,
"1609547089@qq.com" ,
"lancibe@xiyoulinux.com" ,
"123@abc.xx"
} ;
String mailRegEx = "\\w{3,20}@\\w+\\.(com|org|cn|net|gov)" ;
Pattern mailPattern = Pattern. compile ( mailRegEx) ;
Matcher matcher = null;
for ( String mail: mails)
{
if ( matcher == null)
{
matcher = mailPattern. matcher ( mail) ;
}
else
{
matcher. reset ( mail) ;
}
String result = mail+ ( matcher. matches ( ) ? "是" : "不是" ) + "一个有效的邮件地址" ;
System. out. println ( result) ;
}
}
}
除此之外,还可以利用正则表达式对目标字符串进行分割、查找、替换等操作:
import java. util. regex. Matcher;
import java. util. regex. Pattern;
public class ReplaceTest {
public static void main ( String[ ] args)
{
String[ ] msgs=
{
"Java has regular expressions in 1.4" ,
"regular expressions now expressing is Java" ,
"Java represses oracular expressions"
} ;
Pattern p = Pattern. compile ( "re\\w+" ) ;
Matcher match = null;
for ( int i = 0 ; i < msgs. length ; i++ )
{
if ( match == null)
{
match = p. matcher ( msgs[ i] ) ;
}
else
{
match. reset ( msgs[ i] ) ;
}
System. out. println ( match. replaceAll ( "哈哈:)" ) ) ;
}
}
}
实际上,String类中也提供了replaceAll()、replaceFirst()、split()等方法。
import java. util. Arrays;
public class StringReg {
public static void main ( String[ ] args)
{
String[ ] msgs =
{
"Java has regular expressions in 1.4" ,
"regular expressions now expressing is Java" ,
"Java represses oracular expressions"
} ;
for ( String msg: msgs)
{
System. out. println ( msg. replaceFirst ( "re\\w*" , "哈哈:)" ) ) ;
System. out. println ( Arrays. toString ( msg. split ( " " ) ) ) ;
}
}
}
lancibe@lancibe-TM1701:~/java/test/src/chapter7$ java StringReg
Java has 哈哈:) expressions in 1.4
[ Java, has, regular, expressions, in, 1.4]
哈哈:) expressions now expressing is Java
[ regular, expressions, now, expressing, is, Java]
Java 哈哈:) oracular expressions
[ Java, represses, oracular, expressions]
变量处理和方法处理
Java9 引入了一个新的VarHandle类,并增强了原有的MethodHandle类。通过这两个类,允许Java像动态语言一样引用变量、引用方法,并调用他们。
Java9 增强的MethodHandle
该类为Java增加了方法引用的功能,方法引用的概念有点类似于C语言的函数指针。这种方法引用是一种轻量级的引用方式,他不会检查方法的访问权限,也不管方法所属的类、实例方法或静态方法,MethodHandle就是简单代表特定的方法,并可通过该类来调用方法。 为了使用该类,还涉及如下几个类:
MethodHandles:MethodHandle的工厂类,他提供了一系列静态方法用于获取MethodHandle MethodHandles.Lookup静态内部类,他也是MethodHandle、VarHandle的工厂类,专门用于获取MethodHandle和VarHandle MethodType代表一个方法类型。MethodType根据方法的形参、返回值类型来确定方法类型
import java. lang. invoke. MethodHandle;
import java. lang. invoke. MethodHandles;
import java. lang. invoke. MethodType;
public class MethodHandleTest {
private static void hello ( )
{
System. out. println ( "Hello world!" ) ;
}
private String hello ( String name)
{
System. out. println ( "执行带参数的hello" + name) ;
return name+ ",你好" ;
}
public static void main ( String[ ] args)
throws Throwable
{
MethodType type = MethodType. methodType ( void . class ) ;
MethodHandle mtd = MethodHandles. lookup ( ) . findStatic ( MethodHandleTest. class , "hello" , type) ;
mtd. invoke ( ) ;
MethodHandle mtd2 = MethodHandles. lookup ( ) . findVirtual ( MethodHandleTest. class , "hello" ,
MethodType. methodType ( String. class , String. class ) ) ;
System. out. println ( mtd2. invoke ( new MethodHandleTest ( ) , "Lancibe" ) ) ;
}
}
从上面可以看出,程序使用MethodHandles.lookup对象根据类、方法名、方法类型来获取MethodHandle对象。由于此处的方法名只是一个字符串,而该字符串可以来自于变量、配置文件等,这意味着通过MethodHandle可以让Java动态调用某一个方法。
Java9 增加的VarHandle
VarHandle主要用于动态操作数租的元素或对象的成员变量。VarHandle与MethodHandle非常相似,他也需要通过MethodHandles来获取实例,接下来调用VarHandle的方法即可动态操作数租的指定数组的元素或指定对象的成员变量。
import java. lang. invoke. MethodHandles;
import java. lang. invoke. VarHandle;
import java. util. Arrays;
class User
{
String name;
static int MAX_AGE;
}
public class VarHandleTest {
public static void main ( String[ ] args)
throws Throwable
{
String[ ] sa = new String [ ] { "Lancibe" , "love" , "java" } ;
VarHandle avh = MethodHandles. arrayElementVarHandle ( String[ ] . class ) ;
boolean r = avh. compareAndSet ( sa, 2 , "java" , "python" ) ;
System. out. println ( r) ;
System. out. println ( Arrays. toString ( sa) ) ;
System. out. println ( avh. get ( sa, 1 ) ) ;
System. out. println ( avh. getAndSet ( sa, 2 , "C++" ) ) ;
System. out. println ( Arrays. toString ( sa) ) ;
VarHandle vh1 = MethodHandles. lookup ( ) . findVarHandle ( User. class , "name" , String. class ) ;
User user = new User ( ) ;
System. out. println ( vh1. get ( user) ) ;
vh1. set ( user, "孙悟空" ) ;
System. out. println ( user. name) ;
VarHandle vh2 = MethodHandles. lookup ( ) . findStaticVarHandle ( User. class , "MAX_AGE" , int . class ) ;
System. out. println ( vh2. get ( ) ) ;
vh2. set ( 100 ) ;
System. out. println ( User. MAX_AGE) ;
}
}