说明: 这是张孝祥老师2010年放出的"张孝祥2010贺岁视频:Java高新技术"系列视频的笔记. 当时针对的是Java5(而现在Java7了已经), 所以所说新特性之类都是针对而言.
枚举
0. 为什么要有枚举?
答: 要定义星期几或性别的变量,该怎么定义?假设用1-7分别表示星期一到星期日,但有人可能会写成int weekday = 0;或即使使用常量方式也无法阻止意外。
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。
1. 定义枚举类, 使用enum关键字:
import java.util.Arrays;
public class EnumTest {
public static void main(String[] args) {
WeekDay weekDay2 = WeekDay.SUN;
//枚举类对象的自带的方法
System.out.println(weekDay2); //枚举类实现了toString方法
System.out.println(weekDay2.name());
System.out.println(weekDay2.ordinal()); //序数, 该对象是枚举类对象的第几个(从0开始)
//枚举类的静态方法
System.out.println(WeekDay.valueOf("SUN")); //有名称获取元素对象
System.out.println(Arrays.toString(WeekDay.values())); //以数组形式获取枚举类中所有的元素
}
public enum WeekDay{
SUN, MON, TUE, WED, THI, FRI, SAT; //* 分号可以省略
}
}
2. 带有构造方法的枚举类:
import java.util.Arrays;
public class EnumTest {
public static void main(String[] args) {
WeekDay weekDay2 = WeekDay.SUN;
//枚举类对象的自带的方法
System.out.println(weekDay2); //枚举类实现了toString方法
System.out.println(weekDay2.name());
System.out.println(weekDay2.ordinal()); //序数, 该对象是枚举类对象的第几个(从0开始)
//枚举类的静态方法
System.out.println(WeekDay.valueOf("SUN")); //有名称获取元素对象
System.out.println(Arrays.toString(WeekDay.values())); //以数组形式获取枚举类中所有的元素
}
public enum WeekDay{
SUN(1), MON(), TUE, WED, THI, FRI, SAT; //* 如果只有这一行的话,分号可以省略
private WeekDay(){System.out.println("first");}
private WeekDay(int day){System.out.println("second");}
}
}
3. 带有抽象方法的枚举, 例子:
import java.util.Arrays;
public class EnumTest {
public static void main(String[] args) {
WeekDay weekDay2 = WeekDay.SUN;
//枚举类对象的自带的方法
System.out.println(weekDay2); //枚举类实现了toString方法
System.out.println(weekDay2.name());
System.out.println(weekDay2.ordinal()); //序数, 该对象是枚举类对象的第几个(从0开始)
//枚举类的静态方法
System.out.println(WeekDay.valueOf("SUN")); //有名称获取元素对象
System.out.println(Arrays.toString(WeekDay.values())); //以数组形式获取枚举类中所有的元素
}
//带有构造器的枚举类
public enum WeekDay{
SUN(1), MON(), TUE, WED, THI, FRI, SAT;
private WeekDay(){System.out.println("first");}
private WeekDay(int day){System.out.println("second");}
}
//带有抽象方法的枚举类
public enum TrafficLamp {
RED(30){
public TrafficLamp nextLamp()
{
return GREEN;
}
},
GREEN(45){
public TrafficLamp nextLamp()
{
return YELLOW;
}
},
YELLOW(5){
public TrafficLamp nextLamp()
{
return RED;
}
};
public abstract TrafficLamp nextLamp();
private int time;
private TrafficLamp(int time){this.time = time;}
}
}
4. 枚举的其他知识点:
- 枚举类的元素列表必须放在类代码的第一行, 且如果只有这一行的话行尾的分号可以省略.
- 枚举类的构造方法必须必须私有.
- * 元素列表中的元素, 相当于是所属枚举类的子类.
- * 如果枚举只有一个成员时, 就可以作为一种单例的实现方式.
反射
5.** Java程序中的各个Java类属于同一个类事物, 描述这类事物的Java类就是java.lang.Class类. Class类的实例对象对应各个类在内存中的字节码.
6. 如何获得各字节码对应的实例对象:
- 类名.class;
- 对象名.getClass();
- Class.forName("类的完整名称"); * 需要带上包名! 做反射时主要用这种.
8.** 反射定义: 反射就是把Java类中的各种成分映射成相应的Java类.
例如: 一个java类用一个Class类的对象来表, 一个类中的组成部分: 成员变量、方法、构造方法、包等信息也用一个个的java类来表示. 表示java类的Class类要提供一系列的方法, 用来获得其中变量(变量、方法、构造方法、包), 这些变量用相应的类的实例对象来表示, 如java.lang.reflect.Field,Method、Contrutor、Package等.
9. java.lang.reflect.Constructor类, 构造方法的反射应用:
import java.lang.reflect.Constructor;
class Test2
{
public static void main(String [] args) throws Exception
{
//反射获得接收StringBuffer的构造方法
Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
//是接收StringBuffer的构造方法, 所以要传入StringBuffer对象
String str = (String)constructor1.newInstance(new StringBuffer("abc"));
System.out.println(str);
System.out.println(str.charAt(2));
}
}
10. Field类, 成员变量的反射应用:
import java.lang.reflect.Field;
class Test2
{
public static void main(String [] args) throws Exception
{
ReflectPoint pt1 = new ReflectPoint(3, 5);
Field fieldY = pt1.getClass().getField("y"); //getField()方法只能获取可见的
Field fieldX = pt1.getClass().getDeclaredField("x"); //getDeclaredField()方法连不可见的也能获取(此处x是private修饰的)
//fieldY不是对象身上的变量, 而是类, 若果要取值需要指定取哪个对象的
System.out.println(fieldY.get(pt1));
//由于Reflect的x变量是私有的, 不可见, 所以这里要先setAccessible(true), 之后才能访问. * 这叫暴力反射!
fieldX.setAccessible(true);
System.out.println(fieldX.get(pt1));
}
}
下面是例子中用到的ReflectPoint类:
public class ReflectPoint {
private int x;
public int y;
public ReflectPoint(int x, int y) {
super();
this.x = x;
this.y = y;
}
}
11.* 成员变量反射的综合案例:
import java.lang.reflect.Field;
class Test2
{
public static void main(String [] args) throws Exception
{
ReflectPoint pt1 = new ReflectPoint(3, 5);
System.out.println(pt1);
changeStringValue(pt1);
System.out.println(pt1);
}
private static void changeStringValue(Object obj) throws Exception {
Field[] fields = obj.getClass().getFields();
for(Field field: fields){
if(field.getType() == String.class) //? 为什么不用getClass()
{
String oldValue = (String)field.get(obj);
String newValue = oldValue.replace('b', 'a');
field.set(obj, newValue);
}
}
}
}
下面是例子中用到的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) {
super();
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "ReflectPoint [x=" + x + ", y=" + y + ", str1=" + str1
+ ", str2=" + str2 + ", str3=" + str3 + "]";
}
}
12. Method类, 成员方法的反射:
调用方法的invoke(obj, args)执行, 如果第一个参数为null, 就说明是调用的静态方法.
import java.lang.reflect.Method;
public class Test3 {
public static void main(String[] args) throws Exception{
String str = "asdf";
Method methodCharAt = String.class.getMethod("charAt", int.class);
char c = (char)methodCharAt.invoke(str, 2);
System.out.println(c);
}
}
13. 对接收数组参数的成员方法的反射(调用main方法示例): 为了兼容jdk1.4, 如果直接传入一个数组, 则会把这个数组打散, 使其与传入多个元素相同.
import java.lang.reflect.Method;
class TestArguments{
public static void main(String[] args) {
for(String arg: args){
System.out.println(arg);
}
}
}
public class Test3 {
public static void main(String[] args) throws Exception{
String startingClassName = args[0];
Method mainMethod = Class.forName(startingClassName).getMethod("main", String[].class);
mainMethod.invoke(null, new Object[]{new String[]{"111","222","333"}}); //* 为了兼容JDK1.4, 会把传入的数组打散开. 这里就作为三个元素. 把数组强制转换成(Object)也行.
}
}
14. java.lang.reflect.Array类, 数组反射的应用:
同种元素数据类型, 且相同维度的数组, 反射来的类相同.
没有办法得到数组中元素的类型.
15. 其他:
- 看JDK的源代码方法:
找到D:\Program Files\Java\jdk1.7.0_09\src.zip压缩包, 打开, 按所属包名找到源文件即可.
Eclipse中直接Ctrl+左键点击类名即可跳转. 再Ctrl+O搜索想看的就行. - 反射会导致程序性能严重下降.
- Eclipse显示"生成源代码"相关列表的快捷键: Alt+Shift+S
- * 暴力反射: 反射获取Field时, 对于可见的Field直接用getField(...)方法即可; 对于不可见的Field要使用getDeclaredField(..)获得Field对象, 然后调用Field对象的setAccessible(true)之后才能获取. 这叫做暴力反射.
泛型
16. 泛型应用的综合案例:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
class Test2
{
public static void main(String [] args) throws Exception
{
HashMap<String, Integer> maps = new HashMap<String, Integer>();
maps.put("zxx", 28);
maps.put("lhm", 35);
maps.put("flx", 33);
Set<Map.Entry<String, Integer>> entrySet = maps.entrySet();
for(Map.Entry<String, Integer> entry: entrySet){
System.out
.println(entry.getKey() + ":" + entry.getValue());
}
}
}
17. 自定义泛型方法:
private static <T> add(T x,T y){
return null;
}
- 上面的方法接受两个T型参数, 返回一个T型的值. (不能接受子类)
- 其中的T只能代表引用类型, 不能代表基本类型. 例如: T可以不能代替int,但可以代替由int自动装箱来的Integer.
- 用于放置泛型的类型参数的尖括号应出现在其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前。按照惯例,类型参数通常用单个大写字母表示。
- 伟大的Java还支持上限通配符<? extends Type>, 表示必须是Type本身或其子类; 下限通配符<? super Type>, 表示必须是Type本身或其父类.
类加载器
- BootStrap: 加载JRE/lib/rt.jar中的类.* BootStrap不是Java类, 是在JVM内核中的用C++写的一段二进制代码.
- ExtClassLoader: 加载JRE/lib/ext/*.jar中的类.
- AppClassLoader: 加载CLASSPATH指定的所有jar或目录.
- 首先当前线程的类加载器去加载线程中的第一个类. (可以通过getContextClassCloader()和setContextClassLoader(ClassLoader cl)方法, 获取和设置当前线程的类加载器).
- 如果类A中引用了类B, JVM将使用加载A的加载器来加载B.
- 还可以直接调用ClassLoader.loadClass(...)方法来指定某个类加载器去加载某个类.
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/*
* 编写自定义类加载器, 可以对指定被加密的类解密加载.
*/
public class MyClassLoader extends ClassLoader {
private String classDir;
public MyClassLoader(){}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
public static void main(String[] args) throws Exception{
String srcPath = args[0]; //定义源
String destDir = args[1]; //定义目标
FileInputStream fis = new FileInputStream(srcPath);
String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);
String destPath = destDir + '\\' + destFileName;
FileOutputStream fos = new FileOutputStream(destPath);
cypher(fis, fos);
fis.close();
fos.close();
}
private static void cypher(InputStream ips, OutputStream ops) throws Exception {
int b = -1;
while((b=ips.read()) == -1){
ops.write(b ^ 0xff);
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFileName = classDir + "\\" + name + ".class";
try {
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis, bos);
fis.close();
byte[] bytes = bos.toByteArray();
return defineClass(bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
}
24. 能不能自己写个类叫java.lang.System?
答: 可以, 但是通常写了也没用, 因为类加载器的委托机制, 会一步一步往上找, 最终找到BootStrap, 然后BootStrap会在JRE/lib/rt.jar中找到原有的java.lang.System类.
*? 除非自己写个类加载器, 直接调用ClassLoader.loadClass("java.lang.System")来成功加载.
动态代理
25.* 要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能, 如异常处理、日志等, 可以通过编写一个与目标类具有相同接口的代理类, 代理类的每个方法调用目标类的相同方法, 并在调用方法时加上系统功能的代码.
例子(伪代码):
class X
{
void sayHello(){
syso:hello, itcast
}
}
Xproxy //代理类用来记录运行时间
{
void sayHello(){
starttime
X.sayHello();
endtime
}
}
26. 代理架构图:
动态代理的工作原理图:
27. 交叉业务的编程即为面向方面的编程(Aspect oriented program, AOP), AOP的目标就是要使交叉业务模块化. 可以采用将切面代码移动到原始方法的周围, 这与直接在方法中编写切面代码的运行效果是一样的. 使用代理技术正好可以解决这种问题, 代理是实现AOP功能的核心和关键技术.
28.* 如果所有的代理类都自己写, 太累. 所幸, JVM可以在运行期动态生成出类的字节码, 这种动态生成的类往往被用作代理类, 即动态代理.
- JVM生成的动态类必须实现一个或多个接口, 所以, JVM生成的动态类只能用作具有相同接口的目标类的代理。
- CGLIB库, 非JVM自带, 可以动态生成一个类的子类, 一个类的子类也可以用作该类的代理. 过意, 如果要为一个没有实现接口的类生成动态代理, 可以使用CGLIB类.
29. * 代理类的各个方法通常除了要调用目标的相应方法和返回目标返回的结果外, 还要在代理方法中的如下四个位置上加上增强功能的代码:
- 在调用目标方法之前
- 在调用目标方法之后
- 在调用目标方法前后
- * 在处理目标发方法异常的catch块中
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
import java.util.Collection;
public class ProxyTest {
public static void main(String[] args) {
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy1.getName());
System.out.println("--------------begin constructors list--------------");
Constructor[] constructors = clazzProxy1.getConstructors();
for(Constructor constructor: constructors)
{
String name = constructor.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParams = constructor.getParameterTypes();
for(Class clazzParam: clazzParams) {
sBuilder.append(clazzParam.getName()).append(',');
}
if(clazzParams !=null && clazzParams.length != 0)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(')');
System.out.println(sBuilder);
}
}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
public class ProxyTest {
public static void main(String[] args) throws Exception {
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy1.getName());
System.out.println("------------begin creat instance object-------------");
//clazzProxy1.newInstance() 调用不带参数的构造方法.没有这样的.
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class); //InvocationHandler是个接口
class MyInvocationHandler1 implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}
}
Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1());
System.out.println(proxy1);
}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
public class ProxyTest {
public static void main(String[] args) throws Exception {
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy1.getName());
System.out.println("------------begin creat instance object-------------");
//clazzProxy1.newInstance() 调用不带参数的构造方法.没有这样的.
Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class); //InvocationHandler是个接口
class MyInvocationHandler1 implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}
}
Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler1());
System.out.println(proxy1);
proxy1.clear();
Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});
Collection proxy3 = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(), //加载器
new Class[]{Collection.class}, //所实现接口
new InvocationHandler() { //InvocationHandler
ArrayList target = new ArrayList();
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long beginTime = System.currentTimeMillis();
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + " running time of " + (endTime-beginTime));
return retVal;
}
}
);
proxy3.add("zxx"); //** 每次调用代理的一个方法, 其实都去找InbocationHandelr的invoke方法
proxy3.add("lhm");
proxy3.add("bxd");
System.out.println(proxy3.size());
}
}
34. 代理的稀里糊涂的小例子:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
class Test2
{
public static void main(String [] args) throws Exception
{
//Class proxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
Collection proxy3 = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler() {
ArrayList target = new ArrayList();
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long beginTime = System.currentTimeMillis(); //代理加上的
Object retVal = method.invoke(target, args); //这是原来的
long endTime = System.currentTimeMillis(); //跟下一行都是代理加上的
System.out.println(method.getName() + " running time of " + (endTime-beginTime));
return retVal;
}
}
);
proxy3.add("xxx");
proxy3.add("yyy");
proxy3.add("zzz");
System.out.println(proxy3.size());
}
}
34.* 创建代理类都要用到java.lang.reflect.Proxy类的相关方法.
35. * 对于从java.lang.Object类继承来的方法中, 只有toString()、hashCode()和equals()三个方法委托给InvocationHandler中的invoke调用, 其它的不委托.
36.* 编写可生成代理和插入通告的通用方法:
主要类: Test2.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
class Test2
{
public static void main(String [] args) throws Exception
{
final ArrayList target = new ArrayList();
Collection proxy3 = (Collection)getProxy(target, new MyAdvice());
proxy3.add("xxx");
proxy3.add("yyy");
proxy3.add("zzz");
System.out.println(proxy3.size());
System.out.println(proxy3.getClass().getName());
}
private static Object getProxy(final Object target, final Advice advice) {
Object proxy3 = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
/*new Class[]{Collection.class},*/
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/*long beginTime = System.currentTimeMillis(); //代理加上的
Object retVal = method.invoke(target, args); //这是原来的
long endTime = System.currentTimeMillis(); //跟下一行都是代理加上的
System.out.println(method.getName() + " running time of " + (endTime-beginTime));*/
advice.beforeMethod(method);
Object retVal = method.invoke(target, args); //这是原来的
advice.afterMethod(method);
return retVal;
}
}
);
return proxy3;
}
}
用到的接口: Advice.java
import java.lang.reflect.Method;
public interface Advice {
void beforeMethod(Method method);
void afterMethod(Method method);
}
用到的类: MyAdvice.java
import java.lang.reflect.Method;
public class MyAdvice implements Advice{
long beginTime = 0;
public void afterMethod(Method method) {
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + " running time of " + (endTime-beginTime));
}
public void beforeMethod(Method method) {
beginTime = System.currentTimeMillis();
}
}
结束, 好复杂, 以后主要写MyAdvice.java类就行了.
37. 乱入. StringBuilder与StringBuffer区别: 它们应用上基本一样, 都是动态地往字符串上添加内容. 但是StringBuilder线程不安全, 效率高, StringBuffer线程安全,效率低.