test
day18【枚举、类加载器、动态代理】
一、枚举(Enmu)
1.1 枚举概述
枚举(enum),全称enumeration,是JDK 1.5 中引入的新特性。Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等。
在JDK1.5 之前,我们定义常量都是: public static fianl。有了枚举之后,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法。
1.2 定义枚举类型
1.2.1 静态常量案例
我们使用静态常量来设置一个季节类:
package com.dfbz.demo01_静态成员变量;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Season {
public static final Integer SPRING = 1;
public static final Integer SUMMER = 2;
public static final Integer AUTUMN = 3;
public static final Integer WINTER = 4;
}
1/2/3/4分别代表不同的含义
测试类:
package com.dfbz.demo01_静态成员变量;
import org.junit.Test;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_静态成员变量 {
@Test
public void test1() {
method(Season.SPRING);
method(Season.WINTER);
}
public void method(Integer season) {
switch (season) {
case 1:
System.out.println("Spring!"); // 具体的逻辑
break;
case 2:
System.out.println("Summer!"); // 具体的逻辑
break;
case 3:
System.out.println("Autumn!"); // 具体的逻辑
break;
case 4:
System.out.println("Winter!"); // 具体的逻辑
break;
}
}
}
1.2.2 枚举案例
Java 枚举类使用enum关键字来定义,各个常量使用逗号来分割。
package com.dfbz.demo02;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public enum Season {
SPRING,SUMMER,AUTUMN,WINTER
}
Tips:定义枚举类的关键字是enum,而不是Enum,在Java中所有关键字都是小写的!
其中SPRING、SUMMER、AUTUMN、WINTER都是枚举项,它们都是本类的实例,本类一共就只有四个实例对象。并且只能通过这四个关键字获取Season类的实例对象,不能使用new来创建枚举类的对象
package com.dfbz.demo02_枚举的使用;
import org.junit.Test;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_枚举的使用 {
@Test
public void test1() {
// 通过枚举项来获取枚举实例
Season spring = Season.SPRING;
Season spring2 = Season.SPRING;
// 通过相同的枚举项获取的枚举实例是一样的
System.out.println(spring == spring2); // true
// 不同的枚举项获取的枚举实例是不一样的
Season autumn = Season.AUTUMN;
System.out.println(spring == autumn); // false
}
}
1.2.3 枚举与switch
使用枚举,能让我们的代码可读性更强。
package com.dfbz.demo02_枚举的使用;
import org.junit.Test;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_枚举与switch {
@Test
public void test1() {
Season season = Season.AUTUMN;
switch (season){
case SPRING:
System.out.println("春天~");
break;
case SUMMER:
System.out.println("夏天!");
break;
case AUTUMN:
System.out.println("秋天@");
break;
case WINTER:
System.out.println("冬天&");
break;
default:
System.out.println("错误的季节");
}
}
}
1.3 枚举的用法
1.3.1 枚举类的成员
枚举类和正常的类一样,可以有实例变量,实例方法,静态方法等等
- 定义枚举类:
package com.dfbz.demo02_枚举的使用;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public enum Color {
// 在枚举常量后面还有其他成员时,分号是必须的。枚举常量必须在枚举类中所有成员的上方声明
RED, GREEN, BLUE;
public String aaa = "AAA"; // 普通成员变量
public static String bbb = "BBB"; // 静态成员变量
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void method() {
System.out.println("enum hello~");
}
}
Tips:当枚举项后面有其他成员(构造方法、成员变量、成员方法)时,最后一个枚举项必须加分号;
- 测试类:
package com.dfbz.demo02_枚举的使用;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03_枚举类的成员 {
public static void main(String[] args) {
// 访问静态成员变量
System.out.println(Color.bbb);
// 访问静态成员方法
Color.method();
// 通过枚举项获取实例
Color red = Color.RED;
// 通过枚举实例调用成员方法
red.setName("红色");
// 通过枚举实例调用成员方法
System.out.println(red.getName()); // 红色
}
}
1.3.2 枚举类的构造方法
1)枚举的无参构造方法
枚举类也可以有构造方法,构造方法默认都是private修饰,而且只能是private。因为枚举类的实例不能让外界来创建!
默认情况下,所有的枚举项的创建都是调用枚举类的无参构造方法,并且在获取任何一个枚举实例时,其他实例都将会被创建
package com.dfbz.demo03;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public enum Direction {
// 在枚举常量后面还有其他成员时,分号是必须的。枚举常量必须在枚举类中所有成员的上方声明
FRONT,BEHIND,LEFT,RIGHT;
// 枚举类的构造方法都是private修饰的,可写可不写
Direction(){
System.out.println("Direction创建了...");
}
}
Tips:
- 1:当枚举项后面有其他成员(构造方法、成员变量、成员方法)时,最后一个枚举项必须加分号;
- 2:所有的枚举类的构造方法都是私有的(private关键字可加可不加)
测试类:
package com.dfbz.demo02_枚举的使用;
import org.junit.Test;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo04_枚举类的构造方法 {
/**
* 枚举的无参构造方法
*/
@Test
public void test1() {
// 运行代码将会创建四个枚举实例
Direction behind = Direction.BEHIND;
}
}
Tips:一旦创建了枚举实例,便会初始化里面的所有枚举项;创建枚举项就等同于调用本类的无参构造器,所以FRONT、BEHIND、LEFT、RIGHT四个枚举项等同于调用了四次无参构造器
2)枚举的有参构造方法
枚举项就是枚举类的实例,在创建定义枚举项时其实就是创建枚举类的实例,因此在定义枚举项就要传递实际的参数
- 定义枚举项:
package com.dfbz.demo02_枚举的使用;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public enum Week {
// 枚举项就是枚举类的实例,在创建定义枚举项时其实就是创建枚举的实例,因此在定义枚举项就要传递实际的参数
MONDAY("星期一",1),
TUESDAY("星期二"),
WEDNESDAY,
THURSDAY("星期四",3),
FRIDAY("星期五",4),
SATURDAY("星期六",5),
SUNDAY("星期六",4);
private String name;
private Integer loveLevel;
// 注意: 枚举类的构造方法只能是私有(默认情况下也是私有)
Week() { // 空参构造
}
Week(String name) { // 有参构造
this.name = name;
}
Week(String name, Integer loveLevel) { // 有参构造
this.name = name;
this.loveLevel=loveLevel;
}
public void show() {
System.out.println("我是【" + name + "】,我的喜好程度是【" + loveLevel + "】颗星");
}
@Override
public String toString() {
return "Week{" +
"name='" + name + '\'' +
", loveLevel=" + loveLevel +
'}';
}
}
- 测试类:
/**
* 枚举的有参构造方法
*/
@Test
public void test2() {
// 获取第一个枚举实例时,将会创建所有的枚举实例
Week friday = Week.FRIDAY;
friday.show();
System.out.println(friday);
System.out.println("---------------");
Week saturday = Week.SATURDAY;
saturday.show();
System.out.println(saturday);
System.out.println("---------------");
Week tuesday = Week.TUESDAY;
tuesday.show();
System.out.println(tuesday);
System.out.println("---------------");
Week wednesday = Week.WEDNESDAY;
wednesday.show();
System.out.println(wednesday);
System.out.println("---------------");
}
- 运行效果如下:
1.3.3 枚举中的抽象方法
枚举类中可以包含抽象方法,但是在定义枚举项时必须重写该枚举类中的所有抽象方法;
我们前面说过,每一个枚举项其实都是枚举类的实例对象,因此如果当前枚举类包含抽象方法时,在定义枚举项时就需要重写此枚举类的所有抽象方法,这跟我们以前使用的匿名内部类很相似;
- 首先定义一个抽象类(包含了抽象方法):
package com.dfbz.demo03_枚举的抽象方法;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public abstract class AbstractSeason {
public abstract void fun();
}
- 实例化这个抽象类的时候,我们必须抽象其所有的抽象方法:
package com.dfbz.demo03_枚举的抽象方法;
import org.junit.Test;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_枚举的抽象方法 {
@Test
public void test1() {
// 可以把abstractSeason看做是一个枚举项,在定义枚举项时必须重写枚举类的所有抽象方法
AbstractSeason abstractSeason = new AbstractSeason() {
@Override
public void fun() {
System.out.println("重写了这个抽象类的所有抽象方法");
}
};
}
}
当枚举类中含有抽象方法的时候,定义枚举项时,必须重写该枚举类中所有的抽象方法,像下面这种就是一种错误的定义:
package com.dfbz.demo03_枚举的抽象方法;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public enum Season {
SPRING; // 编译报错,定义枚举项时必须重写枚举类包含的所有抽象方法
public abstract void fun();
}
- 正确写法:
package com.dfbz.demo03_枚举的抽象方法;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public enum Season {
SPRING(){
@Override
public void fun() {
System.out.println("我是春天~");
}
};
public abstract void fun();
}
- 定义多几个枚举项:
package com.dfbz.demo03_枚举的抽象方法;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public enum Season {
SPRING(){
@Override
public void fun() {
System.out.println("我是春天...");
}
},
SUMMER(){
@Override
public void fun() {
System.out.println("我是夏天~");
}
};
public abstract void fun();
}
- 测试类:
@Test
public void test2() {
Season spring = Season.SPRING;
Season summer = Season.SUMMER;
spring.fun();
summer.fun();
}
1.4 Enum 类
1.4.1 Enum类中的方法
Java中,所有的枚举类都默认继承与java.lang.Enum类,这说明Enum中的方法所有枚举类都拥有。另外Enum也继承与Object,因此所有的枚举类都拥有与Object类一样的方法;
Tips:枚举类除了不能拥有Object中的clone、finalize方法外,其他方法都能拥有;
Enum类新增(或重写Object)的方法:
int compareTo(E e):比较两个枚举常量谁大谁小,其实比较的就是枚举常量在枚举类中声明的顺序(ordinal值)boolean equals(Object o):比较两个枚举常量是否相等;Class<E> getDeclaringClass():返回此枚举类的Class对象,这与Object中的getClass()类似;int hashCode():返回枚举常量的hashCode;String name():返回枚举常量的名字;int ordinal():返回枚举常量在枚举类中声明的序号,第一个枚举常量序号为0;String toString():把枚举常量转换成字符串;static T valueOf(Class enumType, String name):把字符串转换成枚举常量。
1.4.2 测试方法功能
- 定义一个枚举类:
package com.dfbz.demo07;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public enum Season {
SPRING,SUMMER,AUTUMN,WINTER;
}
- 测试类:
package com.dfbz.demo04_Enum;
import org.junit.Test;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_Enum类的方法使用 {
Season spring = Season.SPRING; // ordinal:0
Season summer = Season.SUMMER; // ordinal:1
Season autumn = Season.AUTUMN; // ordinal:2
Season winter = Season.WINTER; // ordinal:3
@Test
public void test1() {
System.out.println(spring.compareTo(spring)); // 0-0=0
System.out.println(spring.compareTo(summer)); // 0-1=-1
System.out.println(winter.compareTo(summer)); // 3-1=2
System.out.println(winter.compareTo(spring)); // 3-0=3
}
@Test
public void test2() {
Season spring2 = Season.SPRING;
System.out.println(spring == spring2); // true
System.out.println(spring.equals(spring2)); // 枚举对象的equals方法比较的是内存地址值(返回true)
}
@Test
public void test3() {
Class<Season> clazz = spring.getDeclaringClass();
Class<? extends Season> clazz2 = spring.getClass();
System.out.println(clazz == clazz2); // true
}
@Test
public void test4() {
int hashCode = spring.hashCode();
System.out.println(hashCode); // 2027961269
String name = spring.name();
System.out.println(name); // SPRING
int ordinal_spring = spring.ordinal();
int ordinal_summer = summer.ordinal();
int ordinal_autumn = autumn.ordinal();
int ordinal_winter = winter.ordinal();
System.out.println(ordinal_spring); // 0
System.out.println(ordinal_summer); // 1
System.out.println(ordinal_autumn); // 2
System.out.println(ordinal_winter); // 3
}
@Test
public void test5() {
Season s = Season.valueOf("SPRING"); // 通过枚举项的名称获取枚举项
System.out.println(s == spring); // true(返回的还是同一个枚举项)
}
}
1.4.3 枚举的两个抽象方法
每个枚举类都有两个静态方法,而且这两个方法不是父类中的方法。这又是枚举类特殊的地方;
static T[] values():返回本类所有枚举项;static T valueOf(String name):通过枚举项的名字返回枚举项;
- 测试类:
@Test
public void test6() {
// 获取这个枚举类中所有的枚举项
Season[] values = Season.values();
for (Season value : values) {
System.out.println(value);
}
// 通过枚举项的名称获取枚举项
Season s = Season.valueOf("SPRING");
System.out.println(s == spring); // true
}
二、类加载器
2.1 类加载时机
我们知道,所有的代码都是运行在内存中的,我们必须把类加载到内存中才能运行;在Java中,所有的Java类都是通过类加载器加载到内存进行执行的;
- 一个类何时被加载?
- 1)main方法所在的类总是被首先初始化
- 2)创建该类对象时,首先会将内加载到内存(如果该类存在父类,那么首先加载父类到内存,创建父类的对象(super))
- 3)访问该类的静态成员时,会将类加载到内存(该静态成员不能被fianl修饰)
- 4)class.forName("类的全包名")
package com.dfbz.demo01;
import org.junit.Test;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_类何时被加载 {
@Test
public void test1() throws ClassNotFoundException {
// new B(); // 先加载A再加载B
// Integer num = B.num; // 先加载A再加载B
Class<?> clazz = Class.forName("com.dfbz.demo01.B"); // 先加载A再加载B
}
}
class A {
public static Integer num = 10;
static {
System.out.println("A loader...");
}
}
class B extends A {
public static Integer num = 20;
static {
System.out.println("B loader...");
}
}
Tips:不管是用什么方法加载,类从始至终只会加载一次;
2.3 类加载器
2.3.1 类加载器的种类
- 启动类加载器Bootstrap ClassLoader: 是嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负则加载JAVA_HOME/lib下的类库,启动类加载器无法被应用程序直接使用。
- 扩展类加载器Extension ClassLoader:该加载器器是用JAVA编写,且它的父加载器是Bootstrap,是由sun.misc.Launcher$ExtClassLoader实现的,主要加载JAVA_HOME/lib/ext目录中的类库。开发者可以这几使用扩展类加载器。
- 系统类加载器App ClassLoader:系统类加载器,也称为应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文件(第三方jar)。它的父加载器为Ext ClassLoader。
Tips:这里的父加载器并非是Java中的继承关系,而是我们后面学习双亲委派过程中向上委派的加载器,我们将其称为父加载器;
测试类:
package com.dfbz.demo01;
import com.dfbz.demo02.Demo02;
import com.sun.java.accessibility.AccessBridge;
import org.junit.Test;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_类加载器的种类 {
@Test
public void test1(){
// Bootstrap类加载器是获取不到的,为null
System.out.println("Bootstrap ClassLoader: "+ String.class.getClassLoader());
// jre\lib\ext\access-bridge-64.jar
System.out.println("ExtClassLoader ClassLoader: "+ AccessBridge.class.getClassLoader());
System.out.println("AppClassLoader ClassLoader: "+ Demo02.class.getClassLoader());
}
}
2.3.2 双亲委派机制
从JDK1.2开始,类的加载过程采用双亲委派机制,它是一种任务委派模式。即把加载类的请求交由父加载器处理,一直到顶层的父加载器(BootstrapClassLoader);如果父加载器能加载则用父加载器加载,否则才用子加载器加载该类;
- 示例代码:
package com.dfbz.demo01;
import org.junit.Test;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03_类加载的过程 {
@Test
public void test1() {
Class<T> tClass = T.class;
System.out.println(tClass);
}
class T {
}
}
JVM在加载类时,会调用类加载器(ClassLoader)的loadClass方法进行加载;
ClassLoader类加载源码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 检查该类是否被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 使用父加载器加载
c = parent.loadClass(name, false);
} else {
// 如果没有父加载器则使用BootstrapClassLoader加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 如果依旧没有加载,则调用自身的findClass方法进行加载
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
findClass方法源码:
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
可以看到,默认情况下ClassLoader的findClass方法只是抛出了一个异常而已(这个方法是留给我们写的)
- 双亲委派机制流程图:
- 1)从上图我们可以分析,当一个Demo.class这样的文件要被加载时,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。
- 2)如果没有加载,那么会拿到父加载器(向上委派),然后调用父加载器的loadClass方法进行加载。AppClassLoader的父加载器为ExtClassLoader,而ExtClassLoader并没有重写loadClass方法,因此还是调用ClassLoader类的loadClass方法,相当于是一个递归的操作;
- 3)父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意是个递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。

BootstrapClassLoader不能加载该类,因此还是为null,然后调用本类的findClass方法;这里需要注意两点:
- 1)本类还是ExtClassLoader
- 2)ExtClassLoader是自身没有findClass方法,但ExtClassLoader继承与URLClassLoader,并且URLClassLoader提供有findClass方法;

接下来调用到URLClassLoader类中的findClass方法来加载该类:
URLClassLoader类中的findClass方法无法加载我们传递的类,然后向上抛出了一个异常;这里需要注意:
- 1)URLClassLoader类中的findClass方法是通过ExtClassLoader调用findClass方法进去的,因此向上抛出异常后,findClass方法后面的代码将不会执行了,并且触发的异常继续往上抛给调用者(调用loadClass的对象)
- 2)ExtClassLoader的loadClass方法是在AppClassLoader中,通过parent.loadClass()调用进去的,因此异常被抛到了这里;

异常被抛到了AppClassLoader中的loadClass方法中,接着尝试使用AppClassLoader的findClass()方法来加载类;
最终交给AppClassLoader完成类的加载:
2.3.3 双亲委派的好处
我们已经了解了Java中类加载的双亲委派机制,即加载类时交给父加载器加载,如果父加载器不能加载,再交给子加载器加载;这样做有何好处呢?
- 1)避免类的重复加载:当父类加载器已经加载了该类时,就没有必要子 ClassLoader 再加载一次。
- 2)安全问题:有了双亲委派机制,当有人想要替换系统级别的类或者篡改他的实现时,在双亲委派机制下,在任何的Java代码运行之前,会将所有要用到的系统类提前使用BootstrapClassLoader加载进内存(而当一个类需要被加载时必定会轮到BootstrapClassLoader来加载,只是是否能加载的问题,不能加载的必定不是系统级别的类),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。
在指定的系统包下建立指定的类(由BootstrapClassLoader、ExtClassLo
本文来自博客园,作者:jefften,转载请注明原文链接:https://www.cnblogs.com/jefften/articles/17816631.html









浙公网安备 33010602011771号