Mike Simmons

Master Rancher
+ Follow
since Mar 05, 2008
Merit badge: grant badges
For More
Cows and Likes
Cows
Total received
71
In last 30 days
4
Total given
1
Likes
Total received
1037
Received in last 30 days
28
Total given
193
Given in last 30 days
4
Forums and Threads
Scavenger Hunt
expand Rancher Scavenger Hunt
expand Ranch Hand Scavenger Hunt
expand Greenhorn Scavenger Hunt

Recent posts by Mike Simmons

To be extra clear: when we say that fields can't be overridden, or that fields aren't inherited, we mean that the rules for how methods are inherited and overridden do not apply here.  Fields follow different rules.  Specifically, if you use the same name for a field in a class and a field in a subclass, those are actually two different fields.

In your code, an instance of class A has 1 field named x.  An instance of B has two, one declared in A, and one declared in B.  An instance of C has three - one from A, one from B, and one from C.  In each place that the code refers to x, it refers to one of these three - depending which class the code is in.  There are, however, ways to refer to the other x values if you really want to:

 Note that getX() returns the same value no matter how you cast the C to A or B.  Because an instance method call like getX() always uses the actual type of the instance, not the declared type or the casted type.  But the references to field x do not work that way - they use the declared type, or the casted type, to determine which "x" you are referring to.

Of course, this is very confusing for anyone reading the code.  And so, it's generally considered a Bad Idea to do this in code at work.  Or anyplace other than an experiment for learning.  Don't use the same variable name in a superclass and a subclass.
14 hours ago

Fabien Dupuit wrote:From the context, it will find out that <T> parameter from the class is a String so 'a' is therefor a String.


That context is outside the class, in your example.  How far away should the compiler look for context like this?  Like, in the same file, in the same package, in the same project, on the same computer, in the whole organization you're sharing your computer with?  What if there's code somewhere that uses T as a String, and somewhere else there's code that uses T as an Integer?  Or a StringBuffer?  How can the compiler know, within the class A, what other code is going to use it for?

In general, we want to be able to compile a class based on declarations in that class, not by having to scan code outside the class.  Of course when we use another specific class within our class, that other class needs to be compiled before our class.  But with generics like T, there's no other class to go and compile first.  Instead, your class A needs to have all the info it needs to be compiled, within the definition of T.  Code lying around elsewhere is not relevant for compiling class A. Within the class A, the generic parameter T could be anything, since it's just declared there as <T>, with no other restrictions (no super or extends).

Furthermore, the compiler is never going to let you create an instance using "new" without specifying exactly what the class is.  That's how the rules work.  You can't just say "create some sort of T" and let the computer surprise you - T could mean many things.  And with generics, they are generally used in places you need to be flexible, where more than one type is possible.  So the rule is, you can't create a new something with a generic type.  That is, you can create something like new ArrayList<T> (which is fundamentally an ArrayList), but not a new T.
3 days ago

Swapnil Mishra wrote:Compiled, but did nothing.


Did you run it?  It does more that "nothing" when I run it.
4 days ago
No.

As you've written it, anytime a Sample17 instance is created, it will have an instance field "obj" initialized with a new Sample17.  Which would immediately have its own "obj" field and need to initialize it with a new Sample17 instance.  Which in turn... well, it's not pretty.

The thing is, nothing in your code ever creates the first Sample17 instance.  So this chain reaction never starts.

Consider this for your main method:

What do you think will happen?  And, what does happen when you try it?
5 days ago
Thanks!  I hadn't really considered it; in fact I just came up with most of it.  But perhaps it's worth doing something like that...
5 days ago

Anil Philip wrote:peek() is not supported on all data structures. For instance on maps, that are in this pipeline, peek will not work.


It's not just a binary yes/no based on the data structure - it usually has more to do with the combination of operations in the stream, possibly in combination with the data structure.  As the peek() API says:

In cases where the stream implementation is able to optimize away the production of some or all the elements (such as with short-circuiting operations like findFirst, or in the example described in count()), the action will not be invoked for those elements.


I sometimes use a stream.map(e -> { doSomething(e); return e; }) instead of a peek(e -> doSomething(e)).  It's a bit less likely to be optimized away.  Though in some cases, it still can be.  E.g. if the terminal operation is count(), you may not need to evaluate a map() just before that, because the mapping can't possibly affect the count.

Anil Philip wrote:It is actually dangerous in my humble opinion, to have complex pipelines because bugs will never get detected nor fixed - because the code is hard to understand.


Well, it's true that streams can be harder to debug.  And Collectors, and the groupingBy() overloads in particular, are among the most complicated things you can do with a stream.  I find them very useful - usually, more than half of the work for me is getting it to compile in the first place, meaning to get all the types to line up correctly.  Once I've done that, often the result is simply right, with no further work needed on my part.  Or maybe I need to study the result to see how it compares with what I wanted, and modify accordingly.  But, after some time getting used to streams, I now find it's usually quicker and easier for me to use them, compared to coding the old fashioned way. Not always, but more often than not.

It is true, however, that debugging can get more difficult.  Especially with Collectors, which as you say, do not really provide you with a place to put a logging statement or breakpoint.

Unless, of course, you make such places.

Here are some utility functions which I have found useful:


Note that inside the last private log() method you can use whatever output format and/or logging framework you want.  I use System.out here because everyone has it and it's easy to understand - but feel free to use a better logging framework.

Anyway, once you have that, you can use it to decorate the various collectors you have.  I recommend extracting them to separate variables on separate lines for readability (e.g. using IntelliJ's Refactor -> Introduce variable).  For the code

I extracted those nested collectors, thus:

and then applied those utility methods I just showed you, to obtain:

     
Note that the indentLevel stuff is intended to mirror the nested nature of the collectors - logs for the outside the collectors are at indent level 0, the outermost collector logs at indent level 1, the next inside that is at level 2, etc.  The declarations necessarily start with the innermost and work outward, from 3 to 2 to 1, which looks a little backwards, but it works.  If getting the indent levels worked out is too much trouble, you can remove that argument.  But personally, I find it useful.

Last, I also added to toString() to the JavaExam class:

And I get the following output:

That may seem like a lot of work... but, those utility methods can be reused in other projects, maybe tweaking them depending on your use case.
6 days ago
Thanks for the clarification, Jeanne!
Excellent call out - thank you, Paul A.

Fabio, if you're studying for Java 17, it's probably a good idea to be running JDK 17 when you're testing things.  Usually, it's OK to use a later version.  But in this case, they later allowed something that was initially not allowed.  If you try compiling with JDK 17, you should see the error you expected.

Thinking more though, I think that if the book says something like "the pattern variable must be a subtype of the variable on the left side of the expression" (still haven't seen an actual quote), then it's still wrong here.  Because the pattern variable (right hand side) may actually be totally unrelated to the type of the expression (left hand side).


The compiler will accept this, because it knows that it's theoretically possible for a Foo instance to also implement Serializable.  It would have to be a subclass, and that's not what we have here, obviously.  But if the compiler considers only the type of the expression f, which is Foo, and the type of the pattern s, which is Serializable... considering only those things, and the declarations of Foo and Serializable... it is possible that a Foo could also be Serializable.  So it's allowed.

If you make class Foo final, then it becomes impossible, and is not allowed.  Because Foo itself is not Serializable, and no other subclass of Foo can exist, so no Foo is Serializable, ever.

This is pretty similar to the rules for casting - if a cast is probably impossible based on the types involved, it will not be allowed.  If it's at least theoretically possible based on the type, it's allowed.  It may fail at runtime, but it's allowed.

Hoshyar Karimi wrote:Sorry but I don't know what does this mean at all and comparing which one to which one? when the success gets false and when does it get true?


For this it's helpful to consult the Javadoc for the method in question, AtomicInteger.compareAndSet():

public final boolean compareAndSet(int expectedValue, int newValue)

Atomically sets the value to newValue if the current value == expectedValue


Basically, it's only going to set newValue if "this" (the AtomicInteger instance) currently has a value equal to expectedValue.  The idea is, we know we just read that value, and we expect that it hasn't changed.  But in case any other threads are accessing that value at the same time.  So if the value in the AtomicInteger has changed due to another thread, we don't want to set it now, because that would mean we're overwriting whatever the other thread did, which would be rude, and a bug.  Normally this should be done inside a loop, so if you fail the first time, you keep trying until it works.  Eventually you should be able to read the value and change it without another thread intervening.

In this code, you are specifically taking the int value inide the AtomicInteger object called "balance", and comparing it to the int value "expectedValue".  If they are the same, the compareAndSet will succeed and return true.  If they are different, it will fail and return false.
1 week ago

Fabio Yasuo Yamashita wrote:Baiscally the book says the pattern variable must be a subtype of the variable on the left side of the expression. It cannot be same.


Does the book say that the code with Integer does not compile?  Or does it just say that the type of the instanceof needs to be a subtype of the type of the variable?  Does it say that the types can't be the same?  Or did you just infer that from "subtype"?  It would really help to know exactly what the book said.

In an expression like

the type Foo needs to be a subtype of the declared type of x.  But you need to know that "subtype" includes being the same type.  E.g. a Dog is an Animal and a Cat is an Animal, but an Animal is also an Animal.  That's the way "subtype" is defined.
Well in Anil's case, it seems he's really talking about not just Java 17, but also everything else since Java 5, is that right?  So yeah, there's a bit more to deal with.  Streams and lambdas are probably the biggest.

Anil Philip wrote:Can you walk into your office one day and decide you are going to get everyone to use this cool new thing in 17 called records


That depends on the culture of the company, I suppose.  I can't generally decide to force everyone to use something, no.  But I can generally just start using new language features (once we're officially running that version), and as long as I am careful to use them in places where they are notably shorter and/or clearer, or have other advantages, then no one objects.  Streams is a good example - you can start using this within methods, which doesn't force anyone to deal with them outside your method, but can often result in shorter, simpler methods for you.  Gradually, this catches on.  But in some companies there may be an edict against such things.  This would seem extremely short-sighted, to me, because many developers want to be able to keep current, learning and using new language features.  A company that actively prevents this may well be cutting themselves off from being able to attract or retain the developers it needs.  Maybe some companies still limit development this way, but I haven't worked in them.

To be fair, often the java version being used in a given company may be somewhat behind the latest available.  There can be business reasons why they can't or won't upgrade right away - but there are business reasons to upgrade eventually, as well.  That's probably you've been running with compilers and JVMs from Java 8 and 11.  Are you being stopped from using new features at all, though?
It sounds like the book is simply wrong on page 108.  Or maybe it was misunderstood?  What exactly does the book say there?
@Campbell, I'm not sure what your code has to do with my comment.  I was speculating about what Anil might have seen that made him think it was not allowed to have an abstract class with no abstract methods.  You appear to be verifying that this is not actually an error, which we all agree with, though Anil had been surprised.  

Campbell Ritchie wrote:You can now create subclasses without needing any new methods or fields.


Why "now"?  It's always been this way.  There's nothing new here.  Not on that point, anyway.

Note that in your code, there's no way for anyone to access those fields, so I'm not sure what their purpose would be.  Anyway, you could add methods to the base class, but still have a non-abstract subclass that has no new methods.  If there were some reason to do so.  Or even if there isn't.
Yes... and some of the complexity is because things changed over time, usually for the better, but you end up having old ways of doing things, and new ways, and there will be code that uses both.  Long-time developers got to absorb the changes gradually.  New developers (or, new to Java) may see too much stuff at once.  I can see where it can be frustrating.  But, it gets better over time.