0

This is a follow up to: my previous question

My following line of code does not work:

IAccount account = (AccountModel) new AccountRepository().getByEmail(emailaddress);

The returntype of ...getByEmail(...) is Model<Account>, and AccountModel extends Model<Account>.
Yet I get a java.lang.ClassCastException: models.Model cannot be cast to models.AccountModel when I test it. I know this is because every AccountModel is a Model<Account>, but not the other way around.
Is there any way how I can make sure that I can fix this (or work around it).

public class AccountRepository extends Repository<Account> {

    public AccountRepository() {
        super(Account.class);
    }

    public Model<Account> getByEmail(String emailAddress) {
        return this.getCustomHqlSingle("FROM Account a WHERE a.emailAddress = '" + emailAddress + "'");
    }
}

public abstract class Repository<T> implements Serializable {
    protected final Model<T> getCustomHqlSingle(String hql) {
        List<Model<T>> t = this.getCustomHqlList(hql);
        if (t != null && !t.isEmpty()) {
            return t.get(0);
        } else {
            return null;
        }
    }

    protected final List<Model<T>> getCustomHqlList(String hql) {

        Session session = SESSION_FACTORY.openSession();

        try {
            session.beginTransaction();
            List<T> entities = session.createQuery(hql).getResultList();
            List<Model<T>> result = new ArrayList<>();
            for (T t : entities) {
                result.add(this.getByEntity(t));
            }
            return result;
        } finally {
            session.close();
        }
}

To the person that marked this question as duplicate, let me rephrase the following sentence from my question:

I know this is because every AccountModel is a Model<Account>, but not the other way around.

To

I know this is because every Dog is an Animal, but not the other way around.

Community
  • 1
  • 1
ivospijker
  • 702
  • 1
  • 7
  • 22
  • That depends on what your real need is. If you need an `AccountModel`, you must make sure you get one, either by your account repository returning one to you or you construct it some way. Otherwise cast to a weaker type. Would casting to `IAccount` work?? (you probably have tried) – Ole V.V. Jan 04 '17 at 11:35
  • To put it another way, what is it that you need that means you cannot just work with a `Model`? – Ole V.V. Jan 04 '17 at 11:37
  • Possible duplicate of [explicit casting from super class to subclass](http://stackoverflow.com/questions/4862960/explicit-casting-from-super-class-to-subclass) – Sabir Khan Jan 04 '17 at 11:38
  • I peeked in your previous question and saw that you return an Model instance in your getById method. Even though AccountModel extends Model doesnt mean that you can cast all Model instances to a AccountModel - only if it is in fact a instance of an AccountModel. Try to return a new AccountModel in your getByEmail method and see if that works. – stalet Jan 04 '17 at 11:38
  • Just use Model all the way and it will be fine. – glee8e Jan 04 '17 at 11:39
  • @OleV.V. I want to return an IAccount because it has some extra methods (not variables) that aren't present in Model. – ivospijker Jan 04 '17 at 11:39
  • Let me guess, the object actually returned to you is of a runtime type that does not have those methods? In that case there is no easy way to “glue the extra methods on”. – Ole V.V. Jan 04 '17 at 11:42
  • 2
    @SabirKhan, this is hardly a duplicate since the asker already said s/he understands that the `ClassCastException` is because the returned object is not an `AccountModel`. – Ole V.V. Jan 04 '17 at 11:43
  • It would help to know what you're doing in `getByEmail` which is supposedly implemented by you? Right now I don't see why you can't just return an `AcountModel` there instead. – Jorn Vernee Jan 04 '17 at 11:59
  • @stalet The reason why I return a Model is because T is a JPA annotated entity, and my AccountRepository is also an implementation of Repository. I will try and see if I instead make my Repositories depend on Models instead of Entities – ivospijker Jan 04 '17 at 12:15
  • @JornVernee I've added my implementation as requested. – ivospijker Jan 04 '17 at 12:25
  • 1
    So nobody has so called **solution** to his problem and still its not a duplicate. Why everybody keeps explaining same thing about cast etc that is already explained in linked question? Instead of discouraging him to go that way and labeling it as a **bad programming**, he is being encouraged to do a risky cast. Every language has its own limitations, this is one of them, know it and move on. Change your code. Current code will not fit in. – Sabir Khan Jan 04 '17 at 12:52

2 Answers2

1

You have to devise a method in which to convert a Model<Account> to an AccountModel. Taking the code from your other question you could add a constructor for this to the AccountModel class:

public class AccountModel extends Model<Account> implements IAccount {
    private static final AccountRepository REPOSITORY = new AccountRepository();

    public AccountModel(Account entity) {
        super(entity, REPOSITORY);
    }

    public AccountModel(Model<Account> model) { // <---
        super(model._entity, REPOSITORY);
    }

    // Method implementations...
}

Then change your AccountRepository class to return an AccountModel from getByEmail:

public AccountModel getByEmail(String emailAddress) {
    return new AccountModel(this.getCustomHqlSingle("FROM Account a WHERE a.emailAddress = '" + emailAddress + "'"));
}

Which uses the new constructor to convert the Model<Account> to an AccountModel.


There is another option. Rather than calling new Model<T>(...) in Repository, you could have implementing classes implement an abstract method that would return the desired Model type:

public abstract class Repository<T, R> implements Serializable

...

public Repository(Class<T> repositoryClass) {

    if (!Repository._initiated) 
        setup();

    this.cons = cons;
}

protected abstract R getModel(T entity, Repository<T> repo); // <--

Then somewhere in the factory methods:

public R getByFoo(...) {
   ...
   T t = session.get(_repositoryClass, ...);
   return getModel(t, this);
}

Where AccountRepository would return a new AccountModel:

public class AccountRepository extends Repository<Account, AccountModel> {

    public AccountRepository() {
        super(Account.class);
    }

    @Override
    protected AccountModel getModel(Account entity, Repository<Account> repo) {
        return new AccountModel(entity);
    }

}
Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
0

Since you say you understand why you get an error, let me try to explain something else about casting.

Except for some limited cases, the casting operator does not change the item being cast. Casting is an instruction to the compiler only, saying "My logic has guaranteed that the type of the cast object is different than the compiler can determine, so while you (the compiler) would tell me this is an error, I am certain that, at runtime, it will be fine. So don't give me an error".

It does NOT say "take this object and transform it into another, similar object". That operation is not possible in Java, thank heavens.

arcy
  • 12,845
  • 12
  • 58
  • 103
  • Is there any way how I can make sure that I can fix this (or work around it). – ivospijker Jan 04 '17 at 12:28
  • I am not sure what you mean. In order to fix the casting operation, your code must ensure the cast is legal. The compiler can tell you, sometimes, that there is never any way to cast one thing to another, but it cannot tell you there will be no ClassCastException. Instead of a cast, perhaps you can call a method that takes the object you have and instantiates the correct type of object with it? That's a stab in the dark; it would get you past the casting issue, but I have no idea whether it's appropriate in your application. – arcy Jan 04 '17 at 16:15