admin管理员组

文章数量:1579086

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修饰,因此该方法只能被子类重写或调用。
  • 自定义类实现“克隆”的步骤如下:
  1. 自定义类实现Cloneable接口。这是一个标志性的接口,实现该接口的对象可以实现“自我克隆”,接口里没有定义任何方法。
  2. 自定义类实现自己的clone()方法
  3. 实现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)符
\eEscape符
\cxx对应的控制符,例如\cM代表Ctrl-M。x的值必须为A~Z或a~z之一
  • 除此之外,正则表达式中还有一些特殊的字符,这些特殊字符在正则表达式中有其特殊的用途,如果需要匹配这些字符,则必须首先将这些字符转义,也就是加上反斜线。
特殊字符说明
$匹配一行的结尾
^匹配一行的开头
()标记子表达式的开始和结束位置
[]用于确定中括号表达式的开始和结束位置
{}用于标记前面子表达式的出现频度
*指定前面子表达式可以出现零次或多次
+指定前面子表达式可以出现一次或多次
?指定前面子表达式可以出现零次或一次
.匹配除换行符\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],表示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对象
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",
                        "lancibe0326@gmail",
                        "1609547089@qq",
                        "lancibe@xiyoulinux",
                        "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);
    }
}

本文标签: 第七章类库基础Java