X

A blog about Oracle Technology Network Japan

Recent Posts

Oracle Cloud InfrastructureのWeb Application FirewallログをLogging Analyticsに送信してセキュリティ情報を取得する方法とは

※本記事は、Nazih Bachir DJOUMIによる"How to send OCI WAF Logs to OCI Logging Analytics and get Security Insights"を翻訳したものです。 1.はじめに Events Service、サーバーレス・ファンクション、Oracle Identity Cloud Serviceを使用して、Oracle Management Cloud Log Analytics Service経由でOracle Cloud Infrastructure Web Application Firewall(OCI WAF)のセキュリティ情報を取得する方法については、以前の記事でご紹介しました。 今回の記事では、Oracle Cloud Infrastructure(OCI)ネイティブの新サービスであるLogging Analyticsを使用して、OCI WAFのセキュリティ情報をもっとすばやく簡単に取得します。このネイティブとは、Oracle Cloud内に統合される機能として一から開発されたことを表しています。 以下の画像は、WAF Activity Overviewを表示したダッシュボードの例です。WAF Activity Overviewをインポートすることで、WAFログから幅広い有用な情報をすぐに確認できます。 この記事の最後に、さらに詳細にまでドリルダウンした別のWAFダッシュボードも紹介しています。これらのサンプル・ダッシュボードはすべて、この記事にリンクされているリソースに含まれています。   それでは、WAFログをOCI Object Storageバケットに転送する方法、転送されたログをバケットからLogging Analyticsサービスに取り込む方法、およびダッシュボードをLogging Analyticsサービスにインポートする方法について概要を見ていきます。 この記事では、OCIおよびREST APIについて理解できていることを前提に進めていきます。また、Oracle Cloudアカウントをすでにプロビジョニングして、OCI Logging Analyticsサービスをアクティブにし、前提条件のタスクをすべて完了して使用できる状態にあるという想定で説明します。   WAFポリシーがターゲット・コンパートメントのMycompartmentに構成されていること、OCI Object Storageバケットがこのコンパートメント内に作成されていることも必要です。また、アカウント内の管理者権限があることも前提条件となります。 この記事ではセキュリティ上の理由から特定のユーザーを使用しますが、その統合用ユーザーの名前をapi.waf.log.userとしています。 始める前に、OCI、WAFポリシー、OCI Object Storageのそれぞれから、必要になる詳細情報を入手しましょう。 収集した詳細情報については後の手順で簡単に確認できるように、手順の番号とともにテキスト・エディタに保存しておいてください。   2.情報収集 2.1 OCIから 2.1.1.「Administration」→「Tenancy details」に移動し、テナンシーのOCIDをコピーしておきます。   2.1.2.「Administration」→「Tenancy」で、「Region Name」を確認します。 「Administration」→「Region Management」で、そのリージョンに対応するRegion Identifierを保存しておきます。 2.1.3.「Administration」→「Identity」→「Users」で、WAFログとOCIバケット間の統合に使用するための新規ユーザーとしてapi.waf.log.userを作成します。「API Public Key」を追加し、Fingerprintをコピーしておきます。 2.1.4.OCIDをコピーしておきます。 2.1.5.「Customer Secret Keys」をクリックし、新しい秘密鍵を生成します。この秘密鍵を安全な場所に保存します。 2.1.6.アクセス・キーをコピーしておきます。 2.1.7.「Administration」→「Identity」→「Groups」で、新しいグループとしてapigroupを作成し、先ほど作成したユーザーをこのグループに追加します。グループのOCIDをコピーしておきます。 2.1.8.グループ名をコピーしておきます。   2.2 WAFから 2.2.1.「Security」→「WAF Policies」に移動し、作成済みのポリシーをクリックします。ポリシーのOCIDをコピーしておきます。  2.2.2.CNAME Targetをコピーしておきます。 2.2.3.ターゲット・アプリケーションのドメイン名をコピーしておきます。   2.3.Object Storageから 2.3.1.Object Storageに移動し、作成済みのバケットをクリックします。OCIDをコピーしておきます。 2.3.2.バケット名をコピーしておきます。 2.3.3.ネームスペースをコピーしておきます。 2.3.4.コンパートメントをクリックして、コンパートメントのOCIDをコピーしておきます。 2.3.5.コンパートメント名をコピーしておきます。   3.OCIバケットへのWAFログの転送 作成したOCIバケットにWAFログを転送するために、Oracle Supportを使用してSRを作成する必要があります。   3.1.IAMポリシーの設定 手順2.1.3で作成したユーザーには、バケットへの書込み権限が必要になります。そのために、このユーザーを含むグループに対して権限を付与する必要があります。 3.1.1.「Administration」→「Identity」→「Policy」に移動して、以下のポリシー文を作成します。 allow group <group_name_step_2.1.8> to manage object-family in compartment <compartment_name_step_2.5.5> 3.1.2.ポリシーのOCIDをコピーしておきます。   3.2.WAFログのストリーミングの有効化(SRが必要) ポリシーの設定後、Oracle WAF PortalサポートでSRを発行し、以下の情報を提供します。 アプリケーションのドメイン名(手順2.4.3)、および該当する場合は他のドメイン名 アクセス・キー(手順2.1.6) 秘密鍵(手順2.1.5) WAFポリシーのOCID(手順2.4.1) バケット名(手順2.5.2) バケットのOCID(手順2.5.1) ネームスペース(手順2.5.3) テナンシーのOCID(手順2.1.1) コンパートメントのOCID(手順2.5.4) ポリシーのOCID(手順3.1.2) リージョン識別子(手順2.1.2) バケットのリージョン アップロード接頭辞:"%{+YYYY}/%{+MM}/%{+dd}/%{[log_type]}" ユーザーのOCIバケットにログが表示されるようになるまで、実装に数日かかります。 実装の完了後、以下のようにWAFから到着したログがユーザーのOCIバケットに表示されるようになります。   4.OCIポリシーの作成 次に、Logging Analyticsサービスおよびダッシュボードの管理に必要となるすべてのポリシーを作成していきます。 ここでは手順を簡略化するために、先ほど作成したapigroupグループのメンバーであるapi.waf.log.userユーザーに対してポリシーを付与することにします。   「Identity」→「Policies」で、Logging Analyticsサービスの管理に必要となる以下の文を含む新しいポリシーを作成します。 allow service loganalytics to READ loganalytics-features-family in tenancy Allow group apigroup to manage all-resources IN TENANCY where any {request.permission='LOG_ANALYTICS_OBJECT_COLLECTION_RULE_CREATE',request.permission='LOG_ANALYTICS_LOG_GROUP_UPLOAD_LOGS',request.permission='LOG_ANALYTICS_ENTITY_UPLOAD_LOGS',request.permission='LOG_ANALYTICS_SOURCE_READ',request.permission='BUCKET_UPDATE',request.permission='LOG_ANALYTICS_OBJECT_COLLECTION_RULE_DELETE'} allow service loganalytics to read buckets in tenancy allow service loganalytics to read objects in tenancy allow service loganalytics to manage cloudevents-rules in tenancy allow service loganalytics to inspect compartments in tenancy allow service loganalytics to use tag-namespaces in tenancy where all {target.tag-namespace.name = /oracle-tags/} また、ダッシュボードの管理に必要となる以下の文も作成します。 Allow group apigroup to manage management-dashboard-family in tenancy   5.OCI LOGGING ANALYTICSサービスの準備 ここでは2つの手順を実行します。まず、ブログ・リソースのリンク先からログ・ソースとパーサーをインポートして、次に、OCI Logging Analyticsログ・グループを作成します。 ソースとパーサーをOCI Logging Analyticsサービスにインポートするには、このサービスに移動し、「Administration」をクリックして、Actionパネルの「Import Configuration Content」をクリックします。   ブログ・リソースからダウンロードしたログ・ソースのZIPファイルをドラッグ・アンド・ドロップして、「Import」をクリックします。   ログ・ソースとパーサーがインポートされたら、ログ・グループを作成しましょう。 そのために、「Log Groups」をクリックして、グループの作成先となるコンパートメントを選択します。関係性を分かりやすくするために、WAFポリシーとOCIバケットの作成先コンパートメントと同じものを使用できます。次に、「Create Log Group」をクリックします。名前と説明を入力し、「Create」をクリックします。OCIDが次の手順で必要になるため、コピーしておきます。   6.OCI OBJECT STORAGEからのログの収集 ここでは、WAFログを含むオブジェクト・ストレージからログを継続的に収集します。 ログの収集を有効にするには、ObjectCollectionRuleリソースを作成する必要があります。ObjectCollectionRuleリソースを作成するには、手順4で作成したIAMポリシーが必要になります。 この実行には、CLI、oci-curl、またはお好みのREST APIツールを使用できます。 以下の例ではCLIを使用します。 oci-cli environementを設定し、以下のコマンドについて、ルール名を設定し、compartmentId、osNamespace、osBucketName、namespace-nameの値を変更した上で実行します。 oci log-analytics object-collection-rule create --name rule_idcs --compartment-id XXX --os-namespace XXX --os-bucket-name XXX --log-group-id XXX --log-source-name IDCS --namespace-name XXX --collection-type HISTORIC_LIVE --poll-since BEGINNING logSourceNameの値は手順5でインポートしたものですので、そのままにしてください。 また、この例のCollectionTypeとpollSinceの値は、まずすべての履歴データを収集し、その後は新しいログを継続的に収集するという設定になっています。 他のオプションについては、こちらのリファレンスを参照してください。 この時点で、OCI Logging Analyticsに移動すると、以下のようなWAFログを確認できます。   7.ダッシュボードのインポート ここでは、ダッシュボードをOCI Logging Analyticsサービスにインポートします。 ブログ・リソースからダッシュボードのJSONファイルをダウンロードします。 このファイルを編集して、すべてのEDITCOMPARTMENTの部分を、ダッシュボードのインポート先となるコンパートメント・ターゲットのOCIDに書き換えます。 このコンパートメント・ターゲットはMycompartmentまたはMycompartmentの親コンパートメントとなります。 これで、ダッシュボードをインポートできるようになりました。 以下のoci-cliコマンドを実行します。 oci management-dashboard dashboard import --dashboards /path/to/dashboard.json Logging Analyticsサービスに5つの新しいダッシュボードが表示され、使用できるようになっているはずです。   Overviewダッシュボードのほか、以下のように他のセキュリティ情報を利用する詳細なダッシュボードも表示できます。 このブログの内容が皆さまのお役に立てば幸いです。最後に、Oracle Cloud Infrastructureを使い始めるための次のステップについて以下にご紹介します。 Oracle Cloud Infrastructureアカウントをお持ちでない場合は、Free Tierをお試しください。   8.参考資料 ログ・ソース ダッシュボード

※本記事は、Nazih Bachir DJOUMIによる"How to send OCI WAF Logs to OCI Logging Analytics and get Security Insights"を翻訳したものです。 1.はじめに Events Service、サーバーレス・ファンクション、Oracle Identity Cloud Serviceを使用して、Oracle...

Oracle Cloud Infrastructureでのワークロードの自動スケーリング

※本記事は、Peter Jausovecによる"Autoscaling your workload on Oracle Cloud Infrastructure"を翻訳したものです。 この記事では、Oracle Cloud Infrastructure(OCI)コンピュート・インスタンスのインスタンス・プール、インスタンス構成、および構成の自動スケーリングについて学習します。 インスタンス・プールは、同じ構成から複数のコンピュート・インスタンスを作成し、グループとして管理する目的で使用できます。また、自動スケーリング構成にすることで、インスタンス・プール内のコンピュート・インスタンスの数を自動で調整できます。自動スケーリング構成により、需要が多いときには一貫したパフォーマンスを確保し、需要が少ないときにはコストを削減することができます。 インスタンス・プールを作成する前に、インスタンス構成を作成する必要があります。インスタンス構成とは、コンピュート・インスタンスの作成時に使用される設定を定義したテンプレートです。 図1:コンピュート・ワークロードの自動スケーリングで使用するコンポーネント   インスタンス構成は、既存のコンピュート・インスタンスをテンプレートとして使用することで作成できます。インスタンス構成を一から作成する場合は、SDK、CLI、またはAPIを使用します。   インスタンスの作成 まずは、インスタンス構成のテンプレートとして使用できるコンピュート・インスタンスを作成しましょう。 ナビゲーション・メニューを開き、「Compute」に移動し、「Instances」をクリックします。 「Create Instance」をクリックします。 名前として、instance-templateと入力します。 「Change Shape」をクリックします。 Browse All Shapesダイアログ・ボックスで、「Virtual Machine」をクリックします。 「Specialty and Legacy」をクリックします。 お使いのリージョンで使用できるシェイプを選択します(例:VM.Standard.E2.1)。 「Select Shape」をクリックします。 「Create」をクリックします。 インスタンスがプロビジョニングされたら、インスタンスの詳細ページからインスタンス構成を作成できます。 図2:インスタンスの詳細    インスタンス構成の作成 次に、新しく作成したインスタンスをインスタンス構成のテンプレートとして使用します。 インスタンスの詳細ページで、「More Actions」メニューをクリックし、「Create Instance Configuration」を選択します。 Create in compartmentリストで、コンパートメントを選択します。 名前として、instance-configと入力します。 図3:インスタンス構成    「Create Instance Configuration」をクリックします。 インスタンス構成の作成後、詳細ページが表示されます(以下の図を参照)。 図4:インスタンス構成の詳細   インスタンス・プールの作成 インスタンス・プールはインスタンス構成ページから直接作成できます。それには、「Create Instance Pool」をクリックして以下の手順を実行します。 Compartmentリストで、インスタンス構成を作成したコンパートメントを選択します。 名前として、my-instance-poolと入力します。 Instance Configurationリストで、「instance-config」を選択します。 インスタンス数として、5と入力します。このインスタンス数は、インスタンス・プール内にプロビジョニングできるインスタンスの最大数を示します。プロビジョニング可能なインスタンス数は、テナンシーのサービス上限およびリージョン内のコンピュート・シェイプの可用性によって異なります。 「Next」をクリックします。 Configure Pool Placementページで、1つ以上の可用性ドメインを追加できます。各ドメインに対して、フォルト・ドメイン、プライマリ仮想クラウド・ネットワーク(VCN)、およびサブネットを指定できます。デフォルトでは、プール内のインスタンスは、容量に基づくベストエフォート型としてすべてのフォルト・ドメインに分散されます。あるフォルト・ドメインで容量が使用できない状態の場合、インスタンスが他のフォルト・ドメインに配置され、インスタンス・プールが正常に起動できるようになります。高可用性を確保するために、選択したフォルト・ドメインのそれぞれにインスタンスを均等に分散するように指定できます。 また、「Attach a load balancer」チェック・ボックスをオンにして、ロードバランサをインスタンス・プールに関連付けることができます。この機能を使用するには、既存のロードバランサを保持しておく必要があります。 次に、可用性ドメインとして「AD1」を選択し、さらにVCNとサブネットを選択します。「Next」をクリックして、「Create」をクリックすると、インスタンス・プールが作成されます。 インスタンス・プールの作成には数分かかります。作成後、左側のナビゲーション・ペインの「Created Instances」をクリックして、プール内に作成されたインスタンスのリストを開くことができます(以下の図を参照)。 図5:作成されたインスタンス    自動スケーリング構成の作成 ここで、既存のインスタンス数では現在の需要に対応できないというシナリオについて考えてみます。このようなシナリオで、自動スケーリング構成を作成して、プール内のインスタンスの数を自動的にスケーリングすることができます。 インスタンス・プールの詳細ページから自動スケーリング構成を作成しましょう。 「More Actions」メニューをクリックし、「Create Autoscaling Configuration」を選択します。 Create in compartmentリストで、インスタンス・プールを作成したコンパートメントを選択します。 名前として、my-autoscaling-configと入力します。 Instance poolリストで、「my-instance-pool」を選択します。 「Next」をクリックします。Configure Autoscaling Policyページで、「Metric-based Autoscaling」または「Schedule-based Autoscaling」を選択できます。 メトリックベースの自動スケーリング メトリックベースの自動スケーリングでは、CPU使用率またはメモリ使用率により、自動スケーリング・イベントをトリガーすることができます。この場合は、スケールアウトとスケールインのルールを定義します。これらのルールは、プールに追加する(スケールアウト)場合とプールから削除する(スケールイン)場合のしきい値のパーセンテージおよびインスタンス数を指定するものです。 また、スケーリングの上限も定義できます。この設定では、プールの初期インスタンス数(例では5)を使用します。また、ここではインスタンスの最小数と最大数を設定できます。さらに、クールダウン期間(秒単位)の設定によって、スケーリング・イベントに対して最小限の間隔(例では300秒つまり5分)が確保されるため、インスタンスの追加または削除が速すぎるという状況を防止できます。 以下の図は、CPU使用率ベースの自動スケーリング・ポリシーの例を示したものです。このポリシーでは、CPU使用率が70 %を超えると2つのインスタンスが追加されます。また、CPU使用率が40 %を下回ると、2つのインスタンスが削除されます。 図6:CPU使用率ベースの自動スケーリング  メトリックベースの自動スケーリングは、通信量を予測できず、CPU使用率またはメモリ使用率に基づいてスケーリングを自動化したい場合に使用します。需要を予測できる場合、または今後の需要増の発生(例:発売イベント)が分かっている場合は、スケジュールベースの自動スケーリングを使用します。 スケジュールベースの自動スケーリング スケジュールベースの自動スケーリングでは、cron式を使用して、プール内のインスタンス数を変更するタイミングを構成できます。cron式の詳細については、Cron Trigger Tutorialを参照してください。 例として、1月から3月までの期間、毎週月曜日の午後2時に一連の発売イベントの実施を予定している場合を考えてみます。この時間には10個のインスタンスが確実に実行されるようにしましょう。以下の図に、このポリシーを構成する方法を示します。ここでは、ターゲット・プール・サイズとして10を設定し、分(0)、時(14)、日(?)、月(1-3)、曜日(2)、年(*)の値を入力しています。 図7:スケジュールベースの自動スケーリング  スケジュールベースの自動スケーリングでは、複数の自動スケーリング・ポリシーを同時に定義できます。たとえば、上で定義したポリシーの例ではインスタンスの数が10にスケールアウトされるだけで、発売イベント終了後のインスタンス数のスケールインは実行されません。月曜日の発売イベントの終了後にインスタンス数を減らすとなれば、別のポリシーを追加し、ターゲット・プール・サイズを5に設定する必要があります。 自動スケーリング・ポリシーの構成後、「Create」をクリックしてポリシーを作成します。同じインスタンス・プールに対して複数の自動スケーリング構成を定義でき、これらの定義は個別に有効化または無効化できます。   まとめ この記事では、インスタンス・プール、インスタンス構成、および構成の自動スケーリングについて学習しました。また、CPU使用率、メモリ使用率、または定義したスケジュールに基づいてインスタンス・プールのスケールインまたはスケールアウトを実行するための、メトリックベースおよびスケジュールベースの自動スケーリング構成の作成方法について確認しました。 需要が多いときには一貫したパフォーマンスを確保し、需要が少ないときにはコストを削減するために、以下のリソースを活用してください。 参考資料 インスタンス・プール インスタンス構成 自動スケーリング ユースケースには何一つ同じものはありません。Oracle Cloud Infrastructureがご自分に合ったものなのかを判断できる唯一の方法が、お試しいただくことです。Oracle Cloud Free Tierまたは30日間の無償トライアルをお選びいただけます。無償トライアルには、コンピュート、ストレージ、ネットワーキングを含む幅広いサービスを使い始めるための300米ドル分のクレジットが含まれています。

※本記事は、Peter Jausovecによる"Autoscaling your workload on Oracle Cloud Infrastructure"を翻訳したものです。 この記事では、Oracle Cloud Infrastructure(OCI)コンピュート・インスタンスのインスタンス・プール、インスタンス構成、および構成の自動スケーリングについて学習します。 インスタンス・プールは、同...

全国のOTNおすすめ技術者向けセミナー、イベント&書籍(2020年12月)

セミナー&イベント ORACLE MASTER 資格取得者数 27万人突破 フェア開催中 日本オラクルが制定している認定資格制度「ORACLE MASTER」の資格取得者が、累計27万人を突破したのを記念して、「ORACLE MASTER 取得応援フェア」を期間限定で実施中。無償セミナーやスキルチェック等どなたでもご参加いただけます。この機会に是非お試しください。   Oracle University 無償オンラインセミナー(12月) Oracle University の無償オンラインセミナーに参加しませんか。ORACLE MASTER 新資格体系やオラクル認定 Java 資格に関する最新の情報や解説をお届けします。   ウェビナーシリーズ|毎週水/木、約1時間でわかるOracle Cloud 進化し続ける Oracle Cloud InfrastructureやOracle Database をはじめとする、さまざまな製品についての最新情報や活用事例および技術情報を業務部門からIT部門のエンジニアの方々までの幅広い皆様へ向けてウェビナーを通じてお届けします。さまざまなテーマや理解度レベルのコンテンツを取り揃えていますので、ぜひご活用ください。 【直近の開催予定】 11月26日(火)13:00-13:50 「パブリック・クラウドのデータ管理を強化する方法 ~ ExaC@Cで得られるメリット ~」 11月26日(木)15:00-16:00 「Analytic Actions Power Augmented Work」 12月2日(水)15:00-16:00 「【まずはここから】 はじめてのOracle Cloud Infrastructure」 12月11日(金)15:00-17:00 「一歩進んだ分析でビジネスにチカラを!! ~あなたもデータ アナリスト~」 【エントリーシリーズ】【ファンデーションシリーズ】【プロフェッショナルシリーズ】も開催。詳しい内容は特設ページをご覧ください。また、過去に開催したウェビナーもオンデマンドでご覧いただけます。   【期間限定特別編】オラクルの開発責任者が語る、データマネジメントの未来 社会インフラ、エンタープライズ企業のビジネス基幹システムを支援し続けるOracle Database。その製品開発責任者がデータマネジメントのこれからや、トレンド、企業においてのデータ活用についてのヒントをご紹介します。 10月1日(木)15:00-16:00 The Modern Data Warehouse 〜 次世代データウェアハウス基盤で広がるデータ相乗効果の加速 ~ 10月14日(水)15:00-16:00 Lowering Costs with Database Consolidation 〜 コスト削減へ! データベース集約による統合・合理化 ~ 【エントリーシリーズ】【ファンデーションシリーズ】【プロフェッショナルシリーズ】も順次開催予定。詳しい内容は特設ページをご覧ください。   書籍 予約受付中!Oracle Cloud Infrastructure エンタープライズ構築実践ガイド Oracle Cloudは、Amazon Web Services(AWS)やMicrosoft Azure、IBM Cloudと同様に、オラクルが提供するクラウドコンピューティングサービスで、Oracle Cloud Infrastructure(OCI)は多くのサービス群が統合されたクラウド基盤です。高い可用性とパフォーマンスが求められるミッションクリティカルなシステムの中心にあるOracleデータベースもクラウド環境で実現できます。本書では、基本的なサービスのほか、システム基盤の設計・構築・実装方法をハンズオン形式で解説しているので、Oracleデータベース管理者はもちろん、他のクラウドアーキテクトの方もミッションクリティカルなシステムの構築方法を学ぶことができます。(商品解説より) Oracle ACE 大塚 紳一郎 氏による連載記事:「Keys to the Oracle Cloud」もご覧ください。   AutonomousDatabaseを無期限 / 無料(Always Free)で使用可能になりました。 Cloudをまだお試しでない方は、無料トライアルをご利用下さい。          

セミナー&イベント ORACLE MASTER 資格取得者数 27万人突破 フェア開催中 日本オラクルが制定している認定資格制度「ORACLE MASTER」の資格取得者が、累計27万人を突破したのを記念して、「ORACLE MASTER 取得応援フェア」を期間限定で実施中。無償セミナーやスキルチェック等どなたでもご参加いただけます。この機会に是非お試しください。   Oracle...

Javaに演算子オーバーロードを導入すべきときが来たのか

※本記事は、Mahmoud Abdelghanyによる"Is it time for operator overloading in Java?"を翻訳したものです。 賛否が両極端に分かれる演算子オーバーロード。本記事は、コードの読み書きとデバッグを簡単にするために演算子オーバーロードは欠かせないと主張する 著者:Mahmoud Abdelghany 2020年6月8日 演算子オーバーロードは一風変わった言語機能の1つで、賛否が両極端に分かれます。強く否定する意見があるのもうなずけます。演算子オーバーロードの利用方法が適切でなければ、たちまちコードはわかりにくくなり、さらに厄介なバグが生まれることになるからです。本記事では、算術演算のプログラミングを題材に、強く支持する意見を簡単に述べたいと思います。 まず、用語を定義するために、ISOのC++ Wikiを確認してみます。そこには、「演算子オーバーロードにより、C/C++のユーザー定義型(クラス)における演算子の意味をユーザーが定義できるようになります。オーバーロードされた演算子は、関数呼出しのシンタックス・シュガーです」と書かれています。 つまり、クラスFooを定義した場合、FooBar = Foo + Bar;のようなプラス演算子の実装も定義できるということです。 演算子オーバーロードは、それほど重要でない言語機能だと一般的に考えられています。シンタックス・シュガーとは、このような事象を表すためにもっともよく使われる用語です。先ほどの定義で、シンタックス・シュガーという部分は正しい記述です。ほぼすべてのプログラミング言語には、シンタックス・シュガーの抽象化が含まれており、複雑な機械語のコードを実際には書かなくても、それに相当する処理を書くことができます。 この点を踏まえて、Javaの世界で演算子オーバーロードがたびたび忌み嫌われている理由の1つ目について見てみます。その最大の理由は、cout<<にあります。この問題は、<<演算子に象徴されます。次のC++文では、この演算子が適切にオーバーロードされています。 cout<<"what the #@$#"<<endl; この文は、次の文と同じ意味です。 System.out.println("what the #@$#"); ここでは、対象を左にシフトしているのではなく、標準出力(通常はコンソール)を意味するcoutに文字列を渡しています。 C++などの言語では、カンマなどの見慣れない演算子をオーバーロードすることができます。不思議なことに、これにも用途があります。さらに、独自の演算子を定義できる言語も存在します。これにも興味深い用途があるのですが、Javaでの演算子オーバーロードの価値について説明する本記事の範囲を超える内容です。 見慣れぬ演算子のオーバーロードはさておき、実情としては、誰かがオーバーロードした演算子を使う場合、実行される演算がその演算子の数学的目的と一致しているという保証はもはや存在しません。 最初に挙げた例、FooBar = Foo + Bar;について考えてみます。 +演算子が加算に似た操作を実際に行うということは、どうすればわかるか 演算によってFooやBarが変更されたらどうなるか また、実行するたびに、単にランダムなFooBarが返されたらどうなるか 副作用はどうか これらはすべて的を射た質問ですが、実は演算子オーバーロードに限った話ではありません。先ほどの式を、次のように書き換えたとします。 FooBar = Foo.plus(Bar); このように変更したからといって、何か別のことが保証されるというわけではありません。演算子オーバーロードに関して考えられる弊害のほとんどは、メソッドの世界でも大して変わりません。その理由を20ページにわたって書き連ねることもできますが、本記事では、Javaに演算子オーバーロードを導入する準備は整っているのか、そして、演算子オーバーロードはどのように役立つ可能性があるのかという2つの問いに答えることに主眼を置きます。まずは、2番目の問いに答えるところから始めます。   演算子オーバーロードは役に立つのか、そうであればどのように役立つのか 少しの間、次のコードを眺めてみてください。どのような意味だと思うでしょうか。 final BigDecimal result = a.multiply(x.pow(2)).plus(b.multiply(x.plus(c))); 図1のような標準形の2次方程式だと答えた方は、ほぼ正解です。   図1:2次方程式  それでは、演算子オーバーロードを使った場合、どんなコードになるでしょうか。次のようなコードになって読みやすくなるでしょう。 final BigDecimal result = a * (x * x) + b * x + c; もう少し複雑な例を見てみます。この2次方程式からxを求めたい場合、変形や整理などを行って、図2のようにします。   図2:方程式の解 演算子オーバーロードを使用せずに、BigDecimalを使用したコードでこの解を表した場合、どのようになるでしょうか。 final BigDecimal sqrt = b.pow(2).min(a.multiply(c).multiply(4)).sqrt(); final BigDecimal x1 = b.negate().plus(sqrt).divide(a.multiply(2)); 演算子オーバーロードを使用した場合、次のコードになります。 final BigDecimal sqrt = (b * b - 4 * a * c).sqrt(); final BigDecimal x1 = (-b + sqrt) / (2 * a); これは好みの問題ですが、多くどころか、おそらくほとんどの開発者は、演算子オーバーロードを用いたものの方が読みやすく、書きやすく、検証しやすいとわかるのではないかと思います。 書きやすいということについて言えば、コードで抽象化が使われる理由には、読みやすさと書きやすさという両方があります。a.multiply(b).multiply(4)よりも、4*a*cの方がはるかに書きやすいというのが筆者の考えです。シンプルにもなっているため、バグが入り込む余地が小さくなっています。   バグへの影響 謝らなければならないことがあります。実は、本記事の最初の例にはバグがあります。おそらくこれは、筆者自身も含め、ほとんどの方が見逃してしまうようなものでしょう。ヒントを出すために、演算子オーバーロードを用いた方にもバグが含まれるように書き換えてみます。そうすれば、バグはほぼ一瞬で明らかになります。 final BigDecimal result = a * (x * x) + b * (x + c); このバグは、演算子の優先順位が間違っているという単純なものです。演算子オーバーロードを使えばすぐに明らかになりますが、問題を引き起こすという点はいずれのコードも同じです。 さらにわかりにくいバグとして、ユーザー定義型の推移的結合があります。ただしこのバグは、演算子オーバーロードを使ってその解釈をコンパイラに任せれば、何もしなくても解決します。次の例について考えてみます。 class Vector{ float x, y, z; } final Vector result = vec1 + vec2 + vec3; final Vector result = vec1.plus(vec2.plus(vec3)); 1,000回のうち999回は、いずれの行も同じ結果になるでしょう。しかし、結果が異なる場合もあります。その理由は、演算子の優先順位にあります。 a + b + cとc + b + aに違いがあるのかと思う方もいるかもしれません。これらの演算には結合法則が成り立つのではないでしょうか。 現実の世界ではそうですが、デジタルの世界では状況次第です。 ご覧のとおり、Vectorクラスには3つの決まった浮動小数点数が含まれています。バイナリの浮動小数点演算では、バイナリ値で表現できる、一番近い値が演算の結果となります。そのため、a.x + b.x = 0.23548787だった場合、32ビット幅の値しかなく、すべての小数を正確に表せるわけではないため、コードの結果が0.2354877Fになる可能性があります。 この情報をコードに適用すると、a + bは近似値となることがわかります。その後、この数はcと加算され、さらに別の近似値が生成されます。 そのため、コードの実行によって不可逆変換が起きる可能性があるため、逆順で同じ計算を行っても結合法則は成立しません。この点は、IEEE浮動小数点演算基準にも記載されています。ほとんどの場合、アプリケーションが表現可能な小数を使っていたり、開発者が損失を気にしなかったりするため(画面のグラフィック計算を行う場合など)、結果は一致することになります。次に例を示します。 final float a = 3.3333333f; final float b = 0.6373606f; final float c = 0.36263946f; System.out.println(a + b + c);//4.3333335 System.out.println(c + b + a);//4.333333 通常、演算を間違った順序で書いた場合や、不適切な括弧を追加した場合、演算子オーバーロードを用いてもこのバグは発生します。しかし、閉じ括弧の位置が前すぎたり後すぎたりするという基本的なバグは、不思議なくらいきれいになくなります。 訂正として、バグのないコードを次に記しておきます。 final Vector result = vec1.plus(vec2).plus(vec3); 演算子オーバーロードを用いずに何らかの計算を行わなければならない場合、こういった種類のバグはとてもよく見られます。その理由は、関数がこのような呼び出され方を想定して作られたものではないからです。学校で代数を習った私たちの脳も、そのように働くものではありません。 James Gosling氏は、「The Evolution of Numerical Computing in Java」(Javaの数値計算の進化)で次のように述べています。「私が知る数値演算を行っている人はすべて、演算子オーバーロードは絶対に欠かせないと主張している。式をメソッド呼出し形式で書くのは、あまりにも不格好で実質的に役に立たない」 演算子について、もう1つ興味深い事実があります。演算子は、それが起こす可能性のある副作用について表現できるということです。言葉を換えれば、プラス演算子は定義上、2つのオペランドを加算し、どちらのオペランドも変更せずに結果を生成します(ただし、ほとんどの言語はプログラマーが演算子をオーバーロードするときに何でも認めてしまうため、必ずそうなるという保証はありません)。明らかな例は、Stringのプラス演算子のオーバーロードです。しかし、plusメソッドには何の前提もありません。メソッドは、望むことが何でもできるものとして理解されているからです。 プログラマーにとってはこちらの方がはるかにおもしろいかもしれませんが、演算子が読取り専用の演算を行うことがわかれば、コンパイラは強力な最適化を行えるようになります。   Javaに演算子オーバーロードを導入する準備は整っているのか この問いかけを行うということは、自ら墓穴を掘るようなものでしょう。というのも、これに回答するのはほぼ不可能だからです。そこで、ニーズとメリットについて考えてみることにします。興味深いことに、データ解析や金融取引の分野でJavaアプリケーションの使用が増えるに従って、このニーズはいつの間にか急上昇しています。このような分野では、高度な数式が多用されています。 もう1つの興味深い点は、ゲーム分野の発展です。すべてJavaで書かれたMinecraft(歴史上特によく売れたゲームの1つ)が登場したことは、ゲームの分野でもJavaは他の最高の作品と肩を並べることができるという証になりました。もちろん、3Dゲーム・エンジンはすてきな代数学と幾何学の集合体でしかありません。 Clojure、Groovy、Kotlin、ScalaなどのJVM言語は、すべて何らかの形で演算子オーバーロードを実装しています。この点も、Javaコミュニティがそのような発展に対してオープンであることを示す明らかな印だと考えることができるでしょう。 Project Valhallaでは、Java開発者にvalue型が提供される予定です。これは、プリミティブタイプのように振る舞うユーザー定義オブジェクトです。つまり、int64やunsigned_int32といった型を独自に定義できるようになるということです。ただし、この新たな値型に対して関数呼出しで演算を行わなければならないというのは、残念なことです。演算子オーバーロードにとって、値型は完璧なユースケースになると考えられます。 補足ですが、一部のプロセッサ・アーキテクチャでは、int64がCPUレジスタに収まります。つまり、64ビット命令を使った算術演算を行う際のJVM最適化をさらに進めることができます。Javaに演算子オーバーロードや、特定の関数が追加型(64ビット幅整数など)の算術演算を行うものであることを示す何らかの仕組みがあれば、この処理は簡単になります。   まとめ 存在するすべての言語機能やライブラリは、諸刃の剣となる可能性を秘めています。起きる被害が明らかなので回避しやすい機能もあれば、わかりにくく見慣れないため被害を回避しにくい機能もあります。 ベスト・プラクティスが導入される理由は、被害を最小限にとどめ、未知の被害について開発者を啓蒙するためです。その点に関して言えば、演算子オーバーロードも例外ではありません。数学的な問題のプログラムを書いたり、そのエラーをデバッグしたりするときに適切に使えば、とても便利なツールになり、多くの時間を節約することができます。つまり、開発者が無駄にする時間が少なくなり、解決しようとしている実際の問題に費やせる時間が増えることになります。 Mahmoud Abdelghany Mahmoud Abdelghany:Javaテクノロジーを専門とするオランダのコンサルティング会社Blue4ITのエンジニア。多くの時間を費やしてゲームを研究することがきっかけとなり、1年の大部分をかけて演算子オーバーロードについて調査している。Twitterのフォローはこちらから。

※本記事は、Mahmoud Abdelghanyによる"Is it time for operator overloading in Java?"を翻訳したものです。 賛否が両極端に分かれる演算子オーバーロード。本記事は、コードの読み書きとデバッグを簡単にするために演算子オーバーロードは欠かせないと主張する 著者:Mahmoud Abdelghany 2020年6月8日 演算子オーバーロードは一風変わっ...

Java 14、Java 15、Java 16、およびそれ以降でのプレビュー機能の役割

※本記事は、David Delabasseeによる"The role of preview features in Java 14, Java 15, Java 16, and beyond"を翻訳したものです。 プレビュー、試験運用版、インキュベータ版の各機能について、オラクルが新しいJDKの機能性に関するフィードバックを集める仕組み 著者:David Delabassee 2020年6月8日 多くの人々が本番環境でJavaを使用して重要なワークロードを実行している今、Javaには世界的な影響力があります。その深さと広さを考えれば、新機能は明確かつ完全に設計されなければならないだけでなく、信頼性とメンテナンス性が高い形で実装されなければなりません。Java開発者が本番環境で使用するために提供される新機能は、すべて最高の品質を持ち合わせている必要があります。したがって、開発者に前もって新機能を使ってもらい、フィードバックを求めるという手続きが欠かせません。フィードバックは、機能の改善と、最終的な確定版として期待される品質レベルの実現に役立ちます。 未確定の新機能には、いくつかの分類があります。 プレビュー版:Javaプラットフォームの新しい機能のうち、仕様が完全に作成され、完全に実装されているが、まだ調整の余地があるもの 試験運用版:主にHotSpot JVMの新機能 インキュベータ版(インキュベータ・モジュール):新しいAPIやJDKツールになる可能性があるもの さらに、これら3つの分類には当てはまらない未確定機能も存在しますが、それについては後ほど説明します。 オラクルは、プレビュー機能、試験運用版機能、インキュベータ・モジュールを活用してコミュニティからフィードバックを集め、その後に新機能をJavaプラットフォームの確定版機能としています。本記事では、この一連の作業がどこでどのように行われているかについて説明します。図1に、新機能の進展状況の例をいくつか示します。   図1:最近の新機能3つの進展状況 ほぼすべての新機能は、JDK拡張提案(JEP)として始まります。JEPは、重要なJDK拡張を管理するための明確に定められた仕組みです。たとえば、次のようなものがあります。 Java言語仕様のJava言語機能(テキスト・ブロックやレコードなど) コアJavaプラットフォームのJava SE API機能(java.lang.Object、java.lang.String、java.io.Fileなど):このようなAPI機能は、名前がjavaで始まるモジュールに含まれる JDK固有機能が含まれるJDK API機能(JDK Flight Recorderなど):このようなAPI機能は、名前がjdkで始まるモジュールに含まれる JDKツール機能(jshellやjlinkなど) Java仮想マシンのOpenJDK実装であるHotSpot JVMの固有機能:ここには、アプリケーション・クラス・データ共有とZガベージ・コレクタ(ZGC)という2つの機能などが含まれる これらの機能の関係を図2に示します。   図2:プレビュー機能、試験運用版機能、インキュベータ版機能が存在する場所  ところで、JDKについて話すとき、Java APIという用語はJava SE APIとJDK APIの両方を表す言葉として使われることが多くなっています。 それでは、重要性はどのように決まるのでしょうか。拡張が重要だと見なされるのは、需要が高い場合、JDKや、JDK自体が開発されるプロセスおよびインフラストラクチャに影響がある場合、あるいは単に多くのエンジニアリング作業が必要になる場合です。JEPのプロセスは、機能の非推奨化や既存機能の改善にも使われます。 ほどんどの大型機能は、JEPを使用する2段階のアプローチに従って導入されます。すなわち、事前アクセス・フェーズから始め、その後にアクティベーション・フェーズが続くという方式です。事前アクセス・フェーズは、1回で済むこともあれば、複数回繰り返されることもあります。その間、開発者は未確定の新機能を使用できます。事前アクセス・フェーズの期間(複数回設けられることもあります)中、開発者は積極的に未確定機能を使用し、その経験をもとにフィードバックを送ることが推奨されています。 提供されたフィードバックから改善の余地が明らかになった場合、その次の事前アクセス・フェーズで対処される可能性があります。このフィードバックは、新しい言語機能が扱われることが多い、プログラマー向けガイドや、新しいAPIのJavadocサンプル、FAQなどのドキュメントの改善に役立てられることもあります。 そして、新機能の準備が整ったと判断されたとき、最終フェーズに移り、そこで新機能がJavaプラットフォームの確定版機能となります。 新機能に取り組んでいるエンジニアがどんな種類のフィードバックを待っているかという点を踏まえて、期待されているフィードバックを知ることが重要です。このようなエンジニアは、開発者がどのように新機能を使用しているかということに主たる関心があります。たとえば、新機能を使用したときに技術的な問題や互換性の問題があるかどうか、新機能は既存機能と緊密に統合されるかどうか、コードをリファクタリングして新機能を使用することに支障があるかどうか、といった点です。 注:新機能に取り組んでいるエンジニアは、実のところ、別のアイデアや、部分的に関連している問題のソリューション候補を探しているわけではありません。また、その機能に関連する要望を探しているわけでもありません。なぜでしょうか。そのような提案は、Javaプラットフォーム全体のビジョンを無視した、短期的で限定的なメリットを求めるものであることがほとんどだからです(実際に機能を使ってみることさえせず、単なる反対や、どのみち役に立たない提案を行いたいという人からのフィードバックには、特にそのような傾向があります)。 それでは、フィードバックを送るべきなのは、どのような人でしょうか。エンジニアたちが歓迎しているのは、Java開発者からのフィードバック(新しいAPIに関するものなど)やツールのベンダーからのフィードバック(新しいJDKツールに関するものなど)です。究極的には、建設的で今後の行動につながるフィードバックならすべて歓迎です。 ところで、フィードバックは、使用を求められている手段で送ることが重要です。ソーシャル・ネットワークに投稿されたコメントが検討される可能性はほとんどありません。そのため、各JEPでは、フィードバックを集めるためのメーリング・リストを明示しています。たとえば、JEP 359では、amber-devメーリング・リストにフィードバックを送ることを求めています。 要するに、Javaエンジニアたちは、早い段階で未確定版の新機能を使ってもらうことで、新機能について、実際に使用した経験の報告や、その後の行動につながるフィードバックを求め、必要な調整を行っています。その後、新機能は最終的な確定版としてJavaプラットフォームに組み込まれます。   プレビュー機能 Java言語機能とJava SE API機能はとても広く使われているため、設計にミスがあれば否定的な結果につながってしまいます。そのようなリスクを避けるため、あるJEP(JEP 12)で、新しいJava言語およびJava SE API機能のプレビューを行えるようにすることが提案されました。プレビュー機能とは、仕様が完全に作成され、完全に実装されていると考えられるものの、Javaプラットフォームに最終的な確定版として組み込まれる前に変更される可能性があるものを指します。収集されたフィードバックは、評価を経た後、機能が確定版になる前に最終の調整を行うために使用されます。 たとえば、Project Amberは、Java言語を進化させることで開発者の生産性向上を目指すOpenJDKプロジェクトです。Amberでは、プレビュー機能の仕組みを活用して、標準の確定版機能をJavaプラットフォームに少しずつ導入しています。Amberの新機能が確定版になる前に、その後の行動につながるフィードバックを集めるためには、2回のプレビュー・ラウンドが妥当と考えられることがわかります(図3参照)。   図3:Java15時点で提供されている、Project Amberの機能  さらに詳しく見てみるために、switch式について考えてみます。switch式はProject Amberで開発され、Java 12(JEP 325)とJava 13(JEP 354)でプレビューが行われ、Java 14(JEP 361)で標準の言語機能になりました。 Java 12では、switch式の値を生成するためにbreakキーワードが使われていました(例:break 42;)。しかし、フィードバックからこのようなbreakの使い方はわかりにくいという可能性が浮上したため、Java 13では、同じことをするためにyieldキーワードが導入されました(例:yield 42;)。 確定版のswitch式(Java 14)では、Java 13においてプレビューが行われたyieldのアプローチがそのまま使用されています。プレビュー機能は確定版に非常に近いものとして作られていますが、オラクルはプレビュー版の間で変更を行う権利を持っています。たとえば、Java 12のプレビュー機能でのbreak 42;をJava 13のプレビュー機能でのyield 42;に変えるような変更です。長期互換性のルールに従うのは、Java 13のyieldを引き継いだ確定版、つまりJava 14のswitch式だけです。   試験運用版機能 試験運用版機能は、HotSpotの重要な拡張についてのフィードバックを集めるための試験台として使用される仕組みです。プレビュー機能でのJEP 12とは異なり、試験運用版機能について定めたJEPはありません。試験運用版機能のプロセスは、公式なプロセスというよりは、HotSpotの慣習として確立されたものです。 例として、Zガベージ・コレクタを取り上げます。ZGCは低遅延ガベージ・コレクションであり、一時停止時間は10ミリ秒未満、通常は2ミリ秒程度です。さらに、数メガバイトという小さなものから、数テラバイトという大きなものまで、ヒープ・サイズによらないという特徴もあります。 ZGCチームは、試験運用版機能の仕組みを複数回活用しました。最初のZGCは、JDK 11でLinux x64限定の試験運用版機能として導入されました(JEP 333)。それ以降、ZGCに改善が追加されました(同時クラス・アンロード、メモリの返却、プラットフォームの追加など)が、一方で削除されたZGC機能もありました。 試験運用版機能の仕組みを複数回使用して得られたフィードバックや使用報告すべてにより、ZGCを徐々に改善することができました。現在では、HotSpot機能に期待される高品質を備えるようになっています。その結果、現在、JEP 377で、ZGCをJDK 15の正式なHotSpot本番環境向け機能とする提案が行われています。   インキュベータ・モジュール JEP 11でインキュベーションという概念が導入されています。今後改善や安定化が行われてから将来的にJava SEプラットフォームまたはJDKの一部としてサポートされる可能性がある、JDK APIおよびJDKツールをインキュベーションの対象に含めることができます。たとえば、HTTP/2 Client APIは、JEP 110によってJDK 9およびJDK 10でJDK固有のAPIとしてインキュベーションが行われてきました。最終的にはインキュベーション・フェーズを抜けて、Java 11で標準Java SE APIの一部となりました(JEP 321)。   未確定機能の使用 開発者が意図せず未確定機能を使ってしまうことがないように、重要な予防策が導入されています。この対策が必要なのは、未確定機能が後のJavaフィーチャー・リリースで最終的な確定版になる際に、変更される可能性があるためです。さらに、Javaの厳密な下位互換性ルールの対象になるのは、最終的な確定版機能のみです。 そのため、意図せずに使用されることがないように、プレビュー機能と試験運用版機能は、デフォルトでは無効になっています。また、JDKドキュメントでも、こういった機能やその関連APIは未確定であることが開発者に対して明確に警告されています。 プレビュー機能は特定のJava SEフィーチャー・リリースに固有です。プレビュー機能を使うためには、コンパイル時と実行時に特殊なフラグを指定する必要があります。あるJava SEプラットフォーム・リリース(たとえばJava 14)でjavac --enable-preview --release 14 ...と指定することにより、プレビュー機能を使用したクラス・ファイルをJavaコンパイラで生成できます。その際、コンパイラはプレビュー機能が使われていることを開発者に警告します。同様に、java --enable-preview ...と指定することにより、対応するJVM(この場合はバージョン14)でクラスを実行できます。また、jshell --enable-previewと指定することにより、対応するバージョンのjshellでプレビュー機能を使用できます。 ほとんどのIDEではプレビュー機能の使用に対応しています。そのため、開発者がお気に入りのIDEでプレビュー機能を使用できるだけでなく、プレビュー機能が最終的な確定版になってから短時間でIDEが対応するようになっています。たとえばIntelliJ IDEA 2020.1では、"Project"と"Modules"の設定で"Project language level"を"14"から"14 (Preview) - Records, patterns, text blocks"に切り替えるだけで、JDK 14のプレビュー機能を有効化することができます。 ところで、未確定機能が必要なアーティファクトは配布するべきではありません。たとえば、プレビュー機能を利用したアーティファクトをMaven Centralで配布することは避けてください。このようなアーティファクトは、特定のJavaフィーチャー・リリースでしか実行できないからです。 試験運用版機能は、JVM機能であり、デフォルトでは無効になっています。HotSpotで試験運用版機能を使用できるようにするためには、-XX:+UnlockExperimentalVMOptionsフラグを指定します。そして実際の試験運用版機能は、固有のフラグを追加指定して有効化することができます。たとえばZGCの場合、-XX:+UseZGCとします。 さらに、インキュベータ・モジュールも、意図せずに使用されることがないように保護されています。インキュベーションを行えるのは、jdk.incubatorネームスペースのみであるため、クラスパス上のアプリケーションでは、--add-modulesコマンドライン・オプションを使って明示的にインキュベータ版機能の要求を解決する必要があります。一方、モジュールを使用するアプリケーションでは、インキュベータ版機能に対して直接requiresまたはrequires transitive依存性を指定する必要があります。   補足:早期アクセス・ビルドについて OpenJDKの長期プロジェクトには、Loom、Panama、Valhallaといったものがあります。これらのプロジェクトの目的は、Javaプラットフォームの特定の部分に対して思い切った改善(または完全な再構築)を行うために、個別の領域について基本的な調査をすることにあります。たとえば、Loomの目的は、スレッドを軽量化して使いやすくすることで、Javaプラットフォームの同時実行性を大幅に向上させる点にあります。 これらのプロジェクトで扱う範囲が広大であることを考えれば、いくつかのJavaフィーチャー・リリースにわたって複数の機能が提供され、それらの機能全体によって、取り組みの対象となっている領域に対処することになるでしょう。これを実現するために、さまざまな調査が行われ、可能性のあるソリューションを試すために各種のプロトタイプが開発されることになります。その中には、断念されるアプローチや再考されるアプローチも出てくるかもしれません。 この作業に多くの時間やエンジニアリング作業が必要になることは、言うまでもありません。これらのプロジェクトのもとで開発される新機能は、まったく完成しておらず、安定性が見込めるものでもないため、フィードバックを求める通常の仕組みを活用することはできません。しかし、使用に関するフィードバックに価値がないというわけではありません。それどころか場合によっては、早期のフィードバックによって、設計上の検討事項の一部が明らかになることや、初期プロトタイプが検証されることもあります。 早い段階でそのようなフィードバックを集めるため、設計段階や開発段階で新機能の早期アクセス・ビルドが個別に提供される場合もあります。このような機能固有の早期アクセス(EA)JDKビルドは時々行われますが、その目的は、エキスパート・ユーザーに早い段階で特定の新機能を試してもらうことに限定されています。 EAビルドの対象は高いスキルを持つユーザーに限定されているため、プロジェクト・リードが一部のルールを緩和する(たとえば、互換性に関連すること)ことや、制約を課す(たとえば、新機能の一部を実装しないでおく)こともできます。たとえば、最初のLoom EAビルドは2019年7月に、2回目のEAビルドはその6か月後に登場しました。この2回目のビルドは、プロジェクト・リードの言葉によれば、「最初のEAビルドのAPIとはかなりかけ離れている」ものでした。これは、早期アクセス機能がいかなる互換性ルールの影響も受けないことを実証しています。この点からも、EAビルドはエキスパート・ユーザーが個々のEAビルドの対象範囲内で新機能を試すためだけに使うべきであることが再確認されます。 EA JDKビルドは、jdk.java.netで公開されています。ダウンロード・ページには、EAビルドの対象範囲が示され、制限や既知の問題が記載されています。さらに、フィードバックを提供するためのメーリング・リストも指定されています。このようなフィードバックや使用報告は、今後行われる、新機能の再構築や改善に役立てられます。機能の安定性と品質が期待されるレベルに達すると、JEP(フィードバックの仕組みがある場合もない場合もあります)などの通常の仕組みを活用できるようになります。その最終的な目的は、Javaプラットフォームの確定版機能になることです。   まとめ JavaプラットフォームやJDK、HotSpotの新機能に設計的な欠陥があれば、重大な影響を生むことになります。そのような欠陥を避けるため、オラクルはプレビュー機能、試験運用版機能、インキュベータ・モジュールという複数の仕組みを使い、開発者が前もって未確定版の機能を使用できるようにしています。 多くのJava開発者の手に渡って本番環境で使用される機能には、品質の高さが求められます。この高品質の実現に貢献しているのが、年2回のリリース周期、Javaコミュニティによるサポート、そして未確定機能のフィードバック・ループなのです。 本記事の執筆に協力いただいたJavaプラットフォーム・グループのAlex Buckley氏、Donald Smith氏、Dalibor Topic氏に感謝いたします。 David Delabassee オラクルのJavaプラットフォーム・グループに所属するデベロッパー・アドボケート。それ以前は、Oracle Serverlessイニシアチブを担当していた。Java EE 8と、Jakarta EEイニシアチブの一環としてのEclipse Foundationへのその移管にも深く関与。 大小のカンファレンスやユーザー・グループでの講演など、世界各地で何年にもわたってJavaの普及に努める。https://delabassee.comのブログに加え、さまざまなメディアで技術記事を多数執筆している。 ベルギー在住。Twitterのフォローは@delabasseeから。

※本記事は、David Delabasseeによる"The role of preview features in Java 14, Java 15, Java 16, and beyond"を翻訳したものです。 プレビュー、試験運用版、インキュベータ版の各機能について、オラクルが新しいJDKの機能性に関するフィードバックを集める仕組み 著者:David Delabassee 2020年6月8日 多くの...

Javaの偉大なアプリ25選

※本記事は、Alexa Moralesによる"The 25 greatest Java apps ever written"を翻訳したものです。 宇宙探査からゲノミクスまで、また、リバース・コンパイラからロボティック・コントローラまで、Javaは現在の世界の中核を担っている。無数のJavaアプリの中から、傑出したいくつかのアプリを紹介する 著者:Alexa Morales 2020年6月5日 Javaの歴史が始まったのは1991年、Sun Microsystemsがコンピュータ・ワークステーション市場における優位性を、当時芽吹きつつあった個人向け電子機器の市場に拡大しようとしたときでした。Sunが作ろうとしていたこのプログラミング言語は、コンピューティングの世界を身近なものにし、世界中のコミュニティを感化することになりました。さらに、言語やランタイム・プラットフォーム、SDK、オープンソース・プロジェクト、そして膨大なツール群で構成される永続的なソフトウェア開発エコシステムのプラットフォームにまでなっています。このようになるとは、当時認識されていませんでした。James Gosling氏らは、数年間をかけてJavaプラットフォームを秘密裏に開発しました。Sunがそれをリリースしたのは1995年のことです。「一度書けば、どこでも動く」という画期的なもので、その方向性は、対話型テレビを動作させるという当初の設計から、急速に普及しつつあったWorld Wide Webでアプリケーションを動作させるというものに変更されました。世紀の変わり目には、スマートカードから宇宙船まで、Javaはあらゆることに使われるようになりました。 現在は、数百万人の開発者がJavaのプログラムを書いています。Javaはこれまでにない速さで進化し続けていますが、Java Magazineでは、プラットフォーム誕生25周年となるこの機会に、Javaがどのように世界を作り上げてきたのかについて振り返ることにしました。 これから紹介するのは、Wikipedia検索から米国国家安全保障局のGhidraまで、これまで書かれてきたJavaアプリの中で、特に独創的で影響力が強い25のアプリのリストです。これらのアプリケーションが扱う範囲は、宇宙探査、ビデオ・ゲーム、機械学習、ゲノミクス、自動車、サイバーセキュリティなど、多種多様です。 順不同で紹介しますが、とてもすべてを網羅できるものではありません。明らかに漏れているものがあると思った方は、ぜひお知らせください。Twitterでハッシュタグ#MovedbyJavaと#Top25JavaAppsを付けて@OracleJavaMagにツイートするか、javamag_us@oracle.comに電子メールをお送りください。 個人的な話になりますが、筆者は2000年にSoftware Development誌の編集長になりました。このころ、サンフランシスコのベイエリアの優秀な新進Java開発者たちが注目を浴びていました。Kathy Sierra氏とBert Bates氏による『Head First Java』の初版をむさぼるように読んだことを覚えています。視覚的な表現が多用され、言語の構文だけでなく、Javaを成功に導いたオブジェクト指向プログラミングの概念をわかりやすく伝えているものでした。当時は、その20年後も自分のキャリアでJavaが重要な位置を占めることになるとは考えていませんでした。現在、筆者はオラクルで働いています。 ここに掲載するリストをまとめるにあたっては、オラクルのJava開発チーム(そのほとんどは、2010年のオラクルによる買収以前からSunで働いていました)に協力していただきました。ここで感謝を申し上げます。Java Magazine元編集長のAndrew Binstock氏と、2006年にSunのソフトウェア部門でコンテンツ・マーケティング・ストラテジストを務めていた同僚のMargaret Harrist氏にも感謝をささげたいと思います。さらに、今回の調査では皆さんのようなJavaコミュニティ・メンバーの多くから意見を頂きました。Jeanne Boyarsky氏、Sharat Chander氏、Aurelio García-Ribeyro氏、Manish Gupta氏、Manish Kapur氏、Stuart Marks氏、Mani Sarkar氏、Venkat Subramaniam氏、Dalibor Topic氏をはじめとする、Javaコミュニティ・メンバーに感謝いたします。 それでは、Javaの偉大なアプリ25選の紹介を始めます。   最後のフロンティア 1.Maestro Mars Roverコントローラ:2004年、Javaは人類が別の惑星に到達することに貢献した最初のプログラミング言語になりました。この年、米国カリフォルニア州パサデナにあるNASAジェット推進研究所(JPL)の科学者たちは、JPLのロボット・インタフェース研究室が構築した、JavaベースのMaestro Science Activity Plannerを使い、火星を探査するマーズ・エクスプロレーション・ローバー「スピリット」を3か月にわたって操作しました。JPLによるJavaの実験は、1995年のマーズ・ソジャーナ向けのコマンドや制御システムの作成により、その何年も前に始まっていました。Javaの生みの親であるJames Gosling氏は、JPLの顧問団の一員として多くの時間を費やしました。 2.JavaFX Deep Space Trajectory Explorer:宇宙旅行を計画している方には、米国の航空宇宙関連企業であるa.i.solutionsのツールが必要になるかもしれません。この会社の製品やエンジニアリング・サービスは、20年超にわたって防衛関連企業や民間宇宙機関で使用されています。 軌道設計者は、この会社のJavaFX Deep Space Trajectory Explorerを使って深宇宙での三体系の経路や軌道を計算することができます。このアプリケーションでは、任意の惑星衛星系や小惑星の多次元表示やモデリングを生成し、精密な視覚探索によって数百万個の点をフィルタリングすることができます。 3.NASA WorldWind:NASAは、ロケット科学者が開発したSDKをオープンソースのWorldWindとしてリリースし、誰でも無料で使用できるようにしました。プログラマーは、WorldWindにより、自分のJavaアプリ、Webアプリ、AndroidアプリにNASAのジオグラフィック・レンダリング・エンジンを追加し、仮想的な地球を表示することができます。WorldWindがGoogle Earthよりはるかに優れているのは、NASAのエンジニアが標高モデルやその他のデータソースを使って地形を視覚化し、地理空間データを生成している点です。Webサイトでは、「世界中の組織がWorldWindを使用して、天候パターンの監視、都市や地形の視覚化、車両の動きの追跡、地理空間データの分析を行っているほか、地球についての知識を私たちに与えています」と説明されています。 4.JMARSとJMoon:2003年に公開され、現在もNASAの科学者たちがよく使っているのがJava Mission-planning and Analysis for Remote Sensing(JMARS)です。JMARSは、アリゾナ州立大学のMars Space Flight Facilityの研究者が作成した地理空間情報システムです。JMARS for the Moon(月科学者にはJMoonと呼ばれています)では、月周回衛星ルナー・リコネサンス・オービタの広角カメラ画像を分析します。2009年に打ち上げられたこの衛星は、月の50キロから200キロ上空を周回しつつ、NASAのPlanetary Data Systemに観測データを送っています。 5.Small Body Mapping Tool(SBMT):ジョンズ・ホプキンズ大学のApplied Physics Laboratoryで開発され、宇宙科学者たちに支持されているのがSBMTです。SBMTでは宇宙機ミッションで収集したデータを使い、小惑星、すい星、小衛星などの不規則な形状の天体を3Dで視覚化します。SBMTはJavaで書かれており、オープンソースのVisualization Toolkit(VTK)を使ってJavaでの3Dグラフィックスを実現しています。ドーン、ロゼッタ、OSIRIS-REx、はやぶさ2の飛行ミッション・チームは、すべてSBMTを使ってすい星や小惑星、準惑星を探査しています。   高度なデータの活用 6.Wikipedia検索:人々のための、人々による百科事典は、オープンソース・ソフトウェアで実行するのが適切です。そして、その検索エンジンにはJavaが使用されています。1999年にDoug Cutting氏が書いたLuceneは、彼の妻のミドル・ネームにちなんで名付けられたものです。LuceneはCutting氏が開発した実に5つ目の検索エンジンでした。その他のエンジンは、Cutting氏がXerox PARC、Apple、Exciteのエンジニアとして開発したものです。2014年、WikipediaではLuceneエンジンを分散型REST対応検索エンジンであるElasticsearchに置き換えました。こちらもJavaで書かれたエンジンです。 7.Hadoop:今回のリストに含まれるCutting氏の作品はLuceneだけではありません。Cutting氏は、商用コンピュータの大規模クラスタによるデータ処理用アルゴリズムMapReduceについて書かれたGoogleの論文に触発され、2003年にMapReduce操作用のオープンソース・フレームワークをJavaで作成しました。このフレームワークは、彼の息子が持っていたおもちゃのゾウにちなんでHadoopと名付けられました。Hadoop 1.0は2006年にリリースされました。これによってビッグ・データのトレンドが起こり、多くの企業が「データ・レイク」を収集し、「データ・エキゾースト(排出データ)」のマイニングを戦略化して、データを「新しい石油」と評するようになりました。2008年、Yahoo(Cutting氏の当時の勤務先)は、10,000コアのLinuxクラスタで動作するSearch Webmapが、現存するもので最大の本番向けHadoopアプリケーションであると述べました。Facebookは2012年に、世界最大のHadoopクラスタに100ペタバイトを超えるデータを保持していると述べました。 8.Parallel Graph AnalytiX(PGX):グラフ分析は、データ間の関係や結びつきを把握するためのものです。ベンチマークによると、PGXは世界最速のグラフ分析エンジンの1つです。PGXはJavaで書かれ、Oracle Labsの研究者Sungpack Hongが率いるチームが2014年に初めて公開しました。PGXにより、グラフ・データをロードし、コミュニティ検出、クラスタリング、経路探索、ページ・ランキング、影響分析、異常検知、経路分析、パターン・マッチングなどの分析アルゴリズムを実行することができます。医療、セキュリティ、小売、金融業界でさまざまな用途に使用されています。 9.H2O.ai:機械学習は、初期に習得しなければならないことが多いため、各分野のエキスパートが機械学習のすばらしいアイデアを実現できないでいる可能性があります。自動機械学習(AutoML)は、特徴エンジニアリング、モデルのトレーニングやチューニング、解釈など、MLプロセスの手順の一部を推論し、サポートしてくれます。Javaベースのオープンソース・プラットフォームであるH2O.aiは、Java ChampionのCliff Click氏が作成しました。その目的は、AIを身近なものにし、初心者に対して仮想データ・サイエンティストの役割を果たすとともに、MLエキスパートの効率を向上させることです。   娯楽の世界 10.Minecraft:ブロックでできたバイオーム(生物群系)や人々に加え、自分がブロックで建てた建物でできた平和なMinecraftの世界は、世界中の子どもや大人を長い間魅了しています。そして、Minecraftは歴史上もっとも人気のあるビデオ・ゲームとなっています。Minecraftは、Markus "Notch" Persson氏がJavaで開発し、2009年にアルファ版がリリースされました。この3D空間では、同じ世界ができあがることは決してないため、終わることのない創造性の源となっています。このビデオ・ゲームはJavaで作られているため、自宅や学校のプログラマーが自分のmodを作ることもできます。 11.JitterロボットとleJOS:Jitterは、自律型ロボット掃除機ルンバが登場する前から存在していました。Jitterとは、国際宇宙ステーション(ISS)で浮遊している粒子を吸い込むために作成されたプロトタイプ・ロボットです。壁で跳ねかえり、ジャイロスコープを使って方向制御を行うことで、無重力状態でも移動できました。ある報告によると、ロシアの宇宙飛行士たちは、このロボットのx、y、z回転の処理が、ISS自体の方向を制御する仕組みと見事によく似ていることに気づいたそうです。Jitterは、もっとも世間離れした、leJOSのプロトタイプです。leJOSは、プログラム可能なロボットをおもちゃのレゴ・ブロックで作成するためのハードウェア・ソフトウェア環境であるLego Mindstorms用のJava仮想マシンとして作られたものです。このおもちゃに使用されたOSの歴史は、José Solorzano氏が開始した1999年のTinyVMプロジェクトまでさかのぼります。その後、Brian Bagnall氏、Jürgen Stuber氏、Paul Andrews氏が率いるチームが手を加え、leJOSとなりました。この環境にはロボット・プログラミングに固有なクラスが含まれ、多様な機能が搭載されています。Javaのオブジェクト指向性によってシンプルにまとまっており、誰でも高度なコントローラや動作アルゴリズムを利用できるようになっています。 12.Javaアプレット:『Oxford English Dictionary』によると、アプレットという言葉が初登場したのは1990年のPC Magazine誌です。しかし、アプレットが実際に使用されるようになったのは、Javaが登場した1995年以降です。Javaアプレットは、Webページ(フレーム、新規ウィンドウ、SunのAppletViewer、テスト・ツール)で起動できた小さなプログラムで、ブラウザとは別のJVMによって実行されました。Minecraftが早い時期に成功を収めたのは、プレーヤーがゲームをダウンロードしてインストールする必要がなく、WebブラウザのJavaアプレットで遊ぶことができたからだという人もいます。JavaアプレットはJava 9で非推奨となり、2018年のJava SE 11で削除されましたが、しばらくの間はもっとも高速なゲームでした。豆知識:Javaアプレットは3Dハードウェア・アクセラレーションにも対応していたため、科学計算の可視化にも広く利用されていました。   栄誉あるコード 13.NetBeansとEclipse IDE:Javaの世界に初めて登場した統合開発環境は、NetBeansでした。NetBeansは、1996年にプラハのカレル大学で開発が始まり(Xelfiという名前でした)、起業家のRoman Staněk氏が同名で設立した会社によって1997年に商用化されました。1999年、SunはJavaアプリケーションのすべての種類をサポートするこのモジュール式IDEを買収し、翌年にオープンソース化しました。2016年、オラクルはNetBeansプロジェクト全体をApache Software Foundationに寄贈しました。 有名なJavaベース統合開発環境には、オープンソースのEclipse IDEもあります。このIDEはJavaだけでなく、AdaからScalaまで、さまざまなプログラミング言語のコーディングに使用できます。Eclipse SDKの開発は、IBMが2001年に始めました。IBM VisualAgeをベースとしてJava開発者向けに作られていますが、プラグインで拡張できます。Eclipse IDEは、2004年にIBMからEclipse Foundationに移管されました。そして現在も、特によく使われているIDEの1つであり続けています。 14.IntelliJ IDEA:IDEにはさまざまなものがありますが、2001年に誕生したIntelliJ IDEAは特に普及しているものの1つです。現在のIntelliJ IDEAは、Python、Ruby、Goなど、多様な言語をサポートする多くのIDEのフレームワークとなっています。IntelliJ IDEAと、関連製品であるJetBrains IDEスイートは、Javaで書かれており、多くの開発者が多用するようになっている、生産性機能やナビゲーション機能が追加されています。追加されているのは、コードの索引付け、リファクタリング、コード補完(スマートフォンのテキスト自動補完の前身となった機能)、エラーを検出する動的分析(スペルチェックに似た機能)などです。イギリスのJava Championで、フリーランスのソフトウェアおよびデータ・エンジニアであるMani Sarkar氏は、「IntelliJ IDEAは、JavaやJVMベースの複雑なアプリケーションの管理やデバッグを1つのフレームワークで行うという難題を解決してくれました。このツールを使う開発者は、効果的であり生産的であり、そして何より幸せであると感じています」と話しています。 15.Byte Buddy:オープンソースJavaライブラリであるByte Buddyを作成した、ノルウェーのオスロを拠点とするソフトウェア・エンジニアRafael Winterhalter氏は、非常にニッチな分野に人生をささげていることを認めています。ときには、尋常でないほど細かい部分に焦点を当てることもあります。それでも、その献身の成果は広く普及しています。Byte Buddyはランタイム・コード生成および操作用のライブラリで、HibernateやMockitoといったJavaツールに使用されています。Winterhalter氏によれば、月間2,000万回ダウンロードされているそうです。 16.Jenkins:Sun Microsystemsのエンジニアだった川口耕介氏が2004年に作成したJenkinsは、強力なオープンソース継続的インテグレーション・サーバーです。JenkinsはJavaで書かれています。短時間で自動的にアプリケーションのビルド、テスト、デプロイを行うことができ、一般的に、「Infrastructure as Code」を実現した初期のDevOpsツールの1つとして認識されています。Jenkinsと、コミュニティによる1,500超のプラグインは、GitHub連携から、色覚障害の開発者のサポートやMySQL Connector JARファイルの提供まで、無数のデプロイやテストのタスクに対処しています。 17.GraalVM:Oracle LabsのThomas Wuerthingerが率いるチューリッヒ(スイス)の研究者チームは、Javaでコンパイラを書いたらどうなるか(オリジナルのJVMはCで書かれています)、そのコンパイラがどんな言語で書かれたどんなプログラムでも実行できたらどうなるか、そのコンパイラが非常に効率的だったらどうなるか、という3つのアイデアを数年間かけて研ぎ澄ましてきました。そして、60本の研究論文を発表した後、GraalVMは困難な状況を覆して商用プロダクトとなりました。このテクノロジーを熱烈に支持している企業の1つがTwitterで、GraalVMを使って自社サービスのスピードと計算効率を改善しています。 18.Micronaut:クラウド向けのコードを書いている開発者は、アプリケーションでどのくらいのメモリがどのように使用されるかについて慎重に考える必要があります。マイクロサービス用のJavaフレームワークであるMicronautを作成したGraeme Rocher氏は、「アプリの再起動やフェイルオーバー、シャットダウンと復帰にかかる時間を非常に短くし、起動時間やメモリの使用を最適化する必要がある」と述べています。Micronautでは、アノテーション・メタデータを使って、JVMがアプリのバイトコードを効率的にコンパイルできるようにしています。 19.WebLogic Tengah:1997年、WebLogic Tengahは、実際に実装された初めてのエンタープライズJavaサーバーとなりました。Java MagazineとDr. Dobb's Journal誌の元編集長であるAndrew Binstock氏は、「WebLogic Tengahは、Java 2 Enterprise Editionに先駆けてリリースされBEAの主要製品となり、最終的にはオラクルによるBEA Systems買収につながった」と話しています。同じころ、IBMはビジネス・オブジェクト・フレームワークのSan Francisco Projectで成功していました。これについてBinstock氏は、「このフレームワークによってJavaは、クールな若者たちが遊ぶおもしろそうな新しいものの段階をまさに脱し、重要なビジネス・ツールへと進化した」と述べています。Oracle WebLogic Serverは、現在も主要なJavaアプリケーション・サーバーであり続けています。一方で、他の製品も普及しています。Sunが2005年に開発を始めたオープンソース・アプリケーション・サーバーGlassFishは、2018年にEclipse Foundationに寄贈されました。 20.Eclipse Collections:投資銀行や株式市場などの金融サービス企業で働く、多くの高賃金開発者にJavaの強力なスキルが求められるのには理由があります。Javaプログラミング言語は、並列実行処理に加え、高頻度取引やその他の大規模金融取引でよく使われる、複数の実行スレッドの管理にたけているからです。当初はGoldman Sachs Collectionsとして知られ、後にEclipse Foundationに寄贈されて改名されたEclipse Collectionsは、Java ChampionのMani Sarkar氏によると、「最適化されたデータ構造と、高度で機能的な流れるようなAPI」でネイティブJavaの高パフォーマンス機能を拡張したものです。Sarkar氏は、Eclipse Collectionsにはキャッシュ処理、プリミティブのサポート、並列実行ライブラリ、一般的なアノテーション、文字列処理、入出力などが含まれていると話しています。 21.NSA Ghidra:米国国家安全保障局は、2019年にサンフランシスコで開催されたRSA Conferenceで、Ghidraと呼ばれるJavaベースのオープンソース・ツールを紹介しました。現在、セキュリティの研究者や実務家が、マルウェアの動作を理解するためや、自分たちのコードの弱点を調査するために、このツールを使っています。このリバースエンジニアリング・プラットフォームでは、ソフトウェアを機械語からソース・コード(Javaなどの言語)に逆コンパイルすることができます。このツールには、2017年3月にその存在がWikiLeaksによって暴露されたという、忌まわしいとまでは言えないにしても、よく知られた歴史があります。   ゲノム・マッピング 22.Integrated Genome Browser:ヒトゲノムのマッピングという競争は、1990年に始まり、その13年後に終わりを迎えました。医学研究者ら3,000人が30億米ドルの費用をかけて10年間作業し、生物工学研究者であるCraig Venter氏のDNA塩基対30億個の配列決定に成功したのです。配列決定が完了すると、科学者たちは、私たちの種のソース・コードを読み解く作業に入ることを切望しましたが、どのようにすればよいのでしょうか。この作業は、Javaベースのゲノム・ブラウザを使って行われました。このブラウザは、バイオインフォマティクスを専門とするAnn Loraine教授らのチームが開発した視覚化ツールで、基礎データセットと、参照遺伝子の注釈の両方を調べるものです。オープンソースのIntegrated Genome Browserにより、ゲノム・データのズームやパン、グラフ化を行い、遺伝子の機能を特定して注釈を付けることができます。カリフォルニア大学サンタクルーズ校では、この世界的な取り組みと歩調を合わせ、Jim Kent氏が主導してGenome Browserという同様のツールを作成しています。 23.BioJava:2000年に始まり、現在も順調に作業が進んでいるのがBioJavaです。このオープンソース・ライブラリは、生物学データの処理を行うもので、バイオインフォマティクスと呼ばれる分野に属します。科学者はこのライブラリを使用して、タンパク質およびヌクレオチドの配列を操作することや、遺伝子からタンパク質への翻訳、ゲノミクス、系統進化、高分子構造に関するデータを調査することができます。このプロジェクトをサポートしているのはOpen Bioinformatics Foundationです。世界中に所在するその貢献者には、製薬、医療、ゲノミクス分野のさまざまな関係組織から資金が提供されています。Aleix Lafita氏らのチームは、「Javaで利用できるツールとJavaのクロス・プラットフォーム移植性のおかげで、BioJavaは手法やソフトウェアの開発によく採用されている」と記しています(2019年の論文"BioJava 5:A community-driven open source bioinformatics library")。この論文によると、BioJavaは2009年以来、65人の開発者による貢献を受け、GitHubで224個のフォークができて270個のスターを獲得しました。また、BioJavaは2018年の1年間で19,000回超ダウンロードされました。   お気に入りの「モノ」 24.VisibleTesla:このJavaベースのアプリは、電気自動車メーカーTeslaのファンであるJoe Pasqua氏が、所有するTesla Model Sの状態を監視して操作するために2013年に作成した無償のプログラムです。VisibleTeslaはTesla Motors Clubコミュニティから着想を得ており、Teslaの公式モバイルアプリと同様の機能が提供されています。ユーザーは、ジオフェンシングの設定に加え、ドアのロック忘れや充電状態などの通知設定を行うことができます。また、走行データの収集や操作も可能です。このプロジェクトはオープンソースで、コードはGitHubで公開されています。 25.SmartThings:このInternet of Things(IoT)アプリケーションは、2012年にAlex Hawkinson氏が共同設立し、後にKickstarterで120万米ドルを調達したSmartThingsが開発したものです。このアプリケーションにより、自宅の照明や鍵、コーヒー・メーカー、温度調節器、ラジオからホーム・セキュリティ・システムまで、あらゆるものをスマートフォンやタブレットから制御して自動化することができます。JavaベースのMicronautフレームワーク(#18参照)を使っているため、クラウドベースのサービスはミリ秒未満のスピードで動作します。この会社は、2014年に2億米ドルもの金額でSamsung Electronicsに買収されました。 Alexa Morales オラクルの開発者コンテンツ担当ディレクター。Software Development誌の元編集長。15年超にわたり、テクノロジー・コンテンツ・ストラテジストおよびジャーナリストとして活動している。 Alexa Morales オラクルの開発者コンテンツ担当ディレクター。Software Development誌の元編集長。15年超にわたり、テクノロジー・コンテンツ・ストラテジストおよびジャーナリストとして活動している。

※本記事は、Alexa Moralesによる"The 25 greatest Java apps ever written"を翻訳したものです。 宇宙探査からゲノミクスまで、また、リバース・コンパイラからロボティック・コントローラまで、Javaは現在の世界の中核を担っている。無数のJavaアプリの中から、傑出したいくつかのアプリを紹介する 著者:Alexa Morales 2020年6月5日 Javaの...

Optionalクラスを意図されたとおりに使うための12のレシピ

※本記事は、Mohamed Tamanによる"12 recipes for using the Optional class as it’s meant to be used"を翻訳したものです。 美しくないnullポインタ例外からアプリケーションを守る12のベスト・プラクティスに従い、コードを読みやすく簡潔にする 著者:Mohamed Taman 2020年6月22日 真剣なJava開発者やアーキテクトなら、誰でも迷惑なNullPointerException例外について聞いたことや、体験したことがあります。 これに対して、何ができるでしょうか。プログラマーがメソッドから値を返すとき、値がないことを示すためにnull参照を使うことはよく行われます。しかしこれは、さまざまな重大な問題の原因になります。 このnull参照問題について詳しく知りたい方は、Raoul-Gabriel Urma氏の記事「Tired of Null Pointer Exceptions?Consider Using Java SE 8's 'Optional'!」を読んでみるとよいでしょう。この問題について詳細に説明され、Optionalクラスが紹介されています。 本記事では、Urma氏の記事を踏まえて、Optionalの使い方と、これをどう使うべきかについて確認します。筆者は、開発者のコードをレビューしていたときの経験や実践を通じて、開発者が日々のコーディングでOptionalクラスを使っていると認識しました。そこから、本記事で紹介する、読者のスキル向上とアンチパターン回避に役立つ12のベスト・プラクティスが生まれました。 本記事と、まもなくJava Magazineに掲載される続編の記事では、Java 14までにリリースされている、Optionalクラスのすべてのメソッドを確認します。また、Java 15の完成も近づいているため、Java 15についても触れます。   Optionalクラスの由来 Javaエンジニアはnull問題に長い間取り組んできました。そして、Java 7でこの問題に対処するソリューションを導入しようとしましたが、そのリリースには追加されませんでした。ここでは、言語の設計者がStream APIについてどう考えたのかを振り返る、というよりも想像してみることにします。count()やsum()などのいくつかのメソッドには、値が存在しない場合の必然的な戻りタイプ(0など)が存在します。このゼロは合理的です。 しかし、findFirst()メソッドやfindAny()メソッドはどうでしょう。入力がない場合にnull値を返すのは、合理的ではありません。そういったメソッドへの入力が存在すること(または存在しないこと)を示す、値の型があるべきです。 そこでJava 8では、Optional<T>という新たな型が追加されました。この型は、T型の値が存在するどうかを表します。Optionalは、ストリーム(またはOptionalを返すメソッド)と組み合わせて、Fluent APIを構築する場合の戻りタイプとして使うことを意図したものでした。さらに、開発者がnull参照を適切に扱えるようにするという目的もありました。 ところで、Java SE 11ドキュメントには、Optionalについて次のような記述があります。「Optionalは主に、『結果がない』ことを示す明確な必要性があり、nullを使うことでエラーが起こりやすくなる場合にメソッドの戻りタイプとして使うことを意図しています。Optional型の変数は、決してnullにはならずに、常にOptionalインスタンスを指す必要があります」 まもなくコード・レシピで紹介しますが、筆者流の定義では、「Optionalクラスは、存在しない可能性のある値のコンテナ型である」となります。 また、いくつかの特殊ケースや誘惑は、わなに相当する場合があります。このわなに引っかかった場合、コードの質が低下することや、さらには予期しない動作の原因となることがあります。本シリーズ記事では、それらの点について説明していきます。 「コードはどこだろう」と思う方もいらっしゃるかもしれませんから、本題に入ります。筆者のアプローチは、Optionalクラスのすべての使用場面を分類し、それぞれの分類に関連して開発者がよく抱く質問に答えるというものです。本記事では、次の3つの大分類について、12のレシピを使って説明します。 Optionalを使っているのにnullになるのはなぜですか 値がない場合、何を返せば(または、何を設定すれば)よいですか どうすればOptional値を効率的に使用できますか 次回の記事では、次の2つの分類について説明します。 どうすればOptionalのアンチパターンを回避できますか Optionalは好きですが、どうすればもっとプロらしくできますか   Optionalを使っているのにnullになるのはなぜですか 通常、この質問はOptionalクラスの作成と、データの取得方法に関連します。 レシピ1:オプショナル変数にnullを代入しない。開発者がデータベースを使用して従業員を検索する場合に、Optional<Employee>を返すメソッドを設計することがあります。しかし、データベースから結果が返されない場合に、依然としてnullを返している開発者を見かけたことがあります。次の例をご覧ください。 1 public Optional<Employee> getEmployee(int id) { 2    // 従業員を検索  3    Optional<Employee> employee = null; // 従業員が存在しない場合 4    return employee;  5 } 上記のコードは正しくないため、徹底的に避けるべきです。このコードを修正するためには、3行目を次の行で置き換えます。修正後の行では、空のOptionalでOptionalを初期化しています。 Optional<Employee> employee = Optional.empty(); Optionalは値を保持できるコンテナであるため、nullで初期化するのは意味がありません。 APIメモ:empty()メソッドは、Java 8以降に存在します。 レシピ2:直接get()を呼ばない。次のコードについて考えてみます。何がおかしいでしょうか。 Optional<Employee> employee = HRService.getEmployee(); Employee myEmployee = employee.get(); "employee" Optionalは空になる可能性があるため、get()を直接呼び出すとjava.util.NoSuchElementExceptionがスローされると思ったでしょうか。そう思ったなら、正解です。get()を呼び出しても大丈夫と思った方は、間違いです。必ず最初にisPresent()メソッドを使って値の存在をチェックする必要があります。たとえば、次のようにします。 if (employee.isPresent()) {     Employee myEmployee = employee.get();     ... // "myEmployee"を使って何かを行う } else {     ... // employee.get()を呼ばないで何かを行う } 上記のコードは定型挿入文であるため、望ましくないという点には注意してください。次は、isPresent()/get()ペアを呼び出す代案として、もっと美しいさまざまな方法を紹介します。 APIメモ:isPresent()メソッドとget()メソッドは、Java 8以降に存在します。 レシピ3:Optionalでnull参照を取得するときは、nullを直接使わない。場合によっては、null参照を保持しなければならないこともあります。しかし、Optionalでは直接nullを使わないでください。代わりに、orElse(null)を使います。 次の例について考えてみてください。Reflection APIのMethodクラスに対して、invoke()メソッドを呼び出しています。これは、実行時にメソッドを呼び出す方法です。最初の引数には、呼び出されるメソッドがstaticであればnullを、そうでない場合はメソッドを含むクラス・インスタンスを渡しています。 1 public void callDynamicMethod(MyClass clazz, String methodName) throws ... { 2 Optional<MyClass> myClass = clazz.getInstance(); 3 Method = MyClass.class.getDeclaredMethod(methodName, String.class); 4 if (myClass.isPresent()) { 5 method.invoke(myClass.get(), "Test"); 6 } else { 7 method.invoke(null, "Test"); 8 } 9 } 通常はorElse(null)を使うべきではありません。ただし、このような場合は、上記のコードよりもorElse(null)を使う方が望ましいと言えます。4行目から8行目は、次に示す簡潔な1行のコードで置き換えることができます。 4 method.invoke(myClass.orElse(null), "Test"); APIメモ:orElse()メソッドは、Java 8以降に存在します。   値がない場合、何を返せば(または、何を設定すれば)よいですか 先ほどのセクションでは、Optionalを使っていても発生するnull参照問題を避ける方法について取り上げました。次は、Optionalを使ってデータを設定してターンする別の方法について見てみます。 レシピ4:値の設定とターンには、isPresent()とget()のペアを使わない。次のコードについて考えてみます。もっと美しく効果的にするために、どこを変更できるでしょうか。 public static final String DEFAULT_STATUS = "Unknown"; ... public String getEmployeeStatus(long id) { Optional<String> empStatus = ... ; if (empStatus.isPresent()) { return empStatus.get(); } else { return DEFAULT_STATUS; } } レシピ3と同様に、次のようにしてisPresent()とget()のペアをorElse()に置き換えます。 public String getEmployeeStatus(long id) { Optional<String> empStatus = ... ; return empStatus.orElse(DEFAULT_STATUS); } ここで考慮すべき非常に重要な点は、パフォーマンスのペナルティが発生する可能性があることです。orElse()が返す値は、オプショナル値が存在するかどうかによらず、必ず評価されます。そのため、ここでのルールは、すでに構築した値があり、高価な演算値を使っていない場合に、orElse()を使うということになります。 APIメモ:orElse()メソッドは、Java 8以降に存在します。 レシピ5:演算値を返す場合にorElse()を使わない。レシピ4で説明したように、パフォーマンスのペナルティがあるため、演算値を返す場合はorElse()を使わないようにしてください。次のコード・スニペットについて考えてみます。 Optional<Employee> getFromCache(int id) {     System.out.println("search in cache with Id: " + id);     // キャッシュから値を取得 } Optional<Employee> getFromDB(int id) {     System.out.println("search in Database with Id: " + id);         // データベースから値を取得 } public Employee findEmployee(int id) {             return getFromCache(id)             .orElse(getFromDB(id)                     .orElseThrow(() -> new NotFoundException("Employee not found with id" + id)));} まず、このコードでは、指定されたIDの従業員をキャッシュから取得しようとしています。その従業員がキャッシュに存在しない場合は、データベースから取得しようとします。次に、目的の従業員がキャッシュにもデータベースにも存在しない場合、NotFoundExceptionがスローされます。このコードを実行した場合、従業員がキャッシュに存在するときは、次のように出力されます。 Search in cache with Id: 1 Search in Database with Id: 1 従業員はキャッシュから返されるにもかかわらず、データベースの問合せが呼び出されています。これはとても高価です。そこで、orElseGet(Supplier<? extends T> supplier)を使います。このメソッドはorElse()に似ていますが、1つ違いがあります。Optionalが空だった場合、orElse()では直接デフォルト値が返されますが、orElseGet()にはOptionalが空だった場合にのみ呼び出されるSupplier関数を渡すことができます。これはパフォーマンス改善に役立ちます。 それでは、従業員がキャッシュに存在するという同じ前提のもと、orElseGet()を使って次のように変更したコードを再実行することを考えてみます。 public Employee findEmployee(int id) { return getFromCache(id) .orElseGet(() -> getFromDB(id) .orElseThrow(() -> { return new NotFoundException("Employee not found with id" + id); })); } 今度は、望みどおりの結果が得られ、パフォーマンスが改善されるでしょう。コードからは、次の内容のみが出力されます。 Search in cache with Id: 1 なお、isPresent()とget()のペアを使おうとは思わないでください。この方法は美しくないからです。 APIメモ:orElseGet()メソッドは、Java 8以降に存在します。 レシピ6:値が存在しないときは例外をスローする。値が存在しないことを示すために例外をスローしたい場合もあります。通常は、データベースなどのリソースと連携するサービスを開発する場合にこのようなことがあります。Optionalを使うことで、これが簡単に実現します。次の例について考えてみます。 public Employee findEmployee(int id) { var employee = p.getFromDB(id); if(employee.isPresent()) return employee.get(); else throw new NoSuchElementException(); } 次のようにすれば、上記のようなコードは書かずに、タスクを美しく表現することができます。 public Employee findEmployee(int id) { return getFromDB(id).orElseThrow(); } 上記のコードでは、コール元のメソッドにjava.util.NoSuchElementExceptionをスローします。 APIメモ:orElseThrow()メソッドは、Java 10以降に存在します。まだJava 8または9をお使いの方は、レシピ7を検討してください。 レシピ7:値が存在しない場合、どうすれば明示的な例外をスローできるか。レシピ6の方法では、明示的でない例外の一種であるNoSuchElementExceptionをスローすることしかできませんでした。状況をよりよく説明する、さらに適切な情報とともに問題をクライアントに報告するためには、このような例外では不十分です。 レシピ5では、orElseThrow(Supplier<? extends X> exceptionSupplier)メソッドを使いました。このメソッドは、isPresent()とget()のペアに代わる美しい方法です。したがって、次のコードは避けるように努めてください。 @GetMapping("/employees/{id}") public Employee getEmployee(@PathVariable("id") String id) { Optional<Employee> foundEmployee = HrRepository.findByEmployeeId(id); if(foundEmployee.isPresent()) return foundEmployee.get(); else throw new NotFoundException("Employee not found with id " + id); } orElseThrow()メソッドは、Optionalに値が存在しない場合に、渡された明示的な例外をスローするだけのものです。そこで、次のようにして先ほどのメソッドを美しく書き換えてみます。 @GetMapping("/employees/{id}") public Employee getEmployee(@PathVariable("id") String id) { return HrRepository .findByEmployeeId(id) .orElseThrow( () -> new NotFoundException("Employee not found with id " + id)); } さらに、空の例外をスローするだけでよければ、次のようにすることもできます。 return status.orElseThrow(NotFoundException::new);   APIメモ:orElseThrow()メソッドにnullを渡すと、値が存在しない場合にNullPointerExceptionがスローされます。orElseThrow(Supplier<? extends X> exceptionSupplier)メソッドは、Java 8以降に存在します。   どうすればOptional値を効率的に使用できますか レシピ8:Optional値が存在する場合にのみアクションを実行したい場合は、isPresent()-get()を使わない。Optional値が存在する場合にのみアクションを実行し、そうでない場合は何もしたくないことがあります。そんなときは、ifPresent(Consumer<? super T> action)メソッドの出番です。このメソッドには、引数としてコンシューマ・アクションを渡します。ちなみに、次のコードは避けてください。 1 Optional<String> confName = Optional.of("CodeOne"); 2 if(confName.isPresent()) 3 System.out.println(confName.get().length()); ifPresent()を使っている点はまったく問題ありません。2行目と3行目を次の1行と置き換えます。 confName.ifPresent( s -> System.out.println(s.length())); APIメモ:ifPresent()メソッドは何も返しません。このメソッドは、Java 8以降に存在します。 レシピ9:値が存在しない場合に引数なしのアクションを実行するために、isPresent()/get()は使わない。開発者は、Optional値が存在する場合に何かを行う一方、そうでない場合は引数のないアクションを実行するコードを書くことがあります。たとえば、次のようなものです。 1 Optional<Employee> employee = ... ; 2 if(employee.isPresent()) { 3 log.debug("Found Employee: {}" , employee.get().getName()); 4 } else { 5 log.error("Employee not found"); 6 } ifPresentOrElse()はifPresent()に似ていますが、唯一の違いとして、else分岐にも対応していることに注意してください。そのため、2行目から6行目を以下で置き換えることができます。 employee.ifPresentOrElse( emp -> log.debug("Found Employee: {}",emp.getName()), () -> log.error("Employee not found")); APIメモ:ifPresentOrElse()メソッドは、Java 9以降に存在します。 レシピ10:値が存在しない場合は、別のOptionalを返す。Optionalの値が存在する場合は値を記述するOptionalを返し、存在しない場合はサプライヤ関数が生成するOptionalを返す場合もあります。次のコードは避けてください。 Optional<String> defaultJobStatus = Optional.of("Not started yet."); public Optional<String> fetchJobStatus(int jobId) { Optional<String> foundStatus = ... ; // fetch declared job status by id if (foundStatus.isPresent()) return foundStatus; else return defaultJobStatus; } これを実現するためにorElse()メソッドやorElseGet()メソッドを使い過ぎないでください。この2つのメソッドは、いずれもラップされていない値を返すからです。そのため、次のようなコードも避けてください。 public Optional<String> fetchJobStatus(int jobId) {     Optional<String> foundStatus = ... ; // 宣言されたジョブのステータスをIDによりフェッチ     return foundStatus.orElseGet(() -> Optional.<String>of("Not started yet.")); } 美しく完璧なソリューションは、or (Supplier<? extends Optional<? extends T>> supplier)メソッドを使うものです。次のコードをご覧ください。 1 public Optional<String> fetchJobStatus(int jobId) { 2    Optional<String> foundStatus = ... ; // 宣言されたジョブのステータスをIDによりフェッチ 3    return foundStatus.or(() -> defaultJobStatus); 4 } 最初にオプショナル型defaultJobStatusを定義せず、3行目のコードを次のコードで置き換えることもできます。 return foundStatus.or(() -> Optional.of("Not started yet.")); APIメモ:サプライヤ関数がnullであるか、またはnullの結果を生成する場合、or()はNullPointerExceptionをスローします。このメソッドは、Java 9以降に存在します。 レシピ11:Optionalのステータスは、空かどうかにかかわらず取得する。Java 11以降では、isEmpty()メソッドを使って、Optionalが空かどうかを直接確認できます。このメソッドは、Optionalが空であればtrueを返します。そのため、次のコードは記述しないでください。 1 public boolean isMovieListEmpty(int id){ 2 Optional<MovieList> movieList = ... ; 3 return !movieList.isPresent(); 4 } 3行目を次の行で置き換えて、コードの可読性を高めることができます。 return movieList.isEmpty(); APIメモ:isEmpty()メソッドは、Java 11以降に存在します。 レシピ12:Optionalを使い過ぎない。開発者には、お気に入りの機能を使い過ぎる傾向が見られる場合もあります。Optionalクラスもその1つです。値を取得するという目的だけでメソッド・チェーンを作成し、至るところでOptionalを使おうとする開発者もいます。しかしこれは、わかりやすさ、メモリ・フットプリント、単純さを顧みない方法です。そのため、次のコードは避けてください。 1 public String fetchJobStatus(int jobId) { 2 String status = ... ; // fetch declared job status by id 3 return Optional.ofNullable(status).orElse("Not started yet."); 4 } わかりやすい次のコードで3行目を置き換えて、単純にしてください。 return status == null ? "Not started yet." : status;   まとめ 他のJava言語機能と同じく、Optionalにも正しい使い方と誤った使い方があります。Optionalクラスの最適な使い方を知るためには、本記事で掘り下げた内容を理解し、紹介したレシピをすぐに使えるようにして、ツールキットを強化することが必要です。 筆者は、19世紀の米国の作家であるOliver Wendell Holmes, Sr.の言葉、「若者はルールを知る。しかし老人は例外を知る」がお気に入りです。 ここでは、Optionalクラスの表面をなぞっただけにすぎません。次回の記事では、筆者が開発者のコード(筆者自身のコードも含みます)で気づいた、Optionalのアンチパターンに迫ってみたいと思います。その後、Stream APIや変換などを扱うさらに高度なレシピを紹介し、Optionalクラスに含まれる他のメソッドの使い方について解説したいと思います。 詳しく学びたい方は、Java SE 15 APIのOptionalに関するドキュメントや、Raoul-Gabriel Urma氏、Mario Fusco氏、Alan Mycroft氏による『Java 8 in Action』(Manning、2014年)をご覧ください。 Mohamed Taman Mohamed Taman(@_tamanm):Sirius-XI InnovationsのCEO/オーナー、およびDevTech Systemsのシニア・ソリューション・アーキテクト。セルビアのベオグラードを拠点とするJava Champion、Oracle Groundbreakerで、Jakarta EEのAdopt-a-SpecプログラムとOpenJDKのAdopt-a-JSRのメンバーを務める。

※本記事は、Mohamed Tamanによる"12 recipes for using the Optional class as it’s meant to be used"を翻訳したものです。 美しくないnullポインタ例外からアプリケーションを守る12のベスト・プラクティスに従い、コードを読みやすく簡潔にする 著者:Mohamed Taman 2020年6月22日 真剣なJava開発者やアーキテクト...

Jakarta EEで始める並列プログラミング

※本記事は、Josh Juneauによる"Get started with concurrency in Jakarta EE"を翻訳したものです。 優れたアプリケーションの要の1つである良好なパフォーマンスというのは、複数のタスクをパラレルに同時実行できることを指す場合が多い 著者:Josh Juneau 2020年6月22日 Jakarta EEプラットフォームでアプリケーションやサービスを開発するとき、重要なツールとなるのがJakarta Concurrency APIです。このAPIによって、ユーザーにシームレスな操作を提供するために使用できるさまざまな戦略が実現します。さらに、Java SEのConcurrency API(java.util.concurrent)に大変よく似ているため、これら2つのAPIでの相互移行は容易です。本記事では、Jakarta EEプラットフォームで適格に動作する堅牢なアプリケーションを開発するために使用できる多くの戦略について説明します。 最初に、並列実行の中核的な概念であるスレッド処理について、簡単に確認しておきます。スレッドは、アプリケーションの処理の実行をカプセル化したものです。JVMでは、複数のスレッドを同時に処理できます。スレッドには優先順位があり、優先順位の高いスレッドは優先順位の低いスレッドよりも先に実行可能となっています。 このようなスレッド処理は、Java SEアプリケーションの開発では非常に良好に機能します。しかし残念ながら、サーバー環境で複数のスレッドを無制限に生成するというのは、受け入れられないソリューションです。環境に提供されているすべてのメモリを1つのスレッドが消費してしまう可能性もあるからです。さらに、コンテナでは同時に複数のアプリケーションを実行できます。複数のスレッドが実行された場合、コンテナ内の他プロセスのリソースが枯渇する可能性も高まるでしょう。Jakarta EEサーバー環境では、タスクの実行を管理する多くのサービスを提供しています。そのため、Java SE環境で複数のスレッドを生成するのと同じようにして、タスクをそれらのサービスに渡すことができます。 なお、本記事のすべてのコードは、GitHubで公開されています。   並列プログラミングを使ってみる 完全なJakarta EEプラットフォームには、Jakarta Concurrency APIのサポートが含まれているため、このAPIを使ってみるのは簡単です。Jakarta EEに完全対応しているアプリケーション・サーバーやコンテナには、必ずこのAPIが含まれています。Jakarta EE Web Profileや、Jakarta EEプラットフォームの一部のみを使っている場合、MavenプロジェクトのPOMファイルに以下を追加するだけで必要な依存性を取得することができます。 <dependency> <groupId>jakarta.enterprise.concurrent</groupId> <artifactId>jakarta.enterprise.concurrent-api</artifactId> <version>2.0.0-RC1</version> </dependency> コンテナは、Jakarta Concurrency APIを使ううえで重要な役割を果たします。コンテナには、バックグラウンドでタスクを実行するために使うサービスが含まれているからです。これは、Java SEのデスクトップ・ソリューションで、長時間実行されるタスクをバックグラウンド・スレッドで実行することに似ています。つまり、対応しているJakarta EEコンテナには、このAPIを使えるサービスがデフォルトで存在するということです。ただし、このサービスをカスタマイズすることや、さまざまなアプリケーションで使用するために複数のサービスを作成することも可能です。 注:Jakarta Concurrency 1.1を使っている場合、パッケージのネーミング規則はjavax.enterprise.concurrent.*です。一方、リリース2.0.0以降を使っている場合、パッケージの名前はjakarta.concurrent.*に変更されています。本記事では、Jakarta Concurrency 1.1を含むJakarta EE 8を使用します。   コンテナを構成する 対応するコンテナには、Jakartaの並列プログラミングに使用するための、以下に示すサービスが含まれています。 ManagedExecutorService:Java SE Concurrency APIの一部であるExecutorServiceを拡張したサービスです。タスクをサーバーに送信し、非同期式に処理するために使います。このサービスがタスクの送信を終えると、コール元にFutureオブジェクトが返ります。 ManagedScheduledExecutorService:Java SE Concurrency APIの一部であるScheduledExecutorServiceを拡張したサービスです。遅延タスクや定期タスクを送信して処理するために使います。このサービスがタスクの送信を終えると、コール元にFutureオブジェクトが返ります。 ManagedThreadFactory:Java SE Concurrency APIの一部であるThreadFactoryを拡張したサービスです。このサービスの主な目的は、タスクをサーバーに渡して処理できるように、アプリケーション用のマネージド・スレッドを生成することです。Java SEのスレッド管理と非常によく似た形で機能します。 ContextService:このサービスでは、コンテキストを意識したダイナミック・プロキシ・オブジェクトを生成する方法を提供します。本記事では扱いません。 これらのサービスに送信されたタスクは、コール元と同じアプリケーション・コンテキスト内で実行されます。そのため、タスクはコール元のアプリケーションと同じリソースにアクセスすることができ、簡単に状態管理やデータ共有などを行うことができます。 対応するすべてのJakarta EEコンテナでは、事前構成されたリソースを利用できますが、カスタム・リソースを作成することもできます。たとえば、必要とするそれぞれのアプリケーションやサービス用にカスタムのManagedExecutorServiceを生成するという方法も妥当かもしれません。マイクロサービス環境では、1つのコンテナに1つのサービスのみがデプロイされるのが一般的です。その場合は、デフォルトのリソースで十分でしょう。 対応するすべてのアプリケーション・サーバー・コンテナでは、カスタムのマネージド・リソースを作成する手段が提供されているはずだからです。また、複数の手段を提供しているものも多くあります。たとえば、Payaraサーバーを使う場合、管理コンソールやCLIからいずれかのリソースを作成できます。CLIからManagedExecutorServiceを作成する場合は、asadminユーティリティを使って次のコマンドを発行します。 bin/asadmin create-managed-executor-service concurrent/name-of-service   Jakartaの並列実行のユースケース 並列実行にはさまざまなユースケースがあります。たとえば、複数のアクションを非同期式に実行できるサーバー側タスクの作成です。また、リモート・サーバー上にある大規模なデータセットなどの高価なリソースを取得する場合に、シームレスなフロントエンド・エクスペリエンスを提供するというものもあります。 以降のセクションでは、特に一般的なユースケースのいくつかについて、コンテナ・リソースを使用する方法を示す例を交えながら説明します。本記事の例では、スポーツ・チームの選手リストを管理するアプリケーションを構築します。一元管理されているRDBMSをデータ・ストレージとして使い、選手リストのデータを表示するレポートを提供します。ユーザー・インタフェースはJakarta Server Facesとステートレス・ビューを使って開発します。一方、データベースを呼び出すバックエンド・ロジックは一連のサービスであり、それぞれのサービスが別々のJakarta EEアプリケーションです。なお、注意すべき重要な点は、並列実行するリソースをXMLまたはアノテーションで構成できることです。本記事では、アノテーションを使用します。   ManagedExecutorService Jakarta Concurrency APIで特によく使われるサービスが、ManagedExecutorServiceです。クラスでこのサービスを使う場合、RunnableおよびCallableという2種類のインタフェースを実装できます。Runnableインタフェースは、処理するタスクをManagedExecutorServiceに送信する際に使用できます。その結果として返されるFutureを使用して、ジョブが完了したタイミングを判定することができます。Callableの実装も同じように使いますが、こちらでは結果を返すことができます。 次の例では、アプリケーション・ユーザー・インタフェース内のボタンを使ってレポートを呼び出しています。その際に、レポートを実行するバックグラウンド・プロセスが生成されます。そのプロセスが完了すると、結果がユーザーに返されます。この例では、Runnableを実装するクラスを生成しています。このクラスでは、データベースに問合せを行って結果を取得するWebサービスを呼び出すために使うロジックをカプセル化しています。リスト1のコードはReportRunnableクラスです。このクラスではRunnableを実装しているため、run()メソッドの実装を含んでいます。このメソッドで、長い時間がかかるタスクを実行します。 リスト1: public class ReportRunnable implements Runnable {     private static Logger log = LogManager.getLogger();     private WebTarget resource;     private String reportName;     private List<Roster> rosterList;     public ReportRunnable(String reportName) {         this.reportName = reportName;     }     /**      * このメソッドは、名前付きレポートを実行するためにオーバーライドされている。      */     @Override     public void run() {         if ("RosterReport".equals(reportName)) {             invokeRosterReport();         } else if ("DifferentReport".equals(reportName)) {             System.out.println("running different report...");         }     }     /**      * 選手リストを返すWebサービスを呼び出す。      */     protected void invokeRosterReport() {         // Webサービスの呼出し         resource = Utilities.obtainClient(Constants.ROSTER_URI, "roster").path("findAll");                 setRosterList(resource.request(javax.ws.rs.core.MediaType.APPLICATION_XML)                 .get(new GenericType<List<Roster>>() {                 }));         rosterList.stream().forEach(r -> System.out.println(r.getFirstName() + " " + r.getLastName() + " - " + r.getPosition()));     }     /**      * @return rosterList      */     public List<Roster> getRosterList() {         return rosterList;     }     /**      * @param rosterList 設定するrosterList      */     public void setRosterList(List<Roster> rosterList) {         this.rosterList = rosterList;     } }   ユーザー・インタフェースから話を続けますと、Roster Reportビューには、レポートを起動するために使用するボタンが含まれています。そのボタンのコードを、rosterReport.xhtmlから抜粋して次に示します。 <p:commandButton id="rosterReport" actionListener="#{rosterController.invokeRosterReport}" value="Roster Report"/> このボタンが押されると、actionListenerによって、コントローラ・クラスのinvokeRosterReport()というアクション・メソッドが初期化されます。コントローラ・クラスはRosterControllerという名前(リスト2参照)で、ViewScopedです。これは、ビューにアクセスがあるたびにコンテキストが再生成され、クラスのスコープが再起動することを意味します。ビューが閉じられると、スコープも消滅します。 リスト2: @Named @ViewScoped public class RosterController implements java.io.Serializable {     @Resource     private ManagedExecutorService mes;          @Resource     private ManagedThreadFactory mtf;          Thread rosterThread = null;     private static Logger log = LogManager.getLogger();     private WebTarget resource;     private List<Roster> rosterList;     private Roster current;     private boolean managePlayer = false;     public RosterController() {     }     @PostConstruct     public void init() {         populateRosterList();     }     /**      * ManagedExecutorServiceの例。このアクション・メソッドは、Roster Listビューで      * Refresh Listボタンが押されたときに呼び出される。ここでは、      * 選手リストの人数の呼出しをManagedExecutorServiceに      * 送信して処理を行う。      *      */     public void refreshRosterList() {     }     /**      * List<Roster>を設定する。      */     public void populateRosterList() {         resource = Utilities.obtainClient(Constants.ROSTER_URI, "roster").path("findAll");         System.out.println(resource.getUri());         setRosterList(resource.request(javax.ws.rs.core.MediaType.APPLICATION_XML)                 .get(new GenericType<List<Roster>>() {                 }));     }     /**      * IDを受け取り、対応する<code>Roster</code>を返す。      *      * @param id      */     public void findById(int id) {         resource = Utilities.obtainClient(Constants.ROSTER_URI, "roster");         resource = resource.path(java.text.MessageFormat.format("findById/{0}", new Object[]{id}));         current = resource.request(javax.ws.rs.core.MediaType.APPLICATION_XML)                 // .cookie(HttpHeaders.AUTHORIZATION, authenticationController.getSessionToken())                 .get(                         new GenericType<Roster>() {                 });     }     public String addPlayer() {         String returnPage = null;         resource = Utilities.obtainClient(Constants.ROSTER_URI, "roster").path("add");         Form form = new Form();         form.param("firstName", current.getFirstName().toUpperCase());         form.param("lastName", current.getLastName().toUpperCase());         form.param("position", current.getPosition().toUpperCase());         Invocation.Builder invocationBuilder = resource.request(MediaType.APPLICATION_XML);         //  .cookie(HttpHeaders.AUTHORIZATION, authenticationController.getSessionToken());         Response response = invocationBuilder.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE), Response.class);         if (response.getStatus() == Status.CREATED.getStatusCode() || response.getStatus() == Status.OK.getStatusCode()) {             log.info("Successful roster Entry");             Utilities.addSuccessMessage("Player Successfully Added");             rosterList = null;             populateRosterList();             returnPage = "index";         } else {             log.error("Player entry error");             Utilities.addErrorMessage("Error entering player, please try again.If issue persists, please contact service desk.");         }         return returnPage;     }     public void remove(Roster player) {         resource = Utilities.obtainClient(Constants.ROSTER_URI, "roster");         resource = resource.path(java.text.MessageFormat.format("/{0}", new Object[]{player.getId()}));         try {             resource.request().delete();             Utilities.addSuccessMessage("Removed Player");             rosterList = null;             populateRosterList();         } catch (Exception e) {             Utilities.addErrorMessage("Error Removing Player");         }     }     public void updatePlayer() {         resource = Utilities.obtainClient(Constants.ROSTER_URI, "roster");         resource = resource.path(java.text.MessageFormat.format("/{0}", new Object[]{current.getId()}));         Response response                 = resource.request().put(Entity.entity(current, MediaType.APPLICATION_XML));         if (response.getStatus() == Status.OK.getStatusCode()) {             Utilities.addSuccessMessage("Updated Player");             rosterList = null;             managePlayer = false;             populateRosterList();         } else {             Utilities.addErrorMessage("Error Removing Player");         }     }     public void manage(Roster player) {         current = player;         managePlayer = true;     }     public void cancelAction() {         managePlayer = false;         current = null;         rosterList = null;         populateRosterList();     }     public void clear(AjaxBehaviorEvent event) {         cancelAction();     }     public void invokeRosterReport() {         ReportRunnable rosterReport = new ReportRunnable("RosterReport");         /*              * 通常、このFutureオブジェクトは、どこかにキャッシュしてから、              * 定期的にポーリングしてタスクのステータスを取得する必要がある。          */         Future reportFuture = mes.submit(rosterReport);         while (!reportFuture.isDone()) {             System.out.println("Running...");         }         if (reportFuture.isDone()) {             System.out.println("Report Complete");         }     }          public void invokeThreaddedRosterReport(){         RosterRunnable rosterReport = new RosterRunnable();         /*              * 通常、このFutureオブジェクトは、どこかにキャッシュしてから、              * 定期的にポーリングしてタスクのステータスを取得する必要がある。          */         rosterThread =mtf.newThread(rosterReport);         rosterThread.start();     }     /**      * @return rosterList      */     public List<Roster> getRosterList() {         return rosterList;     }     /**      * @param rosterList 設定するrosterList      */     public void setRosterList(List<Roster> rosterList) {         this.rosterList = rosterList;     }     /**      * @return current      */     public Roster getCurrent() {         if (current == null) {             current = new Roster();         }         return current;     }     /**      * @param current 設定するcurrent      */     public void setCurrent(Roster current) {         this.current = current;     }     /**      * @return managePlayer      */     public boolean isManagePlayer() {         return managePlayer;     }     /**      * @param managePlayer 設定するmanagePlayer      */     public void setManagePlayer(boolean managePlayer) {         this.managePlayer = managePlayer;     } }   ManagedExecutorServiceリソースは、@ResourceアノテーションによってRosterControllerに注入されます。注入が行われたら、Jakarta Concurrency APIを使って、必要なインタフェースを実装しているクラスを呼び出すことができます。invokeRosterReport()メソッドで、その呼出しが行われています。すなわちこのメソッドでは、ReportRunnableクラスのインスタンスが作成され、注入されたManagedExecutorServiceリソースにそのインスタンスが渡されています。 次の抜粋で示すのは、サービスを呼び出して、Futureオブジェクトが返されている部分です。そして、生成されたタスクの実行が完了したタイミングを判定するため、このFutureに対してポーリングが行われています。 ReportRunnable rosterReport = new ReportRunnable("RosterReport"); Future reportFuture = mes.submit(rosterReport); while (!reportFuture.isDone()) { System.out.println("Running..."); } if (reportFuture.isDone()) { System.out.println("Report Complete"); } トランザクション:バックグラウンド・タスクを生成する際に懸念される点の1つに、トランザクションの使用があります。トランザクションは、タスクの一部が失敗した場合に、すでに完了しているかもしれない他の部分をロールバックできるようにして、データ整合性を保証する仕組みです。トランザクションを作成して管理するためには、jakarta.transaction.UserTransactionを使用します。このインタフェースを使う場合、@Resourceを使ってクラスにUserTransactionを注入する必要があります。その後、UserTransaction.begin()メソッドを呼び出して新しいトランザクションを作成することができます。また、commit()メソッドを使用してトランザクションを終えることができます。必要に応じて、rollback()メソッドを使ってロールバックすることもできます。リスト3に、RESTfulサービスを呼び出して応答を待つ際にトランザクションを使ったクラスを示します。 リスト3: public class ReportRunnableTransaction implements Runnable {     private static Logger log = LogManager.getLogger();     private WebTarget resource;     private String reportName;     private List<Roster> rosterList;          @Resource     UserTransaction ut;     public ReportRunnableTransaction(String reportName) {         this.reportName = reportName;     }     /**      * このメソッドは、名前付きレポートを実行するためにオーバーライドされている。      */     @Override     public void run() {         if ("RosterReport".equals(reportName)) {             try {                 ut.begin();                 invokeRosterReport();                 // ここで何らかのデータ・トランザクションを実行                 ut.commit();             } catch (NotSupportedException|RollbackException| SystemException|HeuristicMixedException| HeuristicRollbackException|SecurityException| IllegalStateException ex) {                 java.util.logging.Logger.getLogger(      ReportRunnableTransaction.class.getName())     .log(Level.SEVERE, null, ex);             }         } else if ("DifferentReport".equals(reportName)) {             System.out.println("running different report...");         }     }     /**      * 選手リストを返すWebサービスを呼び出す。      */     protected void invokeRosterReport() {         // Webサービスの呼出し         resource = Utilities.obtainClient(       Constants.ROSTER_URI, "roster").path("findAll");                 setRosterList(resource.request(      javax.ws.rs.core.MediaType.APPLICATION_XML)                 .get(new GenericType<List<Roster>>() {                 }));         rosterList.stream().forEach(r -> System.out.println(r.getFirstName() + " " + r.getLastName() + " - " + r.getPosition()));     } // getterとsetter }   複数の非同期タスクを扱う:複数のタスクを非同期式に実行しなければならないこともあるでしょう。そのような場合には、ManageExecutorService.invokeAll()メソッドを使用します。複数のタスクを送信して非同期実行するためには、実行可能なタスクをArrayListに設定し、その配列をinvokeAll()に渡します。これにより、タスクの完了チェックや結果の取得を目的とした、Futureオブジェクトのリストが返されます。Futureを同じ方式で扱えるように、それぞれのタスク・クラスは共通の形式に準拠する必要があります。この例では、非同期呼出しの対象となるそれぞれのタスク・クラスで実装する型クラスを作成しています。この型で定義されたフィールドやメソッドは、後ほど結果を処理する際に使用できます。 まずは、共通のフィールドおよびメソッドを含む型クラスを作成します。次のコードは、RosterInfoという名前の型クラスを示しています public class RosterInfo { public String team; public List<Roster> players = null; public RosterInfo(String team, List<Roster> players){ this.team = team; this.players = players; } } 次に、型クラスを実装するタスク・クラスを作成します。今回の場合、タスク・クラスではCallableを実装します。こうすることで、Futureの結果を返すことができ、さらにチェック例外も使用できます。この例のタスク・クラスは、RosterTaskという名前です。リスト4にコード全体を示します。このクラスのコンストラクタでは、問合せを行う選手リストが属するチームを特定するIntegerを受け取ります。 リスト4: public class RosterTask implements Callable<RosterInfo>, ManagedTask {     // オンデマンドでレポートを行うためのリクエストのID     Integer teamId;     RosterInfo rosterInfo;     private WebTarget resource;     Map<String, String> execProps;     public RosterTask(Integer id) {         this.teamId = id;         execProps = new HashMap<>();                 execProps.put(ManagedTask.IDENTITY_NAME, getIdentityName());     }     public RosterInfo call() {         // Webサービスの呼出し                  resource = Utilities.obtainClient(Constants.ROSTER_URI, "team");         resource = resource.path(java.text.MessageFormat.format("{0}", new Object[]{teamId}));         Team team = null;         team = (resource.request(javax.ws.rs.core.MediaType.APPLICATION_XML)                 .get(new GenericType<Team>() {                 }));         resource = Utilities.obtainClient(Constants.ROSTER_URI, "roster");         resource = resource.path(java.text.MessageFormat.format("findByTeam/{0}", new Object[]{teamId}));         List<Roster> playerList = null;         playerList = (resource.request(javax.ws.rs.core.MediaType.APPLICATION_XML)                 .get(new GenericType<List<Roster>>() {                 }));                 return new RosterInfo(team.getName(), playerList);     }     public String getIdentityName() {         return "RosterTask:TeamID=" + teamId;     }     public Map<String, String> getExecutionProperties() {         return execProps;     }     public String getIdentityDescription(Locale locale) {          // リソース・バンドルの使用...         return "RosterTask asynchronous REST service invoker";     }     @Override     public ManagedTaskListener getManagedTaskListener() {         return new CustomManagedTaskListener();     } }   このクラスでは、Callable<RosterInfo>とManagedTaskを実装している点に注意してください。クラスでCallable<RosterInfo>を実装することにより、RosterInfo型に含まれる定義への準拠が強制されます。ManagedTaskによって追加機能が実現しますが、この機能は必須ではありません。このインタフェースについては、次のセクションで説明します。 public class RosterTask implements Callable<RosterInfo>, ManagedTask Callableを作成するときは、インタフェースに付随する型を返すcall()メソッドを実装しなければなりません。その型には、結果を扱うために必要な定義を含めます。call()メソッドは、クラスの実装を含み、結果を返します。この例では、call()経由でRosterServiceに問合せを行い、タスク・クラスのコンストラクタに渡された整数で特定されるチームの選手リストを取得しています。その後、RosterInfoの新しいインスタンスを作成し、選手リストに関連する情報を設定したうえで返却しています。 リスト5: @WebServlet(name = "BuilderServlet", urlPatterns = {"/builderServlet"}) public class BuilderServlet extends HttpServlet implements Servlet {     // エグゼキュータのインスタンスを取得     @Resource(name = "concurrent/BuilderExecutor")     ManagedExecutorService mes;     RosterInfo rosterInfoHome;     protected void processRequest(HttpServletRequest req, HttpServletResponse resp)             throws ServletException, IOException {         try {             PrintWriter out = resp.getWriter();             // タスクのインスタンスを作成             ArrayList<Callable<RosterInfo>> builderTasks = new ArrayList<Callable<RosterInfo>>();             builderTasks.add(new RosterTask(1));             builderTasks.add(new RosterTask(2));             // タスクを送信して待機             List<Future<RosterInfo>> taskResults = mes.invokeAll(builderTasks);             ArrayList<RosterInfo> results = new ArrayList<RosterInfo>();             for (Future<RosterInfo> result : taskResults) {                 out.write("Processing Results...");                 while (!result.isDone()) {                     try {                         Thread.sleep(100);                     } catch (InterruptedException e) {                         e.printStackTrace();                     }                 }                 results.add(result.get());             }             out.write("** Results Processed Successfully **");             for (RosterInfo result : results) {                 if (result != null) {                     System.out.println("===========================");                     System.out.println("Team: " + result.team);                     System.out.println("===========================");                     for(Roster roster:result.players){                         System.out.println(roster.getFirstName() + " " +                                 roster.getLastName() + " - " + roster.getPosition());                     }                 }             }         } catch (InterruptedException | ExecutionException ex) {             Logger.getLogger(BuilderServlet.class.getName()).log(Level.SEVERE, null, ex);         }     } // HttpServletのメソッド }   この例では、サーブレットを使って複数のRosterTaskインスタンスを起動し、コンストラクタに毎回異なる整数を渡して、異なるチームの結果を返すようにしています。ArrayListに、タスク・クラスの各インスタンスを含めています。このリストをManagedExecutorService invokeAll()に渡し、タスクを実行しています。サーブレット全体については、リスト5を参照してください。 ArrayList<Callable<RosterInfo>> builderTasks =     new ArrayList<Callable<RosterInfo>>(); builderTasks.add(new RosterTask(1)); builderTasks.add(new RosterTask(2)); // タスクを送信して待機 List<Future<RosterInfo>> taskResults =     mes.invokeAll(builderTasks);   結果を取得するためには、タスクの完了を一定時間待機してから、結果をリストに追加します。この例では、whileループとThread.sleep()を使って、Future.isDone()をチェックしています。 List<Future<RosterInfo>> taskResults       = mes.invokeAll(builderTasks); ArrayList<RosterInfo> results       = new ArrayList<RosterInfo>(); for (Future<RosterInfo> result :          taskResults) {     while (!result.isDone()) {         try {             Thread.sleep(100);         } catch (InterruptedException e) {             e.printStackTrace();         }     }     results.add(result.get()); }   最後に、結果のリストを反復処理します。 for (RosterInfo result : results) {     if (result != null) {         . . .         for(Roster roster:result.players){             System.out.println(                  roster.getFirstName()                    . . . );         }     } }   ManagedTaskの実装:ManagedTaskインタフェースによって、タスクを識別する情報を取得することや、ManagedTaskListenerを提供してライフサイクル情報を取得することや、追加の実行プロパティを提供することができます。たとえば、RosterTaskクラスではManagedTaskを実装しているため、getManagedTaskListener()をオーバーライドすることができます。このメソッドでは、カスタムのタスク・リスナーを返すことができます。次のコードをご覧ください。 @Override public ManagedTaskListener getManagedTaskListener() { return new CustomManagedTaskListener(); } CustomManagedTaskListenerクラス(リスト6)では、ManagedTaskListenerを実装しています。これにより、taskSubmitted()、taskAborted()、taskDone()などのメソッドのオーバーライドが可能になります。そのため、こういったライフサイクル・アクションが実行されるタイミングで、カスタムの処理を起動できます。 リスト6: public class CustomManagedTaskListener implements ManagedTaskListener { @Override public void taskSubmitted(Future<?> future, ManagedExecutorService mes, Object o) { System.out.println("Task Submitted"); } @Override public void taskAborted(Future<?> future, ManagedExecutorService mes, Object o, Throwable thrwbl) { System.out.println("Task Aborted"); } @Override public void taskDone(Future<?> future, ManagedExecutorService mes, Object o, Throwable thrwbl) { System.out.println("Task Complete"); } @Override public void taskStarting(Future<?> future, ManagedExecutorService mes, Object o) { System.out.println("Task Starting"); } }   ManagedScheduledExecutorService 通常、レポート生成などの長時間かかるタスクは、営業時間外などの決められたスケジュールで実行するのが合理的です。ManagedScheduledExecutorServiceにより、スケジュールに基づいたこのようなタスクを、ManagedExecutorServiceと非常によく似た方法で実行できます。 この例では、Rosterアプリケーションから、さまざまなチームの選手リストをあらかじめ指定した間隔で1日中ポーリングしています。ここでは、先ほどの例と同じスタイルのレポート・クラスを使っています。このクラスではRunnableを実装しているため、run()メソッドをオーバーライドしてレポートのロジックを初期化しています。リスト7には、ReportRunnableという名前のクラスのコードが含まれています。このクラスのコンストラクタでは文字列ベースのレポート名を受け取っています。その後、サービスを呼び出して結果を取得することで、レポートの結果を生成しています。 リスト7: public class RosterRunnable implements Runnable {     private static Logger log = LogManager.getLogger();     private WebTarget resource;     private List<Roster> rosterList;     public RosterRunnable() {     }     /**      * このメソッドは、名前付きレポートを実行するためにオーバーライドされている。      */     @Override     public void run() {        obtainRoster();     }     /**      * 選手リストを返すWebサービスを呼び出す。      */     protected void obtainRoster() {         // Webサービスの呼出し         resource = Utilities.obtainClient(Constants.ROSTER_URI, "roster").path("findAll");                 setRosterList(resource.request(javax.ws.rs.core.MediaType.APPLICATION_XML)                 .get(new GenericType<List<Roster>>() {                 }));         rosterList.stream().forEach(r -> System.out.println(r.getFirstName() + " " + r.getLastName() + " - " + r.getPosition()));     }     /**      * @return rosterList      */     public List<Roster> getRosterList() {         return rosterList;     }     /**      * @param rosterList 設定するrosterList      */     public void setRosterList(List<Roster> rosterList) {         this.rosterList = rosterList;     } }   このソリューションで重要な部分は、ReportRunnableを呼ぶコードです。ReportRunnableは、ManagedScheduledExecutorServiceを経由して呼び出されているからです。この例では、ScheduledRosterReportRunnerクラスで定期的にレポートを起動しています。このクラスは、アプリケーションのデプロイ時と起動時に一度だけ呼び出され、次のコードによってレポートのスケジュールを設定しています。完全なコードは、リスト8を参照してください。 @PostConstruct public void rosterScheduler() { System.out.println("Scheduling report..."); ReportRunnable rosterReport = new ReportRunnable("RosterReport"); rosterHandle = mes.scheduleAtFixedRate( rosterReport, 5L, 5L, TimeUnit.MINUTES); System.out.println("Report scheduled...."); } 例を見ればわかるように、並列タスクの実行をスケジューリングする鍵となるのはscheduleAtFixedRate()メソッドです。このメソッドでは、最初のパラメータとしてRunnableを、続いて最初の遅延(Long)、期間(Long)、TimeUnitを受け取ります。または、前回タスクの実行終了から次回タスクの開始までの待機時間をscheduleWithFixedDelay()に指定して、スケジュールを設定することもできます。 リスト8: @Startup @Singleton @ApplicationScoped public class ScheduledRosterReportRunner { Future rosterHandle = null; @Resource(name = "concurrent/_defaultManagedScheduledExecutorService") ManagedScheduledExecutorService mes; public ScheduledRosterReportRunner(){ } @PostConstruct public void rosterScheduler() { System.out.println("Scheduling report..."); ReportRunnable rosterReport = new ReportRunnable("RosterReport"); rosterHandle = mes.scheduleAtFixedRate(rosterReport, 5L, 5L, TimeUnit.MINUTES); System.out.println("Report scheduled...."); } }   ManagedThreadFactory ManagedThreadFactoryは、Jakarta EE環境でスレッドを作成するために導入されました。エンタープライズ・アプリケーションでベアボーンのスレッドを実装するためには、クラスにManagedThreadFactoryを注入してから、newThread()メソッドを呼び出して新しいスレッドを作成します。newThread()メソッドはjava.util.concurrent.ThreadFactoryから継承されているため、Java SE環境でスレッドを扱っている開発者には非常になじみのある実装です。 . . . @Resource private ManagedThreadFactory mtf; . . . public void invokeThreaddedRosterReport(){ RosterRunnable rosterReport = new RosterRunnable(); rosterThread = mtf.newThread(rosterReport); rosterThread.start(); }   まとめ Jakarta Concurrency APIでは、エンタープライズ・アプリケーションで並列ソリューションを実現する多くのオプションや、スレッドの生成、タスクの管理、タスクのスケジューリングの各機能が提供されます。このAPIを使うためには、完全なJakarta EE 8プロファイルをロードするか、必要な依存性をプロジェクトのAPIに含めます。Jakarta EE 8にはJakarta Concurrency 1.1が含まれており、Jakarta EE 9にはJakarta Concurrency 2.0が含まれる予定になっています。 詳しくは、以下を参照してください。 Jakarta EE Jakarta Concurrency API 本記事で紹介しているソース・コードはGitHubからダウンロードできます。 Josh Juneau Josh Juneau(@javajuneau):アプリケーション開発者、システム・アナリスト、データベース管理者。主に、Javaやその他のJVM言語を使った開発に従事。Oracle Technology NetworkやJava Magazineで多くの記事を執筆し、JavaやJava EEに関する複数の書籍をApressから出版している。JSR 372およびJSR 378のJCP専門家グループのメンバーを経験。NetBeans Dream Teamメンバー、Java Champion、CJUG OSS Initiativeのリーダーであり、JavaPubHouse Off Heapポッドキャストにレギュラー出演中。

※本記事は、Josh Juneauによる"Get started with concurrency in Jakarta EE"を翻訳したものです。 優れたアプリケーションの要の1つである良好なパフォーマンスというのは、複数のタスクをパラレルに同時実行できることを指す場合が多い 著者:Josh Juneau 2020年6月22日 Jakarta...

クイズに挑戦:コア関数型インタフェースのリファクタリング(中級者向け)

※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz yourself: Refactoring with the core functional interfaces (intermediate)"を翻訳したものです。 リファクタリングでコードを改善する方法に関する知識を確認する  著者:Simon Roberts、Mikalai Zaikin  2020年6月16日 | 本記事をPDFでダウンロード その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 このJava SE 11クイズでは、リファクタリングを行ってコードを改善したいと思います。コードのコミット履歴を確認しているとき、同僚が次のメソッド・シグネチャを置き換えていたことに気づいたとします。 public void testDrive(Vehicle x) { // ... } 置換後のメソッド・シグネチャは次のようになっていました。 public void testDrive(Supplier<? extends Vehicle> x) { // ... } 次のうち、このようなリファクタリングのメリットを表しているものはどれですか。1つ選んでください。 A. 実行が高速になる B. カプセル化が向上する C. 遅延インスタンス化が行われる D. 車両のキャッシュがサポートされる E. 別の車両サブクラスが使いやすくなる 解答:設問の選択肢が価値の判断に見える場合や意見に基づいているように見える場合は常に、設問が正確であるかどうかや、出題者が考えていたことをどのようにして知らなければならないのかを考えて、注意をそらされてしまいがちです。しかし、出題者が考えていたことを知る必要はありません。 安心していただきたい点は、実際の試験の設問(と選択肢)は細かく精査され、綿密なベータ・テストを経て、合理的な疑いを差し挟む余地があったものは取り除かれるということです。本記事では、設問を作成したのが私たち2人だけであるため、議論や精査は実際の試験よりはるかに少ないものになっています。選択肢を検討するうえで、「これをこのように見たらどうだろうか」と別の角度から考えることに余念がない場合でも、皆さんがそのような設問にどのようにして解答できるかを根拠と論理で説明したいと思います。 それでは、順番に選択肢について考えてみます。 選択肢Aには、コードの実行が速くなるとあります。そのため、リファクタリングの前後をどのように比較するかについて考える必要があります。元々のコードでは、Vehicleオブジェクトは直接的に提供されており、その提供を行うのはコール元の責任です。リファクタリングされた形式では、コール元はVehicleにアクセスする手段であるSupplierを提供しています。ただし、Supplierには、Vehicleを作成しなければならないという制約はなく、既存のオブジェクトを返すこともできます。 一方で、コール元が何らかの理由ですでにVehicleオブジェクトを保持している可能性もあります(同じVehicleに対して連続して多くのテストを行っている場合など)。既存のオブジェクトには、リファクタリング前のコードでもリファクタリングしたコードでも対応できます。しかし、リファクタリングしたコードのtestDriveメソッドは、レイヤーを1つ挟んで間接的にVehicleにアクセスせざるを得なくなります。これまでのところ、リファクタリングしたアプローチには明らかな利点がない一方で、潜在的な欠点が1つ存在します。 ここでもう1つ、testDriveメソッドの動作に実はVehicleが必要なかったらどうなるかについて考えてみます。このメソッドの名前を考えれば、少し飛躍していると思う方もいらっしゃるかもしれません。しかし、おそらくは構成フラグが原因で、テスト自体によって実行のスキップが決定されることも考えられます。その場合、リファクタリングした形式ではSupplierを使用できないというだけの話です。すると、Vehicleがまったく作成されない可能性が出てきます。それによって、実行が速くなるかもしれません。 この時点で、低下と向上の両方向でパフォーマンスに関する多少の議論がありました。いずれも状況次第ではありますが、この状況については何も提示されていません。そのため、この選択肢はさほど魅力的ではありません。 選択肢Bには、リファクタリングした形式ではカプセル化が向上するとあります。これが妥当であるとは思えません。リファクタリングの有無にかかわらず、Vehicleオブジェクトは得られます。また、オブジェクトがカプセル化されているかいないかは、使用方法ではなく実装によるものです。いずれの場合でも、testDriveメソッドはVehicleを知っています。変わったのは、Vehicleへのアクセスの仕方だけです。Vehicle自体について言えば、testDriveメソッドや、このメソッドが属するクラスがカプセル化されているかどうかによる影響はありません。カプセル化は明らかに違うと思われるため、選択肢Bは誤りだと考えることができます。 選択肢Cでは遅延インスタンス化に触れています。これはどういう意味でしょうか。この考え方の一部は、選択肢Aの説明の際に触れました。つまり、Vehicleを作成するタイミング、さらには作成自体が必要かどうかを、呼び出される側のメソッドが制御できるようにすることです。車両なしに車両のテスト運転を行うというのは想像しにくいですが、先ほども挙げたように、何らかの構成フラグによってテストが制御される場合や、実行がスキップされる場合もあるでしょう。そのような状況では、このアプローチによって直接かつ効率的にスキップを行うことができます。ただし、遅延インスタンス化でできることはそれだけではありません。たとえば、テスト運転で複数台の車両が必要になる事態を想定できます(おそらく、衝突で1台目が壊れてしまっているのでしょう)。Supplierを受け取ったメソッドは、Supplierを呼び出さないことも、1回だけ呼び出すことも、複数回呼び出すこともできます。そのため、オブジェクトを必要な数だけ作成できます。 ここで1つの疑問が生じます。このアプローチでは、呼び出されたメソッドがインスタンス化を事実上制御することになります。しかし、これによってコール元は制御を奪われることになるのでしょうか。選択肢Aで問題となっていた欠点が打ち消される可能性はあるでしょうか。その答えはノーです。どのようにVehicleを作成するかをコール元が完全に制御できる点は変わりません。失うのは、オブジェクトを作成するタイミングという一部だけです。実際、コール元は、新しいオブジェクトを作成するのではなく、既存のオブジェクトを渡すだけのSupplierを提供することもできます。ただし、今回の場合は、この2つの側面が互いを打ち消し合わないように、コール元のプログラマーと呼び出されるメソッドのプログラマーとの間で、設計の意図についての共通認識が必要になります。 この時点で、選択肢Aで説明した潜在的な利点が、選択肢Cの利点の一部でしかないことは明らかなはずです。この設問が単一選択で、選択肢Bが誤りであることを考えれば、選択肢Aは誤りと判断し、この時点では選択肢Cが唯一の正解候補と考えることになるはずです。 選択肢Dには、リファクタリング後はVehicleインスタンスのキャッシュが可能になるとあります。当然ながら、Supplierの実装ではキャッシュを使用できます。しかし、Supplierを提供するのはコール元の責任であるため、キャッシュをサポートするかどうかを決めるのはコール元の責任です。リファクタリング前の設計では、コール元が直接Vehicleを提供していますが、この設計でもキャッシュ自体には簡単にアクセスすることができます。結論としては、コール元が制御するキャッシュをSupplierで使用できるのは確かですが、これは具体的な利点であるとは言えません。リファクタリング前のアプローチでも同じことができ、いずれかのアプローチの方が優れていると考えられるわけではないからです。そのため、選択肢Dは誤りです。 選択肢Eには、Supplierによって、Vehicleの別のサブクラスが使いやすくなるとあります。この選択肢が合理的でないのは明らかです。一般化という考え方により、Vehicleのサブクラスは実行時に置き換えることができます。この設問では、コール元はVehicle、またはVehicleのSupplierを提供しなければなりません。そして、いずれの場合でも、コール元は代入互換性があるオブジェクトを渡してメソッドを呼び出すことができます。そのため、選択肢Eは誤りです。 以上の内容から、選択肢Cは明らかに選択肢Aより優れており、その他3つの選択肢に実際の利点はないことがわかったはずです。よって、選択肢Cは正解です。 1つ補足しておきます。遅延インスタンス化は、適切な状況で使用すれば非常に大きなメリットが得られる可能性があります。そして、このアプローチは現在、ロギング・フレームワークで広く使用されています。ログ・メッセージを準備する場合、Exceptionのスタック・フレームを走査して多くの文字列連結を行うのが普通です。これをすべて行うためには多くのCPUが必要になり、メモリも大量に使用するかもしれません。それにもかかわらず、ロギング・レベルが「error」や「severe」に設定されている場合の「trace」レベルのメッセージのように、完全に破棄されるメッセージもよくあります。実際のStringではなくSupplier<String>をロギング・システムに渡すことで、メッセージが実際には使用されない場合のメッセージ作成処理を避けることができます。 リファクタリング前のバージョンで、単純にロギングを呼び出すコードは次のようになるでしょう。 logger.log(Level.FINER, Stream .of(t.getStackTrace()) .map(StackTraceElement::toString) .collect(Collectors.joining("\n")); この場合、非常に高価なlogの呼出しが行われる前にメッセージが作成されている点に注意してください。実際にはロガーがメッセージをそのまま捨ててしまうかもしれない場合でも同様です。 一方、次の形式では、メッセージを必要としないロギング・レベルの場合、メッセージ作成操作は必要ないと認識されます。なお、この例で示した、手動でのスタックの走査と収集は、論点を強調するためにあえてこのようにしています。例外のトレースを処理する際の適切な方法を示すものではありませんので、ご注意ください。 logger.log(Level.FINER, () -> Stream .of(t.getStackTrace()) .map(StackTraceElement::toString) .collect(Collectors.joining("\n")); 正解は選択肢Cです。 Simon Roberts Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO'Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。   Mikalai Zaikin Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。 .button { background-color: #759C6C; color: white; border: 2px solid #759C6C; border-radius: 12px; padding: 5px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; margin: 4px 2px; cursor: pointer; } function answer1() { alert("This answer is incorrect. "); } function answer2() { alert("This answer is incorrect. "); } function answer3() { alert("This answer is correct, but it is not the only correct answer. "); } function answer4() { alert("This answer is correct, but it is not the only correct answer. "); } function answer5() { alert("This answer is incorrect. "); }

※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz yourself: Refactoring with the core functional interfaces (intermediate)"を翻訳したものです。 リファクタリングでコードを改善する方法に関する知識を確認する  著者:Simon Roberts、Mikalai Zaikin 2020年6月16日...

クイズに挑戦:安全なシリアライズとデシリアライズ(上級者向け)

※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz yourself: Secure serialization and deserialization (advanced)"を翻訳したものです。 頻繁に変更されるコードをメンテナンスしやすくする方法に関する知識を確認する 著者:Simon Roberts、Mikalai Zaikin  2020年6月22日 | 本記事をPDFでダウンロード その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 シリアライズとデシリアライズを安全にしつつ、頻繁に変更されるコードをメンテナンスしやすくしたいと思います。 今回の上級レベルのJava SE 11クイズでは、ビジネス・オブジェクトのシリアライズを多用するアプリケーションに関する作業を行っていることを想定します。ビジネス・オブジェクトのクラスには、時間とともにフィールドの追加や削除が行われます。これにより、新しいコードが古いシリアライズ表現と一致しなくなるという問題が発生します。 変更点の管理を容易にし、ビジネス・オブジェクトが円滑に動作し続けるようにするために行う、もっとも適切な変更はどれですか。1つ選んでください。 A. A.    java.io.Externalizableを実装し、シリアライズする変数を選択する B. 必要ないインスタンス変数をprivateにする C. 必要ないインスタンス変数をtransientにする D. serialPersistentFields配列を追加し、必要な変数を読み書きする際にwriteObject/readObjectから参照する   解答:シリアライズの仕組みにおけるデフォルトの動作は、オブジェクト全体に含まれるすべてのインスタンスの状態を保存およびリストアするというものです。フィールドの追加や削除が行われることで、アプリケーションが破損することがあり、最悪の場合はセキュリティの問題が紛れ込む可能性もあります。デフォルトのシリアライズを使用する場合、static final long serialVersionUID定数を含め、互換性を伴わない形でクラス構造を変更するたびにこの定数を更新する必要があります。 それでは、この問題を軽減する方法として提案されている選択肢を確認します。 選択肢Aには、java.io.Externalizableインタフェースを実装し、オブジェクトのどの部分を保存およびリストアするかを制御できるようにすることで、シリアライズする変数を選択できるようにすると述べられています。確かに、これは非常に強力な仕組みです。ただし、このモデルで、シリアライズとデシリアライズをすべて低レベルで処理しなければならないのはプログラマーです。そのため、プログラマーはシリアライズとデシリアライズの順番を厳密に管理し、クラスの進化にも完全に対応しなければなりません。これらすべての作業はエラーが起こりやすく、メンテナンスはかなり難しくなります。そのため、このアプローチは大変強力で、役立つこともあるかもしれませんが、当面の問題に対しては作業の負担が大きすぎると考えられます。 問題を軽減できることから、選択肢Aは誤りとは言えません。しかし、設問ではもっとも適切な選択肢が問われているため、他の選択肢との比較が終わるまで判断を保留する必要があります。 選択肢Bでは、privateアクセス修飾子を使ってシリアライズを制御するとしています。しかし、privateによってフィールドのシリアライズ動作が変わることはありません。この選択肢で状況は何も変わらないことを考えれば、この時点ですぐに、選択肢Bは誤りだと判断できます。 選択肢Cでは、一部のフィールドをtransientとマークすることが提案されています。このキーワードを使用した場合、シリアライズ形式に変換される際に、その変数が書き込まれなくなります。そのため、当面の問題の対処策となる可能性があります。しかし、transientにも欠点があります。その1つは、頻繁に変更されるクラスでは、「許可リスト」によるアプローチの方が、「ブロックリスト」によるアプローチよりも一般的にコードのメンテナンスがしやすいという点です。許可リストによるアプローチとは、シリアライズに含めない変数ではなく、含める変数をプログラマーが指定するというものです。 2つ目は、transientでは、シリアライズされる変数の型が変わった場合に互換性が失われる問題の対処策にはならないという点です。この選択肢に多少の利点があることは間違いありませんが、選択肢Aほど表現力は高くありません。 選択肢Dでは、serialPersistentFieldsという予約された名前のprivate static final配列を作るとしています。この配列には、次のようにして、シリアライズするフィールドの名前と型を指定するObjectStreamFieldオブジェクトを設定します。 class BusinessObject implements Serializable { List list; private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("list", List.class) }; } この仕組みを使用して、シリアライズするフィールドをプログラマーが許可リストに明示的に追加することができます。さらに、いくつかの制約のもとで、フィールドの追加や削除もサポートされます。コードが進化した場合でも、writeObjectおよびreadObjectというprivateメソッドを使って、シリアライズされたコピーからカスタムのマッピングを行うことができます。次の例をご覧ください。 class BusinessObject implements Serializable { MyList list; // 変更されたデータタイプ(変更前はList) private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // オリジナルのシリアライズ形式からすべてのデータを取得する ObjectInputStream.GetField gf = ois.readFields(); // そのデータからオリジナルのリストを抽出し、 // そこに含まれる要素から新しいデータタイプMyListを作成する this.list = MyList.createFromList(gf.get("list", list))); } } 4つの選択肢のうち3つが、当面の問題に対する何らかの対処策となる可能性があることがわかりました。そこで、どれが最適な選択肢かについて考えてみます。3つの有効な選択肢とそれぞれの特徴を表形式で整理すれば、一番わかりやすいでしょう。 この表から、serialPersistentFieldsを使う選択肢Dがバランスの点でもっとも優れていることがわかります。この方法は、実装や変更が非常に簡単でありながら、実際のプロジェクトで起こり得る変更の大部分に対処できるほど強力で表現力が高いものです。 一方、Externalizableを使う選択肢Aは、表現力がもっとも高いものの、非常に慎重に実装する必要があります。データ構造に細かな変更が行われるたびにコードを正しく変更するのは厄介な作業です。そのため、選択肢Aは誤りです。 transientを使う選択肢Cはとてもシンプルですが、データタイプの変更には対処できません。さらに、transientを使用する場合、シリアライズしてはいけないフィールドが新しく追加されたときに注意が必要です。そのため、許可リストのアプローチよりもエラーが起こりやすくなる可能性があります。よって、選択肢Cは誤りです。 以上の理由から、serialPersistentFieldsの仕組みを使う方法が最適で汎用的なアプローチであると考えられます。そのため、選択肢Dが正解となります。 正解は選択肢Dです。   Simon Roberts Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO'Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。   Mikalai Zaikin Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。 .button { background-color: #759C6C; color: white; border: 2px solid #759C6C; border-radius: 12px; padding: 5px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; margin: 4px 2px; cursor: pointer; } function answer1() { alert("This answer is incorrect. "); } function answer2() { alert("This answer is incorrect. "); } function answer3() { alert("This answer is correct, but it is not the only correct answer. "); } function answer4() { alert("This answer is correct, but it is not the only correct answer. "); } function answer5() { alert("This answer is incorrect. "); }

※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz yourself: Secure serialization and deserialization (advanced)"を翻訳したものです。 頻繁に変更されるコードをメンテナンスしやすくする方法に関する知識を確認する 著者:Simon Roberts、Mikalai Zaikin 2020年6月22日 |...

クイズに挑戦:コア関数型インタフェースの使用(中級者向け)

※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz yourself: Using core functional interfaces (intermediate)"を翻訳したものです。 Predicate、Consumer、Function、Supplierの各インタフェースの使用方法に関する知識を確認する 著者:Simon Roberts、Mikalai Zaikin  2020年6月8日 | 本記事をPDFでダウンロード その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 このJava SE 11クイズでは、コア関数型インタフェースを使いたいと思います。 次のコード部分について: public static void main(String[] args) throws InterruptedException, ExecutionException { var cf = CompletableFuture.supplyAsync(() -> "Java"); cf = cf.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + "11")); System.out.print(cf.get()); } thenComposeメソッドに渡されるのは、どの関数型インタフェースのインスタンスですか。1つ選んでください。<\p> A. java.lang.Runnable B. java.util.concurrent.Callable C. java.util.function.Function D. java.util.function.BiConsumer E. java.util.function.Supplier   解答:一目でわかるのは、この設問がCompletableFuture APIの知識を必要としているようだということです。しかし、実はそうではありません。試験では、その対象に含まれないはずの見慣れないAPIを含む設問を見かけることがあるかもしれません。見慣れないAPIが試験対象に含まれないことが正しいなら、そのAPIは実のところ設問の核心ではなく、その関連知識を使わずに回答できるという可能性が高いと言えます。今回の設問もそのような問題です。 ただし、Java 11の主な関数型インタフェースについての知識は、試験対象に含まれています。そして、この設問もその範囲内です。関数型インタフェースは多数ありますが、次の一般的な形態を知り、いくつかの変形に注意すれば、おそらく十分でしょう。 Supplier:引数を受け取らず、何かを返す Consumer:1つ以上の引数を受け取り、voidを返す Function:1つ以上の引数を受け取り、何かを返す Predicate:1つ以上の引数を受け取り、プリミティブのbooleanを返す 各種Operator(BinaryおよびUnary):引数と戻りタイプが同じでなければならないという制約がある Bi接頭辞:引数が1つでなく2つであることを示す Int、Long、Double、ToInt、ToLong、ToDoubleの各接頭辞:引数または戻りタイプがそのプリミティブであることを示す java.util.functionパッケージで定義されている43個のインタフェースのうち、40個がこのルールの対象となります。 そして、おそらく知っておくべきインタフェースが、あと3つあります。 Runnable:引数を受け取らず、voidを返す Callable:引数を受け取らず、ジェネリック型を返し、例外をスローする Comparator:同じ型の2つの引数を受け取り、プリミティブのintを返す それでは、thenComposeメソッドに何が渡されるかを確認してみます。 s -> CompletableFuture.supplyAsync(() -> s + "11") 設問のコードを確認するだけで、このラムダ式からいくつかのことがわかります。 ->の左辺は、1つの変数sです。そのため、このラムダ式は1つの引数を受け取ります。引数が括弧で囲われていないのは問題ありません。この1つの引数には、明示的な型もvar疑似タイプも指定されていないからです。 本体は単純な式ですが、波括弧もreturnキーワードもありません。これらの要素は、いわゆる「式ラムダ」では省略可能です。 返される型はvoidではありません。これは、前の行で同じメソッド呼出し(CompletableFuture.supplyAsync)が使われており、そこで変数cfに値を代入していることからわかります。 以上より、1つのパラメータを受け取るラムダ式があることがわかります。それでは、各選択肢について詳しく見ていきます。 選択肢AにはRunnableとありますが、Runnableのabstractメソッドは次の形式です。 public abstract void run(); つまり、パラメータは受け取らず、何も返しません。パラメータがないため、このラムダ式のターゲット型となることはできません。そのため、選択肢Aは誤りです。 選択肢BにはCallable、選択肢EはSupplierとあります。しかし、次に示すように、これらのインタフェースのabstractメソッドにはいずれも引数がありません。 Callableインタフェースは次の形式です。 public interface Callable<V> { V call() throws Exception; } そして、Supplierインタフェースは次の形式です。 public interface Supplier<T> { T get(); } そのため、選択肢Bと選択肢Eはいずれも誤りです。 選択肢DにはBiConsumerとありますが、名前からわかるように、このインタフェースには2つの引数が必要です。 public interface BiConsumer<T, U> { void accept(T t, U u); } BiConsumerには2つの引数が必要であるため、選択肢Dは誤りです。 選択肢CにはFunctionとあります。このインタフェースでは、1つの引数を受け取る次のabstractメソッドを宣言しています。 public interface Function<T, U> { U apply(T t); } このメソッドの引数の個数は要件に一致しているため、選択肢Cは正解です。そして、おそらくご想像のとおりと思いますが、このコードではコンソールにJava11と出力します。 ラムダ式が返す式から情報を集めることができるかどうかを考えてみるのも、興味深いことです。このラムダ式が実装しているメソッドは値を返さなければならないと考えて問題ないでしょうか。それとも、Consumerインタフェースのacceptメソッド(voidを返します)などをこのラムダ式が実装している可能性も考えられるでしょうか。示されているメソッドはvoidでない型を返すことはわかっていますが、Javaでは関数が返す値を無視することが認められています。そのことを示すとても簡単な例は、Listインタフェースのaddメソッドです。ほとんどの場合、addメソッドを使うときは戻り値を無視します。 List<String> ls = new ArrayList<>(); ls.add("Text");  // 返されるブール値を無視 しかし実際には、addメソッドはboolean値を返します。 Javaでは、CおよびC++に存在した、メソッド呼出しの戻り値を無視するという選択肢を採用しました。その理由は、この仕組みがとても便利だからです。Listのaddメソッドの場合、falseを返すことはほとんど考えられないため、戻り値を無視するのは一般的です。 戻り値が存在すること自体に疑問を感じる方もいるかもしれません。このaddメソッドは、Collectionのaddを実装しており、追加が成功したかどうかを示します。Setの場合、提供されるオブジェクトがSetにすでに含まれていた場合、addメソッドはfalseを返します。 しかし、戻り値を無視することが許可されている理由がどのようなものであるにせよ、この動作は有効です。そのため実際には、この設問に記述されているラムダ式をエラーなしでConsumerに代入することもできます(ただし、BiConsumerに代入することはできません)。 正解は選択肢Cです。 Simon Roberts Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO'Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。   Mikalai Zaikin Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。 .button { background-color: #759C6C; color: white; border: 2px solid #759C6C; border-radius: 12px; padding: 5px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; margin: 4px 2px; cursor: pointer; } function answer1() { alert("This answer is incorrect. "); } function answer2() { alert("This answer is incorrect. "); } function answer3() { alert("This answer is correct, but it is not the only correct answer. "); } function answer4() { alert("This answer is correct, but it is not the only correct answer. "); } function answer5() { alert("This answer is incorrect. "); }

※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz yourself: Using core functional interfaces (intermediate)"を翻訳したものです。 Predicate、Consumer、Function、Supplierの各インタフェースの使用方法に関する知識を確認する 著者:Simon Roberts、Mikalai...

Concurnasによる並列プログラミング

※本記事は、Jason Tattonによる"Concurrent programming with Concurnas"を翻訳したものです。 現在の開発者が利用できる今までにないハードウェアの力。新しいJVMプログラミング言語Concurnasでその力を解放する方法に迫る 著者:Jason Tatton 2020年6月22日 並列プログラムを書くのは難しい作業です。しかし、現在の最新マルチコアCPUアーキテクチャの力を利用するためには、同時実行に対応したソフトウェアが欠かせません。さらに、現在のほとんどのコンピュータはグラフィックス・プロセッシング・ユニット(GPU)を利用できます。開発者がGPUを利用すれば、大量の並列問題をこれまでにないスピードで解くこともできます。しかし、GPU向けのネイティブ・コーディングも困難です。 開発者がソフトウェアを構築する方法は変わりました。本質的にイベントドリブンやリアクティブなシステムが増え続けている一方で、ほとんどのプログラミング言語やアルゴリズムはこの新しいパラダイムをうまくモデリングできていません。この課題の発生と時を同じくして、開発者は生産性を向上させて、信頼性、パフォーマンス、およびスケーラビリティに優れたソフトウェアを作成することが期待されています。イノベーションあふれるソリューションでそれらの問題を解決するときが来ているのです。 本記事では、JVMをターゲットとした新しいプログラミング言語Concurnasについて説明します。Concurnasは、信頼性、スケーラビリティ、高パフォーマンス、同時実行性を兼ね備えた最先端の分散型パラレル・システムの構築用に特化して設計されました。   Concurnas言語の概要 Concurnasは、主に次の4つのことを目指して設計されました。 平均的な開発者が、現在のマルチコアCPUおよびGPUハードウェア・アーキテクチャを活用した同時実行パラレル・システムの構築を容易にして扱いやすくする 強力な静的型システムがもたらすパフォーマンスと機能を備えた、動的型付け言語の構文を提供し、学習しやすく、高パフォーマンスの言語となる 経験を積んだソフトウェア・エンジニアでも、コンピューティング以外の分野のスペシャリストでも役立つものとなる 命令型、オブジェクト指向、関数型、リアクティブ・プログラミングの各パラダイムから特に人気の高い機能を簡潔な言語で提供し、プロトタイピングと大規模ソフトウェアの両方をサポートするマルチパラダイム言語サポートを提供する Concurnasでは、JVMで実行するためのコンパイルが行われます。そのため、JVMの優れたパフォーマンス、自動メモリ管理(すなわちガベージ・コレクション)、Just-In-Timeコンパイル、Javaの標準ライブラリへのアクセス(以降の例で説明します)、ツールの使用に加え、JavaやScala、Kotlinといった既存JVM言語との互換性を実現します。 Concurnas言語のコア構文は、Pythonから着想を得ています。次の単純な関数では、与えられた数値と25との最大公約数を計算しています。 def gcd(x int){ y = 25 while(y){ (x, y) = (y, x mod y) } x } このコードに関して注目すべき点は、以下のとおりです。 変数yの型はinteger型と推論されます。関数全体の戻りタイプも同様です。Concurnasでは、使いやすさのため、関数の入力パラメータを除いて型付けはオプションです。型推論(関数型プログラミング言語によく見られる機能)が使用されており、動的型付け言語のように見えます。 関数の定義では、return文は省略されます。すべてのコード・ブロック({}で囲われている部分)は値を返すことができます。最後に参照された値(この例ではx)が暗黙的に返されます。 Concurnasではタプルをサポートしています。(y, x mod y)というタプルの定義があることがわかります。代入演算子の左辺の(x, y)には、分割代入が行われます。タプルはすばらしい手段です。タプルを使うことにより、データのコレクションをまとめるラッパー・クラスを定義することなく、コレクションを扱うことができます。 ブール式が来ることが想定される場所(while(y)の中など)で、すべての型を使うことができます。 関数は独立して存在できます。Javaなどの言語では、関数をクラスのコンテキスト外に定義することはできませんが、Concurnasにはそのようなルールはありません。   Concurnasの同時実行性 Concurnasでは、Javaやその他多くのJVM言語に見られる標準的な同時実行モデルに代わる革新的な仕組みが提供されます。Concurnasの同時実行モデルでは、スレッドやクリティカル・セクション、ロック、共有メモリを明示的に管理する必要はありません。 これは、アイソレートと呼ばれる、スレッドに似た軽量なコンテナで計算を実行することにより、実現しています。Concurnasプログラムのすべてのコードはアイソレートで実行されます。アイソレートは同時実行が可能ですが、メモリを直接共有することはできません。 異なるアイソレート間でデータが参照される場合、データはコピーされます。そのため、意図せずに状態が共有されることはありません。当然ですが、これが話の全容であれば、Concurnasはあまり機能的でないプログラミング言語となるでしょう。安全な形で状態を共有して管理することが、並列コンピューティングには欠かせないからです。 これを実現するため、Concurnasにはrefと呼ばれる特殊な型が存在します。refは任意の型から構成可能であり、型にコロン(:)を追加して示します。refではその同時実行性が安全かつアトミックに管理されるため、コピーすることなくアイソレート間でrefの共有が可能です。アイソレートからの戻り値が見込まれる場合、その戻り値はref型になります。次に示すのは、先ほど定義した関数gcdを使ってアイソレートを作成する方法です。 res1 int: = gcd(92312)! res2 = {gcd(2438210)}! larger = res1 if res1 > res2 else res2 larger = res1 if res1 > res2 else res2 ここでは、感嘆符演算子(!)を使って2つのアイソレートを作成しています。!演算子は、関数呼出しやコード・ブロックの後に配置できます。 この場合、いずれのアイソレートの戻りタイプもint:になります。res1は明示的にint:型として定義しましたが、res2については明示的な定義を省略しました。続いて、コードでは2つの計算を同時に行い、その結果をres1とres2に格納して、いずれが大きいかを判定しています。プログラムの最後の文に到達すると、コードの実行は一時停止し、res1とres2の両方が設定されるまで待機します。 実行が一時停止している間は、コードを実行していた、ベースとなるアイソレートが実行権を手放すため、他のアイソレートを実行できるようになります。res1とres2が設定されると、ベースとなるアイソレートに制御が戻されます。するとベースとなるアイソレートでは、関連する各refが参照する値が取得され、それぞれの値を使ってコードの後続処理が実行されます。 設定される1つまたは複数のrefの状態に応じてアイソレートの実行を一時停止するためには、awaitキーワードを使用します。このキーワードにはオプションでガード条件を付けることもできます。次の例をご覧ください。 result int: {result = 100}! await(result) //実行の継続が可能に... syncキーワードを使うこともできます。このキーワードを使うことにより、コード・ブロック内で作成されたすべてのアイソレートの実行が完了してから、実行が継続されることが保証されます。 result int: sync{ {result = 100}! //...more isolates defined here... } assert result == 100 このアイソレート・モデルが美しいのは、開発者がコードの大部分をシングルスレッドの要領で書くことができ、並列処理制御を行う必要がない点です。並列設定でそのコードを実行する場合でも、アイソレートの生成により、コードがシングルスレッド設定で実行されているかのように振る舞うことがやはり保証されます。このモデルにより、コード内の関心領域が分割されます。 この処理の大部分は、依存データをアイソレート間で暗黙的にコピーすることによって行われます。次に例を示します。 n = 10 nplusone = { n += 1; n }! nminusone = { n -= 1; n }! assert n == 10 assert nplusone == 11 assert nminusone == 9 上記のコードでは2つのアイソレートを生成しています。いずれも変数nを操作するものです。アイソレートの実行は本質的に不確定です。そのため、2つのアイソレートが厳密に同じ時間に実行されるようにスケジュールされる可能性もあれば、最初のアイソレートが2つ目のアイソレートの前に実行される可能性や、2つ目が先に実行される可能性もあります。この順番を知ることはできませんが、これは重要なことではありません。 ベースとなるそれぞれのアイソレートがどのように実行されても、依存データはコピーされるため、常に同じ結果が導き出されます。そして、元のnは変化しません。このようにデータが暗黙的にコピーされるということは、複雑な不変データ構造を導入する必要がないということでもあります。つまり、そういった不変データ構造ではなく、大学のコンピュータ・サイエンスの基礎で学んだおなじみの可変データ構造を使うことができます。現実世界のほとんどのソフトウェアでは、可変データ構造が使われています。 アプリケーションでデータ構造を扱わなければならない場合もあります。何メガバイトもあるような巨大なデータ構造を扱うこともあるでしょう。このようなデータ構造は、複数の同時実行エンティティで共有する必要があります。そのような状況では、データのコピーによってシステムのパフォーマンスに悪影響が生じるでしょう。そのためにConcurnasに導入されているのがsharedキーワードです。このキーワードの使用により、アイソレートが「共有状態は存在しない」という条件に違反することが可能になります。このキーワードは、読取り専用の巨大なデータ構造を使う場合や、スレッドセーフであることがわかっている(つまり、並列設定での実行に適している)他のJVM言語のクラスを参照する場合に、優れたオプションとなります。次に例を示します。 shared lotsOfNumbers = x for x in 0 to 100 step 3 //sharedを付けることで、アイソレート間でlotsOfNumbersがコピーされなくなる def filteredDataSet(alist java.util.List<int>, modof int){ x for x in alist if x mod modof == 0 //上記は、リストを走査してリストを返す例 } mod15 = filteredDataSet(lotsOfNumbers, 15)! //mod15 ==> [0, 15, 30, 45, 60, 75, 90] mod16 = filteredDataSet(lotsOfNumbers, 16)! //mod16 ==> [0, 48, 96] 上記のような形でデータを共有する、別のアプローチに、アクターを使う方法があります。アクターは、コード内でマイクロサービスを生成する手段として優れています。アクターは、Javaなどのオブジェクト指向言語における通常のクラスによく似ており、アイソレートに似た単純な計算モデルをエクスポートします。このモデルでは、アイソレート内のすべてのコードがシングルスレッド的に実行されます。これは、アクター内で専用のアイソレートを暗黙的に生成することで行っています。このアイソレートの仕事は、アクターに対して行われた呼出しを実行することのみです。アクターを以下に示します。 actor IdGenerator(prefix String){ //IdGeneratorには、上記のStringを受け取る1つのコンストラクタがある cnt = 0//暗黙的にプライベート状態 def getNextId(){ toReturn = prefix + "-" + cnt cnt += 1 toReturn } } //使ってみ idGen = IdGenerator("IDX")//アクターを作成する anId1 = idGen.getNextId()//==> IDX-0 anId2 = idGen.getNextId()//==> IDX-1 上記で、IdGeneratorのgetNextIdメソッドの呼出しはアクター内の操作であるため、複数のアイソレートがこのメソッドを呼び出しても、IdGenerator内の状態が矛盾する可能性はありません。そのため、複数のアイソレートに同じ値のIDが2回返されることはありません。これがJavaであれば、ロックやsynchronizedキーワードを使って、getNextIdを囲むクリティカル・セクションを明示的に作成する必要があるでしょう。 既存のクラスのアクターを作成することもできます。次に示すのは、HashSetからアクター・サービスを作成する方法です。 setService = actor java.util.HashSet<int>() setService.add(65) 作成できるアイソレートの数は、コードを実行する物理マシンで利用できるメモリの量によってのみ制限されます。この制限値は、JVMで生成できるスレッド数よりもはるかに大きくなっています。そのため、アイソレートによる同時実行モデルは従来型のアプローチよりもはるかにスケーラブルです。また、同時実行によるメリットをソリューションで生かしたいライブラリ作成者は、さらにスレッドを作成することや、必要なスレッドをホスト・プログラムからライブラリに注入することなく、Concurnasによってパラレル処理を実現できます。   Concurnasによるリアクティブ・コンピューティング Concurnasのrefには、アイソレートが状態の変更に関する通知を受信できるという、他にはない特性があります。この方法により、開発者にリアクティブ・コンピューティングの機能がもたらされます。 金融アプリケーションを構築しているとします。このアプリケーションでは、ある資産の相対価格が別の資産の相対価格を上回ったときに、株式市場で1回の取引を開始するものとします。このアプリケーションでは、市場データのフィードを2つ受け取り、最新の状態に対して何らかの計算を行う必要があります。その計算結果に応じて、何らかのアクションを開始する可能性があります。この動作は、everyキーワードを使って実装することができます。 asset1price int:; asset2price int:; every(asset1price, asset2price){ if(asset1price > asset2price){ //...ここで取引アクションを開始する return//後ほど呼び出されるeveryブロックを終了する } } 上記のeveryブロックは、asset1priceまたはasset2priceが更新されるたびに呼び出されます。リアクティブ・コンポーネント自体が、返された値のストリームを保持するrefを返すこともできます。そのため、リアクティブ・コンポーネントを連鎖させることも可能です。次の例をご覧ください。 a int: b int: c = every(a, b){ a + b } every(c){ System.out.println("latest sum: {c}") } 上記のcは、aまたはbが変更されても、その都度aおよびbの合計と等しくなります。このコードには、もう1つのeveryブロックが含まれています。このブロックでcの最新の値を出力しています。 Concurnasにより、こういった種類のリアクティブ・システムを表現するための自然な方法が提供されます。しかし、Concurnasは書きやすく読みやすいコンパクトな言語となることも目指しています。そこで、上記の構文のさらにコンパクトな形を次に示します。 c <= a + b この場合もcは、aまたはbが変更されても、その都度aおよびbの合計と等しくなります。   同時実行の内部処理 Concurnasの同時実行モデルは、継続渡し(continuation passing)を使って実装されています。これは、Project Loomで継続の実装に使われているアプローチととてもよく似ています。アイソレートは実行時に、ベースとなるハードウェアの一定数のスレッドにマッピングされます。この数は、ベースとなるハードウェアで実行に利用できる計算能力(利用できるCPUコアの数など)に基づいて生成されます。 アイソレートがコードの実行を一時停止する必要があるポイントに到達した場合(値がまだrefに割り当てられていないのに、そのrefの参照先を取得する場合など)、そのアイソレートを実行している、ベースとなるワーカー・スレッドのインスタンスに実行が到達するまで、プログラムの現在の実行スタックがコール・チェーンをパッケージングします。この時点で、別のアイソレートが利用できる場合、実行はそのアイソレートに移ります。そうでない場合、実行は、一時停止したアイソレートの実行を再開できるという通知(たとえば、実行を一時停止していたrefに設定された値)を受け取るまで待機します。 このアプローチには、わずかにパフォーマンスのペナルティがあります。実際のところ、アイソレート間で切り替えが頻発するアプリケーションでは、実行時間が5%ほど余分にかかることがわかっています。この値は、このような変換機能にしては十分小さく妥当だと思います。 現在、アイソレートのスケジューリングの仕組みは、ラウンド・ロビンがベースになっています。ただし、Concurnasの言語とランタイム・プラットフォームが進化するにつれて、Java Fork/Joinで公開されているワークスティーリング型スケジューラを活用することや、エンドユーザーが独自のスケジューラを定義できるようになることで、適切にリソースを分配するようになるかもしれません。このスケジューリングの仕組みは、ガベージ・コレクタの選択や微調整のようなもので、ほとんどの開発者にとっては心配する必要のないことです。当然ですが、この機能が非常に有用だと考える開発者もいます。 スタックを巻き戻す、アイソレート間で状態をコピーする、コードを同時実行に適した状態にする、というこのプロセスは、実行時にConcurnasのクラス・ローダーの中で行われます。これにより、他のJVM言語で書かれたコードとの互換性を実現しています。プラットフォームのパフォーマンスは徐々に改善されていきますが、今後のバージョンのConcurnasは、現在コンパイルされているConcurnasのコードとの互換性を備えたものになる予定です。   グラフィックス・プロセッシング・ユニット ほとんどの最新コンピューティング・プラットフォームには、GPUが搭載されています。GPUは大量のデータをパラレルに計算するチップで、SIMD(単一命令複数データ)操作や一般的にCPUの制約を受ける問題を解くのに最適です。 CPUベースのアルゴリズム実装と比較して、GPUではSIMD問題を100倍以上高速に解ける場合があります。さらに、ギガフロップ当たりの消費電力とハードウェア費用も、CPUベースの計算よりかなり少なく済みます。その理由は、最新のCPUとGPUの仕様を見れば明らかです。 たとえば、最高クラスのグラフィックス・カードであるNVIDIA GeFORCE RTX 2080 Tiには、並列に計算を行えるコアが4,352個あります。一方で、最新のCPUのコアは64個でしょう。GPUコアのクロック数はCPUコアよりもかなり小さいですが、適切な計算タスクであれば、CPUの数桁倍に及ぶ、GPUのコンピューティング能力を利用できます。世界最先端のスーパーコンピュータの多くが、専用のGPUハードウェアを使ってパラレル化を行っています。 ただし、よいことばかりではありません。GPUのプログラミングは難しいこともあります。GPUは一部の(とはいっても、かなり範囲は広くなっています)種類のSIMD問題を解くことにのみ最適化されているばかりでなく、開発者にも、ベースとなるハードウェアを深く理解し、Cなどの低レベル言語でハードウェア・プログラミングを行うことが求められてきたのが通常です。 Concurnasでは、GPUプログラミングと、それに関連するパラレル計算構造をビルトインでサポートしています。Concurnasでは、Concurnasらしいコードを書くことにより、GPUを活用して効率的なパラレル計算を行うことができます。これにより、GPUコンピューティングを活用するハードルが大幅に下がります。コードは、GPUのカーネル内で実行されます。カーネルを並列に実行するのは、GPUの個々のコアです。 それでは、カーネルの簡単な例を見てみます。AおよびBという2つの配列の各要素について、y = a**2 + b + 10を計算します。 gpukernel 1 twoArrayOp(global in A float[], global in B float[], global out result float[]) { idx = get_global_id(0) result[idx] = A[idx]**2 + B[idx] + 10 } このカーネルでは、GPUのグローバル・メモリ空間(CPUアーキテクチャのRAMに類似)に存在する3つの配列を操作しています。このコードでは、カーネルが操作する配列が入力配列または出力配列のいずれであるか(混合モードも可能)を指定しています。これにより、コンパイラで生成されるマシン・コードをGPU向けに最適化することができます。なお、カーネルでは、出力変数に結果を書き込むことはできるものの、値を返すことはできないという点に注意してください。get_global_id(0)という呼出しでは、呼び出すコアに対して、配列のどの要素を操作するかを表すコンテキストを提供しています。コードでは、GPUを特定し、そのGPUにデータをコピーする必要があります。 //GPUデバイスの選択... device = gpus.GPU().getGPUDevices()[0].devices[0] //このGPUにサイズが10である3つの配列を作成、2つは入力用 inGPU1 = device.makeOffHeapArrayIn(float[].class, 10) inGPU2 = device.makeOffHeapArrayIn(float[].class, 10) //1つは出力用 result = device.makeOffHeapArrayOut(float[].class, 10) //GPU上の配列に書き込む c1 := inGPU1.writeToBuffer([ 1.f 2 3 4 5 6 7 8 9 10]) c2 := inGPU2.writeToBuffer([ 1.f 2 1 2 3 1 2 1 2 1]) writeToBufferはコピー操作で、ref(c1とc2)を返します。この操作は非同期であるため、これが実行されている間も、コードでは他の操作を同時に実行できます。後続のGPU操作がこのrefを引数として受け取って、GPU上に操作のパイプラインを構築することができます。スループットが重要になる高パフォーマンス・コンピューティング環境では、GPUでのデータ処理と並行して、GPUとの(他の)データのやり取りを行うことが最善です。 Concurnasでは、GPUをコプロセッサとしてモデリングしています。そのため、GPUの操作は非同期式に行われ、システム内の他のアイソレートと並列に実行されます。 次に、twoArrayOpカーネルへのハンドルを作成します。 inst = twoArrayOp(inGPU1, inGPU2, result) compute := device.exe(inst, [10], c1, c2)//10コアでtwoArrayOpの処理を実行 ret = result.readFromBuffer(compute) twoArrayOpカーネルに境界付きの入出力変数を渡すと、instハンドルが得られます。次に、このハンドルをデバイスに渡し、exeメソッドで実行しています。このコードでは、GPUの実行コア数と、特定のrefも指定しています。特定のrefとは、そのrefが完了するまで実行を待機しなければならないものです。今回の場合、該当するrefはc1およびc2です。この2つは、GPUにデータをコピーする操作に対応します。これは非同期操作で実行されるため、返されるcomputeもrefになります。最後に、computeが完了してから、結果バッファresultから計算結果を読み取っています。 ハードウェア・モデルによっては、GPUでグローバルおよびローカルの専用メモリ空間を利用できる場合があります。空間的局所性が高いアルゴリズム(行列の乗算など)を開発する場合、ローカル・メモリを使うことで、グローバル・メモリを使った場合よりもパフォーマンスが何桁も向上する場合があります。Concurnasでは、GPUの各種メモリ空間もサポートしています。 GPUから最高のパフォーマンスを引き出すためには、GPU計算の細部をある程度理解しておく必要があります。しかし、Concurnasを使うことで、このプロセスは大幅に簡略化されます。以上の機能はすべて、ConcurnasのGPUカーネル・コードをOpenCL Cにトランスパイルすることで実現されています(トランスパイルとは、ある言語で書かれたコードを受け取り、別の言語に変換することを指します)。OpenCLはGPU計算用のマルチプラットフォームAPIで、AMD、Intel、NVIDIAという三大GPUメーカーがサポートしています。トランスパイルしたコードは、アノテーションを使ってコンパイル済みのクラス・ファイルにアタッチし、実行時にGPUに渡します。この仕組みによって、移植性を実現しています。   マルチパラダイム・プログラミング Concurnasはマルチパラダイム言語です。つまり、命令型、関数型、オブジェクト指向、リアクティブ・プログラミングの各パラダイムから、もっともよく使われている最適な側面だけを選ぶことができます。 オブジェクト指向プログラミングと言えば、Concurnasではクラスを定義するコンパクトな仕組みを提供しています。多くのデータ指向クラスを1行のコードで表現できます。次の例をご覧ください。 abstract class UniversityMember(~enrolled boolean) class Person(~firstname String, ~surname String, ~yob short, enrolled = false) < UniversityMember(enrolled) //インスタンス・オブジェクトを作成 p1 = new Person("dave", "brown", 1970) p2 = Person("sandy", "smith", 1978, true)//newキーワードの使用はオプション クラス名の後の括弧内にあるのはフィールド定義です。この定義では、フィールドを設定できるコンストラクタを自動生成するように、Concurnasに伝えています。enrolledフィールドにはデフォルト値false(さらに、型はBooleanであると推論されます)があり、Personクラスのインスタンスを作成する際にオプションでこのフィールドに値を指定できる点に注意してください。上記の例のPersonはUniversityMemberを継承しています。継承は、extendsまたは<キーワードを使うことで表します。Personは、入力パラメータの1つをUniversityMemberに渡すことで、UniversityMemberのスーパーコンストラクタを満たします。そしてこの入力パラメータは、Personクラスのフィールドとしては作成されません。 Concurnasでは、先頭にチルダ(~)文字が付いているすべてのフィールドについて、getterとsetterが自動生成されます。ちなみに、マイナス記号(-)の場合はgetterのみ、プラス記号(+)の場合はsetterのみが生成されます。次に例を示します。 p1 = new Person("dave", "brown", 1970) name = p1.firstname //equivalent to: name = p1.getFirstname()と同じ p1.firstname = "newName" //equivalent to: pe.setFirstname("newName")と同じ Concurnasでは、多くのモダンJVM言語と同じように、等価演算子(==)はほとんどの開発者の想定どおりに動作します。つまり、参照の等価性ではなく値の等価性を評価します。次のコードは有効です。 p1 = Person("dave", "brown", 1970) p2 = p1@  //p1のディープ・コピー assert p1 == p2 //オブジェクトは異なるが値は同じ assert not (p1 &== p2) //参照の等価性は&== 上記のコードでは、@コピー演算子を使っている点にも注意してください。この演算子では、左辺で参照しているオブジェクトのディープ・コピーを行います。 Concurnasでは、すべてのクラスについて、hashcodeメソッドとequalsメソッドが自動生成されます。こうすることによって、セットやマップなどの一般的なデータ構造でそれらのメソッドを簡単に使えるようにしてます。次の例をご覧ください。 p1 = new Person("dave", "brown", 1970) myset = set() myset.add(p1) p2 = new Person("dave", "brown", 1970) assert p2 in myset 前述のように、Concurnasではタプルをサポートしています。さらに、継承による排他的なクラス作成とは対照的に、合成によってクラスを作成できるトレイトもサポートしています。 Concurnasには、以前から関数型プログラミング言語に見られる多くの機能も含まれています。たとえば、メソッド参照や部分関数です。 //関数の参照 funcRef (int, int) int = plus&(int, int) //通常の関数のように呼び出す result = funcRef(2, 3) //=> 5 //部分関数 plusone (int) int = plus&(int, 1) //これも通常の関数のように呼び出す result = plusone(10) // ==> 11 さらに、パターン・マッチングがサポートされています。パターン・マッチングはJavaのswitch文に似ていますが、それよりもはるかに強力な仕組みです。 def matcher(an Object){     match(an){         Person(yob < 1970) => "Person.Born: {an.yob}" //Person型内部のガード・チェック・フィールド         Person => "A Person" //Person型かどうかをチェック         int; < 10 => "small number" //intかどうかをチェック、値が10より小さいことを求めるガード         int => "another number"         x => "unknown input" //その他すべての入力に一致     } } res = matcher(x) for x in [Person("dave", "brown", 1829), Person("dave", "brown", 2010), "oops", 43, 5] //res ==> [Person.Born:1829, A Person, unknown input, another number, small number] パターン条件は、最初から最後に向かう順番でチェックされます。型とのマッチングと、ガード(オプション)もサポートされています。ガードは、オブジェクトのフィールドに対して指定することもできます。 Concurnasで提供されている、関数型プログラミングのその他の側面には、先ほど取り上げた型推論や、ラムダ式、遅延評価などがあります。   データの操作 データ・サイエンスや数値計算のみならず、ほとんどのエンタープライズ・ソリューションでは、何らかの形でデータを操作します。これを実現するため、Concurnasでは一般的なデータ構造を操作する強力な方法を提供しています。それでは、いくつかのデータ構造を定義してみます。 anArray = [1 2 3 4 5 6] aList = [1,2,3,4,5,6] aMatrix = [1 2 3 ; 4 5 6] aMap = {"one" -> 1, "two" -> 2, "three" -> 3} サポートされている操作には、以下のようなものがあります。 cont     = "one" in aMap //マップ内の値をチェック longNames = aMap[key] for key in aMap if key.length() > 3 //マップのキーを使った反復 arrayValue = anArray[2]     //配列の個々の値 subarray   = anArray[4 ...] //サブ配列、[5 6]   Concurnasでは、リストの走査をサポートしています。これは実質的にforループのシンタックス・シュガーで、次の例のi mod 2 == 0のようなガード文を追加できます。 ret = i+10 for i in aList if i mod 2 == 0 Concurnasでは、ベクトル化もサポートしています。ベクトル化とは、リストまたはn次元配列の各コンポーネントに対して同じ要素単位の操作を行う場合に適した技術です。たとえば、行列の各要素xに対してy = x**2 + 1を計算したい場合、次のようにすることができます。 mat = [1 2 ; 3 4] mat2 = mat^*2 + 1 //==> [3 5 ; 7 9] mat^^*2 + 1 //インプレース・ベクトル化操作 数値の範囲は、第一級オブジェクトとしてサポートされています。値は遅延作成されるため、無限の範囲を作ることも可能です。 numRange = 0 to 10           //[0, ..., 10]の範囲 tepRange = 0 to 10 step 2    //[0, 2, ..., 10]の範囲 revRange = tepRange reversed //上記を逆順にした範囲([10, 8, ..., 0]) decRange = 10 to 0 step 2    //[10, 8, ..., 0]の範囲 infRange = 0 to              //[0,... ]の無限列 steInfRa = 0 to step 2       //[0, 2, ... ](2ずつ増加する無限列) decInfRa = 0 to step (-1)    //[0, -1, ... ](1ずつ減少する無限列) val = x for x in numRange    //範囲のリスト走査 check = 2 in numRange        //値の存在をチェック   Concurnasのインストール この言語を使う前に、Java JDK 1.8以降をインストールする必要があります。その後、Concurnasをインストールできます。 Concurnasのダウンロード・サイトでは、Windows用のインストーラと、他のプラットフォーム用のzipファイルが公開されています。なお、LinuxではSDKMAN!SDKマネージャで、次のコマンドを使用してConcurnasをインストールする必要があります。 $ sdk install concurnas JDKとConcurnasをインストールした後は、hello.concという名前で簡単な「hello world」アプリケーションを作ってみます。 for(x in 1 to 5){ System.out.println("hello world {x}")! } Concurnasは、Javaなどの言語と同じく、コンパイルと実行という標準的な2段階のプロセスに従います。hello.concコード・ファイルを保存したら、conccコマンドでコードをコンパイルすることができます。次に例を示します。 $ concc hello.conc コンパイルしたコードは、concコマンドで実行できます。 $ conc hello hello world 1 hello world 2 hello world 5 hello world 4 hello world 3 並列に実行されるため、実際の出力は上記の例とは異なるかもしれません。 コンパイルと実行という2段階のアプローチとは別に、Concurnas Read-Evaluate-Print-Loop(REPL)を使うこともできます。REPLはまさにJavaのJShellのように動作し、引数のないconcコマンドを実行して起動することができます。 $ conc Welcome to Concurnas 1.14.020 (Java HotSpot(TM) 64-Bit Server VM, Java 11.0.5). Currently running in REPL mode.For help type: /help conc>   これで、次のJVMプロジェクトでConcurnasの力の活用を始める準備が整いました。   まとめ 本記事では、Concurnasプログラミング言語の機能や、Concurnasで最新のCPUおよびGPUハードウェア・アーキテクチャを使用して同時実行パラレル・システムを構築する方法について紹介しました。また、現在特に人気のあるプログラミング・パラダイムに基づいたさまざまなConcurnasの機能を確認し、Concurnasがデータ操作や他のJVM言語で書かれた既存コードの活用に理想的な言語であることを示しました。この言語の詳細を確認したい方は、まずConcurnasのWebサイトを見てみるとよいでしょう。 Jason Tatton Jason Tatton(@concurnas):Concurnasプログラミング言語の作成者で、Concurnas Ltdの創業者。バンク・オブ・アメリカ、メリルリンチ、ドイツ銀行、J.P.モルガンなど、世界屈指の投資銀行に向けたアルゴリズム取引システムの開発担当およびチーム・リーダーを経験。テクノロジーとプログラミング、そしてConcurnasをできるかぎり最高のプログラミング言語にすることに情熱を燃やしている。

※本記事は、Jason Tattonによる"Concurrent programming with Concurnas"を翻訳したものです。 現在の開発者が利用できる今までにないハードウェアの力。新しいJVMプログラミング言語Concurnasでその力を解放する方法に迫る 著者:Jason Tatton 2020年6月22日 並列プログラムを書くのは難しい作業です。しかし、現在の最新マルチコアCPUアー...

Java Magazine June 2020

最新記事  Concurnasによる並列プログラミング JVMをターゲットとした新しいプログラミング言語Concurnasについて探ります。この言語を開発したJason Tatton氏が、現在のマルチコアCPUやGPUハードウェア・アーキテクチャを活用した同時実行パラレル・システムを構築する方法について解説します。 Jakarta EEで始める並列プログラミング 同時実行について言えば、うまく書かれたアプリケーションの要の1つは優れたパフォーマンスで、Jakarta EEプラットフォームでは、Jakarta Concurrency APIを使うことになります。Java ChampionおよびJakarta EE AmbassadorのJosh Juneau氏が、Jakarta Concurrency APIや、このAPIとJava SEのjava.util.concurrentとの違いに関し、順を追って説明します。  Optionalクラスを意図されたとおりに使うための12のレシピ Java ChampionおよびJava GroundbreakerのMohamed Taman氏がOptionalクラスを完全解説します。このクラスは、「結果なし」を明確に表す方法が必要であって、NULLを使うと圧倒的にエラーが起こりやすくなる場合に、ライブラリ・メソッドの戻りタイプ用の限定的な仕組みを提供することを意図したものでした。   最近の記事 Javaの偉大なアプリ25選 宇宙探査からゲノミクスまで、また、リバース・コンパイラからロボティック・コントローラまで、Javaは現在の世界の中核を担っています。Alexa Weber Moralesが、無数のJavaアプリの中から、傑出したいくつかのアプリを紹介します。 Java 14、Java 15、Java 16、およびそれ以降でのプレビュー機能の役割 多くの場合、Java SEの新リリースには「本番環境で使用する準備が整っていない」JEPが含まれています。これには、Java言語の新機能のプレビューや、HotSpot JVMの試験運用版機能、さらには、新しいAPIとなる可能性があるもののインキュベーション・テストが含まれます。David Delabasséeが解説します。 Javaに演算子オーバーロードを導入すべきときが来たのか 演算子オーバーロードは一風変わった言語機能の1つで、賛否が両極端に分かれます。強く否定する意見があるのもうなずけます。その利用方法が適切でなければ、たちまちコードはわかりにくくなり、さらに厄介なバグが生まれることになるからです。それでは、強く支持する側からはどのような意見があるでしょうか。Mahmoud Abdelghany氏が考えを述べています。    Java Magazineサブスクリプションのおすすめ  Java Magazineでは、JavaとJVMについて徹底解説しています。エキスパートやJava開発チームのメンバーが執筆した、Javaの言語やプラットフォームについての詳しい説明が満載です。 25万人のサブスクライバに加わると、役に立つ確かなプログラミング情報を皆さんの受信ボックスに直接お届けします。  

最新記事  Concurnasによる並列プログラミング JVMをターゲットとした新しいプログラミング言語Concurnasについて探ります。この言語を開発したJason Tatton氏が、現在のマルチコアCPUやGPUハードウェア・アーキテクチャを活用した同時実行パラレル・システムを構築する方法について解説します。 Jakarta EEで始める並列プログラミング同時実行について言えば、うまく書かれたアプ...

クイズに挑戦:オーバーロードされたメソッドの作成と呼出し(中級者向け)

.button { background-color: #759C6C; color: white; border: 2px solid #759C6C; border-radius: 12px; padding: 5px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; margin: 4px 2px; cursor: pointer; } function answer1() { alert("This answer is incorrect. "); } function answer2() { alert("This answer is incorrect. "); } function answer3() { alert("This answer is correct, but it is not the only correct answer. "); } function answer4() { alert("This answer is correct, but it is not the only correct answer. "); } function answer5() { alert("This answer is incorrect. "); } ※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz Yourself: Creating and Invoking Overloaded Methods (Intermediate)"を翻訳したものです。 条件演算子の使い方を理解するためのJava SE問題 著者:Simon Roberts、Mikalai Zaikin 2020年4月28日 | 本記事をPDFでダウンロード その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 このJava SEに関する設問では、オーバーロードされたメソッドの作成と呼出しを取り上げます。 次のクラスについて: public class Logger { static void log(Object o) { /* code */ } // line n1 static void log(Long[] lwa) { /* code */ } // line n2 public static void main(String[] args) { log(new Integer[]{}); long[] arr = null; log(arr); log(null); } }   それぞれの変更が独立して行われると仮定した場合、正しい文はどれですか。2つ選んでください。 A. このコードのコンパイルは成功する B. 次のメソッドを新しく追加すれば、このコードはコンパイルできるstatic void log(Integer[] iwa) { /* code */ } C. line n1をコメント・アウトすれば、このコードはコンパイルできる D. line n2をコメント・アウトすれば、このコードはコンパイルできる   解答:選択肢Aは、何もしなくてもコードはコンパイルできることを意味しています。そうなるためには、2つのオーバーロードされたメソッドが有効なオーバーロードであることと、すべての呼出しが曖昧さを残すことなく、いずれかのオーバーロードに解決されることが必要です。当然ながら、その他の構文的な問題があってはなりません。 「その他の構文的な問題」に関して言えば、このコードに問題はありません。次に、前述の条件の1つ目について検討します。2つのオーバーロードされたメソッドは、引数リストの型シーケンスが異なっています。片方は1つのObject、もう片方は1つの配列です。この違いがあれば、これらのオーバーロードは十分に共存できます。このルールは、2つの場所にまたがって記載されています。 Java言語仕様のセクション8.4.9「オーバーロード」には、「同じ名前でシグネチャが異なり、オーバーライド等価でない2つのメソッドがクラス内に存在する場合、メソッド名がオーバーロードされていると言う」と書かれています。また、Java言語仕様のセクション8.4.2「メソッドのシグネチャ」では、オーバーライド等価性が説明されています。 この説明は多少回りくどいですが、次のように言い換えることができます。ジェネリクス型パラメータの型消去後の型シーケンスが同じである場合、メソッドはオーバーライド等価です。今回の場合、ジェネリクスは使われておらず、型(ObjectとLongラッパーの配列)は明らかに違うものであるため、オーバーロード自体は有効です。 2つ目のポイントは、オーバーロードされたメソッドの呼出しの曖昧性排除に関することです。この点についても、コードは有効です。ただし、細かい部分について言えば、すべてが予想どおりに動作するというわけではないかもしれません。どの呼出しによってどのメソッドが呼び出されるのかについて考えてみます。そのために、Java言語仕様のセクション15.12.2「コンパイル時の手順2:メソッド・シグネチャの決定」を参照します。ここには、オーバーロードの解決について書かれています。 まず、log(new Integer[]{})という呼出しは、Longの配列を受け取るメソッドに一致することはありませんが、Object引数型には一致します。intはlongに代入できることから、Long[]引数のメソッドが呼び出されると考える方もいるかもしれません。しかし、2つの理由により、そうはなりません。IntegerはLongに代入できないため、この呼出しはうまくいきません。なぜなら、これらのクラスは兄弟クラスであり、親子関係はないからです。しかし、本当の理由は、Javaはさまざまなプリミティブ変換ができるものの、配列の内容の型をこのような形で変更することはできない点にあります。 現在のところ、Javaでは配列のオートボクシングが行われません。そのため、2回目の呼出しであるlog(arr)でも、Objectのパラメータを持つメソッドが選ばれます。 次に、log(null)という呼出しについて見てみます。これは特殊なケースで、驚きに値するかもしれません。コンパイラでは、オーバーロードされたメソッドの中から、コール元の引数型(この場合はnull)を受け取るものを探します。次に、条件を満たすメソッドが複数見つかった場合、コンパイラでは「もっとも具体的な」ものを探します。今回の場合、両方のメソッドが該当します(nullは、プリミティブ以外のどんな型にも適合します)。しかし、Long[]の方が具体的であるため、Object引数よりも優先されます。Objectはすべてのスーパータイプであることから、存在し得るものの中でもっとも一般的な形式であることを覚えておいてください。 結論として、選択肢Aは正解となります。コードはこのままで本当にコンパイルできます。 選択肢Bでは、Integer[]型を引数として受け取る新しいメソッドを追加しようとしています。この追加自体は問題ありません。log(new Integer[]{});という呼出しが、厳密に一致したターゲットを持つようになります。しかし、log(null)が、具体性の点で等しい2つのターゲット・メソッドに一致するようになるため、この行でコンパイルに失敗します(いずれもObjectよりは具体的ですが、両者の具体性に差があるわけではありません)。したがって、コンパイラでは2つのメソッドから1つを選ぶことができないため、メソッド追加後のコードはコンパイルできません。 オーバーロードされたメソッドの宣言が有効でも、呼出しが曖昧であれば、その呼出しはコンパイルできないと覚えておくことが重要です。このことに特に驚いてしまうのは、新しいメソッド1つの追加によって、それまで有効だったメソッド呼出しが有効でなくなる場合でしょう。したがって、選択肢Bは誤りです。 選択肢Cは、log(Object o)メソッドの削除を意味しています。削除した場合、引数がlong[]とInteger[]であるlogの呼出しの有効なターゲットがなくなってしまいます。これらの呼出し先がなくなってしまうため、この変更により、コードはコンパイルできなくなります。以上より、選択肢Cは誤りであることがわかります。 選択肢Dは、log(Long[] lwa)の実装のコメント・アウトを意味しています。コメント・アウトした場合、3回の呼出しのターゲットすべてがlog(Object o)メソッドになります。java.lang.Objectには、nullリテラルを含め、プリミティブ以外のすべてを代入できるからです。この点から、変更後のコードはコンパイルできるため、選択肢Dは正解です。 オーバーロードされたメソッドは、常にコンパイラが引数の型に基づいて選択します。引数の値(nullになる可能性があります)は重要ではありません。そのため、次の2つの呼出しでは、確実に同じターゲット・メソッドが選ばれるでしょう。ただし、log(null)の呼出しでは、違うターゲットが呼び出されるかもしれません。 long[] arr1 = null; log(arr1); long[] arr2 = new long[]{1L}; log(arr2); 正解は選択肢AとDです。 Simon Roberts Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO'Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。   Mikalai Zaikin Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。

※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz Yourself: Creating and Invoking Overloaded Methods (Intermediate)"を翻訳したものです。 条件演算子の使い方を理解するためのJava SE問題 著者:Simon Roberts、Mikalai Zaikin2020年4月28日 |...

クイズに挑戦:switch文とcase句の使用(中級者向け)

.button { background-color: #759C6C; color: white; border: 2px solid #759C6C; border-radius: 12px; padding: 5px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; margin: 4px 2px; cursor: pointer; } function answer1() { alert("This answer is incorrect. "); } function answer2() { alert("This answer is incorrect. "); } function answer3() { alert("This answer is correct, but it is not the only correct answer. "); } function answer4() { alert("This answer is correct, but it is not the only correct answer. "); } function answer5() { alert("This answer is incorrect. "); } ※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz Yourself: Using Switch and Case Statements (Intermediate)"を翻訳したものです。 条件演算子の使い方を理解するためのJava SE問題 著者:Simon Roberts、Mikalai Zaikin 2020年4月21日 | 本記事をPDFでダウンロード その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 if、if/else、switchなどのJavaの制御文を使いたいと思います。 次のクラスについて: public class WeirdSwitch {     public static void main(String[] args) {        byte b = 3;        int i = 0;        switch(b) {            case 3 | 4 : i = i + 4; // line n1            case 2 | 3 : i = i + 2;         }        System.out.println(i);     }     }   どのような結果になりますか。1つ選んでください。 A. 0 B. 2 C. 4 D. 6 E. Compilation fails at line n1.   解答:設問のコードで一番特徴的な部分は、case句で縦棒文字(|)を使っているところでしょう。そしてもちろん、コードの動作を正しく評価するうえで鍵となるのもこの部分です。縦棒文字は、一部の言語(Scalaなど)では代替一致リストを作るために使われます。しかし、Javaでの動作は異なり、単一の縦棒演算子はビット単位のOR演算を表します。そのため、3 | 4という式と2 | 3という式の効果は非常に明確で、それぞれが1つの値です。 caseラベルが何を表しているかを理解するためには、数値の背後にあるバイナリ表現を調べ、ビット単位のOR演算の仕組みを確認する必要があります。2、3、4のバイナリ表現を表1に示します。 表1:10進表現とバイナリ表現  Javaには、接頭辞0bを使うバイナリ・リテラル形式があることを思い出してください。そのため、これらの数値はJavaのバイナリ・リテラルで直接表現でき、それぞれ0b0010、0b0011、0b0100となります。 ビット単位のOR演算では、対応する列において、いずれかのオペランドのビットが1であれば、結果のビットは1になります。つまり、3 | 4は次のようになります。 0011 0100 ------ 0111 0b0111は、10進表現では7です。 同じように、2 | 3も次のように計算できます。 0010 0011 ------ 0011 この結果が3になることに注意してください。 定数式3 | 4および2 | 3の値を、それぞれの式を実際に評価した値で置き換えたコードは、次のようになります。 byte b = 3; int i = 0; switch(b) {     case 7 : i = i + 4; // line n1     case 3 : i = i + 2;  } ここまで来れば、コードの動作は明らかです。3は7と等しくないため、最初のcaseには一致しません。しかし、2つ目のcaseには一致し、iの値に2が加算されます(その結果、2になります)。これでswitchブロックは終了し、画面に2が出力されます。ここから、コードは問題なくコンパイルでき、2が出力されることがわかります。したがって、選択肢Bが正解で、選択肢A、C、D、Eは誤りです。 選択肢Cは誤りであることがわかりましたが、少し補足しておく必要があるでしょう。選択式の試験において、誤答の選択肢は一般的に、よくある誤解をしている場合や間違いを犯している場合に正しいと考えてしまうかもしれないものの代表例となるように決められていることは考慮に値します。 選択肢Cを考えるにあたっては、このコードから4が出力される可能性はない点が重要になります。その理由となるのは、Javaのswitch文のフォールスルー動作です。この構造にはbreak文がないため、コードで4が加算される場合(最初のcaseパターンに一致した場合)、そのまま継続されて、2が加算されるコードも実行されます。そのため、このコードで4が出力されることはあり得ません。iの値は、6、2、または0になります。もちろん、breakの動作(とJavaではこの文が必要になること)を知らない人がこの設問に答えようとした場合は、4が出力される可能性があるという思い違いをしてしまうかもしれません。 正解は選択肢Bです。 Simon Roberts Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO'Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。   Mikalai Zaikin Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。

※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz Yourself: Using Switch and Case Statements (Intermediate)"を翻訳したものです。 条件演算子の使い方を理解するためのJava SE問題 著者:Simon Roberts、Mikalai Zaikin 2020年4月21日 | 本記事をPDFでダウンロード その他の設...

クイズに挑戦:Java演算子の使用(中級者向け)

.button { background-color: #759C6C; color: white; border: 2px solid #759C6C; border-radius: 12px; padding: 5px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; margin: 4px 2px; cursor: pointer; } function answer1() { alert("This answer is incorrect. "); } function answer2() { alert("This answer is incorrect. "); } function answer3() { alert("This answer is correct, but it is not the only correct answer. "); } function answer4() { alert("This answer is correct, but it is not the only correct answer. "); } function answer5() { alert("This answer is incorrect. "); } ※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz Yourself: Use Java operators (Intermediate)"を翻訳したものです。 条件演算子の使い方を理解するためのJava SE問題 著者:Simon Roberts、Mikalai Zaikin 2020年4月21日 | 本記事をPDFでダウンロード その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 このJava SEに関する設問では、Javaの演算子、特に条件演算子の使い方について取り上げます。 次のコードについて: public class Tester { public static String test(byte b) { return "Byte "; } public static String test(char c) { return "Char "; } public static String test(int i) { return "Int "; } public static void main(String[] args) { byte b = 0; char c = 'A'; System.out.print(test(true ? b : c)); System.out.print(test(false ? b : c)); System.out.print(test(true ? 0 : 'A')); System.out.print(test(false ? 'A' : (byte)0)); } } A. Byte Char Int Byte B. Int Int Int Int C. Int Int Char Int D. Int Int Char Byte   解答:この設問は、試験対象の「Javaの演算子の使用」に分類されるでしょうが、変数の初期化の問題に密接に関連した知識とも関係があります。というのも、リテラル式が登場するコンテキストによってその実質的な型がどう変わる可能性があるのかについても知っていなければ、設問の解答にはたどりつけないからです。他にも、いくつか注意しなければならないことがあります。 おそらく、この設問は実際の試験問題よりもかなり難しいものです。 実際にこのように書かれたコードを目にした場合、ほぼ確実に、もう少しわかりやすい形へと書き換えるはずです。この点については、後ほど詳しく説明します。 条件演算子(三項演算子と呼ばれることもあります)になじみがない方のために、簡単に解説します。この演算子は、3つの式を受け取ります。最初の2つはクエリー(?)で、2つ目と3つ目はコロン(:)で区切られています。これによってできるのは、if/elseの効果を持つ式です。つまり、次のような式では、式aがboolean(またはBoolean)型である必要があります。 a ? b : c aの値がtrueである場合、式全体の値はbに、そうでない場合はcになります。ifとelseの両方で同じ変数に違う値を1回代入するif/elseがある場合、条件演算子は特に便利です。 つまり、次の式 String message = isMorning(theTime) ? "Good morning" : " Good afternoon" は、次のコードと同じ効果を持ちます。 String message; if (isMorning(theTime)) { message = "Good morning"; } else { message = "Good afternoon"; } この使用例からわかるのは、選ばれる可能性がある2つの値の両方に、結果を代入する変数との代入互換性が必要であることです。確かに、より一般的な場合を考えれば、2つの型には何らかの共通性があるはずです(共通しているのがObjectのみの場合も含みます)。また、スタイルの観点から言えば、2つ目と3つ目のオペランドは同じ型にするのが賢明でしょう。引数がオブジェクト型である場合、自然とそうなります(上記の例はそうなっています)。しかし、プリミティブの場合は、2つ目と3つ目の場所に登場する型が異なる可能性が高くなるかもしれません。式の中でプリミティブが混在している場合、昇格によって最終的な結果が変わることもあります。 背景の説明はこのくらいで十分です。次は、設問の詳細を見てみることにします。ここで、考察が必要になるかもしれない点がいくつかあります。 条件演算子のboolean引数は条件式全体の型にそもそも影響するのか どのようにしてオペランドの型から条件演算子の型を決めることができるのか 異なるプリミティブ引数型でオーバーロードされたメソッドが複数存在する中から、呼び出されるメソッド1つはどのようにして選ばれるのか コンテキストの中でリテラル値の型はどのようにして決まるのか   当然ながら、Javaには、演算子に渡されるオペランドの型の組み合わせについて、その結果の厳密な型(つまり代入互換性)を決める方法を定めたルールがあります。通常、このルールは十分シンプルなものですが、条件演算子には一般的な場合とは若干異なる特殊なルールが定められています。条件演算子とそのルールは、Java言語仕様、Java SE 11版のセクション15.25に詳しく書かれています。 この説明は気が遠くなるほど長いですが、条件演算子の結果の型はBoolean条件の値には影響されないと書かれています。これは、Boolean条件が定数であるため、コンパイラがわかっている場合にも当てはまります。そのため、コードのBooleanリテラルは無視して問題なく、2つ目と3つ目のオペランドの組み合わせにのみ注目すればよいことになります。 次に、条件式の型についての疑問は置いておき、オーバーロードされたメソッドと、どのような条件でどれが呼び出されるかについて考えてみます。出力、つまり正解は、4回のprint呼出しのそれぞれでどのメソッドが呼び出されるのかに大きくかかっています。ありがたいことに、このコンテキストでは、オーバーロードされたメソッドの中から、呼び出すメソッドを選ぶルールもかなりシンプルに表現できます。この問題の最初のポイントは、オーバーロードされたメソッドが存在する場合、ターゲットはコンパイル時に選ばれることです。2つ目のポイントは、簡単にまとめれば、引数型がもっとも適合するものが選ばれてターゲット・メソッドになると言えることです。それでは、次のような1つのメソッドがある場合について考えてみます。 void doStuff(int x) {} 上記のメソッドは、次に示す形式の呼出しの有効なターゲットです。 short s = 99; x.doStuff(s); この場合、shortの引数はintに昇格することになります。ここで、次のような2つ目のメソッドを追加するとします。 void doStuff(short x) {} すると、1つ目のメソッドよりもこの2つ目のメソッドが優先されて選ばれることになります。short型の引数sはintに昇格可能ですが、厳密に同じ型の引数をとるメソッドを選んだ方が適合度は高いからです。別の状況では、必要とする、引数の型の変更が最低限で済むメソッドが選ばれる場合もあります。 オーバーロードされた複数のメソッドから1つを選ぶ方法の詳細については、Java言語仕様、Java SE 11版のセクション15.12.2にすべて書かれています。このように細かい部分も重要です。しかし、今回のメソッドにはジェネリクスや可変長引数リスト、オートボクシングやアンボクシングは使われていないため、この設問では、複雑になるそのような材料には触れません。 ここまでの説明で、オーバーロードされたtestメソッドのうちどれが呼び出されるか(したがって、どのメッセージが出力されるのか)を決定するために必要となる極めて重要な情報は、4つの条件式の型のみであることがわかっています。 4つの条件式のオペランドは、次のとおりです。 1つ目:byte変数とchar変数 2つ目:byte変数とchar変数 3つ目:int型の定数式とcharリテラル 4つ目:charリテラルとbyte式 前述のように、これらの組み合わせ方を定めたルールがあります。今回の場合は、広く適用できるルールと、条件演算子にのみ適用される特殊なルールが存在します。まずは、一般的なルールについて確認します。 Java言語仕様、Java SE 11版のセクション5.6.2(箇条書きの2番)に、二項演算子には「プリミティブの拡大変換」のルールが適用されると書かれています(条件演算子は二項演算子ではなく、三項演算子であることに注意してください。そのため、今回の設問で重要になるポイントすべてがこのルールに記述されているわけではないことがわかります)。このルールはかなりシンプルで、次のことが述べられています。 いずれかのオペランドがdouble型であれば、もう片方はdoubleに変換される そうでない場合、いずれかのオペランドがfloat型であれば、もう片方はfloatに変換される そうでない場合、いずれかのオペランドがlong型であれば、もう片方はlongに変換される そうでない場合、両方のオペランドがintに変換される 条件演算子に適用されるのが上記のルールであるとすれば、4つ目のルールが設問の4つの式すべてに当てはまることは明らかです。その結果、出力はInt Int Int Intになるでしょう。しかし、適用されるルールはこれだけではありません。 条件演算子に適用される正確なルール・セットは、Java言語仕様、Java SE 11版のセクション15.25に書かれており、多くのページにわたっています。設問の1つ目、2つ目、4つ目の式を吟味すれば、これらの組み合わせに対して示されているルールは、実際には先ほど説明した動作と同じになることがわかります。仕様には、型はbnp(byte,char)の結果となると書かれています。 bnp(二項数値昇格という意味です)を評価するとintが返され、その結果、1つ目、2つ目、4つ目の式でテキストIntが出力されます。 続いて、3つ目の組み合わせについて吟味してみます。この場合、条件式のルールが「最低でもint」になるとは限らないことと、リテラル式に関するルールから、この式の結果の型は実際にはcharになることがわかります。これは、Java言語仕様、Java SE 11版の表15.25-Aとその補足説明から判断できます。この表で、intリテラルとcharの組み合わせの効果を示す欄に、その結果の型はchar | bnp(int,char)であると記載されています。つまり、リテラルがcharのデータ・サイズに収まる場合、結果はcharになります。そうでない場合は、通常どおり昇格が行われた結果(つまりint)になります。厳密に言えば、このルールは「int型の定数式」に適用されます。リテラルと定数式はまったく同じではありません。定数式の場合、コンパイラが完全に評価できるものであれば、追加の式が含まれていても構いません。 この要素のintリテラルはゼロであり、charで表現できる範囲内に完全に収まるため、結果の型はcharになります。また、全体の出力はInt Int Char Intになり、選択肢Cが正解となります。 4つ目の式にも、同じロジックが適用されるように見えるかもしれません。しかし、この場合、キャスト式(byte)0はリテラルの扱いにはなりません。単なるbyte型の式として扱われます。その結果、通常の二項昇格が行われ、結果の型はintになります。 注目すべきもう1つのポイントは、リテラル値と型の間のやや特殊な関係がどのようになっているかです。これがもっともよくわかるのは、変数の宣言と同時に初期化を行う場合です。たとえば、単純なルールしかない(Java 1.0当時はそうでした)とすれば、次のコードは無効でしょう。 short x = 1; // Java 1.0では無効、現在は有効 これが無効なのは、1はintリテラルであり、intにはshortとの代入互換性がないからです。しかし、Javaはすばやく変更され、ターゲット変数でリテラル値を問題なく表現できる場合、この代入(およびこれに類似した代入)が認められるようになりました。同じように興味深いと思われるのは、float変数の初期化にはこれが適用されないことです。そのため、次のコードは(まだ)無効です。 float pi = 3.14; この初期化が無効なのは、浮動小数点形式のリテラルがdoubleであり、floatではないからです。この場合、引数が範囲内に収まるかどうかは関係ありません。 正解は選択肢Cです。 Simon Roberts Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO'Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。   Mikalai Zaikin Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。

※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz Yourself: Use Java operators (Intermediate)"を翻訳したものです。 条件演算子の使い方を理解するためのJava SE問題 著者:Simon Roberts、Mikalai Zaikin 2020年4月21日 | 本記事をPDFでダウンロード その他の設問はこちらから 過去にこのクイ...

クイズに挑戦:Javaプラットフォーム・モジュール・システムへの移行(上級者向け)

.button { background-color: #759C6C; color: white; border: 2px solid #759C6C; border-radius: 12px; padding: 5px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; margin: 4px 2px; cursor: pointer; } function answer1() { alert("This answer is incorrect. "); } function answer2() { alert("This answer is incorrect. "); } function answer3() { alert("This answer is correct, but it is not the only correct answer. "); } function answer4() { alert("This answer is correct, but it is not the only correct answer. "); } function answer5() { alert("This answer is incorrect. "); } ※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz Yourself: Migrating to the Java Platform Module System (Advanced)"を翻訳したものです。 アプリケーションをJavaプラットフォーム・モジュール・システムに移行するときのコマンドライン 著者:Simon Roberts、Mikalai Zaikin  2020年4月14日 | 本記事をPDFでダウンロード その他の設問はこちらから 過去にこのクイズの設問に挑戦したことがある方なら、どれ1つとして簡単な問題はないことをご存じでしょう。クイズの設問は、認定試験の中でも難しいものに合わせています。「中級者向け」「上級者向け」という指定は、設問の難易度ではなく、対応する試験による分類です。しかし、ほとんどすべての場合において、「上級者向け」の方が難しくなります。設問は認定試験対策として作成しており、認定試験と同じルールの適用を意図しています。文章は文字どおりに解釈してください。回答者を引っかけようとする設問ではなく、率直に言語の詳細な知識を試すものだと考えてください。 Java SE 9より前のバージョンで開発したアプリケーションをJava SE 11に移行したいと思います。移行にあたっては、トップダウンとボトムアップの移行を考慮し、Java SE 8アプリケーションをモジュールに分割します。 アプリケーションをJavaプラットフォーム・モジュール・システム(JPMS)に移行することを命じられたと考えてください。前提は以下のとおりです。 app.jarおよびlib.jarという2つのJARファイルがある メイン・クラスはapp.Mainという名前で、app.jarに格納されている app.jarは、lib.jarの機能を使用する これはボトムアップ移行であると知らされている 次のうち、移行中にこのアプリケーションの実行に使用できるコマンドラインはどれですか。1つ選んでください。 A. java --module-path lib.jar --add-modules ALL-MODULE-PATH --class-path app.jar app.Main B. java --add-modules lib --class-path app.jar;lib.jar app.Main C. java -cp app.jar -p lib.jar app.Main D. java -p app.jar;lib.jar -m app/app.Main 解答:まず、Javaプラットフォーム・モジュール・システム(JPMS)の背景について確認します。システムの目的を知ることは、その使用方法を理解するうえで役立ちます。 Java 9で導入されたJPMSによって、JavaとJVMの重要なアーキテクチャの一部が改善されています。中でも特に重要なのは、個々のモジュール(モジュールは、JARファイルのライブラリと似たものだと考えてもよいでしょう)・レベルで強力なカプセル化が提供され、依存性管理が改善されている点です。 強力なカプセル化を行えば、個々のモジュールでその内容を保護し、外部からの(誤った)使用を防ぐことができます。これは、クラスのプライベート機能が保護されるのとよく似た仕組みです。ただし、モジュールは一般的に、リフレクション・メカニズムからの保護を目的としても使われる点が異なります。対照的に、通常、クラスのプライベート要素はリフレクションを通じた誤使用に対してぜい弱です。 依存性管理によって、必要なコンポーネントが所定の場所に存在することを開発者やランタイム・システムに保証するとともに、ランタイム・システムの動作が不要なコンポーネントによって妨害されることを防ぐことができます。なお、JPMSはビルド管理システムではない点に注意してください。 強力なカプセル化のもとでもモジュールを有用なものとするために、モジュールはその一部機能へのアクセスを許可しなければなりません。つまり、どの部分へのアクセスを許可する必要があるかを示すために、構成メカニズムを備える必要があります。この構成は、昔から使われているセキュリティの格言に従ったものになっています。すなわち、「明示的に許可されているもの以外は拒否」します。そのため、アクセスできないものを明示的に指定する必要はありません。 依存性管理をサポートするためには、どのモジュールがどのモジュールを必要とするかを判断できることが必要です。 以上の背景を踏まえれば、JPMSより前に作られた既存のプロジェクトを対象に、モジュールを使うように移行するという状況が発生することは避けられません。その状況に対処するためには、何が必要でしょうか。1つ言えるのは、すべての開発を停止し、すべての変更を一斉に行うという方法は現実的でないということです。もう1つは、サードパーティ製のライブラリには、すでにモジュール化されているものも、まだモジュールとしては利用できないものも存在する可能性があるということです。 以上の点から、大きく2つのアプローチが可能だと考えられます(実際はこれらを混ぜ合わせることになる可能性もあります)。モジュール化されているライブラリがあり、そのライブラリをまだモジュール化されていないアプリケーションで使いたいと思うかもしれません。その逆は、アプリケーションのモジュール化に手いっぱいで、ライブラリがまだモジュール化されていない場合でしょう。前者のアプローチをボトムアップ移行、後者をトップダウン移行と呼びます。いずれにせよ、Java 9以降を使っているのであれば、コアJavaライブラリはすでにモジュール化されています。 この設問では、ボトムアップ移行のアプローチを使用すると述べられています。つまり、アプリケーションの中核部はモジュール化されていないものの、その他のライブラリ(この場合はlib.jar)はモジュール化されているということになります。 ボトムアップ、トップダウンという用語は、コア・ドキュメントで明確に示されているものではありません。しかし、試験対象には含まれています。さらに、Mark Reinholdによる解説「The State of the Module System」と、2016年のJavaOneでのプレゼンテーション「Advanced Modular Development」という少なくとも2つのオラクル公式リソースに登場しています。 いずれのリソースも、Java 9が実際にリリースされるよりもかなり前の時点のものです。JPMSの細部にはその後変更された部分もありますが、前述の2つでは移行の概念が十分に説明されています。 したがって、この設問では、ボトムアップという言葉が以下を暗示していることを知っておくべきです。この点は、実際の試験にも当てはまります。 システムのライブラリには、モジュール化されるものもあれば、されないものもある メイン・アプリケーションは(まだ)モジュール化されていない 将来的には、さらに多くのコードがモジュール化される メイン・アプリケーションのモジュール化は最後に行われ、それにより移行が完了する 実際には、このようにきれいで予測可能な形では進展しない可能性があることに注意してください。 おそらく、この設問の解答を考えるうえで最初に解決しなければならない問題は、モジュールが含まれているJARファイルがあるとすればそのファイルはどれなのか、そして従来型のJARファイルはどれなのかを判断することです。 設問で触れられていないため、現在が移行のどの段階にあるのかを100%断言することはできません(ただし、この点については、かなり妥当な解釈が存在します)。しかし、現在の段階を断言できないことは問題ではありません。 まず、「妥当な解釈」として、ライブラリはおそらくモジュールであり(そうでなければ、移行を始めてもいないことになります)、app.jar(ここにapp.Mainクラスが含まれていると記述されています)はおそらくモジュールではありません(そうでなければ、移行が完了したことになります)。 移行のどの段階にあるかがわからなくても問題がない理由の2つ目は、どの段階でも動作可能なコマンドは1つだけで、先ほどの解釈が正しい場合に限られるとわかるからです。それでは、lib.jarはモジュール化されていて、app.jarはモジュール化されていないという前提に立ったとき、どういうことが言えるかについて考えてみます。 このコードを実行するためには、JVMが次に示す重要な情報を取得できることが必要です。 通常のクラスを探す場所 モジュールを探す場所 ロードするモジュール プログラムを開始する方法 メイン・プログラムは従来型のJARファイルからロードしたクラスより起動されるという前提で考えれば、そのJARファイルはクラスパス上に存在していなければなりません。そうなるためには、app.jarファイルがクラスパス(-cp、-classpath、--class-pathのいずれかで指定します)上にあることが必要です。この条件を満たすのは、選択肢A、B、Cです。選択肢Dでは、-p(これは--module-pathの省略形です)を使ってapp.jarファイルをモジュール・パスで指定しています。 次にJVMが知る必要があるのは、モジュールをロードする場所です。ここでも、lib.jarはモジュールであるという前提を使用します。つまり、モジュール・パスにlib.jarが含まれていることが必要です。これを満たすのは、選択肢A、C、Dです。しかし、選択肢Bでは、lib.jarをクラスパスで指定しています。先ほどの前提が正しい場合、この配置は誤っていることになります。 モジュールを使うコードと従来のコードには、大きく違う点があります。非モジュール環境で実行中のコードがクラスパス上にある任意のクラスを呼び出した場合、そのクラスがロードされます。一方のモジュール環境では、JVMはモジュール・グラフ内にあるモジュールしか検索しません。 モジュール・システムにおいてJVMは、必要だと特定したすべてのモジュールのグラフを構築します。アプリケーションのメイン・クラスを含むモジュールから始まるグラフを考えてください。そのモジュールは他のモジュールが必要である(requires)と宣言しているため、それらのモジュールが依存性グラフに追加されます。次に、追加されたモジュールが必要とするすべてのモジュールを追加します。この処理が推移的に繰り返され、必要なすべてのモジュールのグラフが構築されます。当然のことながら、これらのモジュールも実際にモジュール・パス上に存在しなければなりません。 ここで繰り返します。JVMの実行中に、コードがあるクラスを呼び出した場合、JVMではこのグラフ上にあるモジュールのみをチェックします。これやや単純化されていますが、前述のとおり、JVMでは非モジュールのJARファイルに含まれるメイン・クラスでは動かない可能性があります。そういったメイン・クラスを使用できないのは、非モジュールJARファイルにはモジュール・ディスクリプタが含まれていないため、requiresディレクティブが含まれることはないからです。問題の本質は、requiresディレクティブがない場合、必要となるモジュールはどうすればわかるのかということです。つまり、この例を実行するためには、libモジュールの必要性を何らかの形で示す必要があります。もしそうしないならば、app.Mainでlibモジュールのクラスが必要となったときに、JVMは「class not found」エラーをスローします。 このエラーを解消するためには、コマンドラインにrequiresディレクティブと同等なものを明示的に追加する必要があります。これを行うのが、--add-modulesコマンドライン・オプションです。このオプションを使う場合は、(選択肢Bのように)明示的なモジュール名を指定することも、3つの特殊な値であるALL-DEFAULT、ALL-SYSTEM、ALL-MODULE-PATHのいずれかを指定することもできます。ALL-MODULE-PATHを指定した場合は、モジュール・パス上にあるすべてのモジュールがルート・モジュール・セットに追加されます(したがって、必要なすべてのモジュールのグラフを構築するために使われます)。この場合、強制的にlibモジュールが利用可能になるという効果があります。このコマンドライン・オプションは、選択肢Aと選択肢Bに存在します。このことから、先ほどの前提が正しい場合、選択肢CとDは誤りと考えられると結論づけることができます。 ただし、自信を持って結論を出すにはまだ早すぎます。そもそも、選択肢AとBのその他の部分は問題なく動作するでしょうか。前提は正しいということが証明できるでしょうか。 選択肢Aをもう少し詳しく見てみます。JVMでは、プログラムのエントリ・ポイントも知る必要があります。app.jarが、クラスパス上にある非モジュールのJARファイルである場合、javaコマンドの要素の最後(当然ながら、プログラム自体に引数がある場合は除きます)にメイン・クラスの完全修飾名を指定するだけで済みます。選択肢Aはまさにそのようになっています。そのため、先ほどの大前提が正しい場合、選択肢Aは問題なく動作します。 選択肢Bでは--add-modulesを使っており、モジュールlibを明示的に呼び出しています。この部分は問題ありません。しかし、lib.jarファイルがモジュール・パス上ではなく、クラスパス上にあります。通常のJARファイルは、モジュール・パス上に配置することでモジュールとして振る舞うことができますが、クラスパス上にある何かが名前付きモジュールのように振る舞うことは決してありません。そのため、lib.jarはモジュールで、app.jarはモジュールでないという前提が正しいかどうかにかからわらず、ここで使用している--add-modulesは機能しません。先ほど、lib.jarをクラスパス上に配置しても期待どおりには動作しないと述べましたが、ここまで来れば、選択肢Bに関する内容に矛盾があることがわかるでしょう。そのため、選択肢Bは誤りです。 選択肢Cは選択肢Aとよく似ています。省略形の-p使ってlib.jarをモジュール・パスに追加して、app.jarをクラスパスに追加しています(ここでも、省略形の-cpを使っています)。さらに、選択肢Aと同じようにしてメイン・エントリ・ポイントを呼び出しています。lib.jarをモジュール、app.jarを従来型として扱っているため、期待できる選択肢です。しかし、--add-modulesは使われていません。その結果、lib.jarのクラスはロードできないことになり、プログラムは実行できません。lib.jarがモジュールであるか従来型であるかは関係ありません。JARファイルがモジュール・パス上にあったとしても、そこに含まれるクラスを利用できるのは、そのモジュールがモジュール・グラフ上にある場合のみです。このことから、選択肢Cは誤りであることがわかります。 選択肢Dでは、app.jarをモジュール・パス上に配置しようとしています。app.jarがmodule-infoを含むモジュールであれば、これは機能するでしょう。しかし、その場合、移行が完了しているか、トップダウン型であることになってしまいます。この設問では、ボトムアップ移行が進行中であることが明記されているため、この選択肢はあり得ません。従来型のapp.jarをモジュール・パスに追加した場合、自動モジュールとして扱われます。しかし、コマンドラインに--add-modulesがやはり不足しています。そのため、選択肢Dは誤りです。 正解は選択肢Aです。 Simon Roberts Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO'Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。   Mikalai Zaikin Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。

※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz Yourself: Migrating to the Java Platform Module System (Advanced)"を翻訳したものです。 アプリケーションをJavaプラットフォーム・モジュール・システムに移行するときのコマンドライン 著者:Simon Roberts、Mikalai Zaikin 202...

テストを容易にするJUnit 5.6の新機能

※本記事は、Mert Çalişkanによる”JUnit 5.6 Makes Testing Easy with New Features“を翻訳したものです。 テストの実行順序の定義、テストのパラレル実行などの新機能により、重要度の高いリリースが実現 著者:Mert Çalişkan 2020年5月4日 広く使われているJava単体テスト・フレームワークであるJUnitは、2017年に画期的なJUnit 5をリリースしました。10年間進化し続けてきたバージョン4.xを経て、JUnit 5ではフレームワーク全体が完全に書き直されました。節目となる5.0.0リリースの後、開発のペースを速めたJUnitチームは、4か月から5か月ごとにマイナー・リリースを行っています。最新のマイナー・バージョンとなる5.6.0は2020年1月20日に、その更新版となる5.6.1は3月22日にリリースされました。 本記事では、改めてJUnitフレームワークを取り上げ、導入された最新機能について、コード・サンプルを交えながら紹介します。さらに、関連機能についても、その機能がどのバージョンでフレームワークに導入されたのかがわかるように、「(5.x以降)」という形で補足しています。 まずは、用語の簡単な定義を記載しておきます。JUnit 5は、3つの独立したモジュールで構成されています。 JUnit Platformは、JVMでテスト・フレームワークを実行する土台となります。多くのIDEやビルド・ツールがこのモジュールをサポートしています。 JUnit Jupiterは、最新のプログラミング・モデルであり、JUnit 5テストのTestEngineでもあります。 JUnit Vintageは、古いJUnit 3とJUnit 4のテスト用のTestEngineです。 (編集部より:ツールボックスにJUnit 4テストが含まれている方は、Brian McGlauflin氏による記事「JUnit 4からJUnit 5に移行する:重要な違いと利点」をお読みください。) Mavenをお使いの方は、リスト1のようにして、JUnit 5.6の依存性を簡単に追加することができます。 リスト1: <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.6.0</version> <scope>test</scope> </dependency> Gradleをお使いの方は、リスト2のようにして、依存性を追加することができます。 リスト2: testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.6.0' それではいよいよ、新機能の紹介に入っていきます。   テストの実行順序の定義 あるクラスのテスト・メソッドの実行順序を、英数字順、ランダム、@Orderアノテーションによる指定順(5.4以降)、カスタムの順序定義のいずれかで定義できます。希望する順序の適用は、クラス・レベルに付加する@TestMethodOrderアノテーション(5.4以降)を使用すればうまくいきます。 テスト・メソッドをメソッド名の英数字順で実行する方法をリスト3に示します。 リスト3: @TestMethodOrder(MethodOrderer.Alphanumeric.class) class OrderedTest { @Test void testC() { System.out.println("Test C"); } @Test void testZ() { System.out.println("Test Z"); } @Test void testA() { System.out.println("Test A"); } } リスト3での実行順序は、testA() -> testC() -> testZ()となります。 定義@TestMethodOrder(MethodOrderer.Random.class)を使用して、ランダムな順序を定義することもできます。この場合、メソッドの実行順序はランダムに選ばれます。テスト間に依存性がないという現在の状態を維持し、今後のステージでテスト間に余計な関係が構築されないようにしたい場合、この機能が意味を持つことになります。 テストの順番を明示的に定義したい場合は、@Orderアノテーションを使うことができます。リスト4をご覧ください。 リスト4: class OrderedTest { @Test @Order(1) void testZ() { System.out.println("Test Z"); } @Test @Order(2) void testC() { System.out.println("Test C"); } } リスト4では、小さい値ほど優先されます。そのため、testZ()メソッドがtestC()メソッドより先に実行されます。@Orderアノテーションが付加されていないメソッドのデフォルト値は、Integer.MAX_VALUE / 2です(5.6以降)。 カスタムで順序を決めるメカニズムを定義し、@TestMethodOrderアノテーションでそのメカニズムを指定することもできます。リスト5の実装では、メソッド名の長さによってテスト・メソッドの実行順序が決まり、メソッド名が短いものの方が、長いものよりも先に実行されます。 リスト5: class CustomOrder implements MethodOrderer { @Override public void orderMethods(MethodOrdererContext context) { context.getMethodDescriptors().sort( Comparator.comparingInt( methodDescriptor -> methodDescriptor.getMethod() .getName() .length())); } } CustomOrderの実装は、@TestMethodOrderアノテーションを使って指定する必要があります。リスト6をご覧ください。 リスト6: @TestMethodOrder(CustomOrder.class) class OrderedTest { @Test void a_very_long_test_method() { } @Test void short_mthd() { } } 実行順序は、short_mthd() -> a_very_long_test_method()となります。   宣言的タイムアウトの定義 JUnitには、指定されたしきい値を超える時間がかかった場合にテストを失敗とマークするオプションがあります。この機能は、長時間実行されるテストを診断する場合にとても役立ちます。JUnit 5.5以降では、@Timeoutアノテーションを使ってタイムアウトの制限値を定義することができます。使用方法をリスト7に示します。 リスト7: @Test @Timeout(value = 1) void checkJobDoesNotExceedLimit() throws InterruptedException { Thread.sleep(1500); } checkJobDoesNotExceedLimit()テスト・メソッドでのタイムアウト値を1秒に設定しています。時間の単位は、デフォルトでは秒です。このテストは1,500ミリ秒間実行されるため、失敗するのは明らかです。別の単位を指定する場合は、リスト8のようにします。 リスト8: @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) タイムアウト値をシステム・プロパティで定義することもできます。リスト9では、実行する各テストのタイムアウトの制限値を100ミリ秒と定義しています。 リスト9: -Djunit.jupiter.execution.timeout.test.method.default=100ms アノテーションでも値が設定されている場合、その値はシステム・プロパティで定義された値よりも優先されます。タイムアウト関連で指定できる、システム・プロパティのキーすべてと、その説明を以下に示します。 junit.jupiter.execution.timeout.default:すべてのテスト可能なライフサイクル・メソッドに適用されるデフォルトのタイムアウト値 junit.jupiter.execution.timeout.testable.method.default:すべてのテスト可能なメソッドに適用されるデフォルトのタイムアウト値 junit.jupiter.execution.timeout.test.method.default:@Testアノテーションが付加されているメソッドに適用されるデフォルトのタイムアウト値 junit.jupiter.execution.timeout.testtemplate.method.default:@TestTemplateアノテーションが付加されているメソッドに適用されるデフォルトのタイムアウト値 junit.jupiter.execution.timeout.testfactory.method.default:@TestFactoryアノテーションが付加されているメソッドに適用されるデフォルトのタイムアウト値 junit.jupiter.execution.timeout.lifecycle.method.default:すべてのライフサイクル・メソッドに適用されるデフォルトのタイムアウト値 junit.jupiter.execution.timeout.beforeall.method.default:@BeforeAllアノテーションが付加されているメソッドに適用されるデフォルトのタイムアウト値 junit.jupiter.execution.timeout.beforeeach.method.default:@BeforeEachアノテーションが付加されているメソッドに適用されるデフォルトのタイムアウト値 junit.jupiter.execution.timeout.aftereach.method.default:@AfterEachアノテーションが付加されているメソッドに適用されるデフォルトのタイムアウト値 junit.jupiter.execution.timeout.afterall.method.default:@AfterAllアノテーションが付加されているメソッドに適用されるデフォルトのタイムアウト値 @Timeoutアノテーションをクラス・レベルに付加して、そのクラスに含まれる各テスト・メソッドのデフォルトのタイムアウト値を指定することもできます。また、@Timeoutアノテーションは、@BeforeAll、@BeforeEach、@AfterEach、@AfterAllのいずれかのアノテーションが付加されているライフサイクル・メソッドでも使用できます。@TimeoutアノテーションはJUnit 5.6において試験運用版であるため、今後のリリースで変更される可能性もあります。   条件付きテスト実行 JUnit 5.1以降では、アノテーションで定義した条件に基づき、テストの実行をプログラム的に有効または無効にすることができます。このセクションでは、さまざまなカテゴリに属する、条件付きテスト実行に関連したアノテーションを示し、その使い方について説明します。 JRE条件:JREのバージョンに基づいて、テストの実行を有効または無効にすることができます。@EnabledOnJreアノテーション(5.1以降)により、指定したJREバージョンのみでテストの実行が有効になります。リスト10のテスト・メソッドは、Javaバージョン8のみで実行されます。 リスト10: @Test @EnabledOnJre(value = JRE.JAVA_8) void shouldOnlyPassOnJava8() { // ... } } @DisabledOnJreアノテーション(5.1以降)は上記の逆であり、指定したJREバージョンでテストの実行が無効になります。JUnitバージョン5.6では、8から15までのJREバージョンを定義できます。 @EnabledForJreRangeおよび@DisabledForJreRange(いずれも5.6以降)という2つのアノテーションは、これらのアノテーションが付加されたメソッドの実行を特定のJREバージョン範囲で有効または無効にする条件を定義するためのものです。リスト11は、特定のJREバージョン範囲でテストの実行を有効にする例です。 リスト11: @Test @EnabledForJreRange(min = JRE.JAVA_9, max = JRE.JAVA_11) void shouldRunBetweenJava8AndJava11() { // ... } オペレーティング・システム条件:オペレーティング・システム(OS)の種類に基づいて、テストの実行を有効または無効にすることもできます。リスト12では、@EnabledOnOsアノテーション(5.1以降)により、macOSの場合にテストの実行が有効になります。 リスト12: @Test @EnabledOnOs(value = OS.MAC) void shouldOnlyRunOnMacOs() { // ... } @DisabledOnOSアノテーション(5.1以降)は上記の逆であり、指定した種類のOSでテストの実行が無効になります。OSの種類を表す値には、AIX、LINUX、MAC、SOLARIS、WINDOWS、OTHERを指定できます。 環境変数条件:環境変数の存在に基づいて、テストの実行を有効または無効にすることができます。@EnabledIfEnvironmentVariableアノテーション(5.1以降)により、指定した名前の環境変数が、指定した正規表現と一致する場合にテストの実行が有効になります。@DisabledIfEnvironmentVariableアノテーション(5.1以降)はその逆であり、テストの実行が無効になります。リスト13の例では、HOME環境変数が/Users/mcaliskanまたは/Users/mertcaliskanに設定されている場合にテストが実行されます。 リスト13: @Test @EnabledIfEnvironmentVariable(named = "HOME", matches = "/Users/mcaliskan|/Users/mertcaliskan") void shouldOnlyRunOnSpecifiedHOMEDirectory() { // ... } システム・プロパティ条件:システム・プロパティの存在に基づいて、テストの実行を有効または無効にすることができます。@EnabledIfSystemPropertyアノテーション(5.1以降)により、指定した名前のシステム・プロパティが、指定した正規表現と一致する場合にテストの実行が有効になります。@DisabledIfSystemPropertyアノテーション(5.1以降)はその逆であり、テストの実行が無効になります。リスト14の例では、os.archシステム・プロパティの存在に基づいて、テストの実行が有効になります。このテストは、64ビット・バージョンのJREが使われている場合にのみ実行されます。 リスト14: @Test @EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*") void worksOnlyOn64BitArchitecture() { // ... } JUnit 5.6では、@EnabledIfEnvironmentVariable、@DisabledIfEnvironmentVariable、@EnabledIfSystemProperty、@DisabledIfSystemPropertyを繰り返すことができるようになりました。そのため、同じテスト・メソッドに複数回定義できるようになっています。JUnit 5.6では、@EnabledIfアノテーションと@DisabledIfアノテーションがコードベースから削除されています。この2つのアノテーションは、以前のリリースですでに非推奨になっていました。前述の各カテゴリで定義されているアノテーションを使うことをお勧めします。   プログラムによる拡張機能登録 拡張機能の主な目的は、テスト・クラスやテスト・メソッドの動作を拡張し、そのロジックを複数のテストで再利用できるようにすることです。JUnit 5では、拡張機能メカニズムの導入により、優れた手法が実現しました。Extension APIを使用して、テスト実行ライフサイクルの所定のポイントで一時停止し、フックされている拡張機能を実行することができます。 @RegisterExtensionアノテーション(5.1以降)により、プログラムから拡張機能を登録できます。リスト15に示すのは、テスト・メソッドの実行時間をロギングする拡張機能の例です。 リスト15: public class LogTestExecutionTime implements BeforeTestExecutionCallback, AfterTestExecutionCallback { private long startTime; public void beforeTestExecution(ExtensionContext context) { startTime = System.currentTimeMillis(); } public void afterTestExecution(ExtensionContext context) { long elapsedTime = System.currentTimeMillis() - startTime; System.out.printf("Test Execution took %d ms", elapsedTime); } } リスト16では、この拡張機能をテスト・クラス内に登録しています。 リスト16: class ExtensionTest { @RegisterExtension LogTestExecutionTime logTestExecutionTime = new LogTestExecutionTime(); @Test void longRunningTest() { // … } } JUnit 5.5では、拡張機能を登録する際に制約が適用されます。その1つが、@RegisterExtensionアノテーションを付加するフィールドがprivateであってはならないというものです。もう1つが、拡張機能のクラスは、少なくとも1つのExtension APIを実装しなければならないというものです。バージョン5.4より前では、適切に構成されていない拡張機能は、何も表示されることなく無視されました。   テストのパラレル実行 デフォルトでは、JUnit Jupiterは1つのスレッドでテストを逐次実行します。しかし、バージョン5.3以降では、テストのパラレル実行も可能になっています。ただし、重要な注意事項があります。これは試験運用版の機能です。 まず、システム・プロパティで構成パラメータjunit.jupiter.execution.parallel.enabledをtrueに設定する必要があります。そして、テスト・メソッドまたはそれを含むクラスに、@Execution(ExecutionMode.CONCURRENT)アノテーション(5.3以降)を付加する必要があります。リスト17に示す簡単なテストの例では、エンドポイントを並列に5回テストしています。この実装では@RepeatedTestアノテーションを使っており、@RepeatedTestアノテーションには@TestTemplateアノテーションが使われています。 リスト17: @Execution(ExecutionMode.CONCURRENT) @RepeatedTest(value = 5, name = "{displayName} {currentRepetition}/{totalRepetitions}") void testEndpointConcurrently() { // … } パラレル実行構成のために定義された、システム・プロパティのキーは、junit.jupiter.execution.parallel.mode.default (A)とjunit.jupiter.execution.parallel.mode.classes.default (B)です。この2つにconcurrentまたはsame_threadのいずれかを適宜設定して、パラレル実行を有効にすることもできます。 図1は、この設定によって、1つのクラスに含まれるすべてのメソッドの実行と、トップレベルのクラスの実行がそれぞれどのようになるかを表しています。最初の列の(A, B)は、前述のキー(A)と(B)に、same_threadとconcurrentをどのように指定するかを表しています。 図1:テストのパラレル実行の例 テストのパラレル実行の詳細については、ドキュメントをご覧ください。   まとめ JUnitチームは、4か月から5か月のリリース周期に合わせて、5.xのブランチに関する作業を懸命に進めています。再設計されたバージョンのJUnitで新機能がリリースされているのは歓迎すべきことです。 本記事では、リリース5.0から5.6までの間に導入された主要機能の一部を紹介しましたが、新機能は他にもあります。その中で特筆すべきは、生成されるJUnitアーティファクトにOSGiメタデータが含まれるようになったことです。そのため、お気に入りのOSGiコンテナでも簡単にJUnitを使えるようになっています。同様に注目すべきは、バージョン5.6以降のJUnitでは、Gradleモジュールのメタデータも公開されていることです。このメタデータは、Gradleユーザー向けの、バリアントに対応したきめ細かい依存性解決メカニズムです。 ただし、一部のJUnit 5 APIはまだ変更される可能性がある点に注意してください。JUnitチームは、パブリックな型に@APIアノテーションを付加し、Experimental、Maintained、Stableなどの値を割り当てています。 Learn More JUnit 5の公式ドキュメント Mert Çalışkan Mert Çalışkan(@mertcal):Java Champion。『PrimeFaces Cookbook』(Packt Publishing、2013年)および『Beginning Spring』(Wiley Publications、2015年)の共著者。最新刊となる『Java EE 8 Microservices』を出版したばかりであり、AtlassianでBitbucketのプリンシパル・エンジニアを務めている。

※本記事は、Mert Çalişkanによる”JUnit 5.6 Makes Testing Easy with New Features“を翻訳したものです。 テストの実行順序の定義、テストのパラレル実行などの新機能により、重要度の高いリリースが実現 著者:Mert Çalişkan 2020年5月4日 広く使われているJava単体テスト・フレームワークであるJUnitは、2017年に画期的なJUnit...

Unsafeクラス:どんなスピードでも危険

※本記事は、Ben Evansによる”The Unsafe Class: Unsafe at Any Speed“を翻訳したものです。 ルールを破ることが可能だからといって、ルールを破るべきというわけではないが、しかるべき理由が存在する場合もある 著者:Ben Evans 2020年5月4日   ときには、ルールを破らねばならない場合もあるかもしれません。Javaプラットフォームにおいて、そのような行為は3つの主要メカニズムのいずれかを使って行われるのが一般的です。3つの主要メカニズムとは、リフレクション、クラス・ローディング(関連するバイトコードの変換を含む)、Unsafeです。 Javaのパワー・ユーザーは、たとえ必要に迫られた場合のみに最後の手段として使うものであっても、これら3つのテクニックすべてを理解しておく必要があります。「できるからといって、やるべきというわけではない」という原則は、その他のことと同様、ソフトウェアの設計を選択するうえでも当てはまります。 前述の3つのうちで、潜在的な危険性をもっともはらんでいるのがUnsafeです(それゆえ、もっとも強力です)。他の方法ではできない形で、そしてプラットフォームの確立されたルールを破る形で、特定のことを行う方法を提供するからです。 たとえば、開発者はUnsafeを使って次のようなことを実行できます。 CPUなどのハードウェア機能に直接アクセスする コンストラクタを実行せずにオブジェクトを作成する 通常の検証を経ずに真に匿名なクラスを作成する オフヒープ・メモリを手動で管理する その他、不可能と思われるさまざまなことを行う 一見して、Java 8のUnsafeクラス(sun.misc.Unsafe)の危険性は明らかです。この点は、クラスの名称だけでなく、クラスが存在するパッケージからもわかります。sun.miscパッケージは、内部用で実装固有の場所です。Javaコードでは、このパッケージに直接触れるべきではありません。Java 9以降のバージョンでは、Unsafeの機能がjdk.unsupportedという名前のモジュールに移動したため、内部向けであるという性質がいっそう明確になっています。 当然ですが、Javaのライブラリがこういった種類の実装の詳細に直接アクセスすることは想定されていません。この立場を強調すべく、Javaのプラットフォームのメンテナンス・チームは、そのようなことをするのは危険であり、ルールを破って内部実装の詳細にリンクしているアプリケーション開発者は自身のリスクでそれを行っていると長い間言い続けてきました。 こういった明確な警告にもかかわらず、具合が悪いことに現実として、Unsafeは至る所で使われています。Javaエコシステムの主要フレームワークでは、ほぼ例外なく、一部の機能においてUnsafeを使っています。最先端のJava開発者が期待するようになったダイナミックさ、柔軟性、パフォーマンスの大部分は、何らかの形でUnsafeを使って実現されていると言っても過言ではありません。   ハードウェアCPU機能へのアクセス Unsafeの典型的な使用法を確認してみます。例として、「コンペア・アンド・スワップ」(CAS)と呼ばれるハードウェア機能を取り上げます。この機能はほぼすべての最新CPUに搭載されていますが、Javaのメモリ・モデルに含まれていないことはよく知られています。 最初の例では、ルールに従い、Javaのメモリ・モデルの一部である同期機能を使用します。 public final class SynchronizedCounter implements Counter { private int i = 0; @Override public synchronized int increment() { return i = i + 1; } @Override public synchronized int get() { return i; } } 続いて、上記と、Unsafeを用いたAtomicCounterの実装とを比較してみます。リフレクションを使ってUnsafeクラスにアクセスする必要があるため、定型挿入文がかなり多く含まれています。 public final class AtomicCounter implements Counter { private static final Unsafe unsafe; private static final long valueOffset; private volatile int value = 0; static { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); unsafe = (Unsafe) f.get(null); valueOffset = unsafe.objectFieldOffset (AtomicCounter.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } @Override public int increment() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } @Override public int get() { return value; } } これら2つでは、パフォーマンスにかなりの差があります。最新のハードウェアでは、Unsafe実装の方がおよそ2倍から3倍高速に実行できます。 重要なポイント:先に述べたように、最新のフレームワークでは、ほぼ例外なく内部的にUnsafeがすでに使われています。アプリケーション開発者は、Unsafeを直接使用すればコードのパフォーマンスが少しでも向上すると考えるべきではありません。フレームワークの開発者はこれまで、実行可能であればいつでも、Unsafeを安全な方法で活用してきています。したがって、アプリケーション開発者は、フレームワークで提供されているものを使うべきです。 AtomicCounterのコードや、そこで使われているJDKのコードを調べれば、Javaでは不可能と思われるいくつかのことがこの実装で行われていることがわかります。 最初に目に付くのは、ポインタのオフセット(AtomicCounterオブジェクトの開始場所と、フィールドvalueが存在する場所との差分)の計算です。JVMバイトコード命令に、この計算を実現できるシーケンスは含まれていません。JVMの内部データ構造に直接アクセスするネイティブ・コードだけがこの計算を行うことができます。 次に、フィールドを使って間接的にアクセスするのではなく、ポインタのオフセットを使ってメモリに直接アクセスしています。さらに、メモリにアクセスする際のモード(この場合はvolatile)を選択しています。通常であれば、このモードは変数の宣言方法によって決まります。 「朝食前の6つの不可能なこと」とまったく同じではありませんが、かなり近いものです。 厳密に言えば、このコードでは前述の内容すべてを行うことで、Java仕様のルールを破っています。このコードでは内部機能が使用されていますが、ユーザーのコードはルールに従うことになっているのに対し、内部機能はルールに従っているとは限らないからです。 しかし現実には、Java仕様に厳格に従い続けるためだけに、パフォーマンスを大幅に犠牲にしてもまったく構わないというJava開発者はいません。このことは誰もがよく知っています。本当の問題は、Unsafeの機能へのアクセスが実に簡単であり、開発者がこの機能を使うことは推奨されていないことです。 それゆえに、この点は根本的な疑問につながります。Unsafeによって実現し、最新のJavaエコシステムが依存している、パフォーマンス面や機能面のメリットを維持しつつ、JavaからUnsafeクラスを削除するためにはどうすればよいのでしょうか。   どうすればUnsafeを削除できるか 直接的または間接的に、最新のJavaアプリケーションでUnsafeが使われている範囲を考えれば、選択肢は次のものに限られます。 いくつかの内部機能が事実上、Java APIの一部になっていることを認め、標準としてサポート対象に加える 数え切れないJavaアプリケーションが動作しなくなるリスクを冒して、Unsafeへのアクセスを削除する 実際のところ、選択肢はまったくありません。しかるべき通知と検討なしにアクセスが削除されれば、プラットフォームで事実上のフォークが発生して分裂するリスクがあります(Python 2とPython 3のような状態、あるいはそれよりひどくなる可能性もあります)。 Unsafeへのアクセスを単純に遮断すれば、ほぼすべてのJavaアプリケーションがアップグレードできなくなってしまうでしょう。一般的なアプリケーションでは、Unsafeの機能への推移的依存性があるライブラリをほぼ例外なく使用しているからです。 これに関連する(そして過小評価されがちな)点として、Javaプラットフォーム開発チームは、エンドユーザーのJavaアプリケーション(またはそれをサポートするライブラリ)が内部実装の詳細に依存していることを心配せずに、内部詳細を自由に変更できなければならないということが挙げられます。 この状況が、Unsafeの根幹にある別の本質的な問題を浮かび上がらせています。最近のバージョンのJavaは、不完全なカプセル化しか行われていなかったプラットフォームから、さらに強固なモジュール境界を目指して進化しています。その一方で、Unsafeはモジュール・システムを採用するうえでの障害になっています。 これを解決するためには、妥協が必要です。Java 11までに、Unsafe関連メソッドの多くが、jdk.internal.misc.Unsafe(java.base内)とsun.misc.Unsafe(jdk.unsupported内)という2つの異なるクラスに移行されています。 jdk.unsupportedモジュールは、次のように宣言されています。 module jdk.unsupported { exports sun.misc; exports sun.reflect; exports com.sun.nio.file; opens sun.misc; opens sun.reflect; } この宣言では、unsupportedモジュールに明示的に依存する任意のアプリケーションからのアクセス許可を指定しています。重要なのは同時に、Unsafeを含むパッケージsun.miscに対して、リフレクションによる無制限のアクセスを認めている点です。 これにより、Unsafeはモジュールと親和性の高い形式への移行が促進されているものの、「この妥協の産物はいつまで維持されるべきなのか」と尋ねたくなる方もいるはずです。 実際にUnsafeを削除するためには、リフレクションによるアクセスがもたらす、カプセル化の穴をふさぐ必要があります。そしておそらく、この問題に対処する最適な方法は、リフレクションに関する一般ポリシーの変更です。Java 9の開発が行われていた当時、無制限のリフレクションが広く使用されていたことが、--illegal-access=permitをデフォルトにするという妥協の選択につながりました。 しかし、これはほんの一時的なものとして計画されました。そこで、これら両方の一時的な妥協について、その必然性を再考してみます。   これまでに行われてきたこと 実際にUnsafeから削除されたメソッドもあります。たとえば、次に示すものです。 fieldOffset() monitorEnter() monitorExit() tryMonitorEnter() Java 9では、defineClass()メソッドもMethodHandlesクラスに移動しました。 VarHandle APIの導入も大きな前進です。このAPIは、UnsafeのAPIの一部に代わる安全な手法を提供することを主目的として、Java 9で追加されました。 VarHandleの重要な目的の1つは、CAS機能と、volatileなフィールドまたは配列へのアクセスに代わる機能を提供することです。この実例を確認するため、Unsafeの代わりにVarHandleを使ってアトミック・カウンタを実現するアプローチについて、簡単な例を紹介します。 public class AtomicVHCounter implements Counter { private volatile int value = 0; private static final VarHandle vh; static { try { MethodHandles.Lookup l = MethodHandles.lookup(); vh = l.findVarHandle(AtomicVHCounter.class, "value", int.class); } catch (ReflectiveOperationException e) { throw new Error(e); } } @Override public int increment() { int i; do { i = (int) vh.getVolatile(this); } while (!vh.compareAndSet(this, i, i + 1)); return i; } @Override public int get() { return value; } } このコードは、Unsafeを使っている先ほどの例と機能的に同等です。しかし、こちらのコードでは完全にサポートされているAPIのみを使っています。主な手順は以下のとおりです。 Lookupオブジェクトを使って、適切なフィールドのVarHandleを取得する VarHandleをキャッシュする VarHandleとvolatileメモリ・セマンティックを使ってフィールドにアクセスする 非常に重要な変更点は、MethodHandles.Lookupを使っている部分です。setAccessible()を使ってプライベート・フィールドにアクセスするリフレクションとは異なり、Lookupオブジェクトには呼出し元のコンテキストと同等のアクセス権しかありません。このコンテキストには、プライベート・フィールドであるvalueへのアクセス権も含まれています。 リフレクションからメソッド・ハンドルやフィールド・ハンドルに移行するということは、Java 8のUnsafeに存在していた多数のメソッドが、未サポートAPIから削除されたことを意味します。たとえば、次のようなものです。 compareAndSwapInt() compareAndSwapLong() compareAndSwapObject() VarHandleには、多くの便利なアクセッサ・メソッドに加え、これらのメソッドと同等の機能が含まれるようになっています。プリミティブタイプやObject用のgetメソッドやputメソッドも存在し、通常アクセス・モードとvolatileアクセス・モードの両方で使用できます。また、効率的な加算を行う、以下のようなメソッドもあります。 getAndAddInt() getAndAddLong() getAndSetInt() getAndSetLong() getAndSetObject() VarHandleの別の重要な目的は、JDK 9以降で利用できる新しいメモリ・オーダー・モードへの低レベル・アクセスを許可することです。 総じて言えば、事実上のAPIであるUnsafeの代替機能を作成するという作業は、明確な進展を遂げています。たとえば、VarHandleの他にも、Stack-Walking API(JEP 259)によってUnsafeのgetCallerClass()機能が提供されるようになっています。しかし、やるべきことはまだ残されています。   Hiddenクラス 上記以外で、進展があった主な領域の1つが、JEP 371に記述されているHiddenクラスの実装に向けた作業です。このJEPは、Unsafeで特に多く見られる用途について述べています。その用途とは、他のクラスから直接使用することはできない(ものの、リフレクションを通じて扱うことはできる)クラスをその場で作りたいというものです。 このようなクラスを匿名クラスと呼ぶことがあり、Unsafeのメソッドも、匿名クラスという意味を含むdefineAnonymousClass()という名前になっています。しかし、この用語によって開発者が混乱する可能性があります。通常のJavaアプリケーション・コードでの匿名クラスとは、インタフェースのネストした実装を提供し、静的な型をインタフェースとして宣言する方法を指すからです。たとえば、次のようなものです。 public class Scratch { public void foo() { Runnable r = new Runnable() { @Override public void run() { System.out.println("We had to do it this way before lambdas!"); } }; } } 多くのJavaプログラマーは、このRunnable実装のようなクラスが実際のところ匿名ではないことを知っています。というのも、コンパイラはScratch$1というような名前でクラスを生成し、そのクラスは純粋なJavaクラスとして使用できるからです。このクラスの名前をJavaソース・コードから利用することはできません。しかし、自動生成された名前を使えば、クラスを見つけてリフレクションからアクセスし、他のクラスと同じように利用することができます。 しかし、Unsafeについて話す場合、違う意味になります。Hiddenクラスという用語を使うべきなのはそのためです。 Hiddenクラスも真に匿名なクラスではありません。ClassオブジェクトのgetName()を直接呼び出すことで取得できる名前があるからです。この名前は、診断情報、JVMツール・インタフェース、Java Flight Recorderイベントなど、他のいくつかの場所にも表示される可能性があります。しかし、Hiddenクラスは、クラス・ローダーでも、リフレクション(たとえば、Class.forName())などの、通常のクラスを検出できる仕組みでも検出できません。 Hiddenクラスに名前があるのは、通常のクラスとは異なるネームスペースへと明示的に格納する方法を提供するためです。 最新バージョンのHiddenクラスの実装(OpenJDKプロジェクトで現在も開発が進行中)では、JVMのクラス名に通常は2つの形式があることを利用した命名スキームになっています。1つはバイナリ名(com.acme.Gadget)で、ClassオブジェクトのgetName()を呼び出すことにより、返されるものです。もう1つは、内部形式(com/acme/Gadget)です。 Hiddenクラスの場合、これと同じパターンの命名にはなりません。HiddenクラスのClassオブジェクトのgetName()を呼び出すことにより、com.acme.Gadget/1234というような名前が返されます。これはバイナリ名でも内部形式でもありません。この名前に一致する通常のクラスを作ろうとしても、失敗します。 この命名スキーム(と、このような形でHiddenクラスを差別化すること)のメリットの1つは、JVMのクラス・ローディング・メカニズムで通常行われている、強力で厳密な検査の対象とする必要はない点にあります。これは、Hiddenクラスを使うと想定されているのが、一般的なJavaクラスに課される通常の強固なチェックで検出できないことを必要としているフレームワーク作成者などであるという、全般的な設計に適合しています。 UnsafeについてJEP 371が目指すのは、Lookup APIの一部としてHiddenクラスがサポートされるようになったときにUnsafeのdefineAnonymousClass()メソッドを非推奨とすることであり、ひいては、将来のリリースでこのメソッドを削除可能とすることです。 これは純粋に内部的な変更です。少なくとも最初のうちは、Hiddenクラスの導入によってJavaプログラミング言語に何らかの変化が発生することは想定されていません。しかし、LambdaMetaFactory、StringConcatFactory、LambdaFormなどのクラスの実装は、新しいAPIを使うように更新されるでしょう。   それでもサポート対象APIに移行する必要があるもの どのユースケースのサポートが必要かについて考えてみます。重要な特殊ケースとして挙げられるのは、モックやプロキシです。このような目的に使う場合、具体的には次の2つの特殊な性質を持つオブジェクトと考えることができます。 モックやプロキシの対象となる元のクラスと交換できる モックやプロキシの対象となるクラスのコンストラクタを呼び出さずに作成される 最先端のライブラリやフレームワークの多くでは、上記のことを実現するために低レベルのObjenesisプロジェクトの機能を利用しています。Objenesisでは、さまざまなメカニズム(いずれも通常の方法では利用できません)を使ってその機能を実現しています。その1つが、UnsafeのallocateInstance()です。 したがって、Unsafeを完全に削除するためには、このメソッドまたはそれと同等の機能のいずれかをサポート対象のAPIに移行する必要があります。Java開発者に、モックやプロキシを作成する公式な方法が必要な点は変わりません。 これを要約すれば、クラスのコンストラクタを呼び出さずにオブジェクトをインスタンス化する公式な方法が必要だということになります。別の言い方をするなら、領域を割り当ててインスタンスのメタデータをセットアップし、その結果として得られたオブジェクトを元のクラスのインスタンスの代わりに使用できることが必要です。 モック・オブジェクトの場合、ライブラリがモックを作成する際に使う特殊なAPIを提供する新しいjdk.testモジュールを作成して、この問題を解決することもできます。しかし、この方法ではプロキシの問題は解消されません。プロキシは、アプリケーションのコードでテスト時だけでなく実行時にも使用されるものだからです。 この問題は、シリアライズとも密接に関連しています。現在のデシリアライズのメカニズムでは、クラスで宣言されているコンストラクタがバイパスされるからです。そのため、賢いフレームワーク開発者は、シリアライズAPIに便乗してモックやプロキシのインスタンス化を行うことができます。このようにしてオブジェクトを作成することは、Javaの初期の頃からずっと可能でした。 ただし、長期的に見れば、このアプローチがこの先も可能とは限りません。今後、いずれかの時点でシリアライズの仕様が変更され、デシリアライズにコンストラクタが使われるようになることが明言されています。そうなれば、プロキシ用のライブラリが使用している重要なメカニズムが使えなくなることになります。   まとめ Unsafeから危険な部分を取り除き、サポート対象の標準APIに置き換える作業は、大きな進展を遂げています。フレームワークやライブラリの作成者は、そのような標準APIを使用できます。しかし、ゴールは見え始めているものの、作業はまだ完了してはいません。一部の機能では、置き換える同等な方法が存在しない、安全でないメソッドを依然として使用しているからです。 うまくいけば、このプロセスは今後数回のJavaリリースで完了し、Javaエコシステムが非標準機能に著しく依存している状況に終止符が打たれることでしょう。 Ben Evans Ben Evans(@kittylyst):Java Champion。New Relicのプリンシパル・エンジニア。先日発刊された『Optimizing Java』(O'Reilly)を含め、プログラミングに関する5冊の書籍を執筆している。jClarity(Microsoftにより買収)の創業者の1人であり、Java SE/EE Executive Committeeの元メンバーである。

※本記事は、Ben Evansによる”The Unsafe Class: Unsafe at Any Speed“を翻訳したものです。 ルールを破ることが可能だからといって、ルールを破るべきというわけではないが、しかるべき理由が存在する場合もある 著者:Ben Evans 2020年5月4日   ときには、ルールを破らねばならない場合もあるかもしれません。Javaプラットフォームにおいて、そのような行為は3...

コード・レビューでの5つのアンチパターン

※本記事は、Trisha Geeによる”Five Code Review Antipatterns“を翻訳したものです。 ベスト・プラクティスは誰もが気にするが、ワースト・プラクティスの方が参考になることもある 著者:Trisha Gee 2020年5月4日   コード・レビューは欠かせませんが、常に正しく行われているとは限りません。本記事では、コード・レビューを受けたときや、プル・リクエストを送信したときに、すべての開発者がおそらく経験してきた具体的なアンチパターンを取り上げ、正したいと思います。   アンチパターン:つまらぬあら探し 以下のシナリオについて考えてみてください。コード作成者たちは、何日とまではいかなくても、何時間もかけて、最適と思われるソリューションを作成しました。複数の設計オプションを考えたうえで、もっとも適切と思われる方法を選択しました。既存アプリケーションのアーキテクチャも考慮し、しかるべき場所に変更も加えました。そのうえで、プル・リクエストとしてそのソリューションを送信するか、またはコード・レビューのプロセスを開始しました。そして、エキスパートから寄せられたフィードバックは次のようなものでした。 「空白ではなく、タブを使うべきだ」 「このセクションの波括弧の位置が気に入らない」 「ファイルの最後に空行が入っていない」 「列挙型が大文字になっている。大文字は先頭の1文字だけにすべきだ」 スタイル面で新しいコードと既存のコードが一貫しているのは重要なことですが、どう考えても人間がスタイルについてレビューする必要はありません。人間によるレビューは高価で、コンピュータではできないことが可能です。スタイルの基準が守られていることをチェックする作業はコンピュータで簡単に実行できるものであり、コード・レビューの真の目的からは外れます。 コード・レビューの際に開発者がこういったコメントを多く見かけるとしたら、チームにスタイル・ガイドがないか、スタイル・ガイドはあってもスタイルのチェックが自動化されていないかのいずれかでしょう。これを解決するためには、checkstyleなどのツールを使ってスタイルのガイドラインが遵守されるようにするか、sonarqubeを使って一般的な品質やセキュリティの問題を見つけます。このような問題への警告は、人間によるレビューに頼らずに、継続的インテグレーション環境によって行うことができます。 場合によっては、このようなチェックを自動化しにくいこともあります。たとえば、コードのガイドラインが存在しない場合や、時間とともに社内のコード・スタイルが進化し、さまざまな部分で違いが生じている場合です。このような状況でチェックを自動化するアプローチも存在します。たとえば、取り決められたコード・スタイルを適用し、その他の変更は行わないというコミットを1回だけ行うと合意することもできます。または、バグ修正や機能追加によってファイルを変更するときは、そのファイルに対して新しいスタイルを適用するように申し合わせることもできます。自動化ツールは、変更されたファイルのみをチェックするように構成できます。 チーム内にさまざまなコード・スタイルが存在し、スタイルのチェックを自動化できない場合は、次のわなにもはまりがちです。   アンチパターン:一貫性のないフィードバック コード・レビューに開発者を呼ぶたびに、少なくとも1つの意見が増えることになります。増える意見は、おそらく1つだけではないでしょう。同時に複数の意見を持っている可能性もあります。ときに、コード・レビューが、アプローチの違いに関するレビュー担当者間の議論に移ってしまうことがあります。たとえば、ストリームまたは従来型のforループのいずれが最適かといったようなことです。チームのメンバーが同じコードについて正反対の意見を持っていたとしたら、開発者はどのようにして変更を行い、レビューを終えて、コードを本番環境にプッシュすればよいのでしょうか。 レビュー担当者が1人だけであっても、1回のレビューの中で、あるいは何回かのレビューの中で、意見が簡単に変わることもあります。レビュー担当者は、あるレビューでO(1)読取り操作のデータ構造を使うように作成者に強制しているかもしれません。一方で、次のレビューでは、同じレビュー担当者がさまざまなユースケースで複数のデータ構造があるのはなぜかと問い、単一構造で線形検索を行ってコードを簡略化することを提案するかもしれません。 このようなシナリオは、「ベスト・プラクティス」がどのようなものかについてチームに明確な考え方がない場合や、チームで何を優先すべきかがわかっていない場合に発生します。たとえば、次のような状況です。 Javaの最新スタイルに近づくようにコードを移行していくべきか。それとも、コードの一貫性の方が重要であるため、すべての場所で「従来型」の構造を使い続けるのか。 システムのすべてのパーツでデータ構造にO(1)読取り操作を持たせることが重要なのか。それとも、O(n)でも構わない部分があるのか。 設計に関する質問は、ほぼすべての場合において、「状況による」という答えになります。優れた答えに近づけるように、開発者はアプリケーションやチームの優先事項を理解しておく必要があります。   アンチパターン:最後の最後での設計変更 コード・レビューにおいて開発者が受け取る可能性のあるフィードバックのうち、もっとも落胆するのは、レビュー担当者がソリューションの設計やアーキテクチャに根本的に反対し、コードの完全な書き直しを強制した場合です。一連のレビューにおいて徐々に書き直させられるとき(次のセクションを参照)も、コードが容赦なく却下されて最初からやり直さざるを得なくなるときもあります。 コード・レビューは、設計レビューを行う時間ではありません。チームが従来型の「ゲートウェイ」コード・レビューを行っている場合、最終ステップとして別の開発者がコードを確認する前に、コードは動作してすべてのテストに合格しているはずです。その時点で、レビュー対象のコードに関して、数時間、数日、場合によっては数週間の作業(ただし、数週間ではないことを切に期待します。コード・レビューは小規模であるべきですが、それはまた別の話題です)が行われています。コード・レビューの際に、ベースとなる設計が誤っているという話をするのは、全員の時間を無駄にすることになります。 コード・レビューで設計レビューを行うことも可能ですが、それが目的なら、実装の最初の段階で行うべきです。その後、先に進みすぎる前に、開発者は意味のある名前や手順を含むいくつかのスタブ・クラスやスタブ・メソッド、テストなどを作成し、考え方の概略をまとめることができます。さらに、チームのメンバーからそのアプローチに関するフィードバックをもらうために、開発者がテキストや図を提出することもできるかもしれません。 チームのメンバーがゲートウェイ・レビュー(つまり、コードが完成して動作しているタイミング)で根本的な設計上の問題を見つけるようなら、もっと早い段階で問題を見つけるようにプロセスを更新すべきです。これは、前の段落で示したような別種のレビューを行うことかもしれません。あるいは、アイデアをホワイトボードに書き出して議論することや、ペア・プログラミングを行うことや、提案されたソリューションについてテック・リードと話し合うことかもしれません。最終コード・レビューで設計上の問題を見つけるというのは、開発の時間の無駄であり、コード作成者を著しく落胆させることでもあります。   アンチパターン:ピンポン・レビュー 理想の世界では、作成者がコードをレビューに提出したら、レビュー担当者がいくつかのコメントとともに明快なソリューションを提示します。作成者が提案された変更を反映させてコードを再提出したら、レビューが完了してコードはプッシュされます。これが一般的であれば、コード・レビューのプロセスを正す必要などありません。 現実の世界では、次のようなことがよく起こっています。 コード・レビューが始まります。 複数のレビュー担当者がさまざまな提案を行います。小規模で簡単なものもあれば、明らかな解決策のない曖昧なものや、複雑なものもあります。 作成者が何らかの変更を行います。最低でも、前述の簡単なものは修正します。あるいは、レビュー担当者を喜ばせるためにいくつかの変更を行うかもしれません。作成者は、詳細を確認するためにレビュー担当者に質問することもあるでしょう。または、変更を行わなかった理由を説明するためのコメントを追加するかもしれません。 レビュー担当者が戻ってきて、いくつかの更新を受け入れ、その他についてさらにコメントしたうえで、元々のコードで気に入らない部分を他にも見つけ、質問に回答し、レビューのコメントについて他のレビュー担当者や作成者と議論します。 コードの作成者はさらに変更を加え、コメントや質問の追加などを行います。 レビュー担当者は変更をチェックし、さらにコメントや提案の追加などを行います。 ステップ5と6が繰り返されます。永久に終わらないこともあるかもしれません。 理論上、このプロセスでは、変更やコメントはゼロに向かって収束し、やがてコードが完成するはずです。もっとも意気消沈するのは、繰り返しのたびに、クローズした古い問題の数以上の新しい問題が出てくるケースです。そのような場合、チームは「コード・レビューの無限ループ」に入っています。この無限ループは、いくつかの理由で起こります。 レビュー担当者が細かいあら探しをした場合や、一貫性のないフィードバックを行った場合に発生します。このような傾向を持つレビュー担当者にとって、指摘すべき問題や行うべきコメントはいくらでも存在します。 レビューに明らかな目的がない場合や、レビューの際に従うべきガイドラインがない場合に発生します。そのような場合、すべてのレビュー担当者が存在するすべての問題を発見しなければならないと感じているからです。 レビュー担当者のコメントにおいてコードの作成者に要求していることが明確でない場合に発生します。各コメントは必要な変更を示しているのでしょうか。すべての質問は、コードが自己記述的でなく改善する必要があると暗黙的に言っているのでしょうか。それとも、一部のコメントは次回のためにコードの作成者を教育しようとしているだけであり、提示されている質問はレビュー担当者の理解を助け、認識を深めるためだけのものなのでしょうか。 コメントは、根本的な問題なのかそうでないのかがわかるようにすべきです。また、承認前にコードを変更する必要があるかどうかを決めるのがレビュー担当者であるなら、コード作成者が取るべき正確なアクションが明確になっている必要があります。 さらに、レビューが「完了した」と判断する責任者は誰なのかを理解することが重要です。これを実現するのは、チェック済み項目や合格した項目を記したタスク・リストや、「問題なし」と判断する資格がある個人です。また、一般的には、こう着状態を打破して意見の相違を解消できる人も必要になります。これは上級開発者や、リーダー、アーキテクトかもしれませんし、場合によっては、チームで信頼を集めているコード作成者かもしれません。ただし、誰かがいずれかのタイミングで「レビューは完了した」または「この手順が終われば、レビューは完了する」と言う必要があります。   アンチパターン:ゴースト・レビュー ゴースト・レビューは、筆者が一番犯しがちなアンチパターンです。レビュー担当者やコード作成者が、コード・レビューに対応しないとき(場合によっては最初から)があります。重要な機能や興味深い機能の確認を依頼されているため、「きちんと見ること」ができる「しかるべきとき」までそのままにしているのかもしれません。または、レビューが大規模で、時間を長く取っておきたいのかもしれません。あるいは、コード作成者が、1回(または20回)の反復の後、それ以上コメントを読んで回答することができなくなり、「頭がすっきりするまで」待つことにしているのかもしれません。 似たような経験はないでしょうか。 原因は何であれ、レビュー・プロセスを担当する誰かがレビューに対応しないことがあります。その場合、その人がコードを見るまでレビューが停止状態になる可能性があります。これは無駄なことです。誰かが時間をかけてアセット(新しいコード)を作成しても、本番環境にアップされるまでは何の価値も生みません。実際、コードベースに含まれる他のコードにどんどん後れを取り、腐ってしまうでしょう。 ゴースト・レビューが発生する要因はいくつかあります。その1つが、大規模なコード・レビューです。変更されたファイルを何十、何百と苦労して確認したい人はいません。コード・レビューが、実際の仕事や、成果物の一部とは見なされていない点も要因の1つです。難しいコード・レビューや落胆するコード・レビューの経験も、大きな要因です。コーディング(通常、開発者はこれを楽しんでいます)を中止して、うんざりするほど単調で時間がかかる作業に向かいたいと思う人はいません。 以下に、ゴースト・レビューに対処するための提案を記します。     コード・レビューを小規模に保ちます。どの程度の規模かはチームごとに決める必要がありますが、レビュー作業は数週間とはならず、数時間か数日となるべきでしょう。 コード・レビューの目的や、レビュー担当者が確認すべきポイントを明確にします。「コードに潜んでいるかもしれないあらゆる問題を探す」というようなスコープでは、モチベーションを維持するのは困難です。 開発プロセスの中でコード・レビューの時間を設けます。 最後のポイントには、チームの規律が必要になるかもしれません。または、設定した目標や、開発者の生産性を判定する何らかの仕組みによって優れたコード・レビューを報奨するなどして、レビューを行う時間を取ることをチームで推奨した方がよいでしょう。   チームでできること 確かなコード・レビュー・プロセスを作成することに集中してください。筆者のブログにも記載していますが、ここでそのプロセスの一部について説明します。 コード・レビューを行う際には、考慮すべきことが数多くあります。開発者がコード・レビューのたびにそういった点をすべて心配するなら、どんなコードでもレビュー・プロセスの通過はほぼ不可能でしょう。誰にとってもうまくいくコード・レビュー・プロセスを実現する最適な方法は、以下の問いかけについて考えることです。     なぜチームはレビューを行うのでしょうか。明確に定義された目的があれば、レビュー担当者は仕事がしやすくなります。また、コード作成者も、レビュー・プロセスで予期せぬ嫌なことに遭遇しにくくなります。 チームのメンバーは何を求めているのでしょうか。目的がある場合、開発者はコードをレビューする際に、その目的にいっそう合った成果を生み出すことができます。 関与するのは誰でしょうか。レビューを行うのは誰で、意見の対立を解決する責任者は誰で、最終的にコードにゴー・サインを出すのは誰でしょうか。 チームはいつレビューを行うのでしょうか。また、レビューはいつ完了するのでしょうか。レビューは、開発者がコードに手を入れている間や、そのプロセスが終わるタイミングで、繰り返し行われる可能性があります。最終的にコードにゴー・サインを出すのはいつかという明確な取り決めがなければ、レビューが延々と続く可能性もあります。 チームはどこでレビューを行うのでしょうか。コード・レビューに特定のツールは必要ありません。そのため、作成者が自席で同僚に順を追ってコードを説明するなどして、簡単にレビューを行うこともできます。 このような問いかけに答えたら、チームは適切に機能するコード・レビュー・プロセスを作れるはずです。なお、レビューの目的はコードを本番環境に投入することであり、開発者の賢さを証明することではないことは心しておいてください。   まとめ 明確なコード・レビュー・プロセスを準備すれば、コード・レビューのアンチパターンを排除できます。少なくとも、緩和することはできます。コード・レビューを行うべきと考えているチームは多くても、なぜレビューを行うのかについての明確なガイドラインを定めているチームは多くありません。 チームが異なれば、必要になるコード・レビューの種類も異なります。アプリケーションが異なれば、ビジネスやパフォーマンスの要件が異なるのと同じです。最初のステップは、なぜチームにコード・レビューが必要なのかを明らかにすることです。そのうえで、チームは以下の作業に入ることができます。     簡単なチェックを自動化する(コード・スタイルのチェック、よくあるバグの検出、セキュリティ問題の発見など) いつレビューを行うか、何を求めるのか、誰がレビューの終了を判断するのかに関する明確なガイドラインを作成する コード・レビューを開発プロセスの重要なポイントにする なぜコード・レビューを行うのかにフォーカスすることは、チームがコード・レビュー・プロセスのベスト・プラクティスを作成する際に役立ちます。そうすることで、コード・レビューのアンチパターン回避がより一層容易になります。 Trisha Gee 金融、製造、ソフトウェア、非営利など、さまざまな業界と規模の企業を対象としたJavaアプリケーションの開発を経験。Java高パフォーマンス・システムを専門とし、開発者の生産性向上に情熱を注ぎつつ、オープンソース開発も少々行っている。スペインを拠点として活躍するJava Championで、Sevilla Java User Groupのリーダーを務める。健全なコミュニティの存在と、アイデアの共有が、失敗から学んで成功につなげることに役立つとの信念を持っており、JetBrainsのデベロッパー・アドボケートとして、日々発見したおもしろいことを発信している。Twitterのフォローは@trisha_geeから。

※本記事は、Trisha Geeによる”Five Code Review Antipatterns“を翻訳したものです。 ベスト・プラクティスは誰もが気にするが、ワースト・プラクティスの方が参考になることもある 著者:Trisha Gee 2020年5月4日   コード・レビューは欠かせませんが、常に正しく行われているとは限りません。本記事では、コード・レビューを受けたときや、プル・リクエストを送信したとき...

JDK最優秀機能トーナメント

※本記事は、Sharat Chanderによる”The Best of the JDK Face-Off“を翻訳したものです。 Javaは25年の間に発展を遂げ、言語、ライブラリ、ツール、ランタイムに多くのイノベーションを巻き起こしてきたが、その中で、もっとも重要なものは何だろうか 著者:Sharat Chander 2020年5月18日 Javaは誕生25周年を迎えます。1995年5月23日にJavaの最初のリリースが配信されてから、もう四半世紀がたっています。とてもそうとは思えないという方もいらっしゃるのではないでしょうか。 当初、この言語(と言語の背後にある概念)は興奮の渦の中心にありました。最初はOakという名前が検討され、後にGreenとなり、最終的にJavaという名前が採用されました。しかし、Javaが現在の地位を築いたのは、すばらしい才能を持ったエンジニア集団がシンプルで一致したビジョンを持っていたからです。それこそ、世界はハードウェア・ファーストからソフトウェア・ファースト+ネットワーク配信というパラダイム変革の途上にあるという認識です。新しいリリースのたびに使いやすさ、信頼性、セキュリティ、プラットフォームの独立性が向上するプログラミング言語と開発プラットフォームが生まれたのは、その変革があったからこそです。 しかし、Javaのイノベーションを進めたのは、エンジニアリングの非常に高度な専門知識ばかりではありません。この草創期に、進化し続けるテクノロジー・ランドスケープの需要を満たすため、Javaの設計に絶え間なく貢献したのは、ユーザーでした。そのスローガンは、「開発者にフォーカスしよう」でした。現在の開発の世界では当然のように思えますが、1990年代当時、開発者ファーストは一般的ではありませんでした。 現在、Javaのテクノロジーとエコシステムでは、慎重さとフォーカスを2つの均等な足場として継続的にイノベーションを推進するという、他にはない共生関係を実現しています。その結果、とどまることのない生産性が皆さんのような開発者に提供されています。 Javaコミュニティはこの重要なJavaのマイルストーンを祝っています。そこで、JavaチームとJava Magazineでは、ちょっとした息抜きを兼ねて皆さんにご参加いただく、JDK最優秀機能トーナメントを企画しました。 ご存じのように、Javaには記憶に残る重要なイノベーションを実現してきた豊かな歴史があります。Java 8では、開発者の生産性が驚くほど向上しました。その後も、Java 9から現在のJava 14リリースに至るまで、すばらしいイノベーションが蓄積されています。しかし、もっとも重要な進化とはいったい何でしょうか。それが本日の問いかけです。 Javaチームでは、ここ数年間で特に話題に上がることが多かった機能の一覧を作成しました(小さな機能も大きな機能も含みます)。皆さんには、ぜひお気に入りの機能に投票していただきたいと思います。 これを軽々しく考えないでください。実際にどの機能に興奮し、どの機能によって生産性が向上し、Javaプログラミングの専門知識をさらに得たのかについて、自己分析してじっくりと考えてみてください。 これからの数週間、JavaチームのTwitterアカウント(@Java)で、重要な機能を以下の4つの部門に分けてトーナメントを行います。 言語 ライブラリ ツール ランタイム 投稿される各対決では、24時間、Twitterからの投票を受け付けます。毎日新しい対決が行われ、最終的には決勝に進んだ2つが対決します。このトーナメントは、これからの数週間にわたって@Javaでソーシャルに行われ、勝者となる1つの機能が選ばれます。 開始にあたり、図1にトーナメント表を示します。見てわかるように、楽な対決はありません。 図1:JDK対決トーナメント表 | 大きな画像を表示 このトーナメント表を見るとき、以下の問いについてよく考えてください。      いずれの機能を選ぶか 自分にとっていずれの機能が重要か いずれの機能がさらなる学習につながったか どのようにして投票先を決めるべきか 準備はよいでしょうか。それでは、全員の認識が一致するように、4つの部門について詳しく確認します。見慣れない機能がある方のために(一部はJava 14の新機能です)、最新情報を確認できるお勧めのリソースも示します。   言語部門 デフォルト・メソッド:デフォルト・メソッドは、インタフェースでdefault修飾子を使って宣言するメソッドです。このメソッドの本体は、対象のインタフェースを実装しており、かつ対象のメソッドをオーバーライドしていないすべてのクラスにおいてデフォルトの実装になります。これにより、既存の(すでに広く使われているものである可能性もあります)インタフェースに新機能を追加できるようになります。さらに汎用的な使い方として、この機能は動作の多重継承をサポートするメカニズムにもなります。 instanceofのパターン・マッチング:パターン・マッチングとは、プログラムの共通ロジックを簡潔かつ安全に表現できるようにする方法です。具体的には、条件に応じてオブジェクトからコンポーネントを抽出します。これは、JDK 14のプレビュー言語機能です。詳しくはこちらをご覧ください。 テキスト・ブロック:テキスト・ブロックは、ほとんどのエスケープ・シーケンスを省略できる複数行文字列リテラルです。文字列を予測可能な方法で自動的に書式設定するだけでなく、開発者がその書式設定を制御することもできます。 Javaの型アノテーション:JEP 104によって、Javaプログラミング言語の構文で、型の宣言だけでなく、型の使用でもアノテーションを記述できるようになっています。 ローカル変数の型推論(var):JEP 286でJava言語が拡張され、宣言時に初期化されるローカル変数の型推論が行われるようになっています。 レコード:JEP 359で定義されるレコードにより、コンパクトな構文で浅い不変データを格納する透過的なクラスを宣言できます。これは、JDK 14のプレビュー言語機能です。 switch式:JEP 361によってswitchが拡張され、文または式として使用できるようになっています。どちらの形式でも、従来型のcase ... :ラベル(フォールスルーあり)または新しいcase ... ->ラベル(フォールスルーなし)を使用できます。さらに、switch式から値を生成する新しい文も使用できます。以上の変更により、毎日のコーディングが簡略化され、switchでパターン・マッチングを利用する準備が進むことになります。 メソッド参照:メソッド参照は、ラムダ式を使って匿名メソッドを作成する方法です。しかし、ラムダ式の中には、既存のメソッドを呼び出す以外に何もしないものもあります。そのような場合は一般的に、既存のメソッドを名前で参照する方がシンプルです。メソッド参照を使うことで、これが可能になります。メソッド参照は、すでに名前を持つメソッドに対応する、コンパクトで読みやすいラムダ式です。   ライブラリ部門 複数解像度イメージ:JEP 251では、複数解像度イメージAPIを定義しています。これにより、解像度が異なるイメージの操作や表示が容易にできるようになります。 JVM定数:JEP 334では、重要なクラスファイルとランタイム・アーティファクト、とりわけ定数プールからロードできる定数の、名前による説明をモデリングするAPIを導入しています。 不揮発性バイト・バッファ:JEP 352では、JDK固有のファイル・マッピング・モードを新しく追加しています。これにより、FileChannel APIを使って不揮発性メモリを参照するMappedByteBufferインスタンスを作成できるようになっています。 HTTP/2クライアント:JEP 110では、HTTP/2とWebSocketを実装し、従来のHttpURLConnection APIを置き換えることができる新しいHTTPクライアントAPIを定義しています。 Stack-walking API:JEP 259では、スタックを横断するための効率的な標準APIを定義しています。このAPIにより、スタック・トレース内の情報のフィルタリングや遅延アクセスが容易に可能となっています。 TLS 1.3:JEP 332では、Transport Layer Security(TLS)プロトコルのバージョン1.3を実装しています。 便利なファクトリ・メソッド:JEP 269では、少数の要素を持つコレクションやマップのインスタンス作成を便利にするライブラリAPIを定義しています。このAPIにより、Javaプログラミング言語にはコレクション・リテラルを記述する方法がないという欠点が緩和されます。 非推奨の強化:JEP 277では、@Deprecatedアノテーションを刷新するとともに、APIライフサイクルを強化するツールを提供しています。   ツール部門 JDK Mission Control:JDK Mission Control(JMC)は、OpenJDKおよびOracle JDK用のツール・スイートで、本番環境でのプロファイリングや診断に特化しています。 jlink:一連のモジュールとその依存性をアセンブルして最適化し、カスタム・ランタイム・イメージを作成できるツールがJEP 282によって誕生しています。 jpackage:JEP 343により、自己完結型Javaアプリケーションのパッケージングを行うツールが誕生しています。 jdeprscan:この静的解析ツールでは、JARファイル(などのクラス・ファイルの集合体)をスキャンし、使用されている非推奨のAPI要素を検出します。 単一ソース・コード・ファイルによるプログラムのランチャ:JEP 330によってjavaランチャが拡張され、1つのJavaソース・コード・ファイルで提供されるプログラムを実行できるようになっています。たとえば、「シェバン」ファイルによるスクリプトや、それに関連する手法から使用できます。 Javadoc検索:JEP 225により、検索ボックスが追加され、標準のドックレットで生成されたAPIドキュメントのページすべてのヘッダーに表示されるようになっています。この検索ボックスを使用して、ドキュメント内のプログラム要素や、タグが付いた単語および語句を検索することができます。 jshell:JEP 222では、Javaプログラミング言語の宣言、文、式を評価するインタラクティブなツールを提供しています。さらに、他のアプリケーションでこの機能を活用できるようにするAPIも提供しています。 マルチリリースJARファイル:JEP 238でJARファイルの形式が拡張され、1つのアーカイブにおいてJavaの複数のリリースに固有なバージョンのクラス・ファイルを共存させることができるようになっています。   ランタイム部門 アプリケーション・クラスデータ共有:起動時間の短縮とフットプリント・サイズの削減を目的として、JEP 310で既存のクラスデータ共有機能が拡張されています。この拡張により、共有アーカイブにアプリケーション・クラスを配置できるようになっています。 文字列の圧縮:JEP 254で、これまでよりスペース効率のよい、文字列の内部表現が採用されています。 ZGC:Zガベージ・コレクタ(ZGC)は、スケーラブルで待機時間の短いガベージ・コレクタです。停止時間を10ミリ秒未満にする、ヒープやライブセットのサイズが増加しても停止時間が増加しない、サイズが8 MBから16 TBのヒープを扱う、という目標を満たすように設計されています。 Graal JIT:JEP 317により、Linux/x64プラットフォームでJavaベースのJITコンパイラであるGraalが試験運用版JITコンパイラとして使用できるようになっています。 モジュール式JDK:JSR 376で仕様が規定され、JEP 261で実装されているJavaプラットフォーム・モジュール・システムを使用し、JDKをモジュール化します。詳しくは、JEP 200をご覧ください。 スレッドローカルなハンドシェイク:JEP 312により、グローバルVMセーフポイントを実行せずにスレッド上でコールバックを実行する方法が導入されています。この方法により、すべてのスレッドを一律に扱うだけでなく、個々のスレッドを安価に停止できるようになっています。 JDK Flight Recorder:JEP 328では、JavaアプリケーションやHotSpot JVMのトラブルシューティングを目的とした、低オーバーヘッドのデータ収集フレームワークを提供しています。 便利なNullPointerException:JEP 358により、JVMで生成されるNullPointerExceptionの使い勝手が向上し、どの変数がnullだったのかまで詳しくわかるようになっています。   まとめ ぜひ、じっくり考えてみてください。Twitterの投票を行うときは、スレッドになぜそのように投票したのかを説明するコメントを投稿してください。さらに、その対決についての意見も記載してください。ハッシュタグ#MovedByJavaを使えば、コミュニティのより多くの皆さんが会話に参加できます。 なお、このトーナメントは、合意に基づいて行うわけではありません。すべての方に、意見の違いを聞き、理解し、受け入れてもらいたいと思っています。活発な議論を行うのは健全なことですが、常に礼儀正しく、専門家らしく、偏見を排除して誠実であるよう心がけてください。それこそが、Javaエコシステムの精神です。 ハッピー・バースデー、Java。最後にもう1つ。2020年5月20日(火)午前9時(太平洋標準時)から、この節目を祝うパネル・ディスカッションが行われます。どうぞご参加ください。 Sharat Chander Bell Atlantic、Verizon、Sun Microsystems、Oracleなどの企業で、20年間IT業界に従事。Java開発ツール、グラフィック・デザイン、プロダクト/コミュニティ管理に関する経験や技術専門性を有する。15年にわたってJavaコミュニティに積極的に関与しており、Javaの認知度向上、受容促進、採用促進、支持拡大の活動を行っている。OracleのJava開発者リレーション部門のディレクターとして、7年にわたってJavaOneカンファレンスのコンテンツ・チェアパーソンを務め、カンファレンスの技術コンテンツ戦略や、カンファレンスへのJavaコミュニティの参加を担当。世界中の開発者プログラムに数多く参加し、基調講演も頻繁に行っている。米国メリーランド大学で企業金融の学士号を、ロヨラ大学メリーランドで国際ビジネスのMBAを取得。世界のさまざまな開発者イベントやJavaコミュニティの会合にも登場する。Javaの啓発にあたっていないときは、故郷のボルチモア・オリオールズの熱狂的なファンとして野球にも情熱を注いでいる。 Twitterハンドル:@Sharat_Chander

※本記事は、Sharat Chanderによる”The Best of the JDK Face-Off“を翻訳したものです。 Javaは25年の間に発展を遂げ、言語、ライブラリ、ツール、ランタイムに多くのイノベーションを巻き起こしてきたが、その中で、もっとも重要なものは何だろうか 著者:Sharat Chander 2020年5月18日 Javaは誕生25周年を迎えます。1995年5月23日にJava...

Java 14におけるinstanceofのパターン・マッチング

※本記事は、Mala Guptaによる”Pattern Matching for instanceof in Java 14“を翻訳したものです。 instanceofのパターン・マッチングでJavaのinstanceof演算子の使用が簡略化されるため、コードが安全になり書きやすくなる 著者:Mala Gupta 2020年5月18日   多くのJava開発者は、参照変数と型を比較する際にinstanceof演算子を使用しています。この結果がtrueである場合、次の手順は明らかです。比較対象とした型に明示的にキャストして、そのメンバーにアクセスします。この手順には、型と比較 – 真であれば – 型にキャストという繰り返しが含まれています。 次のようなコードをよく見かけます。 1. void outputValueInUppercase(Object obj) { 2. if (obj instanceof String) { 3. String str = (String) obj; 4. System.out.println(str.toUpperCase()); 5. } 6. } このコードでは、2行目で参照変数objとString型を比較しています。結果がtrueである場合、3行目のコードでローカル変数strを定義し、objを明示的にString型にキャストして変数strに代入します。4行目のコードでは、strが参照するStringの値のメンバーにアクセスできます。 次のコードは、instanceofのパターン・マッチングを使って、上記の冗長なコードを除去する方法を示しています。具体的には、String型の直後にパターン変数strを記述するという形式でinstanceof演算子を使っています。 1. void outputValueInUppercase(Object obj) { 2. if (obj instanceof String str) { 3. System.out.println(str.toUpperCase()); 4. } 5. } このコードでは、instanceofの条件がtrueである場合、パターン変数strは変数objが参照するインスタンスにバインドされます。これにより、toUpperCase()メソッドを呼び出す前に、新しい変数を定義する必要や、その変数を明示的にStringにキャストする必要はなくなります。 パターン変数 パターン変数は、宣言と同時に初期化されたfinalなローカル変数です。その他のfinalなローカル変数の場合、宣言だけを行い、後で代入することも可能です。なお、パターン変数は暗黙的にfinalであるため、別の値を代入することはできません。 パターン変数のスコープは限られています。elseブロックでアクセスしようとした場合、エラーが発生します。 これはわかりにくいかもしれません。次のコードでは、PatternMatchingクラスでパターン変数(s)と同じ名前のインスタンス変数を定義していますが、コードはコンパイル可能です。クラスで同名の静的変数を定義している場合も同様です。この場合、elseブロック内のsが参照しているのは、ifブロックのパターン変数ではありません。 public class PatternMatching { private String s = "initial value"; void outputValueInUppercase(Object obj) { if (obj instanceof String s) { System.out.println(s.toUpperCase()); // refers to pattern var } else { System.out.println(s.toLowerCase()); // refers to field s } } } equals()メソッドを簡略化する パターン・マッチングを使うことで表記を簡略化できますが、間違いも犯しがちです。一般的に、開発者は次のようにしてクラスのequals()メソッドをオーバーライドします。次のコードでは、Monitorクラスでmodel(String値)とprice(double値)という2つのフィールドを定義しています。 public class Monitor { String model; double price; @Override public boolean equals(Object o) { if (o instanceof Monitor) { Monitor other = (Monitor) o; if (model.equals(other.model) && price == other.price) { return true; } } return false; } } instanceofのパターン・マッチングを使い、if文をさらに簡略化することで、上記のequals()メソッドを簡略化することができます。次のコードをご覧ください。 public class Monitor { String model; double price; @Override public boolean equals(Object o) { return o instanceof Monitor other && model.equals(other.model) && price == other.price; } } 簡潔で読みやすいコード instanceofのパターン・マッチングを使用して、さまざまな場所でコードを簡略化することができます。次のコードのisFeasibleメソッドに注目します。 class Project { Lang lang; Emp projManager; private boolean isFeasible(Project project, Location location) { if (project.getLang() != Lang.PASCAL) { return false; } if (!(project.getProjManager() instanceof CEO ceo)) { return false; } return ceo.availableAt(location); } public Emp getProjManager() { return projManager; } public void setProjManager(Emp projManager) { this.projManager = projManager; } public Lang getLang() { return lang; } public void setLang(Lang lang) { this.lang = lang; } } Replace from here to the end of the code listing with enum Lang {JAVA, PASCAL} class Emp { } class Location { } class CEO extends Emp { Location loc; boolean availableAt(Location location) { return loc.equals(location); } } 次のコードは、instanceofのパターン・マッチングを使用して、isFeasibleメソッドをどのように簡略化できるかを示しています。ここでは、冗長なキャストを除去してから、if文を簡略化しています。 1. private boolean isFeasible(Project project, Location location) { 2. return project.getLang() == Lang.PASCAL && 3. project.getProjManager() instanceof CEO ceo && 4. ceo.availableAt(location); 5. } 上記のコードでは、3行目でinstanceofのパターン・マッチングを使用しています。 Stream APIでinstanceof のパターン・マッチングを使用する パターン変数の導入によって、さまざまな改善の可能性が生まれます。processという名前のメソッドが次のように定義されているとします。 void process(Font font, int size) { final ArrayList list = modules.getChildren(); for (Iterator i = list.iterator(); i.hasNext(); ) { final Object o = i.next(); if (o instanceof LetterNode) { final LetterNode letterNode = (LetterNode) o; if (letterNode.isLatin()) { if (!isLetterTrueFont(letterNode.getNodeValue(), font, size)) { i.remove(); } } } } } 上記のコードは、instanceofのパターン・マッチングとStream APIを使用するコードを渡すことで簡略化することができます。次のコードをご覧ください。 void process(Font font, int size) { modules.getChildren().removeIf(o -> o instanceof LetterNode letterNode && letterNode.isLatin() && !isLetterTrueFont(letterNode.nodeValue, font, size)); } コード・ブロックでジェネリクスと複数のinstanceofを使用している場合 instanceofのパターン・マッチングは、ジェネリクスでも動作します。 instanceofのパターン・マッチングを使用できる場所を探すには、instanceof演算子と変数の明示的なキャストを使っている場所を探します。たとえば次のコードでは、instanceof演算子と明示的キャストが複数回使われています。 void processChildNode(Tree tree) { if (tree.getChildNodes() instanceof Map) { Map<?, Node> childNodes = (Map<?, Node>) tree.getChildNodes(); if (childNodes.size() == 1) { Node = childNodes.get("root"); if (node instanceof LetterNode) { LetterNode = (LetterNode) node; System.out.println(letterNode.isLatin()); } } } } 上記のコード・ブロックは、次のように簡略化することができます。 void processChildNode(Tree tree) { if (tree.getChildNodes() instanceof Map<?, Node> childNodes && childNodes.size() == 1 && childNodes.get("root") instanceof LetterNode letterNode) { System.out.println(letterNode.isLatin()); } } 上記の例では、キャストのチェックを行っていません。この点について疑問に思う方のために説明すると、getChildNodes()メソッドはMap<String, Node>型の値を返します。Map<String, Node>をMap<?, Node>にキャストする操作は、アップキャストであるため問題はありません。 instanceofのパターン・マッチングはプレビュー言語機能 instanceofのパターン・マッチングは、JEP 305に基づく、Java 14のプレビュー言語機能としてリリースされました。実際のところ、プレビュー機能とは、開発者に使ってもらう準備は整っているものの、開発者からのフィードバックによっては、今後のJavaリリースで細かい部分が変更される可能性もある機能という意味です。 Javaに6か月の新しいリリース周期が導入されたことで、新しい言語機能はプレビュー機能としてリリースされています。つまり、機能としては完成していますが、確定版ではありません。言語機能はAPIとは異なり、将来において廃止される可能性はありません。そのため、instanceofのパターン・マッチングに関してフィードバックがある方は、JDKメーリング・リスト宛てにお送りください。 プレビュー言語機能を使用するためには、その機能を使用しているコードをコンパイルおよび実行するときに、機能を有効化する必要があります。こうすることで、プレビュー言語機能が誤って使用されることがないようになっています。 instanceofのパターン・マッチングを含むソース・ファイルをコンパイルするためには、--enable-previewオプションと-release 14オプションを使用する必要があります。コマンドラインを使ってJava14.javaというソース・ファイルをコンパイルする例を次に示します。 javac --enable-preview --release 14 Java14.java プレビュー機能は変更される可能性もあることを強調するため、先ほどのコマンドを実行した際には、図1に示すようなコンパイラ警告が表示されます。   図1:プレビュー言語機能が使われているコードに対して表示されるコンパイラ警告 クラスJava14を実行するときも、--enable-previewオプションを使う必要があります。 java --enable-preview Java14 まとめ Java 14のプレビュー言語機能である、instanceofのパターン・マッチングを使うことで、毎日読み書きするコードを簡略化することができます。instanceof演算子にパターン変数を追加することで、コードが簡略化されて読み書きしやすくなります。Javaの今後のバージョンでは、switch構造やその他の文で使用されるように拡張が行われる可能性があります。   Mala Gupta Mala Gupta(@eMalaGupta):Java Champion。JetBrainsのデベロッパー・アドボケート。eJavaGuru.comの創設者で、認定試験に関する何冊かの人気書籍を執筆。Delhi Java User Groupの共同リーダーであり、Women Who Codeデリー支部のディレクターも務める。

※本記事は、Mala Guptaによる”Pattern Matching for instanceof in Java 14“を翻訳したものです。 instanceofのパターン・マッチングでJavaのinstanceof演算子の使用が簡略化されるため、コードが安全になり書きやすくなる 著者:Mala Gupta 2020年5月18日   多くのJava開発者は、参照変数と型を比較する際にinstan...

Java Magazine May 2020

最新記事  Java 14におけるinstanceofのパターン・マッチング Java ChampionでJetBrainsのデベロッパー・アドボケートを務めるMala Gupta氏による解説です。instanceofのパターン・マッチングによって、Javaのinstanceof演算子の使用が簡略化されます。バインド変数が導入されることで、変数を追加したり、明示的にキャストしたりする必要がなくなり、コードが安全で読みやすく、書きやすくなります。   JDKベスト機能 Javaの25周年を祝うにあたり、このプラットフォームで特に話題になったイノベーションの一部を振り返ってみましょう。さらに、Sharat Chanderが皆さんの投票をお待ちしています。ぜひご参加ください。    その他の記事 コード・レビューでの5つのアンチパターン コード・レビューは欠かせませんが、常に正しく行われているとは限りません。この記事では、コード・レビューを受けたり、プル・リクエストを送信したりしたときに誰もが経験してきた具体的なアンチパターンについて、Trisha Gee氏が取り上げて正します。これを機に、優れた慣例を身につけましょう。   Unsafeクラス:どんなスピードでも危険 ときには、ルールを破らねばならない場合もあるかもしれません。そんな場合に使われるテクニックの1つがUnsafeです。Unsafeは潜在的な危険性をはらんでいます。他の方法ではできない形で、そしてプラットフォームの確立されたルールを破る形で、特定のことを行う方法を提供するからです。Ben Evans氏が詳しく解説します。   テストを容易にするJUnit 5.6の新機能 Mert Çalışkan氏は、JUnitの最新アップデートに満足しています。実行順序の明示的な定義、宣言的タイムアウトの定義に加え、JREバージョンや環境変数、特定のシステム・プロパティに基づく条件付きテスト実行など、いくつかの機能を紹介します。望むなら、テストをパラレル実行することさえできます。

最新記事  Java 14におけるinstanceofのパターン・マッチング Java ChampionでJetBrainsのデベロッパー・アドボケートを務めるMala Gupta氏による解説です。instanceofのパターン・マッチングによって、Javaのinstanceof演算子の使用が簡略化されます。バインド変数が導入されることで、変数を追加したり、明示的にキャストしたりする必要がなくなり、コー...

WebLogic Serverと組み合わせて使用するATPデータベース

※本記事は、Stephen Feltsによる"ATP Database use with WebLogic Server"を翻訳したものです。 このブログでは、WebLogic Server(WLS)データソースと組み合わせた、オラクルのAutonomous Transaction Processing(ATP)サービスの使用について解説します。この点について扱ったドキュメントはさまざまなソースから公開されていますが、この記事ではWLSに絞って情報をまとめ、オラクルのお客様が直面してきた問題に対するソリューションを紹介します。この記事では、特にATP-Sについて解説しています(ATP-DやRACベースのバージョンではありません)。   はじめに: ATPの概要は、Oracle CloudのATPのページでご覧になれます。ATPについてさらに詳しく説明したドキュメントには、ATPのユーザー・ガイドがあります。   Configuring a WebLogic Data Source to use ATPというブログでは、OCIコンソールからATPデータベースを新規作成する場合のスクリーンショットが掲載されており、データベースのADMINユーザーのパスワードと、クライアント資格証明ウォレットのパスワードを設定しています。今回の記事は、その手順をすでに完了し、ウォレットのZIPファイルをダウンロード済みであることを前提としています。統一を取るために、このブログでは/shared/atpに保存されているウォレット・ファイルと同じディレクトリ構造を使用します。WLSで使用するためにそれらのファイルを変更する必要はありません(1つだけ例外がありますが、後ほど説明します)。ファイルの使用については、データソースの構成と関連させて、この後詳しく解説します。必要な情報は、tnsnames.oraファイルにあるエイリアス名だけです。WLSでは、「dbnnnnnnnnnnnn_tp」という形式のエイリアス名を使用する必要があります。このサービスは、WLSのトランザクション処理を適切に実行できるように構成されます。 前述のリンク先のブログには、WLS管理コンソールを使用して、ATPデータベース向けのWLSデータソースを作成する際のスクリーンショットが掲載されています。コンソールを使用するときには、こちらのプロパティの暗号化に関するブログで説明されている手順でJKSパスワードを暗号化できます。 このブログには、オンラインのWLSTまたはRESTを使用してデータソースを作成するための実用的なスクリプトが掲載されています。スクリプトを実行する前に、いくつかの前提条件を確認しましょう。 19.3のドライバが付属し、JDK 8をサポートするWebLogic Serverのリリース12.2.1.4.0および14.1.1.0.0であれば、ATPをそのまま使用できます。もっともシンプルなプラットフォームで開始するために、上記のいずれか、またはそれ以降のリリースを使用することをお勧めします。この後のセクションでは、ATPを実行するための要件を説明します。   動作保証 WebLogic Server12.2.1.3.0以降であれば、ATPの動作が保証されています。この後のセクションでは、現在動作保証またはサポートされていない古いバージョンを使用する場合の情報が掲載されていることがあります。   JDKの前提条件: WebLogic Server 12.1.3以降ではJDK 8がサポートされています。`java -version`を実行してJDKのアップデート番号を確認し、1.8.0_169以降であることを確かめてください。JDK 8の年4回のCPUに追いついていない場合(困ったことですが)は、少なくともアップデート169か、それ以降をインストールするか(こちらを強く推奨します)、JCE Unlimited Strength Jurisdiction Policy Files 8をダウンロードします。関連するREADMEファイルで、インストールの注意事項を確認してください。そうしないと、データベースに接続するときに'fatal alert: handshake_failure'と表示されてしまいます。  WebLogic Server 10.3.6~12.1.2は、JDK 7で動作します(JDK 8はサポートされていません)。JDK 7で実行する場合は、JCE Unlimited Strength Jurisdiction Policy Files 7をダウンロードしてインストールする必要があります。   JDBCドライバの前提条件: WLS 12.2.1.3.0には、12.2.0.1のOracleドライバが付属します。このドライバを使用するにあたっての特別な要件はありません。また、サンプルのスクリプトを変更せずに使用できます。そのため、構成(と認証)がシンプルです。この場合は次のセクションに進んでかまいません。 WLSバージョン12.1.3~12.2.1.2.0には、12.1.0.2のOracleドライバが付属します。WLSバージョン10.3.6~12.1.2には、11.2.0.3のドライバが付属します。11.2.0.3のドライバは、18cおよび19cのデータベース・サーバーで使用できます。こちらのリンクにある情報を参考にして、11.2.0.3ドライバを12.1.0.2ドライバにアップグレードできます(ドライバのアップグレードがサポートされるのはWLSのみで、JRFとFAは非対応です)。11.2.0.3と12.1.0.2のドライバでTLSv1.2をサポートするには、wlserver_10.3/server/lib/ojdbc7.jarにパッチを適用する必要があります。こちらのリンクを参照してjarファイルをダウンロードするか、バグ23176395にパッチを適用してください。詳細については、MOS note 2122800.1を参照してください。 12.2.xより前のドライバを使用する場合は、サンプルのスクリプトからも分かるように、SSLを構成するためのドライバ接続プロパティの使用はサポートされていません(スクリプトから削除する必要はありません。無視されます)。代わりに、こちらのリンクで説明されているように、コマンドライン・システムのプロパティを使用する必要があります。たとえば、-Doracle.net.tns_admin=<クライアント資格証明への絶対パス>、-Doracle.net.wallet_location=file://<クライアント資格証明への絶対パス>、-Doracle.net.ssl_version=1.2をJVM引数として設定します。 11gで動作しているFMW/FAの場合は、ojdbc7.jarはwlserver_10.3/server/lib/ojdbc7.jarにあり、ojdbc7dms.jarはmodules/oracle.jdbc_11.1.1/ojdbc7dms.jarにあります。更新された18c/19cバージョンのoraclepki.jar、osdt_core.jar、osdt_cert.jarを、oracle_common/modulesディレクトリにコピーする必要もあります(たとえば、18.3ドライバのjarファイルは、https://www.oracle.com/database/technologies/appdev/jdbc-ucp-183-downloads.htmlからダウンロードできます)。また、新しいセキュリティ・プロバイダとして、oracle.secruity.pki.OraclePKIProviderを3番目の位置でjdk1.7/jre/lib/security/java.securityに追加します(これでSSOまたはPKCS12ウォレットを使用できます)。   HTTPプロキシのサポート: HTTPプロキシの構成などの比較的新しい機能を使用する場合は、Oracleドライバのjarファイルを新しいバージョン(18c以降)に更新できますし、更新が要求される場合もあります。この場合、JDK 8を実行する環境が必要になります。 クライアントがファイアウォールで保護されていて、HTTPプロキシをインターネットに接続する必要があるネットワーク構成の場合は、2つの選択肢があります。まず、ネットワーク管理者に頼んで、HTTPプロキシを経由せず、ポート1522を使用してoraclecloud.comドメインのホストにアウトバウンド接続してもらう方法があります。JDK 8で動作している場合のもう1つの選択肢は、Oracle 18c以降のJDBCシン・クライアントにアップグレードすることです。そうすると、HTTPプロキシ経由の接続が有効になります。jarファイルの入手とCLASSPATH/PRE_CLASSPATHの更新の方法については、Oracle 18.3 Database Support with WebLogic Serverのブログを参照してください。さらに、dbnnnnnnnnnnnn_tpを更新する必要があります。tnsnames.oraファイルのサービス・エントリを更新して、"address="を"address= (https_proxy=proxyhostname)(https_proxy_port=80)"に変更します。そうしないと、データベースへの接続がハングしたり、ホストが見つからなかったりします。   WLSまたはRESTを使用した構成: これで、必要な資格証明ファイル、JDK、ドライバのjarファイルがそろい、データソースを作成する準備ができました。 オンラインWLSTのスクリプトは、こちらのオンラインWLSTのスクリプトへのリンクから入手できます。サーバーが適切なJDKとドライバのjarファイルで起動している場合、以下を実行するだけで、スクリプトを実行できます。 java weblogic.WLST online.py RESTスクリプトは、こちらのRESTスクリプトへのリンクから入手できます。サーバーが起動している場合、ドメインのホーム・ディレクトリから以下を実行するだけで、スクリプトを実行できます。 sh ./rest.sh どちらのスクリプトも、同じデータソース記述子ファイルを作成し、データソースをサーバーに展開します。記述子を見て、データソースの構成を確認してみましょう。各スクリプトで設定する必要のある変数は一番上にあるので、ロジックを変更することなく、すぐにスクリプトを更新できます。WLSTはpython変数を、RESTスクリプトはshell変数を使用しています。  dbnnnnnnnnnnnn_tpという形式のエイリアス名(変数はserviceName)は、tnsnames.oraファイルから取得されます。URLは@aliasの形式である"jdbc:oracle:thin:@dbnnnnnnnnnnnn_tp"を使用して生成されます。そのためには、oracle.net.tns_adminのドライバ・プロパティを使用して、tnsnames.oraファイルがあるディレクトリを指定する必要があります(変数はtns_admin)。   tnsnames.oraのURL情報は長い形式を使用しているため、プロトコルをTCPSに指定できます。 また、データソース名(変数はdsname)がJNDI名の生成に使用され、サンプルでは"jndi."が先頭に付けられています。これはアプリケーションの要件に応じて変更できます。 テストとパフォーマンスを最適なものとするために、テスト用の表の名前は"SQL ISVALID"とすることをお勧めします。社内の標準をベースにして、他の接続プールのパラメータを設定することもできます。 ATP(ATP-S)の最新バージョンでは、コンテナ・データベース(CDB)のプラガブル・データベース(PDB)の1つにアクセスできます。PDBでの操作の多くは、通常のオラクル・データベースの場合と同様です。ADMINというユーザーがあり、SYSDBAロールはありませんが、いくらかの管理権限が付与されていて、スキーマ・オブジェクトの作成や権限の付与などが可能です。セッションの数は100 * ATPコア数に設定されています。表領域を作成することはできず、デフォルトで使用できる表領域にはDATA、一時表領域にはTEMPという名前が付けられています。ブロック・サイズは8kに固定されているため、サイズが概算で6kを超える索引を作成することはできません。   制限に関するその他の情報は、Autonomous Transaction Processing for Experienced Oracle Database Usersで参照できます。 ATPは、GRIDやRACがインストールされない構成になっています。つまりFANがサポートされておらず、WLS GENERICデータソースのみを作成できます(マルチ・データソースとActive GridLinkは使用できません)。ドライバがONSサーバーからFANイベントを取得しようとする場合がありますが、構成されていないため取得できません。この状況を回避するには、ドライバ・プロパティのoracle.jdbc.fanEnabled=falseを設定する必要があります。18c以降のドライバを使用している場合は、このプロパティは不要です。 接続を作成するには、データソースのユーザーとパスワード(変数名はuserとpassword)を指定する必要があります。サンプルでは、データベースの作成時に構成されたAdminユーザーを使用しています。大抵はアプリケーションを使用するために、追加のユーザーを作成することになります。SQLPlusなどの任意のツールを使用してスキーマ・オブジェクトを作成するか、WLS utils.Schemaを使用してオブジェクトを作成できます。関連するSQL DDL文については、ユーザー・ガイドのこちらのリンクで説明されています。データソースの記述子では、パスワードは暗号化されます。   構成の残りの部分では、おもにクライアントとデータベース間の双方向SSLを設定します。これを構成するには2つのオプションがあり、両方ともウォレットのZIPファイルに資格証明があります。 どちらのオプションの場合でも、2つのドライバ・プロパティを設定します。 oracle.net.ssl_server_dn_match=true oracle.net.ssl_version=1.2(12.xドライバの場合のみ必要) 最初のオプションは、オラクルの自動オープンのSSOウォレットであるcwallet.oraを使用することです。このウォレットを使用して、データベースへの双方向SSL接続の情報を指定します。これは、データベースのユーザー/パスワードの資格証明を格納するためのウォレットの使用とは異なるもので、資格証明の場合は、データソースの記述子から削除できます(ウォレットのブログで説明されています)。このオプションを使用する場合、設定する必要があるドライバ・プロパティはoracle.net.wallet_location(変数はwallet_location)で、ウォレットがあるディレクトリを指定します。 2つ目のオプションは、Java KeyStore(JKS)ファイルのtruststore.jksとkeystore.jksを使用することです。このオプションでは、javax.net.ssl.keyStoreType、javax.net.ssl.trustStoreType、javax.net.ssl.trustStore、javax.net.ssl.trustStorePassword、javax.net.ssl.keyStore、javax.net.ssl.keyStorePasswordのドライバ・プロパティを設定する必要があります。また、パスワードの値を暗号化された文字列として保存する必要もあります。 これで、データソースの構成に関する話は終了です。 データベース・サービスでアプリケーション・コンティニュイティを有効にする場合は、ユーザー・ガイドのこちらのリンクで説明されているデータベースの手順を実行する必要があります。   既知の問題 ATPを使用するときには、ATP以外のデータベースでは発生しないいくつかの問題が発生する可能性があります。こちらを見ておくと、いくらか時間を節約できるかもしれません。 12.2.0.1のオラクル・データベース・サーバーで使用される19.3のOracleドライバでは、アプリケーション・コンティニュイティが機能しません。これは19.6のドライバで修正されています。 oracle.sql.Blobのオブジェクトは、19.3のOracleドライバではシリアライズできません。次のシーケンスは失敗します。 Blob blob = BLOB.getEmptyBLOB(); ObjectOutputStream oos = new ObjectOutputStream(new ByteArrayOutputStream()); oos.writeObject(blob); この問題は、Blobの値を使用しているWebLogicジョブ・スケジューラで発生しました。oracle.sql.BLOB.empty_lob()ではなくConnection.createBlob()を使用することで、この問題を回避できました(WebLogic 14.1.1.0.0では解決されていますが、WebLogic 12.2.1.4.0では解決されていません)。 この問題は19.6のOracleドライバで修正されました。 コアあたりのセッション数が100に制限されており、セッションの最大数に到達すると"no more data to read from socket"というエラーが発生するのを解決することは困難です。 "_tp"サービスではなく"_high"サービスを使用していると、AWRレポートでいくつかの経過時間が非常に長くなることが確認されました。上記で説明されているように、"_tp"サービスを使用してください。 ATP-Sにはリスナーの速度制限があり、1秒あたりの接続数を100までとして、接続リクエスト数を調整しています(ATP-Sは共有リソースであるため)。一般的にWebLogic Serverでこれが問題となるのは、接続の最低数を100よりも大きい値にしてデータソースを初期化する場合のみです。そのため、これを回避する方法の1つは、最低数の値を100より少ない数に設定することです。WebLogic Server 12.2.1.4.0では、接続を作成できるスレッドの最大数を制限するために、weblogic.jdbc.maxConcurrentCreateRequestsとweblogic.jdbc.concurrentCreateRequestsTimeoutSecondsというドライバ接続プロパティが追加されました(詳しくはドキュメントを参照してください)。  

※本記事は、Stephen Feltsによる"ATP Database use with WebLogic Server"を翻訳したものです。 このブログでは、WebLogic...

IDを管理せずに、IDを管理する方法とは

※本記事は、Paul Toalによる"How to do Identity Management, whilst not doing Identity Management"を翻訳したものです。 この数週間で、忘れられないことが2つありました。まず、先日あるウェビナーに参加して、セキュリティの一般的な課題、リスク、リスクの軽減について話をしたのですが、質疑応答の時間に、複数の異なるユーザー名とパスワードの扱いに関する質問を受けてとても驚きました。思い出したくもないほど長い期間、IDの問題と格闘してきた人の1人である私にとっては、シングル・サインオン(SSO)が当たり前なのです。しかし、誰もがSSOを使えるわけではないことを心に留めておかなければなりません。私の一番下の娘は今月大学に入ったのですが、複数のシステムで複数の資格証明を使い分けています。  2つ目は、ID管理について、複数のお客様と何度も同じような会話をしているということです。お客様は普通、たくさんのサードパーティ製のサービスを購入しています。Oracle Cloudのサービスかもしれませんし、SaaS、PaaS、IaaS、またはその他のサービスかもしれません。そうしたサービス間でSSOを使用するにはどうすればよいのか、と訊かれるところから会話が始まります。頻繁に交わされる会話はこのようなものです。     私:今は、ユーザーIDをどこに保存して、ユーザーを認証しているのですか?     お客様:AD(またはその他のIDストア)です。     私:そうですか。統合を進めたいと思っているID管理システムがあるのでしょうか?     お客様:はい。ADです。     私:いえ、それはディレクトリですよね。ID管理システムは、利用されていますか?     お客様:いいえ、いくつかスクリプトを使ってはいますが…… 私は決して、ID管理システムをしかるべきところに用意していないことで、お客様を責めようとしているのではありません。たしかに、ID管理システムには大きなビジネス上の価値があり、法律や規制の遵守に役立ちますし、短期間で成果を上げつつ、段階的なメリットを得ることもできます。しかし以前にも説明したように、そうしたシステムは概して複雑であるため、注意深く実装する必要があります。 ただし、先ほどの例のようにお客様と話をするとき、お客様はID管理のプログラムを探しているわけではありません。お客様はいくつかのサービスを使っていて、パスワードをあっちにもこっちにもそっちにも入力することなく、そうしたサービスへのアクセスを管理したいと思っているのです。 そこで私は、「IDを管理せずに、IDを管理する方法とは」というテーマで記事を書くことにしました。もう少し正確に言うと、ID管理のプログラムをフル装備せずに、新しいサービスを導入するにはどのようなことを検討すればよいのか、ということがテーマです。これは重要です。あなたの会社が人材管理(HCM)のためにOracleのSaaSを導入したところだとしましょう。HCMプログラムの一環としてIdentity and Access Management(IAM)を導入することは避けたいと思っています。IAMを利用する場合は、専用のプログラムを作成して、会社全体に展開しなければならないからです。  IAMを導入しない場合は、HCMプロジェクトのみを対象にして何をする必要があるのか、明確に分かっていなければなりません。   シングル・サインオン(SSO) 私がお客様と話すときに、一番よく質問されるのがこれです。すでに使っている認証の方法にSSOを統合して、ユーザーがユーザー名とパスワードをいくつも入力しなくて済むようにするにはどうすればよいのでしょうか。 統合の方法はいくつかありますが、比較的新しいと言えるサービスを使っているのであれば、おそらくフェデレーション(つまりSAML)による統合を選ぶことになるでしょう。 次のことを考えてみる必要があります。 既存の資格証明のうち、ユーザーが認証を行う必要があるものはどれか。 ユーザーの種類(顧客、従業員、サプライヤーなど)が複数ある場合、資格証明の形式も異なっている必要があるか。 自社が使用できる統合の方式(SAMLなど)を、プロバイダはサポートしているか。 対象となっている特定のサービスでは、資格証明を確認するためのもっと強力な方法(多段階認証など)が必要ではないか。 対象となっている新しいサービスへのアクセスを管理する、コンテキストベースのルール(地理的位置ベースのアクセスなど)が必要ではないか。 そのサービスには社外と社内の両方からアクセスできる必要があるか。      このような点について検討し、現在の認証プロバイダと統合する方法を決めたら、IDライフサイクル管理(ILM)について考えてみる必要があります。   IDライフサイクル管理 ここが、ちょっと注意しなければならないところです。ここで油断していると、もっと大きな意味でのID管理に関する議論が始まって、身動きが取れなくなってしまいます。対象のシステムがHCMの場合は特にそうです。大がかりなIAMプログラムでは、HCMはID情報の主要なソースでもあるからです。  しかし、ここで想定している新しいサービスでは、社内のユーザーにサービスを普及させる方法、そしてユーザーが更新された場合の管理の方法に重点を置き、対応していく必要があります。 次のことを考えてみてください。 新しいサービスに入力する自分のIDデータをどこから取得したいのか。 ソースのIDデータへの更新や変更を、ターゲットでどのように管理するのか。 ソースとターゲットの両方で使用できるインタフェースはあるか。 ユーザーデータだけを同期しているのか、それともロールを含める必要があるのか。 すべてのユーザー/ロールを同期しているのか、それとも一部分だけか。 新しいサービスには、アクセス制御を判断するのに必要なID情報がすべてそろっているのか。  大抵は複数の選択肢があり、何が回答としてふさわしいのかは、さまざまな要素に左右されます。新しいサービスが特殊なもので、社内で5人しかアクセスしないのであれば、手動でユーザーを作成して管理すれば済むかもしれません。または、10万人の社員全員が給与情報にアクセスする必要があるので、ILMを自動化したいと思っているのかもしれません。 これまでお話ししたような、単体での統合に変更不能なものはなく、要件が変われば進化していくでしょう。ですから、IAMを導入する場合でも、SSOやILMといったサービスが自分の選んだIAMプラットフォームとどのように統合されるかを理解しておくことが必要です。IAMでは、フェデレーションとILMだけではできないたくさんのことを実現できるという点を忘れないでください。高度なIAMプログラムでは、次のようなアクセスやガバナンスのフレームワークを全社で利用できます。 認証(フェデレーション、SSO、MFA、リスクベースなど) 認可 アクセス制御 ユーザー・プロビジョニング/同期 認定 職務分掌 セルフサービス/委任管理 一元化されたID監査 ワークフローと承認 特権アクセスの管理 もしかすると私は、IAMプログラムをフルに導入したいという読者の気持ちをくじけさせてしまったでしょうか。または、考え抜かれて定義されたプログラムではなく、ポイント・ソリューションにこだわりすぎたでしょうか。いえ、そんなことはまったくありません。優れたIAMプログラムは重要であると私は考えています。とはいえ、すべての人がそれぞれの組織内で、同じような高度な環境にいるわけではありません。IAMプログラムが使える状態にないからといってイノベーションを止めることはできませんし、新しいサービスを導入しないわけにもいきません。そのため、私がここでお話ししているのは、新しいサービス導入への実際的なアプローチであり、このアプローチはもっと広範なIAMプログラムにも完全に対応しています。 ですから新しいサービスを採り入れるときには、そのサービスでSSOを実装する方法、また最低限のユーザー管理を行う方法に重点を置いてください。その両方を勘案しておけば、短期間で成果を上げることができますし、将来もっと戦略的なソリューションを導入するときにも妨げとなることはないでしょう。  

※本記事は、Paul Toalによる"How to do Identity Management, whilst not doing Identity Management"を翻訳したものです。 この数週間で、忘れられないことが2つありました。まず、先日あるウェビナーに参加して、セキュリティの一般的な課題、リスク、リスクの軽減について話をしたのですが、質疑応答の時間に、複数の異なるユーザー名とパスワー...

全国のOTNおすすめ技術者向けセミナー、イベント(2020年11月)

Oracle University 無償オンラインセミナー(11月) Oracle University の無償オンラインセミナーに参加しませんか。11月は限定で ORACLE MASTER Gold DBA のセミナー、Oracle Certified Java Programmerのセミナーほか、オラクルユニバーシティのコースを体験できるセミナーを実施します。お見逃しなく!   無償で学ぶ、MySQL入門 入門レベルのトレーニングを無償で学べる 「Oracle Learning Explorer」がリリースされました。Linuxへのインストール、MySQL Workbench、クエリ実行やトラブルシューティングなど、これからMySQLを学びたい方におすすめの内容です。   Oracle Cloud ウェビナーシリーズ 進化し続ける オラクルのIaaS/PaaS(Oracle Cloud Infrastructure)やOracle Database をはじめとする、さまざまな製品についての最新情報や活用事例および技術情報を業務部門からIT部門のエンジニアの方々までの幅広い皆様へ向けてウェビナーを通じてお届けします。 【期間限定特別編】オラクルの開発責任者が語る、データマネジメントの未来 社会インフラ、エンタープライズ企業のビジネス基幹システムを支援し続けるOracle Database。その製品開発責任者がデータマネジメントのこれからや、トレンド、企業においてのデータ活用についてのヒントをご紹介します。 11月26日(木)15:00-16:00 Analytic Actions Power Augmented Work 【エントリーシリーズ】【ファンデーションシリーズ】【プロフェッショナルシリーズ】も開催。詳しい内容は特設ページをご覧ください。また、過去に開催したウェビナーもオンデマンドでご覧いただけます。 AutonomousDatabaseを無期限 / 無料(Always Free)で使用可能になりました。 Cloudをまだお試しでない方は、無料トライアルをご利用下さい。          

Oracle University 無償オンラインセミナー(11月) Oracle University の無償オンラインセミナーに参加しませんか。11月は限定で ORACLE MASTER Gold DBA のセミナー、Oracle Certified Java Programmerのセミナーほか、オラクルユニバーシティのコースを体験できるセミナーを実施します。お見逃しなく!   無償で学ぶ、MySQL...

津島博士のパフォーマンス講座 第78回 Oracle DatabaseのJSONについて

津島博士のパフォーマンス講座 Indexページ ▶▶ 皆さんこんにちは、今年は10月から気温が低いので、日々の急激な寒暖差に身体がついていけませんね。 今回は、Oracle Databaseに格納されたJSONデータを扱うための機能について取り上げます。後半に、Oracle Database 12cR2(Oracle12cR2)からの問合せパフォーマンスを向上させるインメモリJSONデータについても説明していますので、参考にしてください。   1. JSONデータとOracle Database 従来のアプリケーション開発では、急速に変化するビジネス要件に迅速に対応できないため、スキーマレス開発を採用する機会が増えています。その中でよく使用されるのが、XML(Extensible Markup Language)よりシンプルなJSON(JavaScript Object Notation)です。Oracle Databaseは、スキーマを必要としないJSONを、Simple Oracle Document Access (SODA) APIを使用したNoSQLスタイルのアプリケーション開発以外に、リレーショナル・データベース機能(トランザクション、索引、SQL問合せ、ビューなど)を使用してネイティブにサポートしています(つまり、他のデータと同じように扱うことが可能です)。ここでは、SQLとJSONデータの基本的な連携方法について説明します。   (1)JSONとは まずは、JSONについて簡単に説明します。 JSONは、言語に依存しない軽量のデータ交換フォーマットです(コードの理解や記述が容易なため、現在は多くで利用されています)。JavaScriptでオブジェクトを作成する際は、中カッコ({})や角カッコ ([])などを使って記述しますが、JSONはその表記法を元にしています。そのため、オブジェクト(キーの名前/値のペア)と配列の構造に基づいていることから、ほとんどのプログラミング言語から簡単に扱えるので、理想的なデータ交換言語となっています。以下に、簡単なJSONドキュメントを示します("... "は、省略を意味しています)。 {"PONumber" : 1600, "Reference" : "ABULL-20140421", "Requestor" : "Alexis Bull", "User" : "ABULL", "CostCenter" : "A50", "ShippingInstructions" : {"name" : "Alexis Bull", "Address" : {"street" : "200 Sporting Green", "city" : "South San Francisco", "state" : "CA", "zipCode" : 99236, "country" : "United States of America"}, "Phone" : [{"type" : "Office", "number" : "909-555-7307"}, {"type" : "Mobile", "number" : "415-555-1234"}]}, "Special Instructions" : null, "AllowPartialShipment" : true, "LineItems" : [{"ItemNumber" : 1, "Part" : {"Description" : "One Magic Christmas", "UnitPrice" : 19.95, "UPCCode" : 13131092899}, "Quantity" : 9.0}, ... ] } ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。 JSONの値は、オブジェクト、配列、数値、文字列、ブール値(trueまたはfalse)、NULLのいずれかです。上記の発注書オブジェクトの例では、"PONumber"には数値が、"Reference"には文字列が、"ShippingInstructions"にはオブジェクトが、"LineItems"には配列が、"AllowPartialShipment"にはブール値が、" Special Instructions"にはNULLが含まれています。   (2)JSONドキュメントの格納 次に、JSONドキュメントの格納方法について説明します。 Oracle Databaseでは、JSONデータを既存のデータ型(VARCHAR2、CLOB、BLOB)に格納することで、データ挿入も同じように行います(以下は、VARCHAR2の例ですが、CLOBにはTO_CLOBファンクション、BLOBにはutl_raw.cast_to_rawファンクションを使用して挿入を行います)。このときに、"IS JSON"チェック制約を指定しないと使用できない機能があるので注意してください(ただし、構文解析のためにJSON文書の取り込みが若干遅くなります)。 CREATE TABLE j_purchaseorder (id VARCHAR2 (32) NOT NULL PRIMARY KEY, date_loaded TIMESTAMP (6) WITH TIME ZONE, po_document VARCHAR2 (32767) CONSTRAINT ensure_json CHECK (po_document IS JSON)); INSERT INTO j_purchaseorder VALUES (SYS_GUID(), to_date('30-DEC-2014'), '{"PONumber" : 1600, "Reference" : "ABULL-20140421", "Requestor" : "Alexis Bull", "User" : "ABULL", "CostCenter" : "A50", "ShippingInstructions" : {"name" : "Alexis Bull", "Address" : { ... }, "Phone" : [ ... ]}, "Special Instructions" : null, "AllowPartialShipment" : true, "LineItems" : [ ... ] }'); ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。 Oracle12cR2では、外部表を使用することで、データベースの外部に格納されたJSONドキュメントの操作も可能です。   (3)JSONデータの問合せ 次に、JSONデータのSQL問合せについて説明します。 Oracle Databaseでは、以下の2つの方法(ドット表記法の式、SQL/JSONパス式)を使用したJSONデータのSQLアクセスが提供され、リレーショナル・データと同じように問合せや分析が可能になります(ドット表記法の式やJSONパス式では、この式と一致するか、この式を満たすJSON値が選択されます)。 ・単純な問合せ(ドット表記法を使用した問合せ) ドット表現法は、ドット(.)で区切られた1つ以上のJSONフィールド(キーの名前)から構成されます。ただし、"IS JSON"チェック制約とテーブル・エイリアスが必要で("IS JSON"チェック制約は、DISABLEにするだけでもORA-00904エラーになりません)、戻り値は常にVARCHAR2(4000)の文字列です(4000バイトよりも長い値は戻せません)。以下に、ドット表記法の代表的な例を示します。 -- JSON値(ShippingInstructionsのAddressのcity)の問合せ SELECT po.po_document.ShippingInstructions.Address.city FROM j_purchaseorder po; -- 配列の使用(LineItemsの最初の要素) SELECT po.po_document.LineItems[0] FROM j_purchaseorder po; -- WHERE句の条件での使用 SELECT po.po_document FROM j_purchaseorder po WHERE po.po_document.PONumber = 1600; ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。 ・高度な問合せ(JSONパス式を使用した問合せ) より多くの機能が必要な場合には、以下の5つのSQL/JSON関数を使用できます。JSON関数は、JSONパス言語を完全にサポートし、ドット表記式で可能な範囲よりも強力で柔軟性があります(RETURNING句、ラッパー句、エラー句、空白フィールド句などが指定できます)。 関数 説明 json_query() JSONデータから1つ以上の値をSQL文字列として選択する。特に、JSON文章の断片(オブジェクト、配列)を取得するために使用する。 json_value() JSONデータからスカラー値を選択する。SELECTリストやWHERE句で使用したり、ファンクション索引を作成したりするために、最も多く使用される。 json_exists() JSONデータ内に特定の値が存在するかどうかをテストする。 json_table() JSONから表形式でデータを取得する。JSON_VALUEを大量に使用する代わりにも使用する。 json_textcontains() JSONデータのフル・テキスト検索を行う(テキスト述語に基づいて存在をテストする)。   以下に、JSONパス式の代表的な例を示します(最初の3つは、ドット表記法と同じ結果を得る問合せです。つまり、ドット表記法は、JSON_VALUE関数とJSON_QUERY関数の省略表記法です)。 --- JSON_VALUESでの絶対パス(パス式のコンテキスト項目を表すドル記号($)から始まります) SELECT JSON_VALUE(po_document,'$.ShippingInstructions.Address.city') City FROM j_purchaseorder; -- JSON_QUERYを使用した配列の問合せ(配列はスカラー値でないのでJSON_VALUEは使用できない) SELECT JSON_QUERY(po_document,'$.LineItems[0]') FROM j_purchaseorder; -- JSON_VALUESでのRETURNING句(返される値のデータ型)とエラー句(実行時エラーの処理方法を「特別な処理なし」に変更) SELECT po_document FROM j_purchaseorder WHERE JSON_VALUE(po_document,'$.PONumber' RETURNING NUMBER(10) ERROR ON ERROR) = 1600; -- JSON_EXISTSでの相対パス(フィルタ式内で@から始まります) SELECT po_document FROM j_purchaseorder WHERE JSON_EXISTS(po_document,'$.LineItems?(@.Part.UPCCode == 13131092899)'); -- JSON_TABLEでのCOLUMNS句を使用した仮想表(インライン・ビュー) SELECT jt.* FROM j_purchaseorder po, JSON_TABLE(po.po_document,'$' COLUMNS(po_number NUMBER(10) PATH '$.PONumber', reference VARCHAR2(30 CHAR) PATH '$.Reference', … )) AS jt; ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。 (4)JSONデータの索引 最後に、JSONデータの索引について説明します。 問合せのパフォーマンスを向上するには、通常のデータ型と同じように、JSONフィールドに対する索引作成を行います。以下のように、定型検索のための索引と非定型検索のための索引があります。 ・定型検索のためのファンクション索引 定型検索に対して、JSON_VALUE関数を使ったファンクション索引、JSON_EXISTS関数を使ったファンクション・ビットマップ索引などを作成することができます。 -- JSON_VALUE関数を使ったファンクション索引(Bツリー索引またはビットマップ索引) CREATE UNIQUE INDEX po_num_idx1 ON j_purchaseorder (JSON_VALUE(po_document,'$.PONumber' RETURNING NUMBER)); -- JSON_EXISTS関数を使ったファンクション索引(値がtrueとfalseだけのためビットマック索引) CREATE BITMAP INDEX po_zipcode_idx ON j_purchaseorder (JSON_EXISTS(po_document,'$.ShippingInstructions.Address.zipCode')); ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。 複数列Bツリー索引は、以下のように仮想列を作成してから行う必要があります。 ALTER TABLE j_purchaseorder ADD (userid VARCHAR2(20) GENERATED ALWAYS AS (JSON_VALUE(po_document,'$.User' RETURNING VARCHAR2(20)))); ALTER TABLE j_purchaseorder ADD (costcenter VARCHAR2(6) GENERATED ALWAYS AS (JSON_VALUE(po_document, '$.CostCenter' RETURNING VARCHAR2(6)))); CREATE INDEX user_cost_ctr_idx ON j_purchaseorder (userid, costcenter); ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。 以下のようにSQL文を実行することで索引が使用されます。 SQL> SELECT po_document FROM j_purchaseorder WHERE JSON_VALUE(po_document,'$.PONumber' RETURNING NUMBER) = 1600; ------------------------------------------------------- | Id | Operation | Name | ------------------------------------------------------- | 0 | SELECT STATEMENT | | |* 1 | TABLE ACCESS BY INDEX ROWID| J_PURCHASEORDER | |* 2 | INDEX UNIQUE SCAN | PO_NUM_IDX1 | ------------------------------------------------------- ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。 ・非定型検索のための索引(JSON検索索引) これの実体は、Oracle Text索引(ドメイン索引)で、一般的な索引で特定のJSONパス式が対象ではありません。JSON_TEXTCONTAINS関数では、JSON検索索引がない場合はエラー「ORA-40467: JSON_TEXTCONTAINS() cannot be evaluated without a JSON-enabled context index」が発生します(Oracle TextのCONTAINS()に相当する関数です)。JSON検索索引は、JSON_EXISTS関数、JSON_VALUE関数でも使用されます。以下の例は、LineItemのPartのDescriptionに'Magic'が含まれる発注書を検索するフル・テキスト問合せです。 SQL> CREATE SEARCH INDEX po_search_idx ON j_purchaseorder (po_document) FOR JSON; SQL> SELECT po_document FROM j_purchaseorder WHERE JSON_TEXTCONTAINS(po_document,'$.LineItems.Part.Description', 'Magic'); ------------------------------------------------------- | Id | Operation | Name | ------------------------------------------------------- | 0 | SELECT STATEMENT | | |* 1 | TABLE ACCESS BY INDEX ROWID| J_PURCHASEORDER | |* 2 | DOMAIN INDEX | PO_SEARCH_IDX | ------------------------------------------------------- ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。   2. インメモリJSONデータ ここでは、Oracle12cR2からのインメモリJSONデータについて説明します。 JSONデータをIM列ストアに格納することで、以下の二つのメリットにより高速にアクセスすることができます。 JSON文書を高度に最適化されたインメモリ・バイナリ形式(OSON)で格納 SQL/JSON関数の仮想列をインメモリ仮想列(IM仮想列)に格納 (1)インメモリ・バイナリ形式(OSON) インメモリJSONデータは、SQL/JSONパス式のアクセス・パフォーマンスが強化されています。SQL関数 (json_table、json_query、json_value、json_exists)は、JSONパス引数を受け入れているので、JSONデータをインメモリ化することで、高速にアクセスすることができます。 JSON列が含まれる表にINMEMORY句を指定すると、以下の条件のときに各JSON列に対する仮想列が、その表のインメモリ式ユニット(IMEU)に自動的に追加されます。これには、対応するJSON列と同じJSONデータが含まれますが、その形式はOracleのバイナリ形式OSON(バイナリJSON列)になります。 初期化パラメータMAX_STRING_SIZEはEXTENDEDに設定されている。 初期化パラメータINMEMORY_VIRTUAL_COLUMNSがENABLEに設定されている(明示的にINMEMORYを指定していない仮想列も対象にする)。 JSONデータ列に"IS JSON"チェック制約が設定されている(JSONデータであることが分かっている)。 これがJSON列の問合せに使用されると、JSONパス式を解析せずに評価することができ、ディスク読取りや解析に関連するオーバーヘッドが不要になります。そのため、非定型問合せでは、データ・スキャンを迅速に行えるため、JSONデータをインメモリ化するメリットが受けられます。定型問合せは、「(2)JSON関数のIM仮想列」を使用することで、さらにパフォーマンスが向上されます。 IM列ストアは、32,767バイトまでのJSON文書の問合せに使用されます。これより大きな文書の問合せでは、IM列ストアを利用するメリットがないので注意してください。   (2)JSON関数のIM仮想列 JSONデータ列が格納された表をIM列ストアにポピュレートすると、コストがかかる式を使用する問合せパフォーマンスも向上することができます。第61回のIM仮想列は、JSON関数のスカラー値を抽出する仮想列でも使用できるので、関数の事前処理結果を格納することができ、より高速なアクセスが可能になります(JSONデータのファンクション索引は、明示的に仮想列を作成する必要はありません)。ただし、JSON検索索引は、ファンクション索引ではないので、IM仮想列にできません。そのため、JSON_TEXTCONTAINS関数を使用したフル・テキスト検索では、IM列ストアを使用するメリットはないので、JSON検索索引を使用する必要があります(Oracle20cからのJSONデータ型とIn-Memory Textで改善されます)。 定型問合せでは、以下のようなJSON_TABLE関数にマテリアライズド・ビューを作成し、そのビューをIM列ストアにロードするのも有効です。 CREATE MATERIALIZED VIEW j_purchaseorder_materialized_view BUILD IMMEDIATE REFRESH FAST ON COMMIT WITH PRIMARY KEY AS SELECT po.id, d.* FROM j_purchaseorder po, JSON_TABLE(po.po_document, '$' COLUMNS (po_number NUMBER(10) PATH '$.PONumber', reference VARCHAR2(30 CHAR) PATH '$.Reference', requestor VARCHAR2(128 CHAR) PATH '$.Requestor', userid VARCHAR2(10 CHAR) PATH '$.User', ... ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。   (3)インメモリ列ストアへのポピュレート ポピュレートは、通常のデータ型と同じように、JSON列が格納された表にINMEMORY句を設定することで、その表をIM列ストアに格納します(以下の例は、PRIORITYがNONEのため、フル・スキャンでポピュレートしています)。 SELECT COUNT(1) FROM j_purchaseorder WHERE JSON_EXISTS(po_document,'$.ShippingInstructions?(@.Address.zipCode == 99236)'); ---------------------------------------------- | Id | Operation | Name | ---------------------------------------------- | 0 | SELECT STATEMENT | | | 1 | SORT AGGREGATE | | |* 2 | TABLE ACCESS FULL| J_PURCHASEORDER | <- 実行計画は、TABLE ACCESS FULLです ---------------------------------------------- -- テーブルをINMEMORYとして指定します。デフォルトのPRIORITY設定はNONEであるため、 -- フル・スキャンからトリガーされたときにのみデータがポピュレートされます。 ALTER TABLE j_purchaseorder INMEMORY; -- テーブルを再度クエリして、IM列ストアにポピュレートします。 SELECT COUNT(1) FROM j_purchaseorder WHERE JSON_EXISTS(po_document,$.ShippingInstructions?(@.Address.zipCode == 99236)'); ------------------------------------------------------- | Id | Operation | Name | ------------------------------------------------------- | 0 | SELECT STATEMENT | | | 1 | SORT AGGREGATE | | |* 2 | TABLE ACCESS INMEMORY FULL| J_PURCHASEORDER | <- 実行計画は、TABLE ACCESS INMEMORY FULLです ------------------------------------------------------- ※コードがうまく表示されない方はこちらから.txtをダウンロードしてご確認ください。 ただし、以下で作成されたJSON列が含む表は、IM列ストアにポピュレートする前にアップグレードする必要があります。 Oracle12cR2以降の互換性設定(初期化パラメータCOMPATIBLE)のないデータベース MAX_STRING_SIZE=EXTENDED設定のないデータベース これを行うには、スクリプトrdbms/admin/utlimcjson.sqlを実行します。実行すると、JSON列が含まれるすべての既存の表が、IM列ストアにポピュレートできるようにアップグレードされます。   3. おわりに 今回は、Oracle DatabaseのJSONについて説明しましたが、少しは参考になりましたでしょうか。これからも頑張りますのでよろしくお願いします。 それでは、次回まで、ごきげんよう。    

津島博士のパフォーマンス講座 Indexページ ▶▶ 皆さんこんにちは、今年は10月から気温が低いので、日々の急激な寒暖差に身体がついていけませんね。今回は、Oracle...

CLOUD INFRASTRUCTURE SECURITY

Maximum Security Zonesで、クラウドのセキュリティ対策の弱体化を防ぐ

※本記事は、Paul Toal (DISTINGUISHED SOLUTION ENGINEER - CYBER SECURITY)による"Prevent a weak cloud security posture with Maximum Security Zones"を翻訳したものです。 2019年9月、Oracle Cloud Infrastructure(OCI)エンジニアリングのExecutive Vice Presidentを務めるClay MagouyrkがOracle OpenWorldに登壇し、「エンタープライズ級のセキュリティがシンプルであるべき」理由と、「最大限のセキュリティを簡単に確保する」方法について語りました。同時に、そのビジョンを実現するために用意された、Oracle Cloud GuardやMaximum Security Zonesなどの多数の機能を発表しました。これらの新しいサービスはすでに、提供が開始されています。   2つの新サービスは、エンタープライズのワークロードを実行するのに理想とされる、もっとも安全なクラウドを実現するというオラクルのビジョンを具現化するにあたっての重要なマイルストーンです。2つのサービスは補い合って、予防と検知の両方を管理し、クラウド環境を守ります。Maximum Security Zonesは予防面をコントロールし、ユーザーが実装について不適切な判断を下すのを阻止して、セキュリティ対策が弱体化しないようにします。問題発生を予防できない場合は、検知と修正を制御するCloud Guardが、セキュリティ対策の弱点を突き止め、自動で修正します。Cloud Guardについては過去に、こちらとこちらで記事を公開しています。そのようなわけで本日の記事では、Maximum Security Zonesに注目してみたいと思います。 では、課題を把握するところから始めましょう。新しいプロジェクトを開始し、新しいソリューションを作成しようとするときには、以下のような実にさまざまなソースから、ベスト・プラクティスに関する多数の情報を入手できます。 ベンダーの推奨事項 組織としての標準とポリシー 外部のフレームワーク 規制準拠 リファレンス・アーキテクチャ こうしたベスト・プラクティスは通常、認証、暗号化、ストレージ、アクセス制御など、さまざまなセキュリティの項目を広くカバーしています。とはいえ、多くの場合、ベスト・プラクティスに関するアドバイスは無視されるものです。プロジェクトのスケジュール、予算の制限、知識の差、環境(開始時は非本番環境)などあらゆる要素によって、関連度の高いベスト・プラクティスが実行されないために、環境のセキュリティが保たれず、セキュリティ対策が弱くなるのを、私たちは幾度となく目にしてきました。 OCI内で使用される新しいMaximum Security Zonesサービスによって、このリスクを最低限に留めることができます。セキュリティ・ゾーンというのは予防的な制御であり、機密性の高いデータやリソースが含まれるという性質を持っているために、制限が多くなる仕様になっています。たとえば、Maximum Security Zonesは、最大限のセキュリティ・ポリシーが有効にされた状態でリリースされます。したがって、パブリック・アクセスを許可すべきではない、機密データはできる限りインターネットから切り離す、という方針が採用されます。セキュリティ・ポリシーで、ポリシーに違反する可能性のあるリソースをユーザーが作成できないようにリアルタイムで阻止することによって、その方針を運用します。 Oracle Cloud Infrastructureに詳しくない読者のために説明しておくと、OCI内のリソース、つまりコンピュート・インスタンス、ネットワーク、ストレージなどは、論理的にグループ化されて複数のコンパートメントに分かれており、それぞれに異なるアクセス制御を適用できます(予算管理やコストの追跡なども同様です)。たとえば、私がコンパートメントAの管理者で、その中にある特定のリソース・セットの管理をしているとします。しかし私は、他のコンパートメントのリソースには一切アクセスできません。なぜこのような話をしたかと言うと、セキュリティ・ゾーンとはそもそも、セキュリティ・ゾーン・ポリシーが適用された特殊なコンパートメントだからです。セキュリティ・ゾーンが作成されると、コントロール・プレーンで操作がリアルタイムに監視され、セキュリティ・ポリシーに違反する操作はブロックされます。 Maximum Security Zonesでは、複数のポリシーがあらかじめ定義されており、レシピと呼ばれています(Cloud Guardと同じ呼び方です)。最初のリリースでは、このレシピによって最大限のセキュリティ保護が適用されます。これはもっとも制限の厳しいポリシーで、変更できません。将来的には、最大限のセキュリティに調整を加えたいお客様向けに、このポリシーをカスタマイズしたバージョンを作成できるようになります。次のスクリーンショットは、最大限のセキュリティ・レシピ内のポリシーの一部です。   新しいセキュリティ・ゾーンを作成すると、すべてのポリシーが一括して、リアルタイムで適用されます。次の例では、セキュリティ・ゾーン内で、新しいパブリック・サブネットが作成されようとしています。   リソースの作成時だけでなく、セキュリティ対策を弱める可能性のある操作が実行されるときにも必ず、チェックが行われます。たとえば次の図のように、オブジェクト・ストレージ・バケットから、カスタマーマネージド・キーの割当てを解除しようとする場合などです。   分かりやすくするために、簡単な例で考えてみましょう。次の図では、インターネットベースのアプリケーションが展開されています。ここにはロードバランサがあり、アプリケーションを実行する2つのサーバーと接続されています。また、バックエンドのデータベースと、オブジェクト・ストレージもいくらかあります。お分かりのとおり、この基本設計にはいくつも問題があります。   セキュリティ・ゾーンを使うと、この構成はどのように変わるでしょうか。次の図はその一例です。   セキュリティ・ゾーンに関すること以外にも、いくつかの設計上の選択がなされています。 インターネットに接続されているコンポーネントをバックエンドから切り離す。その場合、サブネットとセキュリティ・リストを使用するだけではなく、Local Peering Gatewayで接続された独自の仮想クラウド・ネットワーク内に、コンパートメントを完全に隔離する。 Webアプリケーション・ファイアウォールを追加してロードバランサ、ひいてはWebアプリケーションを保護する。 Autonomous DatabaseとObject Storageを、プライベートIPアドレスのみで使用できるようにする。 すべてのブロック、ブート、オブジェクト・ストレージについて、オラクルマネージド・キーから、カスタマーマネージドの暗号化キーに変更する。 それでは、セキュリティ・ゾーンはここでどのような役割を果たすのでしょうか。上の図で、赤いボックスで囲まれているセキュリティ・ゾーンでは、セキュアな構成が適用されています。適用されているポリシーには、以下のようなものがあります。 すべてのコンピュート・インスタンスは、そこで使用されているストレージを暗号化するために、カスタマーマネージド・キーを使用して作成されていなければならない。 オブジェクト・ストレージ・バケットは、暗号化のためにカスタマーマネージド・キーを使用して作成されていなければならない。 どのカスタマーマネージド・キーも、ストレージ・リソースのいずれかから割当て解除することはできない。すなわち、オラクルマネージド・キーを再び使用することはできない。 オブジェクト・ストレージ・バケットは公開できない。 Autonomous DatabaseはプライベートIPアドレスで作成されていなければならず、パブリックIPアドレスを割り当てることはできない。 すべての仮想マシンはプライベートIPアドレスで作成されていなければならず、パブリックIPアドレスを割り当てることはできない。 すべての仮想マシンはオラクルが提供するプラットフォーム・イメージで作成されていなければならない。 サブネットはプライベートでなければならず、パブリック・サブネットを作成することはできない。 セキュリティ・ゾーンのブロック・ボリュームとブート・ボリュームは、セキュリティ・ゾーン外のコンピュート・インスタンスに接続することはできない。 ポリシーはこれだけではありません。用意されているポリシー全体を確認する場合は、Maximum Security Zonesのドキュメントをご覧ください。 セキュリティ・ゾーンが実際にどのように使用されるのかを知りたい方は、下の画像をクリックしてください。セキュリティ・ゾーンの簡単なユースケースを4つご紹介するデモを見ることができます。   上記のデモをご覧になると分かることですが、Maximum Security Zonesは数多くのチェックを実施するため、リソースを作成する管理者は、安全性に問題があるさまざまな操作がチェックされ、ブロックされるたびに、多数の違反メッセージを目にすることになります。そのため、ユーザー・エクスペリエンスに優れているとは言い難いだけではなく、経験の少ない管理者にとっては、そうしたメッセージが混乱のもととなるかもしれません。 この問題に対応するため、また「セキュリティを簡単に」というオラクルのビジョンをMaximum Security Zonesによってさらに推し進めるために、オラクルはOCI Security Advisorもご用意しました。これはコンピュート・リソースなどのリソースの作成時に使用できるガイド付きのフローをお客様にご提供するもので、お客様は最初から安全な構成で、そうしたリソースを作成できます。

※本記事は、Paul Toal (DISTINGUISHED SOLUTION ENGINEER - CYBER SECURITY)による"Prevent a weak cloud security posture with Maximum Security Zones"を翻訳したものです。 2019年9月、Oracle Cloud...

Developer Tools

Oracle Cloud Infrastructureの.NET向けソフトウェア開発キットをVS CodeまたはVS Codespacesで使用する

※本記事は、Yan Sunによる"Using Oracle Cloud Infrastructure Software Development Kit for .NET with VS Code or VS Codespaces"を翻訳したものです。 本記事はオラクルの同僚であるNabeel Al-Saberと共同で執筆したものです。 このブログでは、Visual Studio CodeとOracle Cloudの.NET向けソフトウェア開発キット(SDK for .NET)を使用して、Oracle Cloudにアクセス可能なアプリケーションを構築する方法について説明します。Oracle Cloudへのオンボーディングの手順、Oracle Cloud Infrastructure開発者用ツールを使用するために必要となる一般的なセットアップ方法および.NET SDK特有の詳細情報、さらにはこのアプリケーションをVS Codespacesで実行、デバッグする方法についても取り上げたいと思います。 Oracle Cloudの.NET向けソフトウェア開発キット(SDK for .NET)は2020年7月にリリースされました。Oracle Cloudの開発者用ツール・ファミリーに加わるSDK for .NETによって、.NET/C#開発者がOracle Cloudを操作できるようになります。   Oracle Cloud無償ティアの提供について アプリケーションの開発を始める前に、Oracle Cloud Infrastructureアカウントについて簡単にご紹介しましょう。 Oracle Cloudを初めて使用する場合に必要となる最初のステップは、Oracle Cloud Infrastructureアカウントの作成です。オラクルのクラウド・サービスにサインアップして感触をつかむには、Oracle Cloud無償ティアが適しています。   Oracle Cloudでの認証のための構成 Oracle Cloud Infrastructure SDK for .NET経由でOracle Cloudを操作する場合には、アクティブなOracle Cloudアカウントで認証する必要があります。このプロセスでは、APIの鍵ペアと、SDKで読み取り可能な構成ファイルを使用します。 Oracle Cloud SDK for .NETは公開されている他のSDKと同様に、PEM形式のRSA鍵ペアを利用して認証します。この鍵ペアと関連情報を生成するには、以下の手順を実行します。 Oracle Cloudサービスにアクセスするための適切な権限を持つユーザーを作成します。ユーザー、グループの作成方法と権限の設定方法については、 こちらを参照してください。ユーザーを作成済みの方はこの手順をスキップしてください。 まだ入手していない場合は、鍵ペアを生成します。API署名用の鍵の生成方法については、こちらを参照してください。この際に、秘密鍵をパスフレーズで保護することを推奨します。秘密鍵はご自分で厳重に保管し、公開鍵を以下のいずれかの手順でアップロードしてください。 公開鍵のフィンガープリントを取得:その方法については、こちらを参照してください。フィンガープリントはコンソールから容易に取得できます。次の手順で公開鍵をアップロードした後、コンソールにフィンガープリントが表示され、そこからコピーできます。 コンソールで公開鍵をユーザー・アカウントにアップロード(まだの場合):その方法については、こちらを参照してください。 構成ファイルではOracle Cloud SDK for .NETへの情報を指定します。このファイルのデフォルトの場所はホーム・ディレクトリ(~/.oci/config)ですが、他の場所に保存することもできます。構成ファイルには、生成された鍵とフィンガープリントに加えて、テナンシーOCIDとユーザーOCIDを含める必要もあります。これらの一意識別子はコンソールで確認できます。テナンシーOCIDとユーザーOCIDの確認方法については、こちらを参照してください。 以下のサンプルの構成ファイルをお使いの環境にコピーしてご利用ください。その際、自分のユーザー・アカウントに合わせて、このファイル内の値を更新してください。 ? 1 2 3 4 5 6 7 [DEFAULT] user=ocid1.user.oc1.. fingerprint=f7:12:34:56:78:90:ab:bc:cd:de:ef:98:87:65:43:21 key_file=~/.oci/my_oci_api_key.pem tenancy=ocid1.tenancy.oc1.. region=us-phoenix-1 pass_phrase=SomePassPhrase   VS CodespacesでのOCI構成ファイルのアップロード VS CodespacesのようなオンラインIDEを使用する場合は、アプリケーションの実行時にOracle Cloudのリソースにアクセスできるように、構成ファイルと必要な鍵をアップロードする必要があります。 これらのファイルをリモートIDEにアップロードする方法はいくつかあります。このブログでは、ターミナルから構成ファイルを作成する方法を説明します。 まだ表示されていない場合は、VS Codespaceでターミナルを開きます。メニューを開き、「View」に移動して「Terminal」を選択します。次に、以下の手順を実行します。 ? 1 2 3 4 5 6 7 8 9 10 11 # Create a new config file under ~/.oci mkdir ~/.oci # Copy the content of the config vi ~/.oci/config # Select the correct path for the key file in the config: key_file=~/.oci/oci_api_key.pem    # Copy the content of the local oci_api_key.pem file and paste it in the remote file vi ~/.oci/oci_api_key.pem    # Copy the content of the local oci_api_key_public.pem file and paste it in remote file vi ~/.oci/oci_api_key_public.pem 以下のスクリーンショットに、構成ファイルと鍵ファイルを作成した後のVS CodespacesのTerminalを示します。   .NET Coreアプリケーションでの作業 C#拡張のインストール VS CodeのC#サポートはオプションの拡張を通じて利用でき、この拡張はマーケットプレイスからインストールできます。この拡張をインストールするには、左側の「Extensions」アイコンをクリックし、C#を検索します。Microsoft製のこの拡張をインストールします。 C#拡張はOmniSharpによって動作しており、project.jsonやcsprojプロジェクトのサポート、IntelliSenseなどのコード編集サポート、go to定義、構文ハイライト、参照先検索など、C#開発のための多くの便利な機能を提供しています。   新規.NET Coreアプリケーションの作成 VS Codeでは利用できない機能の1つに、アプリケーション作成ウィザードがあります。そのため、アプリケーションの作成はVS Codeの外部で実行する必要があります。 .NET Core SDKがインストール済みであれば、.NET Core CLIからプロジェクトを作成できます。 ターミナルを開き、プロジェクトを作成するフォルダに移動します。このフォルダ名は次の手順でプロジェクト名として使用されます。 以下の.NET Core CLIコマンドを実行すると、完全な機能を備えたプロジェクトが作成されます。 ? 1 dotnet new console この新規プロジェクトをビルドして実行するには、以下の.NET Core CLIコマンドを実行します。成功した場合は、“Hello World!”と出力されます。 ? 1 dotnet run VS Codespacesでは、この手順をターミナルで実行することでプロジェクトを作成できます。   既存プロジェクトのロード VS Code C#拡張をインストールし.NET Coreアプリケーションを作成した状態で、プロジェクトをVS Codeで開くことができます。 Fileメニューから「Open...」を選択します。 C#プロジェクトを格納しているフォルダに移動します。 Openをクリックします。 “Required assets to build and debug are missing.Add them?”という通知が表示された場合は、「Yes」を選択して依存先を自動的にロードします。 これで、プロジェクト構造とソース・コードをVS Codeで確認できるようになりました。その中に.csprojファイルとProgram.csファイルがあるはずです。 VS Codespaces まず、プロジェクト・ファイルをGitHubにアップロードします。これで、Codespaceを作成して、インポートするプロジェクトのGitHubリンクを指定できるようになります。   Oracle Cloud Infrastructure SDK for .NETパッケージのインポート Oracle Cloud Infrastructure SDK for .NETはNuGetパッケージとして公開されています。このSDKを使用してOracle Cloudを操作するには、NuGetパッケージをインポートする必要があります。VS CodeではC#サポートの場合と同様に、“NuGet Package Manager”という拡張をインストールする必要があります。もう一度Extensionsビューに移動し、“nuget package manager”を検索します。目的の拡張が結果リストの一番上に表示されます。 この拡張をインストールした後、個別のNuGetパッケージを検索してインストールできます。 Viewメニューから、「Command Palette...」を選択します(またはショートカット・キーを使用します)。 ポップアップ内で“nuget”と入力すると、検索結果に“NuGet Package Manager:Add Package”が表示されます。このオプションを選択します。 次のダイアログ・ボックスで、インポートするパッケージの名前を入力して[Enter]キーを押します。名前の一部(OCI.DotNetSDKなど)を入力してもかまいません。その場合、その一部を含むすべてのパッケージが検索ボックスに表示されます。 インポートするパッケージを選択すると、そのパッケージで見つかったすべてのバージョンが表示されます。使用するパッケージに加えて、OCI.DotNetSDK.Commonパッケージもインポートする必要があります。認証やHTTPリクエストの送信などの重要な機能がすべて含まれているためです。 使用するバージョン(通常は最新バージョン)を選択すると、そのパッケージがプロジェクトに自動的に追加されます。 この時点でプロジェクト内の.csprojファイルを見ると、ItemGroupセクションに新規のPackageReferenceが含まれています。 このアプリケーションでは、Oracle Cloud SDK for .NETから以下の2つのNuGetパッケージを検索してインポートします。 OCI.DotNetSDK.Common OCI.DotNetSDK.Identity .csprojファイルに以下のようなItemGroupセクションがあるはずです。 ? 1 2 3 4 <ItemGroup>   <PackageReference Include="OCI.DotNetSDK.Common" Version="4.1.0"/>   <PackageReference Include="OCI.DotNetSDK.Identity" Version="4.1.0"/> </ItemGroup>   初めてのOracle Cloud .NETアプリケーションのコーディング これで、アプリケーション内でソース・コードを作成してOracle Cloudを操作する準備ができました。Oracle Cloudでのユーザー認証に必要となるすべての情報がすでに構成ファイルに含まれているため、あとはサービス・クライアントがこの構成ファイルを使用するように構成し、そのサービス・クライアントを使用してサービスAPIを呼び出すだけです。自動生成されたProgram.csに以下の変更を加えて更新します。 正しいusing文がすべて記述されていることを確認します。以下に重要な例を示します。 System.Threading.Tasks:非同期メソッドのために使用される Oci.Common.Auth:認証詳細プロバイダを作成するために使用される Oci.<Service>:サービス・クライアントを作成するために使用される Oci.<Service>.Requests、Oci.<Service>.Responses:APIリクエスト・オブジェクトとレスポンス・オブジェクトのために使用される 以下の例ではIdentityServiceが使用されています。 ? 1 2 3 4 5 using System.Threading.Tasks; using Oci.Common.Auth; using Oci.IdentityService; using Oci.IdentityService.Requests; using Oci.IdentityService.Responses; Main関数で、“Hello World!”を出力する文を削除して、構成ファイルの値を使用する認証詳細プロバイダを作成します。構成ファイルを使用して認証詳細を初期化する際の記述方法は複数あります。 構成ファイルがデフォルトの場所(~/.oci/config)に格納されている場合は、プロファイル名を指定するだけで認証詳細プロバイダを作成できます。 ? 1 var config = new ConfigFileAuthenticationDetailsProvider("DEFAULT"); 構成ファイルがカスタムの場所にある場合は、ファイル・パスも指定する必要があります。 ? 1 var config = new ConfigFileAuthenticationDetailsProvider("/path/to/config/file", "DEFAULT"); この認証詳細プロバイダを使用してサービス・クライアントを作成します。 ? 1 2 3 using (var client = new IdentityClient(config)) { } リクエスト・オブジェクトを作成し、サービス・クライアントを使用してこのオブジェクトを送信し、サービスから返されるレスポンスを受け取ります。 ? 1 2 3 4 5 6 7 8 9 var listCompartmentsRequest = new ListCompartmentsRequest {     CompartmentId = compartmentId };   using (var client = new IdentityClient(config)) {     ListCompartmentsResponse response = await client.ListCompartments(listCompartmentsRequest); } このように非同期関数を呼び出しているため、Main関数を非同期関数にして更新する必要もあります。 ? 1 static async Task Main(string[] args) 最終的なコードは以下のブロックのようになります。 ? 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 using System; using System.Threading.Tasks; using Oci.Common.Auth; using Oci.IdentityService; using Oci.IdentityService.Requests; using Oci.IdentityService.Responses;   namespace oci_sdk_dotnet_blog {     class Program     {         static async Task Main(string[] args)         {             // Needs a valid compartment ocid from your tenancy.             var compartmentId = "ocid1.tenancy.oc1..";             try             {                 var config = new ConfigFileAuthenticationDetailsProvider("DEFAULT");                   var listCompartmentsRequest = new ListCompartmentsRequest                 {                     CompartmentId = compartmentId                 };                   using (var client = new IdentityClient(config))                 {                     ListCompartmentsResponse response = await client.ListCompartments(listCompartmentsRequest);                     Console.WriteLine($"Found {response.Items.Count} compartments.");                 }             }             catch (Exception e)             {                 Console.WriteLine(e);             }         }     } }   VS Codeでのアプリケーションの実行とデバッグ これで、アプリケーションの実行やデバッグができるようになりました。これらはVS Code内で実行できます。   アプリケーションの実行 デバッグをせずにアプリケーションを実行するには、「Run」メニューに移動し、「Run Without Debugging」を選択します。 このアプリケーションを初めて実行する場合、VS Codeでのこのアプリケーションの構成が正しくない可能性があります。VS Codeが自動的に判断できない場合、実行環境を指定するように求められます。この手順の実行中に環境の指定が求められた場合は、「.NET Core」を選択します。これにより、プロジェクト・ディレクトリの“.vscode”というフォルダ内に2つのファイルが自動的に作成されます。このtasks.jsonとlaunch.jsonという2つのファイルは、VS Codeに対してアプリケーションのビルド方法と実行方法を指示するものです。 tasks.jsonファイルとlaunch.jsonファイルが作成された後にプロジェクトを再実行すると、VS Codeウィンドウの下側のデバッグ・コンソールにログが表示されます。正常に実行された場合、このプロジェクトのビルドが完了し、以下のような出力が表示されます。 ------------------------------------------------------------------- You may only use the Microsoft .NET Core Debugger (vsdbg) with / Visual Studio Code, Visual Studio, or Visual Studio for Mac software to help you develop and test your applications. ------------------------------------------------------------------- Found 40 compartments.   アプリケーションのデバッグ 他のIDEを使用してコードをデバッグする場合と同様に、行番号の左側をクリックすることでコード内にブレークポイントを設定できます。 デバッグを開始するには、「Run」メニューに移動し、「Start Debugging」を選択します。先ほどと同様に、VS Codeでプログラムの実行やデバッグが構成されていない場合は、アプリケーションの実行に関する前述の手順に従って、tasks.jsonファイルとlaunch.jsonファイルを作成します。デバッグ・セッションを開始した後は、進行状況を監視し、設定したブレークポイントで停止できます。VS Codeウィンドウの左側でRunアイコンをクリックして実行ビューまたはデバッグ・ビューに移動します。このビューで、コール・スタック、変数など、すべてのIDEに共通する情報を確認できます。 VS Codeウィンドウの右上にはフローティング・コントロールが表示されています。このコントロールを使用して、ステップ・オーバー、ステップ・イン、ステップ・アウト、再開、停止などの一般的なデバッグ操作を実行できます。   ロギング ロギングはプログラムの重要なデバッグ手法であり、Oracle Cloud Infrastructure SDK for .NETではロギングのためにNLogが使用されます。このSDKを使用するすべてのアプリケーションでSDKからのログを取得できますが、そのためにはプロジェクトに独自のNLog構成ファイルを配置する必要があります。この構成ファイルに、ロギングのターゲットと、希望するロギング・レベルを定義します。 以下のサンプルの構成ファイルでは、NLogに対して、コンソールとファイルの両方にロギングするように指示しています。コンソール出力では、Infoロギング・レベル以上のメッセージが出力されます。ログ・ファイルには、Debugロギング・レベル以上のすべての情報が記録されます。ロギング・レベルの詳細については、NLogの公式ドキュメントを参照してください。 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">     <targets>         <target name="console" xsi:type="ColoredConsole" />         <target name="file" xsi:type="File" fileName="${CurrentDir}/logs/${date:format=yyyy-MM-dd}.log" />     <code></targets>     <rules>         <logger name="*" minlevel="Info" writeTo="console" />         <logger name="*" minlevel="Debug" writeTo="file" />     </rules> </nlog> </code> SDKからのログを参照するには、NLog.configファイルを作成して、プロジェクトのルートに追加します。さらに、.csprojファイルに以下のセクションを追加して、このファイルを更新します。 ? 1 2 3 4 5 <ItemGroup>   <Content Include="NLog.config">     <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>   </Content> </ItemGroup> プログラムを実行するかデバッグしてみましょう。通常のコンソールへのロギングに加えて、“logs”というフォルダ(NLog.configで定義されています)がプロジェクトに追加されました。このフォルダにログ・ファイルが格納され、タイムスタンプがそのファイル名に付けられます(ファイル名についてもNLog.configで定義されています)。コンソール出力とログ・ファイルの両方に、以前にはなかった情報が含まれています。それらのロギングの行はOracle Cloud SDK for .NETからのものです。 NLog.configをプロジェクトに追加することで確認できるのはOracle Cloud SDK for .NETからのロギングだけですが、NLogを使用してアプリケーションからの情報もロギングできます。その場合に必要となる変更は、クラスに以下のロガーを追加することだけです。 ? 1 private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); さらに、コンソールに書き込まれていたすべての文を、以下のようなログ出力メソッドに書き換えることもできます。 ? 1 logger.Info($"Found {response.Items.Count} compartments.");   ユーザー値の指定 先ほどの例では、ListCompartmentRequestオブジェクトから要求される“CompartmentId”の値を明示的に設定しました。この値をソース・コードから隠したい場合や、他の開発者が値をカスタマイズできるようにしたい場合は、以下のいずれかの方法で、情報をアプリケーションに提供するようにしてください。   環境変数からの読取り VS Codeでは、すべてのターミナル・セッションで利用可能な環境変数(たとえば、MacOSやLinuxで.bash_profileにより設定された環境変数)にアクセスできます。または、launch.jsonファイルに“env”セクションを追加して、VS Code内で特定のアプリケーション用に環境変数を定義することもできます。launch.jsonファイルの生成方法については、“VS Codeでのアプリケーションの実行とデバッグ”セクションを参照してください。 ここでは例として、このアプリケーションの実行やデバッグの際に使用する環境変数“VSCODE_COMPARTMENT_ID”を追加します。 これで、compartmentId変数に値をハードコードする代わりに、以下の環境変数から値を取得できます。 ? 1 var compartmentId = Environment.GetEnvironmentVariable("VSCODE_COMPARTMENT_ID"); 環境変数をlaunch.jsonファイル内で設定する場合、ターミナル(VS Code内のターミナルを含む)からアプリケーションを実行すると、この環境変数にアクセスできません。アプリケーションがこの値を環境変数として読み取ることができるのは、VS Codeでアプリケーションを起動する場合に限られます。   構成ファイルからの読取り 構成ファイル経由でアプリケーションに対して値を提供することもできます。これまでは、このファイルにユーザー認証情報を指定していましたが、キーと値のペアの形式で他のデータも指定できます。Oracle Cloud SDK for .NETでは、構成から値を読み取るための関数が用意されています。既存の構成ファイルのデフォルト・プロファイルの後ろに、さらにキーと値のペアを追加してみましょう。 ? 1 2 3 4 5 6 7 8 [DEFAULT] user=ocid1.user.oc1.. fingerprint=f7:12:34:56:78:90:ab:bc:cd:de:ef:98:87:65:43:21 key_file=~/.oci/my_oci_api_key.pem tenancy=ocid1.tenancy.oc1.. region=us-phoenix-1 pass_phrase=SomePassPhrase config_compartment_id=ocid1.tenancy.oc1.. 次に、キー“config_compartment_id”を使用して構成ファイルから値を読み取るようにコードを更新できます。ConfigFileReaderにアクセスするには、“using Oci.Common;”を追加します。 ? 1 2 var config = ConfigFileReader.Parse(ConfigFileReader.DEFAULT_FILE_PATH); var compartmentId = config.GetValue("config_compartment_id"); まとめ このブログでは、Oracle Cloud Infrastructure SDK for .NETを使用する.NET CoreアプリケーションをVS Codeで作成、構成、実行またはデバッグするエンド・ツー・エンドのサンプルを段階的に確認しました。多数のIDEが存在する中で、VS Codeにも独自の長所と制約があります。C#開発用に構成した後のVS Codeは、Oracle Cloud SDK for .NET NuGetパッケージの閲覧、検索、インポート、ソース・コード編集、アプリケーションの実行やデバッグの機能を利用して、.NET Coreアプリケーションを簡単に開発できます。 Oracle Cloudに関心のある.NET開発者の方には、このSDKを通じてOracle Cloudを試した上で、オラクルが無償ティア・プログラム経由で提供している優れたサービスを活用されることを強く推奨します。Oracle Cloudを操作するアプリケーションを初めて開発する皆さまのためにこのブログがお役に立つことを願っております。ご質問やご意見がありましたら、コメントをお送りください。 Oracle Cloud Infrastructure SDK for .NET Oracle Cloud無償ティア How to Generate API Keys and Find OCIDs for the Configuration File

※本記事は、Yan Sunによる"Using Oracle Cloud Infrastructure Software Development Kit for .NET with VS Code or VS Codespaces"を翻訳したものです。 本記事はオラクルの同僚であるNabeel Al-Saberと共同で執筆したものです。 このブログでは、Visual Studio CodeとOracle ...

全国のOTNおすすめ技術者向けセミナー、イベント&書籍(2020年10月)

セミナー&イベント 10/15 無償セミナー! 黒本著者が解説 ORACLE MASTER Bronze DBA 試験対策) オンライン限定の特別企画です。「オラクルマスター教科書 Bronze DBA Oracle Database Fundamentals」 (2020年9月17日発売) の出版を記念して、著者である株式会社コーソル 渡部 亮太氏が同書籍を基に Bronze DBA 試験の重要トピック、および事前にいただいた質問に関連するトピックを解説します。   Oracle Learning Explorer リリース:無償トレーニングと履修証明バッジ Oracleのクラウド・ソリューションに関する知識を効率的に習得するための無償プログラム、Oracle Learning Explorer をリリースしました。今すぐ無料で学習を開始してみませんか。そして履修証明となる Explorerバッジを取得してください。   ウェビナーシリーズ|毎週水/木、約1時間でわかるOracle Cloud オラクルのクラウド製品(特に、IaaS/PaaS)やOracle Databaseについての最新情報や技術情報をオラクル・クラウドのスペシャリストからご紹介するだけでなく、クラウド・テクノロジーを活用され成功をおさめられた客様の声を事例を通じてお届けしています。毎回、約1時間にまとたさまざまなテーマや理解度レベル別(初心者レベルから、中級、上級者向けまで)のコンテンツ、業務部門からIT部門/ITエンジニアの方々まで、幅広いみなさまにお楽しみいただける内容を取り揃えています。当日はQ&Aもお受付しておりますので、日々の課題など、直接お聞かせください。   【期間限定特別編】オラクルの開発責任者が語る、データマネジメントの未来 社会インフラ、エンタープライズ企業のビジネス基幹システムを支援し続けるOracle Database。その製品開発責任者がデータマネジメントのこれからや、トレンド、企業においてのデータ活用についてのヒントをご紹介します。 10月1日(木)15:00-16:00 The Modern Data Warehouse 〜 次世代データウェアハウス基盤で広がるデータ相乗効果の加速 ~ 10月14日(水)15:00-16:00 Lowering Costs with Database Consolidation 〜 コスト削減へ! データベース集約による統合・合理化 ~ 【エントリーシリーズ】【ファンデーションシリーズ】【プロフェッショナルシリーズ】も順次開催予定。詳しい内容は特設ページをご覧ください。   【オンデマンド・ウェビナー】Oracle Cloud ウェビナーシリーズ動画 Oracle Cloudは大規模なコンテナの管理、デプロイおよび運用に適したクラウドサービスを複数提供しています。本ハンズオンセミナーではOracle Cloudが提供する下記コンテナ・サービスを実際に利用しKubernetes環境の構築からコンテナ・アプリケーションのデプロイ、CI/CDまでの一連の流れを体感いただけます。 書籍 新 Bronze DBA のオラクルマスター教科書(黒本) 「オラクルマスター教科書 Bronze DBA Oracle Database Fundamentals」(翔泳社出版) が9月17日に刊行されました。出題範囲を網羅し、かつ「試験に出るところ」を一冊に凝縮された日本オラクル株式会社監修の書籍です。 AutonomousDatabaseを無期限 / 無料(Always Free)で使用可能になりました。 Cloudをまだお試しでない方は、無料トライアルをご利用下さい。          

セミナー&イベント 10/15 無償セミナー! 黒本著者が解説 ORACLE MASTER Bronze DBA 試験対策) オンライン限定の特別企画です。「オラクルマスター教科書 Bronze DBA Oracle Database Fundamentals」 (2020年9月17日発売) の出版を記念して、著者である株式会社コーソル 渡部 亮太氏が同書籍を基に Bronze DBA 試験の重要トピッ...

Autonomous Database

アプリケーション開発者のためのOracle Autonomous JSON Database

※本記事は、Beda Hammerschmidtによる"Introducing Oracle Autonomous JSON Database for application developers"を翻訳したものです。 August 13, 2020 オラクルは本日、シンプルなNoSQL APIを備えた、使いやすくコスト効果の高いJSONデータベースを探している開発者向けに、新しいクラウド・サービスとして、Oracle Autonomous JSON Databaseの提供開始を発表しました。このOracle Autonomous JSON DatabaseにはMongoDBのコア機能がすべて揃っているうえ、高パフォーマンス、シンプルなエラスティシティ、フルACID対応、完全なSQLの機能性といった特徴も備えています。   {JSONを踏まえて} JSONは非常に人気の高いデータフォーマットです。もともとはJavaScriptのオブジェクトをシリアライズ化するフォーマットとしてスタートしましたが、その後Webアプリケーションの事実上のメッセージ・フォーマットとなり、現在はデータベース層を含む多くの新しいアプリケーションのメインのデータ・モデルとなっています。     {       "name":"San Jose",       "population": 1021795 ,       "county":" Santa Clara "        }       {       "name":"Atlanta",       "population":506811,       "county":["Fulton","DeKalb" ]       }   開発者が JSONを好む理由は、これが動的なスキーマに対応しているため、スキーマの変更が容易であるという点にあります。データを正規化して表と列による固定のリレーショナルスキーマにしなくても、JSONドキュメントを使用すれば、データ層の柔軟性を獲得できるため、アプリケーションの変更においても便利だからです。   {これを使いやすくします} オラクルでは当初からJSONのメリットとその必要要件を特定していました。2014年、Oracle DatabaseはまずSQL/JSONのエンタープライズクラスの実装を実現しました。これはオラクルによるオープン・スタンダードで、以来、他の多くの商用データベース製品やオープンソースのデータベース製品に採用されてきました。     SQLは分析や複雑なレポーティングに適した優れた言語ですが、多くの開発者が重視しているのは、JSONデータとのやり取りをシンプルかつ柔軟に実施することです。そこでオラクルは、JavaやJavaScript、Pythonといった一般的なプログラミング言語用にSODA(Simple Oracle Document Access)というネイティブでオープンソースのドキュメント・ストアAPIを追加することにしました。これにより、オラクルでJSONとSODAにてアプリケーションを開発することは、MongoDBなどのNoSQLのデータベースにて開発することと同じくらい簡単になりました。      soda create cities;    soda insert cities {"name":"San Jose","population":1021795,"county":"Santa Clara"}    soda insert cities {"name":"Atlanta","population":506811,"county":["Fulton", "DeKalb"]}    soda get cities -f {"county":"Fulton"}    soda get cities -f {"population":{"$gte":1000000}} さらにオラクルのJSONに対するデータベース・イノベーションは続き、JSONアプリケーション開発者に自律機能によるあらゆるメリットを提供するOracle Autonomous JSON Databaseを本日発表に至りました。   {自律をリード} しかもオラクルは、JSONクラウド・サービスを一から構築したわけではありません。Oracle Autonomous JSON Databaseは Oracle Autonomous Database を基盤として構築されています。そのためこのサービスでは、新しいデータベースを数分でプロビジョニングしたり、アプリケーションを中断することなくスケール・アップやスケール・ダウンを実施したり、オンラインでデータベースにパッチを適用したり、ポイント・イン・タイム・リカバリにて自動バックアップを実行したり、障害復旧機能を利用したりすることができ、また高度なセキュリティ機能も用意されています。自律型データベースの目標は管理作業をゼロにすることであるため、開発者はデータベースのセットアップや管理業務ではなく、アプリケーション自体に時間を費やすことができます。   {自律型クラウド・サービスを提供} JSONドキュメントは、ネイティブのツリー指向のバイナリ・フォーマットにて、Autonomous JSON Databaseに保存されています。このネイティブのJSONフォーマットは、高速での読み取り(線形スキャンを回避)と部分アップデート(REDO/UNDOのログサイズの縮小)向けに、高度に最適化されています。その結果、待機時間の短いCRUD操作と常に完全なACID(マルチドキュメントのトランザクションを含む)の両立、アプリケーション開発のネイティブ・ドキュメントAPIとアプリケーションの完全なSQLサポートの両立、ネイティブJSONストレージとスケーラブルかつパラレルなインメモリ・クエリーの最適化の両立を実現する、妥協のないドキュメント・データベースが誕生することとなりました。   Oracle Autonomous JSON Databaseには、以下のとおり、成熟度で劣っているNoSQLデータベースにはない豊富なアプリケーション機能が用意されています。 組込みの機械学習 アルゴリズム、空間クエリー きめ細かなアクセス制御など、高度なセキュリティ機能 成熟度の高いサーバー側の手続き型言語 完全なローコード開発環境 時間またはトランザクション・サイズに上限のないACIDトランザクション シンプルで高速なコレクション間の結合および/または集約 JSONドキュメント全体を対象としたインテリジェント検索インデックス その他にもさまざまな機能があります。   {手頃な価格} Oracle Autonomous JSON Databaseは驚くほど 低コストです。このサービスは、アプリケーション開発者がオラクル上で新しいJSONアプリケーションを構築することを意図して設計されており、開発者は非常に手頃な価格でこの自律型データベースのすべての機能を利用することができます。Oracle Autonomous JSON Databaseのコストは同等のMongoDB Atlasより30%低く、MongoDB AtlasがM60ティアの専用クラスタで時間当たり3.95ドルなのに対し、Oracle Autonomous JSON Databaseは8つのOCPUで時間当たり2.74ドルとなっています。しかもOracle Autonomous JSON Databaseはエラスティックで、決まったハードウェア型に依存していないため、使用するOracle Autonomous JSON Databaseに合わせてCPUの数を選択できます。そのため実際のコストは、MongoDB Atlasよりさらに低くなる可能性があります。 またOracle Autonomous JSON Databaseのコストには、バックアップおよびBIツールへのシンプルな接続性が含まれていますが、MongoDB Atlasの場合は、どちらも追加費用が必要になります。   {...しかもスケーラブルで高速} Oralce Autonomous JSON Databaseでは、多くの機能があるからといってパフォーマンスを犠牲にしているわけではありません。それどころか、(上述のコスト比較の際の条件にて)MongoDB Atlasと比較しても、さまざまなワークロード・タイプおよびコレクション・サイズにて一貫してその2倍のスループットを実現しています。MongoDB Atlasの測定結果は、業界標準のYCSBベンチマークを用いてMongoDBによって測定されたもので、こちらのページで公開されています。 Oracle Autonomous JSON Database(8 OCPU)とMongoDB Atlas(M60)との比較 業界標準のYahoo Cloud Serving Benchmark(YCSB) 出典:2020年8月12日時点のMongoDBの測定結果(https://www.mongodb.com/atlas-vs-amazon-documentdb/performance ) {ぜひお試しください!} Oracle Autonomous JSON Databaseは、無償のOracle Cloudトライアル・アカウントにてお試しいただけます。こちらからご登録ください。 Oracle Autonomous JSON DatabaseとOracle Cloudの無償ティアに関する情報 Oracle Autonomous JSON DatabaseはAutonomous Databaseファミリーに属しています。自動化、ライフサイクル管理、セキュリティ、可用性、スケーラビリティ、エラスティシティのためのすべてのコア機能に加え、自律型データベースのその他のサービスもすべて揃っています。 Oracle Cloudの無償ティアにてOracle Autonomous JSON Databaseを試してみたい場合は、まずはOracle Autonomous Transaction Processingから始めることをお勧めします。そのうえでシステムを拡張または本番環境に移行する準備が整ったら、無償ティアのOracle Autonomous Transaction ProcessingをOracle Autonomous JSON Databaseの有償版に直接移行させます。 なお、Oracle Autonomous JSON Databaseに移行せずに、Oracle Autonomous Transaction Processingの無償版の機能だけを使用していく方法はありません。無償ティアのデータ・サイズの上限は20GBで、Oracle Autonomous JSON Databaseでも同様に20GBの非JSONデータをサポートしています。   {AUTONOMOUS JSONとMONGODB ATLASの比較} Oracle Autonomous JSON DatabaseがMongoDB Atlasより優れている点はコストと速度だけではありません。その他にも以下のようにさまざまなメリットがあります。   Autonomous JSON Database MongoDB Atlas 最大ドキュメント・サイズ 32 MB 16 MB ドキュメントの最大ネストレベル 1024レベル 100レベル コレクション当たりのインデックス数 無制限 64 複合インデックス・フィールド 無制限 32 フル・ドキュメント・インデックス JSON検索インデックス X サーバー側の関数 関数、プロシージャ、トリガー 非推奨* マルチ・ドキュメント・トランザクション 常にACID 明示的なAPIコールにてリクエストされた場合のみACID トランザクション継続時間 無制限 デフォルトで60秒 トランザクション・サイズ 無制限 最大1000ドキュメント* 集約可能なデータ・サイズ 無制限 100MB RAM + 明示的allowDiskUse param サーバーレス自動スケーリング ✓ X JSONドキュメントへのSQLアクセス ✓ X 包括的なセキュリティ (例:Oracle Virtual Private Database、 Oracle Data Redaction、カスタム・データベース・ロール) ✓ X コスト $2.74 / 時間 $3.95 / 時間 * MongoDBのドキュメントに基づく推奨:リンク1、 リンク2    {AJDから始めるには:ステップ・バイ・ステップ} Oracle Cloudにログインし、左側のメニューにある「Autonomous JSON Database」を選択します。   この画面が開いたら、青色のボタンをクリックしてデータベースを作成します。 データベース名(および表示名)を入力し、「JSON」が選択されていることを確認します。   同じ画面にて「管理者用」パスワードを設定します。このパスワードは後で必要になります。   「Create Autonomous Database」をクリックすると、新しいインスタンスがプロビジョニングされていることを確認できます。 これには数分もかかりません。画面が更新され、緑色のロゴが表示されれば、このサービスは利用可能です。   「Tools」をクリックし、「SQL Developer Web」を選択します。 ここで「管理者用」パスワードを入力します。   Webコンソールが開き、SQLおよびSODAコマンドを入力できるようになります。SODAとは'Simple Oracle Document Access'の略で、コレクションにJSONドキュメントを保存するためのシンプルなドキュメント・ストア・インタフェースになります。'soda help'と入力すれば、sodaコマンドをまとめて確認できます。   以下のように入力して'cities'というコレクションを作成し、2つのJSONドキュメントを保存します。なお、この2つのドキュメントには少し違いがあります。最初のレコードは、1つの市が1つの郡にしか属していないことを前提としています。しかし1つの市が複数の郡に属している場合もあります。そのため2つめのドキュメントでは配列を使用しています。次のコマンドを実行し、コレクションを作成して、2つのドキュメントを挿入してみましょう。    soda create cities;    soda insert cities {"name":"San Jose","population":1021795,"county":"Santa Clara"}    soda insert cities {"name":"Atlanta","population":506811,"county":["Fulton", "DeKalb"]} これで、コレクションに問合せをして、検索/フィルタ条件に一致するドキュメントを見つけられるようになりました。オラクルではこれを'Query By Example'または略してQBEと呼んでいます。最初のQBEでは、'Fulton'郡にある市を検索しています。    soda get cities -f {"county":"Fulton"}   2つめのQBEでは、人口が25万人以上のすべての市を検索しています。この場合、両方のドキュメントが選択されます。    s oda get cities -f {"population":{"$gt":250000}} これらの例では、コンソールを使用してSODAコマンドを入力しました。しかし一般的には、プログラミング言語にて直接SODAを使用することになるでしょう。オラクルには、Java、JavaScript(nodeJS)、Python、REST、Pl/Sql、ODPI-C用のSODAドライバがあるからです。 またJSONデータはOracle Databaseに保存されているため、SQLを使用して同じデータにアクセスすることも可能です。まずはコレクションを見てみましょう。    describe cities; ご覧のとおり、JSONコレクションには標準的なテーブルが使用されています。JSONデータは、高速での読み取りと部分アップデートに最適化されたバイナリ表現にて保存されています。これをJSON文字列に変換するには、JSON_SERIALIZEを使用します。    select JSON_Serialize(JSON_Document) from cities; JSON_Tableでは、JSONデータをネスト解除して、リレーショナル列および行に反映させることが可能です。なお、1つの市が2つの郡に属しているため、2つのJSONドキュメントからは3つの行が生成されます。    select j.* from cities NESTED json_document              COLUMNS (name, population number,                NESTED county[*]                COLUMNS(countyName PATH '$')) j; 関係表現からJSONに戻す場合も、同様に簡単です。1つ(または複数の)JSON関数をクエリーに追加するだけです。以下の例では、すべての都市名の配列を生成しています。    select JSON_ArrayAgg(c.json_document.name) from cities c;   詳細は、Oracle Autonomous JSON Databaseにてご確認ください。 var count = $(".oracle-singlepageview__post").find("p").length; var middle = Math.floor(count / 2); var end = count - 1; function displayAd(location, adgroup) { if (location == "middle") { if ($("p:eq(" + middle + ")").text().length > 100) { $("p:eq(" + middle + ")").append(''); } else { var loc = middle + 1; $("p:eq(" + loc + ")").append(''); } } else if (location == "end") { $("p:eq(" + end + ")").append(''); } else {} } function displayAds(adgroup1, adgroup2, adgroup3) { if (count > 17) { if (adgroup1 != "NONE PROVIDED") { if ($("p:eq(4)").text().length > 100) { $("p:eq(4)").append(''); } else { $("p:eq(5)").append(''); } } if (adgroup2 != "NONE PROVIDED") { if ($("p:eq(10)").text().length > 100) { $("p:eq(10)").append(''); } else { $("p:eq(11)").append(''); } } if (adgroup3 != "NONE PROVIDED") { if ($("p:eq(16)").text().length > 100) { $("p:eq(16)").append(''); } else { $("p:eq(17)").append(''); } } } else if (count > 11) { if (adgroup1 != "NONE PROVIDED") { if ($("p:eq(4)").text().length > 100) { $("p:eq(4)").append(''); } else { $("p:eq(5)").append(''); } } if (adgroup2 != "NONE PROVIDED") { if ($("p:eq(10)").text().length > 100) { $("p:eq(10)").append(''); } else { $("p:eq(11)").append(''); } } } else if (count > 5) { if (adgroup1 != "NONE PROVIDED") { if ($("p:eq(4)").text().length > 100) { $("p:eq(4)").append(''); } else { $("p:eq(5)").append(''); } } } } displayAd("middle", "NONE PROVIDED");

※本記事は、Beda Hammerschmidtによる"Introducing Oracle Autonomous JSON Database for application developers"を翻訳したものです。 August 13, 2020 オラクルは本日、シンプルなNoSQL APIを備えた、使いやすくコスト効果の高いJSONデータベースを探している開発者向けに、新しいクラウド・サービスと...

Cloud Security

Oracle Data Safeにて、オンプレミスにあるオラクル・データベースのセキュリティをシンプルに

※本記事は、Clive D'Souzaによる"Simplify Security for your on-premises Oracle Databases with Oracle Data Safe"を翻訳したものです。 September 2, 2020 オンプレミスのデータベースとOracle Data Safeを簡単な数ステップで接続 Oracle Data Safeがオンプレミスのデータベースに対応するようになりました。Oracle Data Safeを使用してオンプレミスのデータベースを保護すべき主な理由については、ブログ記事 Keeping Data Safe – on-premises! にて説明されていますので、本ブログでは、Oracle Data Safeにオンプレミスのデータベースを接続し、登録する方法について説明いたします。 主なステップは、Oracle Data Safeへのネットワーク接続パスを作成して、オンプレミスのデータベースとつなぐことになります。このステップさえ完了すれば、データベースとの通信に、それがOracle Cloud上にあるか、オンプレミスにあるかは、まったく関係なくなります。   要件 接続の前に、まずは仮想クラウド・ネットワーク(VCN)が何であるかを理解しておく必要があります。VCNとは、Oracle Cloud Infrastructure内のプライベート・ネットワークです。従来のデータセンター・ネットワークと同様、VCNではネットワーク環境を完全に制御することができます。VCNは一般的に、コンピュート、ストレージ、データベースといったOracle Cloud Infrastructureのリソースと接続されています。また、他のサービスにOracle Cloud Infrastructureを使用していない場合は、リソースなしで空のVCNを作成することもできます。 現在のOracle Data Safeでは、データセンターとOracle Cloud InfrastructureとをつなぐFastConnectまたはVPN接続が必要で、これにより下図のように、VCNをオンプレミスのネットワークにまで実質的に拡大させることができます。FastConnectおよびVPN接続に関する詳細については、本ブログの最後に掲載しているリンクからご確認ください。 図1 - FastConnect またはVPN経由で、VCNをオンプレミスのネットワークまで拡大する FastConnectやVPN接続を使用できない場合、またはネットワークとは接続したくない場合は、特別プログラムの一環として提供される別の接続方法を利用することも可能です。詳しくは、本ブログの最後までスクロールして、ご確認ください。   オンプレミスのデータベースとOracle Data Safeを接続する FastConnectまたはVPN接続にてOracle Cloud Infrastructureとの接続が確立できたら、次のシンプルな3つのステップを実行するだけで、データベースとOracle Data Safeを接続できます。 VCNにOracle Data Safeの窓口を作成する Oracle Data Safeからオンプレミスのデータベースへの通信を許可する Oracle Data Safeにデータベースを登録する プライベートVCNにてオラクルのクラウド・データベースを運用するためにOracle Data Safeを使用されている場合は、すでに同様のステップを経験されているはずです。(詳細は、私の別のブログ記事にてご確認ください)   ステップ1 – VCNにOracle Data Safeの窓口を作成する このステップでは、オンプレミスのネットワークに接続されているVCNの名前を把握しておく必要があります。以下の環境では、FastConnect経由でCorpDev1-iad.vcn.というVCNに接続しています。このVCNにはCorpDev1-iad.というサブネットが1つあります。 図2 – VCNとサブネットの例 VCNにOracle Data Safeの窓口を作成し、最終的にOracle Data Safeとオンプレミスのデータベースとの通信を許可するには、Oracle Data Safeのプライベート・エンドポイントを作成する必要があります。 このプライベート・エンドポイントを作成するには、左側の「Database related services」のメニューから「Data Safe」を選択し、さらに「Private Endpoints」をクリックして、Oracle Cloud InfrastructureのData Safeコンソールを開きます。 図3 – Data Safeコンソール このコンソールにて「Create Private Endpoint」を選択し、新たに作成するプライベート・エンドポイントの名前を入力し、オンプレミスのデータベースに接続されているVCNとサブネットを選択します。 図4 - Oracle Data Safeのプライベート・エンドポイントの作成 プライベート・エンドポイントが作成できたら、その名前をクリックして、そこに割り当てられているプライベートIPアドレスを確認します。このプライベート・エンドポイントが、ネットワークにおけるOracle Data Safeの仮想窓口になります。 図5 - プライベート・エンドポイントの情報 なお、Oracle Data Safeに登録したいオンプレミスのデータベースがいくつあっても、仮想クラウド・ネットワーク用に作成するOracle Data Safeのプライベート・エンドポイントは1つで十分です。   ステップ2 – Oracle Data Safeのプライベート・エンドポイントからデータベースへの通信を許可する 次は、Oracle Data Safeのプライベート・エンドポイントからデータベースへの通信を許可するステップです。ここでは、仮想クラウド・ネットワークからアクセスできるオンプレミスのすべてのデータベースへの通信を許可するか、または特定の1つまたは複数のデータベースへの通信のみを許可するかを選択することになります。Oracle Data Safeのプライベート・エンドポイントからの送信を許可するには、送信ルールを定義します。 今回は、VCNのセキュリティ・ルールを定義して、Oracle Data Safeのプライベート・エンドポイントから、VCNにてアクセス可能なオンプレミスの1つのデータベースへの通信を許可していますが、これをVCNのセキュリティ・ルールではなく、ネットワーク・セキュリティ・グループ(NSG)を使用して実施することもできます。送信ルールのシンプルな例は次のとおりです。これにより、Oracle Data Safeのプライベート・エンドポイントからデータベース(10.89.69.237、ポート 1527)への通信が許可されています。 図6 - 送信ルールの例 注:データベースに複数のデータベース・ノードがある場合は、それらをすべてセキュリティ・リストの送信ルール(またはNSG)に含める必要があります。   ステップ3 – Oracle Data Safeにオンプレミスのデータベースを登録する あとは、Oracle Data Safeにオンプレミスのデータベースを登録すれば完了です。 データベースを登録するには、Oracle Cloud InfrastructureのData Safeコンソールに戻り、「Service Console」ボタンをクリックします。Data Safe UIで、トップメニューから「Targets」を選択し、「+ Register」ボタンをクリックします。 登録用のダイアログボックスにてデータベース名を入力し、「Target Type」の下のドロップダウン・メニューから「Oracle On-Premises Database」を選択します。これにより、入力オプションの一部が変わります。「Connectivity Option」では「Private Endpoint」が自動的に選択されています。次の入力フィールドにて、ステップ1で作成したOracle Data Safeのプライベート・エンドポイントを選択します。続いてIPアドレスやポート番号、データベースのサービス名など、データベースの接続情報を入力します。データベースに複数のデータベース・ノードがある場合は、IPアドレスのフィールドにそのすべてのノードを入力します。 最後に入力するのは、データベースに接続する際にOracle Data Safeで使用する資格情報です。データベース内にData Safe専用のデータベース・ユーザーを作成することをお勧めします。このデータベース・ユーザーに必要な権限を付与するために、登録ダイアログから権限スクリプトをダウンロードし、それをデータベース内で実行してから、登録操作を完了することもできます。「Test Connection」をクリックして、すべてが正しく設定されていることを確認します。最後に、「Register Target」をクリックします。 図7 - データベースの登録 詳細なステップ・バイ・ステップの説明が必要な場合は、Oralce Data Safeのユーザー・ガイド を参照してください。 これで完了です。これでオンプレミスのデータベースはOracle Data Safeによって保護されるように設定されました。ここで最初にSecurity AssessmentとUser Assessmentを実行することをお勧めします。そのためにはData Safeホームページの「Security Assessment」に移動し、データベースを選択して「Assess」ボタンをクリックします。次に、この操作をUser Assessmentでも繰り返します。数分で潜在的なリスクがわかる包括的な評価レポートが作成されるため、適切な対応をとることができます。Oracle Data Safeの始め方や詳細情報についてはこちら を参照してください。 図8 - Data Safeのホームページとダッシュボード   別の接続方法 FastConnectやVPN接続を使用できない場合、またはネットワークとは接続したくない場合は、オラクルから特別プログラムの一環として提供されているData Safeのオンプレミス・コネクタを利用することも可能です。このコネクタは軽量で、設定およびダウンロードして、ネットワークのノードにデプロイすることができます。またインストールも簡単で、ネットワークの詳しい知識も必要ありません。これでオンプレミスのすべてのデータベースに接続することができます。 この詳細情報および特別プログラムへの参加方法については、こちら からご確認ください。 役に立つリンク: オラクルのオンプレミス・データベースの登録 – ユーザー・ガイド オンプレミスのネットワークと仮想クラウドネットワーク(VCN)の間にOracle Cloud Infrastructure FastConnectを設定する 顧客構内機器(CPE)を使用して、オンプレミスのネットワークと仮想クラウドネットワーク(VCN)の間にVPN接続を設定する

※本記事は、Clive D'Souzaによる"Simplify Security for your on-premises Oracle Databases with Oracle Data Safe"を翻訳したものです。 September 2, 2020 オンプレミスのデータベースとOracle Data Safeを簡単な数ステップで接続 Oracle Data...

OCI

Oracle Cloud VMware Solutionの一般的な移行ユースケース

※本記事は、Clive D'Souzaによる"Common Migration Use Cases for Oracle Cloud VMware Solution"を翻訳したものです。 August 13, 2020 当社は先週、管理機能へのフルアクセスが可能な、業界初のベアメタルでカスタマーマネージドのVMwareソリューションとして、 Oracle Cloud VMware Solutionを発表いたしました。そこで本日は、このOracle Cloud VMware Solutionの顧客ユースケースをいくつか紹介したいと思います。 データセンターの移行というと、まずは既存のインフラストラクチャを効率的に移行させるにはどうすればよいかというのが、重要な課題になります。VMwareからは、仮想マシン(VM)をインフラストラクチャ間で移行させるための手段が数多く提供されていますが、Oracle Cloud VMware Solutionではそのすべてを利用できます。   ユースケース 以下の図は、 VMware HCXを使用して、環境全体をオンプレミスからOracle Cloud VMware Solutionへ移行する場合の例になります。VMware HCXでは、2つのVMware環境の間にペアリングを設定することで、移行を完了できるようになっており、場合によってはサービスを停止する必要もありません。 しかしオンプレミスを維持しつつ、既存のVMware環境を単純に拡大させたり、環境の一部をOracle Cloud VMware Solutionに移行させたりしたい場合もあるでしょう。規制要件やガバナンス、またはデータやオペレーションの局所性などの理由で、こうした選択を必要とされるお客様もいらっしゃいます。そこで次は、VMware HCXを使用してハイブリッド環境を構築する例を紹介いたします。VMware HCXを使用すれば、ハイブリッドのソフトウェア定義データセンター(SDDC)を構築し、ソース(オンプレミス)と宛先(お客様のOracle Cloud Infrastructureテナンシ)との間にペアリングを設定して、必要に応じてネットワークを拡大したり、両サイドに仮想アプライアンスをデプロイしたりすることができます。セットアップが完了したら、必要な仮想マシンのグループを選択して移動させることで、ワークロードを双方向で移動させることができます。 最後に、Oracle Cloud VMware Solutionでは、お客様のプライマリデータセンターにて障害が発生した場合に備え、実際的なディザスタ・リカバリ・シナリオを策定しておくことができます。VMwareのSite Recovery Manager(SRM)を使用すれば、ディザスタ・リカバリ・プランを指定して、それを自動化することも可能です。プライマリSDDCで障害が発生した場合も、Oracle Cloud VMware Solution環境からVMwareのワークロードに直接アクセスできます。またOracle Cloud VMware Solutionでは、新しいディザスタ・リカバリ・プランを策定して、既存のものと置き換えたり、既存のものに追加したりすることも可能です。   他のサービスとの比較 これら3つのユースケースを把握したとしても、やはり他のプロバイダとの違いは知りたいところだと思います。そこで、Oracle Cloud VMware Solutionの競合との主な違いを以下の表にまとめました。 .tg { border-collapse: collapse; border-spacing: 0; } .tg td { border-color: black; border-style: solid; border-width: 1px; } .tg th { border-color: black; border-style: solid; border-width: 1px; } }   オラクル AWS Azure GCP (Cloud Simple) 管理性 カスタマーマネージドおよびコントロール AWSおよびVMwareによるマネージド・サービス Cloud Simpleによるサードパーティ・マネージド・サービス 限定的なvCenterコントロール サポート オラクル VMwareおよびAWS サードパーティによるサポート サードパーティによるサポート セキュリティ 顧客がルート資格証明を所有し、オラクルはルート資格証明にもメタデータにもアクセスできない。 AWSがルート資格証明およびメタデータを永続的に保有。 Microsoftがルート資格証明およびメタデータを永続的に保有。 GCPがルート資格証明およびメタデータを永続的に保有。 可用性 19のOracle Cloudリージョンと、顧客専用のリージョンであるCloud@Customer。 17のAWSリージョンに限定。 3つのAzureリージョンにプレビューあり。 2つのGCPリージョンに限定。 アップデート、パッチ、アップグレード アップグレードの実施の是非および時期を顧客がコントロール。 AWSがコントールおよび決定権を保有。 Microsoftがコントールおよび決定権を保有。 GCPがコントールおよび決定権を保有。 デプロイメント 顧客のVCNにデプロイ。 仮想ネットワーク外に共同設置。 不明 仮想ネットワーク外に共同設置。 最小本番構成 3x BM.DenseIO2.52(52コア、768 GB RAM、51.2 TBストレージ) 3x r5(48コア、768 GB RAM、0 TBストレージ) 3x i3(36コア、512 GB RAM、10.7 TBストレージ) 3x CS28(28コア、256 GB RAM、5.62 TBストレージ) 3x CS36(36コア、512 GB RAM、11.25 TBストレージ) 3x ve1-standard-72(36コア、768 GB RAM) 機能性 NSX-T、vSphere、vSAN、vCenter、HCX NSX-T、vSphere、vSAN、vCenter、HCX NSX-T、vSphere、vSAN、vCenter、HCX NSX-T、vSphere、vSAN、vCenter、HCX   当社では、他のクラウド・ベンダーをも、顧客の期待をも上回ることに、フォーカスしています。そのためOracle Cloud VMware Solutionのワークロードは2.5時間足らずでデプロイすることができ、その時間も今後さらに短縮していくことを目指しています。一方、AWSではデプロイまでに数週間を要しています。また他のVMwareクラウド・サービスは、共同設置やマネージド・サービスに限定されているため、エンタープライズ・クラスの顧客が必要とするコントロール性、柔軟性、クラウド統合性を実現できていません。たとえば大半の企業は、大規模なアプリケーション・ポートフォリオにおいて互換性を確保するためn-1戦略を採用しているのに、VMware Cloud on AWSではVMwareソフトウェアの最新のバージョンしか、提供もサポートもしていません。またVMware Cloud on AWSを導入するインフラストラクチャおよびデータセンターは、AWSで使用されるものとは別になるため、これら2つのソリューション間の接続性やスループットが限定的になるという懸念もあります。 一方Oracle Cloud VMware Solutionは、他のどのクラウド・プロバイダとも異なり、リージョン内の他のOracle Cloudサービスとはネイティブで接続性があり、Oracle Cloud Infrastructure VCNインフラストラクチャ上で稼働します。ラウンドトリップの遅延は1ミリ秒未満で、ネットワーク境界を越えなくてもOracle Cloud Infrastructureのほぼすべてのサービスにアクセスできるため、Oracle CloudネイティブのソリューションやVMwareベースのソリューションのデプロイメントや統合も、エンドポイントを定義するだけで済むほど簡単です。ネットワークのパフォーマンスや複雑なルーティングに頭を悩まされることもありません。 ではデータベース・サービスへのアクセスはどうかというと、Azure VMware Solutionの顧客はExpressRouteを使用して、VNetピアリング接続を手動でセットアップする必要があるため、待機時間の発生や安定性上の懸念、管理オーバーヘッド、多くの本番ワークロードの無効化などが伴うことになります。一方Oracle Cloud VMware Solutionの場合、必要な場所にデータベースをデプロイし、接続を開始するだけで済みます。他の作業は一切発生しません。 また請求においても、Oracle Cloud VMware Solutionは明細項目のひとつとして他のクラウド・サービスと合わせて請求されるため、顧客が処理する請求書はひとつで済みます。しかし他のプロバイダはどこも、個別の契約および支払いが必要で、VMwareは、プロバイダがコア・サービスとみなしているサービスからさらに切り離されています。調達プロセスをシンプル化することは、顧客中心という当社の精神をさらに進めるものであり、またソリューションを個別にとらえるよりも統合する方が顧客のためになるという当社の考えを具現化するものでもあります。   まとめ 私は何か月にもわたり、顧客とのディスカッションにおいて、フィードバックを受け、彼らの示唆に耳を傾け、そのニーズを理解してきました。そこから私は多くのことを学ばせていただきました。その結果私は、顧客の最も需要の高いワークロードを極めて安全かつスケーラブルに移行するうえで、このパブリック・クラウドのVMwareソリューションがベストなソリューションであると確信するに至りました。Oracle Cloud VMware Solutionを始めるには、をご覧ください。

※本記事は、Clive D'Souzaによる"Common Migration Use Cases for Oracle Cloud VMware Solution"を翻訳したものです。 August 13, 2020 当社は先週、管理機能へのフルアクセスが可能な、業界初のベアメタルでカスタマーマネージドのVMwareソリューションとして、 Oracle Cloud...

クイズに挑戦:コア関数型インタフェースの使用:Predicate(上級者向け)

.button { background-color: #759C6C; color: white; border: 2px solid #759C6C; border-radius: 12px; padding: 5px 20px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; margin: 4px 2px; cursor: pointer; } function answer1() { alert("This answer is incorrect. "); } function answer2() { alert("This answer is incorrect. " ); } function answer3() { alert("This answer is correct, but it is not the only correct answer. "); } function answer4() { alert("This answer is correct, but it is not the only correct answer. "); } function answer5() { alert("This answer is incorrect. "); } ※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz Yourself: Using Core Functional Interfaces: Predicate (Advanced)"を翻訳したものです。 Predicateインタフェースは複雑になることがあるため、その結果を扱う場合は十分に注意を 著者:Simon Roberts、Mikalai Zaikin  2020年4月6日 その他の設問はこちらから   Predicate、Consumer、Function、Supplierなどのコア関数型インタフェースを使いたいと思います。この設問では、条件に注目します。 次のコードについて: Predicate<Integer> p1 = (i1) -> i1 > 9; Predicate<Integer> p2 = (i2) -> i2 % 2 == 0; Predicate<Integer> p3 = (i3) -> i3 > 19; boolean result = p1.and(p2.negate()).and(Predicate.not(p3)).test(i); 次の引数のうち、結果がtrueになるのはどれですか。2つ選んでください。 A. 9 B. 10 C. 11 D. 19 E. 21 解答:java.util.function.Predicateは、Java 8で導入された関数型インタフェースで、1つのパラメータを受け取ってBooleanを返す、1つの抽象メソッドboolean test(T t)があります。このインタフェースでは当初から、defaultメソッドとしてand(...)、or(...)、negate()が提供されたため、条件を組み合わせることが容易になっています。Java 11では、このインタフェースにstaticメソッドのPredicate.not(Predicate p)が追加されました。このメソッドでは、渡された条件の否定を表す条件が作成されます。 設問には、3つの条件が含まれています。1つ目のp1では、引数が9より大きいかどうかが評価されます。2つ目のp2では、引数が偶数かどうかが評価されます。そして3つ目のp3では、引数が19より大きいかどうかが評価されます。 設問のコードの最終行では、3つの条件が論理ANDで結合されています。1つ目のp1では、値が9より大きいかどうかが確認されます。2つ目はp2を反転させたもので、ここでは奇数かどうかが確認されます。3つ目では、値が19以下かどうかが確認されます(「19より大きい」を否定、つまり反転させたものです)。 すべての条件を満たすのは、選択肢CとDのみです。いずれのパラメータも9より大きく、奇数であり、19以下です。 正解は選択肢CとDです。 選択肢Aは、パラメータが9より大きいかどうかが評価される、最初の条件チェックで失敗します。したがって、選択肢Aは誤りです。 選択肢Bは、パラメータが奇数かどうかが評価される、2つ目の条件チェックで失敗します。したがって、選択肢Bは誤りです。 選択肢Eは、パラメータが19以下かどうかが評価される、3つ目の条件チェックで失敗します。したがって、選択肢Eは誤りです。 Simon Roberts Simon Roberts:Sun Microsystemsがイギリスで初めてJavaの研修を行う少し前にSunに入社し、Sun認定Javaプログラマー試験とSun認定Java開発者試験の作成に携わる。複数のJava認定ガイドを執筆し、現在はフリーランスでPearson InformITにおいて録画やライブによるビデオ・トレーニングを行っている(直接またはO'Reilly Safari Books Onlineサービス経由で視聴可能)。OracleのJava認定プロジェクトにも継続的に関わっている。   Mikalai Zaikin Mikalai Zaikin:ベラルーシのミンスクを拠点とするIBA IT ParkのリードJava開発者。OracleによるJava認定試験の作成に携わるとともに、複数のJava認定教科書のテクニカル・レビューを行っている。Kathy Sierra氏とBert Bates氏による有名な学習ガイド『Sun Certified Programmer for Java』では、3版にわたってテクニカル・レビューを務めた。

※本記事は、Simon RobertsとMikalai Zaikinによる"Quiz Yourself: Using Core Functional Interfaces: Predicate (Advanced)"を翻訳したものです。 Predicateインタフェースは複雑になることがあるため、その結果を扱う場合は十分に注意を 著者:Simon Roberts、Mikalai Zaikin  2020年...