Because the moment you do something like
public float returnSomething(String theThing) {
switch (theThing) {
case "height":
return height;
case "rotation" :
return rotation;
case "width":
return width;
default:
return 0;
}
}
I can feel the next question for Stack Overflow, "Why is my height always 0?"
And then post code like
public class GameThingy {
//...
private void doStuff(GameObject gameObject) {
float gravity = 5*gameObject.returnSomething("hieght");
gameObject.setSomething("velocyty", gravity+50);
}
}
Technically the moment you make a typo anywhere, you'll have issues finding the source of the problem. That, and you're lucky that the fields are all float
, they don't have to be.
Edit: By the way, this is actually a typical problem in defining the field of interest in some database queries. As in, having to specify the field you're looking for by a String
.
A real-life example is RealmQuery<T>
.
A RealmQuery<T>
looks like this:
RealmQuery<User> query = realm.where(User.class);
// Add query conditions:
query.equalTo("name", "John");
// Execute the query:
RealmResults<User> result1 = query.findAll();
Where they assume the class User
is something like this:
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
An interesting note is that Realm creates a "proxy subclass" where they redefine the setName
and getName
methods (aka, you need getters/setters to make some systems work! They assume you have them!)
But what's important here is that you need to provide the field name with "name"
.
Later on, anyone can make a typo, or you just won't remember the fields off the top of your head. In this particular case, they tend to create a metamodel (something that stores the name of the fields for you) for the purpose that you won't have to use Strings to refer to field names.
For example, in Criteria API, instead of
Root<Pet> pet = cq.from(Pet.class);
cq.select(pet.get("name"));
They have the metamodel like this:
Root<Pet> pet = cq.from(Pet.class);
cq.select(pet.get(Pet_.name));
Thus, it eliminates the need for String
s.
And similarly, what I tend to do with Realm is that I create a "metamodel" (although you can now have it generated for you automatically with RealmFieldNamesHelper):
public class User
extends RealmObject {
@PrimaryKey
private long id;
private String name;
public static enum Fields { //this is the "metamodel"
ID("id"),
NAME("name");
private String fieldName;
Fields(String fieldName) {
this.fieldName = fieldName;
}
public String getField() {
return fieldName;
}
@Override
public String toString() {
return getField();
}
}
}
And thus you can replace the query like so
RealmResults<User> result2 = realm.where(User.class)
.equalTo(User.Fields.NAME.getField(), "John")
.or()
.equalTo(User.Fields.NAME.getField(), "Peter")
.findAll();
But with getters and setters, you already have the type safety, and you already have the methods that you don't have to remember off the top of your head - and so, you have the compilation time error checks.
So to answer your question, the reason why it's a bad practice to refer to variables by string name in Java is because
1.) typos can happen, and the error occurs only in runtime, not compile time
2.) it is not type-safe; you are lucky that you get float
s back no matter what in this example, but the moment it can be a String
, you need to return Object
and cast it or use public <T> T get(String field) { ... }
and whoever calls the method must know exactly what they are receiving - also runtime-error prone.