补充内容
1.split
split可以把串分解为片语(token)。此动作,俗称“打散”。
需要注意的是,其中的参数并不是“分隔串”,而是一个“正则表达式”。
String s = "xyz,abc,123";
String[] ss = s.split(",");
这种用法是正确的。因为,逗号并不是正则表达式的关键字。如果想把“12+25”分解为“12”和“25”,就不能像下面这样写了:
String s = "12+25";
String[] ss = s.split("+");
这是因为“加号”在正则表达式中有特殊的含义。如果希望作为分隔符的就是“+”,则可以采用转义的办法来表达。
String[] ss = s.split("[+]");
或者
String[] ss = s.split("\\+");
其中,有些奇怪的是第二种写法。之所以写两个反斜线,是因为,反斜线也是java串中的转义符号,所以此处是经过了两次转义。第一次是java转义,第二次是正则表达式的转义。
2.java.util.StringTokenizer
有的时候,使用StringTokenizer更容易对串进行分解工作。这是类可根据我们提供的分隔符号把一个串,分解为多个token,分解的时候,也可以包含分隔符本身,也可以不包含
String s = "23+45-(3+5*2)";
StringTokenizer st = new StringTokenizer(s,"+-*/()",true);
while(st.hasMoreTokens()){
System.out.println(st.nextToken());
}
另外,countTokens()可以返回tokens的数目。
3.正则表达式
正则表达式代表字符序列的一定的“样式”。我们可以去判断,给定的样式是否与某个串相匹配。也可以从一个序列中去提取出匹配的子串来。
String s1 = " AD12 ";
String s2 = "\\s*[A-Za-z]{1,3}[0-9]{1,4}\\s*";
System.out.println(Pattern.matches(s2,s1));
[0-9A-Fa-f] 含义就是:0|1|2|3...|A|B|C|D|E|F|a|...
matches()方法判断给定的样式s2是否与s1相匹配。它的返回值是boolean类型。上面这段代码判断s1是否符合excel表的单元格“相对表示法”。
\s 表示空白字符
方括号内表示字符本身。有一些限定数量的量词,它们可能是:
* 表示0个或多个
+ 表示1个或多个
? 表示0个或1个
{m,n} 表示至少m个,至多n个
{m} 表示正好m个
下面是判断手机号码的一个例子:
String s1 = "1350555117";
String s2 = "(138|136|135)[0-9]{8}";
System.out.println(Pattern.matches(s2,s1));
下面是判断身份证号码的例子:
String s1 = "320111197103254419";
String s2 = "[0-9]{6}19[0-9][0-9][0-1][0-9][0-2][0-9][0-9]{4}";
System.out.println(Pattern.matches(s2,s1));
Pattern除了能够判断是否匹配,还能够把已经匹配上的子串从母串中分离出来。
String s1 = "56+14-5*(2+6)-56+48";
String s2 = "\\+|\\-|\\*|\\/|\\(|\\)";
Pattern p = Pattern.compile(s2);
Matcher m = p.matcher(s1);
while(m.find()){
System.out.println(m.group() + "," + m.start() + "," + m.end());
}
上例中,寻找 +-*/ 及括号的位置,输出匹配项,并其起始和结束位置。
Matcher类代表一个遍历串的匹配工具。它用find()搜索下一个匹配项,group()输出匹配项的内容,start(), end()给出匹配项出现的开始和结束位置。
String s1 = "2+5*3/5-4";
String s2 = "[0-9]{1,2}\\*[0-9]{1,2}|[0-9]{1,2}\\/[0-9]{1,2}";
Pattern p = Pattern.compile(s2);
Matcher m = p.matcher(s1);
if(m.find()){
String t = m.group();
s1 = m.replaceFirst("kkk");
}
System.out.println(s1);
上面的代码,在s1中搜索第一个出现的乘法或除法算式,并把它用“kkk”去代替。此处,replaceFirst()方法,把第一处匹配用另一个串去代替。这个方法很有用。我们可以利用这个动作去对四则运算求值。
4.JDK5.0新特性:foreach
foreach是c#中的特性,jdk5.0开始也引入了这个特性。
这个特性,只对数组和容器使用。
1) 对数组的用法:
int[] x = {10,20,30,50};
for(int k: x){
System.out.println(k);
}
2) 对容器的用法
Vector x = new Vector();
x.add("xyz");
x.add("abc");
x.add("abc");
for(Object k: x){
System.out.println(k);
}
5.JDK5.0新特性:enum
如果我们需要定义很多个常量,用final修饰是不方便的。
在许多语言中都有“枚举”类型。jdk5.0也引入了这个特性。
定义如下:
enum ZhiCheng
{
ZHU_GONG, GONG_CHENG_SHI, GAO_GONG;
}
使用如下:
ZhiCheng x;
x = ZhiCheng.GAO_GONG;
if(x==ZhiCheng.ZHU_GONG) ....
6.JDK5.0新特性:泛型
jdk5.0以前的版本中,数据结构一般都是用Object为操作元素,这样在写入和读出的时候,需要转换,比较烦琐。而且,java在对动态类型进行管理的时候,也要耗费时间。
泛型技术,类似于c++中的类模板的技术。就是定义类时,产生的是模板类型,在使用类的时候,再去指定它需要的类型,这时才把类真正固定下来
Vector<String> a = new Vector<String>();
a.add("abc");
a.add("1234");
a.add("xyz");
Iterator<String> i = a.iterator();
while(i.hasNext()){
String s = i.next();
System.out.println(s);
}
我们可以像java的泛型类一样,创建自己的泛型类。
class MyStack<T>{
private Object[] m_data;
private int m_num;
public MyStack(){
m_data = new Object[100];
m_num = 0;
}
public void push(T x){
m_data[m_num] = x;
m_num++;
}
public T pop(){
m_num--;
return (T)m_data[m_num];
}
}
使用这个泛型类:
MyStack<String> a;
a = new MyStack();
a.push("abc");
a.push(new Integer(555));
System.out.println(a.pop());
java的泛型与c++很不一样。c++采用的是模板的替换技术,为每个不同的类型生成了新的版本。java的泛型采用“拭去”技术。本质上仍就是Object,只是java在适当的时机进行了适当的类型转换,从而把“不安全的转换”这个敏感的工作交给编译器来完成。
可以去查找关于泛型的高级话题:
类型范围的限制:class <T extends Shape> ...
类型通配符: void f(Collection<?> x){ ... }
泛型方法:<T> void f(Collection<T> x){ ... }
7.java反射
java为什么能够实现多态?必须在某种机制,能够在运行的时候,准确地判定一个对象的真实类型。这称为运行时类型识别(RTTI)。
java中,保存类型信息的类为Class。由它可获得类型的类名,方法,属性等信息。任何一个装入内存的类或者接口,java都会为它创建一个Class类的对象来保存该类型的详细信息。
与许多语言不同,java并不是一上来就把所有的类都加载入内存。它在用到一个类的时候,才会去查找它的Class对象,如果不存在,才去加载类
正是通过了Class对象,java才能完成由那个类创建对象的工作。
Object中的getClass()方法,返回该类的类型信息实例
还可以用Class对象之静态方法forName()获得特定的类型信息。
Class c1 = Class. forName("MyA");
还可以用某个具体类型获得它的类型信息对象。这称为“类标记”。
Class c2 = MyA.class;
class c3 = int.class;
类型信息不仅可用于类,还可以用于数组和接口,甚至是原生类型。
类型信息的用处:
1)判断某个对象是否为某类型所创建
if(x.getClass().getName() ...) ... // 通过类名来判断
if(x.getClass == MyA.class) ... // 通过类型信息的实唯一性
2)根据一个字符串去创建一个类的对象
Object m = Class. forName("MyA").newInstance();
newInstance()是用未知类型创建对象的好方法
3)执行一个动态类中的动态方法
假如,曾有一个类是这样的:
class AAA{
public int myadd(int x, int y){
return x + y;
}
}
在编译时,不用知道这个类,就可在写出如下的代码来:
Class c1 = Class.forName("AAA");
Object obj = c1.newInstance(); //创建了动态类的对象实例
Class[] para_type = {Integer.TYPE, Integer.TYPE};
Method m = c1.getMethod("myadd", para_type); //在该类信息中找到需要调用的方法
Object[] para = {new Integer(5), new Integer(9)}; //准备实参
Integer i = (Integer)m.invoke(obj,para); //调用方法
System.out.println(i);
8.java中的垃圾回收
java语言,较之c语言的较大改进之一就是:它提供了垃圾回收机制。我们不需要自己来管理内存。程序员只负责分配对象,在不负责清理不再使用的对象。但也会存在内存泄漏的问题。
java中的垃圾回收,即gc
怎样发现某个对象,不再使用?较多地使用“引用遍历法”。
java的垃圾回收,一般分为3个阶段的动作:标记,清除,压缩
jvm只在内存紧张的时候,才去做收垃圾的工作。
System.gc()方法,可以催促jvm去回收空间(不能保证一定会执行回收动作)
对象被回收的时候,会去调用protected void finalize()方法
每个jvm的不同实现,都可能会采用独特的垃圾回收办法。并没有哪一种垃圾回收办法具有绝对的优势。这与应用中怎样创建和使用对象有很大的关系。
一般可以通过调整jvm的参数,来优化垃圾回收,可参看jvm的帮助。