Java基础加强
1、Eclipse的使用 (myeclipse 10)
(1)工作空间(workspace)、工程(project)
(2)在eclipse下Java程序的编写和运行,及java运行环境的配置。
(3)快捷键的配置,常用快捷键:
内容提示:Alt + /
快速修复:Ctrl + 1
导包:Ctrl + shift + O
格式化代码块:ctrl + shift + F
向前向后:Alt + 方向键
添加注释 Ctrl+Shift+/
除去注释 Ctrl+Shift+\
(4)程序的调试和运行
F5(跳入) F6(跳过) F7(跳出)
Junit
(5)查看方法说明:F2
重置透视图
更改为大写 Ctrl+Shift+X
更改为小写 Ctrl+Shift+Y
复制行 Ctrl+Alt+向下键(有些不能用) -------------- > win7 下更改 快捷键 (屏幕分辨率---> 高级设置---> ...)
查看源代码
1、ctrl+T
2、ctrl+shift+T
测试用例
assert 类 api
2、JDK 5.0 新特性 1.0
JDK5中新增了很多新的java特性,利用这些新语法可以帮助开发人员编写出更加高效、清晰,安全的代码。
(1)静态导入
(2)自动装箱/拆箱
(3)增强for循环
(4)可变参数
(5)枚举
(6)泛型
(7)元数据
3、静态导入
(1)JDK 1.5 增加的静态导入语法用于导入类的某个静态属性或方法。使用静态导入可以简化程序对类静态属性和方法的调用。
(2)语法:
Import static 包名.类名.静态属性|静态方法|*
(3)例如:
import static java.lang.System.out
import static java.lang.Math.*
4、自动装箱/拆箱
(1)JDK5.0的语法允许开发人员把一个基本数据类型直接赋给对应的包装类变量, 或者赋给 Object 类型的变量,这个过程称之为自动装箱。
(2)自动拆箱与自动装箱与之相反,即把包装类对象直接赋给一个对应的基本类型变量。
(3)典型应用:
List list = new ArrayList();
list.add(1);
int j = (Integer)list.get(0);
5、增强for循环
(1)引入增强for循环的原因:在JDK5以前的版本中,遍历数组或集合中的元素,需先获得数组的长度或集合的迭代器,比较麻烦!
(2)因此JDK5中定义了一种新的语法——增强for循环,以简化此类操作。增强for循环只能用在数组、或实现Iterator接口的集合类上
(3)语法格式:
for(变量类型 变量 :需迭代的数组或集合){
}
例子:
int arr[] = new int[5];
for(int num : arr){
num = 1;
}
System.out.println(arr[0]);
6、可变参数
(1)测试JDK中具有可变参数的类Arrays.asList()方法。分别传多个参、传数组,传数组又传参的情况。
注意:传入基本数据类型数组的问题。
(2)从JDK 5开始, Java 允许为方法定义长度可变的参数。语法:
public void foo(int … args){
}
(3)注意事项:
调用可变参数的方法时, 编译器将自动创建一个数组保存传递给方法的可变参数,因此,程序员可以在方法体中以数组的形式访问可变参数
可变参数只能处于参数列表的最后, 所以一个方法最多只能有一个长度可变的参数
7、枚举类
(1)为什么需要枚举?
一些方法在运行时,它需要的数据不能是任意的,而必须是一定范围内的值,此类问题在JDK5以前采用自定义带有枚举功能的类解决,Java5以后可以直接使用枚举予以解决。
(2)JDK 5新增的 enum 关键字用于定义一个枚举类。
(3)枚举类具有如下特性:
1、枚举类也是一种特殊形式的Java类。
2、枚举类中声明的每一个枚举值代表枚举类的一个实例对象。
3、与java中的普通类一样,在声明枚举类时,也可以声明属性、方法和构造函数,但枚举类的构造函数必须为私有的(这点不难理解)。
4、枚举类也可以实现接口、或继承抽象类。
5、JDK5中扩展了swith语句,它除了可以接收int, byte, char, short外,还可以接收一个枚举类型。
若枚举类只有一个枚举值,则可以当作单态设计模式使用。
练习:请编写一个关于星期几的枚举WeekDay,要求:
枚举值:Mon,Tue,Wed,Thu,Fri,Sat,Sun
该枚举要有一个方法,调用该方法返回中文格式的星期。
(3)Java中声明的枚举类,均是java.lang.Enum类的孩子,它继承了Enum类的所有方法。常用方法:
name()
ordinal()
valueof(Class enumClass, String name)
values() 此方法虽然在JDK文档中查找不到,但每个枚举类都具有该方法,它遍历枚举类的所有枚举值非常方便。
8、反射
(1)什么是反射?
反射就是把Java类中的各种成分映射成一个个的java对象(加载类,解剖出类的各个组成部分)。例如,一个类有:成员变量,方法,构造方法,包等等信息,利用反射技术可以对一个类进行解剖,把各个组成部分映射成一个个对象。
(2)反射用在哪里?
(3)学习反射应该掌握些什么?
9、Class类
(1)Class类用于表示.class文件,画图演示一个对象的创建过程。
(2)如何得到某个class文件对应的class对象。
类名.class,
对象.getClass()
Class.forName(“类名”)
(3)数组类型的Class实例对象
Class.isArray()
(4)总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int,void…
10、Constructor类
(1)Constructor类的实例对象代表类的一个构造方法。
(2)得到某个类所有的构造方法,例:
Constructor [] constructors= Class.forName("java.lang.String").getConstructors();
(3)得到某一个构造方法,例:
Constructor constructor = Class.forName(“java.lang.String”).getConstructor(StringBuffer.class);
(4)利用构造方法创建实例对象:
String str = (String)constructor.newInstance(“abc”);
(5)Class类的newInstance()方法也可创建类的实例,其内部工作原理是先得无参的构造方法,再用构造方法创建实例对象。
String obj =(String)Class.forName("java.lang.String").newInstance();
(6)String obj =(String)Class.forName(“java.lang.String”).newInstance(); 这个也是 为什么 大家在学 基础班的时候 , 老师跟你说 如果你的 一个类定义了一个有参的构造函数,那么还需要在定义一个无参的构造函数.
11、Field类
(1)Field类代表某个类中的一个成员变量
(2)问题:得到的Field对象是对应到类上面的成员变量,还是对应到对象上的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldX 代表的是x的定义,而不是具体的x变量。(注意访问权限的问题)
(3)示例代码:
ReflectPoint point = new ReflectPoint(1,7);
Field y = Class.forName("cn.itcast.corejava.ReflectPoint").getField("y");
System.out.println(y.get(point));
//Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getField("x");
Field x = Class.forName("cn.itcast.corejava.ReflectPoint").getDeclaredField("x");
x.setAccessible(true);
System.out.println(x.get(point));
12、Method类
(1)Method类代表某个类中的一个成员方法
(2)得到类中的某一个方法:
例子: Method charAt = Class.forName("java.lang.String").getMethod("charAt", int.class);
(3)调用方法:
通常方式:System.out.println(str.charAt(1));
反射方式: System.out.println(charAt.invoke(str, 1));
如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!
(4)jdk1.4和jdk1.5的invoke方法的区别:
Jdk1.5:public Object invoke(Object obj,Object... args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str”, new Object[]{1})形式。
13、用反射方式执行某个类中的main方法
(1)目标:
写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。用普通方式调完后,大家要明白为什么要用反射方式去调啊?
(2)问题:
启动Java程序的main方法的参数是一个字符串数组,即public static void main(String[] args),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定要兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用代码mainMethod.invoke(null,new String[]{“xxx”}),javac只把它当作jdk1.4的语法进行理解,而不把它当作jdk1.5的语法解释,因此会出现参数类型不对的问题。
(3)解决办法:
mainMethod.invoke(null,new Object[]{new String[]{"xxx"}});
mainMethod.invoke(null,(Object)new String[]{"xxx"}); ,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会数组打散成若干个参数了
14、内省(Introspector) — JavaBean
(1)什么是JavaBean和属性的读写方法?
(2)访问JavaBean属性的两种方式:
直接调用bean的setXXX或getXXX方法。
通过内省技术访问(java.beans包提供了内省的API),内省技术访问也提供了两种方式。
通过PropertyDescriptor类操作Bean的属性
通过Introspector类获得Bean对象的 BeanInfo,然后通过 BeanInfo 来获取属性的描述器( PropertyDescriptor ),通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法,然后通过反射机制来调用这些方法。
15、内省—beanutils工具包
(1)Apache组织开发了一套用于操作JavaBean的API,这套API考虑到了很多实际开发中的应用场景,因此在实际开发中很多程序员使用这套API操作JavaBean,以简化程序代码的编写。
(2)Beanutils工具包的常用类:
BeanUtils
PropertyUtils
ConvertUtils.regsiter(Converter convert, Class clazz)
自定义转换器
16、泛型(Generic)—泛形的作用
(1)JDK5以前,对象保存到集合中就会失去其特性,取出时通常要程序员手工进行类型的强制转换,这样不可避免就会引发程序的一些安全性问题。例如:
(2)ArrayList list = new ArrayList();
list.add("abc");
Integer num = (Integer) list.get(0); //运行时会出错,但编码时发现不了
list.add(new Random());
list.add(new ArrayList());
for(int i=0;i<list.size();i++){
(?)list.get(i); //此处取出来的对象应转换成什么类型
}
(3)JDK5中的泛形允许程序员在编写集合代码时,就限制集合的处理类型,从而把原来程序运行时可能发生问题,转变为编译时的问题,以此提高程序的可读性和稳定性(尤其在大型程序中更为突出)。
(4)注意:泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。
(5)泛形的基本术语,以ArrayList<E>为例:<>念着typeof
ArrayList<E>中的E称为类型参数变量
ArrayList<Integer>中的Integer称为实际类型参数
整个称为ArrayList<E>泛型类型
整个ArrayList<Integer>称为参数化的类型ParameterizedType
17、泛型典型应用
(1)使用迭代器迭代泛形集合中的元素。
(2)使用增强for循环迭代泛形集合中的元素。
(3)存取HashMap中的元素。
(4)使用泛形时的几个常见问题:
使用泛形时,泛形类型须为引用类型,不能是基本数据类型
ArrayList<String> list = new ArrayList<Object>();
ArrayList<Object> list = new ArrayList<String>();
ArrayList<String> list = new ArrayList ();// 兼容老程序
ArrayList list = new ArrayList<String>();
18、自定义泛形——泛型方法
(1)Java程序中的普通方法、构造方法和静态方法中都可以使用泛型。方法使用泛形前,必须对泛形进行声明,语法:<T> ,T可以是任意字母,但通常必须要大写。<T>通常需放在方法的返回值声明之前。例如:
public static <T> void doxx(T t);
(2)练习:
编写一个泛形方法,实现指定位置数组元素的交换。
编写一个泛形方法,接收一个任意数组,并颠倒数组中的所有元素。
(3)注意:
只有对象类型才能作为泛型方法的实际参数。
在泛型中可以同时有多个类型,例如:
public static <K,V> V getValue(K key) { return map.get(key);}
/点到任意数组中的指定位置上的两个元素
//[1,2,3] 0 2 [3,2,1]
public static <T> void m1(T[] t,int index1,int index2){
//缓冲区
T temp = t[index1];
t[index1] = t[index2];
t[index2] = temp;
}
//颠倒任意数组中的元素顺序
//[1,2,3] [3,2,1]
public static <T> void reverse(T[] t){
int startIndex = 0;
int endIndex = t.length -1;
while(startIndex<endIndex){
T temp = t[startIndex];
t[startIndex] = t[endIndex];
t[endIndex] = temp;
startIndex++;
endIndex--;
}
}
package com.itheima.base;
//把泛型定义在了类上面。但是只对实例方法有效
public class Demo2<T> {
//<T>泛型的定义,后面的T就是使用了
//必须先定义才能使用。定义的语法就是<T>(字符随便),但是必须放在返回值的前面
public T findOne(){
return null;
}
public void get(Class<T> clazz){
}
//静态方法总是需要单独定义泛型类型
public static <T> void get1(Class<T> clazz){
}
}
19、自定义泛形——泛型类和反射泛形
(1)如果一个类多处都要用到同一个泛型,这时可以把泛形定义在类上(即类级别的泛型),语法格式如下:
public class GenericDao<T> {
private T field1;
public void save(T obj){}
public T getId(int id){}
}
(2)注意,静态方法不能使用类定义的泛形,而应单独定义泛形。
(3)泛形的典型应用:BaseDao和反射泛型
package com.itheima.dao;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import org.hibernate.Session;
public class BaseDao<T> implements Dao<T> {
private Session session;
private Class clazz;
// public void setClazz(Class clazz) {//注入
// this.clazz = clazz;
// }
public BaseDao(){//注入
//给clazz赋值,知道具体子类的操作对象到底是什么类型的
Class cz = this.getClass();//当前实例对象
//System.out.println(cz.getName());
//得到参数化的类型
ParameterizedType type = (ParameterizedType)cz.getGenericSuperclass();// BaseDao<Book> BaseDao<Product>
clazz = (Class)type.getActualTypeArguments()[0];//得到第一个实际的泛型参数类型
}
public void add(T t) {
session.save(t);
}
public void update(T t) {
session.update(t);
}
public void delete(Serializable id) {
T t = findOne(id);
session.delete(t);
}
public T findOne(Serializable id) {
return (T)session.get(clazz, id);
}
}
package com.itheima.dao;
public class Book {
}
package com.itheima.dao;
public interface BookDao extends Dao<Book>{
}
package com.itheima.dao;
public class BookDaoImpl extends BaseDao<Book> implements BookDao {
}
package com.itheima.dao;
public class Client {
public static void main(String[] args) {
//ProductDao pDao = new ProductDaoImpl();
BookDao bDao = new BookDaoImpl();
}
}
package com.itheima.dao;
import java.io.Serializable;
public interface Dao<T> {
/**
* 执行添加实体的功能
* @param t
*/
void add(T t);
/**
* 更新记录
* @param t
*/
void update(T t);
/**
* 按照主键删除记录
* @param id
*/
void delete(Serializable id);
/**
* 按照主键查询一条记录
* @param id
* @return
*/
T findOne(Serializable id);
}
package com.itheima.dao;
public class Product {
}
package com.itheima.dao;
import java.util.List;
//操作产品的dao
public interface ProductDao extends Dao<Product> {
//根据条件进行查询
List<Product> findByCondition(String where);
}
package com.itheima.dao;
import java.util.List;
public class ProductDaoImpl extends BaseDao<Product> implements ProductDao {
//public ProductDaoImpl(){
//super(Product.class);
//}
public List<Product> findByCondition(String where) {
return null;
}
}
20、泛型的高级应用——通配符
(1)定义一个方法,接收一个集合,并打印出集合中的所有元素,如下所示:
void print (Collection<String> c) {
for (String e : c) {
System.out.println(e);
}
}
(2)问题:该方法只能打印保存了String对象的集合,不能打印其它集合。通配符用于解决此类问题,方法的定义可改写为如下形式:
void print (Collection<?> c) {//Collection<?>(发音为:"collection of unknown")
for (Object e : c) {
System.out.println(e);
}
}
(3)此种形式下需要注意的是:由于print方法c参数的类型为Collection<?>,即表示一种不确定的类型,因此在方法体内不能调用与类型相关的方法,例如add()方法。
(4)总结:使用?通配符主要用于引用对象,使用了?通配符,就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。
import java.util.ArrayList;
import java.util.Collection;
public class Demo3 {
public static void main(String[] args) {
m1(new ArrayList<String>());
}
//泛型通配符
public static void m1(Collection<?> collection){
// collection.add(1);//使用通配符,由于泛型的类型不确定,因此方法内部不能调用与泛型有关的方法
for(Object o:collection)
System.out.println(o);
}
}
21、泛型的高级应用——有限制的通配符
(1)限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
(2)限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
22、Annotation(注解) 概述
(1)从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是 Annotation(注解)。
(2)什么是Annotation,以及注解的作用?三个基本的 Annotation:
@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示某个程序元素(类, 方法等)已过时
@SuppressWarnings: 抑制编译器警告.
package com.itheima.annotation;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class Demo1 {
@SuppressWarnings({"all"})
public static void main(String[] args) {
Date d = new Date();
System.out.println(d.toLocaleString());
List list = new ArrayList();
int i = 10;
mm();
}
@Override
public String toString() {
return super.toString();
}
@Deprecated
public static void mm(){
}
}
(3)Annotation 其实就是代码里的特殊标记, 它用于替代配置文件,也就是说,传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行。在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。
(4)掌握注解技术的要点:
如何定义注解
如何反射注解,并根据反射的注解信息,决定如何去运行类
23、自定义 Annotation
(1)定义新的 Annotation 类型使用 @interface 关键字
(2)声明注解的属性
注解属性的作用:原来写在配置文件中的信息,可以通过注解的属性进行描述。
Annotation 的属性声明方式:String name();
属性默认值声明方式:String name() default “xxx”;
特殊属性value:如果注解中有一个名称value的属性,那么使用注解时可以省略value=部分,如@MyAnnotation(“xxx")
特殊属性value[];
package com.itheima.annotation;
//都是Annotation子类
public @interface MyAnnotation1 {
String name();
int age();
String gender() default "male";
MyAnnotation2[] initParam();
}
package com.itheima.annotation;
public @interface MyAnnotation2 {
String paramName() default "";
String paramValue() default "";
}
package com.itheima.annotation;
public @interface MyAnnotation3 {
String value();
String name();
}
package com.itheima.annotation;
public @interface MyAnnotation4 {
String[] value();
}
package com.itheima.annotation;
public class MyAnnotationTest {
@MyAnnotation1(name = "shit", age = 18, gender = "female", initParam = {
@MyAnnotation2(paramName = "a", paramValue = "b"),
@MyAnnotation2(paramName = "x", paramValue = "y") })
//@MyAnnotation3(value="abc")
@MyAnnotation3(value="abc",name="xxx")//abc就是value属性赋值
@MyAnnotation4({"abc","def"})
public void m1() {
}
}
24、JDK 的元 Annotation
(1)元 Annotation指修饰Annotation的Annotation。JDK中定义了如下元Annotation:
(2)@Retention: 只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 可以保留的域, @Rentention 包含一个 RetentionPolicy 类型的成员变量, 通过这个变量指定域。
RetentionPolicy.CLASS: 编译器将把注解记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注解. 这是默认值
RetentionPolicy.RUNTIME:编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注解. 程序可以通过反射获取该注释
RetentionPolicy.SOURCE: 编译器直接丢弃这种策略的注释
(3)@Target:指定注解用于修饰类的哪个成员. @Target 包含了一个名为 value,类型为ElementType的成员变量。
(4)@Documented: 用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档.
(5)@Inherited: 被它修饰的 Annotation 将具有继承性.如果某个类使用了被 @Inherited 修饰的 Annotation, 则其子类将自动具有该注解
package com.itheima.annotation.app1;
public class UserDaoImplTest {
private UserDaoImpl dao = new UserDaoImpl();
/**
* 测试添加操作
*/
@MyTest
public void testAddUser() {
dao.addUser();
}
@MyTest
public void testDelUser() {
dao.delUser();
}
}
package com.itheima.annotation.app1;
public class UserDaoImpl {
public void addUser(){
System.out.println("addUser");
}
public void delUser(){
System.out.println("delUser");
}
}
package com.itheima.annotation.app1;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface MyTest {
}
public class MyTestRunner {
public static void main(String[] args) throws Exception {
runTest(UserDaoImplTest.class);
}
/**
* 执行测试
* @param clazz测试类的的字节码
* @throws Exception
*/
public static void runTest(Class clazz) throws Exception{
//只有@MyTest注解的方法才是测试方法
//得到测试类中的所有方法 Tips:Class Method Field Constructor都实现了AnnotatedElement接口
Method ms[] = clazz.getMethods();
//遍历:看看谁的上面有@MyTest
for(Method m:ms){
//谁有就执行谁
boolean b = m.isAnnotationPresent(MyTest.class);
if(b)
m.invoke(clazz.newInstance(), null);
}
}
}
package com.itheima.annotation.app2;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Limit {
float value();
}
package com.itheima.annotation.app2;
public class Client {
public static void main(String[] args) throws Exception {
Account a = new Account(1000000);
a.drawMoney(3000);
}
}
package com.itheima.annotation.app2;
import java.lang.reflect.Method;
public class Account {
private float amount;//余额
public Account(float amount){//开户
this.amount = amount;
}
//取款
@Limit(4000)
public void drawMoney(float money) throws Exception{
//先判断余额足吗
if(money>amount)
throw new RuntimeException("余额不足");
//用传统方式
//ResourceBundle rb = ResourceBundle.getBundle("com.itheima.annotation.app2.cfg");
//String sLimit = rb.getString("limit");
//用注册方式
Class clazz = Account.class;
Method m = clazz.getMethod("drawMoney", float.class);//得到取款这个方法
Limit limitAnno = m.getAnnotation(Limit.class);//得到方法上的Limit注解
float limit = limitAnno.value();//取属性的值
//判断一次取款是否超限:2K 3K
//float limit = Float.parseFloat(sLimit);
if(money>limit)
throw new RuntimeException("一次取款不能超过"+limit);
amount = amount-money;
System.out.println("您本次取款:"+money+",余额是:"+amount);
}
}
25、提取 Annotation 信息
(1)JDK 5.0 在 java.lang.reflect 包下新增了 AnnotationElement 接口, 该接口代表程序中可以接受注释的程序元素
(2)当一个 Annotation 类型被定义为运行时 Annotation 后, 该注释才是运行时可见, 当 class 文件被载入时保存在 class 文件中的 Annotation 才会被虚拟机读取
(3)程序可以调用 AnnotationElement 对象的如下方法来访问 Annotation 信息
26、动态代理
(1)明确两个概念:
代理对象存在的价值:主要用于拦截对真实业务对象的访问。
代理对象有什么方法?
(2)现在要生成某一个对象的代理对象,这个代理对象通常也要编写一个类来生成,所以首先要编写用于生成代理对象的类。
(3)如何编写生成代理对象的类,两个要素:
代理谁
如何生成代理对象
(4)代理谁?
设计一个类变量,以及一个构造函数,记住代理类 代理哪个对象。
(5)如何生成代理对象?
设计一个方法生成代理对象(在方法内编写代码生成代理对象是此处编程的难点)
(6)Java提供了一个Proxy类,调用它的newInstance方法可以生成某个对象的代理对象,使用该方法生成代理对象时,需要三个参数:
1.生成代理对象使用哪个类装载器
2.生成哪个对象的代理对象,通过接口指定
3.生成的代理对象的方法里干什么事,由开发人员编写handler接口的实现来指定。
(7)初学者必须理解,或不理解必须记住的2件事情:
Proxy类负责创建代理对象时,如果指定了handler(处理器),那么不管用户调用代理对象的什么方法,该方法都是调用处理器的invoke方法。
由于invoke方法被调用需要三个参数:代理对象、方法、方法的参数,因此不管代理对象哪个方法调用处理器的invoke方法,都必须把自己所在的对象、自己(调用invoke方法的方法)、方法的参数传递进来。
package com.itheima.proxy;
public class SpringBrother implements Human {
public void sing(float money) {
System.out.println("拿到"+money+"钱,开唱");
}
public void dance(float money) {
System.out.println("拿到"+money+"钱,开跳");
}
public void eat() {
System.out.println("吃饭");
}
}
package com.itheima.proxy;
public interface Human {
void sing(float money);
void dance(float money);
void eat();
}
package com.itheima.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
final Human h = new SpringBrother();
//动态得到代理人
/**
* loader:代理人使用的类加载器。与被代理人h使用的是一样的
* interfaces:代理人要实现的接口。与被代理人h所实现的接口是一样的
* InvocationHandler h:策略设计模式。具体该怎么代理?
*/
Human proxyHuman = (Human)Proxy.newProxyInstance(h.getClass().getClassLoader(), h.getClass().getInterfaces(), new InvocationHandler() {
//具体怎么代理:具体代理策略
/**
* 调用被代理人的任何方法都会执行该方法
* proxy:代理对象的引用
* method:当前执行的是什么方法
* args:当前执行的方法需要的参数
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// System.out.println("被代理了");
if("sing".equals(method.getName())){
//判断出场费
float money = (Float)args[0];
if(money>=10000){
return method.invoke(h, money/2);
}
return null;
}else if("dance".equals(method.getName())){
//判断出场费
float money = (Float)args[0];
if(money>=20000){
return method.invoke(h, money/2);
}
return null;
}else{
return method.invoke(h, args);
}
}
});
proxyHuman.sing(20000);
proxyHuman.dance(30000);
proxyHuman.eat();
}
}
27、动态代理应用
(1)在动态代理技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的处理器的invoke方法(这相当于invoke方法拦截到了代理对象的方法调用)。
(2)并且,开发人员通过invoke方法的参数,还可以在拦截的同时,知道用户调用的是什么方法,因此利用这两个特性,就可以实现一些特殊需求,例如:拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。
28、类加载器
(1)类加载器负责将 .class 文件(可能在磁盘上, 也可能在网络上) 加载到内存中, 并为之生成对应的 java.lang.Class 对象
(2)当 JVM 启动时,会形成由三个类加载器组成的初始类加载器层次结构:
29、bootstrap classloader
(1)bootstrap classloader:引导(也称为原始)类加载器,它负责加载Java的核心类。这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。可以通过执行以下代码来获得bootstrap classloader加载了那些核心类库:
因为JVM在启动的时候就自动加载它们,所以不需要在系统属性CLASSPATH中指定这些类库
30、extension classloader
extension classloader -扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中的JAR包。这为引入除Java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的 JAR类包对所有的JVM和system classloader都是可见的。
31、system classloader
(1)system classloader - 系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。
(2)可以通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。
32、全盘负责委托机制 :父类委托机制
(1)classloader 加载类用的是全盘负责委托机制。
(2)全盘负责:即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的其它Class通常也由这个classloader负责载入。
(3)委托机制:先让parent(父)类加载器 寻找,只有在parent找不到的时候才从自己的类路径中去寻找。
(4)类加载还采用了cache机制:如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么修改了Class但是必须重新启动JVM才能生效,并且类只加载一次的原因。