My recollection of Java was it was a simplified C++. The big simplification was getting automatic garbage collection by splitting into Value and Reference types. Then it does other things like cutting out call-by-reference and operator overloading. I didn't love it, but it seemed fine.
When I looked at Java again after years I was surprised at how weird it got, or about things it always had that I didn't notice.
I'll start with minor things that don't seem very Java-like to me:
a1==a2
is pointer compare and a1.equals(a2)
is memberwise compare. Nicer languages might give you a defualt equals
which compares each member. Java doesn't. Here's the problem: Java's default equals is a pointer compare. This means if you write if(a1.equals(a2))
, wanting to know whether a1 and a2 have identical contents, but forgot write equals
for them, Java purposely gives you the wrong answer. It would be much better to give you an "equals is not defined on this class" error.final
means constant. We were calling it const
before Java, and they changed it, which would be fine. Except Java final
has 2 other meanings, in classes. It's as if Java purposely picked a confusing name on purpose.A.length
(no parens). I suppose it's a nice shortcut, but it really sticks out.c1.useThisCat()
and also Cats.useaCat(c1)
. It turns out Java invented that junk.String
. bool is written the long way as boolean
. It's awkward. And it's not as if someone is going to go "bool? Oh, you mean boolean!" Boolean isn't an everyday word.for(Cat c : CatList)
. The point of a
foreach is to be a shortcut. Make it short by letting us omit
the type.I knew this but forgot how silly it was. Java's main entry point is main(string[] Args)
. Fine so far. But it must be inside of a dummy class, named the same as the file it's in:
// file: jtest.java import java.io.*; // typical leading imports public class jtest { // <- "jtest" is the required name public static void main(String[] ags) throws IOException { System.out println("main entry)"); } }
I forgot what a rigid structure Java imposes on files. Each public class must be in a file by itself, named the same as the class. If it's in a namespace (which Java calls packages) it must be in a directory with that name. Here's the filsystem required to have a Dog and Cat class in an Animals namespace:
// a directory with 2 files: Animals // everything in the Animal namespace must be here: Dogs.java // Dog class, plus non-public dog-using classes Cats.java // Cat class
Then here's the cat file. despite being in the required Animal folder, it needs to declare itself as being in Animals in the first line. But at least it can have any number of private helper classes:
There's more. If you don't start with package
pckgName (which effectively is your namespace) Our Dog class would need
package Animals;
. To sum up, a sample cats.java
file:
// Cats.java: package Animals; // must be Animals, since we're in that directory public class Cats { ... } // class must be named Cats, to match filename // non-public, so naming rules don't apply: class CatHelper1 { ... } class CatHelper2 { ... }
Things don't need to be in a package. Without package xyz;
on top they goes into the global namespace. Those files can be ... anywhere. Arrgg!!
Java wants everything to be a class. I thought C# invented that nonsense, but it was Java. That means Java can't have true global functions or variables. But they do anyway, using an ugly hack through a fake static class:
// this class is really a namespace: final class catUtils { public static int oldestCat=24; // global variable public static bool checkCats(Cat c1, Cat c2) { ... } // global function // a hack to be sure this isn't a real class: private catUtils() {} // private constructor prevents "new" }
To make it more official, a final
in front prevents inheritance and a private
constructor prevents creating an instance. But those make it look more like the "pretend this class is a namespace" hack that it is.
And it gets worse. We can't import this class the usual way. We need an extra static
:
import Animals.Cats; // cat class import Machines.*; // everything in machines package import static catUtils.checkCats; // that function from catUtils
Java private and public work the usual way. But this is weird -- the default scope is package level. Weirder, to me, you cna't even make that explicit. For example:
class Cat { private String name; // this is private to the class public double weight; // public to anyone int socSec; // not private or public -- package scope // is there a formal way, like "pkg int socSec;"? No } c1.socSec=1; // legal -- package scope & same file
Every language has slightly different syntaxes for funny class stuff. I forgot how old and weird Java's is. To subclass
use extends
. To have one constructor call another,
use this()
. To call an overwritten function from
your parent use super
:
class Cat extends Animal { // subclass using extends // calling Animal constructor with super: public Cat(int age, String name) { super(age,name); .. } public Cat() { // calling another constructor using this() this(0,""); } // also call an overriden function with super: public double cost() { double baseCost=super.cost(); } }
One more fun way to use super
is with inherited
variables. If Cat inherits name
from Animal, Cat's
are allowed to call it super.name
(or name
or this.name
). That seems ... pointless.
It's great how every function in Java is automatically virtual. If you want to override in a subclass, just do it -- no keywords required. It's a great system. Python uses it (sort of). But then Java messed it up.
The problem they wanted to solve was mispelling an attempted override. If you attempt to override eat()
but misspell it eet()
there's no error, no warning -- but nothing will happen. A solution, used by languages older than Java, is the override
keyword. Now override eet()
gives a "nothing to override" error. But Java didn't want to add a keyword. Instead the "suggest" you use a decorator:
@override public void goLeeft() { ... } // helpful compile error "no such function to override" // but @override is optional
Java introduced the idea of preventing inheritance from a class, by adding final
in front. Or use
final
to prevent a function from being overriden:
// can't sublass a Dog: final class Dog extends Animal { // Dog.eat can't be overridden: // (which is would only matter if Dog could have subclasses) public final void eat() { ... }
That seems so quaint now. Java was at the peak of the OOP movement when coders wanted to subclass anything. Today all of those final
's seem silly -- who are all of these people trying to subclass from you? It's extra funny how C# stole the idea but changed the name to an equally
unhelpful sealed
.
Java started the idea of eliminating multiple inheritance, replacing it with requirements-only interface classes. The rules for writing an interface are fine:
// pure abstract classes anyone can inherit from: public interface buyable { double cost(); } public interface talks { string mySound(double anger); }
Then it gets a little odd. Inheriting from a
real class or from an interface is the same concept. You don't need to know which is which, but the keyword is different (implements
):
class Cat extends Animal implements buyable, talks { ... // while we're here, implement the required functions: public void petFor(double timeSecs) { ... } public string talk(double anger) { ... } }
Not a huge deal, but these are the same people who re-used final
for const, to reduce the number of keywords.
This struck me as odd: interfaces can have static variables. Declare them, in the interface, like normal variables:
public interface buyable { double cost(); int maxCost; // a static } // using it. It's really a static: buyable.maxCost=99;
Instances of a nested class need to be created "from" some instance of the enclosing class. This is so they can get a free hidden backpointer to the "parent" instance. Pretty cool and useful. An example where a Forest has Trees which automatically know which forest they are in:
class Forest { public int temperature; void makeATree() { Tree t1 = new Tree(); // the new tree automatically backlinks to this Forest ... } // nested Tree class: class Tree { public grow() { // using the temperature of this Tree's forest: double n = temperature/10; // <-- Wow! }
Whoa! Inside Tree's grow
function we use Forest's temperature
like it was a global. That's a classic rookie mistake, except it's legal in Java. As a bonus, you're allowed to explicitly follow your Forest link with an odd syntax:Forest.this.temperature
. In languages without a free backlink we're constantly adding Forest myForest;
then using myForest.temperature
.
But this is weird -- what if a non-Forest member function creates a Tree. That Tree requires a secret backlink to some Forest. Java forces us to supply one:
Forest f1 = new Forest(); Tree t1 = f1.new Tree(); // <- special syntax to specify the owning forest
It gets weirder. Unlike Scala (a version of Java), there's no enforcement that a forest only use its trees. Here forest#2 plays with a tree belonging to forest#1:
// tree1 links back to Forest f1: Forest.Tree t1 = f1.new Tree(); // but Forest f2 can use it: f2.useTree(t1);
Java gives us one more funny rule for this. Suppose you want to nest a regular class -- one that has no reason for a backlink. You can by writing static
in front, as in
static public class Tree
. That's weird for such a minimalist language. If you don't want a free back-pointer, don't nest the class.
When Java was made it seemed obvious that Value and Reference types was the best compromise. Value for small things is fast, while Reference for larger classes allows useful pointers. Then other languages came along and showed how "everything is a reference" can make things easier. An obvious trick turning Value types into References is creating creating a 1-item class. This is known as "boxing" (we put the Value type into a Reference-type box):
class IntHolder { public int n; } IntHolder num1 = new IntHolder(); num1.n=7;
That works, but it's a pain, so Java invented auto-boxing. It works pretty well. They use Integer
, Float
, Double
, Character
, and Boolean
and classes invisibly holding 1 item of that basic value type. Here's Integer
in use:
// Integer is a class: Integer n1 = new Integer(5); Integer n2 = 9; // shortcut int n3 = n1; // also a shortcut // note how the types are different: int = boxed int n1=null; // fine, since a reference n3=null; // nope. Error
It's pretty seemless. But at the same time, it's awfully complicated to remember we now have 2 types in integers and so on.
Java requires that a function list every possible error
it might cause. Wow. It's not that bad since you're allowed
to use try-catch
inside. Ideally you catch
every possible error and don't need to junk up the function header -- no errors can be thrown out of it. But otherwise you'll need throws
and a list of error codes. Yikes:
// this attempts to open a file, which may fail: public void doStuff() throws IOException { // we catch bad indexes, otherwise they'd be listed above: try { int n=A[x]; // errors here will be caught by us ... } catch(IndexOutOfBounds err) { ... } // handle it finally { ... } // finally is optional... // ...it always runs, even for uncaught exceptions
Mutltiple exceptions in a catch
strangely combine with | (a vertical bar). I realize there's no existing "combine types" operator, but this feels janky:
try { ... } catch(IOException | NullPointerException e1) { ... } catch(OutOfMemoryError e2) { ...} // mutiple catches are allowed
But all-in-all it's a nice idea, but maybe not explained well. In Java you can handle all of your own errors, like in a normal program; or you can choose not to as long as you tell callers which errors they have to worry about.
Ah, yes! One of the main reasons to use Java is that it can automatically save and restore variables to JSON or whatever. Then it gets weird. Java's built-in serialization is considered not-so-good; everyone uses some 3rd party library. And not the same one.
Adding implements Serializable
lets most Java
classes serialize themselves. It will even follow references and
serialize those. That fails horribly when you refer to items "owned" by someone else. But you can add transient
and
re-construct it yourself later. Ex:
class savableCat implements Serializable { // this will be serialized: CatCollar myCollar; // points to an item in a master list. so not serialized: transient CatCollar myDreamCollar; // Arg. We need a way to restore myDreamCollar: int myDreamCollarID; // this will be serialized }
So it's great for simple stuff, but for anything complex it's no magic bullet. Actually even normal serialization is a pain:
To serialize you need to create an ObjectOutputStream, backed by a file or a byte output stream, then write your object to it:
Dog d = new Dog(); // set dog values ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); ObjectOutputStream byteWriter = new ObjectOutputStream(bytesOut); // byteWriter can write any class, writing one dog now: byteWriter.writeObject(d); // writes out Dog d to the backing bytesOut byte[] dogS = bytesOut.toByteArray(); // convert to normal byte array f.write(dogS); // finally to a file
Reading back is the reverse process, from a file or byte array:
ByteArrayInputStream bytesIn = new ByteArrayInputStream(dogS); ObjectInputStream dogReader = new ObjectInputStream(bytesIn); // dogReader is ready to read whatever is in dogS // pull out whatever's there. It will be an "Object", but we know its a Dog: Dog d2=(Dog)dogReader.readObject();
Adding synchronized
in front of any function limits access to one thread at a time:
public int synchronized getSomething(...) { ...}
Container classes have synchronized versions (all of their functions automatically start with synchronized
, I think.
You can't declare an object to be synchronized. Instead
each has a built-in lock. synchronized(a1) {}
either
gets a1's lock, or waits until it's free. The lock is auto-released st the end:
synchronized(c1) { // wait for and acquire c1's lock // no one else can use c1 until we finish }
That's not too bad. It's just a built-in mutex variable.
Of course, synchronized
is for threads. Making Java threads is a pain.
First create a class inheriting from java.lang.Thread
. Overload the run()
function. Run it with new Thread(new myNewThread()).start();
(why start
? Because start
runs run()
, obviously). Here's an example
where 3 identical threads slowly count to 10.
With a synch, they run in sequence. Without, the numbers from each thread will interleave:
class threadRunner { // main entry: public void runThreeThreads() { threadTest t1 = new threadTest(); // t1 is ready to be spawned threadTest t2 = new threadTest(); threadTest t3 = new threadTest(); new java.lang.Thread(t1).start(); // starting the threads new java.lang.Thread(t2).start(); new java.lang.Thread(t3).start(); // they will now all count 1 to 10 } // nested class for the Tread (so we can synch on the main instance) class threadTest extends java.lang.Thread { // <- actual Thread public void run() { // <- the required "run" function // manually synch on our common backpointer: synchronized(threadRunner.this) { // print 1 to 10 with a delay between #'s" for(int i=0;i<10;i++) { for(int j=0;j<1000000;j++) {} // delay System.out.println(i); } } } } }
I vaguely recall some crazy Java containers in the past,
but looking at them now, they're fine. One weird part are how everything must be an Object -- int's are autoboxed into Integer and so on. Another is how, since Java has no operator overloading, you can't use A[0] for any of the lookups. The boringly-named
ArrayList
is a standard list implemented as an array.
It lives in the java.util
package. It has
the usual operations:
import java.util.ArrayList; ArrayList<String> A1 = new ArrayList<String>(); A1.add("cow"); // to end A1.add(0,"moo"); // inserting before index 0 A1.size(); // 2 (why is it called size? Oh, well) A1.remove(1); // by index A1.remove("cow"); // search for item
remove
seems confusing since it takes an int index or an object to search for. The secrete is you can't have an
ArrayList
of regular int's, only the class Integer
. That seems error prone to me, but oh well.
Instead of A[0]. We have a
set
and a get
. Yeesh:
A1.set(0,"bear"); // A1[0]="bear"; String w = A1.get(0); // w=A1[0];
A little stranger: the thread-safe ArrayList. is named Vector
.
LinkedList is the linked list type. It has
multiple identical functions: addLast
, push
, and offer
add an item to the end. peek
and poll
get the value of the first element.
This allows you to pretend a LinkedList is a stack
or a queue (or whatever uses poll).
LinkedList isn't synchronized. There's no synchronized version, but you can use SynchronizedCollection(myLL) to make one (I think. It's not pretty).
Java's old-fashioned fixed-length array is pretty normal. Still always an object, gets to use [], and has the only Java "property" with dot-length:
Cat[] C = new Cat[8] C.length; // Sigh...no parens, the only place C[2]=new Cat(); // [] only for on "real" arrays
The basic hash-map is HashMap. It also can't use H["cat"]=5;
. Instead we have put
and get
:
// note how we need to use Integer, which is a reference type: HashMap<String, Integer> MaxAge = new HashMap<String, Integer>(); MaxAge.put("cow",17); // instead of MaxAge["cow"]=17 int xx = MaxAge.get("cow"); // 17 // note: Integer to int is auto-unboxing
Java can now fake standard function passing. This sorts cats by age using what looks exactly like an anonymous function:
// use built-in sort to arrange cats by age: C.sort( (c1,c2)-> c2.age-c1.age ); // NOTE: age1-age2 is a hacky <0 / 0 / >0 compare function
Below, Predicate<Cat>
acts
just like a function pointer, taking a Cat-to-boolean, set to another anonymous function:
import java.util.function.Predicate; Predicate<Cat> isKitten = c -> c.age<3;
We also can pass isKitten
into a function in a normal-looking way. This uses it to get part of a list:
YoungCats = sublist( C, isKitten ); public ArrayList<Cat> sublist(ArrayList<Cat> C, Predicate<Cat> useIt) { ArrayList<Cat> C2 = new ArrayList<Cat>(); foreach(Cat c : C) if( useIt.test( C ) ) C2.add(c); return C2; }
That looks normal except for if(useIt.test( C ))
. What is the member function call test
? How does useIt
even have member functions? It turns out we've been
creating and passing classes, and not functions, all along. Yikes!
Predicate
is a class and we've been secretely making a subclass. Double Yikes!
It turns out Java was made to be fully OOP. It doesn't really have first-class functions. For real it only uses the OOP Command design pattern. It's very complicated. First we need an abstract single-function class. It will eventually hold a real function. We'll assume we want one converting a String to an int:
// abstract base class: interface stringConverter { int value(String w); }
Anyone can take that base class as input and call its function:
void processString(String w, stringConverter cnvt) { ... // cnvt is a class, calling its value member function: int x = cnvt.value(w); ... }
To supply any function we choose, we create a subclass:
// subclass: class zPos implements stringConverter { // implementing our function as the single "value" function: public int value(String w) { return w.indexOf('z'); } } // we need to create an instance to pass it somewhere: processList(L, new zPos());
This is a huge pain. To make it simpler, Java created rules for anonymous subclasses. Here's the formal way to sort cats by age:
C.sort( new java.util.Comparator<Cat>() { public int compare(Cat c1, Cat c2) { return c1.age-c1.age; } } );
The () { ... }
syntax creates an anonymous subclass
of Comparator<Cat>. Comparator is the base class sort
expects. It has a single function named compare
.
When you supply only a "lambda function", as in sort((c1,c2) ->c1.age-c2.age)
, Java converts it to
anonymous subclass syntax. It's
really quite clever.
Or consider Function<String,Int> f1 = (w)->w.length();
. Function
is actually an abstract class (with a single member -- apply
). Java uses (w)->w.length()
to create an anonymous Function
subclass. Anyone using f1
would
use f1.apply(w)
.
Here's one more of those, using a base class we create:
interface DtoD { double convert(double d); } // creates an anonymous subclass of DtoD: DtoD tripler = (n) -> n*3; ff(4.5, tripler); // computes 13.5 void ff(double d, DtoD mutator) { // calling the convert member function: double d2 = mutator.convert(d); ... }
Abstract classes such as DtoD
are required,
but it's a pain making them. So Java provides several. Predicate
takes anything to bool. Function converts 1 input into any a bool output (as an exercise: Predicate<Double> is the same as Function<Double, boolean>). UnaryOperator
converts an input to an output of the same type. Consumer
has 1 input and no output. IntConsumer
is a shortcut for Consumer<Integer>
. Supplier
is the reverse -- no input, 1 output. BinaryOperator
has 2 inputs and 1 output, all of the same type,
while BiFunction
can have 3 different types.
What a mess. C# managed to do it with just Func
(anything returning a value) and Action
(anything with no return value.
But there's more. Your abstract base class is allowed to have extra functions as long as you clearly mark which one the shortcut
will fill in. Mark the extras with default
:
public interface buyable{ double cost(Cat c); // <-this one is auto-overloaded default boolean tooExpensive(Cat c) { return cost(c)>10; } default String comment(Cat c) { return "wow!"; } } buyable allMustGo( (c)-> 0.1 ); // this will be buyable.cost
To sum up, passing functions in Java is now almost completely normal. But only through the slickest of hacks on Java's real system, which is a mess.
Java's system lives on in C#. sort
can take a function as input, but it can also take a subclass of the Comparator
interface.
Basic Java enums work as usual:
// basic enum: enum color { RED,GREEN,BLUE; } color c1 = color.RED; // note how the type goes in front
Then Java goes further and allows you to add extra data to enums, which is normally a scripting language trick. The extra data goes in parens after each enum value:
enum color { // extra data goes in parens after each: RED(3.2), GREEN(4.7), BLUE(6.8); // the name for each color's value: public double prettyness; // auto-run constructor sets prettyness for each color: color(double val) { prettyness=val; } } color c1=color.RED; c1.prettyness; // 3.2 color.BLUE.prettyness; // 6.8
Java actually creates each color as an instance (just as simply an int). The funny stuff with prettyness and the constructor is about naming and installing the values (3.2, 4.7 and such) into each enum item. Use it like color.RED.prettyness
.
You can even give them member functions!
We could add bool isPretty() { return prettyness>5; }
then use it with if(c1.isPretty())
.
Bizarrely, these extra values don't need to be const/final. Each color can change it's prettyness:
color.BLUE.prettyness=14;
is fine. It affects every reference to color.BLUE
(obviously, since they all point to the same object).
Java uses the restrictive form of template classes: the template type must be a class which you can't assume anything about. For example, oldEnough
is illegal since it tries to use age
(even if we only ever call it with age-having things):
static <T> boolean oldEnough(T t) { return t.age>=3; } // error -- what is age?
Notice how the template type goes in
Templates let us use class features only if we specify a required parent class. This runs on anything inheriting from ageHaver
:
class ageHaver { public int age; } // legal age-having generic, taking any type inheriting from ageHaver: static <T extends ageHaver> boolean oldEnough(T t) { return t.age>=3; }
That's not so useful since we could already do that using normal inheritance.
One last cool thing about templates is they don't really do anything. Everything in Java
really takes just Objects anyway. For example, ArrayList<String>
is a list of Objects. Java's type system only checks they have the required bits during compile time. The system is called Type Erasure.
Comments. or email adminATtaxesforcatses.com
name test