List vs List<Object>
Asked Answered
S

6

15

Why do we lose type safety when using List and not while using List<Object>? Aren't they basically the same thing?

EDIT: I found that the following gives a compilation error

public class TestClass
{
    static void func(List<Object> o, Object s){
        o.add(s);
    }

    public static void main(String[] args){
        func(new ArrayList<String>(), new Integer(1));
    }
}

whereas this doesn't

public class TestClass
{
    static void func(List o, Object s){
        o.add(s);
    }

    public static void main(String[] args){
        func(new ArrayList<String>(), new Integer(1));
    }
}

Why?

Synthiasyntonic answered 21/7, 2011 at 21:56 Comment(1)
The direct answer to why does the second one compile is so that code written before java 5 can be compiled by 5 and later.Cholecystotomy
P
5

Why do we lose type safety when using List and not while using List<Object>? Aren't they basically the same thing?

No they are not the same thing.

If you are providing an API,

class API {
  static List<Object> getList() { ... }
  static void modifyList(List<Object> l) { ... }
}

and a client uses it improperly

List<Integer> list = API.getList();
API.modifyList(list);
for (Integer i : list) { ... }  // Invalid

then when your API specifies List<Object> they get a compile-time error, but they don't when API.getList() returns a List and API.modifyList(list) takes a List without generic type parameters.

EDIT:

In comments you mentioned changing

void func(List<Object> s, Object c) { s.add(c); }

to

void func(List s, Object c) { s.add(c); }

so that

func(new List<String>(), "");

would work.

That is violating type safety. The type-safe way to do this is

<T> void func(List<? super T> s, T c) { s.add(c); }

which is basically saying that func is a parameterized function that takes a List whose type can be any super class of T, and a value of type T, and adds the value to the list.

Peerage answered 21/7, 2011 at 22:5 Comment(7)
Yes, I found a similar case. The program doesn't compile when i have this: func(List<Object> s, Object c){ s.add(c)} and call func(new ArrayList<String>(), new Integer(1));. It compiles when I change the method to func(List s, Object o) and call it again in the same waySynthiasyntonic
Without generic type, the for loop would not compile as well.Haakon
@Varun, a List<String> is not a List<Object> because myList.add(Boolean.FALSE) is valid when myList is a List<Object> but not when it is a List<String>. Maybe your function should be <T> void func(List<? super T> s, T c) { s.add(c); }Peerage
"The type-safe way to do this is..." It is sufficient to say <T> void func(List<T> s, T c) { s.add(c); }Sacking
@user102008, true. That is not as general though. You cannot then do List<Object> heterogeneousList = ...; func(heterogeneousList, "foo"); because the "foo" makes T bind to String at that call-site, but the hetergeneousList has a type of List<Object> which is not a List<T>. If I was trying to be as general as possible though, I should've probably used a Collection instead of a List.Peerage
@Mike Samuel: Yes you can. Try it.Sacking
@user102008, You're right. The <T> in your signature binds to the type-bottom of (String, Object) which is (Object), but in my signature <T> binds to String. I was mistakenly assuming it would fail because I was assuming <T> would be String in both cases.Peerage
K
8

List is a list of some type you don't know. It could be a List<String>, List<Integer>, etc.
It's effectively equivalent to List<?>, or List<? extends Object>, except that it doesn't document that fact. It's only supported for backwards compatibility.

List<Object> is a list of Objects. Any object of any type can be put inside it, contrary to a List<String>, for example, which only accepts strings.

So no, they're not the same thing.

Kyle answered 21/7, 2011 at 22:16 Comment(0)
P
5

Why do we lose type safety when using List and not while using List<Object>? Aren't they basically the same thing?

No they are not the same thing.

If you are providing an API,

class API {
  static List<Object> getList() { ... }
  static void modifyList(List<Object> l) { ... }
}

and a client uses it improperly

List<Integer> list = API.getList();
API.modifyList(list);
for (Integer i : list) { ... }  // Invalid

then when your API specifies List<Object> they get a compile-time error, but they don't when API.getList() returns a List and API.modifyList(list) takes a List without generic type parameters.

EDIT:

In comments you mentioned changing

void func(List<Object> s, Object c) { s.add(c); }

to

void func(List s, Object c) { s.add(c); }

so that

func(new List<String>(), "");

would work.

That is violating type safety. The type-safe way to do this is

<T> void func(List<? super T> s, T c) { s.add(c); }

which is basically saying that func is a parameterized function that takes a List whose type can be any super class of T, and a value of type T, and adds the value to the list.

Peerage answered 21/7, 2011 at 22:5 Comment(7)
Yes, I found a similar case. The program doesn't compile when i have this: func(List<Object> s, Object c){ s.add(c)} and call func(new ArrayList<String>(), new Integer(1));. It compiles when I change the method to func(List s, Object o) and call it again in the same waySynthiasyntonic
Without generic type, the for loop would not compile as well.Haakon
@Varun, a List<String> is not a List<Object> because myList.add(Boolean.FALSE) is valid when myList is a List<Object> but not when it is a List<String>. Maybe your function should be <T> void func(List<? super T> s, T c) { s.add(c); }Peerage
"The type-safe way to do this is..." It is sufficient to say <T> void func(List<T> s, T c) { s.add(c); }Sacking
@user102008, true. That is not as general though. You cannot then do List<Object> heterogeneousList = ...; func(heterogeneousList, "foo"); because the "foo" makes T bind to String at that call-site, but the hetergeneousList has a type of List<Object> which is not a List<T>. If I was trying to be as general as possible though, I should've probably used a Collection instead of a List.Peerage
@Mike Samuel: Yes you can. Try it.Sacking
@user102008, You're right. The <T> in your signature binds to the type-bottom of (String, Object) which is (Object), but in my signature <T> binds to String. I was mistakenly assuming it would fail because I was assuming <T> would be String in both cases.Peerage
A
3

A List<Object> isn't really any more typesafe than a List. However, the Object in the code does imply intent. When someone else looks at it later, they can see that you purposefully chose Object as the type, rather than wondering if you just forgot to put a type or are storing something else and typecasting it elsewhere.

Since code gets read more than it gets written, hints at the intent of the code can be very valuable later on.

Appellation answered 21/7, 2011 at 22:5 Comment(0)
V
1

The reason you have a compiler warning when you use List instead of List<Object> is that when you have a List the compiler doesn't know what type of List it is, so while you could treat it as a List<Object>, the compiler can't ensure that at some other point in the code it wasn't set to reference a List<String> and the type safety of the Generics cannot be checked. That is really the compiler warning here - it is saying it can't help ensure the type safety of the generics, and it won't happen at runtime either (until at some later point there is an actual cast in the code).

Vanadium answered 21/7, 2011 at 22:4 Comment(0)
V
0

For purposes here, you could say they're the same thing. But presumably you almost never actually fill a List with pure instance of Object. They're Strings or something. In this example, List<Object> is technically using generics but not really taking any advantage of it. So, it loses the compile-time type checking of generics.

Veda answered 21/7, 2011 at 21:59 Comment(0)
P
0

Type Erasure is one answer and the backward compatibility to pre Java 1.5 and tighter type check in case of first one.

Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:

Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods. Insert type casts if necessary to preserve type safety. Generate bridge methods to preserve polymorphism in extended generic types. Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.

Precondition answered 22/6, 2020 at 16:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.