0x150
0x150
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
Reified generics solve this problem, but they're not supported in java yet.
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
This isn't exactly foolproof, since you can technically pass an array to the method manually and use some other type extending T, but it's the best method we have right now.
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
Generic types are placeholders for reference types that can be used to make a piece of code work for multiple types, without having to duplicate the code. They're like C++'s templates, although less advanced. Whereas C++'s templates are literal templates that just insert the type (or value) used in the generic type parameter, Java figures out the types at compile time. The resulting bytecode then uses the first parent type applicable to all possible types of the generic type, and can safely cast that parent type to the used generic type based on usage.
<ElementType, ListType extends List<ElementType>> ListType addAnElementTo(ListType theList, ElementType theElement) {
theList.add(theElement);
return theList;
}
<ElementType, ListType extends List<ElementType>> ListType addAnElementTo(ListType theList, ElementType theElement) {
theList.add(theElement);
return theList;
}
this will be compiled to:
List addAnElementTo(List theList, Object theElement) {
theList.add(theElement);
return theList;
}
List addAnElementTo(List theList, Object theElement) {
theList.add(theElement);
return theList;
}
but due to compile time constraints, javac can safely transform usages of the code like
List<String> strings = addAnElementTo(new ArrayList<>(), "hello");
List<String> strings = addAnElementTo(new ArrayList<>(), "hello");
to
List<String> strings = (ArrayList<String>) addAnElementTo(new ArrayList<String>(), "hello");
List<String> strings = (ArrayList<String>) addAnElementTo(new ArrayList<String>(), "hello");
. Doing it this way means that basic generic types are supported, but this also means that type information about a generic type can't be accessed at runtime, since the generic type doesn't really exist at runtime anymore. A hack is using an automatically filled vararg parameter with the generic type you want to test, such as:
<T> void printTheTypeGiven(T... typeProbe) {
System.out.println(typeProbe.getClass().getComponentType());
}
<T> void printTheTypeGiven(T... typeProbe) {
System.out.println(typeProbe.getClass().getComponentType());
}
because this method can be called like this.<String>printTheGivenType(), without passing the vararg parameter explicity, javac will generate an empty array of T, and pass it to the method, which looks like this at runtime: printTheGivenType(new String[0]). With that information, we can figure out which type the array has, and can thus figure the type out.
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
This small example shows how a generified type can be used:
List<String> strings = new ArrayList<>(); // strings can only hold... strings
strings.add("string one");
strings.add("string two");
strings.add(LocalDate.now()); // Will not compile

String one = strings.get(0); // No check or cast needed
LocalDate three = strings.get(2); // Will not compile
List<String> strings = new ArrayList<>(); // strings can only hold... strings
strings.add("string one");
strings.add("string two");
strings.add(LocalDate.now()); // Will not compile

String one = strings.get(0); // No check or cast needed
LocalDate three = strings.get(2); // Will not compile
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
For instance, the aforementioned java.util.List interface defines a single generic parameter, <T>. Most of the methods in the interface make use of this generic parameter for their own arguments and/or return type.
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
However, generics allow types and methods to define one or more generic parameters. The compiler uses these to constrain the the types of parameters or return values that are allowed at a call site or within a method.
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
Obviously, number 2 is not practical (and may not even be possible). So, before generics, the List interface operated on Object. This works, but is not type-safe. There is nothing preventing someone from inserting a String into a list that is expected to contain Integers. This would actually work on the add call. But down the road, when objects are read from the list, the client would have no way of knowing whether the objects it retrieves are Integers or something else, without checking every time.
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
The classic use case for generics are collections of objects. As an example, think about the java.util.List interface. Without generics, there would be only 2 possibilities for the API: 1. Methods like add and get would operate on Object, so that any possible object could be passed/returned 2. Methods like add and get would need to be overridden for every possible class
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
Generics are a language feature that was added to Java 5. They allow code to work with objects of any type (or a constrained type), rather than a single specific type.
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
Generics is used for type templating where the return type of a method can be different types or class uses different types of members. My favourite example of a good usage of generics is with Transformers or a Provider or List. You're welcome ( ̄︶ ̄)↗
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
Generics enables developers to define functions, classes, and interfaces that can work with multiple data types, without the need for explicit type casting or separate implementations for each type. Example
public class Container<T> {
private T value;

public Container(T value) {
this.value = value;
}

public T getValue() {
return value;
}

public void setValue(T value) {
this.value = value;
}
}
public class Container<T> {
private T value;

public Container(T value) {
this.value = value;
}

public T getValue() {
return value;
}

public void setValue(T value) {
this.value = value;
}
}
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
Wildcards can be specified using the?-symbol meaning "any fixed type" whereas ? extends Temporal refers to "any fixed subtype of Temporal". It is also possible to use the super keyword to specify bounds requiring a supertype.
List<?> anyList = new ArrayList<String>();
anyList = new ArrayList<LocalDate>();

List<? extends Temporal> temporalList = new ArrayList<LocalDate>();
temporalList = new ArrayList<>(Temporal);
temporalList = new ArrayList<>(LocalDateTime);
//temporalList = new ArrayList<>(String);//compiler error

List<? super String> listAllowingStringInsertion = new ArrayList<String>();//the type of this list could be String or supertypes of it
listAllowingStringInsertion.add("Hello World");
listAllowingStringInsertion = new ArrayList<Object>();//any List<Object> is a List<? super String>
List<?> anyList = new ArrayList<String>();
anyList = new ArrayList<LocalDate>();

List<? extends Temporal> temporalList = new ArrayList<LocalDate>();
temporalList = new ArrayList<>(Temporal);
temporalList = new ArrayList<>(LocalDateTime);
//temporalList = new ArrayList<>(String);//compiler error

List<? super String> listAllowingStringInsertion = new ArrayList<String>();//the type of this list could be String or supertypes of it
listAllowingStringInsertion.add("Hello World");
listAllowingStringInsertion = new ArrayList<Object>();//any List<Object> is a List<? super String>
Generics can have bounds specifying that the types must be within these bounds.
public class SomeClass<T extends Temporal> {
private T element;
}
public class SomeClass<T extends Temporal> {
private T element;
}
SomeClass<LocalDate> thisIsValid = new SomeClass<>();
//SomeClass<String> thisIsInvalid = new SomeClass<>();//violates the <T extends Temporal> bound
SomeClass<LocalDate> thisIsValid = new SomeClass<>();
//SomeClass<String> thisIsInvalid = new SomeClass<>();//violates the <T extends Temporal> bound
These bounds can also make use of wildcards. For example, it is possible to require a generic type to extend Collection of some Temporal
public class SomeClassWithCollection<C extends Collection<? extends Temporal>> {
private C theCollection;
public SomeClassWithCollection(C collection) {
theCollection = collection;
}
public C getCollection(){
return theCollection;
}
public Optional<Temporal> findSomeElement(){
Iterator<? extends Temporal> it = theCollection.iterator();
if(it.hasNext()){
return Optional.of(it.next());
}
return Optional.empty();
}
}
public class SomeClassWithCollection<C extends Collection<? extends Temporal>> {
private C theCollection;
public SomeClassWithCollection(C collection) {
theCollection = collection;
}
public C getCollection(){
return theCollection;
}
public Optional<Temporal> findSomeElement(){
Iterator<? extends Temporal> it = theCollection.iterator();
if(it.hasNext()){
return Optional.of(it.next());
}
return Optional.empty();
}
}
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
Methods can also declare generic types by specifying them before the return type:
public <T> T someMethod(T element){
return element;
}
public <T> T someMethod(T element){
return element;
}
String stringPassedToMethod = someMethod("abc");
LocalDateTime localDateTimePassedToMethod = someMethod(LocalDateTime.now());
String stringPassedToMethod = someMethod("abc");
LocalDateTime localDateTimePassedToMethod = someMethod(LocalDateTime.now());
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/22/2024 in #❓︱qotw-answers
Week 105 — What are generics and how can they be used?
Generics allow specifying placeholders for types in classes and methods. If a class specifies a generic/type parameter using the <T> syntax, it can use the type T for variables, parameters, return types and superclasses/interfaces.
public class SomeGenericClass<T> {
private T someField;
private List<T> someList;

public SomeGenericClass(T element) {
someField = element;
someList = new ArrayList<>();
someList.add(element);
}

public T getElement(){
return someField;
}

public static void someStaticMethod(){
//static methods cannot make use of generics specified at a class level
}
}
public class SomeGenericClass<T> {
private T someField;
private List<T> someList;

public SomeGenericClass(T element) {
someField = element;
someList = new ArrayList<>();
someList.add(element);
}

public T getElement(){
return someField;
}

public static void someStaticMethod(){
//static methods cannot make use of generics specified at a class level
}
}
When an object of that class is created, all generics should be assigned to concrete types. Generics cannot be assigned to primitives (as of JDK 23 which is the current Java version at the time of writing this).
SomeGenericClass<String> objectOfGenericClassWithTypeParameterSetToString = new SomeGenericClass<>("Hello World");
String element = objectOfGenericClassWithTypeParameterSetToString.getElement();
SomeGenericClass<LocalDateTime> objectOfGenericClassWithTypeParameterSetToLocalDateTime = new SomeGenericClass<>(LocalDateTime.now());

SomeGenericClass.someStaticMethod();
SomeGenericClass<String> objectOfGenericClassWithTypeParameterSetToString = new SomeGenericClass<>("Hello World");
String element = objectOfGenericClassWithTypeParameterSetToString.getElement();
SomeGenericClass<LocalDateTime> objectOfGenericClassWithTypeParameterSetToLocalDateTime = new SomeGenericClass<>(LocalDateTime.now());

SomeGenericClass.someStaticMethod();
15 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/15/2024 in #❓︱qotw-answers
Week 104 — What options are there for logging information in Java applications?
Third-party logging frameworks also exist, and can provide advantages over the JDK's logging facilities. Log4J and Logback are two of the more popular ones. SLF4J (Simple Logging Facade for Java) is also an interesting option. It provides a common facade, along with adapters, for working with other logging frameworks, including java.util.logging.
7 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/15/2024 in #❓︱qotw-answers
Week 104 — What options are there for logging information in Java applications?
The java.util.logging package is a logging framework built directly into the JDK (for JDK 9+, the JDK module java.logging is required). There are 3 main components -- Logger, Handler, and Formatter. Loggers are the component that client code typically works with. Applications use them to send log messages into the logging subsystem. Each message has a level associated with it. The level is hierarchical, and allows the verbosity of the log messages to be controlled at runtime. For instance, if the logging system is set to INFO, then only messages at the SEVERE, WARNING, or INFO levels will be output to the log; messages at CONFIG or lower will be suppressed. Handlers provide a destination for the log messages. For instance, the ConsoleHandler will direct messages to System.out or System.err. Multiple handlers can be configured, each with their own settings, and the subsystem will take care of sending each log message to all applicable handlers. Formatters are attached to handlers and control the format of the log messages. In addition to the log message from application code, formatters can add metadata like the timestamp of the message, the level, etc. They can even output the message in special formats such as JSON or XML.
7 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/15/2024 in #❓︱qotw-answers
Week 104 — What options are there for logging information in Java applications?
The most basic option for logging is simply writing to System.out and System.err. However, anything more than a trivial application will want to make use of a proper logging framework.
7 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/15/2024 in #❓︱qotw-answers
Week 104 — What options are there for logging information in Java applications?
7 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/15/2024 in #❓︱qotw-answers
Week 104 — What options are there for logging information in Java applications?
you can use the native logging library (java.logging.Logger)
7 replies
JCHJava Community | Help. Code. Learn.
Created by JavaBot on 12/15/2024 in #❓︱qotw-answers
Week 104 — What options are there for logging information in Java applications?
The easiest way to log data is to use System.out which normally forwards everything to the console associated with the application (stdout and stderr for System.err).
System.out.println("some log");
System.out.println("some log");
However, this does not allow specifying log levels and cannot be configured apart from redirecting it to a different PrintStream or by redirecting stdout for the entire application and does not differentiate between log levels/severities. Because of this, the java.util.logging package in the java.logging module has been introduced.
Logger logger = Logger.getLogger("my-name");

//log something at different levels
logger.log(Level.FINE, "some informative message");
logger.log(Level.WARNING, "some warning message");
Logger logger = Logger.getLogger("my-name");

//log something at different levels
logger.log(Level.FINE, "some informative message");
logger.log(Level.WARNING, "some warning message");
These loggers can be configured in a logging.properties file which can be specified using the java.util.logging.config.file parameter. Other than that, SLF4J (Simple Logging Farcade for Java) provides a general API that can be used with many different loggers. To use the API, one needs to add the slf4j-api dependency.
Logger logger = LoggerFactory.getLogger(SomeClass.class);

logger.info("some informative message");
logger.warn("some warning message");
Logger logger = LoggerFactory.getLogger(SomeClass.class);

logger.info("some informative message");
logger.warn("some warning message");
When an SLF4J logger implementation dependency like logback is added at runtime, these messages will be logged using that dependency.
7 replies