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 Lancibe1Lancibe
使用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.txt文件中 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) { //下面程序中s1和s2是两个不同的对象 String s1 = new String("Hello"); String s2 = new String("Hello"); //String重写了hashCode()方法——改为根据字符序列来计算hashCode值 //因为s1和s2的字符序列相同,所以他们的identityHashCode值不同 System.out.println(s1.hashCode() "----" System.identityHashCode(s2)); String s3 = "lancibe"; String s4 = "lancibe"; //s3和s4是相同的字符串对象,所以他们的identityHashCode值相同 System.out.println(System.identityHashCode(s3) "----" System.identityHashCode(s4)); }}
lancibe@lancibe-TM1701:~/java/test/src/chapter7$ java IdentityHashCodeTest 69609650----225534817664740647----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信息 ProcessHandle.Info info = ph.info(); //通过ProcessHandle.Info信息获取进程相关信息 System.out.println("进程命令:" info.command()); System.out.println("进程参数:" info.arguments()); System.out.println("进程启动时间:" info.startInstant()); System.out.println("进程累计运行时间:" info.totalCpuDuration()); //通过CompletableFuture在进程结束时运行某个任务 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();//返回该对象的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("西长安街"); } //通过super.clone()来实现clone()方法 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)); //obj不能为null,如果obj为null则引发异常 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)); //计算反余弦,返回的角度范围在0.0到pi之间 System.out.println("Math.acos(1.2):" Math.acos(1.2)); //计算双曲正弦 System.out.println("Math.sinh(1.2):" Math.sinh(1.2)); //将矩形坐标(x,y)转换为极坐标(r, thet) 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)); //下面是乘方开方指数运算 //欧拉数的e的n次幂 System.out.println("Math.exp(2):" Math.exp(2)); }}
正则表达式
- 正则表达式是一个强大的字符串处理工具,可以对字符串进行查找、提取、分割、替换等操作。String类里也提供了如下几个特殊的方法:
boolean matches(String regex);//判断字符串是否匹配指定的正则表达式String replaceAll(String regex, String replacement);//将该字符串中所有匹配regex的子串替换成replacementString replaceFirst(String regex, String replacement);//将该字符串中第一个匹配regex的子串替换为replacementString[] split(String regex);//以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的值必须为AZ或az之一 |
- 除此之外,正则表达式中还有一些特殊的字符,这些特殊字符在正则表达式中有其特殊的用途,如果需要匹配这些字符,则必须首先将这些字符转义,也就是加上反斜线。
特殊字符 |
说明 |
$ |
匹配一行的结尾 |
^ |
匹配一行的开头 |
() |
标记子表达式的开始和结束位置 |
[] |
用于确定中括号表达式的开始和结束位置 |
{} |
用于标记前面子表达式的出现频度 |
* |
指定前面子表达式可以出现零次或多次 |
|
指定前面子表达式可以出现一次或多次 |
? |
指定前面子表达式可以出现零次或一次 |
. |
匹配除换行符\n以外的任何单字符 |
\ |
用于转义下一个字符,或指定八进制、十六进制字符 |
| |
制定两项之间任选一项 |
- 注意如果需要匹配特殊字符本身,应使用
\\m
的形式。
- 下面举例说明具体使用方法
"\u0041\\\\" //匹配A"\u0061\t" //匹配a<制表符>"\\?\\[" //匹配?[
- 前面的正则表达式依然只能匹配单个字符,这因为还未在正则表达式中使用“通配符”,他是可以匹配多个字符串的特殊字符。正则表达式中的“通配符”远远超出了普通通配符的功能,他被称为预定义字符
预定义字符 |
说明 |
. |
可以匹配任何字符 |
\d |
匹配0~9之间的所有数字 |
\D |
匹配非数字 |
\s |
匹配所有的空白字符,包括空格、制表符、回车符、换行符、换页符等 |
\S |
匹配所有的非空白字符 |
\w |
匹配所有的单词字符,包括0~9所有数字、26个英文字母和下划线_ |
\W |
匹配所有的非单词字符 |
"c\\wt" //可以匹配cat、cbt、c9t等一批字符串"\\d\\d\\d-\\d\\d\\d-\\d\\d\\d\\d" //匹配一个型如000-000-0000形式的电话号码
- 在一些特殊的情况下,例如只想匹配a~f之间的字母,或者匹配除ab以外的所有小写字母,或者匹配中文字符,上面的预定义字符就无能为力了,此时就需要方括号表达式。
方括号表达式 |
说明 |
表示枚举 |
例如[abc],表示a、b、c其中任意一个字符 |
表示范围:- |
例如[a-f],表示af之间的任意字符,[\\u0041-\\u0056]表示十六进制的这两个字符之间的字符,[a-cx-z]表示ac或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, javalancibehello, java
- 当从"hello, java"字符串中查找匹配"\w*"子串时,第一行使用了贪婪模式,所以直到逗号才停止,第二行使用了勉强模式,故匹配了0个字符。
使用正则表达式
- 一旦在程序中定义了正则表达式,就可以通过Pattern和Matcher来使用正则表达式。
- Pattern对象是正则表达式编译后在内存中的表示形式,因此,正则表达式字符串必须先被编译为Pattern对象,然后再利用该Pattern对象创建对应的Matcher对象,执行匹配所涉及的状态保留在Matcher对象中,多个Matcher对象可以共享同一个Pattern对象。
- 因此,典型的调用顺序如下:
//将一个字符串编译成Pattern对象Pattern p = Pattern.compile("a*b");//使用Pattern对象创建Matcher对象Matcher m = p.matcher("aaaaab");boolean b = m.matches(); //返回true
- 上面定义的Pattern对象可以多次重复使用。如果一个正则表达式仅需要一次使用,这可以直接使用Pattern类的静态matches()方法,此方法自动把指定字符串编译成匿名的Pattern对象,并执行匹配:
boolean p = Pattern.matches("a*b", "aaaaab");//返回true
- 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子串结束位置:4is子串起始位置:5子串结束位置:7not子串起始位置:8子串结束位置:11very子串起始位置:12子串结束位置:16easy子串起始位置: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类方法 private static void hello() { System.out.println("Hello world!"); } //定义一个private实例方法 private String hello(String name) { System.out.println("执行带参数的hello" name); return name ",你好"; } public static void main(String[] args) throws Throwable { //定义一个返回值为void、不带形参的方法类型 MethodType type = MethodType.methodType(void.class); //使用MethodHandles.Lookup的findStatic获取类方法 MethodHandle mtd = MethodHandles.lookup().findStatic(MethodHandleTest.class, "hello", type); //通过MethodHandle执行方法 mtd.invoke(); //使用MethodHandles.Lookup的findVirtual获取实例方法 MethodHandle mtd2 = MethodHandles.lookup().findVirtual(MethodHandleTest.class, "hello", //指定获取返回值为String、形参为String的方法类型 MethodType.methodType(String.class, String.class)); //通过MethodHandle执行方法,传入主调对象和参数 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"}; //获取一个String[]数组的VarHandle对象 VarHandle avh = MethodHandles.arrayElementVarHandle(String[].class); //比较并设置:如果第三个元素是java,则该元素被设置成python boolean r = avh.compareAndSet(sa, 2, "java", "python"); //输出比较结果 System.out.println(r); //看到第三个元素被替换 System.out.println(Arrays.toString(sa)); //获取sa数组的第二个元素 System.out.println(avh.get(sa,1)); //获取并设置:返回第三个元素,并将第三个元素设置为C System.out.println(avh.getAndSet(sa, 2, "C ")); //看到第三个元素被替换成C System.out.println(Arrays.toString(sa)); //用findVarHandle方法获取User类中名为name、类型为String的实例变量 VarHandle vh1 = MethodHandles.lookup().findVarHandle(User.class, "name", String.class); User user = new User(); System.out.println(vh1.get(user)); //通过VarHandle设置指定实例变量的值 vh1.set(user, "孙悟空"); //输出user的name实例变量的值 System.out.println(user.name); //用findVarHandle方法获取User类中名为MAX_AGE、类型为Integer的类变量 VarHandle vh2 = MethodHandles.lookup().findStaticVarHandle(User.class,"MAX_AGE", int.class); //通过VarHandle获取指定类变量的值 System.out.println(vh2.get()); //通过VarHandle设置指定类变量的值 vh2.set(100); //输出User的MAX_AGE类变量 System.out.println(User.MAX_AGE); }}
来源:https://www.icode9.com/content-1-769651.html