New Features in Java 14
blogs.oracle.com
blogs.oracle.com
I'm in no way a Rust expert, but I see a lot of code with Optionals, something like
match sth {
Some(v) => do_something
None => nothing_to_do
}
This looks awfully a lot like if (something == null) {
nothing_to_do
} else {
do_something
}
It might look more pleasant, but it doesn't "solve" anything, only shifts it in a different place.Go is in the middle, because structs have no null value, only a zero value; the only thing that can be nil are pointers which I personally feel should used as little as possible.
In Java you tend not to know where or when null checks have or will happen. So you tend to sprinkle null checks all over your code just incase someone somewhere forgot to check.
The key difference is that now the type system can tell you the truth. In other languages when you have a reference to a type T, null is considered a valid T but you cannot treat it as one or everything blows up. Meanwhile in rust when you have a reference to T, you don't have a secret (not)T that invisibly breaks everything.
doThingA(param: any) // you can pass anything including null and undefined
doThingB(param: object | number | string | boolean | bigint | symbol) // you can pass anything except null and undefinedAre you sure? Each line of Java or Javascript can be equivalent to multiple `unwrap()` calls. You can't see them because the compiler is generating ([0]) them but they are there and there are many hundreds of thousands (or maybe even millions including dependencies) of them in most Java projects.
Here is an example: company.board.members.size() is equivalent to company.unwrap("NPE).board.unwrap("NPE).members.unwrap("NPE).size() and every single line in Java is like that. You can't opt out. You can't tell your coworkers to stop using "unwrap" during code reviews. The entire language forces you to do this in every single line that involves a reference.
[0] Well, it's actually just trapping the 0 address but it's equivalent.
> Please respond to the strongest plausible interpretation of what someone says, not a weaker one that's easier to criticize. Assume good faith.
I am well aware of the issues in other popular languages. You and I agree that there is an implicit `unwrap()` call in each dereference operation.
The post I was responding to claimed that in Rust you cannot leave null/None values unhandled. This is false because `unwrap()` exists and more importantly is in fairly common use.
The distinction is not whether you can (at your own peril of course) assume a value is not null. Rust lets you do that.
As you point out however, it does force you to be more explicit. Why is that? It's because the concept of `None` is not hidden from the type system.
The reason that your Java examples work that way is not because `unwrap()` is implicit, it's because the type system doesn't know about nulls in the first place.
> The key difference is that in your second example you can just forget about the else branch, while in Rust you must explicitly handle the None case, otherwise it won't compile.
Do you really think that `unwrap()` is meaningfully different from "forget[ting] about the else branch"? Does it require you to "explicitly handle the None case"?
My goal here was not to get mired in semantic arguments that ignore the context of the discussion. It was merely to highlight that Rust lets you assume that things won't be null the same as any other language, and that the important difference is that rust lets you be clear about what can and cannot be null.
I think this is the point of contention. Languages with some Option type and no null are not the same as any other language. Yes, you can ignore error handling, but you cannot forget it.
I believe that claims that Rust forces you to not "forget about the else branch" are actively harmful to language adoption, I've seen many people use `unwrap()`'s existence as evidence that Rust's approach to empty value types doesn't actually provide any useful benefits.
If you're implying that coders use unwrap() pre-emptively, I don't think there's any evidence for that. Besides, you can't just slap it on every value you have - it must actually be a result type! - so even if it's applied incorrectly in advance, that still requires some conscious consideration.
This is not at all the same as forgetting an "else" - or, far more often, forgetting the "if" altogether.
match x {
Some(v) => return v
None => die painfully
}
?If so, the None is still handled. Dying painfully is an escape hatch out of the type system in any language that lets you do it.
> The key difference is that in your second example you can just forget about the else branch, while in Rust you must explicitly handle the None case, otherwise it won't compile.
Do you really think that `unwrap()` is meaningfully different from "forget[ting] about the else branch"? Does it require you to "explicitly handle the None case"?
My goal here was not to get mired in semantic arguments that ignore the context of the discussion. It was merely to highlight that Rust lets you assume that things won't be null the same as any other language, and that the important difference is that rust lets you be clear about what can and cannot be null.
I'm not sure I agree with this though:
> Do you really think that `unwrap()` is meaningfully different from "forget[ting] about the else branch"? Does it require you to "explicitly handle the None case"?
I haven't used Rust, so but digging up the source shows unwrap is indeed just a `match` that panics on None.
The fact that "you can put thing inside a function and call that function" doesn't mean you "don't have to explicitly do thing". We end up with a pretty useless standard for "explicitly doing thing" if putting thing in a function doesn't count.
In Rust you're passed a reference to an object. Can it be null? No, it simply can't! There does not exist a magic "null" value for references. To represent the possible absence of a value, you explicitly encode it into the type system by using `Option<T>`.
The actual difference is the reliance on pattern matching and the compiler enforcing coverage on those languages.
This doesn't require an option type. Option types are actually quite awkward compared to language integration. Kotlin doesn't use an Option type yet still delivers all the same benefits as Rust/Haskell's approach, with benefits (e.g. zero overhead, integrated syntax). Note that Option type overhead isn't merely about heavyweight syntax and the need for the compiler to successfully scalarise: using language integration around standard null pointers means the CPU spends no time on checking the 'maybe' branch. If the type system wasn't violated and a value is present, it's used immediately without any additional instructions or code bloat. If it's not then that will trigger a page fault at some point in the CPU pipeline and transfer control to an exception handler. If the type system indicates nullability and you need to actually do more than just unwrap it, then you have to take a branch of course but it's fully inlined and cheap.
I agree. In Java at least the semantics are practically identical.
I can't comment on Rust. I suspect it does a slightly better job.
Crystal is another language where there is no null. It's quite cool.
Java and Rust are practically the same regarding the differences between Optional<T> and T (java), and Option<T> and T (rust). Optional<String> in Java has the `.get()` method which corresponds to the `.unwrap()` method in Rust. And in both are different different types than String in their respective languages.
But in Java you can have an Optional<String> that's null, not just empty. So that's three states - null, wrapping null, or wrapping a value.
I'd say the big difference is that in Rust you can have places that refuse nulls (e.g. using T instead of Option<T>), so you have well defined points of control/checking. That, combined with the difficulty in recovering from a panic (the equivalent of a null pointer exception when trying to unwrap a value that's missing) and the Result type, are Rusts real strength wrt. missing values.
Regarding unwrap: If you tell the compiler "I know what I'm doing" and you don't know what you are doing then no one can help you. It's the same as putting casts everywhere when the compiler yells at you about incompatible types: If you lie to the compiler it will do your bidding, but your code will run into errors at runtime that you could have prevented at compile time. Again: No one can help you here.
func getFoo() (*Foo, error)
func main(){
foo, err := getFoo()
if err != nil {
// be an adult and handle your error
}
foo.Work()
// panic because whoever implemented getFoo returned a nil object
}
Also worth noting dealing with booleans in JSON is wonky because boolean values default to false, even if that json property wasn't set over the wire. Thus you end up unmarshaling to a pointer to a boolean, which now requires nil guards throughout your codebase when working with that unmarshaled object...The equivalent rust code would fail to compile.
Kotlin solves it much better. sth?.do_something() ?: nothing_to_do
[0]: https://doc.rust-lang.org/edition-guide/rust-2018/error-hand...
Java has solved memory safety. Now the task is to make sure that code that can panic will not compile
Exception/panic-free code is fairly straightforward; you just need to have APIs return Option<_>/Result<_>/other error code equivalents to indicate failure. The problem with that is that it introduces some amount of friction that will probably make some programmers unhappy.
If you want to avoid returning Option<_>/Result<_>/other error code equivalents, you need some way to prove your inputs cannot trigger an invalid result, and that is a very difficult problem.
With this new feature I'd argue the Java/Kotlin world now has the best handling of optionality of any language, anywhere:
• Pleasant, concise syntax for handling optionality. Not bolted on with an option type.
• Highly efficient translation to machine code. No boxing, no unnecessary branching in the unwrap/!! case.
• Excellent interop with older code written in OOP languages that don't represent nulls in the type system.
• But that older code can be annotated with @NotNull and @Nullable to retrofit the information; within Java IntelliJ will statically analyse these and add warnings where an NPE/panic would occur, when such code is used by Kotlin the annotations cause the types to be correctly derived as nullable or non-null.
• The large wealth of libraries that want to explore object graphs continue to work without being distracted by option/maybe types (e.g. serialisation, UI binding)
• On the rare occasions where you do need to unwrap and you got it wrong, you now get an error message that breaks down the sub-expression where the null pointer occurred, so there's no incentive to break up long chains of dereferences just to get better debugging in case of failure.
That's a featureset around optionality that's hard to match.
> • Pleasant, concise syntax for handling optionality. Not bolted on with an option type.
Optionality is not special enough to be worth a special case in the type system, IMO. Maybe some syntactic sugar could be worthwhile, but not a magic type that behaves differently from other types (which is what Kotlin's ? is) - you need it to behave like a normal type so that you can write and reuse generic code. Arrow-kt can't implement functions that work with nullable values, and has to implement its own Option type instead.
> • Highly efficient translation to machine code. No boxing, no unnecessary branching in the unwrap/!! case.
Getting the semantics right is the important thing; it doesn't matter how fast the code runs if it's broken. It's still possible to compile a well-behaved option type into something unboxed in the cases where it can be represented that way (look at what Rust does, where the option is "packed" if 0 is not a valid value for the inner type, but still does the right thing for Option<Option<T>>, Option<Int> and so on).
In the `if == null` method, the `something` reference is null, but if you have functions that also reference `something` down the line, you're going to have to check it again for null-ness.
sth.map(|v| do_something);
incidentally.It means that you've told the type system what it needs to support you.
If your function tells the type system you need a reference to a type, you get a reference to that type. There's no hidden timebomb in there where you might get something that supposedly is that type but can't be treated as that type.
This lets you write your functions to accept nulls where appropriate, and require some form of unwrapping at the call site otherwise. It tells you when you've missed something and it lets you refactor without having to constantly repeat all of your checks, once you know something is valid the type system reflects that for you.
In short if you have a T in rust, you know that it's not secretly a (not)T that is implicitly allowed anywhere a T can be.
It shifts it right into your face. You're forced to do something instead of pretending that everything could be potentially null. Since the only places where you can actually encounter "null" are documented you are no longer wasting your brain cells on the happy path where you are guaranteed to never see a null value when you don't expect it. Instead you can free up all that cognitive capacity to actually write correct code that handles null values.
I wouldn't quite phrase it that way, since it's nothing new. OCaml from the 1990s has no null value (you need to represent potentially missing values with Option). I suspect there are older examples than that.
Instead, with the implementation that we got in Java, if you tried to write this Map.getOption then if your map was ever used with old code that used nulls then it would break. So there's no way to ever start migrating to using options.
There are other problems with null, but they mostly boil down to the same issue: different code uses null to represent two different things, and so gets confused about what it means. (The other problem is that you can't look at a value and know that it can't be null, but optional definitely can't solve that until the whole ecosystem migrates to it).
If you ask me, they should have introduced another class, something like NullableOptional, similar to Optional for those use-cases. I don't agree that putting `null` in every optional just because of few bad APIs is a good idea, because people use Optional exactly to avoid dealing with nulls.
On the contrary, a selling point of Optionals is that they let you avoid that problem. Like I said, you could have a Map.getOption(key) function that unambiguously tells you whether the value is present or not, because it only ever returns None for absence, and will always return Some(value) (which might be Some(None) or Some(null), but those can be distinguished from None) if the value is present.
> I don't agree that putting `null` in every optional just because of few bad APIs is a good idea, because people use Optional exactly to avoid dealing with nulls.
Optional should be a transparent, consistent class that can contain any value that's valid in the language. Maybe Java will eventually reach a point where null can be considered invalid, but until that day, having Option ban putting null in it because it's old and deprecated makes about as much sense as if ArrayList banned you from making lists that contained deprecated classes.
Scala avoids the biggest pitfall of multiple inheritance elegantly, by only allowing one parent to have a constructor; classes may only be the first parent, and traits can't have constructors that take arguments. Unfortunately this doesn't mean they don't need to be initialized; in particular, vals in a mixed-in trait will be null if you try to access them in an earlier constructor.[2] As with any null, in the worst cases you may not get an error until much later on.
case class Person (
firstName: String,
lastName: String,
age: Int
)
you could create an instance like this: val emily1 = Person("Emily", "Maness", 25)
and then create a new instance by updating several parameters at once, like this: // emily is married, and a year older
val emily2 = emily1.copy(lastName = "Wells", age = 26)Kotlin has a very similar feature in its data classes, FWIW.
I really wish Java would add both default arguments for functions, and also support for passing function arguments by name.
One of these days...
Writing simple methods with few arguments helps.
Ok, it cheats a little bit because named params are actually a syntax sugar for Maps. But other languages do the same (e.g. Python) and is very useful too. And if you can type check the map (as in Typescript) the functionality is equivalent to named params.
[1] http://docs.groovy-lang.org/latest/html/documentation/#_name...
Ruby before 2.7 has a syntax sugar where keyword parameters are the same as a trailing hash. But that's changing as well.
Kotlin provides named arguments and a copy method on data classes/records that uses them. You do indeed have binary compatibility issues with them, but it's not a fundamental problem. It's just that it sits at the intersection of:
- Problems that only affect library writers, rarely a high priority for language designers (Java being a exception)
- Obscure JVM knowledge and new techniques
- Poor Android JVM holding back the ecosystem by discouraging any bytecode techniques that post-date Java circa 2010.
The kotlinx.serialisation design is pretty nice. It's basically what you describe. The compiler generates generic code that can call into a variety of "codecs" but they don't have to actually be serialisation specific. So there are codecs for JSON, protobufs, CBOR etc but you can also do arbitrary object graph transformations with the framework. A deep clone being a simple hello-world type example. I've never used Shapeless but it sounds pretty similar in some ways.
So what does the value that gets passed into your codec look like? What makes Shapeless tick is that it can represent records generically at the type level; records will have a type like
type Book = 'author ->> String :: 'title ->> String :: HNil
that you can actually break down and do meaningful operations on (e.g. you could run it through a function that counts the lengths of strings and get something of type 'author ->> Int :: 'title ->> Int :: HNil, in a fully type-safe way). I'd be impressed if someone managed to encode those kinds of types into Kotlin - e.g. I don't think Kotlin has singleton types, so being able to compare field names at the type level is probably impossible?The assumption of inputs based on the order of the class items frightens me!
val emily1 = Person(firstName="Emily", lastName="Maness", age=25)
Scala's approach to constructors is a good example of convention over configuration; in Java 99.9% of classes have a constructor like: this.firstName = firstName;
this.lastName = lastName;
this.age = age;
which is just ceremonial boilerplate that obscures the actual logic of what the class is doing (an IDE can generate the constructor for you, but the person maintaining your code still has to take the time to comprehend it). You can do the more complicated things in cases where you need to, but it's important to keep the simple case simple.It's a compromise.
val emily2 = emily1.newBuilder()
.lastName("Wells")
.age(26)
.build()
I think the inner Builder pattern is common enough in immutable java object implementations that they might want to include that fully in the Record class generation at some point. Most immutable object libraries that I've seen include a lot more functionality than Records, like builders. val emily2 = { ...emily1, age: 30 };
This has many advantages, one being that it provably and declaratively creates a copy, which is not the case with the builder. In fact the obsession with methods (and hence the implied state) is what makes Java a terrible misfit for functional paradigms such as immutability. // Java
val emily2 = emily1.toBuilder().age(30).build()
As far as adding the spread operator to Java, I think you'd have to require a Spreadable interface to use it or limit usage of the spread operators to records. It's not clear to me that the expressive power is worth it over a builder.Otherwise, at some point you need to add a field to Person, and then the compiler can’t tell you about all the places where your code fails to initialize the field.
Worse, some people pass builders around.
At that point, the problem of checking that you’ve passed a legal set of arguments when constructing objects is as hard as the halting problem.
You can write tests to make sure your program is typesafe, but that takes time away from writing more useful tests.
Optional arguments with reasonable defaults don't present a problem for refactoring. It does make it hard to spot the cases where the reasonable defaults don't work. One trick is to temporarily add it to the constructor, review all the errors, and then remove the constructor arg.
Alternatively, they could automatically generate a builder class for records.
As always in java, I guess that somebody will duct tape an annotation based solution on top of the language to solve this.
F#: let updated = { old with prop = newValue }
Elixir: update = %{ old | prop: newValue }
Even JavaScript have similar stuff: const update = { ...old, prop: newValue }
just a fn in the end
it's purposefully dead simple. this is why many of us switched to alternative JVM langs ... Java can't keep up with the innovation other Langs have without breaking its philosophy of slowmoving/backwards compat
The Java 6 era of no changes whatsoever was slow as molasses. The joke back then was that even C++ got lambdas before Java.
It’s moving along a lot faster now thankfully but if it was going this fast 20 years ago we may not have needed Kotlin or Groovy
Switch statements are standard, and Java has had them for a long time. Switch expressions are different in that the switch "statement" now returns a value that can be assigned to a variable.
I posted a bit about it here http://www.benf.org/other/cfr/java14instanceof_pattern.html
it was first noted https://twitter.com/tagir_valeev/status/1210431331332689920 here
but the main thing is that if the 'taken' conditional is guaranteed to exit, then scope hiding happens, if not, not.
But in java
if (true) { throw new Exception(); }
is not guaranteed to exit.
So: https://github.com/leibnitz27/cfr_tests/blob/master/src_14/o...
In case you don't want to run, as of java (build 14-ea+34-1452) this prints:
Fred
WIBBLE
public class InstanceOfPatternTest10 {
static String s = "WIBBLE";
public static void test(Object obj) {
if (!(obj instanceof String s)) {
throw new IllegalStateException();
}
System.out.println(s);
}
public static void test2(Object obj) {
if (!(obj instanceof String s)) {
if(true) {
throw new IllegalStateException();
}
}
System.out.println(s);
}
public static void main(String ... args) {
test("Fred");
test2("Fred");
}
}https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.htm...
"14.21. Unreachable Statements It is a compile-time error if a statement cannot be executed because it is unreachable.
This section is devoted to a precise explanation of the word "reachable." The idea is that there must be some possible execution path from the beginning of the constructor, method, instance initializer, or static initializer that contains the statement to the statement itself. The analysis takes into account the structure of statements. Except for the special treatment of while, do, and for statements whose condition expression has the constant value true, the values of expressions are not taken into account in the flow analysis."
Is there some bug or mail list thread with reaction from Java developers?
(again, reachability analysis of unrelated code changes semantics.)
The problem is that this IS defined behaviour - the scope of the instanceof-assigned variable is dependent on whether or not the taken if-statement is provably exiting.
This is intended to allow
{
if (!(obj instanceof String s)) return;
// s exists now.
}
But it's not been thought through. void test1(boolean b) {
String s;
if (b) {
return;
} else {
s = "test";
}
System.out.println(s);
}
This code compiles. void test2(boolean b) {
String s;
if (b) {
if ("a".equals("a")) {
return;
}
} else {
s = "test";
}
System.out.println(s);
}
This code does not compile. But it does not mean that println will try to resolve s to something else. I think that they should have gone to a similar route, where declared variable will be available for entire lexical block where `if` was used, but initialized only inside matched branch. Usage in other code would error with "variable might not have initialized" consistently how it works now.Of course that would require to shadow previous declaration for consecutive `if`-s. But it would be much more obvious and understandable. Actually the whole construction would be just a syntax sugar almost expressible with current Java constructions:
/*
if (o instanceof String s) {
System.out.println(s.length());
}
//System.out.println(s.length()); // variable might not have initialized
if (o instanceof Number s) {
System.out.println(s.intValue());
}
*/
String s;
if (o instanceof String) {
s = (String) o;
System.out.println(s.length());
}
//System.out.println(s.length()); // variable might not have initialized
Number s$; // no variable shadowing in Java now, but it could work
if (o instanceof Number) {
s$ = (Number) o;
System.out.println(s$.intValue());
}
//System.out.println(s$.intValue()); // variable might not have initialized
and would be directly expressible if Java would allow variable name shadowing which is a good thing as proven by Go and Rust (although that would be incompatible change for old code, but allowing variable shadowing for patterns would not be incompatible change, because old code does not have pattern variables).Of course I did not think about this problem for too long and probably missed something important, so that's just my 2 cents. I guess, developers took that path for a reason.
Basically they want to following code to work:
String s;
void test(Object o) {
if (o instanceof String s) {
System.out.println(s); // local variable o
} else {
System.out.println(s); // this.s
}
}
and I'd argue that this code should not compile! It's bad code. If developer wants to use `this.s` he should explicitly write that.https://mail.openjdk.java.net/pipermail/amber-spec-experts/2...
Would I ever choose it if I were in charge of a project? Probably not. But it is nice that the language is incorporating these proven features that make a huge difference. It'll make working in Java when I'm not in charge much nicer.
Does anyone know how it will play with type parameters in the interface? I know in Scala, you can simulate GADTs with that.
sealed interface Expr<A> {}
record IntExpr(Integer i) implements Expr<Integer> { }
record StringExpr(String i) implements Expr<String> { }
Will that be legal? And will switch be able to do its magic and carry that type variable through?I like the look of it but I know Java pretty well so it's been hard to not just use that for my personal stuff :p
The difference is that smartphones phones last at most 5 years, but a program that a business depends on lasts forever.
Some people just don't want to invest ANY money to improve their code. They just want to release new features. They would use Java 1 on Windows NT 4 if they could.
However, I found it's weird that hosted languages like Kotlin, Clojure or Scala are more approachable for me as they are working fine with JDK 8 like 'libraries'. They're much easier to upgrade than upgrade JDK itself.
Why is the cast even necessary? Isn't the cast only part of the type checker and thus unnecessary with a smarter type checker? For example TypeScript can do it, if you have code following an if condition that checks the type of the variable, you can use that said variable as if it were of that type without any further assertions necessary, eg.
let x: unknown;
if (typeof x === "string") {
console.log(x.charCodeAt(0));
}PS: just came to my mind: that is a breaking change (in C#) due to the new operator exanple. Will never come (for C#). Maybe Java does not have this issue but most likely they have a similar problem
This is an awesome feature for typescript. Love their work there.
The backwards compatibility part:
class Foo {
void foo(Object o) { }
}
class Bar extends Foo {
void foo(Integer i) { }
}
...
Foo x = ...;
Integer i = 42;
if (x instanceof Bar) {
x.foo(i);
}
Currently this calls Foo.foo, if there was a smart cast it would call Bar.foo, possibly breaking existing code.EDIT: Added method parameter.
But don't trust me, try it!
| Welcome to JShell -- Version 11.0.5
| For an introduction type: /help intro
jshell> class Foo { void foo() { System.out.println("Super"); } }
| created class Foo
jshell> class Bar extends Foo { void foo() { System.out.println("Sub"); } }
| created class Bar
jshell> Foo f = new Bar();
f ==> Bar@58651fd0
jshell> Bar b = new Bar();
b ==> Bar@5419f379
jshell> f.foo();
Sub
jshell> b.foo();
Sub
jshell> ((Foo)b).foo();
Sub jshell> class Foo { void foo(Object o) { System.out.println("Super"); } }
| created class Foo
jshell> class Bar extends Foo { void foo(Integer i) { System.out.println("Sub"); } }
| created class Bar
jshell> Foo f = new Bar();
f ==> Bar@7f9a81e8
jshell> Integer i = 1;
i ==> 1
jshell> f.foo(i)
Super
jshell> ((Bar)f).foo(i);
Subhttps://docs.oracle.com/javase/specs/jls/se7/html/jls-15.htm...
It might even be an intersection between an object and an interface type, and for historical reasons those have different bytecodes for invoking methods!
Flow typing is really cool, but it can make things way more complex.
For me most interesting upcoming changes are
JEP: 352: Non_Volatile Mapped Byte Buffers, JEP 345: NUMA-Aware Memory Allocation for G1 and JEP 370: Foreign-Memory Access API (Incubator). Especially FMA api [1] examples seems most promising but shipped with panama and I am not sure about the maturity yet: https://github.com/zakgof/java-native-benchmark
I wonder how GraalVM stands in this picture that beside of being polyglot, it has AOT and auto vectorization futures and I dont know if those are already/will be shipped also with openJDK
[0] https://jaxenter.com/java-14-update-news-163585.html [1] https://openjdk.java.net/jeps/370
Please nobody copy paste that class.
LocalDate is appropriate for things like user interfaces, where you're modelling an intuitive/vague concept of date-ness only meaningful in some wider human context, or where the actual time at which that day starts just doesn't matter or is unknown. For instance it may be a good type to use for annotated historical events, where the day the event happened is the most accurate you can get.
For a bank transaction where they're international by nature and usually need to be ordered temporally against each other, it's an inappropriate type. The time zone in which the date should be interpreted is important. But really for transactions you'd be better off using Instant, at least internally. Time matters too.
So it didn't quite arrive yet.
The limit as JavaVersion —> Inf = Kotlin?
I won’t complain.
List<String> names = new ArrayList<>();
names.stream().filter(String name -> "Bob Vance".equals(name)).findFirst().get();
By adding the String type declaration, a whole host of bugs in really complicated Lambdas can be eliminated and found easier when the original types and lists are being shuffled aroundEverything looks promising for me.
I say this as a C# developer that would desperately want them supported in C# and was super pissed off when they were discarded from C# 8.0 (I actually need them right now, they would save me a whole day of typing today)
I completely agree as I, myself, see very few instances where I could use records in my code. But properties to remove getter/setter boilerplate? That would remove thousands of LoC. But it's not hot.
Fashion-driven development, that is.
Do you have a source for this claim? It sounds a bit extreme, to say the least, and I had the impression OpenJDK was licensed using fairly standard terms.
OpenJDK license is another matter.
It is up to the courts and copyright holder to decided what to do with their IP.
These are fundamentally different things. One enlarges the set of actions a recipient is free to do relative to what vanilla GPL allows. This is permitted (and in the case of the classpath exception, endorsed) by FSF. The other attempts to shrink the size of that set by denying the user things that the GPL would otherwise allow. The FSF simply does not permit the GPL to be used in that combination (and there would be extreme contrast in your last sentence and the failure to recognize the FSF's say in this).
And secondly, you've yet to substantiate your claim that Java was ever distributed with such GPL-modifying restrictions.
https://www.youtube.com/watch?v=ZYw3X4RZv6Y&feature=youtu.be...
What's more, I've seen this interview multiple times. Listening to Gosling stutter and be coy is not illuminating in the least. He has no idea how to answer the question he was asked, much less what's being discussed here now.
Can you substantiate your claim or not?
Naturally it is hard for anyone to link to anything Sun, given what happened with their assets and Internet presence.
Is a substantiate argument? Maybe not, it doesn't change the fact that Google screwed Sun, didn't bothered to rescued it went it went down, and now we have Java and Android Java.
I guess FSF is happy with the outcome then, since it is allowed to tank companies.
Sure. But what they don't have is domain over the GPL.
I won't respond to the rest of your comment, which has nothing to do with the claim you made to kick off this branch of discussion and is just another attempt to change the subject (with what is an opinion, not a "fact").
This will be my last comment here.