1.类的加载
类加载到内存中,三步,加载,连接,初始化。
加载:将硬盘上的class文件读入到内存中,为其创建class对象
连接:验证 准备 解析
初始化:
加载时机:
创建类的实例
访问类的静态变量
调用类的静态方法
使用反射方式来强制创建某个类或者接口对应的java.lang.class对象
初始化某个类的子类
直接使用java.exe命令来运行某个主类
2.类加载器的概述和分类
类加载器负责将.class加载到内存中,并为之生成class对象。
类加载器机制
根类加载器:Bootstrap ClassLoader java核心类的加载,System.String类等,在rt.jar中
拓展类加载器:Extension ClassLoader ext目录
系统类加载器:System ClassLoader 负责启动JVm启动时加载来自java命令的class文件,以及classpath指定的jar包和类路径
3.反射
在运行状态中,都能知道这个类的方法和属性。对任何对象,都能调用她的属性和方法。这种动态获取类的属性,调用对象的方法,就是java的反射机制。
要想解剖一个类,就必须获取到该类的字节码文件对象。
而解剖使用的就是class类中的方法,所以先要获取每一个字节码文件对应的class类型的对象。
源文件阶段 Person.java class.forName("类名") 读取配置文件
字节码阶段 Person.class Person.class 当做静态方法的锁对象
创建对象阶段 Person p = new Person p.getClass 判断是否为一个字节码对象(equals方法中常见)
4.反射的常用方法
Class.forName()读取配置文件的举例
public static void main(String[] args) throws Exception {
// 创建榨汁机
Juicer j = new Juicer();
// 字符流读取配置文件
BufferedReader br = new BufferedReader(new FileReader("config.properties"));
// 获取该类的字节码文件
Class clazz = Class.forName(br.readLine());
// 创建实例对象
Fruit instance = (Fruit) clazz.newInstance();
j.run(instance);
}
通过反射获取有参构造方法:
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("com.heima.bean.Person");
Constructor c = clazz.getConstructor(String.class, int.class);
Person newInstance = (Person) c.newInstance("张三", 23);
System.out.println(newInstance);
}
通过反射获取成员变量
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("com.heima.bean.Person");
Constructor c = clazz.getConstructor(String.class, int.class);
Person p = (Person) c.newInstance("张三", 23);
// Field field = clazz.getField("name");
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
field.set(p, "李四");
System.out.println(p);
}
clazz的getField可以获取成员变量字段,但是如果是私有的,那么就会报错:
Exception in thread "main" java.lang.NoSuchFieldException: name
getDeclaredField可以获取私有的,属于暴力反射,如果没有setAccessible,就会报错:
Exception in thread "main" java.lang.IllegalAccessException: Class com.heima.reflect.Demo4_Field can not access a member of class com.heima.bean.Person with modifiers "private"
field也可以通过set方法为属性赋值
5.通过反射获取方法及应用
通过反射获取方法
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("com.heima.bean.Person");
Constructor c = clazz.getConstructor(String.class, int.class);
Person p = (Person) c.newInstance("张三", 17);
Method method = clazz.getMethod("eat");
method.invoke(p);
Method method2 = clazz.getMethod("eat", int.class);
method2.invoke(p, 8);
}
get类方法都是宽泛的类获取,获取以后的操作才涉及到具体的对象操作
通过反射越过泛型检查
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<>();
list.add(6);
list.add(8);
System.out.println(list);
Class clazz = Class.forName("java.util.ArrayList");
Method method = clazz.getMethod("add", Object.class);
method.invoke(list, "这是一个字符串");
System.out.println(list);
}
又名:泛型擦除,泛型反射,获取方法,传入参数
因为泛型在编译时期有效,运行时期会被擦除,所以可以通过泛型反射在反省中添加不同类型的数据,需要注意的是add中的参数为E,也就是Object
通过反射写一个通用的设置某个对象的某个属性为指定的值
public static void main(String[] args) throws Exception {
Student s = new Student("张三", 19);
System.out.println(s);
Tool t = new Tool();
t.setProperty(s, "name", "李四");
System.out.println(s);
}
public class Tool {
// 此方法可将obj对象中名为propertyName的属性的值设置为value。
public void setProperty(Object obj, String propertyName, Object value) throws Exception {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(propertyName);
field.setAccessible(true);
field.set(obj, value);
}
}
通过读取配置文件构建实例并调用方法
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new FileReader("config1.properties"));
Class clazz = Class.forName(br.readLine());
DemoClass dc = (DemoClass) clazz.newInstance();
dc.run();
br.
6.动态代理
代理:自己做的事情,请别人来做
动态代理:动态代理就是通过反射生成代理。
在java中,java.lang.reflect包下提供了一个Proxy类和InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。
使用Proxy类的
static Object | newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。 |
完成动态代理,也必须使用InvocationHandler接口的
Object | invoke(Object proxy, Method method, Object[] args) 在代理实例上处理方法调用并返回结果。 |
案例代码:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("权限校验");
method.invoke(target, args); //执行被代理target对象的方法
System.out.println("日志记录");
return null;
}
}
public static void main(String[] args) {
/*UserImp ui = new UserImp();
ui.add();
ui.delete();
System.out.println("-------------------------------");*/
/*
* public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
* InvocationHandler h)
*/
/*MyInvocationHandler m = new MyInvocationHandler(ui);
User u = (User)Proxy.newProxyInstance(ui.getClass().getClassLoader(), ui.getClass().getInterfaces(), m);
u.add();
u.delete();*/
StudentImp si = new StudentImp();
si.login();
si.submit();
System.out.println("-------------------------------");
MyInvocationHandler m = new MyInvocationHandler(si);
Student s = (Student)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), m);
s.login();
s.submit();
}
7.模板设计模式Template
模板方法设计模式就是定义一个算法的骨架,而将具体的算法实现延迟到子类中来实现。
优点:使用模板设计模式,在定义算法骨架的同时,可以灵活地根据需求修改具体算法
缺点:算法骨架改变需要修改具体的抽象类
案例代码:
public class Demo1_Temple {
public static void main(String[] args) {
Demo d = new Demo();
long time = d.getTime();
System.out.println(time);
}
}
abstract class GetTime {
public final long getTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
return end - start;
}
public abstract void code();
}
class Demo extends GetTime {
@Override
public void code() {
int i = 0;
while (i < 100000) {
System.out.println("x");
i++;
}
}
}
定义一个抽象类,用final修饰整个框架的方法,框架内部具体的算法部分实现抽取成为抽象方法,定义子类实现具体的算法。功能实现的时候直接new子类对象,调用final框架方法即可。
8.JDK5新特性:枚举
枚举:将变量的值一一列出来,变量的值只限于列举出来的值的范围内,一年十二个月,一周七天等。
单例:一个类只能有一个实例。
多例类是有有限个实例。
自己实现枚举类三种方法:
public static void demo3() {
Week3 week1 = Week3.week1;
Week3 week2 = Week3.week2;
Week3 week3 = Week3.week3;
}
public static void demo2() {
Week2 week1 = Week2.week1;
Week2 week2 = Week2.week2;
Week2 week3 = Week2.week3;
}
public static void demo1() {
Week week1 = Week.week1;
Week week2 = Week.week2;
Week week3 = Week.week3;
week1.show();
week2.show();
week3.show();
}
public class Week {
public static final Week week1 = new Week();
public static final Week week2 = new Week();
public static final Week week3 = new Week();
private Week() {
}
public void show() {
System.out.println("---");
}
}
public class Week2 {
public static final Week2 week1 = new Week2("周一");
public static final Week2 week2 = new Week2("周二");
public static final Week2 week3 = new Week2("周三");
private String name;
private Week2(String name) {
this.name = name;
System.out.println(name);
}
public void show() {
System.out.println("---");
}
}
public abstract class Week3 {
public static final Week3 week1 = new Week3("周一") {
@Override
public void show() {
System.out.println("今天是周一");
}
};
public static final Week3 week2 = new Week3("周二") {
@Override
public void show() {
System.out.println("今天是周二");
}
};
public static final Week3 week3 = new Week3("周三") {
@Override
public void show() {
System.out.println("今天是周三");
}
};
private Week3(String name) {
System.out.println(name);
}
public abstract void show();
}
无参的,有参的,匿名内部类构建的。
enum类
上面三种自定义枚举的简化版本,可以直接在项目中创建Enum:
public enum Week {
week1, week2, week3;
}
public enum Week2 {
week1("星期一"), week2("星期二"), week3("星期三");
private String name;
private Week2(String name) {
this.setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public enum Week3 {
week1 {
@Override
public void show() {
System.out.println("星期一");
}
},
week2 {
@Override
public void show() {
System.out.println("星期二");
}
},
week3 {
@Override
public void show() {
System.out.println("星期三");
}
};
public abstract void show();
}
枚举的注意事项
定义枚举类要用关键字enum
枚举类的第一行必须是枚举项
枚举类的枚举项后面如果有代码就不能省略;,没有代码可以省略;
所有枚举类都必须是Enum的子类,
枚举类也可以由抽象方法,但是枚举项必须匿名内部类重写该方法。
枚举类可以有构造器,但是一定是private修饰的,默认也是。
枚举在Switch中的使用
Week3 week1 = Week3.week1;
switch (week1) {
case week1:
System.out.println("周一");
break;
case week2:
System.out.println("周二");
break;
case week3:
System.out.println("周三");
break;
}
枚举类的常用方法
案例代码:
public class Test2 {
/**
* int ordinal()
* int compareTo(E o)
* String name()
* String toString()
* <T> T valueOf(Class<T> type,String name)
* values()
* 此方法虽然在JDK文档中查找不到,但每个枚举类都具有该方法,它遍历枚举类的所有枚举值非常方便
*/
public static void main(String[] args) {
Week2 week1 = Week2.week1;
Week2 week2 = Week2.week2;
Week2 week3 = Week2.week3;
System.out.println(week1.ordinal());
System.out.println(week2.ordinal());
System.out.println(week1.compareTo(week2));
System.out.println(week2.compareTo(week3));
System.out.println(week1.name());
System.out.println("----------");
System.out.println(week1.toString());
Week2 valueOf = Week2.valueOf(Week2.class, "week1");
System.out.println(valueOf.toString());
Week2 valueOf2 = Week2.valueOf("week1");
System.out.println(valueOf2.toString());
System.out.println("-----------");
Week2[] values = Week2.values();
for (Week2 week22 : values) {
System.out.println(week22);
}
}
}
9.JDK7的新特性:
A:二进制字面量
B:数字字面量可以出现下划线
C:switch 语句可以用字符串
D:泛型简化,菱形泛型
E:异常的多个catch合并,每个异常用或|
F:try-with-resources 语句,1.7版标准的异常处理代码
10.JDK8的新特性:
接口中可以定义有方法体的方法,如果是非静态,要用default修饰,如果是静态,就不用修饰。
局部内部类使用局部变量在JDK8中不需要加final修饰
为什么以前需要final修饰呢?
调用方法时候,如果没有final修饰,局部变量的生命周期和方法是一样的,方法使用结束会弹栈,局部变量就随之消失,内部类没有马上消失,想要调用局部变量就会失败。而有final修饰,就进入常量池中,即使方法弹栈,不会发生类似情况。