The idea of design is GenericFileService maintains a list of candidate instances of FileService implementations. Each instance can only handle a specific fileType. GenericFileServiceImpl implements GenericFileService and provide a generic method saveFileInfo to accept any supported fileType. I think this should work at runtime, but I got wildcard capture compile error. Could you explain why? How to fix it?
interface FileInfo {
String getFileType();
}
static class MsdsFileInfo implements FileInfo {
private String fileType;
#Override
public String getFileType() {
return fileType;
}
/*
... Other msds file specific properties
* */
}
static class NdgcFileInfo implements FileInfo {
private String fileType;
#Override
public String getFileType() {
return fileType;
}
/*
... Other ndgc file specific properties
* */
}
interface FileService<T extends FileInfo> {
void saveFileInfo(T fileInfo);
boolean support(String fileType);
}
static class MsdsFileService implements FileService<MsdsFileInfo> {
#Override
public void saveFileInfo(MsdsFileInfo fileInfo) {
// implementation for MsdsFileInfo
}
#Override
public boolean support(String fileType) {
return StringUtils.equalsIgnoreCase(fileType, "MSDS");
}
}
static class NdgcFileService implements FileService<NdgcFileInfo> {
#Override
public void saveFileInfo(NdgcFileInfo fileInfo) {
// implementation for NdgcFileInfo
}
#Override
public boolean support(String fileType) {
return StringUtils.equalsIgnoreCase(fileType, "NDGC");
}
}
interface GenericFileService {
<T extends FileInfo> void saveFileInfo(String fileType, T fileInfo);
}
static class GenericFileServiceImpl implements GenericFileService {
private final List<FileService<? extends FileInfo>> candidateFileServices = new ArrayList<>();
public GenericFileServiceImpl() {
candidateFileServices.add(new MsdsFileService());
candidateFileServices.add(new NdgcFileService());
}
#Override
public <T extends FileInfo> void saveFileInfo(String fileType, T fileInfo) {
FileService<? extends FileInfo> fileService = candidateFileServices.stream()
.filter(service -> service.support(fileType))
.findFirst()
.orElseThrow(() -> {
throw new IllegalArgumentException("Unsupported fileType.");
});
// Got wildcard capture error here
fileService.saveFileInfo(fileInfo);
}
}
Below is the error message:
error: incompatible types: T cannot be converted to CAP#1
fileService.saveFileInfo(fileInfo);
^
where T is a type-variable:
T extends FileInfo declared in method <T>saveFileInfo(String,T)
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
I can tell you, where is the problem with this code.
Consider this piece of code you provided:
private final List<FileService<? extends FileInfo>> candidateFileServices = new ArrayList<>();
public GenericFileServiceImpl() {
candidateFileServices.add(new MsdsFileService());
candidateFileServices.add(new NdgcFileService());
}
You defined the variable candidateFileServices, type is List<FileService<? extends FileInfo>>
So, you can add new MsdsFileService() and new NdgcFileService() in this List
Then you use
FileService<? extends FileInfo> fileService = candidateFileServices.stream()...
get a fileService, type is FileService<? extends FileInfo>.
So, which Object can be hold by the fileService? MsdsFileService、NdgcFileService and any class which extends FileService
Then the code fileService.saveFileInfo(fileInfo);
Looking back at its method signature:
interface FileService<T extends FileInfo> {
void saveFileInfo(T fileInfo);
boolean support(String fileType);
}
Its argument is FileInfo or a subtype of FileInfo.
When you use that fileService to call the method saveFileInfo, In fact, at the compilation stage, the compiler cannot determine the type of its parameters at all, so it can only be represented by T, but T is FileInfo or a subtype of FileInfo.
Therefore, the java compiler will consider it unsafe
Because fileService may be MsdsFileService、NdgcFileService and any class which extends FileService, the parameter of the method saveFileInfo may be FileInfo or a any subtype of FileInfo
But you can't use MsdsFileService to call method saveFileInfo(NdgcFileInfo).
In addition, this code, it seems, is deliberately using generics, but in fact it does not need it at all:
interface FileInfo { }
class File1Info implements FileInfo { }
class File2Info implements FileInfo { }
interface FileService {
void saveFileInfo(FileInfo fileInfo);
}
class File1Service implements FileService {
#Override
public void saveFileInfo(FileInfo fileInfo) {
}
}
class File2Service implements FileService {
#Override
public void saveFileInfo(FileInfo fileInfo) { }
}
interface GenericFileService {
void saveFileInfo(FileInfo fileInfo);
}
class GenericFileServiceImpl implements GenericFileService {
private final List<FileService> candidateFileServices = new ArrayList<>();
public GenericFileServiceImpl() {
candidateFileServices.add(new File1Service());
candidateFileServices.add(new File2Service());
}
#Override
public void saveFileInfo(FileInfo fileInfo) {
candidateFileServices.forEach(fileService -> fileService.saveFileInfo(fileInfo));
}
}
If you must use generics, you need to modify it to ensure type safety:
interface GenericFileService {
<T extends FileInfo, R extends FileService<T>> void saveFileInfo(T fileInfo, R fileService);
FileService<? extends FileInfo> getFileService(String fileType);
}
class GenericFileServiceImpl implements GenericFileService {
private final List<FileService<? extends FileInfo>> candidateFileServices = new ArrayList<>();
public GenericFileServiceImpl() {
candidateFileServices.add(new MsdsFileService());
candidateFileServices.add(new NdgcFileService());
}
#Override
public FileService<? extends FileInfo> getFileService(String fileType) {
return candidateFileServices.stream()
.filter(service -> service.support(fileType))
.findFirst()
.orElseThrow(() -> {
throw new IllegalArgumentException("Unsupported fileType.");
});
}
#Override
public <T extends FileInfo, R extends FileService<T>> void saveFileInfo(T fileInfo, R fileService) {
fileService.saveFileInfo(fileInfo);
}
}
Related
I have the following classes
public class PathPart {
private final String pathPart;
public PathPart(String pathPart) {
this.pathPart = pathPart;
}
public String getPathPart() {
return pathPart;
}
}
public class Path {
private final List<? extends PathPart> pathParts;
public Path(String path) {
this.pathParts = getPathParts(path);
}
public List<? extends PathPart> getPathParts() {
return this.pathParts;
}
protected List<? extends PathPart> getPathParts(String path) {
return Arrays.stream(path.split("/")).map(PathPart::new).collect(Collectors.toList());
}
}
public class FooBarPathPart extends PathPart {
public FooBarPathPart(String pathPart) {
super(isFoo(pathPart) ? "bar" : pathPart);
}
private static boolean isFoo(String pathPart) {
return "foo".equals(pathPart);
}
}
public class FooBarPath extends Path {
public FooBarPath(String path) {
super(path);
}
#Override
public List<FooBarPathPart> getPathParts() {
// UNCHECKED CAST HERE
return (List<FooBarPathPart>) super.getPathParts();
}
#Override
protected List<FooBarPathPart> getPathParts(String path) {
return Arrays.stream(path.split("/")).map(FooBarPathPart::new).collect(Collectors.toList());
}
}
where I'd like to capture the structure of a filesystem path /my/path/to/a/directory in my Path object, which stores my, path, to, a, directory each as a PathPart object.
Now, I have a subclass of PathPart called FooBarPathPart, where if the path part is equal to foo, then I want it to change it to bar. And, I also have FooBarPath which is a subclass of Path, which stores a list of FooBarPathPart. So /my/path/to/foo/directory will essentially become /my/path/to/bar/directory
My issue is that I get an Unchecked cast warning from List<? extends PathPart> to List<FooBarPath> in my getPathParts() method in the FooBarPath class.
Is there a way to get rid of this unchecked cast warning properly? Is my usage of the wildcard correct here? Or is there a better way to approach this problem that doesn't involve the wildcard? I'm not very familiar with generics
Thanks for the help Thomas
I have solved the problem using generics and using a creator function. Here's the full solution:
public class PathPart {
private final String pathPart;
public PathPart(String pathPart) {
this.pathPart = pathPart;
}
public String getPathPart() {
return pathPart;
}
}
public class FooBarPathPart extends PathPart {
public FooBarPathPart(String pathPart) {
super(isFoo(pathPart) ? "bar" : pathPart);
}
private static boolean isFoo(String pathPart) {
return "foo".equals(pathPart);
}
}
public abstract class AbstractPath<T extends PathPart> {
private final List<T> pathParts;
private final Function<String, T> factory;
public AbstractPath(Function<String, T> factory, String path) {
this.factory = factory;
this.pathParts = createPathParts(path);
}
public List<T> createPathParts() {
return this.pathParts;
}
private List<T> createPathParts(String path) {
return Arrays.stream(path.split("/")).map(factory).collect(Collectors.toList());
}
}
public class Path extends AbstractPath<PathPart> {
public Path(String path) {
super(PathPart::new, path);
}
}
public class FooBarPath extends AbstractPath<FooBarPathPart> {
public FooBarPath(String path) {
super(FooBarPathPart::new, path);
}
}
Starting with these classes
public class Event {
private EventType eventType;
public EventType getEventType() {
return eventType;
}
public void setEventType(EventType eventType) {
this.eventType = eventType;
}
}
public class OneEvent extends Event {
public OneEvent() {
setEventType(EventType.ONE);
}
}
public interface EventProcessor<T extends Event> {
Stream<? extends Event> process(T element);
EventType getType();
}
public enum EventType {
ONE, TWO
}
I have created OneEventProcessor and TwoEventProcessor that implement it.
public class OneEventProcessor implements EventProcessor<OneEvent> {
#Override
public Stream<? extends Event> process(OneEvent element) {
return null;
}
#Override
public EventType getType() {
return EventType.ONE;
}
}
In another class I want to add them to a map type->processor and then retrieve and use.
Map<EventType, EventProcessor<? extends Event>> map = new HashMap<>();
OneEventProcessor oep = new OneEventProcessor();
map.put(oep.getType(), oep);
public Stream<? extends Event> process(Event event) {
return map.get(event.getEventType()).process(event);
}
This is not working because the map will retrieve a EventProcessor< ? extends Event > that expects a capture of < ? extends Event > in process method.
Any way I can work around this?
This can be achieved with few changes in your code.
Use OneEvent that you parametrize OneEventProcessor with:
class OneEventProcessor implements EventProcessor<OneEvent> {
#Override
public Stream<OneEvent> process(OneEvent element) {
return null;
}
#Override
public EventType getType() {
return EventType.ONE;
}
}
Use properly T that you parametrize EventProcessor with:
interface EventProcessor<T extends Event> {
Stream<T> process(T element);
EventType getType();
}
Add proper type bounds to your process() method (I included map initialisation in the body of that method for brevity):
public <T extends Event> Stream<T> process(T event) {
Map<EventType, EventProcessor<T>> map = new HashMap<>();
EventProcessor oep = new OneEventProcessor();
map.put(oep.getType(), oep);
return map.get(event.getEventType()).process(event);
}
I now came several times across this problem and always kinda solved this with some casts and #SuppressWarnings annotations.
The relevant interfaces / abstract classes:
public abstract class Data { }
public interface DataOperations {
boolean isValid();
}
public interface DataOperationsFactory<T extends Data> {
Class<T> getDataClass();
DataOperations getOperations(T data);
}
Example Implementations:
public class DataImpl1 extends Data {
public String foo;
}
public class DataImpl1Operations implements DataOperations {
private DataImpl1 data;
public DataImpl1Operations(DataImpl1 data) {
this.data = data;
}
public boolean isValid() {
return data.foo != null;
}
}
public class DataImpl1OperationsFactory extends DataOperationsFactory<DataImpl1> {
public Class<DataImpl1> getDataClass() {
return DataImpl1.class;
}
DataOperations getOperations(DataImpl1 data) {
return new DataImpl1Operations(data);
}
}
Using this pattern, I can decide whether I need to create a new DataImpl1Operations everytime. Or maybe use a final static NO_OP implementation or what have you.
The Code:
Now I'd like to put all those factories inside a Map<Class<T>, DataOperationsFactory<T>> (constructor). And afterwards read from it (getOps method).
public class Test {
Map<Class<?>, DataOperationsFactory<?>> map;
public Test(List<DataOperationsFactory<?>> fs) {
for(DataOperationsFactory<?> f : fs) {
map.put(f.getDataClass(), f);
}
}
#SuppressWarnings("unchecked")
public <T extends Data> DataOperations getOps(T data) {
// --> Here I need to do an unchecked cast <--
DataOperationsFactory<? super T> f =
(DataOperationsFactory<? super T>) map.get(data.getClass());
return f.getOperations(data);
}
}
Is there any way doing this without unchecked casting?
You can delegate to a private method that captures the type, so it can be used to reliably cast to the correct Data subclass:
Map<Class<?>, DataOperationsFactory<?>> map;
// Unchanged
public Test(List<DataOperationsFactory<?>> fs) {
for(DataOperationsFactory<?> f : fs) {
map.put(f.getDataClass(), f);
}
}
public DataOperations getOps(Data data) {
DataOperationsFactory<?> f = map.get(data.getClass());
return getOperations(f, data);
}
private static <T extends Data> DataOperations getOperations(DataOperationsFactory<T> f,
Data data) {
return f.getOperations(f.getDataClass().cast(data));
}
I have a generic interface Handler
public interface Handler<T> {
void handle(T obj);
}
I can have n implementations of this interface. Let's say I have following 2 implementations for now. One which handles String objects and another handles Date
public class StringHandler implements Handler<String> {
#Override
public void handle(String str) {
System.out.println(str);
}
}
public class DateHandler implements Handler<Date> {
#Override
public void handle(Date date) {
System.out.println(date);
}
}
I want to write a factory which will return handler instances based on the class type. Something like this :
class HandlerFactory {
public <T> Handler<T> getHandler(Class<T> clazz) {
if (clazz == String.class) return new StringHandler();
if (clazz == Date.class) return new DateHandler();
}
}
I get following error in this factory :
Type mismatch: cannot convert from StringHandler to Handler<T>
How to fix this?
SIMPLE SOLUTION
You could save your mappings Class<T> -> Handler<T> in a Map. Something like:
Map<Class<T>, Handler<T>> registry = new HashMap<>();
public void registerHandler(Class<T> dataType, Class<? extends Handler> handlerType) {
registry.put(dataType, handlerType);
}
public <T> Handler<T> getHandler(Class<T> clazz) {
return registry.get(clazz).newInstance();
}
In some place, initialize handlers (could be in the factory itself):
factory.registerHandler(String.class, StringHandler.class);
factory.registerHandler(Date.class, DateHandler.class);
And in another place, you create and use them:
Handler<String> stringhandler = factory.getHandler(String.class);
Handler<Date> dateHandler = factory.getHandler(Date.class);
MORE COMPLEX SOLUTION
You can "scan" classes using reflection and, instead of register manually the mappings Class<T> -> Handler<T>, do it using reflection.
for (Class<? extends Handler> handlerType : getHandlerClasses()) {
Type[] implementedInterfaces = handlerType.getGenericInterfaces();
ParameterizedType eventHandlerInterface = (ParameterizedType) implementedInterfaces[0];
Type[] types = eventHandlerInterface.getActualTypeArguments();
Class dataType = (Class) types[0]; // <--String or Date, in your case
factory.registerHandler(dataType, handlerType);
}
Then, you create and use them like above:
Handler<String> stringhandler = factory.getHandler(String.class);
Handler<Date> dateHandler = factory.getHandler(Date.class);
To implement getHandlerClasses(), look at this to scan all classes in your jar. For each class, you have to check if it is a Handler:
if (Handler.class.isAssignableFrom(scanningClazz) //implements Handler
&& scanningClazz.getName() != Handler.class.getName()) //it is not Handler.class itself
{
//is a handler!
}
Hope it helps!
Your problem is that the compiler cannot make the leap to the fact thet the type of the result is correct.
To help the compiler you can make the factory delegate the construction. Although this looks strange and unwieldly it does manage to properly maintain type safety without sacrifices such as casting or using ? or raw types.
public interface Handler<T> {
void handle(T obj);
}
public static class StringHandler implements Handler<String> {
#Override
public void handle(String str) {
System.out.println(str);
}
}
public static class DateHandler implements Handler<Date> {
#Override
public void handle(Date date) {
System.out.println(date);
}
}
static class HandlerFactory {
enum ValidHandler {
String {
#Override
Handler<String> make() {
return new StringHandler();
}
},
Date {
#Override
Handler<Date> make() {
return new DateHandler();
}
};
abstract <T> Handler<T> make();
}
public <T> Handler<T> getHandler(Class<T> clazz) {
if (clazz == String.class) {
return ValidHandler.String.make();
}
if (clazz == Date.class) {
return ValidHandler.Date.make();
}
return null;
}
}
public void test() {
HandlerFactory factory = new HandlerFactory();
Handler<String> stringHandler = factory.getHandler(String.class);
Handler<Date> dateHandler = factory.getHandler(Date.class);
}
The whole point of using a generic type is to share the implementation. If the n implementation of your Handler interface are so different that they can't be shared, then I don't think there is any reason to use define that generic interface at the first place. You'd rather just have StringHandler and DateHandler as top level classes.
On the other hand, if the implementation can be shared, as is the case of your example, then the factory works naturally:
public class Main {
static public interface Handler<T> {
void handle(T obj);
}
static public class PrintHandler<T> implements Handler<T> {
#Override
public void handle(T obj) {
System.out.println(obj);
}
}
static class HandlerFactory {
public static <T> Handler<T> getHandler() {
return new PrintHandler<T>();
}
}
public static void main(String[] args) {
Handler<String> stringHandler = HandlerFactory.getHandler();
Handler<Date> dateHandler = HandlerFactory.getHandler();
stringHandler.handle("TEST");
dateHandler.handle(new Date());
}
}
You can use something like:
class HandlerFactory {
public <T> Handler<T> getHandler(Class<T> clazz) {
if (clazz.equals(String.class)) return (Handler<T>) new StringHandler();
if (clazz.equals(Date.class)) return (Handler<T>) new DateHandler();
return null;
}
}
T is generic and the compiler can't map that at compile time. Also it is safer to use .equals instead of ==.
Define an interface for creating an object, but let subclasses decide which class to instantiate.
Factory method lets a class defer instantiation to subclasses.
Define generic abstract class
public abstract class Factory<T> {
public abstract T instantiate(Supplier<? extends T> supplier);
}
And a generic supplier
public class SupplierFactory<T> extends Factory<T> {
#Override
public T instantiate(Supplier<? extends T> supplier) {
return supplier.get();
}
}
Then an implementation needs to have concrete classes to implement the base interface and a main class to show class defer instantiation . i.e
The base interface (desired interface of the requirement)
public interface BaseInterface {
void doAction();
}
The first concrete class
public class Alpha implements BaseInterface {
#Override
public void doAction() {
System.out.println("The Alpha executed");
}
}
And the second one
public class Beta implements BaseInterface {
#Override
public void doAction() {
System.out.println("The Beta executed");
}
}
The main
public class Main {
public static void main(String[] args) {
Factory<BaseInterface> secondFactory = new SupplierFactory<>();
secondFactory.instantiate(Beta::new).doAction();
secondFactory.instantiate(Alpha::new).doAction();
}
}
Basically you can do:
public Handler getHandler( Class clazz ){
if( clazz == String.class ) return new StringHandler();
if( clazz == Date.class ) return new DateHandler();
return null;
}
public static void main( String[] args ){
HandlerFactory handlerFactory = new HandlerFactory();
StringHandler handler = ( StringHandler )handlerFactory.getHandler( String.class );
handler.handle( "TEST" );
DateHandler handler2 = ( DateHandler )handlerFactory.getHandler( Date.class );
handler2.handle( new Date() );
}
Output:
TEST
Tue Dec 15 15:31:00 CET 2015
But instead writing two different methods to get handlers separately always is a better way.
I edited your code and allowed Eclipse to "fix" the errors and it came up with this.
public Handler<?> getHandler(Class<?> clazz) {
if (clazz == String.class)
return new StringHandler();
if (clazz == Date.class)
return new DateHandler();
return null;
}
Yout HandlerFactory don't know about T. Use your factory like below-
public class HandlerFactory {
public Handler<?> getHandler(Class<?> clazz) {
if (clazz == String.class) {
return new StringHandler();
}
if (clazz == Date.class) {
return new DateHandler();
}
return null;
}
}
i would like to get rid of these warnings about unchecked conversion and parameterization without surpressing them.
interface Switch {
void toggle();
}
enum A implements Switch {
a1,a2;
#Override public void toggle() {
state=!state;
}
boolean state;
}
enum B implements Switch {
b1,b2;
#Override public void toggle() {
state=!state;
}
boolean state;
}
public class Warnings {
public static void main(String[] args) {
Class<? extends Enum>[] enums=new Class[]{A.class,B.class};
for(Class<? extends Enum> clazz:enums)
try {
Enum s=Enum.valueOf(clazz,args[0]);
((Switch)s).toggle();
} catch(IllegalArgumentException eee) {}
}
}
You can't without writing your own valueOf. Enum is defined as:
class Enum<E extends Enum<E>>
and Enum.valueOf is defined as:
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name)
Note the recursive type parameterization which implies that you can only call valueOf with a specific enum class (e.g. A.class), but not with a generic one, as a Class<? extends Enum<?>> is not a match because the two question marks aren't assumed to represent the same (unknown) type by the compiler.
So apart from using generic collections instead of arrays, you have to write your own valueOf method that accepts any enum class.
public class Warnings {
public static void main(final String[] args) {
List<Class<? extends Enum<?>>> enums = new ArrayList<Class<? extends Enum<?>>>();
enums.add(A.class);
enums.add(B.class);
for (Class<? extends Enum<?>> clazz : enums) {
try {
Switch s = valueOf(clazz, args[0]);
s.toggle();
} catch (IllegalArgumentException eee) {
}
}
}
private static Switch valueOf(final Class<? extends Enum<?>> enumClass, final String name) {
Enum<?>[] enumConstants = enumClass.getEnumConstants();
for (Enum<?> constant : enumConstants) {
if (constant.name().equals(name)) {
return (Switch) constant;
}
}
throw new IllegalArgumentException(name + " is not a constant of enum class " + enumClass.getName());
}
}
Don't mix arrays and generics. They do not work well together because generics in java is implemented using type erasure.
This should work.
interface Switch {
void toggle();
}
enum A implements Switch {
a1, a2;
#Override
public void toggle() {
state = !state;
}
boolean state;
}
enum B implements Switch {
b1, b2;
#Override
public void toggle() {
state = !state;
}
boolean state;
}
public class Test {
public static void main(String[] args) {
List<Class<? extends Switch>> enums = new ArrayList<Class<? extends Switch>>();
enums.add(A.class);
enums.add(B.class);
for (Class<? extends Switch> clazz : enums)
try {
Switch s = clazz.getEnumConstants()[0];
((Switch) s).toggle();
} catch (IllegalArgumentException eee) {
}
}
}