-1

I have a class called Item, which - besides everything else as usual - defines the toString method like this:

@Override
public String toString() {
    return "Item [id=" + id + ", name=" + name + ", type=" + type + "]";
}

Then I have an SQL connector for dumping all items in the table like this:

public ArrayList<Item> dumpAll() {
        ArrayList<Item> dump = new ArrayList<Item>();
        connectDB();
        try {
            result = connectDB().prepareStatement("SELECT * FROM Items").executeQuery();
            if (result != null) {
                connectDB().close();
                while (result.next()) {
                    Item item = new Item();
                    item.toString();
                    dump.add(item);
                }
            }
        } catch (Exception e) {
            System.out.println("Query failed.");
            // e.printStackTrace();
        }
        return dump;
    }

connectDB(); is working fine. When I call the toString method elsewhere, I can see a result like this:

Item [id=0, name=null, type=null]

This line is repeated as many times as I have items in the table, i.e. I have 50 items; I get 50 lines showing zeroes and nulls. The format is exactly as I want it but obviusly the results are not. I think I'm missing something obvious handling the result, but trying to debug with String itm = result.toString();, Item.equals(result.toString()); and so on hasn't worked out.

Air
  • 8,274
  • 2
  • 53
  • 88
lapingultah
  • 196
  • 4
  • 17
  • You create an empty item, call `toString()` on it (for no effect), then add it to a list. You're left with a list of empty items. Surprised? – Kayaman Dec 08 '16 at 18:47
  • So after creating a new `Item`, I am to `item.xxxx(result.yyyy));`, but... what? – lapingultah Dec 08 '16 at 18:49

2 Answers2

2

Answer

It's your

Item item = new Item();

line. That says: "initialise an Item with NOTHING!" So of course you have empty objects. It should be:

Item item = new Item(result.getInt(0), result.getString(1), result.getString(2));

Or whatever column names you gave them in the SQL table.


Side note

It's bad practice to allow invalid objects. You shouldn't HAVE a default constructor for your Item object, because there should never be an Item object with invalid data. Trust me when I say it makes everything easier in the long run.

https://softwareengineering.stackexchange.com/questions/261585/should-a-getter-throw-an-exception-if-its-object-has-invalid-state


Experimental solution

If you must use the SQL columns automatically (without having to specify each column), it's possible using reflection, but it's fairly brittle. It only works if the following is satisfied:

  • The property names on the Java object are exactly the same as the SQL columns
  • The property types on the Java object are the default types from ResultSet (primitives to Wrapper classes are ok)

The reflection part can be put into a base class to be extended elsewhere. Here I've called it SimpleSQL. (This has the distinct feeling that I'm being too clever with this answer. I take no responsibility for this code; it's purely being presented as a "you could do this" scenario)

import java.lang.reflect.*;
import java.sql.ResultSet;

public class SimpleSQL {

    public SimpleSQL(ResultSet result) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {

        for (Field f: getClass().getDeclaredFields()) {
            // Get the field type name, converting the first character to uppercase
            String fTypNam = f.getType().getSimpleName();
            fTypNam = fTypNam.substring(0,1).toUpperCase() + fTypNam.substring(1);

            // Get an object that represents each of the methods we want to call
            Method getM = result.getClass().getMethod("get" + fTypNam, String.class);
            Method setM = f.getClass().getMethod("set", Object.class, Object.class);

            // Set the property of this object using the field object's set
            //  and the ResultSet's get.
            setM.invoke(f, this, getM.invoke(result, f.getName()));
            // For a hypothetical 'id' field that is an int, this is equivalent to:
            //   this.id = result.getInt("id");
        }

    }

    @Override
    public String toString() {
        String result = getClass().getName()+"[";
        try {
            Field[] fields = getClass().getDeclaredFields();
            result += fields[0].getName() + ": " + fields[0].get(this);
            for(int i = 1; i < fields.length; ++i) {
                result += ", " + fields[i].getName() + ": " + fields[i].get(this);
            }
        } catch(IllegalAccessException e) {
            // This happens if the field is private.
            throw new RuntimeException(e.getMessage());
        }
        result += "]";
        return result;
    }
}

(see more on toString and reflection here. tl;dr, the Apache Commons library provides a ReflectionToStringBuilder class for a better version of my toString())

Then you can just define your Item as:

import java.lang.reflect.*;
import java.sql.*;

public class Item extends SimpleSQL {
    protected int id;
    protected String name;
    protected String description;

    public Item(ResultSet result) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        super(result);
    }
}
Community
  • 1
  • 1
Multihunter
  • 5,520
  • 2
  • 25
  • 38
  • I understand the approach, but say that my table would have 200 columns with `int`s and `double`s and such mixed, would I have to `get` _every_ one of them separately when I have a method toString() to dump all of them out at once? – lapingultah Dec 08 '16 at 19:05
  • What I'm asking is that there's no way to utilize (an automatically generated) `toString()` method which would help avoid all the manual `get`s? – lapingultah Dec 08 '16 at 19:10
  • I don't think you fully understand what the result.next() is doing. – Multihunter Dec 08 '16 at 19:32
  • 1
    The result set object has some temporary memory area for a single row of the returned results. When you call result.next() it grabs the next row of the response, and puts it in this temporary memory. Then, when you call `result.getString(0)`, you are grabbing the value in the first column of the the loaded row and parsing it as a string. The 'manual `get`s' are surely not that big a deal; you only have to specify what to do per-row. – Multihunter Dec 08 '16 at 19:35
  • Oh, sorry you mentioned many rows. Yes, if you have 200 columns, then you would have to manually `get` each one. However, really, I can't imagine a well-designed use case that requires that many columns for a single object. You should consider which ones you **REALLY** need and only use those. If you **really** do need 200 of them, then the way Java is designed, then you should be creating an object that has 200 properties and passing through 200 parameters. – Multihunter Dec 08 '16 at 19:40
  • That explains the behavior, thanks. So basically my question is useless since it cannot be answered. – lapingultah Dec 08 '16 at 21:42
  • Thinking on it further, it should be possible to use reflection to automatically fill the properties into such an object.... – Multihunter Dec 09 '16 at 07:19
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/130192/discussion-between-multihunter-and-lapingultah). – Multihunter Dec 09 '16 at 08:32
1

Your Item class should have getters(), setters(), constructor(), and the toString() methode like this:

public class Item {

    private int id;
    private String name;
    private String type;

    public Item(int id, String name, String type) {
        this.id = id;
        this.name = name;
        this.type = type;
    }

    //...Getters and Setters

    @Override
    public String toString() {
        return "Item [id=" + id + ", name=" + name + ", type=" + type + "]";
    }
}

So after that you should to create an object like this:

while (result.next()) {
    Item item = new Item(result.getInt("id"), result.getString("name"), result.getString("type"));
    item.toString();
}

Hope this can help you.

Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140