雑記

世界らん展2021

世界らん展2021に行きました。例年,東京ドームで開催されているイベントですが,今年はコロナ禍の影響からか,東京ドームではなくその脇にあるプリズムホールでの開催となってしまいました。出店も少なく,大したものは買えませんでした。

JDK

Java 16新機能(4)

Stream APIにtoList()メソッドが実装され,StreamからListへの変換が容易に行えるようになりました。これまではStreamのcollectメソッドにCollectors.toList()を渡していました。今後はStreamオブジェクトに対して直接toList()メソッドを実行してList型に格納してくれるようになります。

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class StreamToList
{
	public static void main(String[] args)
	{
		IntStream stream = IntStream.range(1, 100);
		//List<Integer> list = stream.boxed().collect(Collectors.toList());
		List<Integer> list = stream.boxed().toList();
		for(int item : list)
		{
			System.out.printf("%d\t", item);
		}
		System.out.println("");
	}
}

警告:実験的なモジュールを使用しています: jdk.incubator.foreign
警告1個
WARNING: Using incubator modules: jdk.incubator.foreign
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

JDK

Java 16新機能(3)

Java 16からは値ベースのAPIクラス,つまり,プリミティブ型のラッパークラスであるInteger,Long, Float, Double, Booleanなどが非推奨となり,コンパイル時に警告が出されるようになりました。次のような非常に簡単なプログラムをコンパイルしてみると,この振る舞いを確認できます。

public class ValueBased
{
	public static void main(String[] args)
	{
		Integer i = new Integer(1);
		Double d = new Double("123.54");
	}
}

警告:実験的なモジュールを使用しています: jdk.incubator.foreign
ValueBased.java:5: 警告:[removal] IntegerのInteger(int)は推奨されておらず、削除用にマークされています
Integer i = new Integer(1);
^
ValueBased.java:6: 警告:[removal] DoubleのDouble(String)は推奨されておらず、削除用にマークされています
Double d = new Double(“123.54”);
^
警告3個

JDK

Java 16新機能(2)

Java 16では,JDK内部の隠蔽が強化されています。例えば,enumクラスのprotectedメソッドclone()をリフレクションを使って呼び出してみましょう。このメソッドの実装は単にCloneNotSupportedExceptionをスローするだけのものになっています。次のようなプログラムを書いて,JDK11とJDK16での挙動の違いを比較してみます。

JDK11で実行した場合は,CloneNotSupportedExceptionがスローされており,protectedメソッドであるclone()がリフレクションによって実行できていることがわかります。しかし,JDK 16で実行した場合はsetAccessible()メソッドを呼び出した段階で,InaccessibleObjectExceptionがスローされています。そのため,invokeメソッドによってclone()メソッドを呼び出すことができていないことがわかります。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class StronglyEncapsulate
{
	enum EnumExample { A, B, C; }
	public static void main(final String[] args) throws Throwable
	{
		try
		{
			Method method = Enum.class.getDeclaredMethod("clone");
			method.setAccessible(true);
			String result = (String)method.invoke(EnumExample.A);
			System.out.println(result);
		}
		catch(InvocationTargetException e)
		{
			throw e.getCause();
		}
	}
}

JDK 11での実行結果

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by StronglyEncapsulate to method java.lang.Enum.clone()
WARNING: Please consider reporting this to the maintainers of StronglyEncapsulate
WARNING: Use –illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
Exception in thread “main” java.lang.CloneNotSupportedException
at java.base/java.lang.Enum.clone(Enum.java:165)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at StronglyEncapsulate.main(StronglyEncapsulate.java:12)

JDK 16での実行結果

警告:実験的なモジュールを使用しています: jdk.incubator.foreign
警告1個
WARNING: Using incubator modules: jdk.incubator.foreign
Exception in thread “main” java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Object java.lang.Enum.clone() throws java.lang.CloneNotSupportedException accessible: module java.base does not “opens java.lang” to unnamed module @13221655
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:357)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
at StronglyEncapsulate.main(StronglyEncapsulate.java:11)

JDK

Java 16 新機能(1)

3月16日にJava 16がリリースされました。早速,新しい機能を試してみることにします。

リリースノート(Oracle)

まずは,Foreign Linker APIというものです。これはJavaから他の言語で作成されたネイティブAPIを呼び出す機能です。従来はJNIという仕組みで行っていました。しかし,JNIはC言語のヘッダファイルを用意して呼出規約を定義する必要があり,非常に面倒でした。 Foreign Linker API はまだincubaterという位置づけですが,非常に簡単にネイティブ関数を呼び出すことができる魅力的な機能です。早く正式なAPIとしてJDKに入れて欲しいと願っています。

以下のプログラムではC言語の文字列長を返すstrlen関数を呼び出しています。strlenに”Hello World”という文字列を渡して,文字列の長さが11と返されています。

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import jdk.incubator.foreign.Addressable;
import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.LibraryLookup;
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemorySegment;
public class ForeignLinker
{
	public static void main(String[] args) throws Throwable
	{
		int length = callStrlen("Hello World");
		System.out.println(length);
	}
	public static int callStrlen(String str) throws Throwable
	{
		int result = 0;
		Addressable address = LibraryLookup.ofDefault().lookup("strlen").get();
		MethodType methodType = MethodType.methodType(int.class, MemoryAddress.class);
		FunctionDescriptor descriptor = FunctionDescriptor.of(CLinker.C_LONG, CLinker.C_POINTER);
		CLinker clinker = CLinker.getInstance();
		MethodHandle strlen = clinker.downcallHandle(address, methodType, descriptor);
		try (MemorySegment cString = CLinker.toCString(str))
		{
			result = (int)strlen.invokeExact(cString.address());
		}
		return result;
	}
}

警告:実験的なモジュールを使用しています: jdk.incubator.foreign
警告1個
WARNING: Using incubator modules: jdk.incubator.foreign
11

ちなみにこれをJNIで実現しようとすると,以下のようになります。

[Strlen.java]
public class Strlen
{
    static
    {
        System.loadLibrary("Strlen");
    }
    public static void main(final String[] args)
    {
        Strlen instance = new Strlen();
        int result = instance.execStrlen("Hello World");
        System.out.println(result);
    }
    public native int execStrlen(String arg);
}
[Strlen.h]
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Strlen */

#ifndef _Included_Strlen
#define _Included_Strlen
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Strlen
 * Method:    execStrlen
 * Signature: (Ljava/lang/String;)I
 */
JNIEXPORT jint JNICALL Java_Strlen_execStrlen
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif
[Strlen.c]
#include <string.h>
#include "Strlen.h"

JNIEXPORT jint JNICALL Java_Strlen_execStrlen(JNIEnv *env, jobject obj, jstring arg)
{
    const char* bytes = (*env) -> GetStringUTFChars(env, arg, NULL);
    size_t length = strlen(bytes);
    (*env) -> ReleaseStringUTFChars(env, arg, bytes);
    return length;
}