Java高级编程:Java常用类库
目录标题
四、正则表达式
通过之前一系列的分析可以发现,String是一个非常万能的类型,因为String 不仅仅可以支持有各种字符串的处理操作,也支持有向各个数据类型的转换功能,所以在项目的开发之中,只要是用户输入的信息基本上都用String表示。于是在向其它数据类型转换的时候,为了保证转换的正确性,往往需要对其进行一些复杂的验证处理,那么这种情况下如果只是单纯的依靠 String 类中的方法是非常麻烦的。
认识正则表达式
现在假设有一个字符串要求你判断字符串是否由数字所组成,如果由数字所组成则将其变为数字进行乘法计算
class JavaAPIDemo {
public static void main(String[] args) {
String str = "123";
if (isNumber(str)) {
int num = Integer.parseInt(str);
System.out.println(num * 2);
}
}
public static boolean isNumber(String str) {
char data[] = str.toCharArray();
for (int x = 0; x < data.length; x++) {
if (data[x] > '9' && data[x] < 'e')
return false;
}
return true;
}
}
实际上这种验证的功能是非常简单的,但是这如此简单的功能却需要开发者编写大量的程序逻辑代码,那么如果是更加复杂的验证呢?
那么在这样的情况下,对于验证来讲最好的做法就是利用正则表达式来完成。
范例:使用正则表达式实现同样的效果
class JavaAPIDemo {
public static void main(String[] args) {
String str="123";
if (str.matches("\\d+")){
int num=Integer.parseInt(str);
System.out.println(num*2);
}
}
}
正则表达式最早是从Perl语言里面发展而来的,而后在 JDK1.4以前如果需要使用到正则表达式的相关定义则需要单独引入其它的*.jar文件,但是从JDK 1.4之后,正则已经默认被JDK所支持。并且提供有java.utilregex开发包,同时针对于String进行了一些修改,使其可以有方法直接支持正则处理。
使用正则最大的特点在于方便进行验证处理,以及方便进行复杂字符串的修改处理。
常用正则标记
如果要想进行正则的处理操作,那么就首先需要对常用的正则标记有所掌握,从JDK1.4开始提供有java.util.regexy这个包里面提供有一个 Piattern程序类,在这个程序类里面定义有所有支持的正则标记。
String类对正则的支持
在进行正则表达式大部分处理的情况下都会基于String类来完成,并且在 String类里面提供有如下与正则有关的操作:
下面通过一些具体的范例来对正则的使用进行说明。
范例:实现字符串替换 (删除掉非子母与数字)
public class JavaDemo01 {
public static void main(String[] args) throws Exception {
String str="asda$D%as18s1d5a/sd;22@!)odskdaPP0)";//要判断的数据
String regex="[^a-zA-Z0-9]+";//正则表达式
System.out.println(str.replaceAll(regex,""));
//asdaDas18s1d5asd22odskdaPP0
}
}
范例:按照数字对字符串进行拆分
public class JavaDemo01 {
public static void main(String[] args) throws Exception {
String str="a1258b125c147dd789eee258fff";
String regex="\\d+";
String result[]=str.split(regex);
System.out.println(Arrays.toString(result));
}
}
//[a, b, c, dd, eee, fff]
在正则处理的时候对于拆分与替换的操作相对容易一些,但是比较麻烦的是数据验证部分。
范例:判断一个数据是否为小数,如果是小数则将其变为double类型
public class JavaDemo01 {
public static void main(String[] args) throws Exception {
String str="100.1";
String regex="\\d+(\\.\\d+)?";
System.out.println(str.matches(regex));//true
}
}
范例:现在判断一个字符串是否由日期所组成,如果是由日期所组成则将其转为Date类型
public class JavaDemo01 {
public static void main(String[] args) throws Exception {
String str="1981-20-15";
String regex="\\d{4}-\\d{2}-\\d{2}";
if (str.matches(regex)){
System.out.println(new SimpleDateFormat("yyyy-MM-dd").parse(str));
}
}
}
需要注意的是,正则表达式无法对里面的内容进行判断,只能够对格式进行判断处理。
范例:判断给定的电话号码是否正确?
public class JavaDemo01 {
public static void main(String[] args) throws Exception {
String str="(010)-51283346";
String regex="((\\d{3,4})|(\\(\\d{3,4}\\)-))?\\d{7,8}";
System.out.println(str.matches(regex));//true
}
}
既然已经可以使用正则进行验证了,那么下面就可以利用其来实现一个email地址格式的验证。
范例:验证email格式,
email 的用户名可以由字母、数字、_所组成 (不应该使用“_”开头)
email的域名可以由字母、数字、_、-所组成
域名的后缀必须是:.cn、.com、.net、.com.cn、.gov
public class JavaDemo01 {
public static void main(String[] args) throws Exception {
String str="java_888@163.com";
String regex="[a-zA-Z0-9]\\w+@\\w+\\.(cn|com|com.cn|net|gov)";
System.out.println(str.matches(regex));//true
}
}
java.util.regex包支持
虽然在大部分的情况下都可以利用String类实现正则的操作,但是也有一些情况下需要使用到java.utilregex开发包中提供的正则处理类
在这个包里面一共定义有两个类: Pattern(正则表达式编译)、Matcher (匹配)。
1、Pattern类提供有正则表达式的编译处理支持: public static Pattern compile(String regex);
同时也提供有字符串的拆分操作: public String[]split(CharSequence input); .
2、Matcher类,实现了正则匹配的处理类,这个类的对象实例化依靠Pattern类完成
- Pattern类提供的方法: public Matcher matcher(CharSequence input);
当获取了Matcher类的对象之后就可以利用该类中的方法进行如下操作:
-
正则匹配: public boolean matches():
-
字符串替换: public String replaceAll(String replacement)
范例:字符串匹配
public class JavaDemo01 {
public static void main(String[] args) throws Exception {
String str="JakdWEas51da&S&*ayas*(Ddada21321AD5da2@!!0iAS";
String regex="[^a-zA-Z]+";
Pattern pattern=Pattern.compile(regex);//编译正则表达式
String[] result=pattern.split(str);
System.out.println(Arrays.toString(result));
//[JakdWEas, da, S, ayas, Ddada, AD, da, iAS]
}
}
public class JavaDemo01 {
public static void main(String[] args) throws Exception {
String str="101";
String regex="\\d+";
Pattern pattern=Pattern.compile(regex);//编译正则表达式
Matcher matcher=pattern.matcher(str);
System.out.println(matcher.matches());//true
}
}
如果纯粹的是以拆分、替换、匹配三种操作为例根本用不到java.util.regex开发包,只依靠 String类就都可以实现了。
而 Matcher类里面提供有一种分组的功能,而这种分组的功能是 String不具备的。
public class JavaDemo01 {
public static void main(String[] args) throws Exception {
//要求取出“#{内容}”标记中的所有内容
String str = "INSERT INTO dept(deptno, dname,loc) VALUES (#{deptno} , #{dname} ,#{loc})";
String regex="#\\{\\w+\\}";
Pattern pattern=Pattern.compile(regex);//编译正则表达式
Matcher matcher=pattern.matcher(str);
while (matcher.find()){
System.out.println(matcher.group(0));
System.out.println(matcher.group(0).replaceAll("#|\\{|\\}",""));
}
}
}
/*
#{deptno}
deptno
#{dname}
dname
#{loc}
loc
*/
java.util.regex开发包,如果不是进行一些更为复杂的正则处理是很难使用到的,而 String类所提供的都是基本操作。
五、国际化程序实现
国际化程序实现原理
所谓的国际化的程序指的是同一个程序代码可以根据不同的国家用不同的语言描述,但是程序处理的核心业务是相同的。
现在假设有一款世界都认可的企业管理平台,那么这个企业的老板决定将这个产品推广到全世界各个大型上市公司,这些公司可以来自于:中国、美国、德国,那么在这样的情况下,首先要考虑的问题是什么呢?
通过分析之后可以发现,如果要想实现国际化的程序开发,那么要解决的问题有两个:
-
如何可以定义保存文字的文件信息
-
如何可以根据不同的区域语言的编码读取指定的资源信息
Locale类
通过分析可以发现,如果要想实现国际化,那么首先需要解决的就是不同国家用户的区域和语言编码问题,而在 java.util 里面提供有一个专门描述区域和语言编码的类:Locale。
而后主要可以使用Locale类中的两个构造方法进行实例化
-
构造方法: public Locale(String language);
-
构造方法:public Locale(String language, String country);
此时需要的是国家和语言的代码,而中文的代码:zh_CN、美国英语的代码时:en_US
class LocalTest1 {
public static void main(String[] args) {
Locale locale=new Locale("zh","CN");//手动设置为中文环境
System.out.println(locale);//zh_CN
}
}
对于这些区域和语言的编码最简单的获取方式就是通过IE浏览器。
如果说现在要想自动获得当前的运行环境,那么现在就可以利用Locale类本身默认的方式进行实例化
- 读取本地默认环境: public static Locale getDefault()
class LocalTest2 {
public static void main(String[] args) {
Locale locale=Locale.getDefault();//获取默认环境
System.out.println(locale);//zh_CN
}
}
为了简化开发,Locale也将世界上一些比较著名的国家的代码设置为常量
使用常量的优势在于可以避免一些区域编码信息的繁琐。
class LocalTest3 {
public static void main(String[] args) {
Locale locale=Locale.CHINA;//使用常量
System.out.println(locale);//zh_CN
}
}
ResourceBundle读取资源文件
现在已经准备好了资源文件,那么随后就需要进行资源文件的读取
而读取资源文件主要依靠的是 java.util.ResourceBundle类完成,此类定义如下:
- public abstract class ResourceBundle extends Object
ResourceBundle是一个抽象类,如果说现在要想进行此类对象的实例化可以直接利用该类中提供的一个static方法
- 获取ResourceBundle类对象: public static final ResourceBundle getBundle( String baseName);
- baseName:描述的是资源文件的名称,但是没有后缀( cn.mldn.message.Mlessages);
根据key读取资源内容:
- public final String getString(String key);
范例:使用ResourceBundle类读取内容
public class LocalTest {
public static void main(String[] args) {
//src/message.properties
ResourceBundle resourceBundle=ResourceBundle.getBundle("message");
String val=resourceBundle.getString("info");
System.out.println(val);
//src/com.lut.message.properties
ResourceBundle resourceBundle2=ResourceBundle.getBundle("com.lut.message");
String val2=resourceBundle.getString("info");
System.out.println(val2);
}
}
在进行资源读取的时候数据的key一定要存在,如果不存在则会出现如下异常
java.util.MissingResourceException: Can’t find resource for…
实现国际化程序开发
现在国际化程序的实现前期准备已经全部完成了,也就是说依靠资源文件、Locale、ResoureeBundle类就可以实现国际化的处理操作,那么下面来进行国际化的程序实现(核心关键:读取资源信息)。
1、在CLASSPATH下建立:cn.mldn.message.Messages_zh_CN.properties(中文资源)
info=欢迎您的访问!
2、在 CLASSPATH下建立: cn.mldn.message.Messages_en_US.properties(英文资源)
info=Welcome!
现在加上没有默认的区域的资源文件,一共定义了三个资源
cn.mldn.message
Messages_en_uS.properties、Messages_zh_CN.properties、Messages.properties
3、通过程序进行指定区域的资源信息加载
//加载本地资源
public class LocalTest {
public static void main(String[] args) {
ResourceBundle resourceBundle=ResourceBundle.getBundle("cn.mldn.message.Messages");
String val=resourceBundle.getString("info");
System.out.println(val);
}
}
此时在利用ResourceBundle类读取资源的时候并没有设置一个明确的Locale对象,但是发现“Messages_ zh_ CN”文件被加载了,因为这个方法里面默认加载的就是当前本地的Locale的资源:
如果现在有指定区域的资源文件存在的时候,那么没有设置区域的资源文件的信息将不会被读取。
public class LocalTest {
public static void main(String[] args) throws UnsupportedEncodingException {
ResourceBundle resourceBundle=ResourceBundle.getBundle("cn.mldn.message.Messages");
//String val=resourceBundle.getString("info");
//转码:解决中文乱码问题
String valueCN = new String(resourceBundle.getString("info").getBytes("ISO-8859-1"), "UTF8");
System.out.println(valueCN);
}
}
资源读取顺序:读取指定区域的资源文件>默认的本地资源>公共的资源
格式化文本显示
如果说现在某一位用户登录成功了,那么一般都会显示这样的信息,“xxx,欢迎您的光临!",也就是说这个时候会显示用户名,那么此时如果这些内容保存在了资源文件里面,则就需要通过占位符来进行描述,同时对于读取出来的数据也需要进行消息格式化的处理。
修改资源立件
【中文资源文件】cn.mldn.message.Messages_zh CN.properties.
info=欢迎{0}的访问,当前日期是{1}!
【英文资源文件】cn.mldn.message.Messages_en_US.properties
info=welcome {0} , date:{1} !
如果有需要则可以继续添加“{1}”、“{2}”之类的内容
此时如果要进行资源读取则会将占位符的信息一起读取出来,所以此时就需要利用MessageFormat 类进行格式化处理。
在 MessageFormat类中提供有一个格式化文本的方法: public static String MessageFormat.format()
范例:格式化文本实现国际化
public class LocalTest {
public static void main(String[] args) throws UnsupportedEncodingException {
Locale locale=new Locale("en","US");
ResourceBundle resourceBundle=ResourceBundle.getBundle("cn.mldn.message.Messages",locale);
String val=resourceBundle.getString("info");
String value2= MessageFormat.format(val,"liming",new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
System.out.println(val);
System.out.println(value2);
//welcome{0},date:{1}
//welcomeliming,date:2021-03-21
}
}
如果在日后开发的过程之中见到资源文件里面出现有“{0}",“{1}”的结构表示一定是占位符,并且需要进行格式化处理
六、开发支持类库
UUID类
UUID是一种生成无重复字符串的一种程序类,这种程序类的主要功能是根据时间戳实现一个自动的无重复的字符串定义。
一般在获取UUID的时候往往都是随机生成一的个内容,所以可以通过如下方式获取:
-
获取UUID对象: public static UUID randomUUID();
-
根据字符串获取UUID内容: public static UUID fromString(String name);
public class UUIDTest {
public static void main(String[] args) {
System.out.println(UUID.randomUUID());
UUID uuid=UUID.randomUUID();
System.out.println(uuid.toString());
}
}
在对一些文件进行自动命名处理的情况下,UUID类型非常好用。
Optional类
Optional类的主要功能是进行null i的相关处理,以前进行程序开发的时候,如果为了防止程序之中出现空指向异常,往往会追加有null的验证。
范例:传统的引用传递问题
public class JavaAPI {
public static void main(String[] args) {
MessageUtil.useMessage(MessageUtil.getMessage());
}
}
class MessageUtil {
private MessageUtil(){}
public static IMessage getMessage(){
return null;
}
public static void useMessage(IMessage message){
if (message!=null){
System.out.println(message.getContent());//有可能出现空
}
}
}
interface IMessage{
public String getContent();
}
class MessageImpl implements IMessage{
@Override
public String getContent() {
return "hello";
}
}
在引用接收的一方往往都是被动的进行判断,所以为了解决这种被动的处理操作,在Java类中提供有一个Optional 的一个类可以实现null的处理操作,在这个类里面提供有如下的一些操作方法:
-
返回空数据: public static <T> Optional<T> empty()
-
获取数据: public T get()
-
保存数据,但是不允许出现null: public static <T> OptionalT> of(T value)
- 如果在保存数据的时候存在有null,则会抛出NullPointerException异常
-
保存数据,允许为空: public static <T> Optional<T> ofNullable(T value)
-
空的时候返回其它数据: public T orElse(T other)
范例:修改程序,按照正规的结构完成.
package cn.mldn.demo;
import sun.plugin2.message.Message;
import java.util.Optional;
public class JavaAPI {
public static void main(String[] args) {
IMessage temp=MessageUtil.getMessage().get();
MessageUtil.useMessage(temp);
}
}
class MessageUtil {
private MessageUtil(){}
public static Optional<IMessage> getMessage(){
return Optional.of(new MessageImpl());//有对象
}
public static void useMessage(IMessage message){
if (message!=null){
System.out.println(message.getContent());//有可能出现空
}
}
}
interface IMessage{
public String getContent();
}
class MessageImpl implements IMessage{
@Override
public String getContent() {
return "hello";
}
}
由于Optional类中允许保存有null 的内容,所以在数据获取的时候也可以进行null 的处理。
但是如果为 null,则在使用get()获取数据的时候就会出现
“Exception in thread "main”java.util.NoSuchElementException: No value present” 异常信息,
所以此时可以更换为 orElse()方法
范例:处理null
package cn.mldn.demo;
import sun.plugin2.message.Message;
import java.util.Optional;
public class JavaAPI {
public static void main(String[] args) throws Exception{
IMessage temp=MessageUtil.getMessage().orElse(new MessageImpl());//获取数据
// IMessage temp=MessageUtil.getMessage().orElse(null);//获取数据
MessageUtil.useMessage(temp);
}
}
class MessageUtil {
private MessageUtil(){}
public static Optional<IMessage> getMessage(){
// return Optional.of(new MessageImpl());//有对象
return Optional.ofNullable(null);//没有对象
}
public static void useMessage(IMessage message){
if (message!=null){
System.out.println(message.getContent());//有可能出现空
}
}
}
interface IMessage{
public String getContent();
}
class MessageImpl implements IMessage{
@Override
public String getContent() {
return "hello";
}
}
在所有引用数据类型的操作处理之中,null 是一个重要的技术问题,所以 JDK 1.8后提供的这个新的类对于null 的处理很有助,同时也是在日后进行项目开发之中使用次数很多的一个程序类。
ThreadLocal类
资源引用传递多线程安全问题的解决方案
在真正去了解ThreadLocal类作用的时候下面编写一个简单的程序做一个前期的分析范例:现在定义这样的一个结构:
public class JavaAPI {
public static void main(String[] args) {
Message message=new Message();
message.setInfo("hello");
Channel.setMessage(message);
Channel.send();
}
}
class Message{
private String info;
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
class Channel{
private static Message message;
private Channel(){};
public static void setMessage(Message m) {
message = m;
}
public static void send(){
System.out.println("发送消息:"+message.getInfo());
}
}
对于当前的程序实际上采用的是一种单线程的模式来进行处理的。
那么如果在多线程的状态下能否实现完全一致的操作效果呢?为此将启动三个线程进行处理。
范例:多线程的影响
public class JavaAPI {
public static void main(String[] args) {
new Thread(()->{
Message message=new Message();//实例化消息主体对象
message.setInfo("第一个线程消息");//设置要发送的内容
Channel.setMessage(message);//设置要发送的消息
Channel.send();//发送消息
},"消息发送者A").start();
new Thread(()->{
Message message=new Message();
message.setInfo("第二个线程消息");
Channel.setMessage(message);
Channel.send();
},"消息发送者B").start();
new Thread(()->{
Message message=new Message();
message.setInfo("第三个线程消息");
Channel.setMessage(message);
Channel.send();
},"消息发送者C").start();
}
}
class Message{
private String info;
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
class Channel{
private static Message message;
private Channel(){};
public static void setMessage(Message m) {
message = m;
}
public static void send(){
System.out.println("发送消息:"+Thread.currentThread().getName()+message.getInfo());
}
}
在保持Channel(所有发送的通道)核心结构不改变的情况下,需要考虑到每个线程的独立操作问题。那么在这样的情况下就发现对于Channel类而言除了要保留有发送的消息之外,还应该多存放有一个每一个线程的标记(当前线程),那么这个时候就可以通过以通过ThreadLocal类来存放数据。
在 ThreadLocal类里面提供有如下的操作方法: .
-
构造方法: public ThreadLocal();
-
设置数据: public void set(T value);
-
取出数据: public T get();
-
删除数据: public void remove():
范例:解决线程同步问题
public class JavaAPI {
public static void main(String[] args) {
new Thread(()->{
Message message=new Message();//实例化消息主体对象
message.setInfo("第一个线程消息");//设置要发送的内容
Channel.setMessage(message);//设置要发送的消息
Channel.send();//发送消息
},"消息发送者A").start();
new Thread(()->{
Message message=new Message();
message.setInfo("第二个线程消息");
Channel.setMessage(message);
Channel.send();
},"消息发送者B").start();
new Thread(()->{
Message message=new Message();
message.setInfo("第三个线程消息");
Channel.setMessage(message);
Channel.send();
},"消息发送者C").start();
}
}
class Channel{
private static final ThreadLocal<Message> THREADLOCAL=new ThreadLocal<Message>();
private Channel(){};
public static void setMessage(Message m) {
THREADLOCAL.set(m);
}
public static void send(){
System.out.println("发送消息:"+Thread.currentThread().getName()+THREADLOCAL.get().getInfo());
}
}
class Message{
private String info;
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
每一个线程通过ThreadLocal只允许保存一个数据。
定时调度
定时器的主要操作是进行定时任务的处理,就好比你们每天早晨起来的铃声一样。在Java 中提供有定时任务的支持,但是这种任务的处理只是实现了一种间隔触发的操作。【如果想要实现在每一年的某个月的某个时候执行某个操作将会比较麻烦】
如果要想实现定时的处理操作主要需要有一个定时操作的主体类,以及一个定时操作的主体类,以及一个定时任务的控制。可以使用两个类实现:
- java.util.TimerTask类:实现定时任务处理
- java.util.Timer类:进行任务的启动,启动的方法
- 任务启动: public void schedule(TimerTask task, long delay)、延迟单位为毫秒
范例:实现定时任务处理
class MyTask extends TimerTask { //任务主体
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"定时执行任务,当前时间"+System.currentTimeMillis());
}
}
public class Demo01 {
public static void main(String[] args) {
Timer timer=new Timer();//定时任务
//定义间隔任务,100ms开始执行,每秒执行一次
timer.scheduleAtFixedRate(new MyTask(),100,1000);
}
}
这种定时是由 JDK最原始的方式提供的支持,但是实际上开发之中利用此类方式进行的定时处理实现的代码会非常的复杂
在后面的开发中会接触一些定时调度组件,将有利于简化开发
Base64加密与解密
正常来讲加密基本上永远都要伴随着解密,所谓的加密或者是解密往往都需要有一些所谓的规则。在 JDK1.8开始新的加密处理操作类,Base64处理,在这个类里面有两个内部类:
-
Base64.Encoder:进行加密处理
- 加密处理: public byte[]encode(byte[] src)
-
Base64.Decoder:进行解密处理
- 解密处理: public byte[]decode(String src)
范例:实现加密与解密操作
public class Base64Test {
public static void main(String[] args) {
String msg="1234567890";
String enMsg=new String(Base64.getEncoder().encode(msg.getBytes()));
System.out.println(enMsg);
String oldMsg=new String(Base64.getDecoder().decode(enMsg));
System.out.println(oldMsg);
}
}
虽然Base64可以实现加密与解密的处理,但是其由于是一个公版的算法,所以如果直接对数据进行加密往往并不安全,那么最好的做法是使用盐值操作
public class Base64Test {
public static void main(String[] args) {
String salt="javaSALT";
String msg="1234567890"+"{"+salt+"}";
String enMsg=new String(Base64.getEncoder().encode(msg.getBytes()));
System.out.println(enMsg);
String oldMsg=new String(Base64.getDecoder().decode(enMsg));
System.out.println(oldMsg);
}
}
即便现在有盐值实际上发现加密的效果也不是很好,最好的做法是多次加密。
package cn.mldn.demo;
import java.util.Base64;
public class Base64Test {
private static final String SALT="javaSALT";
private static final Integer REPEAT=5;//加密次数
//加密处理
public static String encode(String string){
String msg=string+"{"+SALT+"}";
byte data[]=msg.getBytes();//转为字节数组
for (int i = 0; i <REPEAT ; i++) {
data=Base64.getEncoder().encode(data);
}
return new String(data);
}
//解密处理
public static String decode(String string){
byte data[]=string.getBytes();
for (int i = 0; i <REPEAT ; i++) {
data=Base64.getDecoder().decode(data);
}
return new String(data).replaceAll("\\{\\w+\\}","");
}
public static void main(String[] args) {
String password="1234567890";
String str1=encode(password);
System.out.println(str1);
String str2=decode(str1);
System.out.println(str2);
}
}
最好的做法是使用2-3种加密程序,同时再找到一些完全不可解密的加密算法。