返回泛型列表时强制转换对象

时间:2013-07-11 04:25:30

标签: java generics

我对Java和泛型相对较新。我正在尝试理解我是否在编写通用方法时做错了。我有以下代码(大大简化):

public class ContentIniter {       
    public ContentType getContentType();
}

public interface Content {
}

public class Show implements Content {       
}

public class Movie implements Content {       
}

public enum ContentType {
    Movie, Show
}

public class Channel {

    public List<Show> getShows() {
        return getContentByType(ContentType.Show)
    }

    public List<Movie> getMovies() {
        return getContentByType(ContentType.Movie)
    }

    private <T> List<T> getContentByType(ContentType contentType) {
        List<T> typeContents = Lists.newArrayList();
        List<ContentIniter> allContentIniters = someMethod(); // Returns initers for both shows and movies
        for (Content contentIniter : allContentIniters) {
            if (contentIniter.getContentType().equals(contentType)) {
                switch (contentType) {
                case Movie:
                    typeContents.add((T) new Movie(contentIniter));
                    break;
                case Show:
                    typeContents.add((T) new Show(contentIniter));
                    break;
                }
            }
        }
        return typeContents;
    }

}

我的问题与这条线有关:

typeContents.add((T) new Movie(contentIniter));

我能够获得编译代码的唯一方法是将内容对象转换为T.但这对我来说似乎很难吃(我不明白为什么编译器无法推断基于类型在电话上)。此外,即使代码有效,IntelliJ也会抱怨未经检查的演员。

有没有更好的方法来编写泛型方法?

UPDATE :当我尝试简化代码时,将代码搞砸了一下。修正了对typeContents的引用。此外,我添加了更多的复杂性,以便更好地反映现实,希望解释为什么我不只是检查instanceof

更新2 :意识到还有另一个错误... ContentIniter没有实现内容。值得注意的是,ContentIniter只是一个组成的对象。如果它看起来很奇怪,可以将其视为内容对象用于委派某些行为的事件或其他策略。

4 个答案:

答案 0 :(得分:1)

你没有正确使用泛型,当你没有必要时,你会将它们与你的枚举混合在一起。理想情况下,您将调用getContentByType<Show>(),然后使用反射从allContents确定正确类型的列表。

尝试更多类似(未经测试)的内容:

private <T> List<T> getContents() {
    List<T> typeContents = Lists.newArrayList();
    List<Content> allContents = someMethod(); // Returns both shows and movies
    for (Content content : allContents) {
        if (content instanceof T) {
            typeContents.add((T) content);
        }
    }
    return typeContents;
}

并致电:

List<Show> shows = getContents<Show>();

然后,您可以将其上调用的类型限制为仅扩展Content

的类型
private <T extends Content> List<T> getContents() {
    ...
}

答案 1 :(得分:0)

实际上答案比你想象的要简单:你只需要检查你的内容实例是一个节目还是一个电影来让你的编译器满意:

if (content instanceof Movie)
    contents.add((Movie) content);
if (content instanceof Show)
    contents.add((Show) content);

无论如何,我会说你编写通用方法的方式是正确的。但是,由于有一种本机方式来检查实例的类型(instanceof),您应该使用它:)

编辑:我仍然认为你应该使用instanceof

另外,您应该使用List<Content>而不是List<ContentIniter>,因为内容是一种更全球化的类型:如果有人想出另一种内容实现,他就不必更改您的代码。实际上,当您使用接口列表而不是ArrayList时,您正在做同样的事情,因为List不像ArrayList那样具体。

另外,使用枚举不是一个错误:如果你想使用它,你可以。但它不应该用于确定实例的类型。实例的类型包含在实例本身中。不过,我会说Daniel Imms的解决方案比我的更优雅,并且更好地利用了Java类型的功能。

public interface Content {
    public STContentType getContentType();
}

public class ContentIniter implements Content {       
}

// You can keep the enum, as long as it's not used 
// to check for the type of an instance of ContentIniter
public enum ContentType {
    Movie, Show
}

public class Show implements Content {       
}

public class Movie implements Content {       
}


public class Channel {

    public List<Show> getShows() {
        return getContentByType(ContentType.Show)
    }

    public List<Movie> getMovies() {
        return getContentByType(ContentType.Movie)
    }

    private <T> List<T> getContentByType(ContentType contentType) {
        List<T> typeContents = Lists.newArrayList();
        // Using more generic type Content
        List<Content> allContentIniters = someMethod(); // Returns initers for both shows and movies
        for (Content contentIniter : allContentIniters) {
            // If it's a Show and I asked for Shows
            if (contentIniter instanceof Show && contentType == ContentType.Show)) {
                typeContents.add(contentIniter);
            }
            // If it's a Movie and I asked for Movies
            if (contentIniter instanceof Movie && contentType == ContentType.Movie){
                typeContents.add(contentIniter);
            } 
        }
        return typeContents;
    }

}

答案 2 :(得分:0)

我在代码示例更改后删除了原来的答案。

我真的不认为你可以避免演员和@SuppressWarnings("unchecked")

只要你知道自己在做什么,这可能是最好的解决方案。

替代方法是不使用getByContentType方法,只需在getShows()getMovies()方法上使用一些重复的逻辑。

例如:

public List<Show> getShows() {
    List<Show> shows = new ArrayList<Show>();

    List<ContentIniter> allContentIniters = someMethod();

    for(ContentIniter initer: allContentIniters) {
        if(initer.getContentType().equals(ContentType.Show)) {
            shows.add(new Show(initer));
        }
    }

    return shows;
}

public List<Movie> getMovies() {
    List<Movie> movies = new ArrayList<Movie>();

    List<ContentIniter> allContentIniters = someMethod();

    for(ContentIniter initer: allContentIniters) {
        if(initer.getContentType().equals(ContentType.Movie)) {
            movies.add(new Movie(initer));
        }
    }

    return movies;
}

答案 3 :(得分:0)

使用enum在这里看起来很奇怪,你失去了使用泛型的优势。

初学者的事情让事情更加奇怪和混乱。

用这样的东西看起来可能更自然:

public interface Content {
}

public class Show implements Content {       
}

public class Movie implements Content {       
}

//......
    private <T extends Content> List<T> getContentByType(Class<T> contentType) {
        List<T> result = Lists.newArrayList();

        List<Content> allContents = someMethod();   // ContentIniter is just a mess
                                                    // Get all content you have!
        for (Content content: contents) {
            if (contentType.isAssignableFrom(content.getClass())) {
                result.add(content);
            }
        }
        return result;
    }

使用方法是

List<Show> result = channel.getContent(Show.class);