一、反射
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文件
javapackage 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等后期学的一些主流框架技术中都使用了动态代理技术。
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克隆方法;
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());
}深层复制
实现流程
- 修改children类实现Cloneable接口
- 修改children类重写clone方法
- 修改Person类重写clone方法,在clone方法中调用children的clone方法
弊端:
需要修改类中成员变量对应的源码,如果成员变量特别多,那么就需要修改多个类的源码。
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();
}
}
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;
}
}/**
* 需求:测试深层复制
* */
@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进行克隆
步骤:
- 创建ByteArrayOutputStream,将数据可以转换成字节
- 创建ObjectOutputStream,关联ByteArrayOutputStream
- 使用ObjectOutputStream的writeObject,读取要复制的对象
- 使用ByteArrayInputStream读取ByteArrayOutputStream的转换的对象字节数据
- 创建ObjectInputStream读取对象字节数据,创建新的对象
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 +
'}';
}
}@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改写克隆方法
@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
javaimport 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; } }测试代码
javaimport 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的调用栈:

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);
}
isInstantiable:如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。针对serializable和externalizable我会在其他文章中介绍。
desc.newInstance:该方法通过反射的方式调用无参构造方法新建一个对象。
防止序列化破坏单例
- 单例class
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;
}
}测试类
javaimport 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分析
javaif (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则返回trueinvokeReadResolve:通过反射的方式调用要被反序列化的类的readResolve方法。所以,原理也就清楚了,主要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。
java对象的序列化
Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。
使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知,对象序列化不会关注类中的静态变量。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。
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 +
'}';
}
}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、通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化
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中的元素保留下来。
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中定义了来个方法: writeObject和readObject。
在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。
如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
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();
}
}
}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。 但是,作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写writeObject 和 readObject方法的方式把其中的元素保留下来。
writeObject方法把elementData数组中的元素遍历的保存到输出流(ObjectOutputStream)中。
readObject方法从输入流(ObjectInputStream)中读出对象并保存赋值到elementData数组中。
ObjectOutputStream
对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的
ObjectOutputStream的writeObject的调用栈:
writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObjectinvokeWriteObject:
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--->invokeWriteObjectwriteObject0方法中有这么一段代码:
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
类
javaclass 类名称 <泛型标识,泛型标识,…> { private 泛型标识 变量名; ..... } // 构建类对象 类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>(); // java1.7后 类名<具体的数据类型> 对象名 = new 类名<>(); // 子类也是泛型类,子类和父类的泛型类型要一致 class ChildGeneric<T> extends Generic<T> // 子类不是泛型类,父类要明确泛型的数据类型 class ChildGeneric extends Generic<String>l泛型类,如果没有指定具体的数据类型,此时,操作类型是Object
l泛型的类型参数只能是类类型,不能是基本数据类型
l泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型
泛型接口
javainterface 接口名称 <泛型标识,泛型标识,…> { 泛型标识 方法名(); ..... }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所以,类型通配符是类型实参,而不是类型形参。
// 类型通配符上限 要求该泛型的类型,只能是实参类型,或实参类型的子类类型。
类/接口<? extends 实参类型>
// 类型通配符的下限 要求该泛型的类型,只能是实参类型,或实参类型的父类类型。
类/接口<? super 实参类型>类型擦除
泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为--类型擦除。
无限制类型的没有使用通配符标识上限和下限的会被转换为object,使用通配符标识的会转换为通配符的类型
泛型数组
l可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象
l可以通过java.lang.reflect.Array的newInstance(Class<T>,int)创建T[]数组
TreeMap
get方法源码分析
//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方法源码分析
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集合
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;
}
}测试类
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输出的即为支持的脚本语言
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();
}
}
}获取脚本引擎
// 根据扩展名得到脚本引擎
// 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接口才可以动态调用脚本语言中的方法。
/**
* @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进行编译,否则将抛出一个错误。
/**
* @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();
/**
* @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);
}
}