@liayun
2016-06-24T16:47:05.000000Z
字数 17488
阅读 1526
java基础加强
Java类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。
Class类代表Java类,它的各个实例对象又分别对应什么呢?
对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型就是Class。
学习反射,首先就要明白Class这个类。写如下代码进行对比理解:
Person p1 = new Person("zhangsan");
Person p2 = new Person("lisi");
Class cls = new Class(); // 没有这种做法
Class x1 = Person.class; // Person类在内存里的字节码
Class x2 = Date.class; // Date类在内存里的字节码
如何得到各个字节码对应的实例对象( Class类型)
Sytem.class
new Date().getClass()
Class.forName("java.util.Date")
得到一个类的字节码有两种情况:
字节码只被装载一次,而它构造的实例对象的构造方法被调用了多次,用如下代码更进一步说明Class的实例是什么?是一份字节码,一个类在虚拟机中通常只有一份字节码。
String str1 = "abc";
Class cls1 = str1.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");
System.out.println(cls1 == cls2); // true
System.out.println(cls1 == cls3); // true
九个预定义Class实例对象
通过查看JDK API帮助文档,我们可知基本的Java类型(boolean、byte、char、short、int、long、float和double)和关键字void也表示为Class对象。
public boolean isPrimitive()
:判定指定的Class对象是否表示一个基本类型。
System.out.println(cls1.isPrimitive()); // 不是基本类型的字节码,false
System.out.println(int.class.isPrimitive()); // true
注意:int.class == Integer.TYPE
,TYPE代表包装类型所包装的基本类型的字节码。
System.out.println(int.class == Integer.class); // false,各有各的字节码
System.out.println(int.class == Integer.TYPE); // true
数组类型的Class实例对象
public boolean isArray()
:判定此Class对象是否表示一个数组类。
System.out.println(int[].class.isPrimitive()); // false
System.out.println(int[].class.isArray()); // true
总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[],void…
一个奇怪的问题:加载了字节码,并调用了其getMethods之类的方法,但是没有看到类的静态代码块被执行,只有在第一个实例对象被创建时,这个静态代码才会被执行。准确的说,静态代码块不是在类加载时被调用的,而是第一个实例对象被创建时才执行的。
一个类有多个组成部分,例如:成员变量,方法,构造方法等。反射就是加载类,并解剖出类的各个组成部分。
反射就是把Java类中的各种成分映射成相应的java类。例如,一个Java类中用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。
Constructor类代表某个类中的一个构造方法。Constructor对象代表一个构造方法,大家觉得Constructor对象上会有什么方法呢?得到名字,得到所属于的类,产生实例对象。
得到某个类所有的构造方法:
Constructor[] constructors = Class.forName("java.lang.String").getConstructors();
得到某一个构造方法:
Constructor constructor1 = String.class.getConstructor(StringBuffer.class); // 获得方法时要用到类型
一个类有多个构造方法,用什么方式可以区分清楚想得到其中的哪个方法呢?
答:根据参数的个数和类型,例如Class.getMethod(name, Class... args)
中的args参数就代表所要获取的那个方法的各个参数的类型的列表。重点:参数类型用什么方式表示?用Class实例对象。
创建实例对象
通常情况下我们是这样做的:
String str = new String(new StringBuffer("abc"));
而用反射是这样做的:
Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
String str2 = (String)constructor1.newInstance(/*"abc"*/new StringBuffer("abc")); // 调用获得的方法时要用到上面相同类型的实例对象
综合练习:反射类无参、有参、私有的构造函数,创建类的对象。
先描述一个Person类,如下:
public class Person {
public String name = "aaaa";
public Person() {
System.out.println("person");
}
public Person(String name) {
System.out.println(name);
}
public Person(String name, int password) {
System.out.println(name+":"+password);
}
private Person(List list) {
System.out.println("list");
}
}
解剖类的构造函数,创建类的对象。
反射构造函数:public Person() {...}
public void test1() throws Exception {
Class clazz = Class.forName("cn.itcast.reflect.Person");
Constructor c = clazz.getConstructor(null);
Person p = (Person) c.newInstance(null);
System.out.println(p.name);
}
反射构造函数:public Person(String name) {...}
public void test2() throws Exception {
Class clazz = Class.forName("cn.itcast.reflect.Person");
Constructor c = clazz.getConstructor(String.class);
Person p = (Person) c.newInstance("xxxxx");
System.out.println(p.name);
}
反射构造函数:public Person(String name, int password) {...}
public void test3() throws Exception {
Class clazz = Class.forName("cn.itcast.reflect.Person");
Constructor c = clazz.getConstructor(String.class, int.class);
Person p = (Person) c.newInstance("xxxx", 12);
System.out.println(p.name);
}
反射构造函数:private Person(List list) {...}
public void test4() throws Exception {
Class clazz = Class.forName("cn.itcast.reflect.Person");
Constructor c = clazz.getDeclaredConstructor(List.class);
/*
* 面试题:私有的东西外面可以访问吗?
* 答:不可以,私有的东西只能被内部访问,但是反射可以做到这一点
*/
c.setAccessible(true); // 暴力反射
Person p = (Person) c.newInstance(new ArrayList());
System.out.println(p.name);
}
Class.newInstance()方法
该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象,所以利用此种方式创建类对象时,类必须有一个无参的构造函数。该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。
String obj = (String)Class.forName("java.lang.String").newInstance();
Field类代表某个类中的一个成员变量。
ReflectPoint类的定义如下:
public class ReflectPoint {
private int x;
public int y;
public ReflectPoint(int x, int y) { // 快捷键——Alt+Shift+S + O
super();
this.x = x;
this.y = y;
}
}
以下代码会输出什么?
ReflectPoint pt1 = new ReflectPoint(3, 5);
Field fieldY = pt1.getClass().getField("y");
// fieldY的值是多少?是5,错!fieldY不是对象身上的变量,而是类上的,要用它去取某个对象上对应的值
System.out.println(fieldY.get(pt1)); // 5
问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?
答:类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldY代表的是y的定义,而不是具体的y变量。
以下代码:
Field fieldX = pt1.getClass().getField("x");
发现会报异常:java.lang.NoSuchFieldException: x
,究其原因是该字段是用private
修饰的,那要获取该对象又该如何?可这样做:
Field fieldX = pt1.getClass().getDeclaredField("x"); // 只要是声明的字段,不管私有还是公有
接着去取pt1对象上对应的值:
System.out.println(fieldX.get(pt1));
发现还是报错,如果非要获取,怎么办,可用暴力反射:
fieldX.setAccessible(true); // 暴力反射
System.out.println(fieldX.get(pt1)); // 3
我把自己的变量定义成private,就是不想让人家访问,可是,现在人家用暴力反射还是能够访问我,这说不通啊,能不能让人家用暴力反射也访问不了我。
首先,private主要是给javac编译器看的,希望在写程序的时候,在源代码中不要访问我,是帮组程序员实现高内聚、低耦合的一种策略。你这个程序员不领情,非要去访问,那我拦不住你,由你去吧。同样的道理,泛型集合在编译时可以帮助我们限定元素的内容,这是人家提供的好处,而你非不想要这个好处,怎么办?绕过编译器,就可以往集合中存入另外类型了。
练习一:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。
解:
如有ReflectPoint类定义如下:
public class ReflectPoint {
private int x;
public int y;
public String str1 = "ball";
public String str2 = "basketball";
public String str3 = "itcast";
public ReflectPoint(int x, int y) { // 快捷键——Alt+Shift+S + O
super();
this.x = x;
this.y = y;
}
@Override
public String toString() {
return str1 + ":" + str2 + ":" + str3;
}
}
定义一个方法完成该练习:
private static void changeStringValue(Object obj) throws Exception {
Field[] fields = obj.getClass().getFields();
for(Field field : fields) {
// if(field.getType().equals(String.class))
if(field.getType() == String.class) { // 因为是同一份字节码,所以用==比较(字节码用==比较)
String oldValue = (String)field.get(obj);
String newValue = oldValue.replace('b', 'a');
field.set(obj, newValue);
}
}
}
练习二:利用Field分别设置和获取公有、私有的属性。
先描述一个Person类,如下:
public class Person {
public String name = "aaaa"; // 字段反射出来是为了封装数据
private int password = 123;
private static int age = 23;
}
反射字段
反射字段:public String name = "aaaa";
public void test1() throws Exception {
Person p = new Person();
Class clazz = Class.forName("cn.itcast.reflect.Person");
Field f = clazz.getField("name");
// 获取字段的值
System.out.println(f.get(p));
}
更为严谨一点的做法是要先获取反射出来的字段类型是什么?
// 反射出来的字段类型是什么?如何获得
Class type = f.getType();
System.out.println(type); // 输出class java.lang.String
所以,完整代码为:
public void test1() throws Exception {
Person p = new Person();
Class clazz = Class.forName("cn.itcast.reflect.Person");
Field f = clazz.getField("name");
// 获取字段的值
Object value = f.get(p);
// 获取字段的类型
Class type = f.getType();
if(type.equals(String.class)) {
String svalue = (String) value;
System.out.println(svalue);
}
// 设置字段的值
f.set(p, "xxxxxxxxxxxxx");
System.out.println(p.name);
}
反射字段:private int password = 123;
public void test2() throws Exception {
Person p = new Person();
Class clazz = Class.forName("cn.itcast.reflect.Person");
Field f = clazz.getDeclaredField("password");
f.setAccessible(true); // 暴力反射
System.out.println(f.get(p));
}
反射字段:private static int age = 23;
public void test3() throws Exception {
Class clazz = Class.forName("cn.itcast.reflect.Person");
Field f = clazz.getDeclaredField("age");
f.setAccessible(true); // 暴力反射
System.out.println(f.get(null));
}
Method类代表某个类中的一个成员方法。
得到类中的某一个方法:
Method methodCharAt = String.class.getMethod("charAt", int.class);
应通过思考和推理的方式来学习反射中的API,例如,Class.getMethod方法用于得到一个方法,该方法要接受什么参数呢?显然要一个方法名,而一个同名的方法有多个重载形式,用什么方式可以区分清楚想得到重载方法系列中的哪个方法呢?根据参数的个数和类型,例如,Class.getMethod(name, Class... args)
中的args参数就代表所要获取的那个方法的各个参数的类型的列表。再强调一遍参数类型用什么来表示啊?用Class实例对象!
调用方法:
通常方式:
System.out.println(str.charAt(1));
反射方式:
System.out.println(methodCharAt.invoke(str1, 1));
如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!
System.out.println(methodCharAt.invoke(null, 1)); // 说明该Method对象对应的是一个静态方法,静态方法调用不需要对象
综合练习:使用Method分别执行无参、有参、多个参(带数组和基本数据类型)、静态、私有的方法。
先描述一个Person类,如下:
public class Person {
public String name = "aaaa";
public void aa1() {
System.out.println("aa1");
}
public void aa1(String name, int password) {
System.out.println(name+":"+password);
}
public Class[] aa1(String name, int[] password) {
return new Class[]{String.class};
}
private void aa1(InputStream in) {
System.out.println(in);
}
public static void aa1(int num) {
System.out.println(num);
}
}
反射类的方法。
反射类的方法:public void aa1() {...}
public void test1() throws Exception {
Person p = new Person();
Class clazz = Class.forName("cn.itcast.reflect.Person");
Method method = clazz.getMethod("aa1", null);
method.invoke(p, null);
}
反射类的方法:public void aa1(String name, int password) {...}
public void test2() throws Exception {
Person p = new Person();
Class clazz = Class.forName("cn.itcast.reflect.Person");
Method method = clazz.getMethod("aa1", String.class, int.class);
method.invoke(p, "zxx", 38);
}
反射类的方法:public Class[] aa1(String name, int[] password) {...}
public void test3() throws Exception {
Person p = new Person();
Class clazz = Class.forName("cn.itcast.reflect.Person");
Method method = clazz.getMethod("aa1", String.class, int[].class);
Class[] cs = (Class[]) method.invoke(p, "zxx", new int[]{1,23});
System.out.println(cs[0]); // 输出class java.lang.String
}
反射类的方法:private void aa1(InputStream in) {...}
public void test4() throws Exception {
Person p = new Person();
Class clazz = Class.forName("cn.itcast.reflect.Person");
Method method = clazz.getDeclaredMethod("aa1", InputStream.class);
method.setAccessible(true); // 暴力反射
method.invoke(p, new FileInputStream("c:\\1.txt"));
}
反射类的方法:public static void aa1(int num) {...}
public void test5() throws Exception {
/*
Person p = new Person();
Class clazz = Class.forName("cn.itcast.reflect.Person");
Method method = clazz.getMethod("aa1", int.class);
method.invoke(p, 23);
*/
Class clazz = Class.forName("cn.itcast.reflect.Person");
Method method = clazz.getMethod("aa1", int.class);
method.invoke(null, 23);
}
JDK1.4和JDK1.5的invoke方法的区别:
public Object invoke(Object obj, Object... args)
public Object invoke(Object obj, Object[] args)
,即按JDK1.4的语法,需要将一个数组作为参数传递给invoke方法,数组中的每个元素分别对应被调用方法中的一个参数。 a(String name, String passowrd)
,按照JDK1.4的语法来调用就是:Method.invoke(Object obj, new Object[]{"aaa", "123"});
。所以,调用charAt方法的代码也可以用JDK1.4改写为:
System.out.println(methodCharAt.invoke(str1, new Object[]{2})); // 即按JDK1.4的语法调用
目标
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,大家要明白为什么要用反射方式去调啊?
假设有这样一个类:
class TestArguments {
public static void main(String[] args) {
for(String arg : args) {
System.out.println(arg);
}
}
}
解:
String startingClassName = args[0];
Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
mainMethod.invoke(null, new String[]{"111","222","333"});
发现报异常java.lang.IllegalArgumentException: wrong number of arguments
。
原因:此时实际调用的是main(String a1, String a2, String a3)
而TestArguments类中没有接收3个String类型参数的main(),TestArguments类中main()只接收一个参数,结果你调用的时候传了"111","222","333"3个参数,所以会报异常——错误的参数个数。
public static void main(String[] args)
,通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢? mainMethod.invoke(null, new String[]{"111","222","333"})
,javac只把它当作JDK1.4的语法进行理解,而不把它当作JDK1.5的语法解释,因此会出现参数类型不对的问题。mainMethod.invoke(null,new Object[]{new String[]{"111","222","333"}});
我给你的数组,你不会当作参数,而是把其中的内容当作参数。mainMethod.invoke(null, (Object)new String[]{"111","222","333"});
编译器会作特殊处理,编译时不把参数当作数组看待,也就不会将数组打散成若干个参数了。具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象(此处比较与值无关)。
定义一些数组如下:
int[] a1 = new int[]{1, 2, 3};
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[]{"a","b","c"};
所有具有相同元素类型和维数的数组共享同一份字节码:
System.out.println(a1.getClass() == a2.getClass()); // true
public String getName()
:以String的形式返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称。以下代码输出[I
System.out.println(a1.getClass().getName());
代表数组的Class实例对象的getSuperclass()方法返回的父类为Object类对应的Class。
public Class<? super T> getSuperclass()
:返回表示此Class所表示的实体(类、接口、基本类型或 void)的超类的Class。以下代码输出java.lang.Object
System.out.println(a1.getClass().getSuperclass().getName());
System.out.println(a3.getClass().getSuperclass().getName());
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
定义一些数组如下:
int[] a1 = new int[]{1, 2, 3};
int[] a2 = new int[4];
int[][] a3 = new int[2][3];
String[] a4 = new String[]{"a","b","c"};
则以下代码哪些是正确的?
Object aObj1 = a1;
Object aObj2 = a4;
Object[] aObj3 = a1;
Object[] aObj4 = a3;
Object[] aObj5 = a4;
解:
Object aObj1 = a1;
Object aObj2 = a4;
Object[] aObj3 = a1; // 错!基本类型int不是Object
Object[] aObj4 = a3; // int[]是Object
Object[] aObj5 = a4; // String是Object
Arrays.asList()方法处理int[]和String[]时的差异
在学集合框架中的Arrays类时,详情可参考Java集合框架——Map,有结论:如果数组中的元素都是对象,那么变成集合时,数组中的元素就直接转成集合中的元素。如果数组中的元素都是基本数据类型,那么会将该数组([[I@139a55])作为集合中的元素存在。
同样,如下代码:
int[] a1 = new int[]{1, 2, 3};
String[] a4 = new String[]{"a","b","c"};
System.out.println(Arrays.asList(a1));
System.out.println(Arrays.asList(a4));
第一行代码会输出诸如[[I@25154f]
这样的内容,这是因为asList()
方法按照JDK1.5的语法,将int[]
当作一个Object
对待;第二行代码会输出[a, b, c]
,这是因为asList()
方法按照JDK1.4的语法,将String[]
当作一个Object[]
数组来对待。
Array工具类用于完成对数组的反射操作。
例,利用反射打印一个数组:
private static void printObject(Object obj) {
Class clazz = obj.getClass();
if(clazz.isArray()) {
int len = Array.getLength(obj);
for(int i = 0; i < len; i++) {
System.out.println(Array.get(obj, i));
}
} else {
System.out.println(obj);
}
}
调用:
printObject(new String[]{"a","b","c"});
printObject("xyz");
思考题:怎么得到数组中的元素类型?
答:想得到数组中的元素类型,是没有办法的,只能得到某一个具体元素的类型,不能得到整个数组的元素类型。需要取出每个元素对象,然后再对各个对象进行判断,因为其中每个具体元素的类型都可以不同,例如Object[] x = new Object[]{"abc",Integer.Max}
。
关于ArrayList
和HashSet
的比较,可参考我的Java集合框架——Collection,有结论如下:
hashCode()
和equals()
来完成的。如果元素的HashCode值相同,才会判断equals()
是否为true。如果元素的HashCode值不同,不会调用equals()
。有类ReflectPoint
如下,类中覆盖hashCode
和equals
方法。
import java.util.Date;
public class ReflectPoint {
private int x;
public int y;
public String str1 = "ball";
public String str2 = "basketball";
public String str3 = "itcast";
public ReflectPoint(int x, int y) { // 快捷键——Alt+Shift+S + O
super();
this.x = x;
this.y = y;
}
@Override
public String toString() {
return str1 + ":" + str2 + ":" + str3;
}
@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;
}
}
分别创建ArrayList和HashSet的实例对象,比较两个集合的运行结果差异。
创建ArrayList的实例对象
public class ReflectTest2 {
public static void main(String[] args) throws Exception {
Collection collections = new ArrayList();
ReflectPoint pt1 = new ReflectPoint(3, 3);
ReflectPoint pt2 = new ReflectPoint(5, 5);
ReflectPoint pt3 = new ReflectPoint(3, 3);
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt1);
System.out.println(collections.size());
}
}
很显然,由于ReflectPoint
类中覆盖了equals
方法,所以输出的是3
。
创建HashSet的实例对象
public class ReflectTest2 {
public static void main(String[] args) throws Exception {
Collection collections = new HashSet();
ReflectPoint pt1 = new ReflectPoint(3, 3);
ReflectPoint pt2 = new ReflectPoint(5, 5);
ReflectPoint pt3 = new ReflectPoint(3, 3);
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt1);
System.out.println(collections.size());
}
}
很显然,由于ReflectPoint
类中覆盖了hashCode
和equals
方法,所以输出2
。
注意:参与计算HashCode值的成员变量不能发生变化。若发生变化,如:
pt1.y = 7;
y变化后,哈希code值就变了,那么就在这个区域里面找pt1,结果原来的pt1藏在另外一个区域里面,就产生内存泄露。所谓内存泄露,即对象不再使用,没被释放,会一直占用内存空间。
collections.remove(pt1); // 不会移除掉pt1
所以,产生两个面试题:
面试题一:hashCode()方法的作用。
面试题二:java中有内存泄露吗?为什么?试举例。
综合案例
采用配置文件加反射的方式创建ArrayList和HashSet的实例对象,比较观察运行结果差异。
先加载配置文件config.properties
第一种方式:首当其冲的一种方式,在Java Web开发中,利用API中XX类的getRealPath()
得到整个项目在硬盘上的真实位置(绝对位置),然后在项目内部找到配置文件config.properties
的位置。记住:一定要记住用完整的路径,但完整的路径不是硬编码,而是运算出来的。
InputStream ips = new FileInputStream("config.properties"); // 先演示相对路径
第二种方式:一个类加载器能加载.class文件,那它当然也能加载classpath环境下的其他文件,既然它有如此能力,它没有理由不顺带提供这样一个方法,但它也只能加载classpath环境下的那些文件。注意:直接使用类加载器时,不能以/打头。Hibernate
/Struts
/Spring
等框架用的配置文件都是放在classpath指定的目录下的,其内部就是使用的类加载器加载的配置文件。
InputStream ips = ReflectTest2.class.getClassLoader().getResourceAsStream("cn/itcast/day1/config.properties");
第三种方式:Class提供了一个便利方法getResourceAsStream
,用加载当前类的那个类加载器去加载相同包目录下的文件,既可用相对路径又可用绝对路径。Class类也提供getResourceAsStream
方法的比喻:如果你每次都找我给你去商店买可乐,那我还不如直接向你买可乐,即直接提供一个买可乐的方法给你。
相对路径:
InputStream ips = ReflectTest2.class.getResourceAsStream("resources/config.properties"); // 相对路径
绝对路径:
InputStream ips = ReflectTest2.class.getResourceAsStream("/cn/itcast/day1/resources/config.properties"); // 绝对路径(路径前加/就是绝对路径)
然后使用反射的方式创建ArrayList和HashSet的实例对象:
Properties props = new Properties();
props.load(ips);
ips.close(); // 对象关联的操作系统资源释放,本对象由垃圾回收机制回收
String className = props.getProperty("className");
Collection collections = (Collection)Class.forName(className).newInstance();
ReflectPoint pt1 = new ReflectPoint(3, 3);
ReflectPoint pt2 = new ReflectPoint(5, 5);
ReflectPoint pt3 = new ReflectPoint(3, 3);
collections.add(pt1);
collections.add(pt2);
collections.add(pt3);
collections.add(pt1);
System.out.println(collections.size());