Java学习笔记
第七章——Java基础类库
与用户互动
运行Java程序的参数
- mian函数的方法签名如下:
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.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----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信息
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对象。
...
- 大量细节应查找Java API文档。w3school Java API文档
Math类
- Java通过该类来完成复杂的数学运算
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的子串替换成replacement
String replaceFirst(String regex, String replacement);//将该字符串中第一个匹配regex的子串替换为replacement
String[] 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, java
lancibehello, 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
子串结束位置: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类方法
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);
}
}