📜 ⬆️ ⬇️

What we lack in java



In this article, we will look at some of the missing features in Java. But it must be immediately emphasized that certain things will be deliberately omitted, which are either already actively discussed or require too much work at the virtual machine level. For example:

There are no materialized generics (reified generics) . Only the lazy one didn’t write about it, and most of the comments indicate a lack of understanding of the essence of type mashing. If a Java developer says, “I don’t like typewriting,” then in most cases this means, “I need a List int .” The question of the primitive specialization of generics is only indirectly related to mashing, and the benefits of generics seen during the execution are greatly exaggerated by the rumors.
')
Unsigned calculations (unsigned arithmetic) at the virtual machine level. The lack of support for unsigned arithmetic types in Java has caused discontent among developers for many years. But this is a deliberate decision of the creators of the language. The presence of only sign calculations greatly simplifies the language. If today we begin to implement unsigned types, then this will entail a very serious processing of Java, which is fraught with a mass of large and small bugs that will be difficult to catch. At the same time greatly increases the risk of destabilization of the entire platform.

Long pointers for arrays . Again, the introduction of this functionality will require too deep processing of the JVM with possible unpleasant consequences, and not only in terms of the behavior and semantics of garbage collectors. Although it should be noted that Oracle is looking for ways to implement such functionality using the VarHandles project.

Here we will not go into details of the possible Java syntax for the discussed functionality. Unfortunately, such discussions in general often fall into disputes about the syntax, although semantics is more important.

More expressive import syntax


The syntax for importing into Java provides us with not so many possibilities, only two options are available: import of one class or the whole package. And if you need to import only part of the package, then you have to pile up a bunch of lines. Also, the poverty of the syntax makes it necessary to use IDE features such as import convolution for the largest Java files.

It would make life easier if we could import several classes from one package in one line:

 import java.util.{List, Map}; 

The readability of the code would increase due to the possibility of local type renaming (or creating an alias). At the same time there would be less confusion with types that have the same short class name:

 import java.util.{Date : UDate}; import java.sql.{Date : SDate}; import java.util.concurrent.Future; import scala.concurrent.{Future : SFuture}; 

Benefit and implement extended wildcard characters:

 import java.util.{*Map}; 

This is a small but useful change that can be fully implemented using javac.

Literals Collections


In Java, there is a syntax (albeit limited) for declaring array literals. For example:

 int[] i = {1, 2, 3}; 

This syntax has several disadvantages. For example, literals should only be used in initializers.

Arrays in Java are not collections. The “bridge methods” presented in the auxiliary class Arrays also have flaws. Say, the Arrays.asList() method returns an ArrayList , which on closer inspection turns out to be Arrays.ArrayList . This inner class does not contain alternative methods for the List , and similar methods throw an OperationNotSupportedException exception. As a result, an ugly seam occurs in the API, making it difficult to navigate between arrays and collections.

There are no reasons for refusing the syntax of declaring an array literal; in one form or another it is present in many languages. For example, in Perl you can write this:

 my $primes = [2, 3, 5, 7, 11, 13]; my $capitals = {'UK' => 'London', 'France' => 'Paris'};  Scala — : val primes = Array(2, 3, 5, 7, 11, 13); val m = Map('UK' -> 'London', 'France' -> 'Paris'); 

Unfortunately, Java has no useful collection literals. This question has been raised repeatedly, but this functionality has not appeared in either Java 7 or Java 8. Object literals are also interesting, but in Java they are much more difficult to implement.

Structural typing


Naming plays a very important role in the type system in Java. All variables must refer to named types, and it is impossible to express a type only through the definition of its structure. In other languages, for example, in Scala, you can express a type without declaring it when implementing an interface (or Scala trait), but simply by confirming that it contains a specific method:

 def whoLetTheDucksOut(d: {def quack(): String}) { println(d.quack()); } 

In this case, any type that contains the quack() method will be accepted, regardless of whether there is inheritance or types using a common interface.

It is no coincidence that quack() was chosen as an example - structural typing has much in common with duck typing in the same Python. However, in Scala, typing is done at compilation, which indicates the flexibility of the language in terms of expressing types that would be difficult or impossible to express in Java. Unfortunately, here the type system has very little structural typing capabilities. You can specify a local anonymous type with additional methods, and if one of them is immediately called, Java will allow you to compile the code.

This is where the possibilities end: we can create only one “structural” method. From it it is impossible to return the type that contains the additional information we need. All structural methods are valid, expressed in bytecode and support reflexive access. They simply cannot be expressed using the Java type system. Perhaps this should not surprise anyone, since structural methods are actually implemented using an additional class file that corresponds to an anonymous local type.

Algebraic data types


Thanks to generics, Java is a language with parameterized types (parameterized types), which are reference types with type parameters. When they are substituted into some types, others are formed. That is, the resulting types consist of “containers” (generic types) and “payload” (values ​​of type parameters).

In some languages, the supported composite types are very different from Java generics. As an example, tuples are immediately suggested, although sum type (sum type), sometimes also called “disjoint union of types” or “hosted unions” (tagged union), are of much more interest.

Type-sum is an unambiguous type, that is, at each moment of time variables can have only one value. But at the same time it can be any valid value belonging to a specified range of different types. This is true even if the non-connected types that are values ​​are in no way related to each other in terms of inheritance. For example, in the F # language, you can specify the type Shape, instances of which can be rectangles or circles:

type Shape =
| Circle of int
| Rectangle of int * int

F # is very different from Java, but in Scala these types are implemented with restrictions: sealed types are used with case classes. A sealed class cannot be extended beyond the current compilation unit. This is practically an analogue of the terminal class in Java, but in Scala, the base unit of compilation is a file, and numerous high-level open classes (public classes) can be declared in a single file.

This leads us to a pattern in which a sealed abstract base class is declared along with several subclasses that correspond to possible incoherent types from a type-sum. The Scala standard library contains many examples of using this pattern, including Option[A] , which is similar to the Optional T type from Java 8.

In Scala, the disjoint unions of the two possibilities include Option and Some , as well as the None and Option type .

If we implemented the same mechanism in Java, we would face a restriction when the compilation unit is essentially a class. It turns out not as convenient as in Scala, but you can still come up with solutions. For example, you could use javac to handle the new syntax for the classes we want to seal:

 final package example.algebraic; 

Such a syntax would mean that the compiler should allow for class extension taking into account the final packaging within the current folder, rejecting all other expansion attempts. This change could also be implemented using javac, but without checks during execution it cannot be completely protected against a cyclic code (reflective code). In addition, a Java implementation would be less useful than in Scala, since Java lacks developed match expressions.

Dynamic call sites


Starting from version 7 in Java, a surprisingly useful tool has appeared: invokedynamic bytecode, designed to perform the role of the main calling mechanism. This allows you to run dynamic languages ​​on top of the JVM, as well as extend the type system in Java by adding built-in methods and changing the interface, while previously it was not possible. Pay for this account for a slightly increased complexity. But with skillful handling, invokedynamic is a powerful tool.

True, he has one strange limitation. Despite the declared support in Java 7, for some reason, direct access to methods of dynamic invocation is still not provided. Although the whole point of dynamic dispatch is to allow developers to decide for themselves which method to call from a particular call point, and the decision can be postponed until the moment the code is executed.

Note: Do not confuse this dynamic binding method with the C # dynamic keyword. In our case, an object is entered that dynamically defines its bindings during execution; this does not work if the object does not support the requested method calls. Instances of such dynamic objects in the course of execution are indistinguishable from "ordinary" objects, and the mechanism itself turns out to be unsafe.

While invokedynamic used to implement lambda expressions and embedded methods in Java, developers do not have direct access and cannot perform dispatching at runtime. In other words, in Java there is no keyword or other construct for creating general purpose invokedynamic call points. The javac compiler simply does not translate invokedynamic instructions outside of the language infrastructure.

You can simply add this functionality to Java. For example, using some keyword or annotating. You will also need an additional library and support at the assembly stage.

Glimmers of hope?


The development of language architecture and its implementation is the art of achieving the possible. There are many examples when important changes make their way very long. For example, in C ++, lambda expressions appeared only in version 14.

Many people do not like the leisurely development of Java. But James Gosling takes the position that functionality cannot be implemented until it is fully understood and understood. Although the conservatism of the Java architecture is one of the reasons for the success of this language, at the same time, many impatient young developers who are eager for rapid change do not like it. Are any of the above possibilities being implemented? You can carefully assume this.

Some of the ideas described can be implemented using the same invokedynamic . As you remember, it should fulfill the role of the main mechanism of the call, postponed until the moment of execution. According to the proposal to improve the JEP276 language, you can standardize the Dynalink library, which was originally created by Attila Szegedi for the implementation of the “meta-object protocol” in the JVM. Later, the library author moved to work at Oracle, which used Dynalink in Nashorn, a JavaScript implementation on the JVM. The library description is on Github, but it has been removed from there.

Essentially, Dynalink allows you to talk about object-oriented operations - “get the value of a property”, “assign a property a value”, “create a new object”, “call a method” - without having to implement their semantics with the help of the corresponding statically typed, low-level JVM operations.

This binding technology can be used to implement dynamic linkers, whose behavior will be different from the standard. In addition, it can act as a kind of draft for the implementation of new properties of the type system in Java.

Some key Scala developers considered this mechanism as a possible replacement for the implementation of structural types in this language. Although in the current version the bet is made on reflection, but the appearance of Dynalink on the scene can change everything.

Source: https://habr.com/ru/post/281513/


All Articles