------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------
正则表达式:
(1):特殊字符
\\:反斜线
\r:回车
\n:换行
(2):字符类
[abc]:a,b或者c的任意一个。
[^abc]:除了a,b,c以外的字符。
[a-zA-Z]:包括了26个英文字母。
[0-9]:包括了0-9这个10个数字字符。
(3):预定义字符类
. 任意字符
\d 数字[0-9]
\D 非数字[^0-9]
\w 单词字符:[a-zA-Z_0-9]
\W 非单词字符:[^\w]
(4):边界匹配器
^ 行的开头
$ 行的结尾
\b 单词边界
(5):Greedy 数量词
x? 0次或1次
x* 0次或多次
x+ 1次或多次
x{n} 恰好n次
x{n,} 至少n次
x{m,n} 至少m次,但不能超过n次
代码事例:
package day26;
/*
* 正则表达式:符合某种规则的字符串。
*
* 规则?
*
* 举例:校验qq号码.
*1:要求必须是5-15位数字
*2:0不能开头
*/
import java.util.Scanner;
public class RegexTest {
public static void main(String[] args) {
//封装键盘录入
@SuppressWarnings("resource")
Scanner sc = new Scanner(System.in);
System.out.println("请输入QQ号");
String qq = sc.nextLine();
//用正则表达式做
boolean flag = checkQq(qq);
System.out.println(flag);
}
private static boolean checkQq(String qq) {
return qq.matches("[1-9]\\d{5,15}");
}
}
1.概述:是符合某种规则的字符串;是操作字符串的。
2.正则表达式的规则字符:
A,字符:
x字符x 任意字符代表自己本身。
\\ 反斜线字符
\r 回车
\n 换行
B,字符类:
[abc] a,b或c,任意字符一次。
[^abc]任意字符除了a,b或c。
[a-zA-Z] a-z或A-Z,两头的字母包括在内。
[0-9] 任意的数字字符一次。
C,预定义字符类
. 任意的字符。
\d 数字:[0-9]
\w 单词字符:[a-zA-Z 0-9]
单词字符:英文,数字及_
D,边界匹配器
^行的开头。
$行的结尾。
\b单词边界(也就是说这里出现的不能是单词字符)
E,数量词
F,组
3.功能:
A判断功能:
public boolean matches(String regex)
代码事例:
package day26;
import java.util.Scanner;
/*
* 正则表达式的判断功能:
* public boolean matches(String regex)
* 看到这个方法,我们应该知道有一个字符串对象调用该方法,还应该有一个字符串规则。
*/
public class RegexDemo1 {
public static void main(String[] args) {
//校验电话号码
/*
* 移动电话:
* 13436975980
* 13245678901
* 13212345678
* 15812345678
* 15613245678
* 座机:
* 010-88786541
* 0311-8867567
*/
String regex = "1[35]\\d{9}";
@SuppressWarnings("resource")
Scanner sc =new Scanner(System.in);
System.out.println("请输入你的手机号:");
String phone = sc.nextLine();
boolean flag = phone.matches(regex);
System.out.println(flag);
}
}
B切割功能:
public String[] split(String regex)
代码事例:
/*
* 切割功能:
* public String[] split(String regex)
*/
public class RegexDemo2 {
public static void main(String[] args) {
// 切割字符串"aa,bb,cc";
String str = "aa,bb,cc";
String regex = ",";
String[] strArray = str.split(regex);
for (String s : strArray) {
System.out.println(s);
}
System.out.println("**************");
// 切割字符串"aa.bb.cc"
String str2 = "aa.bb.cc";
String regex2 = "\\.";
String[] strArray2 = str2.split(regex2);
for (String s : strArray2) {
System.out.println(s);
}
System.out.println("**************");
// 切割字符串"-1 99 4 23"
String str3 = "-1 99 4 23";
String regex3 = " ";
String[] strArray3 = str3.split(regex3);
for (String s : strArray3) {
System.out.println(s);
}
System.out.println("**************");
// 切割字符串"-1 99 4 23"
String str4 = "-1 99 4 23";
String regex4 = " +";
String[] strArray4 = str4.split(regex4);
for (String s : strArray4) {
System.out.println(s);
}
System.out.println("**************");
// 切割字符串"D:\itcast\20131130\day27\code"
String str5 = "D:\\itcast\\20131130\\day27\\code";
String regex5 = "\\";
String[] strArray5 = str5.split(regex5);
System.out.println("--------------------");
for (String s : strArray5) {
//System.out.println("--------------------");
System.out.println(s);
}
System.out.println("--------------------");
}
}
C替换功能:
public String replaceAll(String regex,String replacement)
用给定的字符串去替换字符串对象中满足正则表达式的字符。
代码事例:
/*
* 替换功能:
* public String replaceAll(String regex,String replacement)
* 用给定的字符串去替换字符串对象中满足正则表达式的字符。
*/
public class RegexDemo3 {
public static void main(String[] args) {
// 用”#”替换叠词:"sdaaafghccccjkqqqqql";
String str = "sdaaafghccccjkqqqqql";
String regex = "(.)\\1+";
String result = str.replaceAll(regex, "#");
System.out.println(result);
System.out.println("--------------------");
// 叠词保留一次
String str2 = "sdaaafghccccjkqqqqql";
// 叠词是在同一个字符串中用\编号来引用
String regex2 = "(.)\\1+";
// 在替换方法中,第二个字符串数据中可以使用$编号的形式来引用第一个正则表达式中的组的内容
String result2 = str2.replaceAll(regex2, "$1");
System.out.println(result2);
System.out.println("--------------------");
// 有些论坛不允许发电话,qq号,银行账号等.
// 把数字用“*”替换wert13245asfklwyoeworewsfd6744232433fafs
String str3 = "wert13245asfklwyoeworewsfd6744232433fafs";
// String regex3 = "\\d+";
String regex3 = "\\d";
String result3 = str3.replaceAll(regex3, "#");
System.out.println(result3);
}
}
D获取功能:
使用模式对象Pattern和匹配器对象Mathcher
代码事例:
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
* 获取功能:
* 使用模式对象Pattern和匹配器对象Mathcher
*/
public class RegexDemo4{
public static void main(String[] args) {
// // 把正则表达式编译成模式对象
// Pattern p = Pattern.compile("a*b");
// // 通过模式对象调用匹配方法获取到匹配器对象
// Matcher m = p.matcher("aaaaab");
// // 调动匹配器对象的判断功能
// boolean b = m.matches();
// System.out.println(b);
// 这道题目我们还可以通过判断功能
// String regex = "a*b";
// String str = "aaaaab";
// boolean flag = str.matches(regex);
// System.out.println(flag);
// 定义规则
String regex = "\\b[a-z]{3}\\b";
String str = "da jia zhu yi le, ming tian bu fang jia, xie xie!";
// 想要获取3个字符组成的单词
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(str);
// public boolean find()
// 判断看是否存在有满足条件的子串
// boolean flag = m.find();
// System.out.println(flag);
// //获取子串
// //public String group()
// String s = m.group();
// System.out.println(s);
while(m.find()){
System.out.println(m.group());
}
}
}
反射
透彻分析反射的基础_Class类
1) 概述
class
Class--->代表一类什么样的事物?
Person类的实例对象
Person p1 = new Person();
Person p2 = new Person();
Data
Math
Java类(Class)的实例对象,对应各个类在内存中的字节码。
概念:
所有的类文件都有共同属性,所以可以向上抽取,把这些共性内容封装成一个类,这个类就叫Class(描述字节码文件的对象)。
如何得到各个字节码对应的实例对象(Class类型)--加载字节码的方式:
1.类名.class
Class cls1 = Data.class//字节码1:表示Data类在内存里的字节码,一旦用到Data类名字,内存里就会出现这个类的字节码,从硬盘里加载进来。
Class cls2 = Person.class//字节码2
2.对象.getClass()
p1.getClass();
3.Class.forName("java.name.String"),主要用于反射
作用:返回字节码文件
返回的方式有两种:
1) 类的字节码已经加载到内存,直接找到字节码,返回;
2) 虚拟机还没有字节码,用类加载器Class.forName()指定类加载器去加载,缓存起来,然后返回字节码;
该方法有两种形式:
Class.forName(String name, boolean initialize, ClassLoader loader)
参数 name表示的是类的全名;initialize表示是否初始化类;loader表示加载时使用的类加载器。
Class.forName(String className)
相当于设置了参数 initialize的值为 true,loader的值为当前类的类加载器。
Class.forName("Foo")等于Class.forName("Foo", true, this.getClass().getClassLoader())
九个预定义Class实例对象
表示八个基本类型和 void。这些类对象由 Java 虚拟机创建,与其表示的基本类型同名,
即 boolean、byte、char、short、int、long、float 和 double。对应获取Class对象的方法为:
Boolean.TYPE, Byte.TYPE,Character.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE
int.class == Integer.TYPE
boolean isPrimitive() :判定指定的 Class 对象是否表示一个基本类型。
数组类型的Class实例对象
boolean isArray():判定此 Class 对象是否表示一个数组类。
Class.isArray();
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如:int[],void
3) 使用
class ReflectTest
{
public static void main(String[] args) throws Exception
{
String str = "fff";
Class c1 = String.class;
Class c2 = str.getClass();
Class c3 = Class.forName("java.lang.String");
System.out.println(c1==c2);//true
System.out.println(c1==c3);//true
System.out.println(c1.isPrimitive());//false
System.out.println(int.class.isPrimitive());//true
//static Class<Integer> TYPE 表示基本类型 int 的 Class 实例。
System.out.println(int.class == Integer.TYPE);//true
//boolean isArray():判定此 Class 对象是否表示一个数组类。
System.out.println(int[].class.isArray());//true
}
}
理解反射的概念
反射的概念
通过Class实例对象,把java类中的各种成分映射成相应的java类。例如,一个java类是用一个Class类的对象来表示,一个类的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包、数组等信息。这些信息就是用相应的类的实例对象来表示,他们是Field、Method、Contructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象,
Package getPackage() :获取此类的包。返回的是Package类的实例对象;
Method getMethod(String name, Class<?>... parameterTypes) : 返回一个 Method 对象,代表某个类中的一个方法,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Constructor<T> getConstructor(Class<?>... parameterTypes) :返回一个 Constructor 对象,代表某个类中的一个构造方法,它反映此 Class 对象所表示的类的指定公共构造方法。
.............
问题:
得到这些实例对象后有什么用?怎么用?
构造方法的反射应用
1) 概述
构造方法的反射应用
Constructor类代表某个类中的一个构造方法
得到某个类所有构造方法:
Constructor[] ct = Class.forName("java.lang.String ").getConstructors();
得到某个类的一个构造方法:
//获得String类的某一个构造方法,只获得参数为一个,且为StringBuffer类型的构造方法
Constructor ct1 = Class.forName(java.lang.String).getConstructor(StringBuffer.class);//获得方法时要用StringBuffer.class类型
创建指定构造函数的实例对象(三步:classàConstructorànew Object)
通常方式:String str = new String(new StringBuffer("abc"));
反射方式:String str1 = (String)ct1.newInstance(new StringBuffer("abc"));
//调用获得的方法时要用到上面相同类型的实例对象
创建默认构造函数的实例对象(两步:classà new Object) Class.newInstance()方法
例子:String obj = (String)Class.forName("java.lang.String").newInstance();
该方法内部先得到默认构造方法(空参数的构造方法),然后用该构造方法创建实例对象。
该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象(Constructor对象)。
反射会导致程序性能严重下降
2) 使用
class ReflectTest
{
public static void main(String[] args) throws Exception
{
//用反射实现类似功能:new String(new StringBuffer("asd"));
Constructor con1 = String.class.getConstructor(StringBuffer.class);//编译器只看变量的定义(=左边),不看代码的执行(=右边),只是将右边编程二进制
//用构造方法new一个实例,此时创建的实例为Object类型,需要强转
String str1 = (String)con1.newInstance(new StringBuffer("asdf"));
System.out.println("str1="+str1);
String str2 = (String)Class.forName("java.lang.String").newInstance();
str2="123";
System.out.println("str2="+str2);
}
}
成员变量的反射
1) 概述
Field类:代表某个类中的一个成员变量
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?
类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?
所以字段FieldX代表的是x的定义,而不是具体的x变量
2) 使用(获取指定成员变量)
参考类:
public class ReflectPoint {
private int x;
public int y;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
public static void main(String[] args)
{
System.out.println("我是一个参考类");
}
}
应用类
class ReflectTest
{
public static void main(String[] args) throws Exception
{
ReflectPoint r1 = new ReflectPoint(8,9);
Field fieldy = r1.getClass().getField("y");//获得其他类的成员变量,先从其他类中得到字节码,字节码中有成员变量
/*fieldy的值?
fieldy只代表这个类字节码上的变量,没有对应到对象身上;每一个对象身上都有fieldy,要用它去取某个对象上的值
*/
//取出变量在某个对象上的值
System.out.println("fieldy在r1对象上的值 = "+fieldy.get(r1));
//Field fieldx = r1.getClass().getField("x");//x是私有的,fieldx变量看不见,不能获得此变量。
Field fieldx = r1.getClass().getDeclaredField("x");//x是私有的,只要是声明过的,fieldx都看得见,但是不能访问私有的。
fieldx.setAccessible(true);//设置允许访问,暴力反射
System.out.println("fieldx在r1对象上的值 = "+fieldx.get(r1));
}
}
3) 什么是暴力反射?
通过反射获取一个具体的变量(构成函数,方法)时,该变量在所属的类中是私有成员变量,不能直接访问,需要先获取该变量getDeclaredField(),然后通过 (setAccessible(true))方法得到他的访问权利。
成员变量反射的综合案例(获取部分成员变量)
Class<?> getType() :返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型。
Object get(Object obj) :返回指定对象上此 Field 表示的字段的值。
void set(Object obj, Object value) :将指定对象变量上此 Field 对象表示的字段设置为指定的新值
请看如下示例:
//参考类
class ReflectPoint1 {
public String str1;
public String str2;
public String str3;
public ReflectPoint1(String str1, String str2, String str3) {
super();
this.str1 = str1;
this.str2 = str2;
this.str3 = str3;
}
//要打印对象的属性值,需覆盖toString()方法
public String toString()
{
return str1+";"+str2+";"+str3;
}
}
//应用类
class ReflectTest
{
public static void main(String[] args) throws Exception
{
ReflectPoint1 rp = new ReflectPoint1("bababa","abdgfbbaaabbbb","string");
strchange(rp);
System.out.println(rp);
}
public static void strchange(Object obj)throws Exception
{
Field[] fi = obj.getClass().getFields();
for(Field field:fi)
{
if(field.getType() == String.class);//字节码的比较用==,比较的都是一份字节码;
{
String strolds = (String)field.get(obj);
String strnews = strolds.replace('b','a');
field.set(obj,strnews);
}
}
}
}
成员方法的反射
1) 概述
成员方法的反射
Method类代表某个类中的一个成员方法;
得到类中的某一个方法:
Method getMethod(String name, Class<?>... parameterTypes) :返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
name - 方法名
parameterTypes - 参数列表
Method method = String.class.getMethod("charAt", int.class);
调用方法:
通常方式:str.charAt(1);
反射方式:method.invoke(str,1);
如果invoke(null,1),表示该Method对象对应的是一个静态的方法
JDK1.4和JDK1.5的invoke方法的区别
1.4:public Object invoke(Object obj,Object[] args)
将数组作为参数传递给invoke方法,
数组中的每个元素分别对应被调用方法中的一个参数,
所以可写成method.invoke("str",new object[]{1})形式
1.5:public Object invoke(Object obj,Object... args)传入的参数为可变参数
2) 使用
class ReflectTest
{
public static void main(String[] args) throws Exception
{
//Object invoke(Object obj, Object... args):对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
String str = "abcd";
//用反射实现str.charAt(1)功能
Method m = String.class.getMethod("charAt", int.class);
System.out.println(m.invoke(str,1));//jdk1.5 传入可变参数
System.out.println(m.invoke(str,new Object[]{2}));//jdk1.4传入数组参数
//m.invoke(null,1),表示不用对象就能调用的方法,静态方法
}
}
对接收数组参数的成员方法进行反射
1) 概述
对接收数组参数的成员方法进行反射
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
调用方法
通常方式:类名.main(new String[]{"ttt","www","qq"});
反射方式:
String classstartname = args[0];
Method mainmethod = Class.forName(classstartname).getMethod("main", String[].class);
mainmethod.invoke(null, new Object[]{new String[]{"11","22"}});
为什么要用反射去调用?
程序中已知要调用main方法,但是不知道哪个类的main方法,要通过传进来的参数,确定类名,
然后调用传进来的类的main方法。
为什么会出现参数个数异常呢?
通过反射的方法调用main方法时,为invoke方法传递参数的方式是:
JDK1.5:整个数组是一个参数,JDK1.4:数组中每一个元素对应一个参数,
当把字符串数组作为参数传递给invoke方法时,JDK1.5肯定要兼容JDK1.4语法,
会按JDK1.4的去处理,即把数组打散成若干单独参数。所以在给main方法传递参数时,
不能使用mainmethod.invoke(null, new String[]{"11","22"});,会出现参数类型不对问题;
解决办法:
1.mainmethod.invoke(null, new Object[]{new String[]{"11","22"}});
2.mainmethod.invoke(null, (Object)new String[]{"11","22"});
2) 使用
class ReflectTest
{
public static void main(String[] args) throws Exception
{
//实现在此程序中调用testArguments类的main方法
//通常方式
testArguments.main(new String[]{"11","22"});
//反射方式
//假设传进main方法的参数中args[0]就是一个类名,且有main方法
String classstartname = args[0];
Method mainmethod = Class.forName(classstartname).getMethod("main", String[].class);
/*mainmethod.invoke(null, new String[]{"11","22"});
参数个数异常,本来是接收一个数组参数,JDK1.5为了兼容JDK1.4,会将数组拆一次,拆成一个个(多个)参数*/
//解决办法:1.将数组参数再次封装成数组 ; 2.将数组参数强转成Object对象,就不拆包了;
mainmethod.invoke(null, new Object[]{new String[]{"11","22"}});
mainmethod.invoke(null, (Object)new String[]{"11","22"});
}
}
class testArguments//按f2出现完整的类名,将完整的类名复制到run configurations界面
//中的Arguments界面的program arguments中,用来配置运行类的main方法参数
{
public static void main(String args[])
{
for(String str:args)
{
System.out.println(str);
}
}
}
数组与Object的关系及其反射类型
1) 概述
数组的反射
数组元素类型相同,维度相同,得到的字节码就是同一份,属于同一个类型,即具有相同的Class实例对象;
基本数据类型的一维数组可以被当做Object类型使用,不能当做Object[]类型使用,
非基本数据类型的一维数组既可以当做Object类型使用,也可当做Object[]类型使用
Class的getName()方法:
以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称
如果此类对象表示一个数组类,则名字的内部形式为:表示该数组嵌套深度的一个或多个 '[' 字符加元素类型名。
元素类型名的编码如下:
Element Type Encoding
boolean Z
byte B
char C
class or interface Lclassname;
double D
float F
int I
long J
short S
类或接口名 classname 是上面指定类的二进制名称。
String.class.getName():returns "java.lang.String"
(new int[3][4][5][6][7][8][9]).getClass().getName():returns "[[[[[[[I"
Class的getSuperclass()方法:
Class<? super T> getSuperclass() :返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超(父)类的 Class。
2) 使用
class ReflectTest
{
public static void main(String[] args) throws Exception
{
int a0 = 3;
int[] a1 = new int[]{1,2};
int[] a2 = new int[]{3,4,5};
int[][] a3 = new int[][]{{4,4},{3,3},{2,2,2}};
String[] a4 = new String[]{"a","b","c"};
sop(a1.getClass() == a2.getClass());
sop(a1.getClass().getName());//[I
sop(a1.getClass().getSuperclass().getName());//java.lang.Object
sop(a4.getClass().getSuperclass().getName());//java.lang.Object
sop(a2.getClass().getSuperclass().getName());//java.lang.Object
sop(a3.getClass().getSuperclass().getName());//java.lang.Object
sop(String.class.getSuperclass().getName());
Object o0 = a0;
Object o1 = a1;
Object o2 = a2;
Object o3 = a3;
Object o4 = a4;
sop(o0);
sop(o1);
sop(o2);
sop(o3);
sop(o4);
//Object[] b0 = a0;编译不通过,因为a0是个基本数据类型,是个对象
//Object[] b1 = a1;编译不通过,因为a1,a2是基本数据类型的一维数组,是个对象,不是对象数组
//Object[] b2 = a2;
Object[] b3 = a3;//编译通过,因为a3是基本数据类型的二维数组(数组的数组),表示有一个数组,数组里面装的是int[](Objet),是个对象数组
Object[] b4 = a4;//编译通过,因为String是Object,String[]等于对象数组
//可得出:int(基本数据类型)不是对象,String是对象,所以前者不能转成Object数组,后者可以
/*结果没有意义
*sop(a1);
sop(a4);*/
//用Arrays工具类对数组进行操作
//JDK1.5:public static <T> List<T> asList(T... a)
//JDK1.4:public static List asList(Object[] a)
sop(Arrays.asList(a1));//JDK1.5处理
sop(Arrays.asList(a3));//JDK1.4处理+JDK1.5处理
sop(Arrays.asList(a4));//JDK1.4处理
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
数组的反射应用
1) 概述
数组的反射应用
获取,设置,数组的值,获取数组的长度,怎么做?
通过java.lang.reflect.Array类中的方法:
static Object get(Object array, int index)
返回指定数组对象中索引组件的值。
static int getLength(Object array)
以 int 形式返回指定数组对象的长度。
static void set(Object array, int index, Object value)
将指定数组对象中索引组件的值设置为指定的新值。
怎么得到数组中的元素类型?没有办法
int [] a = new int[3]
怎么知道a的数组类型?没有办法
因为
Object[] a =new Object[]{"a",1}
a[0].getClass.getName();
a[0].getClass.getName();
只能得到每一个具体元素的类型,不能得到整个数组的元素类型
2) 使用
class ReflectTest
{
public static void main(String[] args) throws Exception
{
int a0 = 3;
int[] a1 = new int[]{1,2};
int[] a2 = new int[]{3,4,5};
int[][] a3 = new int[][]{{4,4},{3,3},{2,2,2}};
String[] a4 = new String[]{"a","b","c"};
sop(a2);
sop(a3);
sop(a4);
sop("asd");
}
public static void sop(Object obj)//打印数组或基本数据类型
{
Class c = obj.getClass();
if(c.isArray())
{
int len = Array.getLength(obj);//通过Array类访问数组,获取数组长度
for(int i=0;i<len;i++)
{
System.out.println(Array.get(obj, i));//返回指定数组对象中索引组件的值。
}
}
else
System.out.println(obj);
}
}
ArrayList_HashSet的比较及Hashcode分析
1) 概述
反射的综合应用--ArrayList_HashSet的比较及Hashcode分析
哈希算法
哈希算法提高从集合中查找元素的效率,这种方式将集合分成若干区域,每个要存入哈希集合的对象可以计算出一个哈希码,
可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储在哪个区域。
面试题1--HashCode()方法的作用?
返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
当从hashset集合中查找某个对象时,java系统首先调用对象的hashcode()方法获得该对象的哈希码,然后根据哈希码,
找到相应的存储区域,最后取出该存储区域内的每个元素与该对象进行equals方法比较,不用遍历集合中的所有元素,就可以得到结论。
面试题2--内存溢出,内存泄露的例子?
当一个对象被存储进HsahSet集合中后,就不能修改这个对象中的那些参与计算哈希值得字段了,否则,对象修改后的哈希值就不同了,
在这种情况下,去哈希集合中检索对象时,就找不到原来的对象了。这也会导致无法从hashset集合中单独删除当前对象,
从而导致内存泄露。
递归,没有结束标记;
提示:
一个类的两个实例对象用equals()方法比较的结果相等时,他们的哈希码也必须相等,但是,
equals(java.lang.Object) 方法比较结果不相等的对象可以有相同的哈希码,
或者哈希码相同的两个对象,调用equals()方法比较结果可以不等。
2) 使用
类1
public class ReflectPoint {
private int x;
public int y;
public String str1="aa";
public String str2="ss";
public String str3="dd";
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ReflectPoint other = (ReflectPoint) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
//要打印对象的属性值,需覆盖toString()方法
public String toString()
{
return str1+";"+str2+";"+str3;
}
}
类2
class ReflectTest
{
public static void main(String[] args) throws Exception
{
Collection c = new HashSet();//new ArrayList();//
ReflectPoint r1 = new ReflectPoint(2,2);
ReflectPoint r2 = new ReflectPoint(3,3);
ReflectPoint r3 = new ReflectPoint(2,2);
c.add(r1);
c.add(r2);
c.add(r3);
c.add(r1);
r1.y = 8;
c.remove(r1);//将r1的y值更改后,哈希值改变,存储区域改变,remove()方法在原本区域里找不到r1的y值了
//这样原本该删除的对象没有删掉,又新增对象的话,日积夜累,就会导致内存溢出。
sop(c.size());
/*list集合是有序可重复的,存的是对象的引用;
set集合是无序不可重复的,默认equals()比较的是哈希值,hashcode值是根据内存地址值算出来的,
要让r1与r3完全相等,必须同时复写equals(),hashcode()方法(用eclipse自带生成)*/
}
public static void sop(Object obj)//打印数组或基本数据类型
{
Class c = obj.getClass();
if(c.isArray())
{
int len = Array.getLength(obj);//通过Array类访问数组,获取数组长度
for(int i=0;i<len;i++)
{
System.out.println(Array.get(obj, i));//返回指定数组对象中索引组件的值。
}
}
else
System.out.println(obj);
}
}
反射的作用--实现框架功能
1) 概述
框架与工具类
从框架内涵的角度:一个框架是一个可复用设计,它是由一组抽象类及其实例间协作关系来表达的。
从框架用途的角度:一个框架是在一个给定的问题领域内,一个应用程序的一部分设计与实现。
比如:自己做房子给用户住,用户自己安装门,我做的房子就是框架,用户需要使用我的框架,把门插进我提供的框架中。
框架与工具类有区别,工具类被用户的类调用,而框架是调用用户提供的类。
框架要解决的核心问题
我在写框架时,我的框架程序怎样能调用到你以后写的程序呢?
因为在写框架程序时,无法知道要被调用的类名,所以,
在程序中无法直接new某个类的实例对象了,而要用反射的方式(Class.forName(classname).new Instance)来做。
使用步骤:
1、先在程序中直接用new语句创建ArrayList和HashSet的实例对象,
2、然后创建一个properties文件,配置内容: className=java.util.HashSet //className=java.util.ArrayList
3、在程序中加载进配置文件,用配置文件加反射的方式创建ArrayList和HashSet的实例对象
提示
IO流对象.close();的底层含义是:先把widows的widow(系统资源,物理资源)关掉,然后由java VM回收本IO流对象
2) 使用
配置文件的获取方式
1.通过Properties得到资源文件
可以读写配置文件,因为有InputStream和OutputStream
/*资源文件放在project文件夹中,
* 要用完整的路径,但完整的路径不是硬编码,而是运算出来的。
* javaweb中getRealPath()方法动态的得到eclipse的安装目录,再拼上配置文件的路径
*/
InputStream ips = new FileInputStream("config.properties");//此处用的是相对路径,不符合开发。
实际开发中不这样放置配置文件,在javaweb中通过getRealpath()方法得到eclipse的真实安装完整路径,在拼接上配置文件的内置路径
2.通过类加载器得到资源文件的方式(不能替代上面的方式,此方式常用)
所谓类加载器: 就是加载类的工具.类加载器也是java类。类加载器负责读取 Java 字节代码到java虚拟机中,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。
参考地址:http://www.ibm.com/developerworks/cn/java/j-lo-classloader/
开发中一般将配置文件放到classpath路径下(用户目录下,即全部存放的是.class文件的文件夹)
配置文件放到当前包里,保存的时候会直接保存在classpath路径下,即用户目录(.class目录)中生成该配置文件。
通过类加载器获取资源文件,只读。
格式:
/*1.资源文件放在同个包里,保存的时候eclipse会自动将配置文件放到classpath目录下
* 通过类加载器加载,在classpath路径指定目录下,逐一查找要加载的文件.
* 该方式获得资源文件首先要获得class文件,然后获得类加载器,然后才获得资源
* */
InputStream ips = ReflectTest.class.getClassLoader().getResourceAsStream("com/config.properties");
//2.资源文件放在另一个子包里,通过类文件,直接获得资源文件,用相对路径
InputStream ips = ReflectTest.class.getResourceAsStream("resources/config.properties");
//3.资源文件放在另一个子包里,通过类文件,直接获得资源文件,用绝对路径
InputStream ips = ReflectTest.class.getResourceAsStream("/com/resources/config.properties");
类1
public class ReflectPoint {
private int x;
public int y;
public String str1="aa";
public String str2="ss";
public String str3="dd";
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ReflectPoint other = (ReflectPoint) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
//要打印对象的属性值,需覆盖toString()方法
public String toString()
{
return str1+";"+str2+";"+str3;
}
}
类2
class ReflectTest
{
public static void main(String[] args) throws Exception
{
//类名.class.getResourceAsStream(String name) :在classpath路径下查找具有给定名称的资源。
//类名.class.getClassLoader().getResourceAsStream(String name)在classpath路径下查找,返回读取指定资源的输入流。
/*资源文件放在同个包里,保存的时候eclipse会自动将配置文件放到classpath目录下
* 通过类加载器加载,在classpath路径指定目录下,逐一查找要加载的文件.
* 该方式获得资源文件首先要获得class文件,然后获得类加载器,然后才获得资源
* */
InputStream ips = ReflectTest.class.getClassLoader().getResourceAsStream("com/config.properties");
//资源文件放在另一个子包里,通过类文件,直接获得资源文件,用相对路径
//InputStream ips = ReflectTest.class.getResourceAsStream("resources/config.properties");
//资源文件放在另一个子包里,通过类文件,直接获得资源文件,用绝对路径
//InputStream ips = ReflectTest.class.getResourceAsStream("/com/resources/config.properties");
/*资源文件放在project文件夹中,
* 要用完整的路径,但完整的路径不是硬编码,而是运算出来的。
* javaweb中getRealPath()方法动态的得到eclipse的安装目录,再拼上配置文件的路径
* */
//InputStream ips = new FileInputStream("config.properties");//此处用的是相对路径,不符合开发。
Properties pp = new Properties();
pp.load(ips);
ips.close();//先把widows的widow(系统资源,物理资源)关掉,然后java VM回收本对象
String className = pp.getProperty("className");
//通过反射,传进一个类名,得到一个默认构造方法生成的对象
Collection c = (Collection)Class.forName(className).newInstance();
//Collection c = new HashSet();//new ArrayList();//
ReflectPoint r1 = new ReflectPoint(2,2);
ReflectPoint r2 = new ReflectPoint(3,3);
ReflectPoint r3 = new ReflectPoint(2,2);
c.add(r1);
c.add(r2);
c.add(r3);
c.add(r1);
//r1.y = 8;
//c.remove(r1);
sop(c.size());
}
public static void sop(Object obj)//打印数组或基本数据类型
{
Class c = obj.getClass();
if(c.isArray())
{
int len = Array.getLength(obj);//通过Array类访问数组,获取数组长度
for(int i=0;i<len;i++)
{
System.out.println(Array.get(obj, i));//返回指定数组对象中索引组件的值。
}
}
else
System.out.println(obj);
}
}
由内省引出JavaBean的讲解
内省(IntroSpector)à主要对javabean(特殊的Java类)进行操作
1) 什么是内省?
内省是Java语言对Bean类属性、事件的一种缺省处理方法。例如类A中有属性name,那我们可以通过getName,setName来得到其值或者设置新的值。通过getName/setName来访问name属性,这就是默认的规则。Java中提供了一套API用来访问某个属性的getter/setter方法,这套API就是内省。通过这些API可以使你不需要了解这个规则,这些API存放于包java.beans中。
2) 什么是Javabean?
符合特殊规则的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”(Value Object),或“VO”。方法比较少。这些信息储存在类的私有变量中,通过set()、get()获得。
3) Javabean的属性:
去掉set和get前缀,剩余部分就是属性名,如果剩余部分的第二个字母的小写,则把剩余部分的首字母改成小的。
总之,一个类被当做javabean使用时,它的属性是根据方法名推断出来的,根本看不到Java类内部的成员变量。
4) Javabean的好处?
一个符合JavaBean特点的类可以当作普通类一样进行使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean!好处如下:
在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作,别人都这么用和要求这么做,那你就没什么挑选的余地!
JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要你自己去通过getX方法来访问私有的x,怎么做,有一定难度吧?用内省这套api操作JavaBean比用普通类的方式更方便。
对JavaBean的简单内省操作
操作步骤:
1) 创建javabean对象
2) 创建属性名
3) 描述 Java Bean 通过一对存储器方法导出的一个属性。
4) 获取属性的get,set方法
5) 调用获取的get,set方法
public class IntroSpectorTest
{
public static void main(String[] args)throws Exception
{
//1)创建javabean对象
ReflectPoint p1 = new ReflectPoint(66,88);
//2)创建属性名
String ProName = "x";
/*1:
PropertyDescriptor pd = new PropertyDescriptor(ProName, p1.getClass());
Method m= pd.getReadMethod();
Object valX= m.invoke(p1);*/
//调用重构方法,获取属性值
Object valX= getPro(p1,ProName);
System.out.println(valX);
/*2:
Method m1= pd.getWriteMethod();
m1.invoke(p1,886);
*/
//调用重构方法,设置属性值
setPro(p1,ProName,886);
System.out.println(p1.getX());
}
//将上面的步骤1进行方法重构
public static Object getPro(Object obj,String name) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
{
//3)描述 Java Bean 通过一对存储器方法导出的一个属性。
PropertyDescriptor pd = new PropertyDescriptor(name, obj.getClass());
//4)获取属性的get方法
Method m= pd.getReadMethod();
//5)调用获取的get方法
Object valX= m.invoke(obj);
return valX;
}
//将上面的步骤2进行方法重构
public static void setPro(Object obj,String name,Object value) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
{
PropertyDescriptor pd = new PropertyDescriptor(name, obj.getClass());
//4)获取属性的set方法
Method m= pd.getWriteMethod();
m.invoke(obj,value);
}
}
对JavaBean的复杂内省操作
注意:到Bean得Info最好采用“obj.getClass()”方式,而不要采用“类名.class”方式,这样程序更通用。
public static Object getPro(Object obj,String name) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
{
/*//3)描述 Java Bean 通过一对存储器方法导出的一个属性。
PropertyDescriptor pd = new PropertyDescriptor(name, obj.getClass());
//4)获取属性的get方法
Method m= pd.getReadMethod();
//5)调用获取的get方法
Object valX= m.invoke(obj);
return valX;*/
//将上面的步骤用复杂操作
//在 Java Bean 上进行内省,了解其所有属性、公开的方法和事件。
BeanInfo beaninfo = Introspector.getBeanInfo(obj.getClass());
//获得 所有beans PropertyDescriptor
PropertyDescriptor[] pds = beaninfo.getPropertyDescriptors();
Object retVal = null;
//遍历beans PropertyDescriptor
for(PropertyDescriptor ps:pds)
{
if(ps.getName().equals(name))
{
Method m = ps.getReadMethod();
retVal = m.invoke(obj);
break;
}
}
return retVal;
}
使用BeanUtils工具包操作JavaBean
由上述可看出,内省操作非常的繁琐,所以所以Apache开发了一套简单、易用的API来操作Bean的属性——BeanUtils工具包。
BeanUtils工具包:下载:http://commons.apache.org/beanutils/ 注意:应用的时候还需要一个logging包 http://commons.apache.org/logging/
1. 获得属性的值,例如,BeanUtils.getProperty(userInfo,"userName"),返回字符串
2. 设置属性的值,例如,BeanUtils.setProperty(userInfo,"age",8),参数是字符串或基本类型自动包装。设置属性的值是字符串,获得的值也是字符串,不是基本类型。
3. 使用BeanUtils工具类,实现Property和Map的相互转换
4. PropertyUtils工具类
和BeanUtils不同在于,运行getProperty、setProperty操作时,没有类型转换,使用属性的原有类型或者包装类。由于age属性的数据类型是int,所以方法PropertyUtils.setProperty(userInfo, "age", "8")会爆出数据类型不匹配,无法将值赋给属性。
5. BeanUtils的特点:
1). 对基本数据类型的属性的操作:在WEB开发、使用中,录入和显示时,值会被转换成字符串,但底层运算用的是基本类型,这些类型转到动作由BeanUtils自动完成。
2). 对引用数据类型的属性的操作:首先在类中必须有对象,不能是null,例如,private Date birthday=new Date();。操作的是对象的属性而不是整个对象,例如,BeanUtils.setProperty(userInfo,"birthday.time",111111);
public class IntroSpectorTest
{
public static void main(String[] args)throws Exception
{
//1)创建javabean对象
ReflectPoint p1 = new ReflectPoint(66,88);
//2)创建属性名
String ProName = "x";
/*1:
PropertyDescriptor pd = new PropertyDescriptor(ProName, p1.getClass());
Method m= pd.getReadMethod();
Object valX= m.invoke(p1);*/
//调用重构方法,获取属性值
Object valX= getPro(p1,ProName);
System.out.println(valX);
//使用工具类BeanUtils获取属性值,前提是要引入另外一个logging日志开发包,即需要导入两个jar包
System.out.println(BeanUtils.getProperty(p1, ProName));
/*2:
Method m1= pd.getWriteMethod();
m1.invoke(p1,886);
*/
//调用重构方法,设置属性值
setPro(p1,ProName,886);
System.out.println(p1.getX());
//1.使用工具类,设置属性值,以字符串的形式操作javabean;
BeanUtils.setProperty(p1, ProName, "888");//设置属性的值的类型为字符串,但是该属性的类型为int;可以实现自动类型转换;
System.out.println(p1.getX());
//2.使用工具类设置属性的属性的值,即属性的级联操作,但是一级属性需要new一个实例
BeanUtils.setProperty(p1, "birthday.time", "111");
//获取属性的值
System.out.println(p1.getBirthday());
//获取属性的属性的值
System.out.println(BeanUtils.getProperty(p1, "birthday.time"));
//3.使用工具类,实现Property和Map的相互转换
/*Map map = {name:"xiaoming",age:26};//java 7的新特性
BeanUtils.setProperty(map, "name", "lisi");*/
//4.PropertyUtils工具类
PropertyUtils.setProperty(p1, "x", 99);//以属性本身的类型操作javabean;
System.out.println(PropertyUtils.getProperty(p1, "x"));
}
//将上面的步骤1进行方法重构
public static Object getPro(Object obj,String name) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
{
//3)描述 Java Bean 通过一对存储器方法导出的一个属性。
PropertyDescriptor pd = new PropertyDescriptor(name, obj.getClass());
//4)获取属性的get方法
Method m= pd.getReadMethod();
//5)调用获取的get方法
Object valX= m.invoke(obj);
return valX;
}
//将上面的步骤2进行方法重构
public static void setPro(Object obj,String name,Object value) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
{
PropertyDescriptor pd = new PropertyDescriptor(name, obj.getClass());
//4)获取属性的set方法
Method m= pd.getWriteMethod();
m.invoke(obj,value);
}
}
------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------