- eclipse开发工具
- eclipse是用javaw来启动图形化界面,不会有dos命令窗口。
- IDE:Integrated Development Environment,集成开发环境。
- IDE开发工具都支持使用工程化方式管理一个项目的程序开发过程,一般来说一个相对独立的项目就是一个工程,一个项目中涉及的多个java文件,资源文件等用一个工程进行管理。
- 配置编译器版本:Window->Preferences->Compiler
- 配置运行工具版本:Window->Preferences->Installed JREs
- 工作间workspace:
- 一个workspace可以包含多个project,一个workspace保留了eclipse的一套环境选项的配置。
- 如果要为eclispe再配置一套环境选项,可以再创建一个workspace。
- 透视图perspective:
- 一个Perspective包含了若干个view(视图)。
- 一个Perspective包含了若干个view(视图)。
- 设置快捷键:Window->Preferences->General->keys
- 设置代码模板:Window->Preferences->java->editor->Templates
- 高版本的java可以运行低版本的javac编译的程序,反之不可以。
- 版本问题会导致的错误提示:bad version number in .class file
- 版本问题会导致的错误提示:bad version number in .class file
- 为某个工程导入jar包:工程右键选择build path->configure build path->libraries
- 当导入的工程和当前默认的运行环境不同时,可以通过build path改变当前运行环境。
- 当导入的工程和当前默认的运行环境不同时,可以通过build path改变当前运行环境。
- eclipse常用快捷键:
ALT+/ 内容助理 Ctrl+1 快速修复 Ctrl+. 下一个 Ctrl+D 删除当前行 Ctrl+ALT+↑ 向上复制当前行 Ctrl+ALT+↓ 向下复制当前行 ALT+↑ 上移当前行 ALT+↓ 下移当前行 Ctrl+Shift+O 导包 Ctrl+Shift+F 格式化代码块 ALT+← 查看代码(向后) ALT+→ 查看代码(向前) Ctrl+/ 添加//注释 Ctrl+Shift+/ 添加/**/注释 Ctrl+Shift+\ 去掉/**/注释 F2 查看方法说明 Ctrl+Shift+X 更改为大写 Ctrl+Shift+Y 更改为小写 Ctrl+T 查看类的继承关系 Ctrl+O 查看代码结构 Ctrl+鼠标 查看源代码 Ctrl+Shift+T 查看源代码(弹出输入框) Ctrl+Shift+L 查看快捷键列表 Ctrl+Shift+Enter 向上插入空行 Shift+Enter 向下插入空行 ALT+Shift+S 快速生成源代码(快速创建构造函数等) Ctrl+M 切换窗口的大小 Ctrl+PageUp 切换到上一个标签页 Ctrl+PageDown 切换到下一个标签页
- eclipse常用调试功能:
F3 跳到声明或定义的地方 F5 step into F6 step over F7 step return F8 继续执行到下一个断点 drop to frame 跳到当前方法的第一行 resume 跳到下一个断点(如果没有,则运行完整个程序) watch 观察变量或表达式的值
- 断点调试的注意事项:
- 断点调试完成后,要在breakpoints视图中清除所有的断点。
- 断点调试完成后,要结束还在运行的JVM。
- 断点调试完成后,要在breakpoints视图中清除所有的断点。
- eclipse是用javaw来启动图形化界面,不会有dos命令窗口。
- Java5的一些简单新特性
- 静态导入
- 参见静态导入
- 可变参数
- 参见可变参数
- 增强for循环
- 参见增强for循环
- 自动拆装箱及享元设计模式
- 自动拆装箱
- 参见自动拆装箱
- 享元设计模式:Flyweight Pattern
- 有很多个小的对象,它们有很多属性相同,把它们变成一个对象。
- 那些不同的属性变为方法的参数,称之为外部状态。
- 相同的属性称之为这个对象的内部状态。
- 比如:
String s1 = "abc";
String s2 = "abc";
s1 == s2;//true
- 有很多个小的对象,它们有很多属性相同,把它们变成一个对象。
- Java5的枚举
- 为什么定义枚举
- 枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。
- 枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。
- 枚举的使用
- 枚举的元素之间用逗号分隔,元素列表末尾的分号可有可无(如果后面还有其他内容,分号不能省略)。例如:
pulbic enum WeekDay{ SUN,MON,TUE,WED,THURS,FRI,SAT }
- 枚举的元素列表必须放在最前面。构造方法放在元素列表之后,构造方法必须为私有的。
- 枚举的元素初始化时调用默认的空参数构造函数。如果希望元素调用带有参数的构造函数初始化,可以在元素名后面加上参数。
- 枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法。
- 枚举的构造方法必须定义为私有的。
- 每一个枚举类型成员都可以看做是枚举类型的一个实例,这些枚举成员默认都被final、static所修饰,所以当使用枚举类型成员时,直接使用枚举类型名称调用枚举类型成员即可。
- 枚举只有一个成员时,就可以作为一种单例的实现方式。
- 枚举的元素之间用逗号分隔,元素列表末尾的分号可有可无(如果后面还有其他内容,分号不能省略)。例如:
- 交通信号灯枚举示例:
打印结果:package cn.itcast.heima; public class Test { public static void main(String[] args) { //分别打印某个信号灯的下一个信号灯 System.out.println(TrafficLamp.GREEN.nextLamp()); System.out.println(TrafficLamp.RED.nextLamp()); System.out.println(TrafficLamp.YELLOW.nextLamp()); } //定义交通灯枚举 public enum TrafficLamp{ //TrafficLamp类型的子类,调用带参数的构造方法创建对象,并实现nextLamp()抽象方法 RED(30){ @Override public TrafficLamp nextLamp() { return GREEN; } }, GREEN(45){ @Override public TrafficLamp nextLamp() { return YELLOW; } }, YELLOW(5){ @Override public TrafficLamp nextLamp() { return RED; } }; //定义抽象方法,由具体子类实现 public abstract TrafficLamp nextLamp(); private int time; //带参数的构造方法,私有 private TrafficLamp(int time){ this.time = time; } } }
- 反射
- Class类
- Person类代表人,它的实例对象就是张三,李四这样一个个具体的人。
- Java程序中的各个Java类,它们也属于同一类事物,因此可以用一个类来描述这类事物呢,这个类的名字就是Class。
- Class类代表Java类,它的各个实例对象分别对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。
- 如何得到各个字节码对应的实例对象( Class类型):
- 类名.class。例如:System.class
- 对象.getClass()。例如:new Date().getClass()
- Class.forName("类名")。如:Class.forName("java.util.Date")
- 类名.class。例如:System.class
- 有九种预定义的 Class 对象,表示八个基本类型和 void。这些类对象由 Java 虚拟机创建,与其表示的基本类型同名,即 boolean、byte、char、short、int、long、float 和 double。这些Class对象还可以通过Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE来获得。并且这些对象的isPrimitive()方法返回true。
- 反射的概念
- 反射不是Java1.5的新特性,从Java1.2开始就有反射了。
- 反射就是把java类中的各种成分映射成相应的java类。
- 表示Java类的Class类提供了一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
- 构造方法的反射
- Constructor类代表某个类中的一个构造方法。
- 获取某个类的构造方法:
- 获取所有构造方法:
- Constructor<?>[] getConstructors();//Class类的方法
- 比如:Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
- Constructor<?>[] getConstructors();//Class类的方法
- 获取某个构造方法:
- Constructor<T> getConstructor(Class<?>... parameterTypes);//Class类的方法,参数为要获取的构造函数的参数的字节码。
- 比如: Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
- Constructor<T> getConstructor(Class<?>... parameterTypes);//Class类的方法,参数为要获取的构造函数的参数的字节码。
- 获取所有构造方法:
- 创建某个类的实例对象:
- 通用方法:
- T newInstance(Object... initargs);//Constructor类的方法,参数为初始化对象的实参。
- 比如:
- 通常方式:String str = new String(new StringBuffer("abc"));
- 反射方式:String str = (String)constructor.newInstance(new StringBuffer("abc"));
- T newInstance(Object... initargs);//Constructor类的方法,参数为初始化对象的实参。
- 调用空参数构造函数创建对象的简便方法:
- T newInstance();//Class类的方法,调用该类的空参数构造函数创建对象。
- 比如:String obj = (String)Class.forName("java.lang.String").newInstance();
- T newInstance();//Class类的方法,调用该类的空参数构造函数创建对象。
- 通用方法:
- Constructor类代表某个类中的一个构造方法。
- 成员变量的反射
- Field类代表某个类中的一个成员变量。
- 获取某个类的成员变量:
- 获取所有成员变量:
- Field[] getFields();//Class类的方法
- Field[] getFields();//Class类的方法
- 获取某个成员变量:
- Field getField(String name);//Class类的方法,参数为成员变量名,返回相应的 Field 对象,不能访问私有成员变量。
- Field getDeclaredField(String name);//Class类的方法,参数为成员变量名,返回相应的 Field 对象,可以访问私有成员变量。
- Field getField(String name);//Class类的方法,参数为成员变量名,返回相应的 Field 对象,不能访问私有成员变量。
- 获取所有成员变量:
- 获取某个对象的成员变量的值:
- Object get(Object obj);//Field类的方法,返回指定对象上此 Field 表示的字段的值。
- Object get(Object obj);//Field类的方法,返回指定对象上此 Field 表示的字段的值。
- 暴力反射
- 当访问的成员变量为私有时,调用get方法获取成员变量的值会导致编译失败。
- 先调用void setAccessible(true);方法将Field设置为可以访问的,再调用get方法获取成员变量的值就能顺利通过编译。这种访问方式称为暴力反射。
- 但是这样仍旧会在运行时,由于访问了私有成员变量而报出异常。
- 当访问的成员变量为私有时,调用get方法获取成员变量的值会导致编译失败。
- 代码举例:
打印结果://将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"a"改成"b"。 package cn.itcast.heima; import java.lang.reflect.Field; public class Test { public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException { ReflectPoint point = new ReflectPoint(); System.out.println(point); changeStringValue(point); System.out.println(point); } //将obj中所有String类型的成员变量所对应的字符串内容中的"a"改成"b" private static void changeStringValue(Object obj) throws IllegalArgumentException, IllegalAccessException { //获取所有成员变量 Field[] fields = obj.getClass().getDeclaredFields(); //遍历成员变量 for (Field field : fields) { //判断成员变量类型是否为String if (field.getType() == String.class) { String oldValue = (String)field.get(obj); String newValue = oldValue.replace('a', 'b'); field.set(obj, newValue); } } } } class ReflectPoint{ String str1 = "zhangsan"; String str2= "lisi"; String str3 = "wangwu"; @Override public String toString() { return str1 + "::" + str2 + "::" + str3; } }
- Field类代表某个类中的一个成员变量。
- 成员方法的反射
-
- Method类代表某个类中的一个成员方法。
- 获取某个类的成员方法:
- 获取所有成员方法:
- Method[] getMethods();//Class类的方法
- 获取某个成员方法:
- Method getMethod(String name, Class<?>... parameterTypes);//Class类的方法,参数为成员方法名和该方法的参数类型列表,返回相应的 Method 对象,不能访问私有成员方法。
- Method getDeclaredMethod(String name, Class<?>... parameterTypes);//Class类的方法,参数为成员方法名和该方法的参数类型列表,返回相应的 Method 对象,可以访问私有成员方法。
- 比如:Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
- 获取所有成员方法:
- 调用某个对象的成员方法:
- Object invoke(Object obj, Object... args);//Method类的方法,obj表示要调用函数的对象,args表示传给调用函数的实参。
- 比如:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));- 如果传递给Method对象的invoke()方法的第一个参数为null,说明该Method对象对应的是一个静态方法!
- Object invoke(Object obj, Object... args);//Method类的方法,obj表示要调用函数的对象,args表示传给调用函数的实参。
- 数组的反射
- 用反射方式执行某个类中的main方法
- 需求:写一个程序,这个程序能够根据用户提供的类名,去执行其它类中的main方法。
- 问题:
- 启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args)
- 通过反射方式来调用这个main方法时,按jdk1.5的语法,整个数组是一个参数;而按jdk1.4的语法,数组中的每个元素对应一个参数。
- 当把一个字符串数组作为参数传递给invoke方法时,jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。
- 启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args)
- 解决办法:
- mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});//将字符串数组封装到Object数组中作为一个Object元素
- mainMethod.invoke(null,(Object)new String[]{"xxx"});//编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了。
- mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});//将字符串数组封装到Object数组中作为一个Object元素
- 需求:写一个程序,这个程序能够根据用户提供的类名,去执行其它类中的main方法。
- 数组的反射
- 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
比如:
int[] a1 = new int[3];
int[] a2 = new int[4];
a1.getClass() == a2.getClass();//true
- 代表数组的Class实例对象调用getSuperClass()方法,返回Object类对应的Class。所以数组类型的父类是Object类。
- 基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;
非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。
- Arrays.asList()方法处理int[]和String[]时的差异:
- static <T> List<T> asList(T... a);//JDK1.5
- static List aslist(Object[] a);//JDK1.4
- 当String[]作为实参传入时,List集合中存的是String类型的元素。
- 当int[]作为实参传入时,int作为基本数据类型无法匹配Object类型,因此int[]整体作为一个Object对象存入List集合中。
- 具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
- 如何得到数组中的元素类型
- 无法直接得到数组前面的类型。
- 需要取出每个元素对象,然后再对各个对象进行判断,因为其中每个具体元素的类型都可以不同。
例如:Object[] x = new Object[]{“abc”,Integer.Max};//无法得到Object这个类型,只能取出数组的元素,分别判断具体某个元素的类型。
- 内存泄漏的举例:
- 当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了。
- 在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。
- 反射的作用
- 实现框架功能。
- 框架要解决的核心问题:
- 因为在写程序时无法知道要被调用的类名,所以在程序中无法直接new某个类的实例对象,而要用反射的方法实现。
- 因为在写程序时无法知道要被调用的类名,所以在程序中无法直接new某个类的实例对象,而要用反射的方法实现。
- 实现框架功能。
- 管理资源和配置文件
- 工程中的配置文件不要写相对路径,要使用绝对路径。但完整的路径不是硬编码,而是运算出来的。
- 可以将工程中要使用的配置文件等放在eclipse的工程的源文件所在的包下,这样eclipse会自动将该文件也复制到class文件所在路径下,即classpath根目录下查找。
- 读取配置文件:使用类加载器,这种方式只能读,不能写。
- class字节码.getClassLoader().getResourceAsStream(文件相对于classpath根目录的路径<开头不能有/>);//系统会在classpath根目录下查找
- class字节码.getResourceAsStream(文件相对于classpath根目录的路径<以/开头>或者文件相对于源文件所在包的相对路径);//作用同上
- class字节码.getClassLoader().getResourceAsStream(文件相对于classpath根目录的路径<开头不能有/>);//系统会在classpath根目录下查找
- 在javaweb中,可以使用getRealPath()得到文件保存的路径,这种方式既能读,又能写。
- 工程中的配置文件不要写相对路径,要使用绝对路径。但完整的路径不是硬编码,而是运算出来的。
- 内省:IntroSpector,它主要用于对JavaBean进行操作。
- JavaBean
- JavaBean是一种特殊的Java类,主要用于传递数据信息,其中的某些方法符合某种命名规则。
- 如果一个Java类中的一些方法符合某种命名规则,则可以把它当作JavaBean来使用。
- 一个JavaBean也可以当做普通Java类来使用。
- JavaBean的属性:
- JavaBean的属性是根据其中的setter和getter方法来确定的,而不是根据其中的成员变量。
- 去掉set前缀,剩余部分就是属性名,如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。
- 例如:
setId()的属性名:id
isLast()的属性名:last
setCPU的属性名:CPU
getUPS的属性名:UPS
- JavaBean的好处:
- JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。
- 用内省这套API操作JavaBean比用普通类的方式更方便。
- JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。
- JavaBean必须要有一个不带参数的构造函数。
- JavaBean是一种特殊的Java类,主要用于传递数据信息,其中的某些方法符合某种命名规则。
- 对JavaBean的内省操作
- 如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中。
- 这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。
- 想要获取JavaBean的某个属性的get或set方法
- 可以直接new一个PropertyDescriptor对象,调用getReadMethod()和getWriteMethod()方法。
- 也可以调用IntroSpector.getBeanInfo方法得到BeanInfo对象,再调用BeanInfo.getPropertyDescriptors()获得所有PropertyDescriptor对象。
- 代码举例:
打印结果://利用JavaBean获取和设置对象的属性值 package cn.itcast.heima; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class Test { public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, IntrospectionException, InvocationTargetException { ReflectPoint reflectPoint = new ReflectPoint(3, 5); String propertyName = "x";//要操作的变量名 //获取reflectPoint对象中propertyName属性的值 Object retVal = getProperty(reflectPoint, propertyName); System.out.println("oldValue:" + retVal); //将新的值赋给reflectPoint对象中propertyName属性 Object newVal = 7; setProperty(reflectPoint, propertyName, newVal); System.out.println("newValue:" + reflectPoint.getX()); } //设置某个对象的某个属性值的方法 public static void setProperty(Object reflectPoint, String propertyName, Object newVal) throws IntrospectionException, IllegalAccessException, InvocationTargetException { //定义PropertyDescriptor对象用于获取propertyName属性的set方法 PropertyDescriptor propertyDescriptor2 = new PropertyDescriptor(propertyName, reflectPoint.getClass()); Method methodSetX = propertyDescriptor2.getWriteMethod(); methodSetX.invoke(reflectPoint, newVal); } //获取某个对象的某个属性值的方法 public static Object getProperty(Object reflectPoint, String propertyName) throws IntrospectionException, IllegalAccessException, InvocationTargetException { //定义PropertyDescriptor对象用于获取propertyName属性的get方法 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(propertyName, reflectPoint.getClass()); Method methodGetX = propertyDescriptor.getReadMethod(); Object retVal = methodGetX.invoke(reflectPoint); return retVal; } } //定义一个JavaBean类 class ReflectPoint{ private int x; private int y; public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public ReflectPoint(int x, int y){ this.x = x; this.y = y; } }
- 如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中。
- 使用BeanUtils工具包操作JavaBean
- 用eclipse如何加入jar包:
- 在工程上创建lib文件夹,将jar文件复制到文件夹中,选中jar文件右键选择build path->add build path。
- 导入的工具包有可能运行时报错,是由于工具包中的类可能还调用了其它工具包中的类,可以根据提示导入其它jar包。
- 在工程上创建lib文件夹,将jar文件复制到文件夹中,选中jar文件右键选择build path->add build path。
- BeanUtils包:可以方便地设置和读取JavaBean的属性。
- 可以利用BeanUtils类直接接收字符串类型的实参和返回字符串类型的结果,用户不需要进行类型转换。
如果不希望自动进行类型转换,可以使用PropertyUtils类。
- 支持属性的级联操作,即属性本身是引用类型,可以调用属性的属性进行设置和读取操作。
- 可以利用BeanUtils类直接接收字符串类型的实参和返回字符串类型的结果,用户不需要进行类型转换。
- 用eclipse如何加入jar包:
- Java5的注解:Annotation
- 注解概述
- java.lang包中的注解:
- @SuppressWarnings:屏蔽警告
- 例如:@SuppressWarnings("deprecation")
- @Deprecated:过时的
- @Override:覆盖
- @SuppressWarnings:屏蔽警告
- 一个注解是也是一个类。
- 注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记。
- javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,有什么标记,就去干相应的事。
- 标记可以加在包,类,字段,方法,方法的参数以及局部变量上。
- java.lang包中的注解:
- 注解的定义与反射调用:
- 注解的应用结构图:
- 自定义注解:
- 定义一个注解
- public @interface A {}
- public @interface A {}
- 把它加在某个类上
- @A
public class B{}
- @A
- 用反射进行测试B的定义上是否有@A
- boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);//Class的方法,判断指定的注解是否存在。
- <A extends Annotation> A getAnnotation(Class<A> annotationClass);//Class的方法,如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。
- boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);//Class的方法,判断指定的注解是否存在。
- 定义一个注解
- 元注解:注解的注解
- @Retention:注解的生命周期,默认取值是CLASS阶段
- RetetionPolicy.SOURCE:java源文件
- RetetionPolicy.CLASS:class文件
- RetetionPolicy.RUNTIME:内存中的字节码
- 注:class文件并不是字节码,jvm将class文件加载到内存中后,才会转为字节码。
- @Override、@SuppressWarnings和@Deprecated的@Retention的值:
- @Override--SOURCE
- @SuppressWarnings--SOURCE
- @Deprecated--RUNTIME
- @Override--SOURCE
- RetetionPolicy.SOURCE:java源文件
- @Target:适用的程序元素的种类,默认值为任何元素
- ElementType.METHOD:适用于方法上
- ElementType.TYPE:适用于类、接口(包括注释类型)或枚举声明上
- 注:适用于多种元素种类时,可以用数组表示。
- ElementType.METHOD:适用于方法上
- @Retention:注解的生命周期,默认取值是CLASS阶段
- 注解的应用结构图:
- 为注解增加属性
- 注解的属性:
- 加了属性的标记效果为:@MyAnnotation(color="red")
- 加了属性的标记效果为:@MyAnnotation(color="red")
- 定义注解的属性:
- 在注解类中增加String color();
- 同接口类似,属性前面省略了public abstract
- @MyAnnotation(color="red")
- 在注解类中增加String color();
- 获取注解的属性:
- 用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法。
- MyAnnotation a = (MyAnnotation)AnnotationTest.class.getAnnotation(MyAnnotation.class);
- 可以认为@MyAnnotation是MyAnnotaion类的一个实例对象
- 用反射方式获得注解对应的实例对象后,再通过该对象调用属性对应的方法。
- 为属性指定缺省值:
- String color() default "yellow";
- String color() default "yellow";
- value属性:
- String value() default "zxx";
- 如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略value=部分,例如:@MyAnnotation("lhm")。
- String value() default "zxx";
- 数组类型的属性:
- int [] arrayAttr() default {1,2,3};
- @MyAnnotation(arrayAttr={2,3,4})
- 如果数组属性中只有一个元素,这时候属性值部分可以省略大括。
- int [] arrayAttr() default {1,2,3};
- 枚举类型的属性:
- EnumTest.TrafficLamp lamp();
- @MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)
- EnumTest.TrafficLamp lamp();
- 注解类型的属性:
- MetaAnnotation annotationAttr() default @MetaAnnotation("xxxx");
- @MyAnnotation(annotationAttr = @MetaAnnotation(“yyy”))
- MetaAnnotation annotationAttr() default @MetaAnnotation("xxxx");
- 注解的详细语法可以通过看java语言规范了解,即看java的language specification。
- 注解的属性:
- Java5的泛型
- 泛型的内部原理及应用
- 术语:
- 整个称为ArrayList<E>泛型类型。
- ArrayList<E>中的E称为类型变量或类型参数。
- 整个ArrayList<Integer>称为参数化的类型。
- ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数。
- ArrayList<Integer>中的<>读为typeof。
- ArrayList称为原始类型。
- 整个称为ArrayList<E>泛型类型。
- 泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入。
- 编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响。
- 对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。
- 由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
- 参数化类型与原始类型的兼容性:
- 参数化类型可以引用一个原始类型的对象,编译报告警告,例如,
Collection<String> c = new Vector();//ok
- 原始类型可以引用一个参数化类型的对象,编译报告警告,例如,
Collection c = new Vector<String>();//ok
- 参数化类型可以引用一个原始类型的对象,编译报告警告,例如,
- 参数化类型不考虑类型参数的继承关系:
- Vector<String> v = new Vector<Object>(); //错误!
- Vector<Object> v = new Vector<String>(); //也错误!
- Vector<String> v = new Vector<Object>(); //错误!
- 编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型,例如,下面语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];
- 编译器只会逐行判断类型安全,因此下面语句可以通过编译:
Vector v1 = new Vector<String>();
Vector<Object> v = v1;
- 下面这两个方法,编译器会报告错误,它不认为是两个不同的参数类型,而认为是同一种参数类型。
private static void applyGeneric(Vector<String> v){}
private static void applyGeneric(Vector<Date> v){}
- 术语:
- 泛型的通配符扩展应用
- 问题:定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?
- 错误方式:
public static void printCollection(Collection<Object> cols) { for(Object obj:cols) { System.out.println(obj); } /* cols.add("string");//没错 cols = new HashSet<Date>();//会报告错误!*/ }
- 正确方式:
public static void printCollection(Collection<?> cols) { for(Object obj:cols) { System.out.println(obj); } //cols.add("string");//错误,因为它不知自己未来匹配就一定是String cols.size();//没错,此方法与类型参数没有关系 cols = new HashSet<Date>();//没错 }
- 总结:
- 使用?通配符可以引用其他各种参数化的类型。
- ?通配符定义的变量主要用作引用,可以调用与类型参数无关的方法,不能调用与类型参数有关的方法。
- ?只能用作引用,不能用它去给其他变量赋值。
- 限定通配符的上边界:
- 正确:Vector<? extends Number> x = new Vector<Integer>();
- 错误:Vector<? extends Number> x = new Vector<String>();
- 正确:Vector<? extends Number> x = new Vector<Integer>();
- 限定通配符的下边界:
- 正确:Vector<? super Integer> x = new Vector<Number>();
- 错误:Vector<? super Integer> x = new Vector<Byte>();
- 正确:Vector<? super Integer> x = new Vector<Number>();
- 在定义泛型时也可以使用extends限定符,并且可以用&来指定多个边界,如:
<V extends Serializable & cloneable> void method(){}
- 也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句的参数中。
- 泛型类型参数的类型推断:
- 当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,
那么根据调用方法时该处的实际应用类型来确定,
这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4);//static <E> void swap(E[] a, int i, int j);
- 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,
如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5);//static <T> T add(T a, T b);
- 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,
如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,
这时候取多个参数中的最大交集类型,
例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f);//static <T> void fill(T[] a, T v);
- 当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,
如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型,
例如,下面语句实际对应的类型就是Integer了,编译将报告错误,
将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x =(3,3.5f);//static <T> T add(T a, T b);
- 参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,
而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(new Integer[5],new String[5]);//static <T> void copy(T[] a,T[] b);
copy(new Vector<String>(), new Integer[5]);//static <T> void copy(Collection<T> a , T[] b);
- 当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,
- 类加载器
- 类加载器及其委托机制
- 类加载器定义:加载类的工具。
- 类加载器作用:将.class文件从硬盘加载进来并进行一些处理,生成字节码。
- Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:
从上至下依次为继承关系
- BootStrap:
- 第一个类加载器,不是java类,不需要被加载,嵌套在JVM内核中。其它类加载器都是java类。
- 如果某个类的类加载器为BootStrap,则打印该类加载器会显示为null。
- 第一个类加载器,不是java类,不需要被加载,嵌套在JVM内核中。其它类加载器都是java类。
- Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类加载器为其父级类加载。
- 当某个.class文件在多个路径下存在,并且不止一个类加载器可以被调用时,父类加载器会优先被调用,这是类加载器的委托机制造成的。
- 类加载器的委托机制:
- 每个类加载器加载类时,又先委托给其上级类加载器。
- 当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException。
- 不会再去找发起者类加载器的儿子,因为没有getChild方法。
- 每个类加载器加载类时,又先委托给其上级类加载器。
- 当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
- 首先当前线程的类加载器去加载线程中的第一个类。
- 如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
- 还可以直接调用ClassLoader的loadClass()方法来指定某个类加载器去加载某个类。
- 首先当前线程的类加载器去加载线程中的第一个类。
- 每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。
- 面试问题:能不能自己写个类叫java.lang.System?
- 为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System。
- 但是也可以自己指定调用自己的类加载器。
- 为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System。
- 类加载器定义:加载类的工具。
- 自定义类加载器
- 自定义的类加载器的必须继承ClassLoader。
- loadClass方法与findClass方法:
- loadClass方法会调用findClass方法。
- loadClass方法中包括了向上查找类加载器等动作,为了避免自定义的类要自己实现这些功能,自定义的类加载器只需要覆盖findClass方法即可,这是一个模板方法设计模式。
- loadClass方法会调用findClass方法。
- defineClass方法:将class文件的二进制形式的内容转成字节码,即生成Class对象。
- 自定义的类加载器的必须继承ClassLoader。
- 动态代理技术
- 代理类的作用与原理及AOP概念
- 需求:
- 要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能。
- 例如,异常处理、日志、计算方法的运行时间、事务管理、等等。
- 要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能。
- 实现原理:
- 编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
- 如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换。
- 譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
- 编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
- OOP:Object Oriented Programming 面向对象编程
- AOP:Aspect oriented Programming 面向方面编程
- AOP的目标就是要使交叉业务模块化。
- 交叉业务:安全,事务,日志等要贯穿到好多个模块中的功能。
- 可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的。
- AOP的目标就是要使交叉业务模块化。
- 代理架构图:
- 需求:要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!
- 解决方法:
- 动态代理类:JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类。
- 动态代理类:JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类。
- JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
- 如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
- CGLIB库:一个第三方库,可以动态生成一个类的子类,一个类的子类也可以用作该类的代理。
- 在代理方法中可以添加系统功能代码的位置:
- 在调用目标方法之前
- 在调用目标方法之后
- 在调用目标方法前后
- 在处理目标方法异常的catch块中
- 在调用目标方法之前
- 需求:
- 创建动态代理类
- 让jvm创建动态类及其实例对象,需要给它提供三个方面的信息:
- 生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知。
- 产生的类字节码必须有个一个关联的类加载器对象。
- 生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
- 生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知。
- InvocationHandler对象的运行原理:
- 动态类中的各个方法的代码:
invoke方法参数:调用objProxy.add("abc")方法时,参数分别为objProxy对象、add方法、"abc"参数。
代理类每调用一个方法,就会去调用InvocationHandler的invoke方法一次
调用代理对象的从Object类继承的方法时,除了hashCode, equals, 或toString这几个方法将调用请求转发给InvocationHandler对象外,对于其他方法,则不转发调用请求。
- 动态类中的各个方法的代码:
- 动态代理的工作原理图:
- 代码举例:
//创建任意目标类的动态代理类,实现每次调用方法都计算运行时间的功能。 package cn.itcast.heima; 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 Test { public static void main(String[] args) throws Exception { final ArrayList target = new ArrayList();//创建目标类 Collection proxy = (Collection)getProxy(target, new MyAdvice());//生成动态代理类 //调用代理类的方法,添加数据并计算程序运行时间 proxy.add("zhangsan"); proxy.add("lisi"); proxy.add("wangwu"); System.out.println("size:" + proxy.size()); } //根据传递进来的参数确定目标类和通告类,并生成动态代理类 private static Object getProxy(final Object target, final Advice advice) { //创建动态代理类的实例对象 Object proxy = Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.beforeMethod();//插入方法前执行的通告 Object retVal = method.invoke(target, args); advice.afterMethod();//插入方法后执行的通告 return retVal; } }); return proxy;//返回动态代理类 } } //定义通告接口 interface Advice { void beforeMethod(); void afterMethod(); } //定义继承通告Advice接口的类 class MyAdvice implements Advice { long beginTime = 0; //插入目标方法之前的代码 @Override public void beforeMethod() { beginTime = System.currentTimeMillis(); } //插入目标方法之后的代码 @Override public void afterMethod() { long endTime = System.currentTimeMillis(); System.out.println("running time : " + (endTime - beginTime)); } }
- 让jvm创建动态类及其实例对象,需要给它提供三个方面的信息:
- 多线程及Java5的线程并发库
- 传统线程技术回顾
- 参见黑马程序员_多线程
- 问题:
- 如果在Thread子类覆盖的run方法中编写了运行代码,也为Thread子类对象传递了一个Runnable对象,那么,线程运行时的执行代码是子类的run方法的代码?还是Runnable对象的run方法的代码?
- 回答:子类的run方法。
- 如果在Thread子类覆盖的run方法中编写了运行代码,也为Thread子类对象传递了一个Runnable对象,那么,线程运行时的执行代码是子类的run方法的代码?还是Runnable对象的run方法的代码?
- 多线程并不会提高cpu的效率,而是在访问服务器时,多线程可以看做是多个用户同时请求服务,从而抢到了更多的带宽,提高访问效率。
- 定时器技术
- Timer类:定时器
- schedule方法可以在指定的时间执行指定的任务。
- schedule方法可以在指定的时间执行指定的任务。
- TimerTask类:抽象类,定时器要执行的任务
- run()方法中存放要执行的任务。
- run()方法中存放要执行的任务。
- quartz库:一个功能强大的java计划任务系统
- 当需用设计复杂的定时任务时可以使用这个库。
- 当需用设计复杂的定时任务时可以使用这个库。
- Timer类:定时器
- 线程范围内共享变量
- 实现线程范围内共享变量,不同线程之间变量相互独立:将不同的线程和变量值作为键值对存入map中,并按照线程对变量进行存取操作,从而实现需求。
- ThreadLocal类用于实现线程范围内的数据共享,作用相当于上面使用的map。
- TreadLocal类应该在封装变量的类中使用,保证主函数中看不到ThreadLocal。
- 实现线程范围内共享变量,不同线程之间变量相互独立:将不同的线程和变量值作为键值对存入map中,并按照线程对变量进行存取操作,从而实现需求。
- Java5的线程并发库
- Java5原子性操作类
- java.util.concurrent.atomic包中的类提供了在单个变量上解除锁的线程安全编程。
- 用原子方式更新变量的值。
- 线程池
- 在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程。
- 任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
- Executors类中有静态方法用于创建线程池:
- newFixedThreadPool方法:创建固定大小的线程池
- newCachedThreadPool方法:创建一个可根据需要创建新线程的线程池
- newSingleThreadExecutor方法:创建单一线程池
- newFixedThreadPool方法:创建固定大小的线程池
- ExecutorService接口中关闭线程池的方法:
- shutdown方法:当所有任务执行完后,关闭ExecutorService。
- shutdownNow:立刻停止所有任务,关闭ExecutorService。
- 用线程池启动定时器:
- 调用ScheduledExecutorService的schedule方法,返回的ScheduleFuture对象可以取消任务。
- 支持间隔重复任务的定时方式,不直接支持绝对定时方式,需要转换成相对时间方式。(计划时间-当前时间)
- 调用ScheduledExecutorService的schedule方法,返回的ScheduleFuture对象可以取消任务。
- 读写锁技术
- 分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。
- 如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁。
- 如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。
- 总之,读的时候上读锁,写的时候上写锁!
- 分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。
- Semaphere同步工具
- 使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。
- 单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。
- Semaphore(int permits, boolean fair);//fair如果为true,则按照等待的顺序获取信号。
- acquire();//获取信号
- release();//释放信号
- 使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。
- CyclicBarrier同步工具
- 表示大家彼此等待,大家集合好后才开始出发,分散活动后又在指定地点集合碰面,这就好比整个公司的人员利用周末时间集体郊游一样,先各自从家出发到公司集合后,再同时出发到公园游玩,在指定地点集合后再同时开始就餐。
- await();//CyclicBarrier类的方法,在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
- 表示大家彼此等待,大家集合好后才开始出发,分散活动后又在指定地点集合碰面,这就好比整个公司的人员利用周末时间集体郊游一样,先各自从家出发到公司集合后,再同时出发到公园游玩,在指定地点集合后再同时开始就餐。
- CountDownLatch同步工具
- 犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。
- countDown();//CountDownLatch类的方法,递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
- await();//CountDownLatch类的方法,当前线程在锁存器倒计数至零之前一直等待
- 犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。
- Exchanger同步工具
- 用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人将一直等待第二个人拿着数据到来时,才能彼此交换数据。
- exchange(V x);//Exchanger类的方法,将要换出的对象传入参数,方法将返回换回的对象。
- 用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人将一直等待第二个人拿着数据到来时,才能彼此交换数据。
- 阻塞队列Queue
- Collection接口的子接口。
- 除了基本的 Collection 操作外,队列还提供其他的插入、提取和检查操作。
- 每个方法都存在两种形式:一种抛出异常(操作失败时),另一种返回一个特殊值(null 或 false,具体取决于操作)
抛出异常 返回特殊值 插入 add(e) offer(e) 移除 remove() poll() 检查 element() peek()
- Collection接口的子接口。
- 同步集合类
- 传统方式下的Collection在迭代集合时,不允许对集合进行修改。
- Java5中提供了如下一些同步集合类用于解决传统集合的问题:
- ConcurrentHashMap
- CopyOnWriteArrayList
- CopyOnWriteArraySet
- ConcurrentHashMap
- 传统方式下的Collection在迭代集合时,不允许对集合进行修改。