Is throwing an error in unpredictable subclass-specific circumstances a violation of LSP?

Let’s say I wanted to create a Java List<String> (see spec) implementation that uses a complex subsystem, such as a database or file system, for its store so that it acts as a persistent collection rather than an in-memory one.

So here’s a skeleton implementation:

class DbBackedList implements List<String> {
  private DbBackedList() {}
  /** Returns a list, possibly non-empty */
  public static getList() { return new DbBackedList(); }
  public String get(int index) {
    return Db.getTable().getRow(i).asString();  // may throw DbExceptions!
  }
  // add(String), add(int, String), etc. ...
}

My problem lies with the fact that the underlying DB API may encounter connection errors that are not specified in the List interface that it should throw.

My problem is whether this violates Liskov’s Substitution Principle (LSP).

In his paper on LSP, Bob Martin actually gives an example of a PersistentSet that violates LSP. The difference is that his newly-specified Exception there is determined by the inserted value and so is strengthening the precondition. In my case the connection/read error is unpredictable and due to external factors and so is not technically a new precondition, merely an error of circumstance, perhaps like OutOfMemoryError which can occur even when unspecified.

In normal circumstances, the new Error/Exception might never be thrown. (The caller could catch if it is aware of the possibility, just as a memory-restricted Java program might specifically catch OOME.)

Is this therefore a valid argument for throwing an extra error and can I still claim to be a valid java.util.List (or pick your SDK/language/collection in general) and not in violation of LSP?

Edit: This argument might be more palatable if you consider a FileBackedList (more reliable “connection”) rather than a DbBackedList.

If this does indeed violate LSP and thus not practically usable, I have provided two less-palatable alternative solutions as answers that you can comment on, see below.


Footnote: Use Cases

In the simplest case, the goal is to provide a familiar interface for cases when (say) a database is just being used as a persistent list, and allow regular List operations such as search, subList and iteration.

Another, more adventurous, use-case is as a slot-in replacement for libraries that work with basic Lists, e.g if we have a third-party task queue that usually works with a plain List:
new TaskWorkQueue(new ArrayList<String>()).start() which is susceptible to losing all it’s queue in event of a crash, if we just replace this with:
new TaskWorkQueue(new DbBackedList()).start() we get a instant persistence and the ability to share the tasks amongst more than one machine.

In either case, we could either handle connection/read exceptions that are thrown, perhaps retrying the connection/read first, or allow them to throw and crash the program (e.g. if we can’t change the TaskWorkQueue code).

9

The reason why his example is a violation of LSP is not because of the exception per se, it is the reason for the exception — it is changing the contract.

A simpler, but more contrived example — you have a List of ints, you decide you want to use it for a list of elementary grades completed, with a check to make sure that the numbers are within the required range, so you subclass ListInt as ListIntElementary. ListIntElementary violates LSP.

You, on the other hand, have real world constraints that wouldn’t apply to the base class, but your subclass can algorithmically be used anywhere the base class can, it accepts all acceptable inputs, returns only acceptable values. Exceptions are neither input nor output in the LSP sense.

A new exception or new cause for an old exception (if you can find one that maps cleanly) is an implementation detail, not a violation of LSP. It may mean that in practice that it is unsuitable as a replacement for the base class, but in theory once created, it can be used everywhere that the base class can be used.

In short, this is fine.

2

To start with, your DBException hardly qualifies as Error:

subclasses of Error… are abnormal conditions that should never occur.

Note also that if you expect DBException to be thrown in overridden get, it must be unchecked one. Otherwise, your code won’t compile, per JLS 8.4.8.3. Requirements in Overriding and Hiding:

A method that overrides or hides another method, including methods that implement abstract methods defined in interfaces, may not be declared to throw more checked exceptions than the overridden or hidden method…


With above said, it looks like yes, it would violate LSP – because as a user of Java Collections Framework, I would not expect List.get to throw runtime exception for any kind problem other than “programming mistakes”, that is for something that is possible to avoid by changing the code (note, no matter how you change code, database connection won’t be guaranteed).

If you look at the IndexOutOfBoundsException specified for List.get, it reads like one that programmer can avoid by preliminary bounds check:

if the index is out of range (index < 0 || index >= size())

Another runtime exception documented for Collections, including List, is ConcurrentModificationException and per API docs, it is also expected to be dealt with by correcting the code that caused it:

ConcurrentModificationException should be used only to detect bugs.

More detailed explanation for what I expect is provided in JCF Design FAQ. It addresses UnsupportedOperationException, but if you take a look at prior examples of IOOBE and CME, the reasoning fits these as well:

Won’t programmers have to surround any code that calls optional operations with a try-catch clause in case they throw an UnsupportedOperationException?

It was never our intention that programs should catch these exceptions: that’s why they’re unchecked (runtime) exceptions. They should only arise as a result of programming errors, in which case, your program will halt due to the uncaught exception.


Suming up, if I wanted to expose database backed data as List (or any Collection for that matter) in a way that would be least confusing for users of my API, I would probably wrap that data into some helper object that would expose DB related exceptions only when used “outside” of collection context – that is, when client code would try to access the data expected to be stored “inside” the wrapper, not when collections of wrappers are carried over the client code.

For a concrete example how this could be done, take a look at java.util.concurrent.Future which wraps results of an asynchronous computation in a way that doesn’t expose “internal”, wrapped exceptions when carried over in collections.

4

The LSP basically says the answer to “Will code that correctly uses this interface do the wrong thing if used with your implementation of it?” should be no. Throwing new and different exceptions may cause code that thought it was handling all exceptions to fail when a new and unexpected exceptions show up. If it is possible to map exceptions to ones already provided by the interface then that is a possible path to go down, but they have to map cleanly in the intent they are trying to communicate though. You don’t want to map to a fatal error from a nonfatal error and vice versa.

If LSP is unquestionably being violated, I could instead introduced a Option Type class that the List nominally stores and returns, which encapsulates the return types and any errors in retrieval.

/**
 * Wraps a String for writing or reading from a database.
 */
interface DbString {
  public DbString(String str) { /* ... */ }
  /** @returns the String, or "" if hasError() returns true */
  public String() getString();
  /** @returns true if there was an error in retrieving the string */
  public boolean hasError();
}

/**
 * A list of DbStrings
 */
class DbBackedList implements List<DbString> {
  // .. as before

  public DbString get(int index) {
    return Db.getConnection.getTable().getRow(i).asString();  // may throw!
  }

  // add(DbString), add(int, DbString), etc. ...
}

Usage is similar to regular lists.

List<String> l = new DbBackedList();
l.add(new DbString("foo"));
assertFalse(a.get(0).hasError());
assertEquals("foo", a.get(0).getString());

At this point, though, it’s arguably easier to create a new interface from scratch and not retrofit an existing one, however we lose some of the apparent familiarity with the List interface. If we want to provide other collections, such as Set or Map we will then have to create new interfaces for each of them.

2

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật