Is it possible to do this using Predicate interface.
I have a client class that utilizes functions provided by a MathUtility class. Whatever the Mathmatical operation it should happen only within the MathUtility class.
//in client
MathUtility.sum(listOfInts, (Integer i)->{return (i<3);});
//in utility
class MathUtility<T extends Number> {
public static <T extends Number> T sumWithCondition(List<T> numbers, Predicate<T> condition) {
return numbers.parallelStream()
.filter(condition)
.map(i -> i)
.reduce(0, T::sum); //compile time error
}
public static <T extends Number> T avgWithCondition(List<T> numbers, Predicate<T> condition) {
//another function
}
//lot many functions go here
}
Right now it fails with this error - The method reduce(T, BinaryOperator<T>) in the type Stream<T> is not applicable for the arguments (int, T::sum)
Note: I do not want to write sum functions for different Number types
EDIT: Detailed discussion on this topic covered in this Github Notebook
Is there a way to do it without writing a sum function for every possible type of T that i'm expecting?
As Aaron Davis stated in a comment above, you can pass the reduction parameters to the method itself.
public static <T> T sumWithCondition(List<T> numbers, Predicate<T> condition, T identity, BinaryOperator<T> accumulator) {
return numbers.parallelStream().filter(condition).reduce(identity, accumulator);
}
An example would be:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(sumWithCondition(list, i -> i > 1, 0, (a, b) -> a + b));
>> 14
List<BigInteger> list2 = Arrays.asList(BigInteger.ONE, BigInteger.ONE);
System.out.println(sumWithCondition(list2, i -> true, BigInteger.ZERO, (a, b) -> a.add(b)));
>> 2
you must point out which actual type of Number to be summed, Since the Number class has no static sum method.
you must assign identity with type of T extends Number,0 is an concrete type of Integer and does not compatible with type of T.
Possible Solution
you can make which actual type of Number to be summed later, for example:
Integer sumToInt = MathUtility.sum(numbers, condition).as(Integer.class);
Double sumToDouble = MathUtility.sum(numbers, condition).as(Double.class);
OR you can make which actual type of Number to be summed ahead, when using this style you are free to take type of actual Number to every sum to be called, one the other hand, you can reuse it without taking any confused parameters and which is exactly what you want,for example:
SumOp<Integer> sumIntOp = SumOp.of(Integer.class);
//sumIntOp is reused twice.
Integer sumToInt1 = sumIntOp.sum(numbers1, condition1);
Integer sumToInt2 = sumIntOp.sum(numbers2, condition2);
MathUtility
class MathUtility {
private static <T extends Number> Sum sum(List<T> numbers,
Predicate<T> condition) {
return sum(numbers.parallelStream().filter(condition));
}
private static <T extends Number> Sum sum(Stream<T> stream) {
return new Sum() {
public <T extends Number> T as(Class<T> type) {
return SumOp.of(type).sum(stream);
}
};
}
interface Sum {
<T extends Number> T as(Class<T> type);
}
}
SumOp
public class SumOp<T extends Number> {
private static final Map<Class<?>, SumOp<?>> OPERATORS = new HashMap<>();
private final T identity;
private final BinaryOperator<T> plusOp;
private final Function<Number, T> valueExtractor;
static {
register(Integer.class, new SumOp<>(0, Integer::sum, Number::intValue));
register(Double.class, new SumOp<>(0., Double::sum, Number::doubleValue));
//todo: add more SumOp for other Number types
}
public static <T extends Number> void register(Class<T> type,
SumOp<T> sumOp) {
OPERATORS.put(type, sumOp);
}
public static <T extends Number> SumOp<T> of(Class<T> type) {
return (SumOp<T>) OPERATORS.computeIfAbsent(type, it -> {
String message = "No SumOp registered for type:" + type.getName();
throw new IllegalArgumentException(message);
});
}
public SumOp(T identity,
BinaryOperator<T> plusOp,
Function<Number, T> valueExtractor) {
this.identity = identity;
this.valueExtractor = valueExtractor;
this.plusOp = plusOp;
}
public <I extends Number> T sum(List<I> numbers,
Predicate<I> condition) {
return sum(numbers.stream().filter(condition));
}
public T sum(Stream<? extends Number> stream) {
return stream.reduce(identity, this::plus, plusOp);
}
private T plus(Number augend, Number addend) {
return plusOp.apply(valueIn(augend), valueIn(addend));
}
private T valueIn(Number it) {
return valueExtractor.apply(it);
}
}
A much simpler approach I tired is this.
The point to be noted is that the addition logic doesn't happen at the invoking side instead only within the MathUtility.
The downside here is that you have to create Addition classes for every Number type you want the + operation.
System.out.println(
MathUtility.sum(listOfInts, i->i<4, new MathUtility.IntegerAddition()).get()
);
class MathUtility<T extends Number> {
static class IntegerAddition implements BinaryOperator<Integer> {
#Override
public Integer apply(Integer t, Integer u) {
return t + u;
}
}
public static <T extends Number> Optional<T> sum(List<T> list, Predicate<T> condition, BinaryOperator<T> operation){
//ability to add is only here
return list.parallelStream()
.filter(condition)
.map(i -> i)
.reduce(operation);
}
}
The answer is yes, that should be possible. The you defined is not known to have the method "sum", therefore the compiler complains. Try to define
public interace SumInterface {
public int sum(int a, int b);
}
(I haven't tried this code in IDE but this should do the trick)
Related
What's the difference between
? extends Stream<? extends R>
and
Stream<? extends R>
Is <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper) same as <R> Stream<R> flatMap(Function<? super T, Stream<? extends R>> mapper).
Could you give some examples?
Short answer: ? extends SomeType is very different than SomeType.
Longer answer:
The use of Stream<extends R> as SomeType confuses the example. To answer the question, consider a simpler comparison, for example:
? extends Integer
Integer
The first observation is that ? extends Integer as a type expression can be used only within generic declarations. On the other hand, Integer can be used in many more places.
Here are some code examples to help show the difference:
// The following do not compile:
//
// Syntax error on tokens, delete these tokens ('? extends').
//
// private Integer value;
// public ? extends Integer getValue() {
// return value;
// }
//
// Syntax error on tokens, delete these tokens ('? extends').
//
// private Integer value;
// public void setInteger(? extends Integer value) {
// this.value = value;
// }
//
// In both cases, the use of bounded quantification ('? extends') is
// not valid outside of a generic type declaration.
// Here is a very basic example of the use of generics:
//
// Generic 'Wrapped' is quantified on a single type, which
// is unrestricted:
public class Wrapped<T> {
Wrapped() { this.value = null; }
Wrapped(T value) { this.value = value; }
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
// The following is also valid:
//
// 'Wrapped_1' is quantified on a single type, which is restricted
// to be 'Integer' or a subtype of 'Integer'.
public class Wrapped_1<T extends Integer> {
// EMPTY
}
// This is not valid. In addition to being non-valid syntacticly,
// the declaration needs a specific type. Use of a wildcard does
// not provide a specific type.
//
// Syntax error on token "?", Identifier expected
//
// public class Wrapped_2<? extends Integer> {
// }
// The following does not compile:
//
// Cannot instantiate the type Wrapped<? extends Integer>.
// Type mismatch: cannot convert from Wrapped<? extends Integer> to Wrapped<Integer>.
//
// private Wrapped<Integer> Wrapped0 = new Wrapped<? extends Integer>();
// These show the difference in effect between the declarations
// 'Integer' and '? extends Integer'.
private Wrapped<Integer> wrapped_1 = new Wrapped<Integer>( new Integer(4) );
private Wrapped<? extends Integer> wrapped_2 = wrapped_1;
{
// This compiles:
wrapped_1.set( new Integer(5) );
// This does not compile:
//
// The method set(capture#1-of ? extends Integer)
// in the type Wrapped<capture#1-of ? extends Integer>
// is not applicable for the arguments (Integer)
//
// wrapped2.set( new Integer(6) );
}
let say R = Number
Stream<? extends R> can be Stream<Integer> Stream<Long>
But
? extends Stream<? extends R> says which implements Stream<Number> or Stream<Integer> and so on....
class XXXX implements Stream<Integer>{} ...
XXXX overrides all the Stream abstract methods
I ask for something which I see impossible and I'll delete question if it is.
I have got method:
public Object convertBy(Function... functions) {
}
and those functions are :
interface FLines extends Function {
#Override
default Object apply(Object t) {
return null;
};
public List<String> getLines(String fileName);
}
interface Join extends Function {
#Override
default Object apply(Object t) {
return null;
};
public String join(List<String> lines);//lines to join
}
interface CollectInts extends Function {
#Override
default Object apply(Object t) {
return null;
};
public List<Integer> collectInts(String s);
}
interface Sum<T, R> extends Function<T, R> {
#Override
default Object apply(Object t) {
return null;
};
public R sum(T list);//list of Integers
}
Abstract methods in those interfaces return values of different types. I pass lambdas to my convertBy method.
I would like to set convertBy return type the same as return type of functions[functions.length - 1].
Is this is possible?
EDIT:
I've changed the signature of the method and the signature of the methods inside the interface. It works but only if I do cast in the marked places in the main posted below. The weird things it needs cast only in 3 out of 4 method's invocations, I would like to get rid of casts at all in the main.
import java.util.List;
import java.util.function.Function;
public class InputConverter<T> {
private T value;
public InputConverter(T value) {
this.value = value;
}
public <T, R> R convertBy(Function<T, R> special, Function... functions) {
if (functions.length == 0) {
FLines flines = (FLines) special;
return (R) flines.getLines((value instanceof String) ? (String) value : null);
} else if (functions.length == 1) {
FLines flines = (FLines) functions[0];
Join join = (Join) special;
return (R) join.join(flines.getLines((String) value));
} else if (functions.length == 2) {
if (functions[0] instanceof FLines) {
FLines flines = (FLines) functions[0];
Join join = (Join) functions[1];
CollectInts collectInts = (CollectInts) special;
return (R) collectInts.collectInts(join.join(flines.getLines((String) value)));
} else {
Join join = (Join) functions[0];
CollectInts collectInts = (CollectInts) functions[1];
Sum sum = (Sum) special;
return (R) sum.sum(collectInts.collectInts(join.join((List<String>) value)));
}
} else {
FLines flines = (FLines) functions[0];
Join join = (Join) functions[1];
CollectInts collectInts = (CollectInts) functions[2];
Sum sum = (Sum) special;
return (R) sum.sum(collectInts.collectInts(join.join(flines.getLines((String) value))));
}
}
/* public Integer convertBy(Join join, CollectInts collectInts, Sum sum) {
return sum.sum(collectInts.collectInts(join.join((List<String>) value)));
}*/
}
interface FLines<T, R> extends Function {
#Override
default Object apply(Object t) {
return null;
};
public R getLines(T fileName);
// public List<String> getLines(String fileName);
}
interface Join<T,R> extends Function {
#Override
default Object apply(Object t) {
return null;
};
public R join(T lines);//lines to join
// public String join(List<String> lines);//lines to join
}
interface CollectInts<T, R> extends Function {
#Override
default Object apply(Object t) {
return null;
};
public R collectInts(T t);
// public List<Integer> collectInts(String s);
}
interface Sum<T, R> extends Function<T, R> {
#Override
default Object apply(Object t) {
return null;
};
public R sum(T list);//list of Integers
}
The main method:
FLines<String, List<String>> flines ....
Join<List<String>, String> join ...
CollectInts<String, List<Integer>> collectInts ...
Sum<List<Integer>, Integer> sum ...
String fname =/* System.getProperty("user.home") + "/*/ "LamComFile.txt";
InputConverter<String> fileConv = new InputConverter<>(fname);
List<String> lines = fileConv.convertBy(flines);//cannot cast from Object to List<String>
String text = fileConv.convertBy( join, flines);//cannot cast from Object to String
List<Integer> ints = fileConv.convertBy(collectInts,flines, join);//cannot cast from Object to List<Integer>
Integer sumints = fileConv.convertBy(sum, flines, join, collectInts);//works without cast!
I don't understand why compiler understands what sum returns but don't infer what for instance collectInts returns.
It seems, you have some misunderstanding about generic type hierarchies. When you want to extend a generic type, you have to make a fundamental decision about the actual types of the extended class or interface. You may specify exact types like in
interface StringTransformer extends Function<String,String> {}
(here we create a type that extends a generic type but is not generic itself)
or you can create a generic type which uses its own type parameter for specifying the actual type argument of the super class:
interface NumberFunc<N extends Number> extends Function<N,N> {}
Note, how we create a new type parameter N with its own constraints and use it to parametrize the superclass to require its type parameters to match ours.
In contrast, when you declare a class like
interface FLines<T, R> extends Function
you are extending the raw type Function and create new type parameters <T, R> which are entirely useless in your scenario.
To stay at the above examples, you may implement them as
StringTransformer reverse = s -> new StringBuilder(s).reverse().toString();
NumberFunc<Integer> dbl = i -> i*2;
and since they inherit properly typed methods, you may use these to combine the functions:
Function<String,Integer> f = reverse.andThen(Integer::valueOf).andThen(dbl);
System.out.println(f.apply("1234"));
Applying this to your scenario, you could define the interfaces like
interface FLines extends Function<String,List<String>> {
#Override default List<String> apply(String fileName) {
return getLines(fileName);
}
public List<String> getLines(String fileName);
}
interface Join extends Function<List<String>,String> {
#Override default String apply(List<String> lines) {
return join(lines);
}
public String join(List<String> lines);
}
interface CollectInts extends Function<String,List<Integer>> {
#Override default List<Integer> apply(String s) {
return collectInts(s);
}
public List<Integer> collectInts(String s);
}
interface Sum extends Function<List<Integer>, Integer> {
#Override default Integer apply(List<Integer> list) {
return sum(list);
}
public Integer sum(List<Integer> list);
}
and redesign your InputConverter to accept only one function which may be a combined function:
public class InputConverter<T> {
private T value;
public InputConverter(T value) {
this.value = value;
}
public <R> R convertBy(Function<? super T, ? extends R> f) {
return f.apply(value);
}
}
This can be used in a type safe manner:
FLines flines = name -> {
try { return Files.readAllLines(Paths.get(name)); }
catch(IOException ex) { throw new UncheckedIOException(ex); }
};
Join join = list -> String.join(",", list);
CollectInts collectInts=
s -> Arrays.stream(s.split(",")).map(Integer::parseInt).collect(Collectors.toList());
Sum sum = l -> l.stream().reduce(0, Integer::sum);
InputConverter<String> fileConv = new InputConverter<>("LamComFile.txt");
List<String> lines = fileConv.convertBy(flines);
String text = fileConv.convertBy(flines.andThen(join));
List<Integer> ints = fileConv.convertBy(flines.andThen(join).andThen(collectInts));
Integer sumints = fileConv.convertBy(
flines.andThen(join).andThen(collectInts).andThen(sum)
);
You have to change the method signature and inline the last vararg value as a separate parameter.
If you have this parameter as the last one, then you won't be able a use vararg parameter, as it has always to be last one and must be represented as an array in case it's not the last one:
public <T, R> R convertBy(Function[] functions, Function<T, R> special) { }
If you, however, insist to use varargs, then you can move the "special" Function as first parameter:
public <T, R> R convertBy(Function<T, R> special, Function... functions) { }
Thank all of you who elaborated on the subject, your solutions are much better in real world.
As the author I would like to post my solution that enabled not changing the invocations of convertBy() int the main() one bit. It is very short and ugly, but works.
Main:
Function<String, List<String>> flines ... lambda here
Function<List<String>, String> join ... lambda here
Function<String, List<Integer>> collectInts ... lambda here
Function<List<Integer>, Integer> sum ... lambda here
String fname = System.getProperty("user.home") + "/LamComFile.txt";
InputConverter<String> fileConv = new InputConverter<>(fname);
List<String> lines = fileConv.convertBy(flines);
String text = fileConv.convertBy(flines, join);
List<Integer> ints = fileConv.convertBy(flines, join, collectInts);
Integer sumints = fileConv.convertBy(flines, join, collectInts, sum);
System.out.println(lines);
System.out.println(text);
System.out.println(ints);
System.out.println(sumints);
List<String> arglist = Arrays.asList(args);
InputConverter<List<String>> slistConv = new InputConverter<>(arglist);
sumints = slistConv.convertBy(join, collectInts, sum);
System.out.println(sumints);
The InputConverter class:
public class InputConverter<T> {
private T value;
public InputConverter(T value) {
this.value = value;
}
public <T, R> R convertBy(Function... functions) {
Object result = value;
for (int i = 0; i < functions.length; i++) {
result = functions[i].apply(result);
}
return (R) result;
}
}
In several spots in my code I have ArrayLists and TreeSets whose generic type I wish to convert. So for example I have an ArrayList<Integer> which I wish to convert to an ArrayList<Long>. Or I have a TreeSet<BigInteger> which I wish to convert to a TreeSet<String>.
All of these conversions can be made, but then I have to create for each type conversion a different function. Therefore I want to create a generic function whose signature looks something like this:
public static <Q,T> Collection<Q> convert(Collection<T> col, Class<Q> Q)
What I want is to get the class from col (e.g. ArrayList), create a new collection of that class and type Q (called newCol), and then iterate through col and convert each element which is of type T to type Q and add it to newCol and lastly return newCol.
How can I do this?
There's no special mechanism like casting of incompatible classes in Java. You need to specify an explicit function which will perform a conversion. Using Java 8 it's really easy:
public static <Q,T,C extends Collection<Q>> C convert(Collection<T> col, Function<T, Q> fn,
Supplier<C> supplier) {
return col.stream().map(fn).collect(Collectors.toCollection(supplier));
}
Use it like this:
TreeSet<BigInteger> values = // fill them somehow
TreeSet<String> converted = convert(values, BigInteger::toString, TreeSet::new);
#Tagir Valeev is right. You can do it easily in Java 8. But if you use Java 7, you can try to do something like this:
public static <F, T> Collection<T> transform(Collection<F> fromCollection, Function<? super F, T> function) {
return new TransformedCollection<F, T>(fromCollection, function);
}
static class TransformedCollection<F, T> extends AbstractCollection<T> {
final Collection<F> fromCollection;
final Function<? super F, ? extends T> function;
TransformedCollection(Collection<F> fromCollection, Function<? super F, ? extends T> function) {
this.fromCollection = checkNotNull(fromCollection);
this.function = checkNotNull(function);
}
#Override public void clear() {
fromCollection.clear();
}
#Override public boolean isEmpty() {
return fromCollection.isEmpty();
}
#Override public Iterator<T> iterator() {
return Iterators.transform(fromCollection.iterator(), function);
}
#Override public int size() {
return fromCollection.size();
}
}
It's code from Guava library.
I've made an interface of math operation with one method, calculate, taking various number of arguments
public interface MathOperation {
public <T extends Number> T calculate(T... args);
}
There's also simple implementation of this class, which does not work:
private class Sum implements MathOperation {
#Override
public <T extends Number> T calculate(T... args) {
return args[0] + args[1];
}
}
The problem is:
bad operand types for binary operator '+'
first type: T
second type: T
where T is a type-variable:
T extends Number declared in method <T>calculate(T...)
What I'm trying to achieve is a simple class, taking for example two Doubles and returning Double as well.
Is there possibility to achieve this?
+ cannot be applied to types that extend Number. new Integer(5) + new Integer(5) works because of autoboxing. You will have to look at the runtime type of args and do the operation accordingly.
Something on the lines of:
private class Sum implements MathOperation {
#Override
public <T extends Number> T calculate(Class<T> clazz, T... args) {
if (clazz.equals(Integer.class))
{
return Integer.class.cast(args[0]) + Integer.class.cast(args[1]);
} else (....)
}
}
For Addition we can use doubleValue() method of Number class. To return the same type value, the idea is to use a Function or Supplier or a Factory to create instances of the type T.
class MathOperation<T extends Number> {
public double add(T a, T b) {
double d = a.doubleValue() + b.doubleValue();
return d;
}
public T add(T a, T b, Function<Double,T> function) {
double d = a.doubleValue() + b.doubleValue();
return function.apply(d);
}
}
You can test the runtime type as shown in the other answers. Or you can try a different design: Create an abstract class that works as a factory:
interface MathContext<T extends Number> {
...
T valueOf(double d);
T valueOf(int i);
T add (T... args);
}
And concrete classes for the types that you want to use:
DoubleContext implements MathContext<Double> {
...
Double valueOf(int i) {
return i;
}
Double valueOf(double d) {
return d;
}
Double add(Double... args) {
Double res = 0;
for (Double arg: args) {
res += arg;
}
return res;
}
}
Now you could implement your MathOperation using that class. However, it's not really needed any more.
I was wondering if it's possible to write a function that accepts multiple generic types as follows:
public int void myfunction(Set<T> a, Set<T> b) {
return 5;
}
Set<Integer> setA = new HashSet<Integer>();
Set<String> setB = new HashSet<String>();
int result = myfunction(setA, setB);
Will that work? Does the generic in each parameter mean that each parameter must have the same type T that's generic?
Yes - it's possible (though not with your method signature) and yes, with your signature the types must be the same.
With the signature you have given, T must be associated to a single type (e.g. String or Integer) at the call-site. You can, however, declare method signatures which take multiple type parameters
public <S, T> void func(Set<S> s, Set<T> t)
Note in the above signature that I have declared the types S and T in the signature itself. These are therefore different to and independent of any generic types associated with the class or interface which contains the function.
public class MyClass<S, T> {
public void foo(Set<S> s, Set<T> t); //same type params as on class
public <U, V> void bar(Set<U> s, Set<V> t); //type params independent of class
}
You might like to take a look at some of the method signatures of the collection classes in the java.util package. Generics is really rather a complicated subject, especially when wildcards (? extends and ? super) are considered. For example, it's often the case that a method which might take a Set<Number> as a parameter should also accept a Set<Integer>. In which case you'd see a signature like this:
public void baz(Set<? extends T> s);
There are plenty of questions already on SO for you to look at on the subject!
Java Generics: List, List<Object>, List<?>
Java Generics (Wildcards)
What are the differences between Generics in C# and Java... and Templates in C++?
Not sure what the point of returning an int from the function is, although you could do that if you want!
Even more, you can inherit generics :)
#SuppressWarnings("unchecked")
public <T extends Something<E>, E extends Enum<E> & SomethingAware> T getSomething(Class<T> clazz) {
return (T) somethingHolderMap.get(clazz);
}
You can follow one of the below approaches:
1) Basic, single type :
//One type
public static <T> void fill(List <T> list, T val) {
for(int i=0; i<list.size(); i++){
list.set(i, val);
}
}
2) Multiple Types :
// multiple types as parameters
public static <T1, T2> String multipleTypeArgument(T1 val1, T2 val2) {
return val1+" "+val2;
}
3) Below will raise compiler error as 'T3 is not in the listing of generic types that are used in function declaration part.
//Raised compilation error
public static <T1, T2> T3 returnTypeGeneric(T1 val1, T2 val2) {
return 0;
}
Correct : Compiles fine
public static <T1, T2, T3> T3 returnTypeGeneric(T1 val1, T2 val2) {
return 0;
}
Sample Class Code :
package generics.basics;
import java.util.ArrayList;
import java.util.List;
public class GenericMethods {
/*
Declare the generic type parameter T in this method.
After the qualifiers public and static, you put <T> and
then followed it by return type, method name, and its parameters.
Observe : type of val is 'T' and not '<T>'
* */
//One type
public static <T> void fill(List <T> list, T val) {
for(int i=0; i<list.size(); i++){
list.set(i, val);
}
}
// multiple types as parameters
public static <T1, T2> String multipleTypeArgument(T1 val1, T2 val2) {
return val1+" "+val2;
}
/*// Q: To audience -> will this compile ?
*
* public static <T1, T2> T3 returnTypeGeneric(T1 val1, T2 val2) {
return 0;
}*/
public static <T1, T2, T3> T3 returnTypeGeneric(T1 val1, T2 val2) {
return null;
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
System.out.println(list.toString());
fill(list, 100);
System.out.println(list.toString());
List<String> Strlist = new ArrayList<>();
Strlist.add("Chirag");
Strlist.add("Nayak");
System.out.println(Strlist.toString());
fill(Strlist, "GOOD BOY");
System.out.println(Strlist.toString());
System.out.println(multipleTypeArgument("Chirag", 100));
System.out.println(multipleTypeArgument(100,"Nayak"));
}
}
// class definition ends
Sample Output:
[10, 20]
[100, 100]
[Chirag, Nayak]
[GOOD BOY, GOOD BOY]
Chirag 100
100 Nayak
You can declare multiple type variables on a type or method. For example, using type parameters on the method:
<P, Q> int f(Set<P>, Set<Q>) {
return 0;
}
a and b must both be sets of the same type. But nothing prevents you from writing
myfunction(Set<X> a, Set<Y> b)
In your function definition you're constraining sets a and b to the same type. You can also write
public <X,Y> void myFunction(Set<X> s1, Set<Y> s2){...}