confusion with Java wildcards - java

Please explain to me why I have one example that compiles and the other one doesn't compile
This is example that compiles
import java.util.ArrayList;
public class MyClass1 <T> {
public ArrayList<MyClass1<?>> lst;
public MyClass1()
{
lst = new ArrayList<MyClass1<?>>();
}
public static void main(String[] args) {
M(new MyClass1<Double>());
}
public static <T1> void M(MyClass1<T1> t1)
{
var d0 = new MyClass1<Double>();
d0.lst.add(t1);
}
}
But that doesn't compiles
import java.util.ArrayList;
public class MyClass1 <T> {
public ArrayList<?> lst;
public MyClass1()
{
}
public static void main(String[] args) {
M(Double.valueOf(1.1));
}
public static <T1> void M(T1 t1)
{
var d0 = new MyClass1<Double>();
d0.lst.add(t1); // error — java: incompatible types: T1 cannot be converted to capture#1 of ?
}
}
Why in the first case I have wildcard, and everything is OK, but in the second case I have wildcard and it doesn't compile

The second snippet can not be compiled because the generics in Java are about type safety(type guarantee). Thus, if we are declearing List<Integer> the compiler is convinced, that the list will contain only integers. But when we have List<?> (read this like list of some type), compiler cant predict what will the list contain. Today we are adding Integers, tomorrow somebody else will add a ServerSocket. In runtime if we will try to get a value from such a list, we will get a ClassCastException. That is why this code cant even be compiled - to prevent such situations. Read about type erasure and bridge methods.
In your concrete case, i guess, you would like to have something like this:
public class MyClass<T> {
public ArrayList<T> lst;
public static void main(String[] args) {
var obj = new MyClass<Double>();
obj.foo(1.1);
}
public void foo(T s) {
lst.add(s);
}
}
The question mark also can be useful:
static void m(List<? extends Shape> list) {
for (Shape el : list) {
// we are not quite interested what type is this list of.
}
}
And the usage example:
m(new ArrayList<Circle>());
m(new ArrayList<Shape>());

Related

how to specify that T implement Comparable<T>?

I get error: Cannot resolve method 'sort(java.util.List<T>, boolean)' in case:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Arrays;
public class MyClass {
public static void main(String args[]) {
MyArray arr = new MyArray();
arr.set("b");
arr.set("a");
arr.sort();
}
}
class MyArray<T> {
List<T> myList;
MyArray(){
myList = new ArrayList<T>();
}
public void set(T val) {
myList.add(val);
}
public void sort() {
ListFunctions.sort(myList, true);
System.out.println(myList);
}
}
class ListFunctions {
public static <T extends Comparable<T>> void sort(List<T> array, boolean ascending){
if (ascending){
array.sort(null);
}
else {
array.sort(Collections.reverseOrder());
}
}
}
Problem is in line ListFunctions.sort(myList, true). I can't convert this way (List)myList, because meaning of constraint T extends Comparable<T> is lost.
I see examples of code where T need to implement Comparable interface, but in my case I can't specify class. I implicitly use List<String>
Your class
class MyArray<T>
Should simply be
class MyArray<T extends Comparable<T>>
If you don't do that, you may potentially pass an out of bound type to your utility function which the compiler will reject accordingly.
MyArray arr = new MyArray();
will hence become
MyArray<String> arr = new MyArray<>();

Cannot find symbol of written method java.util.function

I have code like
public class Functionz {
public static boolean test() {
return true;
}
public static void main(String[] args) {
Function[] funcs = new Function[] {test}; // and others
for (Function func : funcs) {
func();
}
}
}
and my error is: cannot find symbol: test in the line with the function array declaration.
Hope this isn't a stupid question, very new to java, not new to object oriented languages like python and C++.
A Function in Java does takes one parameter as input and one as output.
You might declare parameter's type this way : Function<Integer, String> is a function that transforms an Integer into a String
Your method test() does not take any input value and outputs a boolean so it's a Supplier.
import java.util.function.Supplier;
public class Main {
public static boolean test() {
System.out.println("lorem ipsum");
return true;
}
public static void main(String[] args) {
Supplier[] funcs = new Supplier[] {Main::test}; // and others
for (Supplier func : funcs) {
func.get();
}
}
}
Your code would compile if test requires one (and only one parameter) like
import java.util.function.Function;
public class Main {
public static boolean test(String str) {
System.out.println(str);
return true;
}
public static void main(String[] args) {
Function[] funcs = new Function[] {(Object anyObject) -> test(anyObject.toString())}; // and others
for (Function func : funcs) {
func.apply("lorem ipsum");
}
}
}
Here's the list of those types
Please note that Function doesn't type its parameters in construction because you can't create arrays with generic type in Java (you might for specific usecases) => Use a List will help you here

What are potential risks of creating generic array with varargs in Java?

I found a way to create generic array inline with varargs and anonymous inner class:
import java.util.Arrays;
import java.util.Objects;
public class GenericArrayCreate<C> {
public void method() {
C[] ans = new Object() { C[] c(int length, C ... cs) { return Arrays.copyOf(cs, length); }}.c(10);
System.out.println(ans.length);
System.out.println(Objects.toString(ans));
}
public static void main(String[] args) {
new GenericArrayCreate<Class<? extends Integer>>().method();
}
}
Java compiler produces no any warning on this code, while code inspection (in IntelliJ) says:
Unchecked generics array creation for varargs parameter
What is happening here and why is it swearing?
Internally you are creating an array of "Object" instead of a array of "C"
Try this code :
public static <C> C[] method(int number) {
return new Object() {
C[] create(int length, C ... cs) {
return Arrays.copyOf(cs, length);
}
}.create(number);
}
public static void main(String[] args) {
System.out.println(Main.<Integer>method(10));
System.out.println(new Integer[10]);
}
[Ljava.lang.Object;#6bc7c054
[Ljava.lang.Integer;#232204a1
As you can see they are not the same.
It is dangerous because if you run something like these:
public static void main(String[] args) {
Integer[] integerArray1 = Main.<Integer>method(10);
Integer[] integerArray2 = new Integer[10];
}
And you will have a ClassCastException
[Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer
If you want to create any kind of array you need to send the class to construct the array, you can do it like this:
public static <C> C[] method(Class<C> clazz, int number) {
return (C[]) Array.newInstance(clazz, number);
}
public static void main(String[] args) {
Integer[] integerArray1 = Main.<Integer>method(Integer.class, 10);
Integer[] integerArray2 = new Integer[10];
}
Code inspection will note that because varargs are implemented with array, and array do not support generics. But you can suppress that warning by adding this:
#SuppressWarnings("unchecked")
Reason why this happens is it can lead to loss of information. You can read more about that here.

Type Erasure in Generics

What does the following code look at runtime after type erasure:
public class Test<T> {
T lst;
List<T> list1;
void meth() throws InstantiationException, IllegalAccessException{ T res = (T)lst.getClass().newInstance();}
static <S> void meth(S t){}
}
class TestUse{
public static void main(String[] args) {
Test<Integer> gint = new Test<Integer>();
Test<String> gstr = new Test<String>();
gint.meth();
gstr.meth();
}
The following piece of code would not work:
T res = (T)lst.getClass().newInstance();
Since T is of type Object at runtime in all the possible cases.
One workaround is using generics with inheritance:
public abstract class Generic<T> {
public abstract Class<T> getConcreteClass();
public void doSomething() {
Class<T> clazz = getConcreteClass();
T t = clazz.newInstance();
doSomethingWithT();
}
}
public class ConcreteClass extends Generic<YourObject> {
public Class<YourObject> getConcreteClass() {
return YourObject.class;
}
}
I think it looks like this
public class Test {
Object lst;
List list1;
void meth() throws InstantiationException, IllegalAccessException{
Object res =lst.getCla ss().newInstance();
}
static void meth(Object t){
}
}
class TestUse{
public static void main(String[] args) {
Genrics gint = new Genrics();
Genrics gstr = new Genrics();
gint.meth();
gstr.meth();
}
although I do not think this would compile as it stands. There is no Genrics class - or is that the Test class.
Assuming that your Genrics class is actually your Test class, it should be equivalent to something like this:
public class Genrics {
Object lst;
List list1;
void meth() throws InstantiationException, IllegalAccessException {
Object res = lst.getClass().newInstance();
}
static void meth(Object t){}
}
class TestUse{
public static void main(String[] args) {
Genrics gint = new Genrics();
Genrics gstr = new Genrics();
gint.meth();
gstr.meth();
}
Whether my assumption is right or wrong, the way to think about this is that all generic type parameters are simply erased from your code and replaced with the most specific bound for the type parameter (Object in your case) or nothing where appropriate. Casts are introduced where needed.
The details of how type erasure works are spelled out in the Java Language Specification.

Java generics passing parameters

Hope somebody can help me out of this confussion.
I made this method:
public static <T> void myMethod(Map<Class<T>, MyInterface<T>> map) {
}
Used paramter T in order to make sure that the class used as key is the same as the class used as parameter in MyInterface.
Now I want to pass a map which different classes as keys, of course, and corresponding implementations of MyInterface.
But it doesn't work, getting syntax errors because of type parameters. Here is the code, I hope is self explanatory.
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
Map<Class<?>, MyInterface<?>> map = new HashMap<Class<?>, MyInterface<?>>();
// Map<Class<Object>, MyInterface<Object>> map = new HashMap<Class<Object>, MyInterface<Object>>();
map.put(Object.class, new MyObjectImpl());
//if I use Map<Class<Object>, MyInterface<Object>> I get a compiler error here
//because map<String> is not map<Object> basically
map.put(String.class, new MyStringImpl());
//this would be possible using <?>, which is exactly what I don't want
// map.put(String.class, new MyIntegerImpl());
//<?> generates anyways a compiler error
myMethod(map);
}
//use T to make sure the class used as key is the same as the class of the parameter "object" in doSomething
public static <T> void myMethod(Map<Class<T>, MyInterface<T>> map) {
}
interface MyInterface<T> {
void doSomething(T object);
}
static class MyObjectImpl implements MyInterface<Object> {
#Override
public void doSomething(Object object) {
System.out.println("MyObjectImpl doSomething");
}
}
static class MyStringImpl implements MyInterface<String> {
#Override
public void doSomething(String object) {
System.out.println("MyStringImpl doSomething");
}
}
static class MyIntegerImpl implements MyInterface<Integer> {
#Override
public void doSomething(Integer object) {
System.out.println("MyIntegerImpl doSomething");
}
}
}
You can't do that, because there is no constraint defined in Map's put() method between the key and the value. If you want to assure that your map is populated properly (i.e. create such constraint), hide the map behind some API that will check the correctness, for example:
public <T> void registerInterface(Class<T> clazz, MyInterface<T> intf) {
map.put(clazz, intf);
}
Then, just call the registerInterface instead of manually populating the map.
As far as I know, you cannot declare a Map like you describe in Java. All you can do is performing type checking and/or add constraints.
Guava offers something that approaches your problem with ClassToInstanceMap. So one way to do this would be to use MapConstraints.constrainedMap (like the example below)
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import com.google.common.collect.MapConstraint;
import com.google.common.collect.MapConstraints;
public class Main {
interface MyInterface<T> {
void doSomething(T object);
Class<T> getType();
}
static class MyObjectImpl implements MyInterface<Object> {
#Override
public void doSomething(Object object) {
System.out.println("MyObjectImpl doSomething");
}
#Override
public Class<Object> getType() {
return Object.class;
}
}
static class MyStringImpl implements MyInterface<String> {
#Override
public void doSomething(String object) {
System.out.println("MyStringImpl doSomething");
}
#Override
public Class<String> getType() {
return String.class;
}
}
static class MyIntegerImpl implements MyInterface<Integer> {
#Override
public void doSomething(Integer object) {
System.out.println("MyIntegerImpl doSomething");
}
#Override
public Class<Integer> getType() {
return Integer.class;
}
}
public static void main(String[] args) throws ParseException {
Map<Class<?>, MyInterface<?>> map = MapConstraints.constrainedMap(new HashMap<Class<?>, Main.MyInterface<?>>(),
new MapConstraint<Class<?>, MyInterface<?>>() {
#Override
public void checkKeyValue(Class<?> key, MyInterface<?> value) {
if (value == null) {
throw new NullPointerException("value cannot be null");
}
if (value.getType() != key) {
throw new IllegalArgumentException("Value is not of the correct type");
}
}
});
map.put(Integer.class, new MyIntegerImpl());
map.put(String.class, new MyStringImpl());
map.put(Object.class, new MyObjectImpl());
map.put(Float.class, new MyIntegerImpl()); //<-- Here you will get an exception
}
}
I do not think this is possible :
Class<T> only ever accepts T.class as value. Class<Object> will not accept String.class, even though Object is a superclass of String.
For this reason any map with Class<T> as key can have only one element, with T.class as key value, whatever the value of T.
The compiler will only ever accept a map with a definite value of T as parameter. You cannot write Map<Class<?>, MyInterface<?>> because each ? is assumed to be different : it does not match Map<Class<T>, MyInterface<T>> which requires T to have the same value.
That said, myMethod will only ever accept single-entry maps, which does not seem useful.
Change your method signature to
public static <T> void myMethod(Map<Class<? extends T>, MyInterface<? extends T>> map) {
}
now your declaration and invocation should work..
Map<Class<?>, MyInterface<?>> map = new HashMap<Class<?>, MyInterface<?>>();
map.put(Integer.class, new MyIntegerImpl());
map.put(String.class, new MyStringImpl());
map.put(Object.class, new MyObjectImpl());
myMethod(map);

Categories

Resources