正则表达式
正则表达式是一个用来描述或者匹配一系列符合某个句法规则的字符串的表达式,也就是符合一定规则的表达式,操作字符串有很多方法,但是这些方法操作起来太简单,组合起来操作复杂数据,代码过多,而对字符串进行操作既便捷又简单的方式就是正则表达式。
作用:用于操作字符串。
特点:用特定符号表示一些代码操作,这样可以简化书写。
好处:可以简化对字符串的复杂操作。
弊端:正则越长,阅读性越差。
正则表达式的构造摘要
字符类
[abc] a、b或c(简单类)
[^abc] 任何字符,除了a、b或c(否定)
[a-zA-Z] a 到z或A到Z,两头的字母包括在内(范围)
[a-d[m-p]] a到d或m到p:[a-dm-p](并集)
[a-z&&[def]] d、e或f(交集)
[a-z&&[^bc]] a到z,除了b和c:[ad-z](减去)
[a-z&&[^m-p]] a到z,而非m到p:[a-lq-z](减去)
预定义字符类
. 任何字符(与行结束符可能匹配也可能不匹配)
\d 数字:[0-9]
\D 非数字: [^0-9]
\s 空白字符:[ \t\n\x0B\f\r]
\S 非空白字符:[^\s]
\w 单词字符:[a-zA-Z_0-9]
\W 非单词字符:[^\w]
边界匹配器
^ 行的开头
$ 行的结尾
\b 单词边界
\B 非单词边界
\A 输入的开头
\G 上一个匹配的结尾
\Z 输入的结尾,仅用于最后的结束符(如果有的话)
\z 输入的结尾
Greedy 数量词
X? X,一次或一次也没有
X* X,零次或多次
X+ X,一次或多次
X{n} X,恰好n次
X{n,} X,至少n次
X{n,m} X,至少n次,但是不超过m次
组:当你想要对一个规则的结果进行重用时,可以将其封装成组,这个组里的结果就可以被再次使用,用()来完成,组封装完以后会有一个自动的编号,从1开始,想要使用已有的组可以通过\n(n就是组的编号)的形式来获取,如"(.)//1",这就表示后一位的结果和前一位的结果一样,如果这个位置的数出现多次,可表示为"(.)//1+",租和matches不同的是matches匹配到不符合规则的就不再匹配,而组会继续往下判断,像这样((())())出现多个组时,我们要判断有几个组,就要看有几个左括号,有几个就有几个组,判断到第几个左括号,它就是第几个组。
其实学习正则表达式就是学习一些特殊符号。
具体操作
匹配
matches(regex):返回的是一个boolean型的值,用规则匹配整个字符串,只要有一处匹配不符合规则,就不再向下匹配。
要注意的是,在正则表达式中,反斜杠“\”都需要成对出现的“\\”,因为在字符串中“\”是转义字符。
练习:对QQ号码进行校验,5~15位,0不能开头,只能是数字。
class RegexDemo
{
public static void main(String[] args)
{
String qq = "045364768";
checkQQ(qq);
}
public static void checkQQ(String qq)
{
String regex = "[1-9]\\d{4,14}";//定义匹配规则;
boolean b = qq.matches(regex);
if(b)
System.out.println("qq格式正确");
else
System.out.println("qq格式错误");
}
}
运行结果:
练习:匹配手机号段只有:13xxx、15xxx、18xxxx。
class RegexDemo
{
public static void main(String[] args)
{
String num = "13253464768";
checkQQ(num);
}
public static void checkQQ(String tel)
{
String regex = "1[358]\\d{9}";
boolean b = tel.matches(regex);
if(b)
System.out.println("号码格式正确");
else
System.out.println("号码格式错误");
}
}
运行结果:
切割
split(String regex):返回的是一个字符串数组。
/*
需求:按照叠词对一个字符串进行切割。
*/
class RegexDemo
{
public static void main(String[] args)
{
String str = "adaawsfffafgwdfbbbbasdd";
String regex = "(.)\\1+";
splitDemo(str,regex);
}
public static void splitDemo(String str,String regex)
{
String[] arr = str.split(regex);//对字符串按照指定规则进行切割;
for(String s : arr)
{
System.out.println(s);
}
}
}
运行结果:
注意:在正则表达式中不能直接用“.”去对字符串进行切割,以为“.”是正则表达式中的一个特殊符号,代表的是任意字符,应该写成“\\.”,同理获取盘符应使用“\\\\”;对于叠词我们可以使用组,并对组进行捕获,用\n的形式表示。
替换
replaceAll(String regex,String replacement):返回的是字符串。
特殊部分
如:将叠词替换成“&”符号.
将重叠的字母替换成单个的该字母:在替换的时候我们可以用一个特殊符号来代表这个组“$1”,意思就是拿前一个规则的第一个组的那个字母("(.)\\1+","$1")。
/*
需求:按照叠词对一个字符串进行切割。
*/
class RegexDemo
{
public static void main(String[] args)
{
String str = "adaa142424wsfffaf4243gwdfb234234234234bbba";
String regex = "\\d{5,}";
String newstr = "*";
String str1 = "sdfggdfdfgdddfdffff";
String regex1 = "(.)\\1+";
removeAllDemo(str,regex,newstr);
removeAllDemo(str1,regex1,"$1");
}
public static void removeAllDemo(String str,String regex,String newstr)
{
String s = str.replaceAll(regex,newstr);
System.out.println(s);
}
}
运行结果:
获取
获取就是按照指定规则取出字符串子串。
匹配返回的是真假,替换返回的是替换后的字符串,切割是把规则以外的取出来,而获取是要把符合规则的取出来。
步骤:1,将正则表达式封装成对象。
2,让正则对象和要操作的字符串相关联。
3,关联后,获取正则匹配引擎。
4,通过引擎对符合规则的子串进行操作,比如取出。
Pattern类:正则表达式的编译表示形式,它可以描述正则表达式,对正则表达式进行封装,它在java.util.regex包中。
compile(regex):该方法是静态的,返回一个Pattern对象,把正则表达式传递给这个方法,它就会把该正则表达式封装成一个Pattern对象返回。
matcher(CharSequence input ):让正则对象和要操作的字符串相关联,返回的是Matcher匹配器。
Matcher类
matches():其实String类中的matches方法就是用的Pattern和Matcher这两个类的对象来完成的,只不过被String的方法封装后,用起来较为简单,但是功能却单一。
find():查找与该模式匹配的输入序列的下一个子序列,就是将规则作用到字符串上,并进行符合规则的子串查找,返回的是一个boolean型的值;
group():返回由以前匹配操作所匹配的输入子序列,就是用于获取匹配后的结果。
这两个方法要结合使用,功能类似迭代器的hasNext方法和next方法,判断有没有下一个,有才取。
end():返回最后匹配字符之后的偏移量;
start():返回以前匹配的初始索引;
边界匹配器:\b—单词边界。
演示:
import java.util.regex.*;;
class RegexDemo
{
public static void main(String[] args)
{
getDemo();
}
public static void getDemo()
{
String str = "ming tian jiu yao fang jia le ,da jia。";
System.out.println(str);
String reg = "\\b[a-z]{4}\\b";
//将规则封装成对象。
Pattern p = Pattern.compile(reg);
//让正则对象和要作用的字符串相关联。获取匹配器对象。
Matcher m = p.matcher(str);
while(m.find())//将规则作用到字符串上,并进行符合规则的子串查找;
{
System.out.println(m.group());//用于获取匹配后结果;
System.out.println(m.start()+"...."+m.end());
}
}
}
运行结果:
在我们使用正则表达式时应该怎么选择呢?
思路方式:
1,如果只想知道该字符是否对是错,使用匹配。
2,想要将已有的字符串变成另一个字符串,替换。
3,想要按照自定的方式将字符串变成多个字符串。切割。获取规则以外的子串。
4,想要拿到符合需求的字符串子串,获取。获取符合规则的子串。
练习1:将字符串转换成"我要学编程"。
/*
需求:
将下列字符串转成:我要学编程。
"我我...我我...我要..要要...要要...学学学....学学...编编编...编程..程.程程...程...程";
思路:将已有字符串变成另一个字符串。使用替换功能。
1,可以先将 . 去掉。
2,在将多个重复的内容变成单个内容。
*/
import java.util.regex.*;;
class RegexDemo
{
public static void main(String[] args)
{
test();
}
public static void test()
{
String str = "我我...我我...我要..要要...要要...学学学....学学...编编编...编程..程.程程...程...程";
str = str.replaceAll("\\.+","");//去掉".";
str = str.replaceAll("(.)\\1+","$1");//将叠词转换成单词;
System.out.println(str);
}
}
运行结果:
练习2:将ip地址进行地址段顺序的排序。
/*
需求:
192.68.1.254 102.49.23.013 10.10.10.10 2.2.2.2 8.109.90.30
将ip地址进行地址段顺序的排序。
思路:还按照字符串自然顺序,只要让它们每一段都是3位即可。
1,按照每一段需要的最多的0进行补齐,那么每一段就会至少保证有3位。
2,将每一段只保留3位。这样,所有的ip地址都是每一段3位。
*/
import java.util.*;
import java.util.regex.*;;
class RegexDemo
{
public static void main(String[] args)
{
ipSort();
}
public static void ipSort()
{
String ip = "192.68.1.254 102.49.23.013 10.10.10.10 2.2.2.2 8.109.90.30";
ip = ip.replaceAll("(\\d+)","00$1");//在每一段地址前补两个0,保证每一段至少3位;
ip = ip.replaceAll("0*(\\d{3})","$1");//每段只保留3位,将多余的0去掉;
String[] arr = ip.split(" ");//用空格对地址串进行切割;
TreeSet<String> ts = new TreeSet<String>();
for(String s : arr)
{
ts.add(s);//将切割后的子串作为元素存到TreeSet集合中,进行排序;
}
for(String s : ts)
{
System.out.println(s.replaceAll("0*(\\d+)","$1"));//把每个地址段前的0去掉;
}
}
}
运行结果:
练习3:对邮件地址进行校验
/*
需求:
对邮件地址进行校验。
*/
import java.util.regex.*;;
class RegexDemo
{
public static void main(String[] args)
{
checkMail();
}
public static void checkMail()
{
String mail = "abc12@sina.com";
String reg = "[a-zA-Z0-9_]+@[a-zA-Z0-9]+(\\.[a-zA-Z]+)+";//较为精确的匹配。
boolean flag = mail.matches(reg);
if(flag)
System.out.println("地址输入正确");
else
System.out.println("地址输入错误,请重输");
}
}
运行结果:
网页爬虫
/*
网页爬虫(蜘蛛)
*/
import java.io.*;
import java.util.regex.*;
import java.net.*;
import java.util.*;
class RegexDemo
{
public static void main(String[] args) throws Exception
{
getMails_1();
}
/*
获取指定网页中的邮件地址。
使用获取功能。Pattern Matcher
*/
public static void getMails_1()throws Exception
{
URL url = new URL("http://www.doyo.cn/game/luntan/viewthread/229522");//将地址封装成对象;
URLConnection conn = url.openConnection();//和服务器建立连接;
BufferedReader bufIn = new BufferedReader(new InputStreamReader(conn.getInputStream()));//获取网络读取流;
String line = null;
String mailreg = "\\w+@\\w+(\\.\\w+)+";//定义规则;
Pattern p = Pattern.compile(mailreg);//将规则进行封装;
while((line=bufIn.readLine())!=null)
{
Matcher m = p.matcher(line);//和读到的每一行字符串进行关联;
while(m.find())//查找匹配规则的邮箱;
{
System.out.println(m.group());//获取匹配的邮箱;
}
}
}
/*
获取指定文档中的邮件地址。
使用获取功能。Pattern Matcher
*/
public static void getMails_2()throws Exception
{
BufferedReader bufr =
new BufferedReader(new FileReader("mail.txt"));//读取文件
String line = null;
String mailreg = "\\w+@\\w+(\\.\\w+)+";//定义规则;
Pattern p = Pattern.compile(mailreg);//将规则进行封装;
while((line=bufr.readLine())!=null)
{
Matcher m = p.matcher(line);//和读到的每一行字符串进行关联;
while(m.find())//查找匹配规则的邮箱;
{
System.out.println(m.group());//获取匹配的邮箱;
}
}
}
}
运行结果:
反射技术
概述
java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,这个属性值是什么,则是由这个类的实例对象来确定的,不同的实例有不同的值,java程序中各个java类,它们是否也属于同一事物?是否可以用一个类来描述这类事物?答案是肯定的,这个类的名字就是:Class,我们可以通过这个类动态的获取其他类中的信息,包括属性和方法等,其实,反射就是把java中的各种成分映射成相应的java类,相当于把一个类进行拆解,然后将拆解后的每个部分拿出来,单独封装。
那么Class类中包含了哪些信息呢?
Class类中包含类的名字,类的访问属性,类属于哪一个包,字段名称列表,方法名称列表等,学习反射,首先就要明白Class这个类。
Class这个类对应的是字节码文件对象,每一份字节码都是一个Class实例对象,获取字节码文件对象的方式有三种。
1,类名.class,如:System.Class。
2,对象.getClass(),如:new Data().getClass();
3,Class.forName(),这是个静态方法,如:Class.forName("java.lang.String"),这里要将完整的类名作为参数传递进去。
forName这个方法得到类的字节码有两种情况,一种是这个类的字节码已经加载到内存中,这是就不需要在加载,直接获取,另一种就是在获取这份字节码时,还没有加载到内存中,这时需要把类加载进来,然后把加载进来字节码在内存中缓存起来,然后通过这个方法返回。
在java中基本数据类型也有对应的Class对象,void也有,如果我们要表示基本数据类型的字节码文件对象,可以用int.class也可以用Integer.TYPE。
总之,只要是在源程序中出现的类型,都有各自的Class实例对象。
Class类为我们提供了很多方法来获取相应的类中变量,构造方法,方法等,下面我们来看这些成分对应的类在反射中是怎么使用的。
Constructor--构造方法的反射应用
getConstructors():这个方法返回的这个类中的所有构造方法;
getConstructor(Class<?>... parameterTypes):返回某一个构造方法。
如:Constructor constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
Constructor对应的是构造方法这个类,而它的对象对应的是某一个构造方法,上面的constructor对应的就是字符串的String(StringBuffer buffer)这个构造函数。
现在我们得到了String类的构造函数,那么怎么通过反射的方式用这个构造函数去创建实例对象呢?
通常方式:String str = new String(new StringBuffer("abc"));
在Constructor类中,有专门获取实例的方法,这个方法就是newInstance
反射方式:String str = (String)constructor.newInstance(new StringBuffer("adc"));注意这里是需要进行强制类型转换的,虚拟机只知道我们在使用newInstance获取构造方法,但是不知道是获取那个对象的构造方法,编译时,只对语法进行检查,并不去执行语句,没执行就不知道,也就是说编译器只看变量的定义,不看代码的执行。
注意:获取方法时,要用到类型,调用获得的方法时,要用到与之相同的类型的对象。
通过反射创建实例对象要经过Class对象获取constructor,再由constructor获取实例对象,在Class类中有一个newInstance方法,这个方法先得到默认的构造方法,然后用该构造方法创建实例对象。
演示:
import java.lang.reflect.*;
class ReflectDemo
{
public static void main(String[] args)throws Exception
{
Class clazz = Class.forName("java.lang.String");//获取String类的Class对象;
Constructor constructor = clazz.getConstructor(String.class);//获取String类的传入String参数的构造函数;
String str = (String)constructor.newInstance("asfsdfsa");//创建String类对象;
System.out.println(str.indexOf('s'));
}
}
Filed类--成员变量的反射
对于一个类中的成员变量,在反射中,我们用Filed类来描述。
方法
getField():获取变量所属的类,返回的是一个Class对象,参数(String name)。
演示:
import java.lang.reflect.*;
class Demo
{
private int x;
public int y;
Demo(int x,int y)
{
this.x = x;
this.y = y;
}
}
class ReflectDemo
{
public static void main(String[] args)throws Exception
{
Demo d = new Demo(4,6);
Field fieldY = d.getClass().getField("y");
/*
这得到的是Class对象,每个Demo对象身上都有一个fieldY,它在d上是6,在其它对象上可
能就不是,fieldY不代表一个具体的值,只代表一个变量,不代表某个对象具体的值,
fieldY它不是对象身上的变量,而是类上的,要用它去取某个对象的值,就要用到get(该类的一个对象)
方法;
*/
System.out.println(fieldY.get(d));//这就是get方法,得到d这个对象y的值是6;
Field fieldX = d.getClass().getDeclaredField("x");//get方法只能得到可见的变量,不可见的要用getDeclaredField这个方法;
fieldX.setAccessible(true);//此个方法可以将这个变量设置为可见,这就是暴力反射;
System.out.println(fieldX.get(d));
}
}
成员变量综合案例:将任意一个对象中的所有String类型的成员变量所对应的字符串内容“b”变成“a”。
getFields():获取一个类中的所有成员变量,返回的是一个Field类型的数组。
在Field中的方法:
get(Object obj):返回的是Object对象,返回这个成员变量的值;
set(Object obj,Object value):设置某个成员变量的值;
getType():获取成员变量的类型对应的字节码文件对象,返回的是一个Class对象。
演示案例:
import java.lang.reflect.*;
class Demo
{
public String str1 = "basketball";
public String str2 = "black";
public String str3 = "field";
}
class ReflectDemo
{
public static void main(String[] args)throws Exception
{
Demo d = new Demo();
Field[] fields = d.getClass().getFields();//获取Demo类中所有的成员变量;
for(Field field : fields)
{
if(field.getType()==String.class)//比较字节码用“==”,虽然也可以用equals,但语义不准确,因为是同一份字节码;
{
String oldValue = (String)field.get(d);//获取变量的值;
String newValue = oldValue.replace('b','d');//将这个变量中的b替换成d;
field.set(d,newValue);//将对象中的原有的值替换成改变后的值;
}
}
System.out.println(d.str1);
System.out.println(d.str2);
System.out.println(d.str3);
}
}
Method类--成员方法的反射
Method类:类里的方法,字节码对象的方法,不是对象的方法。
getMethod(方法名,这个方法中传入的参数对应的字节码对象):返回一个Method对象,获得字节码中的某一个成员方法。
例如:Method charAt = Class.forName("java.lang.String").getMethod("charAt",int.class);
调用方法
普通方式:str.charAt(4);
反射方式:charAt.invoke(str,4);
invoke(对象,传入的参数):对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
也就是说invoke这个方法是charAt这个方法对象身上的方法。
import java.lang.reflect.*;
class ReflectDemo
{
public static void main(String[] args)throws Exception
{
String str1 = "afsdg";
Method methodCharAt = str1.getClass().getMethod("charAt",int.class);//获取方法对象;
System.out.println(methodCharAt.invoke(str1,2));
}
}
invoke(null,1)这个方法不需要对象,说明方法是静态的,也就是说如果你想调用一个静态方法,就在这里设置成空。
用反射执行某个类中的main方法
import java.lang.reflect.*;
class ReflectDemo
{
public static void main(String[] args)throws Exception
{
//Demo.main(new String[]{"111","222","333"});//普通方法
//反射方法
String startClassName = args[0];//动态的在这个main方法上指定要调用的main方法所属的类;
Method mainMethod = Class.forName(startClassName).getMethod("main",String[].class);//获取要调用的main方法对象;
mainMethod.invoke(null,new Object[]{new String[]{"aaa","bbb","ccc"}});//调用main方法。
//或者mainMethod.invoke(null,(Object)new String[]{"111","222","333"});
}
}
class Demo
{
public static void main(String[] args)throws Exception
{
for(String s : args)
{
System.out.println(s);
}
}
}
运行结果:
为什么要用发射来执行main方法?
在我们自己的主函数上是可以传参数进来的,如果你传的第一个参数args[0]是一个类的名字,我们可以通过反射的方式获得这个类中的main方法对象,然后调用这个main方法,这样可以通过在我们的主函数的参数传递不同的类名,动态的获取这些类的主函数。
在invoke方法上,因为main方法是静态的,所以对象设置为null,直接被类名调用,不需要对象,而把字符串转成Object数组是因为新版本要兼容老版本,而老版本中中还没有出现可变参数,那时传的还是数组,所以字符串数组会被拆开,变成了3个对象,这就造成参数传递的错误,会在编译时发生异常。
数组的反射类型
每一个数组都属于同一个Class,但这些数组要保证具有相同的元素类型以及具有相同的维度。
如:int[] a1 = new int[3];
int[] a2 = new int[4];
int[][] a3 = new int[3][4];
String[] a4 = new String[3];
其中a1和a2属于同一个Class,a1和其他的都不是,因为和a3维数不同,和a4元素类型不同。
代表数组的Class实例对象的getSuperClass方法返回的父类为Object类对应的Class。
基本类型数组可以被当作Object使用,但不能被当作Object[]使用,非基本类型的都可以。
Arrays.asList()处理int[]和String[]的区别就在于得到的集合会把int[]整个数组当成一个元素放进集合,而String[]数组会把数组中的每一个字符串当成元素存进数组。
对数组进行反射
Array工具类用于对数组的反射操作,它里边的方法全是静态的。
演示:
import java.lang.reflect.*;
class ReflectDemo
{
public static void main(String[] args)throws Exception
{
int[] a1 = {4,3,2,5,3};
String[] a2 = {"ada","adadsa","adad","fffgf"};
printArray(a1);
printArray(a2);
}
public static void printArray(Object obj)
{
Class clazz = obj.getClass();//获取数组的字节码对象;
if(clazz.isArray())//判断这个字节码对象是不是数组的;
{
int len = Array.getLength(obj);//获取数组的长度;
for(int x=0;x<len;x++)//对数组进行遍历;
{
System.out.println(Array.get(obj,x));
}
}
else
{
System.out.println(obj);
}
}
}
HashCode分析
HashCode方法的作用是通过Hash算法来对对象进行比较,在我们比较一个集合中的元素是否相同时,一般是把这个对象和集合中的每一个元素进行比较,这样如果元素个数太多,比较起来效率就变的很低,所以,有人发明了哈希算法,通过这种算法算出的哈希值,我们把集合分成了很多区域,当某个对象的哈希值符合某个区域的特点时,就到这个区域去寻找是否存在与这个对象相同的元素,这样就大大的缩小了比较范围,增加了效率,但是这种方法只适用于比较原理基于哈希算法的集合。
内存泄漏:当一个对象被存储今HashSet集合,就不能再修改参与HashCode值运算的字段了,否则对象修改后的哈希值就与修改前存进集合的哈希值不同了,这是如果要删除集合中的这个对象,就找不到了,造成内存泄漏。
反射技术开发框架
框架相当于开发商盖房子,盖的时候,并不知道住进来的人会怎么去装修,比如说门窗的安装,这时候,开发商只需要给住户提供门窗的框架,后期住户要使用什么样的门窗有他们自己决定,如果我们自己一步步的去建房,走到装修这一步要花费很多功夫,如果已经有了开发商给我们提供的这个框架,就会变得简单。
其实java中的框架也是一样的道理,我在写框架的时候,并不知道后来的人会给这个框架传递什么样的类,所以在程序中也没办法直接创建这个类的对象,要让框架获得这样未卜先知的能力,我们在开发框架时就需要用到反射。
我们以后使用别人写的类有两种使用方式:一种是我去调用别人的类,另一种是别人调用我写的类,这两种类是有区别的,前一种叫做工具类,另一种就是框架,框架中使用的就是我们所写的类。
下面我们来制作一个简单的框架:
import java.lang.reflect.*;
import java.io.*;
import java.util.*;
class Demo
{
public int x;
public int y;
Demo(int x,int y)
{
this.x = x;
this.y = y;
}
}
class ReflectDemo
{
public static void main(String[] args)throws Exception
{
Demo d1 = new Demo(4,6);
Demo d2 = new Demo(4,5);
Demo d3 = new Demo(4,6);
InputStream in = new FileInputStream("config.properties");//用文件读取流和配置文件相关联;
Properties prop = new Properties();
prop.load(in);//将流中的数据加载进这个集合中;
String className = prop.getProperty("className");//获取键对应的值;
Class clazz = Class.forName(className);//获取这个值对应的class对象;
Collection collection = (Collection)clazz.newInstance();//获取这个字节码的实例对象;
collection.add(d1);//将数据添加进集合中;
collection.add(d2);
collection.add(d3);
System.out.println(collection.size());//获取集合的大小;
in.close();//关流;
}
}