从此

Java 编程技术综合性知识库 - File、IO、反射、序列化、安全、单例、JSON、WASM(WebAssembly)

综述

Java API 在线文档

主要

  sun.misc.Unsafe 于 JDK 23 删除,作用已被 VarHandle API 和 外部函数和内存 API 取代。
  SerializedLambda 用于 Lambda 序列化,可以提取 User::getName 方法引用的 Name 字样。

  record入参过多优化:
    public record Extra (string Address,string Email,int Age);
    public record Person(string FirstName, string LastName, Extra e);

死记:
  Math.toIntExact(123L) 不会像 Long.valueOf(123L).intValue() 返回溢出值,若超出范围则直接抛异常。

常用库

解析字符串中的URL - https://github.com/URL-Detector/URL-Detector

Markdown to HTML - implementation("org.commonmark:commonmark:0.24.0")

音视频处理库 FFmpeg JavaCV - https://github.com/bytedeco
  新 - v8.0 支持音频转字幕 --enable-whisper
  注意 - 
    指针转实际字符串用getString()而非toString():
      var ds = avformat.av_disposition_to_string(st.disposition());
      ds != null ? ds.getString() : null; // 入参为0则返回null。

  用法:
    对象 - AVFrame即单帧;BytePointer即数据(Bytes)指针。
    读摄像头 - new OpenCVFrameGrabber(CAMERA_INDEX); 显示视频 - CanvasFrame
    读 - new FFmpegFrameGrabber(filepath); // frame.image;声音属性.samples
    写 - new FFmpegFrameRecorder(file); r.record(frame);//声音recordSamples
    滤镜 - var f = new FFmpegFrameFilter(filterString,768,320);
      f.push(frame); frame=f.pullImage(); r.record(frame);
      该库默认未启用srt字幕滤镜--enable-libass,执行subtitles=filename=x.srt报No such filter: 'subtitles'

单例模式

单例模式:
  public class Singleton {
    // 懒加载+线程安全
    private static class Holder {
        // 静态内部类成员首次调用时,才会被JVM装载
        public static Singleton instance = new Singleton();
    }

    public static Singleton instance() {
        return Holder.instance;
    }

    private Singleton() {
        //super(p); // extends Father
    } // [可选] 仅能通过instance()获取实例

    private static String p; // super(p);
    public static Singleton instance(String s) {
        p = s; // 多次调用仍会使用首次传参
        return Holder.instance;
    }

    public static void main(String[] args) {
        System.out.println(Singleton.instance());
    }
  }

反射

  反射:
    JDK9起setAccessible(true)会触发非法访问WARNING,可通过--illegal-access=permit和--add-opens module.name/pkg.name=ALL-UNNAMED 解决,最终过渡至 --illegal-access=deny。
    临时生成并反射使用的可通过Lookup::defineHiddenClass来定义。

    获取修饰符 - Modifier.isPublic(m.getModifiers())
    MethodHandle:  
      若报非法访问且无解可降级至传统的Method
      公共成员 - MethodHandles.lookup(); 
        MethodHandles.lookup().findStatic(X.class,"m", MethodType.methodType(String.class)); //参1为返回类型,参2起为入参类型
      私有成员访问使用:module-info.java内需要opens pkg.name
        MethodHandles.privateLookupIn(方法所在类, MethodHandles.lookup());

      注意 - “方法所在类”指该方法必须真实存在所在类中,继承的方法不算。
      实例方法 - lookup.findVirtual(...);  静态方法 - lookup.findStatic(...);
      实例若已绑定mh.bindTo(obj); 则mh.invoke(...)首个参数不用填写该实例,等同静态方法的填参; 切记入参必须强转真实类型mh.invoke((int)byteValue)。
      示例:MethodHandles.lookup().findVirtual(ar.getClass(), "x", MethodType.methodType(void.class)).invoke(ar); // 数组类型用byte[].class
      返回值+入参:MethodType.methodType(void.class,String.class); 泛型T用Object.class代替。
      变量示例:l.findVarHandle(Entity.class, "f", String.class).set(obj,"value");
      VarHandle比反射方式少了设置可访问性步骤 field.setAccessible(true); 
 
      方法访问性设置:
            Method m = cf.getDeclaredMethod("method", String.class, int.class);
            m.setAccessible(true);
            var cfMethod = MethodHandles.lookup().unreflect(m);

    Android缺失的方法处理:
            var v = Class.forName("java.lang.Runtime$Version");
            var rv = MethodHandles.lookup().findStatic(Runtime.class,
                    "version", MethodType.methodType(v));
            System.out.println(rv.invoke().toString());

    反射static final field:
      applicationDefaultJvmArgs = ['--add-opens','java.base/java.lang.reflect=ALL-UNNAMED']

File I/O

InputStream、FilterInputStream类没有实现reset方法,故无法实现流的复用,可通过BufferedInputStream和ByteArrayInputStream来实现复用。
FileInputStream直接读取;BufferedInputStream是分批读取,通常套在FileInputStream外改善性能;ByteArrayInputStream则是全部读取。

综合

HTML转义 - guava 库
com.google.common.html.HtmlEscapers.htmlEscaper().escape(s)
  EQ 就是 EQUAL等于 
  NE 就是 NOT EQUAL不等于 
  GT 就是 GREATER THAN大于  
  LT 就是 LESS THAN小于 
  GE 就是 GREATER THAN OR EQUAL 大于等于 
  LE 就是 LESS THAN OR EQUAL 小于等于

其他

WASM(WebAssembly):
  语法:
    每个元素均用括号区隔,树根元素为module。
    type或import、export元素可单列也可内联: (func $f1 (import "mod" "f1") (type $ft1))
    $标识编译为wasm后会变成索引值,用来重复引用其他元素。

  Java调WASM实例(纯JVM即可) - https://github.com/graalvm/graal-languages-demos/tree/main/graalwasm/graalwasm-starter
    纯JVM无法编译为Native程序 - Unknown name in option specification: macro:truffle-svm 或 [engine] WARNING: The polyglot engine uses a fallback runtime that does not support runtime compilation to native code.
    implementation("org.graalvm.polyglot:polyglot:24.1.1")
    implementation("org.graalvm.polyglot:wasm:24.1.1")
    implementation("org.graalvm.polyglot:js:24.1.1")
    示例代码:
        try (var context = Context.newBuilder()//.allowAllAccess(true)
                // 解决JS调宿主Java报错:Java is not defined
                .allowHostAccess(HostAccess.ALL).allowHostClassLookup(_ -> true)
                //.option("wasm.Builtins", "wasi_snapshot_preview1") // 非实验选项
                //.allowExperimentalOptions(true).option("wasm.Threads", "true")
                .build()) { // or org.graalvm.polyglot.Context.create()
            // note - wasm only supports binary based sources
            context.eval("js", "console.log('js log')");

            var jsCallJava = "var JavaString = Java.type('java.lang.String');"
                    + "JavaString.valueOf(Math.PI); // new JavaString('s');";
            //  Java的String与JS中String重名,故new的类名应加个前后缀。
            var ce = context.eval(Source.newBuilder("js", jsCallJava, null).build());
            // 非兼容类则用.isHostObject()和.asHostObject();
            System.out.println(ce.isString() ? ce.asString() : null);
            context.getBindings("js").getMemberKeys().forEach(System.out::println);

            // wasm用name进行getMember(name)寻址,不设则取文件名,若是ByteSequence则用“js:module-字节哈希”。
            var r = context.eval(Source.newBuilder("wasm", wasmFile).name("topMod").build());
            System.out.println(r.hashCode()); // 等同下行代码返回值:
            var topMod = context.getBindings("wasm").getMember("topMod");
            // 底层实现类则可不设topMod名 - WebAssembly.instanceExport(wasmInstance, "functionName");
            var fr = topMod.getMember("stringFunctionExample").execute("s");
            System.out.println(fr.as(String.class));
            context.getBindings("wasm").getMemberKeys().forEach(System.out::println); // 取定级模块名。
        }

  WASM命令工具 - https://github.com/oracle/graal/blob/master/wasm/README.md/#graalwasm-standalone-distribution

Java其他WebAssembly库 - https://teavm.org/ 、 https://github.com/i-net-software/JWebAssembly


序列化:
  // Jackson使用OffsetDateTime必须引入jackson-datatype-jsr310库,不setDateFormat则为纯数字。
  var om = new ObjectMapper().registerModules(new JavaTimeModule())
    .setDateFormat(new StdDateFormat().withColonInTimeZone(true));
  var jsonString = om.writeValueAsString(obj);

序列化防护:
    try (var ois = new ObjectInputStream(is)) {
        ois.setObjectInputFilter(FilterClass::dateTimeFilter);
        return (LocalDateTime) ois.readObject(); // 类名筛选白名单或黑名单?
    } catch (ClassNotFoundException ex) { }

用之前应对 jakarta.json.JsonObject 判null:
    if (!jsonObject.isNull("q")) {
      var q = jsonObject.getString("q");
    }

UTF-16LE乱码转UTF-8中文:
  说明 - Windows字符用UTF-16小尾序表示;Unicode定义字符位置,UTF(Unicode Transformation Formats)定义字节表达。
    字节byte由八个位(bit/二进制0或1)组成,缩写为16进制符(可读性缘故缩至1-2位/范围是a-f加0-9)。
    UTF-16LE即至少2个或偶数字节表达1个字符,可表达英文、中文等全球文字;Unicode码已超出单个UTF-16的表达范围了,但都是些非经常使用的新增符号;
    纯英文字符可用无字节序的1个字节表达,而中文则适合用可变长(variable-length)的UTF-8或UTF-16LE;
    若不考虑传输长度,可采用固定字节数目UTF-32。
  var buffer = ByteBuffer.allocate(4);
  buffer.order(ByteOrder.LITTLE_ENDIAN);
  buffer.putChar('中'); buffer.putChar('文');
  System.out.println(Arrays.toString(buffer.array()));
  var bytes = new String(buffer.array(), 0, buffer.array().length, "UTF-16LE").getBytes(Charset.forName("UTF-8"));
  System.out.println(Arrays.toString(bytes));
  Files.write(Path.of("d:/a.txt"), bytes);
  JavaScript对应函数 - new TextDecoder('utf-16le').decode(uint16Array);