day7基础加强

day07总结
今日内容
 MyEclipse安装与使用
 JUnit使用
 泛型
 1.5新特性
 自动装箱拆箱
 增强for
 静态导入
 可变参数方法
 枚举
 反射
MyEclipse安装与使用(yes)
安装MyEclipse
先安装了JDK

MyEclipse介绍

 MyEclipse是Eclipse的一个插件;
 MyEclipse是需要花钱的;
 MyEclipse官网不在欢迎中国人登录;

MyEclipse使用

1 创建项目
 选择工作空间;
 工作空间路径不能有空格和中文;
 工作空间以班名来命名:20130922
 创建项目;项目名以day0x为前缀,例如今天是第1天,那么今天的项目以day01为前缀;
 创建项目时配置JRE,如果不配置就是使用默认JRE;
 其实我们指定的都是JDK,而不是JRE。因为我们要开发Java项目,而不是运行!
 创建类,注意,所有类都必须有包,所有类名、变量名、方法名等一定要按规范来做;
 默认包名以cn.itcast为前缀;
 指定类的修饰符;
 指定类的父类;
 指定类的接口;
 指定方法:是否创建main、是否通过父类创建构造器、是否实现抽象方法;
 编写Hello World;
 运行java程序的方式;

断点调试模式运行java程序

1 断点调试的目的
 跟踪源代码;
 观察程序运行状态;

2 调试程序1
  编写求和代码
 设置断点;
 debug运行;
 进入debug透视图;
 程序会运行到断点处停住;
 当前行还没有运行;
 查看变量值:选中变量鼠标右键Watch;
 F5(跳入)、F6(跳过)、F7(跳出);

3 调试程序2
使用Arrays.binarySearch()编写折半搜索数组元素代码
 设置断点;
 测试跳入;
 测试跳过;
 测试跳出;
 添加断点;
 测试进入下一断点;
 测试返回当前方法栈的头部(Drop To Frame);
 清除断点;
 清除表达式;
 注意,停止程序!

MyEclipse快捷键

1 MyEclipse常用快捷键1
 Alt + /(内容助理):补全;
 Ctrl + 1(快速定位):出错时定位错误,与点击“红X”效果一样;
 Ctrl + Shift + O:导包;
 Ctrl + Shift + F:格式化代码块;

2 MyEclipse常用快捷键2
 Ctrl + Shift + T:查看源代码;
 Ctrl + 点击源代码:查看源代码;
 F3:查看选中类的源代码;
 Alt + 左键:查看源代码时的“原路返回”;
 Ctrl + Shift + X:把小写修改为大写;
 Ctrl + Shift + Y:把小写修改为小写;
 Ctrl + Alt + 下键:复制当前行;
 Ctrl + /:添加或撤销行注释;
 Ctrl + Shift + /:对选中代码添加段注释;
 Ctrl + Shift + \:撤销当前段注释;
 Alt + 上键:向上移动当前行;
 Alt + 下键:向上移动当前行;
 Ctrl + D:删除当前行;

MyEclipse:
 工作空间;
 项目名称;
 包名;
 类名;
 Alt + /、Ctrl + 1、Ctrl + shift + o、Ctrl + shift + f

JUnit使用
测试1

1 JUnit的作用
JUnit用来为程序写测试用例。
以前总是需要自己写个main方法来测试某个方法。当需要测试另一个方法时,还要在main中再写一段代码对另一个方法进行测试。
JUnit是专业的测试工具!!!

2 为测试程序创建包
  为JUnit写一个包:junit.test包。所有测试类都写到这个包中。

3 编写Person类
package cn.itcast;

public class Person {
public void run() {
System.out.println(“run”);
}

public void eat() {
	System.out.println("eat");
}

}

4 编写Person的测试用例类:PersonTest
包资源管理器选中Person类右键newJUnit TestCase修改包名为junit.test下一步选中要测试的方法。
每个测试方法都会有@Test注解。
在生成的PersonTest中给testXXX()方法添加测试内容。

5 运行测试用例
 选中PersonTest类右键Run asJUnit Test;
 Outline选中testXXX()方法右键Run as  JUnit Test。

测试2

1 setUp()和tearDown()
 再创建一个测试用例;
 勾选setUp()和tearDown()方法;
 setUp()方法会有@Before注解;
 tearDown()方法会有@After注解。
 setUp()方法会在所有测试方法之前运行;
 setUp()方法可以用来为每个测试方法在测试之前做一些准备工作;
 tearDown()方法会在所有测试方法之后运行。
 tearDown()方法可以用来为每个测试方法在测试之后做一些清理工作;

测试3

1 setUpBeforeClass()和tearDownAfterClass()
 再创建一个测试用例;
 勾选setUpBeforeClass()和tearDownAfterClass();
 setUpBeforeClass()方法会有@BeforeClass注解;
 tearDownAfterClass()方法会有@AfterClass注解;
 setUpBeforeClass()方法会在测试开始之前被调用;
 setUpBeforeClass()方法可以用来在测试开始之前做一些准备工作;
 tearDownAfterClass()方法在测试结束之后被调用;
 tearDownAfterClass()方法可以用来在测试结束之后做一些清理工作;
 这两个方法用的没有setUp()和tearDown()多。

@Test – 必须是public的、返回为void、无参的方法
@Before --必须是public的、返回为void、无参的方法
@After --必须是public的、返回为void、无参的方法
@BeforeClass – 必须是public的、static的、返回为void、无参的方法
@AfterClass – 必须是public的、static的、返回为void、无参的方法

泛型
泛型概述

1 数组与集合
  Java中可以定义任意类型的属性,例如String[]中存放的就是String类型的数据,我们称之为持有String类型的数组。但1.5之前时,Java的集合类却只能持有Object类型,1.5时添加了泛型的概念,泛型允许Java创建持有任意类型的集合对象,例如:new ArrayList()表示这个ArrayList中只能持有String类型的对象。

2 类型变量(参数)
具有一个或多个类型参数的类就是泛型类。
泛型类都至少有一个类型变量,你需要在创建泛型类对象时给类型变量赋值。当然,你要给类型变量赋的值必须是一个类型!
ArrayList arr = new ArrayList();
其中String就是给ArrayList类的类型变量赋值。在ArrayList类中所有使用类型变量的地方都会被String所替换。例如:boolean add(E e),其中e的类型就是变量,它会被String替换,最终变成boolean add(String e)。E get(int index)方法中返回值的类型为变量,它也会被String替换,最终变成String get(int index)。

3 泛型的好处
将运行期遇到的问题转移到了编译期。例如在1.4时,ArrayList类的add()方法参数还是Object类型,当然get()方法的返回值类型也是Object。这就说明使用get()方法获返回值后,你还需要强转。错误的强转可能会出现ClassCastException。
ArrayList list = new ArrayList();
list.add(“hello”);
Integer i = (Integer)list.get(0);//抛出异常

这个问题在有了泛型之后就不会再有了。
ArrayList list = new ArrayList();
list.add(“hello”);//编译出错!
Integer i = (Integer)list.get(0);

很明显,是泛型把只能在运行时才能找到的错误推向了编译期!这就是泛型的优点!!!

定义泛型类(接口)

1 自定义泛型类的语法
自定义泛型类的语法基本与定义正常的法一样:
public class A {}
定义泛型类时,需要在类名后面给出一对尖括号,在尖括号中给出1~N个类型变量。

2 泛型类中使用类型变量
用户在使用泛型类时,需要为类型变量赋值。例如:new A()。
在泛型类中可以使用类型变量:
public class A {
private T t;
public A(T t) {
this.t = t;
}
public T get() {
return t;
}
}
当用户创建A类对象时,就会给A类的类型变量T赋值,例如:new A(),这说明在A类中所有的T都会被String替换。
public class A {
private String t;
public A(String t) {
this.t = t;
}
public String get() {
return t;
}
}

3 类型变量的限制
上例中,A类中定义了T类型的属性t,这个属性t的类型是一个变量,无法确定的,因为你在定义A类时,根本就不知道用户会传递什么类型给T,所以你也不能调用属性t的方法,因为你不知道t的类型,就不知道它有什么方法。但是,我们知道任何类都是Object类的子类,那么就说明你可以调用属性t的Object中存在的方法。
注意,你不能使用T类型的构造器:new T(),因为你不能确定t的类型,那么也就不知道它有什么样的构造器,甚至是否有public构造器,所以不能创建。
相同的道理,也不能创建T类型的属性,例如:new T[10],这也是不行的!
还有一点,在泛型类中,static方法中不能使用类型变量T。

继承(实现)泛型类(接口)

1 继承泛型类之一
如果当前类是泛型类,那么在继承(实现)泛型类(接口)时,可以把自己的类型变量传递给父类(接口)。
public class ArrayList implements List {

}
可以这样来理解,其中public class ArrayList中的表示定义了一个泛型类,其中E是定义的类型变量,而implements List中的表示给List接口传递,这是在使用变量类型E,而不是在定义了。
ArrayList arr = new ArrayList();
在用户创建ArrayList类对象时,传递给ArrayList类中的E的值是String,那么ArrayList会把这个String再传递给List中的E。
你可能会想,为什么一定要给父类或接口传递类型变量。那我会问你,实现类不给List接口中的E来赋值,谁来给它赋值?难道你想new List()这样赋值么?接口是不能被实例化的,你只能让实现类来赋值,你不赋值,接口中的get()方法一直返回E类型!!!

2 继承泛型类之二
泛型类的子类不一定必须为泛型类,也可以是非泛型类。这时因为子类没有类型变量可以传递给父类,那么也就能传递给父类类型常量了。
public class String implements Comparable {
public int compareTo(String other) {…}
}

这时,在重写父类中方法时,所有的类型变量都是String了。

泛型方法

1 泛型方法定义
泛型类是说明这个类有类型变量,在创建这个类对象时需要给类型变量赋值。泛型方法是说明这个方法有类型变量,在调用这个方法时需要给类型变量赋值。
public T get(T[] ts, int index) {
return ts[index];
}
注意,在返回值前面定义类型变量。
get()方法是一个泛型方法,它有一个类型变量T,这说明在调用get()方法时需要给get()方法的T赋值。
如果要定义一个有意义的泛型方法,那么:
 参数需要使用类型变量;
 返回值需要使用类型变量。
所以,通常在调用泛型方法时,只需要传递参数就可以了,例如:
String[] strs = …
String s = o.get(strs, 0);
上面代码中给get()方法的类型变量T赋值为String,因为传递的参数为String数组,所以就是给T赋值为String。
当然,也可以显示给出类型变量的值:o.get(strs,0),在点后面,方法名前面给出类型值,但一般人不会这么打代码。

泛型的边界
编译期状态:编译期状态,例如内部类!内部类就是只有编译器知道,而JVM不知道什么叫内部类!
1 泛型的擦除
泛型其实是编译期状态,即JVM不知道什么是泛型,在JVM那里所有类都是正常的类。没有类型变量。一切的一切都是编译器干的好事儿!
其实List的get()方法返回值还是Object,只是编译器帮我们添加了强转的语句。但这个强转一定不会出现问题。因为本来add()方法添加元素已经限制过了,那么在get()时,一定不会出现强转的问题。
也就是说,在ArrayList类中持有的还是Object类型,而不是我们指定的类型。当然,就算是JVM没有泛型,但编译器会帮我们完成这些问题,我们就可以当泛型真的存在。

2 泛型边界限定的类型值的范围
通常我们看到的泛型类都没有边界限定,也就是说可以给泛型类的类型变量赋任意类型的值(当然基本类型是不可以的)。
java允许给类型变量指定边界,这样用户在给类型变量赋值时就必须在边界之内。
public class A {}表示用户可以给T赋值为Number或Number的子类型。例如:new A()这是可以的,但new A()是不可以的。

通配符

1 通配符的作用
Object[] objs = new String[10];
objs[0] = new Integer(100);
上面代码编译是可以通过的,但在运行时会出现ArrayStoreException。因为objs数组真实的身份是String[],向String[]数组中存放Integer对象当然是不行的。
ArrayList list = new ArrayList();
list.add(new Integer(100);
上面代码在第一行位置编译失败,因为泛型根本就不让把ArrayList赋值给ArrayList,对于ArrayList而言,只能赋值ArrayList,其他的什么都不能赋值。
这也说明一个问题:
public static void printList(List list) {…}
调用printList()方法只能传递给它List类型的参数,而不能传递List,或者List,这说明我们的printList()方法有很多的限制,不够通用!!!你可能会想我再重载几次printList()方法吧,但这是行不通的!
public static void printList(List list) {…}
public static void printList(List list) {…}
因为JVM不知道什么是泛型,这两个方法在到了JVM那里时都是会把泛型参数擦除,这两个方法就是相同的方法了,擦除之后即:
public static void printList(List list) {…}
public static void printList(List list) {…}
当然JVM不可能看到这样的代码,因为编译器不能让你编译通过!
处理这个问题需要使用通配符!

2 子类型通配符
public static void printList(List<? extends Person> list) {…}
这回可以传递给printList()方法List,以及List参数了。只要类型参数为Person,或者是Person子类型就都可以。
你可以这样来理解通配符,通配符表示“不知道”的意思。即一个问号!但子类型通配符还是知道一些信息的,它只知道用户转递的类型参数一定是Person的子类型。虽然使用了通配符之后printList()方法更加通用了,但是这也是要付出一些代价的。因为不知道List中类型参数的真实类型,所以就不能调用list的add()方法了。你可能会想add(new Student())应该是可以的,但如果List是List呢,你怎么向这样的List添加Student呢?再想一想,add()方法已经作废了,什么都传递不了。

3 父类型通配符
public static void printList(List<? super Student> list) {…}
可以传递给printList()方法List,以及List,甚至List也是可以的。只要传递给printList()方法的List类型参数为Student,或者Student的父类型就是可以的。
你现在可以向list中添加Student类型的参数,例如:list.add(new Student())。因为用户提供的参数List、List、List,无论哪一种类型,对于list.add(new Student())都是合法的。
但是,现在我们不知道list的get()方法返回的是什么了!因为用户传递的可能是List、List、List,如果我们用Student s = list.get(0),那么如果list其实是一个List岂不是出错了!没错,只能使用Object来接收list.get(0)的返回值了。

4 无界通配符
所谓无界通配符,即List<?>,对通配符没有限定。你可以给List<?>赋任意的值,但是,你能使用这样的list干什么呢?也不能add(),也只能使用Object来接收get()方法返回值。
所以,通常List<?>只能表示你在使用泛型而已!编译器会认为List<?>比List更加优雅一些!你可能也看出来了,泛型还真是很垃圾!!!这也是没有办法中的办法,现在的泛型是迁移性兼容的一种版本而已!Java设计者不敢让JVM知道泛型的存在,原因是为了兼容1.4之前的版本。当所有人都在使用1.5以上版本的JDK后,Java的泛型可能就不会再是编译期状态了。

1.5新特性
自动装箱拆箱

1 什么是自动装箱拆箱
在1.5之后,Java允许把基本类型与其对应的包装器类型之间自动相互转换。例如:Integer i = 100,把int类型的100直接给了Integer类型的变量i,这就是自动装箱。int a = new Integer(100),这是自动拆箱。
Object o = 100;//其实是把100自动装箱为Integer,即Object o = Integer.valueOf(100)。
int a = (Integer)o;//其实是把o强转为Integer后,自动拆箱为int,即int a=((Integer)o).intValue()。

2 Integer.valueOf()与Integer内部缓存
我们已经知道自动装箱使用的是Integer.valueOf()方法,但我们要了解一下,其实valueOf()方法会使用Integer类内部的缓存来获取Integer对象。
Integer类的内部缓存了-128~127之间的256个Integer对象,如果valueOf()方法需要把这个范围之内的整数转换成Integer对象时,valueOf()方法不会去new对象,而是从缓存中直接获取,这就会导致valueOf(100)两次,都是从缓存中获取的同一个Integer对象!
Integer i1 = Integer.valueOf(100);
Integer i2 = Integer.valueOf(100);
boolean b = i1 == i2;//结果为true

相同的道理:
Integer i1 = 100;
Integer i2 = 100;
boolean b = i1 == i2;//结果为true

但是:
Integer i1 = 200;
Integer i2 = 200;
boolean b = i1 == i2;//结果为false

这是因为200不在Integer内部的缓存之内,所以这时valueOf()方法会new一个Integer对象。每次valueOf(200)都会创建一个新的Integer对象,所以才会是false。

增强for

1 增强for循环概念
可以循环遍历数组或者集合类。

2 增强for循环的语法格式
for(元素类型 e : 数组或集合对象) {
}
增强for每循环一次,都会把数组或集合中的一个元素赋值给e,从头开始遍历,直到最后一个元素。

3 增强for的优缺点
只能从头到尾的遍历数组或集合,而不能只遍历部分。
在遍历List或数组时,不能获取当前元素下标。
增强for使用便简单,这是它唯一的优点了。
增强for比使用迭代器方便一点!

4 增强for与Iterable接口
  任何实现了Iterable接口的类,都可以使用增强for来遍历。

静态导入(鸡肋)

1 什么是静态导入
静态导入也需要使用import关键字;
静态导入后,在调用静态方法,以及使用静态属性时就可以不再给出类名了,例如向控制台打印时可以把System.out.println()写成out.println();System.exit(0)写成exit(0)。

2 静态导入的语法格式
import static 包名.类名.静态方法名;
import static 包名.类名.静态属性名;
import static 包名.类名.*;

3 静态导入真是鸡肋啊
不建议使用!
使用静态导入,使代码可读性降低!

可变参数

1 使用数组为方法参数
int sum(int a, int b) {return a + b;}
int sum(int a, int b, int c) {return a + b;}
int sum(int a, int b, int c, int d) {return a + b + c + d;}

看上面代码。我们知道这种重载是无止境的!
当函数的参数可以是0~n个时,我们最好的办法就是使用数组来处理,例如把上面代码修改为一个函数:
int sum(int[] arr) {
int sum = 0;
for(int i = 0; i < arr.length; i++) {
sum +=arr[i];
}
return sum;
}

修改后的sum()方法可以计算0~N个整数的和,但调用sum()需要传递一个数组,这使调用这个函数很不方便。
int arr = {1,2,3,4,5,5};
sum(arr);

2 可变参数方法的定义
可以把数组类型的参数定义为可变参数,例如:
int sum(int[] arr) {
int sum = 0;
for(int i = 0; i < arr.length; i++) {
sum +=arr[i];
}
return sum;
}
int sum(int… arr) {
int sum = 0;
for(int i = 0; i < arr.length; i++) {
sum +=arr[i];
}
return sum;
}

上面代码把int[] arr修改为int… arr,其中arr就变成了可变参数。
可变参数其实就是数组。

3 调用可变参数方法
当调用int sum(int…arr)方法时就方便多了。如下方式的调用都是正确的:
 int[] arr = {1,2,3}; sum(arr);,使用数组调用;
 sum();,不给参数调用,表示传递0个元素的数组,即:int[] arr={}; sum(arr);
 sum(5);,用一个参数调用,表示传递1个元素的数组,即:int[] arr={5}; sum(arr);
 sum(2,3,4,5);,用多个参数调用,表示传递多个元素的数组,即:int[] arr={2,3,4,5}; sum(arr);。

调用可变参数方法,可以传递0~N个参数来调用,也可以直接传递数组来调用。

4 可变参数方法的要求
 一个方法最多只能有一个可变参数;
 可变参数必须是最后一个参数;
可变参数只能出现在方法的形参中,局部变量或属性是不能使用这种东西的。

枚举
枚举类型概述

1 什么是枚举类型
word文档的对齐方式有几种:左对齐、居中对齐、右对齐;
开车的方向有几种:前、后、左、右;
枚举就是有限实现个数的类型,你可能会说,byte类型也只有256个,没错,但我们真实定义为枚举的类型,一般最多也就十多个实例,再多就不会定义为枚举了。

2 JDK1.4之前的枚举类型
在JDK1.4之前没有枚举类型,都是使用int或字符串类型来表示枚举,如果枚举只有两个选项,那么连int都用不上,只需要使用boolean类型即可。
例如:BorderLayout类的方位给出五个:CENTER、EAST、SOUTH、WEST、NORTH。当使用容器类的方法添加组件时:add(new Button(), “CENTER”),这是合法的,但因为String类型太过宽泛,所以可能会出现add(new Button(), “哈哈”)的调用可能,这会导致运行时出现异常。所以,JDK1.5又新增了枚举类型。

3 定义枚举类型
定义枚举类型需要使用enum关键字,例如:
public enum Direction {
FRONT、BEHIND、LEFT、RIGHT;
}
Direction d = Direction.FRONT;

注意,每个枚举选项之间是用逗号隔开的。如果枚举类没有构造器、方法等,在最后一个枚举选择后面可以不打分号。但是如果枚举类还有其他成员,那么就要在最后一个枚举项后面添加分号了。
Direction类型只有四个选项,你可以理解为这个枚举类只有四个实例对象一样。外界无法去创建新的枚举对象,只能从这四个中去选择。
其实大多数时候,我们使用枚举类型还是与以及使用int或String表示的枚举一样,基本上都是很简单的。

4 枚举与switch
1.5开始枚举类型可以在switch中使用!在1.7之后,String类型也可以放到switch中使用了。
Direction d = Direction.FRONT;
switch(d) {
case FRONT: System.out.println(“前面”);break;
case BEHIND:System.out.println(“后面”);break;
case LEFT: System.out.println(“左面”);break;
case RIGHT: System.out.println(“右面”);break;
default:System.out.println(“错误的方向”);
}
Direction d1 = d;
System.out.println(d1);

注意,在switch中,不能使用枚举类名称,例如:“case Direction.FRONT:”这是错误的,因为编译器会根据switch中d的类型来判定每个枚举类型,在case中必须直接给出与d相同类型的枚举选项,而不能再有类型。

枚举类也是类

1 所有枚举类都是Enum的子类
所有枚举类都默认是Enum类的子类,无需我们使用extends来继承。这说明Enum中的方法所有枚举类都可以的。
 int compareTo(E e):比较两个枚举常量谁大谁小,其实比较的就是枚举常量在枚举类中声明的顺序,例如FRONT的下标为0,BEHIND下标为1,那么FRONT小于BEHIND;
 boolean equals(Object o):比较两个枚举常量是否相等;
 int hashCode():返回枚举常量的hashCode;
 String name():返回枚举常量的名字;
 int ordinal():返回枚举常量在枚举类中声明的序号,第一个枚举常量序号为0;
 String toString():把枚举常量转换成字符串;
 static T valueOf(Class enumType, String name):把字符串转换成枚举常量。

2 枚举类的构造器
  枚举类也可以有构造器,构造器不能给出访问修饰,而且默认都是private构造器。因为枚举类的实例不能让外界来创建!
enum Direction {
FRONT, BEHIND, LEFT, RIGHT;

Direction() {
	System.out.println("hello");
}

}

3 枚举类的方法
再次强调,枚举类也是类,也可以有构造器、方法和属性,只是对构造器有一些限制而已。在语法上有一些怪异罢了!
enum Direction {
FRONT, BEHIND, LEFT, RIGHT;
public void fun() {
System.out.println(“hello Enum!”);
}
}
Direction.FRONT.fun();

4 枚举类的属性
枚举类也可以有属性。但是,如果每个枚举常量的属性值如果都相同,那就失去了意义,我们需要让每个枚举常量的属性值不同,那么就需要自己使用构造器来创建枚举常量,然后在构造器中给每个枚举常量传递不同的值。
enum Direction {
FRONT(“前面”), BEHIND(“后面”), LEFT(“左面”), RIGHT(“右面”);

private String explain;

Direction(String explain) {
	this.explain = explain;
}

public void setExplain(String explain) {
	this.explain = explain;
}

public String getExplain() {
	return explain;
}

}
String explain = Direction.FRONT.getExplain();
System.out.println(explain);

5 使用匿名类来创建枚举常量
  还可以使用匿名类来创建枚举常量,这说明枚举常量的类型是当前枚举类的子类,而且是个匿名类。这可以让每个枚举常量有自己的类型,当然有自己的类型不是目的,而是有自己的行为才是目的!但是我们知道就算给匿名类添加了自己独有的方法,也是无法调用的,因为匿名类没有名字,只能使用父类的引用指向匿名类的实例,而多态之后只能调用父类中存在的方法。所以,使用这种情况时,通常是为了让每个枚举常量重写当前枚举类中的方法(抽象方法)。
enum Direction {
FRONT() {
public void fun() {
System.out.println(“FROND:重写了fun()方法”);
}
},
BEHIND() {
public void fun() {
System.out.println(“BEHIND:重写了fun()方法”);
}
},
LEFT() {
public void fun() {
System.out.println(“LEFT:重写了fun()方法”);
}
},
RIGHT() {
public void fun() {
System.out.println(“RIGHT:重写了fun()方法”);
}
};

public void fun() {
	System.out.println("没有意义的方法");
}

}
Direction.FRONT.fun();
Direction.BEHIND.fun();
Direction.LEFT.fun();
Direction.RIGHT.fun();

通常fun()方法应该定义为抽象的方法,因为每个枚举常量都会去重写它。
你无法把Direction声明为抽象类,但需要声明fun()方法为抽象方法。
enum Direction {
FRONT() {
public void fun() {
System.out.println(“FROND:重写了fun()方法”);
}
},
BEHIND() {
public void fun() {
System.out.println(“BEHIND:重写了fun()方法”);
}
},
LEFT() {
public void fun() {
System.out.println(“LEFT:重写了fun()方法”);
}
},
RIGHT() {
public void fun() {
System.out.println(“RIGHT:重写了fun()方法”);
}
};

public abstract void fun();

}

枚举类的特殊方法

1 每个枚举类都有两个特殊方法
每个枚举类都有两个不用声明就可以调用的static方法,而且这两个方法不是父类中的方法。这又是枚举类特殊的地方,下面是Direction类的特殊方法。
 static Direction[] values():返回本类所有枚举常量;
 static Direction valueOf(String name):通过枚举常量的名字返回Direction常量,注意,这个方法与Enum类中的valueOf()方法的参数个数不同。

枚举的真实世界

1 枚举也是编译期状态
  其实枚举也是编译期状态,在运行时JVM并不知道什么是枚举类型。这也就是说,编译器需要把枚举类型转换成普通类。
enum Direction {FRONT, BEHIND, LEFT, RIGHT}
final class Direction extends Enum {
public static final Direction FRONT;
public static final Direction BEHIND;
public static final Direction LEFT;
public static final Direction RIGHT;
private static final Direction ENUM$VALUES[];

static {
	FRONT = new Direction("FRONT", 0);
	BEHIND = new Direction("BEHIND", 1);
	LEFT = new Direction("LEFT", 2);
	RIGHT = new Direction("RIGHT", 3);
	ENUM$VALUES = new Direction[] {FRONT, BEHIND, LEFT, RIGHT};
}

private Direction(String s, int i) {
	super(s, i);
}

public static Direction[] values() {
	Direction adirection[];
	int i;
	Direction adirection1[];
	System.arraycopy(adirection = ENUM$VALUES, 0,
			adirection1 = new Direction[i = adirection.length], 0, i);
	return adirection1;
}

public static Direction valueOf(String s) {
	return (Direction) Enum.valueOf(Direction.class, s);
}

}

反射
反射概述

1 什么是反射
让我们从Class类开始了解反射!

每个加载到方法区中的class文件都对应一个Class类的对象,你可以把Class类的对象理解为硬盘上的class文件的对应体。

2 反射的作用
反射是Java中的高级特性,在各种Java框架中都需要使用反射。所以,就算你将来很长一段时间不使用反射,但你使用的框架都大量使用了反射,所以想深入学习框架,那么就一定要学习反射。
框架通常通过反射来识别一个对象的“类型信息”。当你传递给框架一个对象时,框架会通过反射来了解对象的真实类型(对象实体的类型,而不是引用的类型),这个类型有几个构造器,有什么样的属性,有什么样的方法。还可以通过反射调用构造器,调用方法,对属性进行读写操作。
你可能觉得这没有什么神奇的,那是你还没了解我说的是什么!你需要再想一想,写一个方法,参数是Object obj,然后你的方法需要创建一个与参数类型相同的对象出来,还要调用这个对象上的方法。需要注意,参数是Object类型,但用户调用这个方法时,可能传递的不是Object实体对象,它的真实类型有可能是任何类型。
public static void fun(Object obj) {
 通过反射创建obj相同类型的对象;
 调用obj的方法,调用的方法可以是obj真实类型独有的方法。而不一定非要Object中的方法。
}

3 猜猜Class类都有什么功能
我们学习面向对象也有一定的时间了,可以通过面向对象的思想,猜测一个类中应该有什么样的方法了。
一个Student类用来表示学生类型,学生应该有名字,那么学生类就应该有getName()方法。学生也应该有学号,那么学生类就应该有getNumber()方法…
一个Class类用来表示类类型,类应该有属性,那么Class类就应该有getField()方法,但一个类可以有多个属性,那么Class类有的就不是getField(),而是getFields()方法,返回值为Field[]类型。相同的道理,Class类也应该有getMethods()和getConstructors()方法,用来返回这个类的所有方法和所有构造器的定义。其实今后我们去学习,很可能就只是想使用一个类的一个方法!然后才去查找这个类,再去学习我们需要的方法,对于这个类的其他就一无所知了!你很可能在编写某个项目时会有这样的想法:某某某方法应该是某某某类的!!!然后你就去查找API!
描述学生的类型、描述老师的类型、描述计算机的类型,都是很好理解的,但描述类的类型总是让人感觉怪怪的,这也是初学者需要习惯的地方。还有,我们在学习过程中称呼这些类型时也需要注意一下,例如Field表示属性类,Field类有一个属性叫name,那么我们需要怎么说明这个name属性呢?“属性类的name属性”,这是一个不错的称呼!
 Class:类的反射对象;
 Field:属性反射对象;
 Method:方法反射对象;
 Constructor:构造器反射对象;
 Class、Field、Method、Construcator:统称为“反射对象”。

Class类

1 反射从Class类开始
要想使用反射,首先你需要得到Class对象,然后才能通过Class对象获取Constructor、Field、Method等对象。所有的反射对象都不可能自己来new,说白一点,这些反射对象对应的是class文件上的信息,你怎么可能自己去new呢?如果可以自己去new一个Class类的对象,那么是不是就不用我们再去编写.java文件,然后再通过编译器去编译成.class文件了呢?当然这是不可能的!
我们需要思考,Class除了可以返回当前对应类型的所有属性、方法、构造器的反射对象外,还有什么功能呢?例如对应类型的类名是什么?对应类型的父类是谁?对应类型是不是public类,是不是final类。对应类型有没有可能是个数组类型?有没有可能是接口类型?有没有可能是基本类型等等!如果你学会了这样思考,那么你今后学习新类是就方便多了!

2 得到Class对象
 通过对象获取Class对象:obj.getClass();
 你很清楚要使用的Class类型:String.class、int.class,只需要在类型的后面添加.class就表示一个Class类型的实例了。
 通过类名来获取Class对象:在只有一个字符串(类名)时使用Class.forName(String className)来获取Class对象。
Class c1 = “”.getClass();
Class c2 = String.class;
Class c3 = Class.forName(“java.lang.String”);
System.out.println(c1 == c2);
System.out.println(c2 == c3);

上面代码输出的都是true,这是因为一个.class文件,在方法区中只对应一个Class对象。

3 加载类
我们已经知道,main()方法是程序的入口。那是不是在main()方法开始执行之前,所有的class文件都已经加载到方法区中了呢?答案是:NO!通常只有需要执行到使用某个类的代码时,才会去CLASSPATH中加载class文件,如果程序从头到尾都没有使用某个类,那么这个类对应的class文件就不会被加载到内存。
可以导致一个类被加载可能有:
 使用一个类的静态方法;
 使用一个类的静态属性;
 创建这个类的对象;
 使用Class.forName()方法加载类;
 反序列化一个类的对象;
 加载一个类的子类时,也会加载其父类;
 加载一个类时,也会加载与该类相关的类。
上面给出的几个可能也只是可能而已,如果当前类没有被加载过,才会去加载,如果已经加载到方法区中了,那么就不可能再去加载。

4 Class类方法
  方法1:
 String getName():返回类名;
 String getSimpleName():返回简单类名,不包含包名,但数组类型使用它比较方便;
 Class getSuperClass():获取父类,Object.class.getSupperClass()返回null;
 int getModifiers():获取类的所有修饰符信息;

方法2:
 Constructor getConstructor(Class… parameterTypes):通过指定的参数类型获取公有构造器反射对象;
 Constructor[] getConstructors():获取所有公有构造器对象;
 Constructor getDeclaredConstructor(Class… parameterTypes):通过指定参数类型获取构造器反射对象。包含私有构造器对象;
 Constructor[] getDeclaredConstructors():获取所有构造器对象。包含私有构造器;
 Field getField(String name):通过名字获取公有属性反射对象,包含父类中声明的公有属性;
 Field[] getFields():获取所有公有属性反射对象,包含父类中声明的公有属性;
 Field getDeclaredField(String name):通过名字获取本类中某个属性,包含本类的private属性,但父类中声明的任何属性都不包含;
 Field[] getDeclaredFields():获取本类中声明的所有属性,包含private属性,但不包含父类中声明的任何属性;
 Method getMethod(String name, Class… parameterTypes):通过方法名和方法参数类型获取方法反射对象,包含父类中声明的公有方法,但不包含所有私有方法;
 Method[] getMethods():获取所有公有方法,包含父类中的公有方法,但不包含任何私有方法;
 Method getDeclaredMethod(String name, Class… parameterTypes):通过方法名和方法参数类型获取本类中声明的方法的反射对象,包含本类中的私有方法,但不包含父类中的任何方法;
 Method[] getDeclaredMethods():获取本类中所有方法,包含本类中的私有方法,但不包含父类中的任何方法。

方法3:
 boolean isArray():是否为数组类型;
 boolean isAnnotation():是否为注解类型;
 boolean isAnnotationPresent(Class annotationClass):当前类是否被annotationClass注解了;
 boolean isEnum():是否为枚举类型;
 boolean isInterface():是否为接口类型;
 boolean isPrimitive():是否为基本类型;
 boolean isSynthetic():是否为引用类型;

方法4:
 T newInstance():使用本类无参构造器来创建本类对象;

其他反射类
  其他反射类都在java.lang.reflect包下
1 AccessibleObject
AccessibleObject类是Constructor、Method、Field三个类的父类。
 Annotation getAnnotation(Class annotationClass):获取作用在当前成员上的annotationClass类型的注解对象;
 Annotation[] getAnnotations():获取作用在当前成员上的所有注解对象;
 boolean isAccessible():判断当前成员是否可访问;
 void setAccessible(boolean flag):设置当前成员是否可访问。

2 Construcator
 String getName():获取构造器名;
 int getModifiers():获取构造器上的所有修饰符信息;
 Class getDeclaringClass():获取构造器所属的类型;
 Class[] getParameterTypes():获取构造器的所有参数的类型;
 Class[] getExceptionTypes():获取构造器上声明的所有异常类型;
 T newInstance(Object… initargs):通过构造器反射对象调用构造器。

3 Method
 String getName():获取方法名;
 int getModifiers():获取方法上的所有修饰符信息;
 Class getDeclaringClass():获取方法所属的类型;
 Class[] getParameterTypes():获取方法的所有参数的类型;
 Class[] getExceptionTypes():获取方法上声明的所有异常类型;
 Class getReturnType():获取方法的返回值类型;
 Object invode(Object obj, Object… args):通过方法反射对象调用方法,如果当前方法是实例方法,那么当前对象就是obj,如果当前方法是static方法,那么可以给obj传递null。args表示是方法的参数;
 setAccessible(true);

4 Field
 String getName():获取属性名;
 int getModifiers():获取属性上的所有修饰符信息;
 Class getDeclaringClass():获取属性所属的类型;
 Class getType():获取当前属性的类型;
 Object get(Object obj):获取obj对象的当前属性值;
 void set(Object obj, Object value):设置obj对象的当前属性值为value;
 XXX getXXX(Object obj):如果当前属性为基本类型,可以使用getXXX()系列方法获取基本类型属性值。假如当前属性为int类型,那么可以使用getInt(Object obj)方法获取obj对象的当前属性值;
 void setXXX(Object obj, XXX value):如果当前属性为基本类型,可以使用setXXX()系统方法基本类型属性值。假如当前属性为int类型,那么可以使用setInt(Object obj, int value)方法设置obj对象的当前属性值为value。

5 Modifier
Modifier类有一系列的static方法用来解析其他getModifiers()方法返回的int值。
Method m = …
int m = m.getModifiers();
boolean b1 = Modifier.isAbstract(m);//解析m中是否包含abstract修饰
boolean b2 = Modifier.isStatic(m);//解析m中是否包含static修饰
String s = Modifiers.toString(m);//把所有修饰都转换成字符串

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值