Skip to content
字数
11729 字
阅读时间
53 分钟

一、反射

  • class类对象:实例表示正在运行的 Java 应用程序中的类和接口

    java
    // 类 类型对象获取方式
    Class c = 对象.getClass();
    Class c1 = MyClass.class;
    //类的全路径信息
    Class c3 = Class.forName("com.fanshe.MyClass");
    
    // 获取类中的成员方法,返回值为Methods[]数组.数组中为Method对象
    //getMethod()方法中也可传入方法名的字符串,获取该方法的Method对象
    Method[] m = class.getMethods();
    
    //Method类的主要方法
    // 获取方法名
    method.getName();
    // 获取方法的修饰符,返回值为int类型,在java中修饰符对应数字
    method.getModifiers();
    //获取返回值类型
    method.getReturnType();
    //获取形参,返回值为一个Parameter[]数组,中为形参Parameter对象
    Parameter[] psParameters = method.getParameters();
    	//获取形参名
    	parameter.getName();
    	//获取形参类型
    	parameter.getType();
    //执行method方法
    method.invoke(object);
    
    //传入参数为参数数组,返回值为带有指定参数列表的构造方法的 Constructor 对象 
    Constructor<Student> sConstructor = class.getConstructor(String.class,int.class);
    //获取所有的构造方法,返回此类所有已声明的构造方法的 Constructor 对象的数组 
    class.getDeclaredConstructors();
    // 指定的初始化参数初始化该实例
    class.newInstance(Object... initargs);
    
    //获取所有的成员变量,返回Field[]数组,
    Field[] fields = class.getFields();
    //获取成员变量的变量名
    field.getName();
    //获取成员变量的类型
    field.getType();
  • 加载指定目录的class文件

    java
    package com.lly.javabasic.classloader;
    
    import java.io.ByteArrayOutputStream;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * @Classname ClassLoaderExpand
     * @Description 自定义类加载器加载指定目录下的class
     */
    public class ClassLoaderExpand extends ClassLoader {
    
        /**
         * name class 类的文件名
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] datas = loadClassData(name);
            // 带包名此处应携带包名
            return defineClass(packageStr+"."+name, datas, 0, datas.length);
        }
    
        // 指定文件目录
        private String location;
        // 指定加载类的包名 没有不用添加
        private String packageStr;
    
        public String getLocation() {
            return location;
        }
    
        public void setLocation(String location) {
            this.location = location;
        }
    
        public String getPackageStr() {
            return packageStr;
        }
    
        public void setPackageStr(String packageStr) {
            this.packageStr = packageStr;
        }
    
        protected byte[] loadClassData(String name)
        {
            FileInputStream fis = null;
            byte[] datas = null;
            try
            {
                fis = new FileInputStream(location+name+".class");
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                int b;
                while( (b=fis.read())!=-1 )
                {
                    bos.write(b);
                }
                datas = bos.toByteArray();
                bos.close();
            }catch(Exception e)
            {
                e.printStackTrace();
            }
            finally
            {
                if(fis != null)
                    try
                    {
                        fis.close();
                    } catch (IOException e)
                    {
                        e.printStackTrace();
                    }
            }
            return datas;
    
        }
    
        public static void main(String[] args) {
            ClassLoaderExpand classLoaderExpand = new ClassLoaderExpand();
            classLoaderExpand.setPackageStr("compiling");
            classLoaderExpand.setLocation("E:\\IdeaWorkRoom\\note_project\\plan\\src\\main\\resources\\compiling\\");
            try {
                //调用 通过字节流生产java类
                Class cl=classLoaderExpand.findClass("TestCompilingClass");
    
                //这里是调用带参数的方法,参数是数组对象
                Method method   =cl.getMethod("main",String[].class);
    
                System.out.println(method.getName());
    
                method.invoke(cl, (Object)new String[]{});
    
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

二、stream

  • 优点:是将集合中的所有元素放入流中,在流中进行操作,可以使用lamda表达式。简化集合操作

  • 主要方法

    java
    // 获取流对象
    Stream<Person> stream = collection.stream();
    Stream.of(集合或数组);
    //去重
    stream.distinct();
    //遍历
    stream.forEach(object->{每个元素的操作});
    //映射 类型转换
    stream.map();
    //过滤
    stream.filter();
    //截取数量
    stream.limit();
    //跳过前几个
    stream.skip();
    //组合,两个流合并 静态方法
    Stream.concat();
    //排序
    stream.sorted((o1,o2)->{比较器逻辑});
    //最小值
    stream.min((o1,o2)->{比较器逻辑});
    //最大值
    stream.max((o1,o2)->{比较器逻辑});
    // 并行流 多线程版
    stream.parallel();
    // 转换为int流
    stream.mapToInt();
    //相加
    stream.sum();
    
    //汇总流
    IntStream is = stream.parallel().mapToInt((o)->o.getInt());
    // 获取汇总流
    IntSummaryStatistics sum = is.summaryStatistics();
    //获取数量
    sum.getCount()
    //获取平均值
    sum.getAverage();
    //获取最大值
    sum.getMax();
    //  获取最小值
    sum.getMin()
    // 求和
    sum.getSum()

三、函数式编程

  • 函数式编程:使用函数式接口,结合lamda表达式,简化代码书写。语法糖

  • @FunctionalInterface注解标识该接口为一个函数式接口,编译器检查该接口是否有且仅有一个抽象方法,否则会报错。函数式接口不添加该注解也可正常使用。注解起编译器检查接口的作用。

  • 常用函数式接口

    java
    // 用来获取一个泛型参数指定类型的对象数据
    public interface Supplier<T> {
         T get();
    }
    
    // 与Supplier接口相反,消费一个数据
    public interface Consumer<T> {
        void accept(T t);
    }
    
    // 判断结果的接口 and 与 or 或  negate 非
    public interface Predicate<T> {
        boolean test(T t);
    }
    // 数据转换 将一个类型转换为另一个类型
    public interface Function<T, R> {
        R apply(T t);
    }

四、注解

元注解概述
    * Java官方提供的注解
    * 用来定义注解的注解
    * 任何官方提供的非元注解的定义都使用到了元注解。

常用的元注解
    * @Target
        * 作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。
        * 可使用的值定义在ElementType枚举类中,常用值如下
            TYPE,类,接口
            FIELD, 成员变量
            METHOD, 成员方法
            PARAMETER, 方法参数
            CONSTRUCTOR, 构造方法
            LOCAL_VARIABLE, 局部变量

    * @Retention
        * 作用:用来标识注解的生命周期(有效范围)
        * 可使用的值定义在RetentionPolicy枚举类中,常用值如下
            * SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
            * CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
            * RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段
什么是注解解析
        * 使用Java技术获得注解上数据的过程则称为注解解析。
    与注解解析相关的接口
        * Annotation: 注解类,该类是所有注解的父类。
        * AnnotatedElement:该接口定义了与注解解析相关的方法
             T getAnnotation(Class<T> annotationClass) 根据注解类型获得对应注解对象
             Annotation[]	getAnnotations()
                * 获得当前对象上使用的所有注解,返回注解数组,包含父类继承的
             Annotation[]	getDeclaredAnnotations()
                * 获得当前对象上使用的所有注解,返回注解数组,只包含本类的
             boolean	isAnnotationPresent(Class<Annotation> annotationClass)
                * 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false

        获取注解数据的原理
            * 注解作用在哪个成员上就会得该成员对应的对象来获得注解
                * 比如注解作用成员方法,则要获得该成员方法对应的Method对象
                * 比如注解作用在类上,则要该类的Class对象
                * 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象。
            * Field,Method,Constructor,Class等类都是实现了AnnotatedElement接口

五、动态代理

动态代理简单来说是:拦截对真实对象方法的直接访问,增强真实对象方法的功能

​ 动态代理详细来说是:代理类在程序运行时创建的代理对象被称为动态代理,也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。也就是说你想获取哪个对象的代理,动态代理就会动态的为你生成这个对象的代理对象。动态代理可以对被代理对象的方法进行增强可以在不修改方法源码的情况下,增强被代理对象方法的功能在方法执行前后做任何你想做的事情。动态代理技术都是在框架中使用居多,例如:Struts1、Struts2、Spring和Hibernate等后期学的一些主流框架技术中都使用了动态代理技术。

java
public class LogProxy {
	// 提供一个方法,用于生产需要被代理对象的代理对象。
	public static Object getProxy(Object obj) {
		return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj
				.getClass().getInterfaces(), new InvocationHandler() {

			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				// 先记录开始时间点
				long startTimer = System.currentTimeMillis();
				try {
                       // 真正去触发被代理对象中该方法的执行
					return method.invoke(obj, args);
				} catch (Exception e) {
					throw new RuntimeException(e);
				} finally {
					long endTimer = System.currentTimeMillis();
					// 在什么时刻执行完,花费了多长时间完成
					SimpleDateFormat sdf = new SimpleDateFormat(
							"yyyy-MM-dd HH:mm:ss");
					System.out.println(method.getName() + "方法执行->"
							+ sdf.format(endTimer) + ",耗时:"
							+ (endTimer - startTimer));
				}
			}
		});
	}

}
public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

1、obj.getClass().getClassLoader()目标对象通过getClass方法获取类的所有信息后,调用getClassLoader() 方法来获取类加载器。获取类加载器后,可以通过这个类型的加载器,在程序运行时,将生成的代理类加载到JVM即Java虚拟机中,以便运行时需要! 

2、obj.getClass().getInterfaces()获取被代理类的所有接口信息,以便于生成的代理类可以具有代理类接口中的所有方法。 

3、InvocationHandler 这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类方法的处理以及访问.
public Object invoke(Object proxy, Method method, Object[] args)
1、Object proxy生成的代理对象,在这里不是特别的理解这个对象,但是个人认为是已经在内存中生成的proxy对象。 
2、Method method:被代理的对象中被代理的方法的一个抽象。
3、Object[] args:被代理方法中的参数。这里因为参数个数不定,所以用一个对象数组来表示。

Object克隆

通过new和反射可以创建内容一模一样的对象。但是,创建对象之后,通过setter方法,完成设置一不一样的内容,如果需要创建更多的内容一致的对象,那么setter方法调用就不断在重复。

浅表复制

使用clone方法,可以大大的减少了创建重复对象代码,但通过克隆方法克隆出来得对象不一样,但底层成员变量得哈希值是一致得,这样得复制称为浅表复制

浅表复制弊端:当操作成员变量内容时,复制出来的对象和源对象都会改变。

object克隆方法;

java

public class Person implements Cloneable{
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
}

 @Test
    public void test2() throws Exception {

        Person p1 = new Person();
        p1.setName("张三");
        p1.setAge(18);

        Person p2 = p1.clone();

        System.out.println(p1+":"+p1.hashCode());
        System.out.println(p2+":"+p2.hashCode());

    }

深层复制

实现流程

  1. 修改children类实现Cloneable接口
  2. 修改children类重写clone方法
  3. 修改Person类重写clone方法,在clone方法中调用children的clone方法

弊端:

需要修改类中成员变量对应的源码,如果成员变量特别多,那么就需要修改多个类的源码。

java

public class Children implements Cloneable {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Children{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public Children clone() throws CloneNotSupportedException {
        return (Children) super.clone();
    }
}
java

public class Person implements Cloneable{
    private String name;
    private Integer age;
    private Children child;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Children getChild() {
        return child;
    }

    public void setChild(Children child) {
        this.child = child;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", child=" + child +
                '}';
    }

    @Override
    public Person clone() throws CloneNotSupportedException {
        Person clone = (Person) super.clone();
        clone.setChild(child.clone());
        return clone;
    }
}
java
/**
     * 需求:测试深层复制
     * */
    @Test
    public void test5() throws Exception {

        Person p1 = new Person();
        p1.setName("张三");
        p1.setAge(28);

        Children children1 = new Children();
        children1.setName("张伟");
        children1.setAge(5);
        p1.setChild(children1);

        Person p2 = p1.clone();

        System.out.println(p1.getChild());
        System.out.println(p2.getChild());

        children1.setName("张三丰");
        System.out.println(p1.getChild());
        System.out.println(p2.getChild());

        Children children2 = p2.getChild();
        children2.setName("张无忌");
        System.out.println(p1.getChild());
        System.out.println(p2.getChild());

        System.out.println(p1.getChild().hashCode());
        System.out.println(p2.getChild().hashCode());
    }

使用IO进行克隆

步骤:

  1. 创建ByteArrayOutputStream,将数据可以转换成字节
  2. 创建ObjectOutputStream,关联ByteArrayOutputStream
  3. 使用ObjectOutputStream的writeObject,读取要复制的对象
  4. 使用ByteArrayInputStream读取ByteArrayOutputStream的转换的对象字节数据
  5. 创建ObjectInputStream读取对象字节数据,创建新的对象
java

import java.io.Serializable;

public class User implements Serializable {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
java
@Test
    public void test7() throws Exception {
        User user = new User();
        user.setName("李四");
        user.setAge(18);

        //1. 创建ByteArrayOutputStream,将数据可以转换成字节
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        //2. 创建ObjectOutputStream,关联ByteArrayOutputStream
        ObjectOutputStream out = new ObjectOutputStream(bout);
        //3. 使用ObjectOutputStream的writeObject,读取要复制的对象
        out.writeObject(user);
        //4. 使用ByteArrayInputStream读取ByteArrayOutputStream的转换的对象字节数据
        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        //5. 创建ObjectInputStream读取对象字节数据,创建新的对象
        ObjectInputStream in =  new ObjectInputStream(bin);
        User obj = (User) in.readObject();

        System.out.println(user+":"+user.hashCode());
        System.out.println(obj+":"+obj.hashCode());
    }

使用IO改写克隆方法

java
@Override
    public Person clone() {

        try {
            //1. 创建ByteArrayOutputStream,将数据可以转换成字节
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            //2. 创建ObjectOutputStream,关联ByteArrayOutputStream
            ObjectOutputStream out = new ObjectOutputStream(bout);
            //3. 使用ObjectOutputStream的writeObject,读取要复制的对象
         out.writeObject(this);
            //4. 使用ByteArrayInputStream读取ByteArrayOutputStream的转换的对象字节数据
            ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
            //5. 创建ObjectInputStream读取对象字节数据,创建新的对象
            ObjectInputStream in = new ObjectInputStream(bin);
            Person clone = (Person) in.readObject();
            return clone;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

序列化

序列化对单例的破坏

  • 单例class

    java
    import java.io.Serializable;
    /**
     * Created by hollis on 16/2/5.
     * 使用双重校验锁方式实现单例
     */
    public class Singleton implements Serializable{
        private volatile static Singleton singleton;
        private Singleton (){}
        public static Singleton getSingleton() {
            if (singleton == null) {
                synchronized (Singleton.class) {
                    if (singleton == null) {
                        singleton = new Singleton();
                    }
                }
            }
            return singleton;
        }
    }
  • 测试代码

    java
    import java.io.*;
    /**
     * Created by hollis on 16/2/5.
     */
    public class SerializableDemo1 {
        //为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
        //Exception直接抛出
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            //Write Obj to file
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(Singleton.getSingleton());
            //Read Obj from file
            File file = new File("tempFile");
            ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
            Singleton newInstance = (Singleton) ois.readObject();
            //判断是否是同一个对象
            System.out.println(newInstance == Singleton.getSingleton());
        }
    }

    通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性。

ObjectInputStream

对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现.ObjectInputStream的readObject的调用栈:

img

java
Object obj;
try {
    obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
    throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);
}

img

isInstantiable:如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。针对serializable和externalizable我会在其他文章中介绍。

desc.newInstance:该方法通过反射的方式调用无参构造方法新建一个对象。

防止序列化破坏单例

  • 单例class
java
package com.hollis;
import java.io.Serializable;
/**
 * Created by hollis on 16/2/5.
 * 使用双重校验锁方式实现单例
 */
public class Singleton2 implements Serializable{
    private volatile static Singleton2 singleton;
    private Singleton2 (){}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton2.class) {
                if (singleton == null) {
                    singleton = new Singleton2();
                }
            }
        }
        return singleton;
    }

    private Object readResolve() {
        return singleton;
    }
}
  • 测试类

    java
    import java.io.*;
    /**
     * Created by hollis on 16/2/5.
     */
    public class SerializableDemo2 {
        //为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
        //Exception直接抛出
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            //Write Obj to file
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(Singleton.getSingleton());
            //Read Obj from file
            File file = new File("tempFile");
            ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
            Singleton newInstance = (Singleton) ois.readObject();
            //判断是否是同一个对象
            System.out.println(newInstance == Singleton.getSingleton());
        }
    }
    //true
  • 分析

    java
    if (obj != null &&
        handles.lookupException(passHandle) == null &&
        desc.hasReadResolveMethod())
    {
        Object rep = desc.invokeReadResolve(obj);
        if (unshared && rep.getClass().isArray()) {
            rep = cloneArray(rep);
        }
        if (rep != obj) {
            handles.setObject(passHandle, obj = rep);
        }
    }

    hasReadResolveMethod:如果实现了serializable 或者 externalizable接口的类中包含readResolve则返回true

    invokeReadResolve:通过反射的方式调用要被反序列化的类的readResolve方法。

    所以,原理也就清楚了,主要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。

java对象的序列化

Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。

使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量

除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。

java对象序列化/反序列化

java
import java.io.Serializable;
import java.util.Date;

/**
 * Created by hollis on 16/2/2.
 */
public class User implements Serializable{
    private String name;
    private int age;
    private Date birthday;
    private transient String gender;
    private static final long serialVersionUID = -6849794470754667710L;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", birthday=" + birthday +
                '}';
    }
}
java
package com.hollis;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import java.io.*;
import java.util.Date;

/**
 * Created by hollis on 16/2/2.
 */
public class SerializableDemo {

    public static void main(String[] args) {
        //Initializes The Object
        User user = new User();
        user.setName("hollis");
        user.setGender("male");
        user.setAge(23);
        user.setBirthday(new Date());
        System.out.println(user);

        //Write Obj to File
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(oos);
        }

        //Read Obj from File
        File file = new File("tempFile");
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(file));
            User newUser = (User) ois.readObject();
            System.out.println(newUser);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(ois);
            try {
                FileUtils.forceDelete(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}
//output 
//User{name='hollis', age=23, gender=male, birthday=Tue Feb 02 17:37:38 CST 2016}
//User{name='hollis', age=23, gender=null, birthday=Tue Feb 02 17:37:38 CST 2016}

1、在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。

2、通过ObjectOutputStreamObjectInputStream对对象进行序列化及反序列化

3、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID

4、序列化并不保存静态变量。

5、要想将父类对象也序列化,就需要让父类也实现Serializable 接口。

6、Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

7、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

ArrayList序列化

ArrayList实现了java.io.Serializable接口,那么我们就可以对它进行序列化及反序列化。因为elementData是transient的,所以我们认为这个成员变量不会被序列化而保留下来,实际通过序列化和反序列化把List中的元素保留下来。

java
public static void main(String[] args) throws IOException, ClassNotFoundException {
        List<String> stringList = new ArrayList<String>();
        stringList.add("hello");
        stringList.add("world");
        stringList.add("hollis");
        stringList.add("chuang");
        System.out.println("init StringList" + stringList);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("stringlist"));
        objectOutputStream.writeObject(stringList);

        IOUtils.close(objectOutputStream);
        File file = new File("stringlist");
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
        List<String> newStringList = (List<String>)objectInputStream.readObject();
        IOUtils.close(objectInputStream);
        if(file.exists()){
            file.delete();
        }
        System.out.println("new StringList" + newStringList);
    }
//init StringList[hello, world, hollis, chuang]
//new StringList[hello, world, hollis, chuang]

writeObject和readObject方法

在ArrayList中定义了来个方法: writeObjectreadObject

在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。

如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。

用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

java
private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }
java
private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

transient

ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。

writeObject and readObject

防止一个包含大量空对象的数组被序列化,为了优化存储,所以,ArrayList使用transient来声明elementData。 但是,作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写writeObjectreadObject方法的方式把其中的元素保留下来。

writeObject方法把elementData数组中的元素遍历的保存到输出流(ObjectOutputStream)中。

readObject方法从输入流(ObjectInputStream)中读出对象并保存赋值到elementData数组中。

ObjectOutputStream

对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的

ObjectOutputStream的writeObject的调用栈:

java
writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject

invokeWriteObject:

java
void invokeWriteObject(Object obj, ObjectOutputStream out)
        throws IOException, UnsupportedOperationException
    {
        if (writeObjectMethod != null) {
            try {
                writeObjectMethod.invoke(obj, new Object[]{ out });
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof IOException) {
                    throw (IOException) th;
                } else {
                    throwMiscException(th);
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }

其中writeObjectMethod.invoke(obj, new Object[]{ out });是关键,通过反射的方式调用writeObjectMethod方法。

在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法时,会通过反射的方式调用。

ObjectOutputStream的writeObject的调用栈:

writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject

writeObject0方法中有这么一段代码:

java
if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum<?>) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }

在进行序列化操作时,会判断要被序列化的类是否是Enum、Array和Serializable类型,如果不是则直接抛出NotSerializableException

泛型

​ Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。

泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数。

泛型定义

l常用的泛型标识:T、E、K、V

  • java
    class 类名称 <泛型标识,泛型标识,…> {
      private 泛型标识 变量名; 
      .....
    }
    
    // 构建类对象
    类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
    // java1.7后
    类名<具体的数据类型> 对象名 = new 类名<>();
    
    // 子类也是泛型类,子类和父类的泛型类型要一致
    class ChildGeneric<T> extends Generic<T> 
    // 子类不是泛型类,父类要明确泛型的数据类型
    class ChildGeneric extends Generic<String>

    l泛型类,如果没有指定具体的数据类型,此时,操作类型是Object

    l泛型的类型参数只能是类类型,不能是基本数据类型

    l泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型

  • 泛型接口

    java
    interface 接口名称 <泛型标识,泛型标识,…> {
      泛型标识 方法名(); 
      .....
    }

    l实现类不是泛型类,接口要明确数据类型

    l实现类也是泛型类,实现类和接口的泛型类型要一致

  • 泛型方法

    l泛型类,是在实例化类的时候指明泛型的具体类型。

    泛型方法,是在调用方法的时候指明泛型的具体类型

    java
    修饰符 <T,E, ...> 返回值类型 方法名(形参列表) {
        方法体...
    }
    
    // 泛型可变参数
    public <E> void print(E... e){
            for (E e1 : e) {
                System.out.println(e);
            }
     }

    lpublic与返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。

    l只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。

    l<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。

    l与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。

l泛型方法能使方法独立于类而产生变化

l如果static方法要使用泛型能力,就必须使其成为泛型方法

类型通配符

l类型通配符一般是使用"?"代替具体的类型实参。

l所以,类型通配符是类型实参,而不是类型形参。

java
// 类型通配符上限  要求该泛型的类型,只能是实参类型,或实参类型的子类类型。
/接口<? extends 实参类型>

// 类型通配符的下限 要求该泛型的类型,只能是实参类型,或实参类型的父类类型。
/接口<? super 实参类型>

类型擦除

泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为--类型擦除。

无限制类型的没有使用通配符标识上限和下限的会被转换为object,使用通配符标识的会转换为通配符的类型

泛型数组

l可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象

l可以通过java.lang.reflect.Array的newInstance(Class<T>,int)创建T[]数组

TreeMap

get方法源码分析

java
//Entry类型表示结点
static final class Entry<K,V> implements Map.Entry<K,V> {
    K key;					//key表示键
    V value;				//value表示值
    Entry<K,V> left;		//left表示左子结点的地址
    Entry<K,V> right;		//rigth表示右子结点的地址
    Entry<K,V> parent;		//parent表示父结点的地址
    boolean color = BLACK;  //color表示结点的颜色
    
    //下面方法省略…………
}

public V get(Object key) {
	//调用方法根据键获取Entry对象
    Entry<K,V> p = getEntry(key);
    //判断对象如果是null返回null,如果不是null返回对象中的值
    return (p==null ? null : p.value);
}

final Entry<K,V> getEntry(Object key) {
        //判断有没有传入comparator
        if (comparator != null)
            //调用方法,使用比较器做查询
            return getEntryUsingComparator(key);
    	//判断传入的键是否为null
        if (key == null)
            //如果要查询的键是null则抛出空指针异常
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
    	//把Object类型的键向下转型为Comparable
        Comparable<? super K> k = (Comparable<? super K>) key;
    	//先把二叉树的根结点赋值给p
        Entry<K,V> p = root;
    	//如果p不为null,一直循环比较
        while (p != null) {
            //调用Comparable的compareTo()方法进行比较
            int cmp = k.compareTo(p.key);
            //如果cmp小于0,表示要查找的键小于结点的数字
            if (cmp < 0)
                //把p左子结点赋值给p对象
                p = p.left;
            //如果cmp大于0,表示要查找的键大于结点的数字
            else if (cmp > 0)
                //把P右子结点赋值给p对象
                p = p.right;
            else
                //要查找的键等于结点的值,就把当前Entry对象直接返回
                return p;
        }
    	//已经找到叶子结点,没有找到要查找的数字返回null
        return null;
    }


//传入比较器的情况下
final Entry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
    		//把Object类型的Key向下转型为对应的键的类型
            K k = (K) key;
    	//给比较器对象起名字cpr
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            //把二叉树的根结点赋值给P对象
            Entry<K,V> p = root;
            //循环用要查找的数字和结点中的数字进行比较
            while (p != null) {
                //调用比较器的compare()
                int cmp = cpr.compare(k, p.key);
                if (cmp < 0)
                    p = p.left;
                else if (cmp > 0)
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
    }

put方法源码分析

java
public V put(K key, V value) {
    //获取根结点赋值给变量t
    Entry<K,V> t = root;
    //判断根结点是否为null
    if (t == null) {
        //对key进行非空和类型校验
        compare(key, key);
		//新建一个结点
        root = new Entry<>(key, value, null);
        //设置集合长度为1
        size = 1;
        //记录集合被修改的次数
        modCount++;
		//添加成功返回null
        return null;
    }
   	//如果根结点不是null则执行下面代码
    int cmp;
    Entry<K,V> parent;
    
    //把比较器对象赋值给变量cpr
    Comparator<? super K> cpr = comparator;
    //判断比较器对象如果是空则执行下面代码
    if (cpr != null) {
        do {
            //把当前结点赋值给变量parent
            parent = t;
            //比较当前结点的键和要存储的键的大小
            cmp = cpr.compare(key, t.key);
            //如果要存储的键小于当前结点,则继续和左边的结点进行比较
            if (cmp < 0)
                t = t.left;
            //如果要存储的键大于当前结点,则继续和右边的结点进行比较
            else if (cmp > 0)
                t = t.right;
            else
                //如果要存储的键等于当前结点的键,则调用setValue()方法设置新的值
                //并结束循环
                return t.setValue(value);
          //循环直到遍历到叶子结点结束为止
        } while (t != null);
    }
    //如果比较器对象不是空则执行下面代码
    else {
        //如果要保存的键为空,抛出空指针异常
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        	//把键转型为Comparable类型
            Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            //把当前结点赋值给变量parent
            parent = t;
            //比较要存储的键和当前结点的键
            cmp = k.compareTo(t.key);
            //如果要存储的键小于当前结点,则继续和左边的结点比较
            if (cmp < 0)
                t = t.left;
            //如果要存储的键大于当前结点,则继续和右边的结点比较
            else if (cmp > 0)
                t = t.right;
            else
                //如果要存储的键等于当前结点的键,则调用setValue()方法设置新的值
                //并结束循环
                return t.setValue(value);
           //循环直到遍历到叶子结点结束为止
        } while (t != null);
    }
    //遍历结束如果没有找到相同的键,则执行下面代码
    //创建新的结点对象,保存键值对,设置父结点
    Entry<K,V> e = new Entry<>(key, value, parent);
    //如果新的键小于父结点的键,则保存在左边
    if (cmp < 0)
        parent.left = e;
    else
        //如果新的键大于父结点的键,则保存在右边
        parent.right = e;
    //维持红黑树的平衡
    fixAfterInsertion(e);
    //集合长度加一
    size++;
    //集合修改次数加一
    modCount++;
    //返回被覆盖的值是null
    return null;
}

自定义TreeMap集合

java
import java.util.Comparator;

public class TreeMap<K, V> {

    //定义比较器变量
    private final Comparator<? super K> comparator;

    //根结点
    private Entry<K, V> root;

    //定义集合长度
    private int size;


    //空参构造
    public TreeMap() {
        comparator = null;
    }

    //有参构造
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    //定义内部类表示键值对
    private class Entry<K, V> {
        //键
        K k;
        //值
        V v;
        //左子结点
        Entry<K, V> left;
        //右子结点
        Entry<K, V> right;
        //父结点
        Entry<K, V> parent;

        //有参构造

        public Entry(K k, V v, Entry<K, V> left, Entry<K, V> right, Entry<K, V> parent) {
            this.k = k;
            this.v = v;
            this.left = left;
            this.right = right;
            this.parent = parent;
        }
    }

    //获取集合长度
    public int size() {
        return size;
    }


    //get()方法的实现
    public V get(K key) {
        Entry<K, V> entry = getEntry(key);
        return entry == null ? null : entry.v;
    }


    //根据键获取Entry对象的方法
    private Entry<K, V> getEntry(Object key) {
        //非空校验
        if (key == null) {
            throw new NullPointerException();
        }

        //给根结点起名
        Entry<K, V> t = root;

        //判断有没有传入比较器
        if (comparator != null) {
            K k = (K) key;
            //循环
            while (t != null) {
                int cmp = comparator.compare(k, t.k);
                if (cmp < 0) {
                    t = t.left;
                } else if (cmp > 0) {
                    t = t.right;
                } else {
                    return t;
                }
            }

        } else {
            Comparable<? super K> k = (Comparable<? super K>) key;

            while (t != null) {
                int cmp = k.compareTo(t.k);
                if (cmp < 0) {
                    t = t.left;
                } else if (cmp > 0) {
                    t = t.right;
                } else {
                    return t;
                }
            }
        }

        //如果找不到返回null
        return null;

    }


    //put()方法的实现
    public V put(K key, V value) {
        //给根结点赋值
        Entry<K, V> t = root;

        //非空校验
        if (key == null) {
            throw new NullPointerException();
        }

        //集合是否为空
        if (t == null) {
            //创建新结点
            Entry<K, V> entry = new Entry<>(key, value, null, null, null);
            //给根结点赋值
            root = entry;
            //集合长度加一
            size++;
            return null;
        }

        //键值对表示新增结点的父结点
        Entry<K, V> parent = t;
        //定义变量
        int cmp = 0;
        if (comparator != null) {
            while (t != null) {
                //给parent
                parent = t;
                //判断键
                cmp = comparator.compare(key, t.k);

                if (cmp < 0) {
                    t = t.left;
                } else if (cmp > 0) {
                    t = t.right;
                } else {
                    //用新的值替换旧的值 把旧的值作为返回值返回
                    V v = t.v;
                    t.v = value;
                    return v;
                }
            }
        } else {
            Comparable<? super K> k = (Comparable<? super K>) key;
            while (t != null) {
                parent = t;

                cmp = k.compareTo(t.k);

                if (cmp < 0) {
                    t = t.left;
                } else if (cmp > 0) {
                    t = t.right;
                } else {
                    //用新的值替换旧的值 把旧的值返回
                    V v = t.v;
                    t.v = value;
                    return v;
                }
            }
        }

        //要添加的键值对 键不重复
        Entry<K, V> entry = new Entry<>(key, value, null, null, parent);

        if (cmp < 0) {
            parent.left = entry;
        } else {
            parent.right = entry;
        }

        //集合长度加一
        size++;

        return null;
    }


    //remove()方法的实现
    public V remove(K key) {

        Entry<K, V> entry = getEntry(key);

        if (entry == null) {
            return null;
        }

        //删除操作

        if (entry.left == null && entry.right != null) {
            //有右子结点没有左子结点

            if (entry == root) {
                root = entry.right;
            } else if (entry.parent.right == entry) {
                entry.parent.right = entry.right;
            } else if (entry.parent.left == entry) {
                entry.parent.left = entry.right;
            }

            //让被删除结点的子结点指向父结点
            entry.right.parent = entry.parent;

        } else if (entry.left != null && entry.right == null) {
            //有左子结点没有右子结点
            //要删除的结点是父结点的右子结点
            if (entry == root) {
                root = entry.left;
            } else if (entry.parent.right == entry) {
                entry.parent.right = entry.left;
            } else if (entry.parent.left == entry) {
                entry.parent.left = entry.left;
            }

            //让被删除结点子结点指向父结点
            entry.left.parent = entry.parent;

        } else if (entry.left != null && entry.right != null) {
            //有左子结点也有右子结点
            //要后继结点
            Entry<K, V> target = entry.right;
            //寻找被删除结点右子结点最左子结点
            while (target.left != null) {
                target = target.left;
            }

            //右子结点作为后继结点
            if (entry.right == target) {
                target.parent = entry.parent;

                if (entry == root) {
                    root = target;
                } else if (entry.parent.right == entry) {
                    entry.parent.right = target;
                } else if (entry.parent.left == entry) {
                    entry.parent.left = target;
                }

                //被删除结点左子结点重新指向新的父结点
                entry.left.parent = target;
                target.left = entry.left;

            } else {
                //右子结点的最左子结点作为后继结点
                if (target.right == null) {
                    //后继结点没有右子结点
                    target.parent.left = null;
                } else {
                    //后继结点有右子结点
                    target.parent.left = target.right;
                    target.right = target.parent;
                }

                //让后继结点替换被删除结点
                if (entry == root) {
                    root = target;
                } else if (entry.parent.right == entry) {
                    entry.parent.right = target;
                } else if (entry.parent.left == entry) {
                    entry.parent.left = target;
                }

                //被删除结点左右子树需要指向后继结点
                entry.left.parent = target;
                entry.right.parent = target;
                target.left = entry.left;
                target.right = entry.right;
            }


        } else {
            //要删除的结点是叶子结点

            if (entry == root) {
                root = null;
            } else if (entry.parent.right == entry) {
                entry.parent.right = null;
            } else if (entry.parent.left == entry) {
                entry.parent.left = null;
            }
        }

        //给集合长度减一
        size--;
        
        return entry.v;
    }


    //toString()方法的实现
    //{1=abc, 3=qwe}
    public String toString() {
        //非空判断
        if (root == null) {
            return "{}";
        }

        String s = "{";
        String s1 = method(root);

        s = s + s1.substring(0, s1.length() - 2) + "}";

        return s;
    }


    //递归的方法
    //1=abc, 3=qwe,
    private String method(Entry<K, V> entry) {

        String s = "";

        //拼接左子结点
        if (entry.left != null) {
            s += method(entry.left);
        }

        //拼接中间结点自己
        s += entry.k + "=" + entry.v + ", ";

        //拼接右子结点
        if (entry.right != null) {
            s += method(entry.right);
        }

        return s;
    }


}

测试类

java

public class Demo {
    public static void main(String[] args) {
        //创建对象
        TreeMap<Integer,String> map = new TreeMap<>();

        //添加键值对
        map.put(5,"aaa");
        map.put(1,"bbb");
        map.put(9,"ccc");
        map.put(98,"ddd");
        map.put(65,"eee");
        map.put(12,"fff");
        //相同的键
        map.put(12,"qqq");


        //根据键获取值
        String s = map.get(5);
        System.out.println(s);
        String s1 = map.get(4);
        System.out.println(s1);

        //删除
        String s2 = map.remove(9);
        System.out.println(s2);

        //删除
        String s3 = map.remove(5);
        System.out.println(s3);


        //打印
        System.out.println(map);
    }
}

ScriptEngine使用

在javax.script包中有很多的类,但这些类中最主要的是ScriptEngineManager,以下操作以js为例

ScriptEngineManager是一个工厂的集合,可以通过name或tag的方式获取某个脚本的工厂并生成一个此脚本的ScriptEngine,目前只有javascript的工厂。通过工厂函数得到了ScriptEngine之后,就可以用这个对象来解析脚本字符串了,直接调用Object obj = ScriptEngine.eval(String script)即可,返回的obj为表达式的值,比如true、false或int值。

CompiledScript可以将ScriptEngine解析一段脚本的结果存起来,方便多次调用。只要将ScriptEngine用Compilable接口强制转换后,调用compile(String script)就返回了一个CompiledScript对象,要用的时候每次调用一下CompiledScript.eval()即可,一般适合用于js函数的使用。

Bindings的概念算稍微复杂点,我的理解Bindings是用来存放数据的容器。它有3个层级,为Global级、Engine级和Local级,前2者通过ScriptEngine.getBindings()获得,是唯一的对象,而Local Binding由ScriptEngine.createBindings()获得,很好理解,每次都产生一个新的。Global对应到工厂,Engine对应到ScriptEngine,向这2者里面加入任何数据或者编译后的脚本执行对象,在每一份新生成的Local Binding里面都会存在。

  • 优点:可以执行完整的JS方法,并且获取返回值;在虚拟的Context中执行,无法调用系统操作和IO操作,非常安全;可以有多种优化方式,可以预编译,编译后可以复用,效率接近原生Java;所有实现ScriptEngine接口的语言都可以使用,并不仅限于JS,如Groovy,Ruby等语言都可以动态执行。

  • 缺点:无法调用系统和IO操作 ,也不能使用相关js库,只能使用js的标准语法。更新:可以使用scriptengine.put()将Java原生Object传入Context,从而拓展实现调用系统和IO等操作。

注意事项

不要共享脚本中的对象,可以共享原生数据类型或者java对象,比如你在script里面定义了一个var i=0,那么这个变量i是全局的变量,是所有线程共享的变量,当你在多线程情况下执行var i=0; i=i+1;时,每个线程得到的结果并不会都是1。当然如果你是在script的function中定义的变量,那么它不会被共享,例如你的script string是:function addone(){var i=0;i=i+1;return i;}那么多线程同时调用这个function时,返回的结果都是1。 这里要注意的一点是function中一定要用var 重新定义变量,否则还是全局的变量.

还有一点是如果你确实想共享一个对象,可以用JSON.stringfy和JSON.parse通过json序列化和反序列化来共享,这样其实内部是生成了java 的String对象,String对象都是新分配内存保存的,所以每个线程都持有的是不同的对象实例,改变互不影响。

sof上看了许多评论,都是说要尽量复用ScriptEngine,因为是将脚本语言编译成了java可执行的字节码来执行的,如果不复用的话,那么每次都要去编译脚本生成字节码,如果是一个固定的脚本的话,这样效率是很低的,

封装工具类使用

mirrors_eobermuhlner/scriptengine-utils - 码云 - 开源中国 (gitee.com)

获取所有的支持脚本语言

Names输出的即为支持的脚本语言

java
import jdk.nashorn.api.scripting.ScriptObjectMirror;

import javax.script.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Classname TestScriptEngine
 * @Description TODO
 */
public class TestScriptEngine {


    public static void main(String[] args) throws ScriptException, NoSuchMethodException {

        ScriptEngineManager manager = new ScriptEngineManager();
        // 得到所有的脚本引擎工厂
        List<ScriptEngineFactory> factories = manager.getEngineFactories();
        for (ScriptEngineFactory factory: factories){
            // 打印脚本信息
            System.out.printf("Name: %s%n" + "Version: %s%n" +"Language name: %s%n" +
                            "Language version: %s%n" +"Extensions: %s%n" +"Mime types: %s%n" +"Names: %s%n",
                    factory.getEngineName(),
                    factory.getEngineVersion(),
                    factory.getLanguageName(),
                    factory.getLanguageVersion(),
                    factory.getExtensions(),
                    factory.getMimeTypes(),
                    factory.getNames());
            // 得到当前的脚本引擎
            ScriptEngine engine = factory.getScriptEngine();
        }
    }
}

获取脚本引擎

java
// 根据扩展名得到脚本引擎
// getEngineByExtension的参数就是Extensions:[js]中[…]里的部分。
ScriptEngine engine = manager.getEngineByExtension("js");
// 根据Mime类型得到脚本引擎
// getEngineByMimeType的参数可以是Mime types: [application/javascript,
// application/ecmascript, text/javascript,text/ecmascript]中的任何一个,
ScriptEngine engine1 = manager.getEngineByMimeType("text/javascript");
// 根据名称得到脚本引擎
// getEngineByName后的参数可以是Names: [js, rhino, JavaScript, javascript, ECMAScript, ecmascript]中的任何一个,
ScriptEngine engine2 = manager.getEngineByName("javascript");

执行js代码

执行js逻辑

@Test
/**
 * @Description: 执行简单代码
 * @param
 * @return: void
 * @date: 2022/12/11
 */
public void execJsCode() throws ScriptException {
    ScriptEngineManager sem = new ScriptEngineManager();
    ScriptEngine engine = sem.getEngineByName("javascript");
    // 设置值
    engine.put("msg", "just a test");
    String str = "msg += '!!!'";
    // 代码执行结果不准确,取值是进行变动的值,应使用engine.get()方法获取
    Object eval = engine.eval(str);
    System.out.println(eval);
    // 从执行结果中获取值
    Object msg = engine.get("msg");
    System.out.println(msg);

    String str1 = "var msg1 = {name:'tom',age:23,hobbies:['football','basketball'],msg:msg}; msg = msg1";
   // 当为复杂类型时,对象类型会转变为 ScriptObjectMirror,可与map进行互转,复杂对象嵌套的集合类型需根据工具类转换
    Object eval1 = engine.eval(str1);
    System.out.println(eval1);

    User user = new User();
    user.setAge("12");
    user.setName("acx");
    Role role = new Role();
    role.setName("管理");
    role.setDesc("描述信息");
    Role role1 = new Role();
    role1.setName("管理");
    role1.setDesc("描述信息");
    List<Role> roles = new ArrayList<>();
    roles.add(role);
    roles.add(role1);
    user.setRoles(roles);
    String str2 = "user.roles[0].desc = 'ceshimiaoashu'";
    // 复杂类型解析也会被修改
    engine.put("user",user);
    Object eval2 = engine.eval(str2);
    System.out.println(eval2);

}

执行js方法

如果脚本中有多个函数或想通过用户的输入来决定调用哪个函数,这就需要使用invoke方法进行动态调用。和编译一样,脚本引擎必须实现Invocable接口才可以动态调用脚本语言中的方法。

java
/**
     * @Description: 测试调用方法
     * @param
     * @return: void
     * @date: 2022/12/11
     */
    @Test
    public void testInvoke(){
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("javascript");
        String name="abcdefg";
        if (engine instanceof Invocable) {
            try {
                engine.eval("function reverse(name) { var output =' ';"+
                        " for (i = 0; i <= name.length; i++) {" +" output = name.charAt(i) + output" +" } return output;}");
                Invocable invokeEngine = (Invocable)engine;
                Object o = invokeEngine.invokeFunction("reverse", name);
                System.out.printf("翻转后的字符串:%s", o);
            } catch (NoSuchMethodException e) {
                System.err.println(e);
            } catch (ScriptException e) {
                System.err.println(e);
            }
        } else {
            System.err.println("这个脚本引擎不支持动态调用");
        }
    }

预编译执行

在运行脚本之前要先将这些脚本进行编译(这里的编译一般将不是生成可执行文件,而只是在内存中编译成更容易运行的方式),然后再执行。如果某段脚本要运行之交多次的话,使用这种方式是非常快的。我们可以使用 ScriptEngine的compile方法进行编译。并不是所有脚本引擎都支持编译,只有实现了Compilable接口的脚本引擎才可以使用 compile进行编译,否则将抛出一个错误。

java
/**
     * @Description: 预编译执行
     * @param
     * @return: void
     * @date: 2022/12/11
     */
    @Test
    public void testCompile(){
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("javascript");
        engine.put("counter", 0); // 向javascript传递一个参数
        // 判断这个脚本引擎是否支持编译功能
         if (engine instanceof Compilable){
             Compilable compEngine = (Compilable)engine;
             try {// 进行编译
                 CompiledScript script = compEngine.compile("function count() { " + " counter = counter +1; " +
                                 " return counter; " + "}; count();");
                 System.out.printf("Counter: %s%n", script.eval());
                 System.out.printf("Counter: %s%n", script.eval());
                 System.out.printf("Counter: %s%n", script.eval());
             } catch (ScriptException e) {
                 System.err.println(e);
             }
         } else {
             System.err.println("这个脚本引擎不支持编译!");
         }
    }

动态实现接口

以Runnable接口为例

如我们要想让脚本异步地执行,即通过多线程来执行,那InvokeEngine类必须实现 Runnable接口才可以通过Thread启动多线程。因此,可以通过getInterface方法来使InvokeEngine动态地实现 Runnable接口。这样一般可分为3步进行。

1. 使用javascript编写一个run函数

engine.eval("function run() {print(异步执行);}");

2. 通过getInterface方法实现Runnable接口

Runnable runner = invokeEngine.getInterface(Runnable.class);

3. 使用Thread类启动多线程

Thread t = new Thread(runner);

t.start();

java
 /**
     * @Description: 动态调用java的接口,做接口实现,以Runnable为例
     * @param
     * @return: void
     * @date: 2022/12/11
     */
    @Test
    public void testDynamicInvokeJavaInterface(){
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("javascript");
        try {
            engine.eval("function run() {print('异步调用');}");
            Invocable invokeEngine = (Invocable)engine;
            Runnable runner = invokeEngine.getInterface(Runnable.class);
            Thread t = new Thread(runner);
            t.start();
            t.join();
        } catch (InterruptedException e) {
            System.err.println(e);
        } catch (ScriptException e) {
            System.err.println(e);
        }
    }