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;
}
JDK

Java 15の新機能(8)-Edwards-Curveデジタル署名アルゴリズム

Java 15では電子署名のアルゴリズムとして,Edwards-Curveデジタル署名アルゴリズム(EdDSA)が追加されている。これは,RFC8032で定義されているものだ。

 さっそく,EdDSAアルゴリズムを用いてデジタル署名を作成するプログラムを作成してみる。

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class EdDSAExample
{
    public static void main(final String[] args) throws Exception
    {
       KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("Ed25519");
       KeyPair keyPair = keyPairGen.generateKeyPair();
        String message = "Hello World";
        Signature signature = Signature.getInstance("Ed25519");
        signature.initSign(keyPair.getPrivate());
        signature.update(message.getBytes());
        byte[] sign = signature.sign();
        System.out.println(byteToHex(sign));
    }
    private static String byteToHex(byte[] bytes)
    {
        return IntStream.range(0, bytes.length)
                        .mapToObj(i->String.format("%02x", bytes[i]))
                        .collect(Collectors.joining(" "));
    }
}

コンパイルして,実行すると次のようなデジタル署名が出力される。

45 7a 1f 2e 40 0a 82 1a 56 e3 96 6e 0c b5 b5 f5 30 7f 28 f2 a1 64 49 5d 62 f6 74 46 6a f4 11 7e 8e dc d9 a7 ce 78 0e 65 ee 71 24 79 89 4f aa 8c 38 a2 98 55 10 4b 79 e1 76 6b d7 de 32 df 40 08

JDK

Java 15の新機能(6)-Foreign-Memoryアクセス

Javaヒープ外にメモリを割り当てたり,読み込んだりする手段が提供された。MemorySegmentインタフェースを用いて,メモリセグメントを取得する。asByteBuffer()メソッドを使えば,ByteBufferのメソッドを使って操作できるため,これまでと同じように扱うことができる。以下のプログラムでは4バイトのメモリを割り当て,全バイトに0xFFを書き込んでいる。そして,その領域をint型で読み込んでintでの値を出力する。

import java.nio.ByteBuffer;
import jdk.incubator.foreign.MemorySegment;

public class ForeignMemoryExample
{
	public static void main(final String[] args) throws Exception
	{
		try(MemorySegment segment = MemorySegment.allocateNative(4L))
		{
			ByteBuffer buffer = segment.asByteBuffer();
			buffer.put(new byte[]{(byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF});
			int intValue = buffer.getInt(0);
			System.out.println(String.format("int型での値は:%d", intValue));
		}
	}
}

これをコンパイルする。その際,module-info.javaを作成して,jdk.incubator.foreignモジュールを利用できるように記述しておく必要がある。

実行するには,次のコマンドを実行する。

java –add-modules jdk.incubator.foreign –enable-preview ForeignMemoryExample

実行すると,次のように出力されて,2の補数表現でintが取得された。

int型での値は:-1

JDK

Java 15の新機能(5)-Record型

Record型はJava 14にプレビュー機能として取り入れられて,引き続きプレビューとなっている。新たにシールインタフェースを実装できるように拡張されている。次のようにAnimalインタフェースを実装するレコードPersonを定義できる。

public class RecordExample
{
    public static void main(String[] args)
    {
        Person person = new Person("はらだ たかひこ", 38);
        System.out.printf("名前: %s\n", person.getName());
        System.out.printf("年齢: %d歳\n", person.getAge());
    }
}
sealed interface Animal permits Person
{
    public String getName();
}
record Person(String name, int age) implements Animal
{
    @Override
    public String getName()
    {
        return name;
    }
    public int getAge()
    {
        return age;
    }
}

レコード型については,次の記事を参照していただきたい。

https://jy-info-sys.com/2020/03/23/java-14-%e6%96%b0%e6%a9%9f%e8%83%bd-4/

JDK

Java 15の新機能(3)-テキストブロック

Java 14でプレビューとなっていたテキストブロック機能が正式リリースとなった。ヒアドキュメントのように複数行にわたって文字列リテラルを記述できる機能である。次のように「”」3つでリテラルを囲むだけでよい。

public class TextBlock
{
    private static final String text = """
            <html>
                <head>
                </head>
                <body>
                </body>
            </html>""";
    public static void main(String[] args)
    {
        System.out.println(text);
    }
}

上のコードをコンパイルして実行すると,以下のように出力される。

<html>
    <head>
    </head>
    <body>
    </body>
</html>