一、基本数据类型的包装类
基本数据类型的包装类(他们的父类为抽象类Number):即把基本类型的值当成一个对象来表示,与基本数据类型相对应,包装类也有八个:Boolean、Character、Byte、Short、Integer、Long、Float 和 Double。例如,一个 Double 类型的对象包含了一个类型为 double 的字段,这表示如果引用某个值,则可以将该值存储在引用类型的变量中。这些类还提供了大量用于转换基值的方法,如:
Double d=new Double(888.8);
double d1=d.doubleValue();
double d2=Double.parseDouble("123"); //将字符串转换为数值
float f=d.floatValue();
二、Math类
Math类是java.lang包中的一个最终类,它提供了各种基本数学运算函数,比如正弦、余弦和平方根,可以方便地进行数学函数的求值及运算。Math类所有的成员方法都是静态的,可以通过类名直接调用。
Math类还定义了两个静态的数学常量圆周率PI和自然对数的底数E,也可以直接通过Math类名来调用。其成员定义如下:
public static final double E :数学常数e
public static final double PI:圆周率π
public static double sin(double a):正弦函数
public static double cos(double a):余弦函数
public static double tan(double a):正切函数
public static double asin(double a):反正弦函数
public static double toRadians(double angdeg):角度转换为弧度
public static double toDegrees(double angrad):弧度转换为角度
public static double log(double a):自然对数
public static double sqrt(double a):平方根函数
public static double pow(double a, double b):求a的b次幂的函数
public static int round(float a):四舍五入函数,即将a四舍五入得到一整数
public static double random():随机数函数:正的、大于或等于 0.0且小于 1.0的小数
public static int abs(int a):求绝对值的函数
这些函数的使用比较简单,在使用时要注意函数的功能及参数和返回值类型,以便正确的使用。
三、日期类
Java中提供了多个与日期相关的类,其中较常用的有Date、Calendar和DateFormat。其中Date类中的大部分函数都已经不提倡使用(标记为Deprecated),而被Calendar和DateFormat中的函数所取代。Java的日期类提供了很多实用的方法以满足程序员的各种需要。
Java语言规定的基准日期为 1970.1.1 00:00:00 格林威治(GMT)标准时间,当前日期是由基准日期开始所经历的毫秒数转换出来的。下面介绍这些类的使用。
1.Date类
Date 类在java.util 包中.使用Date 类的无参数构造方法创建的对象可 获取本地当前 时间.Date 对象表示时间的默认顺序是星期,月,日,小时,分,秒,年。
例如:Date d=new Date();
System.out.println(d.toString());
将会输出类如:Fri May 04 21:49:21 CST 2007。
如果想让它按照要求的形式和习惯输出,如按照“年月日时分秒 星期”输出,可以使用DateFormat或其子类SimpleDateFormat来输出。
2.DateFormat和Calendar类
在程序中可以使用这两个类来格式化日期或得到当前的系统日期。
例1 日期类的使用
import java.text.*;
import java.util.*;
class DateDemo{
public static void main(String[]args){
Date today;
Calendar now;
DateFormat f1,f2;
String s1,s2;
SimpleDateFormat df;
today=new Date(); // 获取系统当前日期
System.out.println("系统默认Date输出格式: "+today.toString());
f1=DateFormat.getInstance(); // 以默认格式生成格式化器
s1=f1.format(today);//以当前系统环境进行默认格式化
System.out.println("当前机器环境(中文)下的默认格式:"+s1);
// 生成长格式的中国日期格式化器
f1=DateFormat.getDateInstance(DateFormat.LONG, Locale.CHINA);
// 生成长格式的中国时间格式化器
f2=DateFormat.getTimeInstance(DateFormat.LONG, Locale.CHINA);
s1=f1.format(today); // 将日期转换为日期字符串
s2=f2.format(today); // 将日期转换为时间字符串
System.out.println("中国格式: "+s1+" "+s2);
now=Calendar.getInstance(); // 获取系统时间
s1=now.get(now.HOUR)+"时"+now.get(now.MINUTE)+"分"
+now.get(now.SECOND)+"秒";//用Calendar类构造自定义格式的系统时间
System.out.println("使用Calendar类自定义的系统时间格式:"+s1);
df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//自定义的时间格式
String s=df.format(today);//将当前时间按自定义的时间格式进行格式化
System.out.println("自定义的日期格式:"+s);
}
}
图 1 例1的运行结果
|
运行结果如图1所示 。
四、随机函数类
Random类是java.util包中提供的又一个实用的工具类,它用来产生各种随机数,比Math类中的random函数使用简单但功能强大的多。
Random类有两个构造函数:
public Random()
public Random(long seed)
前者使用默认的种子构造一个随机数生成器,后者用指定的种子构造,然后即可使用其成员函数生成所需的各种随机数随机数。
例2 Random类的使用
import java.util.*;
class MathDemo{
public static void main(String[]args){
Random r1=new Random();
//使用指定种子生成对象
Random r2=new Random(1234567890L);
boolean b=r1.nextBoolean();
//生成大于等于0小于100的随机数
int i1=r1.nextInt(100);
int i2=r2.nextInt(100); //同上
//生成大于等于0.0小于1.0的随机数
double i3=r1.nextDouble();
double i4=r2.nextDouble(); //同上
//生成long类型的随机数
long l1=r1.nextLong();
long l2=r2.nextLong(); //同上
System.out.println(b+"\n"+i1+"\n"+i2);
System.out.println(i3+"\n"+i4+"\n"+l1+"\n"+l2);
}
}
运行结果如图2所示 。
图 2 例2的运行结果 |
Collection |
五 集合类
Java中集合类的结构如图所示。
Vector类是java.util包中提供的一个工具类,它类似于可变长的数组,但具有比数组更强大的功能。它很好的解决数组长度不断改变的问题,可以保存一组数据类型不同的元素,不能存储基本数据类型(可以通过存储基本类型对应的包装类来实现)。该类提供了很多实用的方法,可以方便的修改和维护存储在其中的数组元素。
1.Vector 类的构造方法:
public Vector():构造一个空向量,默认容量为10,默认增量为0。
public Vector(int initialCapacity):构造一个空向量,通过参数指定容量,默认增量为0。
public Vector(int initialCapacity, int capacityIncrement):构造一个指定容量(initialCapacity)和增量(capacityIncrement)的空向量。
public Vector(Collection c):构造一个包含指定集合c中的元素的向量。
2.Vector类的成员方法:
public void copyInto(Object[] anArray):将向量的元素复制到指定的数组anArray中。
public void trimToSize():对向量的容量进行微调,使其等于向量的当前大小。
public void setSize(int newSize):重新设置向量的大小。
public int capacity():返回向量的当前容量。
public int size():返回向量中的元素数(即向量的大小)。
public boolean isEmpty():判断向量是否为空。
public Enumeration elements():返回向量中所有的元素。
public boolean contains(Object elem):判断次向量中是否含有elem元素。
public int indexOf(Object elem):搜索第一次出现元素elem的位置,找不到返回-1。
public int indexOf(Object elem, int index):从指定位置index处开始搜索第一次出现元素elem的位置。
public int lastIndexOf(Object elem):搜索最后一次出现元素elem的位置,找不到返回-1。
public Object elementAt(int index):返回指定位置处的元素。
public void setElementAt(Object obj, int index):将index处的元素设置为指定元素obj。
public void removeElementAt(int index):删除指定位置的元素。
public void insertElementAt(Object obj, int index):在指定位置index插入元素obj。
其它更多方法及详细介绍请参阅Java API 帮助文档。
例3 Vector使用示例
import java.util.Vector;
class Avector{
public static void main(String args[]){
Vector v=new Vector();
System.out.println("初始状态:"+v.toString());
v.addElement("one");
v.addElement("two");
v.addElement("three");
System.out.println("添加三个元素后:"+v.toString());
v.insertElementAt("zero",0);
v.insertElementAt("oop",3);
System.out.println("插入两个元素后:"+v.toString());
v.setElementAt("three",3);
v.setElementAt("four",4);
System.out.println("替换两个元素后:"+v.toString());
v.removeAllElements();
System.out.println("删除所有元素后:"+v.toString());
}
图 3 例3的运行结果 |
运行结果如图3 。
5.1 迭代器
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
Java中的Iterator功能比较简单,并且只能单向移动:
(1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。
(2) 使用next()获得序列中的下一个元素。
(3) 使用hasNext()检查序列中是否还有元素。
(4) 使用remove()将迭代器新返回的元素删除。
Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。
5.2 Collection接口
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
基本操作 增加元素add(Object obj); addAll(Collection c);
删除元素 remove(Object obj); removeAll(Collection c);
求交集 retainAll(Collection c);
删除元素 remove(Object obj); removeAll(Collection c);
求交集 retainAll(Collection c);
如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
Iterator it = collection.iterator(); // 获得一个迭代子
while(it.hasNext()) {
Object obj = it.next(); // 得到下一个元素
}
由Collection接口派生的两个接口是List和Set。
5.3 Set
Set,即数学中的集合,特点是无重复、无序,即一种不包含重复的元素的Collection,任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
Set(interface): 存入Set的每个元素必须是唯一的,因为Set不保存重复元素。加入Set的Object必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。
HashSet: 为快速查找而设计的Set。存入HashSet的对象必须定义hashCode()。
TreeSet: 保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。
LinkedHashSet: 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
HashSet采用散列函数对元素进行排序,这是专门为快速查询而设计的;TreeSet采用红黑树的数据结构进行排序元素;LinkedHashSet内部使用散列以加快查询速度,同时使用链表维护元素的次序,使得看起来元素是以插入的顺序保存的。需要注意的是,生成自己的类时,Set需要维护元素的存储顺序,因此要实现Comparable接口并定义compareTo()方法。
5.4 List
List是有序的Collection,次序是List最重要的特点;它确保维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(只推荐LinkedList使用)。一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和删除元素。和Set不同,List允许有相同的元素。
实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。
1、LinkedList类
对顺序访问进行了优化,向List中间插入与删除得开销不大,随机访问则相对较慢(可用ArrayList代替)。它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast(),这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。
注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
List list = Collections.synchronizedList(new LinkedList(...));
2、ArrayList类
由数组实现的List。它允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和删除元素,因为这比LinkedList开销要大很多。
和LinkedList一样,ArrayList也是非同步的(unsynchronized)。
5.5 Map接口
请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。
1、Hashtable类
Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。
使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”:
Hashtable numbers = new Hashtable();
numbers.put(“one”, new Integer(1));
numbers.put(“two”, new Integer(2));
numbers.put(“three”, new Integer(3));
要取出一个数,比如2,用相应的key:
Integer n = (Integer)numbers.get(“two”);
System.out.println(“two = ” + n);
由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。
Hashtable是同步的。
2、HashMap类
HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。
3、WeakHashMap类
WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。
5.6 容器使用总结
如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。
六、枚举类
枚举类型是JDK5.0的新特征。Sun引进了一个全新的关键字enum来定义一个枚举类。
在实际应用中,有的变量只有几种可能的取值。如表示颜色的名称,表示月份的名称等。为了提高程序描述问题的直观性,Java后面的版本引入了和C中类似的枚举类型机制。程序用枚举方法列举一组标识符作为枚举类型的值的集合。当一个变量具有这种枚举类型时,它就能取枚举类型的标识符值。所以在要表示变量只有几种可能的取值时,用枚举类型。也就像星期那样只有7种可能,而且7种可能都是确定的!
例4 下面就是一个典型枚举类型的定义:
Java代码
public enum Color{
RED,BLUE,BLACK,YELLOW,GREEN
}
显然,enum很像特殊的class,实际上enum声明定义的类型就是一个类。而这些类都是类库中Enum类的子类(java.lang.Enum<E>)。它们继承了这个Enum中的许多有用的方法。下面我们就详细介绍enum定义的枚举类的特征及其用法。
1、Color枚举类是特殊的class,其枚举值(RED,BLUE...)是Color的类对象(类实例):Color c=Color.RED;而且这些枚举值都是public static final的,也就是我们经常所定义的常量方式,因此枚举类中的枚举值最好全部大写。
2、即然枚举类是class,当然在枚举类型中有构造器,方法和数据域。但是,枚举类的构造器有很大的不同,它不能有public的构造函数,这样做可以保证客户代码没有办法新建一个enum的实例, 构造器只是在构造枚举值的时候被调用。
3、Enum覆载了了toString方法,因此我们如果调用Color.Blue.toString()默认返回字符串”Blue”.
4、Enum提供了一个valueOf方法,这个方法和toString方法是相对应的。调用valueOf(“Blue”)将返回Color.Blue.因此我们在自己重写toString方法的时候就要注意到这一点,一把来说应该相对应地重写valueOf方法。
5、Enum还提供了values方法,这个方法使你能够方便的遍历所有的枚举值。
6、Enum还有一个oridinal的方法,这个方法返回枚举值在枚举类种的顺序,这个顺序根据枚举值声明的顺序而定,这里Color.Red.ordinal()返回0。
枚举类继承了Enum的所有方法,其它介绍参考API文档。
在上一篇文章JDK1.5新特性简介里面,我们讨论了Enum的最简单形式,即类似于,
public enum Color{
Red,
Green,
Blue;
}
这篇文章我们来详细介绍一下enum的各项特征。Enum作为Sun全新引进的一个关键字,看起来很象是特殊的class, 它也可以有自己的变量,可以定义自己的方法,可以实现一个或者多个接口。 当我们在声明一个enum类型时,我们应该注意到enum类型有如下的一些特征。
1.它不能有public的构造函数,这样做可以保证客户代码没有办法新建一个enum的实例。
2.所有枚举值都是public , static , final的。注意这一点只是针对于枚举值,我们可以和在普通类里面定义 变量一样定义其它任何类型的非枚举变量,这些变量可以用任何你想用的修饰符。
3.Enum默认实现了java.lang.Comparable接口。
4.Enum覆载了了toString方法,因此我们如果调用Color.Blue.toString()默认返回字符串”Blue”.
5.Enum提供了一个valueOf方法,这个方法和toString方法是相对应的。调用valueOf(“Blue”)将返回Color.Blue.因此我们在自己重写toString方法的时候就要注意到这一点,一把来说应该相对应地重写valueOf方法。
6.Enum还提供了values方法,这个方法使你能够方便的遍历所有的枚举值。
7.Enum还有一个oridinal的方法,这个方法返回枚举值在枚举类种的顺序,这个顺序根据枚举值声明的顺序而定,这里Color.Red.ordinal()返回0。
了解了这些基本特性,我们来看看如何使用它们。
1.遍历所有有枚举值. 知道了有values方法,我们可以轻车熟路地用ForEach循环来遍历了枚举值了。
for (Color c: Color.values())
System.out.println(“find value:” + c);
2.在enum中定义方法和变量,比如我们可以为Color增加一个方法随机返回一个颜色。
public enum Color {
Red,
Green,
Blue;
/*
*定义一个变量表示枚举值的数目。
*(我有点奇怪为什么sun没有给enum直接提供一个size方法).
*/
private static int number = Color.values().length ;
/**
* 随机返回一个枚举值
@return a random enum value.
*/
public static Color getRandomColor(){
long random = System.currentTimeMillis() % number;
switch ((int) random){
case 0:
return Color.Red;
case 1:
return Color.Green;
case 2:
return Color.Blue;
default : return Color.Red;
}
}
}
可以看出这在枚举类型里定义变量和方法和在普通类里面定义方法和变量没有什么区别。唯一要注意的只是变量和方法定义必须放在所有枚举值定义的后面,否则编译器会给出一个错误。
3.覆载(Override)toString, valueOf方法。
前面我们已经知道enum提供了toString,valueOf等方法,很多时候我们都需要覆载默认的toString方法,那么对于enum我们怎么做呢。其实这和覆载一个普通class的toString方法没有什么区别。
….
public String toString(){
switch (this){
case Red:
return "Color.Red";
case Green:
return "Color.Green";
case Blue:
return "Color.Blue";
default:
return "Unknow Color";
}
}
….
这时我们可以看到,此时再用前面的遍历代码打印出来的是
Color.Red
Color.Green
Color.Blue
而不是
Red
Green
Blue.
可以看到toString确实是被覆载了。一般来说在覆载toString的时候我们同时也应该覆载valueOf方法,以保持它们相互的一致性。
4.使用构造函数。
虽然enum不可以有public的构造函数,但是我们还是可以定义private的构造函数,在enum内部使用。还是用Color这个例子。
public enum Color {
Red("This is Red"),
Green("This is Green"),
Blue("This is Blue");
private String desc;
Color(String desc){
this.desc = desc;
}
public String getDesc(){
return this.desc;
}
}
这里我们为每一个颜色提供了一个说明信息, 然后定义了一个构造函数接受这个说明信息。
要注意这里构造函数不能为public或者protected, 从而保证构造函数只能在内部使用,客户代码不能new一个枚举值的实例出来。这也是完全符合情理的,因为我们知道枚举值是public static final的常量而已。
5.实现特定的接口
我们已经知道enum可以定义变量和方法,它要实现一个接口也和普通class实现一个接口一样,这里就不作示例了。
6.定义枚举值自己的方法。
前面我们看到可以为enum定义一些方法,其实我们甚至可以为每一个枚举值定义方法。这样,我们前面覆载 toString的例子可以被改写成这样。
public enum Color {
Red {
public String toString(){
return "Color.Red";
}
},
Green {
public String toString(){
return "Color.Green";
}
},
Blue{
public String toString(){
return "Color.Blue";
}
};
}
从逻辑上来说这样比原先提供一个“全局“的toString方法要清晰一些。
总的来说,enum作为一个全新定义的类型,是希望能够帮助程序员写出的代码更加简单易懂,个人觉得一般也不需要过多的使用enum的一些高级特性,否则就和简单易懂的初衷想违背了。
七、泛型
在Java SE1.5中,增加了一个新的特性:泛型(日本语中的总称型)。何谓泛型呢?通俗的说,就是泛泛的指定对象所操作的类型,而不像常规方式一样使用某种固定的类型去指定。泛型的本质就是将所操作的数据类型参数化,也就是说,该数据类型被指定为一个参数。这种参数类型可以使用在类、接口以及方法定义中。
1、为什么使用泛型呢?
在以往的J2SE中,没有泛型的情况下,通常是使用Object类型来进行多种类型数据的操作。这个时候操作最多的就是针对该Object进行数据的强制转换,而这种转换是基于开发者对该数据类型明确的情况下进行的(比如将Object型转换为String型)。倘若类型不一致,编译器在编译过程中不会报错,但在运行时会出错,这是一个安全隐患。
使用泛型的好处在于,它在编译的时候进行类型安全检查,并且在运行时所有的转换都是强制的,隐式的,大大提高了代码的重用率。
2、泛型的简单例子:
首先,我们来看看下面两个普通的class定义
public class getString {
private String myStr;
public String getStr() {
return myStr;
}
public void setStr(str) {
myStr = str;
}
}
public class getDouble {
private Double myDou;
public Double getDou() {
return myDou;
}
public void setDou(dou) {
myDou = dou;
}
}
这两个class除了所操作的数据类型不一致,其他机能都是相同的。现在,我们可以使用泛型来将上面两个class合并为一个,从而提高代码利用率,减少代码量。
public class getObj<T> {
private T myObj ;
public T getObj() {
return myObj;
}
public void setObj<T obj> {
myObj = obj;
}
}
那么,使用了泛型后,如何生成这个class的实例来进行操作呢?请看下面的代码:
getObj<String> strObj = new getObj<String>();
strObj.setObj(“Hello Nissay”);
System.out.println(strObj.getObj());
getObj<Double> douObj = new getObj<Double>();
douObj.setObj(new Double(“116023”));
System.out.println(douObj.getObj());
3、例子分析
现在我们来分析上面那段蓝色字体的代码:
1)<T>是泛型的标记,当然可以使用别的名字,比如。使用<T>声明一个泛型的引用,从而可以在class、方法及接口中使用它进行数据定义,参数传递。
2)<T>在声明的时候相当于一个有意义的数据类型,编译过程中不会发生错误;在实例化时,将其用一个具体的数据类型进行替代,从而就可以满足不用需求。
4、泛型的规则和限制
通过上述的例子,我们简单理解了泛型的含义。在使用泛型时,请注意其使用规则和限制,如下:
1)泛型的参数类型只能是类(class)类型,而不能是简单类型。比如,<int>是不可使用的。
2)可以声明多个泛型参数类型,比如<T, P,Q…>,同时还可以嵌套泛型,例如:<List<String>>
3)泛型的参数类型可以使用extends语句,例如<T extends superclass>。
4)泛型的参数类型可以使用super语句,例如< T super childclass>。
5)泛型还可以使用通配符,例如<? extends ArrayList>
5、扩展
1)extends语句
使用extends语句将限制泛型参数的适用范围。例如:
<T extends collection> ,则表示该泛型参数的使用范围是所有实现了collection接口的calss。如果传入一个<String>则程序编译出错。
2)super语句
super语句的作用与extends一样,都是限制泛型参数的适用范围。区别在于,super是限制泛型参数只能是指定该class的上层父类。
例如<T super List>,表示该泛型参数只能是List和List的上层父类。
3)通配符
使用通配符的目的是为了解决泛型参数被限制死了不能动态根据实例来确定的缺点。
举个例子:public class SampleClass < T extends S> {…}
假如A,B,C,…Z这26个class都实现了S接口。我们使用时需要使用到这26个class类型的泛型参数。那实例化的时候怎么办呢?依次写下
SampleClass<A> a = new SampleClass();
SampleClass<B> a = new SampleClass();
…
SampleClass<Z> a = new SampleClass();
这显然很冗余,还不如使用Object而不使用泛型,呵呵,是吧?
别着急,咱们使用通配符,就OK了。
SampleClass<? Extends S> sc = new SampleClass();
只需要声明一个sc变量,很方便把!
例子一:使用了泛型
public class Gen<T> {
private T ob; //定义泛型成员变量
public Gen(T ob) {
this.ob = ob;
}
public T getOb() {
return ob;
}
public void setOb(T ob) {
this.ob = ob;
}
public void showTyep() {
System.out.println("T的实际类型是: " + ob.getClass().getName());
}
}
public class GenDemo {
public static void main(String[] args){
//定义泛型类Gen的一个Integer版本
Gen<Integer> intOb=new Gen<Integer>(88);
intOb.showTyep();
int i= intOb.getOb();
System.out.println("value= " + i);
System.out.println("----------------------------------");
//定义泛型类Gen的一个String版本
Gen<String> strOb=new Gen<String>("Hello Gen!");
strOb.showTyep();
String s=strOb.getOb();
System.out.println("value= " + s);
}
}
例子二:没有使用泛型
public class Gen2 {
private Object ob; //定义一个通用类型成员
public Gen2(Object ob) {
this.ob = ob;
}
public Object getOb() {
return ob;
}
public void setOb(Object ob) {
this.ob = ob;
}
public void showTyep() {
System.out.println("T的实际类型是: " + ob.getClass().getName());
}
}
public class GenDemo2 {
public static void main(String[] args) {
//定义类Gen2的一个Integer版本
Gen2 intOb = new Gen2(new Integer(88));
intOb.showTyep();
int i = (Integer) intOb.getOb();
System.out.println("value= " + i);
System.out.println("----------------------------------");
//定义类Gen2的一个String版本
Gen2 strOb = new Gen2("Hello Gen!");
strOb.showTyep();
String s = (String) strOb.getOb();
System.out.println("value= " + s);
}
}
运行结果:
两个例子运行Demo结果是相同的,控制台输出结果如下:
T的实际类型是: java.lang.Integer
value= 88
----------------------------------
T的实际类型是: java.lang.String
value= Hello Gen!
Process finished with exit code 0
看明白这个,以后基本的泛型应用和代码阅读就不成问题了。
Java泛型编程快速入门
www.fh888.com 2005-11-23 9:55:56
发布:yhn
媒体:cn-java.com 作者:cn-java
JDK1.5 令我们期待很久,可是当他发布的时候却更换版本号为5.0。这说明Java已经有大幅度的变化。本文将讲解JDK5.0支持的新功能-----Java的泛型.
1、Java泛型
其实Java的泛型就是创建一个用类型作为参数的类。就象我们写类的方法一样,方法是这样的method(String str1,String str2 ),方法中参数str1、str2的值是可变的。而泛型也是一样的,这样写class Java_Generics<K,V>,这里边的K和V就象方法中的参数str1和str2,也是可变。下面看看例子:
//code list 1
import Java.util.Hashtable;
class TestGen0<K,V>{
public Hashtable<K,V> h=new Hashtable<K,V>();
public void put(K k, V v) {
h.put(k,v);
}
public V get(K k) {
return h.get(k);
}
public static void main(String args[]){
TestGen0<String,String> t=new TestGen0<String,String>();
t.put("key", "value");
String s=t.get("key");
System.out.println(s);
}
}
正确输出:value
这只是个例子(Java中集合框架都泛型化了,这里费了2遍事.),不过看看是不是创建一个用类型作为参数的类,参数是K,V,传入的“值”是String类型。这个类他没有特定的待处理型别,以前我们定义好了一个类,在输入输入参数有所固定,是什么型别的有要求,但是现在编写程序,完全可以不制定参数的类型,具体用的时候来确定,增加了程序的通用性,像是一个模板。
呵呵,类似C++的模板(类似)。
1.1. 泛型通配符
下面我们先看看这些程序:
//Code list 2
void TestGen0Medthod1(List l) {
for (Object o : l)
System.out.println(o);
}
看看这个方法有没有异议,这个方法会通过编译的,假如你传入String,就是这样List<String>。
接着我们调用它,问题就出现了,我们将一个List<String>当作List传给了方法,JVM会给我们一个警告,说这个破坏了类型安全,因为从List中返回的都是Object类型的,而让我们再看看下面的方法。
//Code list 3
void TestGen0Medthod1(List<String> l) {
for (Object o : l)
System.out.println(o);
}
因为这里的List<String>不是List<Object>的子类,不是String与Object的关系,就是说List<String>不隶属于list<Object>,他们不是继承关系,所以是不行的,这里的extends是表示限制的。
类型通配符是很神奇的,List<?>这个你能为他做什么呢?怎么都是“?”,它似乎不确定,他总不能返回一个?作为类型的数据吧,是啊他是不会返回一个“?”来问程序员的?JVM会做简单的思考的,看看代码吧,更直观些。
//code list 4
List<String> l1 = new ArrayList<String>();
li.add(“String”);
List<?> l2 = l1;
System.out.println(l1.get(0));
这段代码没问题的,l1.get(0)将返回一个Object。
1.2. 编写泛型类要注意:
1) 在定义一个泛型类的时候,在 “<>”之间定义形式类型参数,例如:“class TestGen<K,V>”,其中“K” , “V”不代表值,而是表示类型。
2) 实例化泛型对象的时候,一定要在类名后面指定类型参数的值(类型),一共要有两次书写。例如:
TestGen<String,String> t=new TestGen<String,String>();
3) 泛型中<K extends Object>,extends并不代表继承,它是类型范围限制。
2、泛型与数据类型转换
2.1. 消除类型转换
上面的例子大家看到什么了,数据类型转换的代码不见了。在以前我们经常要书写以下代码,如:
//code list 5
import Java.util.Hashtable;
class Test {
public static void main(String[] args) {
Hashtable h = new Hashtable();
h.put("key", "value");
String s = (String)h.get("key");
System.out.println(s);
}
}
这个我们做了类型转换,是不是感觉很烦的,并且强制类型转换会带来潜在的危险,系统可能会抛一个ClassCastException异常信息。在JDK5.0中我们完全可以这么做,如:
//code list 6
import Java.util.Hashtable;
class Test {
public static void main(String[] args) {
Hashtable<String,Integer> h = new Hashtable<String,Integer> ();
h.put("key", new Integer(123));
int s = h.get("key").intValue();
System.out.println(s);
}
}
这里我们使用泛化版本的HashMap,这样就不用我们来编写类型转换的代码了,类型转换的过程交给编译器来处理,是不是很方便,而且很安全。上面是String映射到String,也可以将Integer映射为String,只要写成HashTable<Integer,String> h=new HashTable<Integer,String>();h.get(new Integer(0))返回value。果然很方便。
2.2 自动解包装与自动包装的功能
从上面有没有看到有点别扭啊,h.get(new Integer(123))这里的new Integer(123);好烦的,在JDK5.0之前我们只能忍着了,现在这种问题已经解决了,请看下面这个方法。我们传入一个int这一基本型别,然后再将i的值直接添加到List中,其实List是不能储存基本型别的,List中应该存储对象,这里编译器将int包装成Integer,然后添加到List中去。接着我们用List.get(0);来检索数据,并返回对象再将对象解包装成int。恩,JDK5.0给我们带来更多方便与安全。
//Code list 7
public void autoBoxingUnboxing(int i) {
ArrayList<Integer> L= new ArrayList<Integer>();
L.add(i);
int a = L.get(0);
System.out.println("The value of i is " + a);
}
2.3 限制泛型中类型参数的范围
也许你已经发现在code list 1中的TestGen<K,V>这个泛型类,其中K,V可以是任意的型别。也许你有时候呢想限定一下K和V当然范围,怎么做呢?看看如下的代码:
//Code list 8
class TestGen2<K extents String,V extends Number>
{
private V v=null;
private K k=null;
public void setV(V v){
this.v=v;
}
public V getV(){
return this.v;
}
public void setK(K k){
this.k=k;
}
public V getK(){
return this.k;
}
public static void main(String[] args)
{
TestGen2<String,Integer> t2=new TestGen2<String,Integer>();
t2.setK(new String("String"));
t2.setV(new Integer(123));
System.out.println(t2.getK());
System.out.println(t2.getV());
}
}
上边K的范围是<=String ,V的范围是<=Number,注意是“<=”,对于K可以是String的,V当然也可以是Number,也可以是Integer,Float,Double,Byte等。看看下图也许能直观些请看上图A是上图类中的基类,A1,A2分别是A的子类,A2有2个子类分别是A2_1,A2_2。
然后我们定义一个受限的泛型类class MyGen<E extends A2>,这个泛型的范围就是上图中兰色部分。
这个是单一的限制,你也可以对型别多重限制,如下:
class C<T extends Comparable<? super T> & Serializable>
我们来分析以下这句,T extends Comparable这个是对上限的限制,Comparable< super T>这个是下限的限制,Serializable是第2个上限。一个指定的类型参数可以具有一个或多个上限。具有多重限制的类型参数可以用于访问它的每个限制的方法和域。
2.4. 多态方法
//Code list 9
class TestGen {
<T extends Object> public static List<T> make(T first) {
return new List<T>(first);
}
}
八、增强的for循环
1、优点:
使用 for/in 与“普通”for 之间的最基本区别是,您不必使用计数器(通常称为 i 或 count)或 Iterator,代码显得更加清晰和简洁,同时与泛型结合可以避免类型转换。
2、缺点:
对于数组,不能方便的访问下标值;对于集合,与使用Interator相比,不能方便的删除集合中的内容(在内部也是调用Interator).
除了简单遍历并读取其中的内容外,不建议使用增强的for循环。
3、语法为:
for (Type value : arrayOrCollection) {
expression value;
}
注意:for/in循环遍历的集合必须是实现Iterable接口的。
4、数组示例
以前我们这样写:
void someFunction() {
int[] array = { 1, 2, 5, 8, 9 };
int total = 0;
for (int i = 0; i < array.length; i++) {
total += array[i];
}
System.out.println(total);
}
现在我们只需这样写(和以上写法是等价的):
void someFunction() {
int[] array = { 1, 2, 5, 8, 9 };
int total = 0;
for (int n : array) {
total += n;
}
System.out.println(total);
}
这种写法的缺点:
显而易见,for/in(for each)循环自动控制一次遍历数组中的每一个元素,然后将它赋值给一个临时变量(如上述代码中的int n),然后在循环体中可直接对此临时变量进行操作。这种循环的缺点是:
1)只能顺次遍历所有元素,无法实现较为复杂的循环,如在某些条件下需要后退到之前遍历过的某个元素;
2)循环变量(i)不可见,如果想知道当前遍历到数组的第几个元素,只能这样写:
int i = 0;
for (int n : array) {
System.out.println("This " + i + "-th element in the array is " + n);
i++;
}
2、遍历集合
以前我们这样写:
void someFunction() {
List list = new ArrayList();
list.add("Hello ");
list.add("Java ");
String s = "";
for (Iterator iter = list.iterator(); iter.hasNext();) {
String temp = (String) iter.next();
s += temp;
}
System.out.println(s);
}
现在我们这样写:
void someFunction() {
List list = new ArrayList();
list.add("Hello ");
list.add("Java ");
String s = "";
for (Object o : list) {
String temp = (String) o;
s += temp;
}
System.out.println(s);
}
如果结合“泛型”,那么写法会更简单,如下:
void someFunction() {
List<String> list = new ArrayList();
list.add("Hello ");
list.add("Java ");
String s = "";
for (String temp : list) {
s += temp; // 省去了对强制类型转换步骤
}
System.out.println(s);
}
综上所述,Java 5.0中提供的增强的for循环——for/in(for each)循环能让我们的代码更加简洁,让程序员使用时更加方便,但是也有它的局限性,所以一定要根据实际需要有选择性地使用,不要盲目追求所谓的“新特性”。
九、Java的反射机制
Java 反射是Java语言的一个很重要的特征,它使得Java具体了“动态性”。
在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?答案是肯定的。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java 语言的反射(Reflection)机制。
Java 反射机制主要提供了以下功能:
² 在运行时判断任意一个对象所属的类。
² 在运行时构造任意一个类的对象。
² 在运行时判断任意一个类所具有的成员变量和方法。
² 在运行时调用任意一个对象的方法。
Reflection 是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等)、superclass(例如Object)、实现之interfaces(例如Serializable),也包括fields和methods的所有信息,并可于运行时改变fields内容或调用methods。
在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中:
² Class类:代表一个类。
² Field 类:代表类的成员变量(成员变量也称为类的属性)。
² Method类:代表类的方法。
² Constructor 类:代表类的构造方法。
² Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
Java反射示例:
package cn.hnpi.lis;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectTester {
@SuppressWarnings("unchecked")
public Object copy(Object object) throws Exception {
// 获得对象类型
Class classType = object.getClass();
System.out.println("" + classType.getName()); // 通过默认构造方法创建一个新的对象
Object objectCopy = classType.getConstructor(new Class[] {})
.newInstance(new Object[] {}); // 获得对象的所有属性
Field fields[] = classType.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
String fieldName = field.getName();
String firstLetter = fieldName.substring(0, 1).toUpperCase(); // 获得和属性对应的getXXX()方法的名字
String getMethodName = "get" + firstLetter + fieldName.substring(1); // 获得和属性对应的setXXX()方法的名字
String setMethodName = "set" + firstLetter + fieldName.substring(1); // 获得和属性对应的getXXX()方法
Method getMethod = classType.getMethod(getMethodName,
new Class[] {}); // 获得和属性对应的setXXX()方法
Method setMethod = classType.getMethod(setMethodName,
new Class[] { field.getType() }); // 调用原对象的getXXX()方法
Object value = getMethod.invoke(object, new Object[] {});
System.out.println(fieldName + ":" + value); // 调用拷贝对象的setXXX()方法
setMethod.invoke(objectCopy, new Object[] { value });
}
return objectCopy;
}
public static void main(String[] args) throws Exception {
Customer customer = new Customer("lunzi", 26);
customer.setId(new Long(1));
Customer customerCopy = (Customer) new ReflectTester().copy(customer);
System.out.println("Copy information:" + customerCopy.getName() + " "
+ customerCopy.getAge());
}
}
class Customer {
private long id;
private String name;
private int age;
public Customer() {
}
public Customer(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
运行结果:
cn.hnpi.lis.Customer
id:1
name:lunzi
age:26
Copy information:lunzi 26
Java 反射机制深入研究
--------------------------------------------------------------------------------
2007-09-19 13:10:56 标签:Java Reflection 反射 [推送到技术圈]
版权声明:原创作品,如需转载,请与作者联系。否则将追究法律责任。
Java反射机制深入研究
Java 反射是Java语言的一个很重要的特征,它使得Java具体了“动态性”。
在Java运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?答案是肯定的。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java 语言的反射(Reflection)机制。
Java 反射机制主要提供了以下功能:
在运行时判断任意一个对象所属的类。
在运行时构造任意一个类的对象。
在运行时判断任意一个类所具有的成员变量和方法。
在运行时调用任意一个对象的方法。
Reflection 是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等)、superclass(例如Object)、实现之interfaces(例如Serializable),也包括fields和methods的所有信息,并可于运行时改变fields内容或调用methods。
一般而言,开发者社群说到动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。
尽管在这样的定义与分类下Java不是动态语言,它却有着一个非常突出的动态相关机制:Reflection。这个字的意思是“反射、映象、倒影”,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。这种“看透class”的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。
在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中:
Class类:代表一个类。
Field 类:代表类的成员变量(成员变量也称为类的属性)。
Method类:代表类的方法。
Constructor 类:代表类的构造方法。
Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
下面给出几个例子看看Reflection API的实际运用:
一、通过Class类获取成员变量、成员方法、接口、超类、构造方法等
在java.lang.Object 类中定义了getClass()方法,因此对于任意一个Java对象,都可以通过此方法获得对象的类型。Class类是Reflection API 中的核心类,它有以下方法
getName():获得类的完整名字。
getFields():获得类的public类型的属性。
getDeclaredFields():获得类的所有属性。
getMethods():获得类的public类型的方法。
getDeclaredMethods():获得类的所有方法。
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
getConstructors():获得类的public类型的构造方法。
getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
下面给出一个综合运用的例子:
public class RefConstructor {
public static void main(String args[]) throws Exception {
RefConstructor ref = new RefConstructor();
ref.getConstructor();
}
public void getConstructor() throws Exception {
Class c = null;
c = Class.forName("java.lang.Long");
Class cs[] = {java.lang.String.class};
System.out.println("\n-------------------------------\n");
Constructor cst1 = c.getConstructor(cs);
System.out.println("1、通过参数获取指定Class对象的构造方法:");
System.out.println(cst1.toString());
Constructor cst2 = c.getDeclaredConstructor(cs);
System.out.println("2、通过参数获取指定Class对象所表示的类或接口的构造方法:");
System.out.println(cst2.toString());
Constructor cst3 = c.getEnclosingConstructor();
System.out.println("3、获取本地或匿名类Constructor 对象,它表示基础类的立即封闭构造方法。");
if (cst3 != null) System.out.println(cst3.toString());
else System.out.println("-- 没有获取到任何构造方法!");
Constructor[] csts = c.getConstructors();
System.out.println("4、获取指定Class对象的所有构造方法:");
for (int i = 0; i < csts.length; i++) {
System.out.println(csts[i].toString());
}
System.out.println("\n-------------------------------\n");
Type types1[] = c.getGenericInterfaces();
System.out.println("1、返回直接实现的接口:");
for (int i = 0; i < types1.length; i++) {
System.out.println(types1[i].toString());
}
Type type1 = c.getGenericSuperclass();
System.out.println("2、返回直接超类:");
System.out.println(type1.toString());
Class[] cis = c.getClasses();
System.out.println("3、返回超类和所有实现的接口:");
for (int i = 0; i < cis.length; i++) {
System.out.println(cis[i].toString());
}
Class cs1[] = c.getInterfaces();
System.out.println("4、实现的接口");
for (int i = 0; i < cs1.length; i++) {
System.out.println(cs1[i].toString());
}
System.out.println("\n-------------------------------\n");
Field fs1[] = c.getFields();
System.out.println("1、类或接口的所有可访问公共字段:");
for (int i = 0; i < fs1.length; i++) {
System.out.println(fs1[i].toString());
}
Field f1 = c.getField("MIN_VALUE");
System.out.println("2、类或接口的指定已声明指定公共成员字段:");
System.out.println(f1.toString());
Field fs2[] = c.getDeclaredFields();
System.out.println("3、类或接口所声明的所有字段:");
for (int i = 0; i < fs2.length; i++) {
System.out.println(fs2[i].toString());
}
Field f2 = c.getDeclaredField("serialVersionUID");
System.out.println("4、类或接口的指定已声明指定字段:");
System.out.println(f2.toString());
System.out.println("\n-------------------------------\n");
Method m1[] = c.getMethods();
System.out.println("1、返回类所有的公共成员方法:");
for (int i = 0; i < m1.length; i++) {
System.out.println(m1[i].toString());
}
Method m2 = c.getMethod("longValue", new Class[]{});
System.out.println("2、返回指定公共成员方法:");
System.out.println(m2.toString());
}
}
输出结果:输出结果很长,这里不再给出。
二、运行时复制对象
例程ReflectTester 类进一步演示了Reflection API的基本使用方法。ReflectTester类有一个copy(Object object)方法,这个方法能够创建一个和参数object 同样类型的对象,然后把object对象中的所有属性拷贝到新建的对象中,并将它返回
这个例子只能复制简单的JavaBean,假定JavaBean 的每个属性都有public 类型的getXXX()和setXXX()方法。
public class ReflectTester {
public Object copy(Object object) throws Exception {
// 获得对象的类型
Class<?> classType = object.getClass();
System.out.println("Class:" + classType.getName());
// 通过默认构造方法创建一个新的对象
Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
// 获得对象的所有属性
Field fields[] = classType.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
String fieldName = field.getName();
String firstLetter = fieldName.substring(0, 1).toUpperCase();
// 获得和属性对应的getXXX()方法的名字
String getMethodName = "get" + firstLetter + fieldName.substring(1);
// 获得和属性对应的setXXX()方法的名字
String setMethodName = "set" + firstLetter + fieldName.substring(1);
// 获得和属性对应的getXXX()方法
Method getMethod = classType.getMethod(getMethodName, new Class[]{});
// 获得和属性对应的setXXX()方法
Method setMethod = classType.getMethod(setMethodName, new Class[]{field.getType()});
// 调用原对象的getXXX()方法
Object value = getMethod.invoke(object, new Object[]{});
System.out.println(fieldName + ":" + value);
// 调用拷贝对象的setXXX()方法
setMethod.invoke(objectCopy, new Object[]{value});
}
return objectCopy;
}
public static void main(String[] args) throws Exception {
Customer customer = new Customer("Tom", 21);
customer.setId(new Long(1));
Customer customerCopy = (Customer) new ReflectTester().copy(customer);
System.out.println("Copy information:" + customerCopy.getId() + " " + customerCopy.getName() + " "
+ customerCopy.getAge());
}
}
class Customer {
private Long id;
private String name;
private int age;
public Customer() {
}
public Customer(String name, int age) {
this.name = name;
this.age = age;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
输出结果:
Class:com.langsin.reflection.Customer
id:1
name:Tom
age:21
Copy information:1 Tom 21
Process finished with exit code 0
解说:
ReflectTester 类的copy(Object object)方法依次执行以下步骤
(1)获得对象的类型:
Class classType=object.getClass();
System.out.println("Class:"+classType.getName());
(2)通过默认构造方法创建一个新对象:
Object objectCopy=classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
以上代码先调用Class类的getConstructor()方法获得一个Constructor 对象,它代表默认的构造方法,然后调用Constructor对象的newInstance()方法构造一个实例。
3)获得对象的所有属性:
Field fields[]=classType.getDeclaredFields();
Class 类的getDeclaredFields()方法返回类的所有属性,包括public、protected、默认和private访问级别的属性
(4)获得每个属性相应的getXXX()和setXXX()方法,然后执行这些方法,把原来对象的属性拷贝到新的对象中
三、用反射机制调用对象的方法
public class InvokeTester {
public int add(int param1, int param2) {
return param1 + param2;
}
public String echo(String msg) {
return "echo: " + msg;
}
public static void main(String[] args) throws Exception {
Class<?> classType = InvokeTester.class;
Object invokeTester = classType.newInstance();
// Object invokeTester = classType.getConstructor(new
// Class[]{}).newInstance(new Object[]{});
//获取InvokeTester类的add()方法
Method addMethod = classType.getMethod("add", new Class[]{int.class, int.class});
//调用invokeTester对象上的add()方法
Object result = addMethod.invoke(invokeTester, new Object[]{new Integer(100), new Integer(200)});
System.out.println((Integer) result);
//获取InvokeTester类的echo()方法
Method echoMethod = classType.getMethod("echo", new Class[]{String.class});
//调用invokeTester对象的echo()方法
result = echoMethod.invoke(invokeTester, new Object[]{"Hello"});
System.out.println((String) result);
}
}
在例程InvokeTester类的main()方法中,运用反射机制调用一个InvokeTester对象的add()和echo()方法
add()方法的两个参数为int 类型,获得表示add()方法的Method对象的代码如下:
Method addMethod=classType.getMethod("add",new Class[]{int.class,int.class});
Method类的invoke(Object obj,Object args[])方法接收的参数必须为对象,如果参数为基本类型数据,必须转换为相应的包装类型的对象。invoke()方法的返回值总是对象,如果实际被调用的方法的返回类型是基本类型数据,那么invoke()方法会把它转换为相应的包装类型的对象,再将其返回。
在本例中,尽管InvokeTester 类的add()方法的两个参数以及返回值都是int类型,调用add Method 对象的invoke()方法时,只能传递Integer 类型的参数,并且invoke()方法的返回类型也是Integer 类型,Integer 类是int 基本类型的包装类:
Object result=addMethod.invoke(invokeTester,
new Object[]{new Integer(100),new Integer(200)});
System.out.println((Integer)result); //result 为Integer类型
四、动态创建和访问数组
java.lang.Array 类提供了动态创建和访问数组元素的各种静态方法。
例程ArrayTester1 类的main()方法创建了一个长度为10 的字符串数组,接着把索引位置为5 的元素设为“hello”,然后再读取索引位置为5 的元素的值
public class ArrayTester1 {
public static void main(String args[]) throws Exception {
Class<?> classType = Class.forName("java.lang.String");
// 创建一个长度为10的字符串数组
Object array = Array.newInstance(classType, 10);
// 把索引位置为5的元素设为"hello"
Array.set(array, 5, "hello");
// 获得索引位置为5的元素的值
String s = (String) Array.get(array, 5);
System.out.println(s);
}
}
例程ArrayTester2 类的main()方法创建了一个 5 x 10 x 15 的整型数组,并把索引位置为[3][5][10] 的元素的值为设37。
public class ArrayTester2 {
public static void main(String args[]) {
int[] dims = new int[]{5, 10, 15};
//创建一个具有指定的组件类型和维度的新数组。
Object array = Array.newInstance(Integer.TYPE, dims);
Object arrayObj = Array.get(array, 3);
Class<?> cls = arrayObj.getClass().getComponentType();
System.out.println(cls);
arrayObj = Array.get(arrayObj, 5);
Array.setInt(arrayObj, 10, 37);
int arrayCast[][][] = (int[][][]) array;
System.out.println(arrayCast[3][5][10]);
}
}
深入认识Class类
众所周知Java有个Object类,是所有Java类的继承根源,其内声明了数个应该在所有Java类中被改写的方法:hashCode()、equals()、clone()、toString()、getClass()等。其中getClass()返回一个Class类的对象。
Class类十分特殊。它和一般classes一样继承自Object,其实体用以表达Java程序运行时的classes和interfaces,也用来表达enum、array、primitive Java types
(boolean, byte, char, short, int, long, float, double)以及关键词void。当一个class被加载,或当加载器(class loader)的defineClass()被JVM调用,JVM 便自动产生一个Class object。如果您想借由“修改Java标准库源码”来观察Class object的实际生成时机(例如在Class的constructor内添加一个println()),不能够!因为Class并没有public constructor
Class是Reflection起源。针对任何您想探勘的class,唯有先为它产生一个Class object,接下来才能经由后者唤起为数十多个的Reflection APIs
Java允许我们从多种途径为一个class生成对应的Class对象。参看本人的《 深入研究java.long.Class类 》一文。
欲生成对象实体,在Reflection 动态机制中有两种作法,一个针对“无自变量ctor”,一个针对“带参数ctor”。如果欲调用的是“带参数ctor“就比较麻烦些,不再调用Class的newInstance(),而是调用Constructor 的newInstance()。首先准备一个Class[]做为ctor的参数类型(本例指定
为一个double和一个int),然后以此为自变量调用getConstructor(),获得一个专属ctor。接下来再准备一个Object[] 做为ctor实参值(本例指定3.14159和125),调用上述专属ctor的newInstance()。
动态生成“Class object 所对应之class”的对象实体;无自变量。
这个动作和上述调用“带参数之ctor”相当类似。首先准备一个Class[]做为参数类型(本例指定其中一个是String,另一个是Hashtable),然后以此为自变量调用getMethod(),获得特定的Method object。接下来准备一个Object[]放置自变量,然后调用上述所得之特定Method object的invoke()。
为什么获得Method object时不需指定回返类型?
因为method overloading机制要求signature必须唯一,而回返类型并非signature的一个成份。换句话说,只要指定了method名称和参数列,就一定指出了一个独一无二的method。
四、运行时变更field内容
与先前两个动作相比,“变更field内容”轻松多了,因为它不需要参数和自变量。首先调用Class的getField()并指定field名称。获得特定的Field object之后便可直接调用Field的get()和set()。
public class RefFiled {
public double x;
public Double y;
public static void main(String args[]) throws NoSuchFieldException, IllegalAccessException {
Class c = RefFiled.class;
Field xf = c.getField("x");
Field yf = c.getField("y");
RefFiled obj = new RefFiled();
System.out.println("变更前x=" + xf.get(obj));
//变更成员x值
xf.set(obj, 1.1);
System.out.println("变更后x=" + xf.get(obj));
System.out.println("变更前y=" + yf.get(obj));
//变更成员y值
yf.set(obj, 2.1);
System.out.println("变更后y=" + yf.get(obj));
}
}
运行结果:
变更前x=0.0
变更后x=1.1
变更前y=null
变更后y=2.1
Process finished with exit code 0
十、正则表达式
正则表达式在处理文本方面用处非常大,最早像在Perl和awk语言中,提供了这种机制,Java在Java 2中也增加了正则表达式这个包java.util.regex。这个包为用户使用正则表达式,提供了易用而全面的支持。我的研究方向是web挖掘。从网页中提取内容,处理文本,当然需要正则表达式这个强大的工具了。
1、首先我们看一下怎么使用正则表达式的一个例子:
A Matcher examines the results of applying a pattern. 我们希望从这句话中找到所有开头为a的单词。 当然这只是一个简单的例子,你可以使用String提供的split方法,得到单词数组,然后
遍历各个单词看是否是否开头为a 。
我们现在看看怎么使用正则表达式来处理这个问题:
Java代码 :
import java.util.regex.*;
public class FindA{
public static void main(String args[])
throws Exception{
String candidate =
"A Matcher examines the results of applying a pattern.";
String regex = "\\ba\\w*\\b";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(candidate);
String val = null;
System.out.println("INPUT: " + candidate);
System.out.println("REGEX: " + regex +"\r\n");
while (m.find()){
val = m.group();
System.out.println("MATCH: " + val);
}
if (val == null) {
System.out.println("NO MATCHES: ");
}
}
}
从这个例子我们可以看到正则表达式涉及到的两个类Matcher和Pattern,我们以后会专门讨论着连个类。
现在主要看看使用正则表达式的流程:
首先使用 Pattern的一个静态的方法compile来创建Pattern对象:
Pattern p = Pattern.compile(regex);
然后调用Pattern的方法matcher:
Matcher m = p.matcher(candidate);
得到了Matcher对象,Matcher对象保存了许多匹配信息,然后可以通过find()方法
查找匹配的部分,如果有匹配的部分,返回真,使用m.group方法得到匹配的各组值,
否则find返回false,这就是一般的使用过程。
Java代码:String regex = "\\ba\\w*\\b";
这个就是一个正则表达式,b,w,*都是正则表达式的meta character原字符,\b表示单词的边界,w表示任意的可构成单词的字母数字,*表示前面的字母(当然可以是更复杂的组之类的了东东)重复0次或0次以上,a当然还是a了。所以这个regex就匹配单词开头为a的单词了。
2、基本的正则表达式的meta character以及它们含义,请参考Java帮助文档:java.util.regex.Pattern类,有详细介绍。
正则表达式
关键字: java regex, regular expression
正则表达式在处理文本方面用处非常大,最早像在Perl和awk语言中,提供了这种机制,Java在Java 2中也增加了正则表达式这个包java.util.regex。这个包为用户使用正则表达式,提供了易用而全面的支持。我的研究方向是web挖掘。从网页中提取内容,处理文本,当然需要正则表达式这个强大的工具了。
一、首先我们看一下怎么使用正则表达式的一个例子:
A Matcher examines the results of applying a pattern.
我们希望从这句话中找到所有开头为a的单词。
当然这只是一个简单的例子,你可以使用String提供的split方法,得到单词数组,然后
遍历各个单词看是否是否开头为a
我们现在看看怎么使用正则表达式来处理这个问题:
Java代码
- import java.util.regex.*;
- public class FindA{
- public static void main(String args[])
- throws Exception{
- String candidate =
- "A Matcher examines the results of applying a pattern.";
- String regex = "\\ba\\w*\\b";
- Pattern p = Pattern.compile(regex);
- Matcher m = p.matcher(candidate);
- String val = null;
- System.out.println("INPUT: " + candidate);
- System.out.println("REGEX: " + regex +"\r\n");
- while (m.find()){
- val = m.group();
- System.out.println("MATCH: " + val);
- }
- if (val == null) {
- System.out.println("NO MATCHES: ");
- }
- }
- }
import java.util.regex.*;
public class FindA{
public static void main(String args[])
throws Exception{
String candidate =
"A Matcher examines the results of applying a pattern.";
String regex = "\\ba\\w*\\b";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(candidate);
String val = null;
System.out.println("INPUT: " + candidate);
System.out.println("REGEX: " + regex +"\r\n");
while (m.find()){
val = m.group();
System.out.println("MATCH: " + val);
}
if (val == null) {
System.out.println("NO MATCHES: ");
}
}
}
从这个例子我们可以看到正则表达式涉及到的两个类Matcher和Pattern,我们以后会专门讨论着连个类。现在主要看看使用正则表达式的流程:
首先使用 Pattern的一个静态的方法compile来创建Pattern对象,
Java代码
- Pattern p = Pattern.compile(regex);
Pattern p = Pattern.compile(regex);
然后调用Pattern的方法matcher
Java代码
- Matcher m = p.matcher(candidate);
Matcher m = p.matcher(candidate);
得到了Matcher对象,Matcher对象保存了许多匹配信息,然后可以通过find()方法
查找匹配的部分,如果有匹配的部分,返回真,使用m.group方法得到匹配的各组值,
否则find返回false.
当然这只是一般的过程,还有许多更细的方法,在以后会陆续的总结,下面我们看一下
Java代码
- String regex = "\\ba\\w*\\b";
String regex = "\\ba\\w*\\b";
这个就是一个正则表达式,b,w,*都是正则表达式的meta character原字符,
\b表示单词的边界,w表示任意的可构成单词的字母数字,*表示前面的字母(当然可以
是更复杂的组之类的了东东)重复0次或0次以上,a当然还是a了。所以这个regex就
匹配单词开头为a的单词了。
二、下面总结一下基本的正则表达式的meta character以及它们含义:
. 匹配任意一个字符 $ 匹配一行的结尾 ^ 匹配一行的开头(在[]里面表示否定)
{} 定义了一个范围 [] 定义了一个字符类 () 定义了一个组
*前面出现0次以上 + 前面匹配一次以上 ?前面出现0次或一次
\ 后面的字符不会看作metacharacter \w 字母数字下划线 \W 非字母数字下划线
\d 单个数字 \D单个非数字 | 或,二者之一 &&与操作符 \b单词边界
下面看看几个简单的例子:
[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](减去)
三、java.util.regex提供的操作接口:
java.util.regex包提供了操作正则表达式的模型,整个模型优雅而简洁,只有三个类:Pattern、Matcher和
PatternSyntaxException。下面将要总结他们提供的方法,以及如何灵活应用来处理文本。
我们还是从Pattern的静态工厂方法来扩展吧:
Java代码
- static Pattern compile(String regex)
static Pattern compile(String regex)
将给定的正则表达式编译到模式中,并创建Pattern对象,这个方法通常是操作正则表达式的第一步,从前面那个例子
我们也可以看到整个的流程。
在看看一个重载的compile方法:
Java代码
- static Pattern compile(String regex, int flags)
static Pattern compile(String regex, int flags)
将给定的正则表达式编译到具有给定标志的模式中。 这个方法参数flags提供了一些特殊的选项来用于特殊的处理,
我们下面看看可使用的选项:
UNIX_LINES:这个主要处理UNIX和其他的操作系统在行结束符不一样的问题,UNIX使用\n代表一行的终止,而Windows
则使用了\r\n,\n,\r,\u2028或者\u0085作为一行的结束符。
CASE_INSENSITIVE:当我们在匹配的时候要忽略字符大小写时
COMMENTS:允许我们在正则表达式中使用注释,例如
Java代码
- Pattern p =Pattern.compile("A #matches uppercase US-ASCII char code 65",Pattern.COMMENTS);
Pattern p =Pattern.compile("A #matches uppercase US-ASCII char code 65",Pattern.COMMENTS);
MULTILINE:表明要输入多行,他们有自己的终止字符。
Java代码
- Pattern p = Pattern.compile("^", Pattern.MULTILINE);
Pattern p = Pattern.compile("^", Pattern.MULTILINE);
如果你的输入的字符串是:This is a sentence.\n So is this..
这样我们匹配的字符时This中的T和So中的S,如果不使用MULTILINE,则只会匹配T
DOTALL:使用这个选项之后metacharacter .就可以包括一行的终止字符了,如果没有这个选项,
一行的终止字符,并不会考虑在字符串之内的。
使用这个选项会降低效率
Java代码
- Pattern p = Pattern.compile(".", Pattern.DOTALL);
Pattern p = Pattern.compile(".", Pattern.DOTALL);
如果我们输入的是Test\n,则匹配的字符是5个。
UNICODE_CASE:处理UNICODE字符集,使用这个选项会降低效率
CANON_EQ:一个字符的实际存储形式是经过编码后的数字,使用CANON_EQ选项就可以匹配一个字母在各种编码了。
例如a可以匹配+00E0和U+0061U+0300
使用这个选项会降低效率
我们可以组合以上选项,只要使用|,进行按位或操作即可
Java代码
- Pattern p =
- Pattern.compile("t # a compound flag example",Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE|
- Pattern.COMMENT);
Pattern p =
Pattern.compile("t # a compound flag example",Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE|
Pattern.COMMENT);
我们还要注意点的时Java对转译字符\的处理,例如我们要匹配一个数字:
我们不能使用:
Java代码
- Pattern p = Pattern.compile("\d");
Pattern p = Pattern.compile("\d");
而是:
Java代码
- Pattern p = Pattern.compile("\\d");
Pattern p = Pattern.compile("\\d");
另外如果regex本身形式是错误的,compile方法会抛出java.util.regex.PatternSyntaxException异常。
下面我们总结一下public Matcher matcher(CharSequence input)方法:
当我们使用compile操作,创建了Pattern对象之后,我们就可以使用Pattern对象的matcher操作,生成
matcher对象了,Matcher对象包含了许多对匹配结果集的操作,我们在总结Matcher对象的时候再说。另外
顺便提一下参数CharSequence,CharBuffer, Segment, String, StringBuffer, StringBuilder 都实现了
这个接口,所以参数可以是这些中的任一种类型了。
下面我们看看:
Java代码
- public int flags()
public int flags()
这个方法返回了我们前面可以设置的并且已经设置的flags选项,我们通过按位与来判断是否设置了某个选项:
Java代码
- int flgs = myPattern.flags();
- boolean isUsingCommentFlag =( Pattern.COMMENTS == (Pattern.COMMENTS & flgs)) ;
int flgs = myPattern.flags();
boolean isUsingCommentFlag =( Pattern.COMMENTS == (Pattern.COMMENTS & flgs)) ;
看看一个简化过程的方法:
Java代码
- public static boolean matches (String regex,CharSequence input)
public static boolean matches (String regex,CharSequence input)
这个方法实际上是:
Java代码
- Pattern p = Pattern.compile(regex);
- Matcher m = p.matcher(candidate);
- m.matches()
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(candidate);
m.matches()
过程的一个简化,我们在后面总结Matcher中的matches方法之后就会理解这个了。
想必我们经常使用把字符串提取出token变成字符串数组的String中的split方法吧,下面我们看看
类似的一个方法:
public String[] split(CharSequence input)
这个方法提供了强大的功能,因为它可以使用正则表达式来作为token的分割:
Java代码
- Pattern p = new Pattern.compile(",|and");
- String fruits[] = p.split("apple,banana and orange");
Pattern p = new Pattern.compile(",|and");
String fruits[] = p.split("apple,banana and orange");
split的一个重载的版本:
Java代码
- public String[] split(CharSequence input, int limit)
public String[] split(CharSequence input, int limit)
它指定了划分的组数,有以下三种情况:
limit==0
这时候和没有limit参数的那个split效果一样
limit>0
如果你仅仅对前limit个感兴趣,你可以使用limit:
Java代码
- String[] tmp = pattern.split("Hello, Dolly, You, Are, My, Favorite",3);
- //tmp[0] is "Hello",
- // tmp[1] is "Dolly";
- //tmp[2] is "You, Are, My, Favorite";
String[] tmp = pattern.split("Hello, Dolly, You, Are, My, Favorite",3);
//tmp[0] is "Hello",
// tmp[1] is "Dolly";
//tmp[2] is "You, Are, My, Favorite";
limit<0
会尽可能的划分所有的组,即使分割符后面是个空字符,也要单独生成一个token:""
Java代码
- Pattern p = Pattern.compile(",");
- String temp[] = p.split("Hello,Dolly,", -1);
- //temp[]={"Hello","Dolly",""}
关键字: java regex, regular expression
Matcher类:
使用Matcher类,最重要的一个概念必须清楚:组(Group),在正则表达式中
()定义了一个组,由于一个正则表达式可以包含很多的组,所以下面先说说怎么划分组的,
以及这些组和组的下标怎么对应的.
下面我们看看一个小例子,来说明这个问题
引用
\w(\d\d)(\w+)
这个正则表达式有三个组:
整个\w(\d\d)(\w+) 是第0组 group(0)
(\d\d)是第1组 group(1)
(\w+)是第2组 group(2)
我们看看和正则表达式匹配的一个字符串x99SuperJava,
group(0)永远都是匹配整个表达式的字符串的那部分x99SuperJava
group(1)是第1组(\d\d)匹配的部分:99
group(2)是第二组(\w+)匹配的那部分SuperJava
下面我们写一个程序来验证一下:
Java代码
- package edu.jlu.fuliang;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- public class RegexTest {
- public static void main(String[] args) {
- String regex = "\\w(\\d\\d)(\\w+)";
- String candidate = "x99SuperJava";
- Pattern p = Pattern.compile(regex);
- Matcher matcher = p.matcher(candidate);
- if(matcher.find()){
- int gc = matcher.groupCount();
- for(int i = 0; i <= gc; i++)
- System.out.println("group " + i + " :" + matcher.group(i));
- }
- }
- }
package edu.jlu.fuliang;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexTest {
public static void main(String[] args) {
String regex = "\\w(\\d\\d)(\\w+)";
String candidate = "x99SuperJava";
Pattern p = Pattern.compile(regex);
Matcher matcher = p.matcher(candidate);
if(matcher.find()){
int gc = matcher.groupCount();
for(int i = 0; i <= gc; i++)
System.out.println("group " + i + " :" + matcher.group(i));
}
}
}
输出结果:
引用
group 0 99SuperJava
group 1 :99
group 2 :SuperJava
下面我们看看Matcher类提供的方法:
public Pattern pattern()
这个方法返回了,创建Matcher的那个pattern对象。
下面我们看看一个小例子来说明这个结果
Java代码
- import java.util.regex.*;
- public class MatcherPatternExample{
- public static void main(String args[]){
- test();
- }
- public static void test(){
- Pattern p = Pattern.compile("\\d");
- Matcher m1 = p.matcher("55");
- Matcher m2 = p.matcher("fdshfdgdfh");
- System.out.println(m1.pattern() == m2.pattern());
- //return true
- }
- }
import java.util.regex.*;
public class MatcherPatternExample{
public static void main(String args[]){
test();
}
public static void test(){
Pattern p = Pattern.compile("\\d");
Matcher m1 = p.matcher("55");
Matcher m2 = p.matcher("fdshfdgdfh");
System.out.println(m1.pattern() == m2.pattern());
//return true
}
}
public Matcher reset()
这个方法将Matcher的状态重新设置为最初的状态。
public Matcher reset(CharSequence input)
重新设置Matcher的状态,并且将候选字符序列设置为input后进行Matcher,
这个方法和重新创建一个Matcher一样,只是这样可以重用以前的对象。
public int start()
这个方法返回了,Matcher所匹配的字符串在整个字符串的的开始下标:
下面我们看看一个小例子
Java代码
- public class MatcherStartExample{
- public static void main(String args[]){
- test();
- }
- public static void test(){
- //create a Matcher and use the Matcher.start() method
- String candidateString = "My name is Bond. James Bond.";
- String matchHelper[] =
- {" ^"," ^"};
- Pattern p = Pattern.compile("Bond");
- Matcher matcher = p.matcher(candidateString);
- //Find the starting point of the first 'Bond'
- matcher.find();
- int startIndex = matcher.start();
- System.out.println(candidateString);
- System.out.println(matchHelper[0] + startIndex);
- //Find the starting point of the second 'Bond'
- matcher.find();
- int nextIndex = matcher.start();
- System.out.println(candidateString);
- System.out.println(matchHelper[1] + nextIndex);
- }
public class MatcherStartExample{
public static void main(String args[]){
test();
}
public static void test(){
//create a Matcher and use the Matcher.start() method
String candidateString = "My name is Bond. James Bond.";
String matchHelper[] =
{" ^"," ^"};
Pattern p = Pattern.compile("Bond");
Matcher matcher = p.matcher(candidateString);
//Find the starting point of the first 'Bond'
matcher.find();
int startIndex = matcher.start();
System.out.println(candidateString);
System.out.println(matchHelper[0] + startIndex);
//Find the starting point of the second 'Bond'
matcher.find();
int nextIndex = matcher.start();
System.out.println(candidateString);
System.out.println(matchHelper[1] + nextIndex);
}
输出结果:
My name is Bond. James Bond.
^11
My name is Bond. James Bond.
^23
public int start(int group)
这个方法可以指定你感兴趣的sub group,然后返回sup group匹配的开始位置。
public int end()
这个和start()对应,返回在以前的匹配操作期间,由给定组所捕获子序列的最后字符之后的偏移量。
其实start和end经常是一起配合使用来返回匹配的子字符串。
public int end(int group)
和public int start(int group)对应,返回在sup group匹配的子字符串最后一个字符在整个字符串下标加一
public String group()
返回由以前匹配操作所匹配的输入子序列。
这个方法提供了强大而方便的工具,他可以等同使用start和end,然后对字符串作substring(start,end)操作。
看看下面一个小例子:
Java代码
- import java.util.regex.*;
- public class MatcherGroupExample{
- public static void main(String args[]){
- test();
- }
- public static void test(){
- //create a Pattern
- Pattern p = Pattern.compile("Bond");
- //create a Matcher and use the Matcher.group() method
- String candidateString = "My name is Bond. James Bond.";
- Matcher matcher = p.matcher(candidateString);
- //extract the group
- matcher.find();
- System.out.println(matcher.group());
- }
- }
import java.util.regex.*;
public class MatcherGroupExample{
public static void main(String args[]){
test();
}
public static void test(){
//create a Pattern
Pattern p = Pattern.compile("Bond");
//create a Matcher and use the Matcher.group() method
String candidateString = "My name is Bond. James Bond.";
Matcher matcher = p.matcher(candidateString);
//extract the group
matcher.find();
System.out.println(matcher.group());
}
}
public String group(int group)
这个方法提供了强大而方便的工具,可以得到指定的group所匹配的输入字符串
应为这两个方法经常使用,同样我们看一个小例子:
Java代码
- import java.util.regex.*;
- public class MatcherGroupParamExample{
- public static void main(String args[]){
- test();
- }
- public static void test(){
- //create a Pattern
- Pattern p = Pattern.compile("B(ond)");
- //create a Matcher and use the Matcher.group(int) method
- String candidateString = "My name is Bond. James Bond.";
- //create a helpful index for the sake of output
- Matcher matcher = p.matcher(candidateString);
- //Find group number 0 of the first find
- matcher.find();
- String group_0 = matcher.group(0);
- String group_1 = matcher.group(1);
- System.out.println("Group 0 " + group_0);
- System.out.println("Group 1 " + group_1);
- System.out.println(candidateString);
- //Find group number 1 of the second find
- matcher.find();
- group_0 = matcher.group(0);
- group_1 = matcher.group(1);
- System.out.println("Group 0 " + group_0);
- System.out.println("Group 1 " + group_1);
- System.out.println(candidateString);
- }
- }
import java.util.regex.*;
public class MatcherGroupParamExample{
public static void main(String args[]){
test();
}
public static void test(){
//create a Pattern
Pattern p = Pattern.compile("B(ond)");
//create a Matcher and use the Matcher.group(int) method
String candidateString = "My name is Bond. James Bond.";
//create a helpful index for the sake of output
Matcher matcher = p.matcher(candidateString);
//Find group number 0 of the first find
matcher.find();
String group_0 = matcher.group(0);
String group_1 = matcher.group(1);
System.out.println("Group 0 " + group_0);
System.out.println("Group 1 " + group_1);
System.out.println(candidateString);
//Find group number 1 of the second find
matcher.find();
group_0 = matcher.group(0);
group_1 = matcher.group(1);
System.out.println("Group 0 " + group_0);
System.out.println("Group 1 " + group_1);
System.out.println(candidateString);
}
}
public int groupCount()
这个方法返回了,正则表达式的匹配的组数。
public boolean matches()
尝试将整个区域与模式匹配。这个要求整个输入字符串都要和正则表达式匹配。
和find不同, find是会在整个输入字符串查找匹配的子字符串。
public boolean find()
find会在整个输入中寻找是否有匹配的子字符串,一般我们使用find的流程:
Java代码
- while(matcher.find()){
- //在匹配的区域,使用group,replace等进行查看和替换操作
- }
while(matcher.find()){
//在匹配的区域,使用group,replace等进行查看和替换操作
}
public boolean find(int start)
从输入字符串指定的start位置开始查找。
public boolean lookingAt()
基本上是matches更松约束的一个方法,尝试将从区域开头开始的输入序列与该模式匹配
public Matcher appendReplacement (StringBuffer sb, String replacement)
你想把My name is Bond. James Bond. I would like a martini中的Bond换成Smith
Java代码
- StringBuffer sb = new StringBuffer();
- String replacement = "Smith";
- Pattern pattern = Pattern.compile("Bond");
- Matcher matcher =pattern.matcher("My name is Bond. James Bond. I would like a martini.");
- while(matcher.find()){
- matcher.appendReplacement(sb,replacement);//结果是My name is Smith. James Smith
- }
StringBuffer sb = new StringBuffer();
String replacement = "Smith";
Pattern pattern = Pattern.compile("Bond");
Matcher matcher =pattern.matcher("My name is Bond. James Bond. I would like a martini.");
while(matcher.find()){
matcher.appendReplacement(sb,replacement);//结果是My name is Smith. James Smith
}
Matcher对象会维护追加的位置,所以我们才能不断地使用appendReplacement来替换所有的匹配。
public StringBuffer appendTail(StringBuffer sb)
这个方法简单的把为匹配的结尾追加到StringBuffer中。在上一个例子的最后再加上一句:
matcher.appendTail(sb);
结果就会成为My name is Smith. James Smith. I would like a martini.
public String replaceAll(String replacement)
这个是一个更方便的方法,如果我们想替换所有的匹配的话,我们可以简单的使用replaceAll就ok了。
是:
Java代码
- while(matcher.find()){
- matcher.appendReplacement(sb,replacement);//结果是My name is Smith. James Smith
- }
- matcher.appendTail(sb);
while(matcher.find()){
matcher.appendReplacement(sb,replacement);//结果是My name is Smith. James Smith
}
matcher.appendTail(sb);
的更便捷的方法。
Java代码
- public String replaceFirst(String replacement)
public String replaceFirst(String replacement)
这个与replaceAll想对应很容易理解,就是只替换第一个匹配的。
常用正常表达式
众所周知,在程序开发中,难免会遇到需要匹配、查找、替换、判断字符串的情况发生,而这些情况有时又比较复杂,如果用纯编码方式解决,往往会浪费程序员的时间及精力。因此,学习及使用正则表达式,便成了解决这一矛盾的主要手段。
大 家都知道,正则表达式是一种可以用于模式匹配和替换的规范,一个正则表达式就是由普通的字符(例如字符a到z)以及特殊字符(元字符)组成的文字模式,它 用以描述在查找文字主体时待匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
自从jdk1.4推出java.util.regex包,就为我们提供了很好的JAVA正则表达式应用平台。
因为正则表达式是一个很庞杂的体系,所以我仅例举些入门的概念,更多的请参阅相关书籍及自行摸索。
\\ 反斜杠
\t 间隔 ('\u0009')
\n 换行 ('\u000A')
\r 回车 ('\u000D')
\d 数字 等价于[0-9]
\D 非数字 等价于[^0-9]
\s 空白符号 [\t\n\x0B\f\r]
\S 非空白符号 [^\t\n\x0B\f\r]
\w 单独字符 [a-zA-Z_0-9]
\W 非单独字符 [^a-zA-Z_0-9]
\f 换页符
\e Escape
\b 一个单词的边界
\B 一个非单词的边界
\G 前一个匹配的结束
^为限制开头
^java 条件限制为以Java为开头字符
$为限制结尾
java$ 条件限制为以java为结尾字符
. 条件限制除\n以外任意一个单独字符
java.. 条件限制为java后除换行外任意两个字符
加入特定限制条件「[]」
[a-z] 条件限制在小写a to z范围中一个字符
[A-Z] 条件限制在大写A to Z范围中一个字符
[a-zA-Z] 条件限制在小写a to z或大写A to Z范围中一个字符
[0-9] 条件限制在小写0 to 9范围中一个字符
[0-9a-z] 条件限制在小写0 to 9或a to z范围中一个字符
[0-9[a-z]] 条件限制在小写0 to 9或a to z范围中一个字符(交集)
[]中加入^后加再次限制条件「[^]」
[^a-z] 条件限制在非小写a to z范围中一个字符
[^A-Z] 条件限制在非大写A to Z范围中一个字符
[^a-zA-Z] 条件限制在非小写a to z或大写A to Z范围中一个字符
[^0-9] 条件限制在非小写0 to 9范围中一个字符
[^0-9a-z] 条件限制在非小写0 to 9或a to z范围中一个字符
[^0-9[a-z]] 条件限制在非小写0 to 9或a to z范围中一个字符(交集)
在限制条件为特定字符出现0次以上时,可以使用「*」
J* 0个以上J
.* 0个以上任意字符
J.*D J与D之间0个以上任意字符
在限制条件为特定字符出现1次以上时,可以使用「+」
J+ 1个以上J
.+ 1个以上任意字符
J.+D J与D之间1个以上任意字符
在限制条件为特定字符出现有0或1次以上时,可以使用「?」
JA? J或者JA出现
限制为连续出现指定次数字符「{a}」
J{2} JJ
J{3} JJJ
文字a个以上,并且「{a,}」
J{3,} JJJ,JJJJ,JJJJJ,???(3次以上J并存)
文字个以上,b个以下「{a,b}」
J{3,5} JJJ或JJJJ或JJJJJ
两者取一「|」
J|A J或A
Java|Hello Java或Hello
「()」中规定一个组合类型
比如,我查询<a href=\"index.html\">index</a>中<a href></a>间的数据,可写作<a.*href=\".*\">(.+?)</a>
在使用Pattern.compile函数时,可以加入控制正则表达式的匹配行为的参数:
Pattern Pattern.compile(String regex, int flag)
flag的取值范围如下:
Pattern.CANON_EQ 当且仅当两个字符的"正规分解(canonical decomposition)"都完全相同的情况下,才认定匹配。比如用了这个标志之后,表达式"a\u030A"会匹配"?"。默认情况下,不考虑"规 范相等性(canonical equivalence)"。
Pattern.CASE_INSENSITIVE(?i) 默认情况下,大小写不明感的匹配只适用于US-ASCII字符集。这个标志能让表达式忽略大小写进行匹配。要想对Unicode字符进行大小不明感的匹配,只要将UNICODE_CASE与这个标志合起来就行了。
Pattern.COMMENTS(?x) 在这种模式下,匹配时会忽略(正则表达式里的)空格字符(译者注:不是指表达式里的"\\s",而是指表达式里的空格,tab,回车之类)。注释从#开始,一直到这行结束。可以通过嵌入式的标志来启用Unix行模式。
Pattern.DOTALL(?s) 在这种模式下,表达式'.'可以匹配任意字符,包括表示一行的结束符。默认情况下,表达式'.'不匹配行的结束符。
Pattern.MULTILINE
(?m) 在这种模式下,'^'和'$'分别匹配一行的开始和结束。此外,'^'仍然匹配字符串的开始,'$'也匹配字符串的结束。默认情况下,这两个表达式仅仅匹配字符串的开始和结束。
Pattern.UNICODE_CASE
(?u) 在这个模式下,如果你还启用了CASE_INSENSITIVE标志,那么它会对Unicode字符进行大小写不明感的匹配。默认情况下,大小写不敏感的匹配只适用于US-ASCII字符集。
Pattern.UNIX_LINES(?d) 在这个模式下,只有'\n'才被认作一行的中止,并且与'.','^',以及'$'进行匹配。
抛开空泛的概念,下面写出几个简单的Java正则用例:
◆比如,在字符串包含验证时
//查找以Java开头,任意结尾的字符串
Pattern pattern = Pattern.compile("^Java.*");
Matcher matcher = pattern.matcher("Java不是人");
boolean b= matcher.matches();
//当条件满足时,将返回true,否则返回false
System.out.println(b);
◆以多条件分割字符串时
Pattern pattern = Pattern.compile("[, |]+");
String[] strs = pattern.split("Java Hello World Java,Hello,,World|Sun");
for (int i=0;i<strs.length;i++) {
System.out.println(strs[i]);
}
◆文字替换(首次出现字符)
Pattern pattern = Pattern.compile("正则表达式");
Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World");
//替换第一个符合正则的数据
System.out.println(matcher.replaceFirst("Java"));
◆文字替换(全部)
Pattern pattern = Pattern.compile("正则表达式");
Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World");
//替换第一个符合正则的数据
System.out.println(matcher.replaceAll("Java"));
◆文字替换(置换字符)
Pattern pattern = Pattern.compile("正则表达式");
Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World ");
StringBuffer sbr = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sbr, "Java");
}
matcher.appendTail(sbr);
System.out.println(sbr.toString());
◆验证是否为邮箱地址
String str="ceponline@yahoo.com.cn";
Pattern pattern = Pattern.compile("[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+",Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(str);
System.out.println(matcher.matches());
◆去除html标记
Pattern pattern = Pattern.compile("<.+?>", Pattern.DOTALL);
Matcher matcher = pattern.matcher("<a href=\"index.html\">主页</a>");
String string = matcher.replaceAll("");
System.out.println(string);
◆查找html中对应条件字符串
Pattern pattern = Pattern.compile("href=\"(.+?)\"");
Matcher matcher = pattern.matcher("<a href=\"index.html\">主页</a>");
if(matcher.find())
System.out.println(matcher.group(1));
}
◆截取http://地址
//截取url
Pattern pattern = Pattern.compile("(http://|https://){1}[\\w\\.\\-/:]+");
Matcher matcher = pattern.matcher("dsdsds<http://dsds//gfgffdfd>fdf");
StringBuffer buffer = new StringBuffer();
while(matcher.find()){
buffer.append(matcher.group());
buffer.append("\r\n");
System.out.println(buffer.toString());
}
◆替换指定{}中文字
String str = "Java目前的发展史是由{0}年-{1}年";
String[][] object={new String[]{"\\{0\\}","1995"},new String[]{"\\{1\\}","2007"}};
System.out.println(replace(str,object));
public static String replace(final String sourceString,Object[] object) {
String temp=sourceString;
for(int i=0;i<object.length;i++){
String[] result=(String[])object[i];
Pattern pattern = Pattern.compile(result[0]);
Matcher matcher = pattern.matcher(temp);
temp=matcher.replaceAll(result[1]);
}
return temp;
}
◆以正则条件查询指定目录下文件
//用于缓存文件列表
private ArrayList files = new ArrayList();
//用于承载文件路径
private String _path;
//用于承载未合并的正则公式
private String _regexp;
class MyFileFilter implements FileFilter {
/**
* 匹配文件名称
*/
public boolean accept(File file) {
try {
Pattern pattern = Pattern.compile(_regexp);
Matcher match = pattern.matcher(file.getName());
return match.matches();
} catch (Exception e) {
return true;
}
}
}
/**
* 解析输入流
* @param inputs
*/
FilesAnalyze (String path,String regexp){
getFileName(path,regexp);
}
/**
* 分析文件名并加入files
* @param input
*/
private void getFileName(String path,String regexp) {
//目录
_path=path;
_regexp=regexp;
File directory = new File(_path);
File[] filesFile = directory.listFiles(new MyFileFilter());
if (filesFile == null) return;
for (int j = 0; j < filesFile.length; j++) {
files.add(filesFile[j]);
}
return;
}
/**
* 显示输出信息
* @param out
*/
public void print (PrintStream out) {
Iterator elements = files.iterator();
while (elements.hasNext()) {
File file=(File) elements.next();
out.println(file.getPath());
}
}
public static void output(String path,String regexp) {
FilesAnalyze fileGroup1 = new FilesAnalyze(path,regexp);
fileGroup1.print(System.out);
}
public static void main (String[] args) {
output("C:\\","[A-z|.]*");
}
Java正则的功用还有很多,事实上只要是字符处理,就没有正则做不到的事情存在。(当然,正则解释时较耗时间就是了|||……)
file的getPath getAbsolutePath和getCanonicalPath的不同
file的这几个取得path的方法各有不同,下边说说详细的区别
概念上的区别:(内容来自jdk,个人感觉这个描述信息,只能让明白的人明白,不明白的人看起来还是有点难度(特别试中文版,英文版稍好些)所以在概念之后我会举例说明。如果感觉看概念很累就跳过直接看例子吧。看完例子回来看概念会好些。
getPath
public String getPath()将此抽象路径名转换为一个路径名字符串。所得到的字符串使用默认名称分隔符来分隔名称序列中的名称。
返回:
此抽象路径名的字符串形式
getAbsolutePath
public String getAbsolutePath()返回抽象路径名的绝对路径名字符串。
如果此抽象路径名已经是绝对路径名,则返回该路径名字符串,这与 getPath() 方法一样。如果此抽象路径名是空的抽象路径名,则返回当前用户目录的路径名字符串,该目录由系统属性 user.dir 指定。否则,使用与系统有关的方式分析此路径名。在 UNIX 系统上,通过根据当前用户目录分析某一相对路径名,可使该路径名成为绝对路径名。在 Microsoft Windows 系统上,通过由路径名指定的当前驱动器目录(如果有)来分析某一相对路径名,可使该路径名成为绝对路径名;否则,可以根据当前用户目录来分析它。
返回:
绝对路径名字符串,它与此抽象路径名表示相同的文件或目录的
抛出:
SecurityException - 如果无法访问所需的系统属性值。
另请参见:
isAbsolute()
getCanonicalPath
public String getCanonicalPath()
throws IOException返回抽象路径名的规范路径名字符串。
规范路径名是绝对路径名,并且是惟一的。规范路径名的准确定义与系统有关。如有必要,此方法首先将路径名转换成绝对路径名,这与调用 getAbsolutePath() 方法的效果一样,然后用与系统相关的方式将它映射到其惟一路径名。这通常涉及到从路径名中移除多余的名称(比如 "." 和 "..")、分析符号连接(对于 UNIX 平台),以及将驱动器名转换成标准大小写形式(对于 Microsoft Windows 平台)。
表示现有文件或目录的每个路径名都有一个惟一的规范形式。表示非存在文件或目录的每个路径名也有一个惟一的规范形式。非存在文件或目录路径名的规范形式可能不同于创建文件或目录之后同一路径名的规范形式。同样,现有文件或目录路径名的规范形式可能不同于删除文件或目录之后同一路径名的规范形式。
返回:
表示与此抽象路径名相同的文件或目录的规范路径名字符串
抛出:
IOException - 如果发生 I/O 错误(可能是因为构造规范路径名需要进行文件系统查询)
SecurityException - 如果无法访问所需的系统属性值,或者存在安全管理器,且其 SecurityManager.checkRead(java.io.FileDescriptor) 方法拒绝对该文件进行读取访问
从以下版本开始:
JDK1.1
二、例子:
1,getPath()与getAbsolutePath()的区别
public static void test1() {
File file1 = new File(".\\test1.txt");
File file2 = new File("D:\\workspace\\test\\test1.txt");
System.out.println("-----默认相对路径:取得路径不同------");
System.out.println(file1.getPath());
System.out.println(file1.getAbsolutePath());
System.out.println("-----默认绝对路径:取得路径相同------");
System.out.println(file2.getPath());
System.out.println(file2.getAbsolutePath());
}
得到的结果:
-----默认相对路径:取得路径不同------
.\test1.txt
D:\workspace\test\.\test1.txt
-----默认绝对路径:取得路径相同------
D:\workspace\test\test1.txt
D:\workspace\test\test1.txt
因为getPath()得到的是构造file的时候的路径。
getAbsolutePath()得到的是全路径
如果构造的时候就是全路径那直接返回全路径
如果构造的时候试相对路径,返回当前目录的路径+构造file时候的路径
2,getAbsolutePath()和getCanonicalPath()的不同
public static void test2() throws Exception {
File file = new File("..\\src\\test1.txt");
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
}
得到的结果
D:\workspace\test\..\src\test1.txt
D:\workspace\src\test1.txt
可以看到CanonicalPath不但是全路径,而且把..或者.这样的符号解析出来。
3,getCanonicalPath()和自己的不同。
就是解释这段话:
表示现有文件或目录的每个路径名都有一个惟一的规范形式。表示非存在文件或目录的每个路径名也有一个惟一的规范形式。非存在文件或目录路径名的规范形式可能不同于创建文件或目录之后同一路径名的规范形式。同样,现有文件或目录路径名的规范形式可能不同于删除文件或目录之后同一路径名的规范形式。
单下边这段代码是看不到结果的,要配合一定的操作来看。下边操作步骤,同时讲解
public static void test3() throws Exception {
File file = new File("D:\\Text.txt");
System.out.println(file.getCanonicalPath());
}
步骤:
确定你的系统是Windows系统。
(1),确定D盘下没有Text.txt这个文件,直接执行这段代码,得到的结果是:
D:\Text.txt
注意这里试大写的Text.txt
(2)在D盘下建立一个文件,名叫text.txt,再次执行代码,得到结果
D:\text.txt
同样的代码得到不同的结果。
同时可以对比getAbsolutePath()看看,这个得到的结果是一样的。
原因:
window是大小写不敏感的,也就是说在windows上test.txt和Test.txt是一个文件,所以在windows上当文件不存在时,得到的路径就是按照输入的路径。但当文件存在时,就会按照实际的情况来显示。这也就是建立文件后和删除文件后会有不同的原因。文件夹和文件类似。
三、最后:
1,尝试在linux下执行上边的步骤,两次打印的结果是相同的,因为linux是大小写敏感的系统。
2,手动删掉test.txt,然后尝试执行下边代码
public static void test4() throws Exception {
File file = new File("D:\\Text.txt");
System.out.println(file.getCanonicalPath());
File file1 = new File("D:\\text.txt");
file1.createNewFile();
file = new File("D:\\Text.txt");
System.out.println(file.getCanonicalPath());
}
public static void test3() throws Exception {
File file1 = new File("D:\\text.txt");
file1.createNewFile();
File file = new File("D:\\Text.txt");
System.out.println(file.getCanonicalPath());
}
执行上边两个函数,看看结果,然后思考一下为什么?
1,的结果是两个大写,
2,的结果试两个小写
连续两个大写的,是否跟上边的矛盾 ?
这是因为虚拟机的缓存机制造成的。第一次File file = new File("D:\\Text.txt");决定了结果.