Javaプログラミング言語とJavaソフトウェアプラットフォームは、ジェネリクスの実装、強制的なオブジェクト指向プログラミング、符号なし数値の処理、浮動小数点演算の実装、主要なJava VM実装であるHotSpotにおけるセキュリティ脆弱性の歴史など、設計上の選択について批判を受けてきました。Javaで書かれたソフトウェア、特に初期バージョンは、他のプログラミング言語で書かれたソフトウェアと比較してパフォーマンスが低いと批判されてきました。開発者はまた、すべてのJava実装で動作する必要のある複雑なJavaプログラムを作成する際には、さまざまなJava実装の違いを考慮に入れる必要があると指摘しています。[ 1 ]
Javaでは、メソッドがスローするチェック例外をメソッドシグネチャ内で宣言する必要があるチェック例外が導入されました。これは、不必要に冗長な定型コードにつながる可能性があります。主要なプログラミング言語で、Javaに続いてチェック例外を実装したものはまだありません。
Java 5.0 にジェネリクスが追加されたとき、既に大規模なクラスのフレームワークが存在していた(その多くは既に非推奨になっていた)ため、ジェネリクスは型消去を使用して実装され、移行の互換性とこれらの既存クラスの再利用を可能にした。これにより、他の言語と比較して提供できる機能が制限された。[ 2 ] [ 3 ]
ジェネリクスは型消去を使用して実装されているため、テンプレートパラメータ E の実際の型は実行時には利用できません。したがって、Java では次の操作は不可能です。[ 4 ]
public class MyClass < E > { public static void myMethod ( Object item ) { if ( item instanceof E ) { // コンパイラエラー// ... } E item2 = new E (); // コンパイラエラーE [] iArray = new E [ 10 ] ; // コンパイラエラー} }さらに、2016年に、Javaが健全でないことが明らかになり、その結果、ClassCastExceptionやその他の種類のランタイムエラーをスローするJVMが技術的に適合していないことが判明しました。[ 5 ]これはJava 10で修正されました。
class Nullless < T , U > { class Constrain < B extends U > { // ここに何か} final Constrain <? super T > constrain ; final U u ;Nullless ( T t ) { u = coerce ( t ); constrain = getConstrain (); }< B extends U > U upcast ( Constrain < B > constrain , B b ) { return b ; }U coerce ( T t ) { return upcast ( constrain , t ); }Constrain <? super T > getConstrain () { return constrain ; }public static void main ( String [] args ) { String zero = new Nullless < Integer , String > ( 0 ). u ; } }Javaは設計上、プログラマーが互いに相互作用する名詞(クラス)の観点から解決策を考え、動詞(メソッド)をその名詞に対して、またはその名詞によって実行できる操作として考えるように促します。[ 6 ]スティーブ・イェッゲは、クラスはそれに対して操作する複数の関数を持つことができるが、関数はクラスに束縛されており、複数の型に対して操作することはできないため、これは言語の表現力に不必要な制限をもたらすと主張しています。[ 7 ]
他の多くのマルチパラダイム言語は、関数をトップレベルの構成要素としてサポートしています。関数オーバーロード(1つの動詞、複数の名詞)やジェネリック関数(1つの動詞、特定の特性を持つ名詞のファミリー)などの他の機能と組み合わせることで、プログラマは特定の問題を名詞で解決するか動詞で解決するかを決定できます。Javaバージョン8では、いくつかの関数型プログラミング機能が導入されました。
Javaにはネイティブの符号なし整数型がありません。符号なしデータはC言語で書かれたプログラムから生成されることが多く、これらの型がないためにC言語とJavaの間で直接データを交換できません。符号なしの大きな数値は暗号化を含む多くの数値処理分野でも使用されており、これらのタスクでJavaを使用するのが不便になる場合があります。[ 8 ] 変換コードとより大きなデータ型を使用してこの問題を回避することは可能ですが、Javaで符号なしデータを扱うのは面倒になります。32ビットの符号付き整数を使用して16ビットの符号なし値を損失なく保持でき、64ビットの符号付き整数を使用して32ビットの符号なし整数を保持できますが、64ビットの符号なし整数を保持するためのより大きな型はありません。いずれの場合も、消費されるメモリは2倍になる可能性があり、通常、2の補数オーバーフローに依存するロジックはすべて書き直す必要があります。抽象化すると、他の言語ではネイティブである多くの操作で関数呼び出しが必要になります。あるいは、Java の符号付き整数を使用して同じサイズの符号なし整数をエミュレートすることも可能ですが、これにはビット演算の詳細な知識が必要です。[ 9 ] JDK 8 では符号なし整数型が一部サポートされていましたが、符号なしバイト型はサポートされておらず、Java 言語でもサポートされていませんでした。[ 10 ]
Java は、ユーザー定義演算子をサポートしていないことで批判されてきました。演算子のオーバーロードは可読性を向上させますが、[ 11 ]その欠如は、特に複素数や行列などの数学的オブジェクトを表すクラスの場合、Java コードの可読性を低下させる可能性があります。Java には、文字列連結のための演算子の非数値的な使用例が 1 つしかありません。ただし、これはコンパイラによって実装され、インスタンスを作成するコードが生成されます。ユーザー定義の演算子オーバーロードを作成することは不可能です。++=StringBuilder
Java には、C の構造体のような複合値型がありません。これは、参照を介して間接的に操作するのではなく、直接操作されるデータの束です。値型は、参照を持つクラスよりも高速で小さい場合があります。[ 12 ] [ 13 ] [ 14 ]例えば、Java の は、オブジェクトHashMapへの参照の配列として実装されています。 [ 15 ]これらのオブジェクトには、キーと値のオブジェクトへの参照が含まれています。何かを検索するには、非効率的な二重の逆参照が必要です。が値型であれば、配列はキーと値のペアを直接格納でき、最初の間接参照がなくなり、参照の局所性が向上し、メモリ使用量とヒープの断片化が削減されます。さらに、Java が汎用プリミティブ型をサポートしていれば、キーと値を配列に直接格納でき、両方のレベルの間接参照がなくなります。HashMap.EntryEntry
Javaは、 2³¹(約21億)以上の要素を持つ配列をサポートしていないことで批判されてきた。 [ 16 ] [ 17 ]これは言語の制限であり、Java言語仕様書のセクション10.4には次のように記載されている。
配列はint値でインデックス付けする必要があります... longインデックス値で配列コンポーネントにアクセスしようとすると、コンパイル時エラーになります。[ 18 ]
大規模な配列をサポートするには、JVM の変更も必要になります。[ 19 ]この制限は、コレクションが 20 億要素に制限されていること[ 20 ]や、2 GB を超える連続したファイル セグメントをメモリ マップできないこと[ 21 ] などの領域で現れます。Javaには (2D 配列以外に) 多次元配列 (単一の間接参照でアクセスされる連続して割り当てられた単一のメモリ ブロック) もないため、科学技術計算のパフォーマンスが制限されます。[ 13 ]
配列とプリミティブはやや特殊であり、クラスとは異なる扱いが必要です。汎用ライブラリを作成する際に多くの関数のバリエーションが必要になるため、これは批判されています[ 22 ] 。
Per Brinch Hansenは1999年に[ 23 ]、Javaの並列処理の実装全般、特にモニタは、安全で信頼性の高い並列プログラミングに必要な保証や強制を提供していないと主張した。プログラマは設計やコーディングの規約を確立できるが、コンパイラはそれらを強制する試みをしないため、プログラマは意図せず安全でない、または信頼性の低いコードを書いてしまう可能性がある。
Java はオブジェクトシリアライゼーションのメカニズムを提供しており、オブジェクトは、そのデータフィールドと、オブジェクト自身およびそのフィールドに関する型情報を含むバイト列として表現できます。オブジェクトがシリアライズされた後、後でデシリアライズできます。つまり、そのデータを表す型情報とバイトを使用して、メモリ内でオブジェクトを再構築できます。[ 24 ]これは、理論的にも実際的にも非常に深刻なセキュリティリスクを引き起こします。[ 25 ] [ 26 ]
Java の浮動小数点演算は、大部分がIEEE 754 (バイナリ浮動小数点演算の標準)に基づいているものの、例外フラグや指向丸めなど、 (現在では冗長な[ 27 ] )修飾子を使用した場合でも、必須の標準機能の一部はサポートされていません。IEEE 754 で定義され、多くのプロセッサでサポートされている拡張精度型は、Java ではサポートされていません。[ 28 ] [ 29 ] [ 30 ]strictfp
Javaはタプルをネイティブにサポートしていないため、プログラマがインポートして処理する必要のあるサードパーティの実装が多数存在する。[ 31 ]汎用タプルクラスに反対する理由は、保守性の低いコードが増殖する危険性があるからである。[ 32 ]
2008年、米国国防総省のソフトウェア技術支援センターは、Journal of Defense Software Engineering誌に、Javaを最初のプログラミング言語として教えるのは不適切であると論じた記事を掲載した。欠点としては、学生が「ソースプログラムとハードウェアが実際に行うこととの関係を全く理解していない」こと、そして「どのメソッド呼び出しが最終的に何を実行するのかを知るのが非常に難しいため、記述したコードの実行時コストを理解できない」ことが挙げられる。[ 33 ] 2005年、ジョエル・スポルスキーは、エッセイ「Javaスクールの危険性」の中で、Javaが大学のカリキュラムの中で過度に重視されていると批判した。[ 34 ]ネッド・バッチェルダーのような他の人々は、スポルスキーが理解しにくいと感じた言語の部分を批判したことに異議を唱え、スポルスキーのコメントは「主観的な暴言」に近いと主張した。[ 35 ]
2000年以前、 Java 1.3でHotSpot VMが実装された当時は、そのパフォーマンスについて多くの批判があった。Javaは最適化されたネイティブコードと同等の速度で動作することが実証されており、最新のJVM実装は、利用可能な言語プラットフォームの中で最も高速なものの1つとして定期的にベンチマークされており、通常はCやC++よりも3倍以上遅くはない。[ 36 ]
初期バージョン以降、パフォーマンスは大幅に向上しました。[ 37 ]いくつかの最適化されたテストでは、 JITコンパイラのパフォーマンスはネイティブコンパイラと比較して非常に似ていることが示されています。[ 37 ] [ 38 ] [ 39 ]
Javaバイトコードは、仮想マシンによって実行時に解釈されるか、ロード時または実行時にコンパイルされてコンピュータのハードウェア上で直接実行されるネイティブコードに変換されます。解釈はネイティブ実行よりも低速ですが、ロード時または実行時のコンパイルには初期パフォーマンスの低下という欠点があります。最新のJVM実装はすべてコンパイル方式を採用しているため、初期起動時間を過ぎるとパフォーマンスはネイティブコードとほぼ同等になります。
ゲームデザイナー兼プログラマーのジョン・カーマックは、2005年に携帯電話のJavaについて次のように結論付けました。「最大の問題は、Javaが非常に遅いことです。純粋なCPU/メモリ/ディスプレイ/通信レベルでは、ほとんどの最新の携帯電話はゲームボーイアドバンスよりもはるかに優れたゲームプラットフォームであるはずです。Javaを使用すると、ほとんどの携帯電話では、オリジナルの4.77MHz(原文ママ)IBM PCと同程度のCPUパワーしか得られず、あらゆるものに対する制御が劣悪になります。」[ 40 ]
Javaプラットフォームは、悪意のあるソフトウェアや不適切に記述されたソフトウェアから保護するために、ユーザーが信頼できないバイトコードを「サンドボックス」内で実行できるように設計されたセキュリティアーキテクチャ[ 41 ]を提供します。この「サンドボックス」機能は、ローカルファイルシステムやネットワークへのアクセス、任意のコマンドの実行など、マルウェアによって悪用される可能性のあるプラットフォーム機能やAPIへのアクセスを制限することで、ユーザーを保護することを目的としています。
2010年には、OracleのJava実装を含むJava実装で使用されているサンドボックス機構のセキュリティ上の欠陥を標的とした悪意のあるソフトウェアが大幅に増加しました。これらの欠陥により、信頼できないコードがサンドボックスの制限を回避し、ユーザーを攻撃にさらすことが可能になりました。これらの欠陥はセキュリティアップデートによって修正されましたが、アップデートが適用されていないマシンでは依然として悪用されていました。[ 42 ]
批評家たちは、ユーザーがJavaのインストールをアップデートしないのは、Javaがインストールされていることを知らないか、アップデートする方法を知らないからだと指摘している。多くの組織はユーザーによるソフトウェアのインストールを制限しているが、アップデートの展開は遅い。[ 42 ] [ 43 ]
Oracleは、既知のセキュリティバグに対するアップデートを迅速に提供しなかったことで批判されてきた。[ 44 ] OracleがJava 7の広く悪用された脆弱性に対するパッチをようやくリリースしたとき、OracleはJava 6をユーザーのマシンから削除したが、Java 6はOracleが脆弱性の影響を受けないと述べていたエンタープライズアプリケーションで広く使用されていた。[ 45 ]
2007 年、マルコ・ピストイア率いる研究チームは、スタック検査に基づいてJava セキュリティ モデルのもう 1 つの重要な欠陥を明らかにしました[ 46 ]。セキュリティ上重要なリソースにアクセスされると、セキュリティマネージャはコール スタックをたどるコードを実行し、スタック上の各メソッドのコード ベースがリソースにアクセスする権限を持っていることを検証します。これは、正当な、より特権のあるプログラムが別のプログラムに騙されて権限を悪用するたびに発生する混乱した代理人攻撃を防ぐために行われます。混乱した代理人の問題は、特権昇格の特定のタイプです。ピストイアは、セキュリティ上重要なリソースにアクセスされると、リソースを取得する責任のあるコードがスタック上に存在しない可能性があることに気付きました。たとえば、過去に実行されたメソッドが、使用するリソースを決定するオブジェクト フィールドの値を変更した可能性があります。そのメソッド呼び出しは、検査時にスタック上に存在しない可能性があります。
一部のパーミッションは、Java の と暗黙的に同等ですAllPermission。これには、現在のセキュリティマネージャを変更する権限 (スタック検査を回避できる可能性のあるものに置き換える)、カスタムクラスローダーをインスタンス化して使用する権限 (AllPermissionロード時に悪意のあるクラスに関連付けることを選択できる)、カスタムパーミッションを作成する権限 (メソッドAllPermissionを介してと同等の強力さを宣言できるimplies) が含まれます。これらの問題は、Pistoia の Java セキュリティに関する 2 冊の本に記載されています。[ 47 ] [ 48 ]
Java 7 より前は、インストーラーは古い Java インストールを削除しませんでした。Windows システムでは、同じコンピューターに複数の Java インストールが存在することはよくありました。[ 49 ] [ 50 ] [ 51 ]複数のインストールが許可されており、悪意のあるプログラムを含む、特定のバージョンに依存するプログラムで使用される可能性がありました。この問題は Java 7 で解決されました。ユーザーの許可があれば、インストーラーは以前のインストールを削除します。[ 52 ]
JITコンパイルは基本的に実行可能データを使用するため、セキュリティ上の課題や悪用される可能性が生じる。
これまでのところ、Java の「一度書いたらどこでも実行できる」という約束は実現していません。Java アプリケーションの大部分はほとんどの Java 実装間で移行できますが、VM 固有の機能を利用すると移植の問題が発生します。
真の矩形多次元配列は、科学技術計算において最も重要なデータ構造です。
231を超えるエントリを
持つ配列は作成できません
...
最初のプログラミング言語としての Java の落とし穴 [...] 学生は、グラフィック インターフェイスのないプログラムを書くのが難しく、ソース プログラムとハードウェアが実際に行うこととの関係を理解できず、(最も有害なことに) ポインタのセマンティクスをまったく理解していなかったため、システム プログラミングで C を使用することが非常に困難でした。
が、将来優れたプログラマーになれない子供たちを選別できていないだけでも問題だが、学校側はそれを自分たちの問題ではないと正当に主張できるだろう。業界、少なくともgrepを使う採用担当者は、Javaの教育を強く求めているのは間違いない。しかし、JavaSchoolsは、子供たちの脳を、優れたソフトウェア設計を行うのに十分な熟練度、俊敏性、柔軟性を備えるように訓練することもできていない。
はなぜポインタと再帰を 2 つのゲートキーパー概念として選んだのでしょうか? 難しいと感じたからでしょうか? Tim Bray が指摘するように、Java は再帰に非常に長けており、いずれにせよ並行処理の方が習得するのがより重要で難しい概念かもしれません。Lisp 言語における再帰の強調は少し行き過ぎで、他のプログラミング文化には引き継がれていません。なぜ人々はソフトウェア エンジニアリングにとって再帰がそれほど重要だと考えているのでしょうか? 誤解しないでください。再帰が適切なツールである場合は私も大好きですが、Joel が基本概念として重視するほど頻繁にはそうではありません。
男と少年を分ける難しい概念を探している間に、2 年前に Joel と私が論争になった概念、例外についてはどうでしょうか。基本的に、彼はポインタが苦手で、混乱してしまうから嫌いなのです。これは、Javaのプログラマーがポインタを嫌うのと何ら変わりません。確かに、例外を避けてステータスリターンを使うことはできますが、ポインタを極力避けることもできます。だからといって、そうすべきだということでしょうか?つまり、ジョエルは自分が好きな概念(ポインタと再帰)を持っていて、それらが衰退していくことを嘆いているのですが、Javaの若者たちが慣れ親しんでいる、彼がまだ理解できていない新しい概念があることには気づいていないようです。