Please help with best approach for following case:
There is table, which has about 20 columns.
Each column has its own short name, full name and type(number or String).
Each column type could have its own operator - For example, String - contains, equals; number - more, less, ==, !=
Each operator could have its own description.
I have to have object of Table class and be able to view all its columns, view short name and full name of each column, use operators based on column type.
I am trying to use enum, but I have no idea, how to connect specific column to specific type.
For example, how to connect "Id" column to "StringType" and "Services" column to "NumberType".
Could you please help.
class Table{
public enum Column {
Id("id", "ID number"),
Services("serv", "Services");
private final String shortName;
private final String fullName;
Column(String shortName, String fullName) {
this.shortName = shortName;
this.fullName = fullName;
}
public String getShortName() {
return shortName;
}
public String getFullName() {
return fullName;
}
}
public enum StringType{
contains("String contain another string"),
equals("String equal a string");
private final String placeholder;
StringType(String fullName) {
this.placeholder = fullName;
}
public String getPlaceholder() {
return placeholder;
}
}
public enum NumberType{
more("value that is more than input"),
less("value that is less than input");
private final String placeholder;
NumberType(String fullName) {
this.placeholder = fullName;
}
public String getPlaceholder() {
return placeholder;
}
}
}
Like any other class, enum types can implement interfaces. You can use this to your advantage:
public interface DataType {
// Deliberately empty. This is a marker interface.
}
public enum StringType
implements DataType {
// ...
}
public enum NumberType
implements DataType {
// ...
}
public enum Column {
Id("id", "ID number", StringType.class),
Services("serv", "Services", NumberType.class);
private final String shortName;
private final String fullName;
private final Class<? extends DataType> type;
Column(String shortName, String fullName, Class<? extends DataType> type) {
this.shortName = shortName;
this.fullName = fullName;
this.type = type;
}
// ...
}
If you plan to actually use these to compare data, you can add methods to the DataType interface:
public interface DataType<T> {
Class<T> getDataClass();
BiPredicate<? super T, ? super T> getTest();
default boolean test(T value1, T value2) {
return getTest().test(value1, value2);
}
default boolean testObjects(Object value1, Object value2) {
Class<T> type = getDataClass();
return test(type.cast(value1), type.cast(value2));
}
}
public enum StringType
implements DataType<String> {
contains("String contain another string", String::contains),
equals("String equal a string", Object::equals);
private final String placeholder;
private final BiPredicate<? super String, ? super String> test;
StringType(String fullName,
BiPredicate<? super String, ? super String> test) {
this.placeholder = fullName;
this.test = test;
}
public String getPlaceholder() {
return placeholder;
}
#Override
public BiPredicate<? super String, ? super String> getTest() {
return test;
}
#Override
public Class<String> getDataClass() {
return String.class;
}
}
public enum NumberType
implements DataType<Number> {
more("value that is more than input",
(n1, n2) -> n1.doubleValue() > n2.doubleValue()),
less("value that is less than input",
(n1, n2) -> n1.doubleValue() < n2.doubleValue());
private final String placeholder;
private final BiPredicate<? super Number, ? super Number> test;
NumberType(String fullName,
BiPredicate<? super Number, ? super Number> test) {
this.placeholder = fullName;
this.test = test;
}
public String getPlaceholder() {
return placeholder;
}
#Override
public BiPredicate<? super Number, ? super Number> getTest() {
return test;
}
#Override
public Class<Number> getDataClass() {
return Number.class;
}
}
public enum Column {
Id("id", "ID number", StringType.class),
Services("serv", "Services", NumberType.class);
private final String shortName;
private final String fullName;
private final Class<? extends DataType<?>> type;
Column(String shortName, String fullName, Class<? extends DataType<?>> type) {
this.shortName = shortName;
this.fullName = fullName;
this.type = type;
}
// ...
}
Java has a Class class that can hold types as its objects, it even works with the primitive types.
It doesn't have a constructor but a factory method called forName() that creates a class using the String provided as its parameter.(although this is considered bad practice).
The better way to obtain a reference to a type is using a class literal. A Class object representing its underlying class can also be obtained from any of its objects using the getClass() method.
Here are some ways through which you can create an object representing a class/type using the Class class:
Using Class Literals
Class<?> type1 = int.class;
Class<?> type2 = boolean.class;
Factory Method
Class<?> type1 = Class.forName("java.lang.String");
It's better to avoid this method since string parsing is involved which could lead to unnecessary runtime errors as pointed out by #VGR.
Using an object
String str = "";
Class type<?> = str.getClass();
You can add an additional variable of type Class<?>and do something like this:
public enum Column {
Id("id", "ID number", String.class),
Services("serv", "Services", Number.class);
Contact("cont", "Contacts", long.class);
private final String shortName;
private final String fullName;
private final Class<?> type;
Column(String shortName, String fullName, Class<?> type) {
this.shortName = shortName;
this.fullName = fullName;
this.type = type;
}
public String getShortName() {
return shortName;
}
public String getFullName() {
return fullName;
}
public Class<?> getType() {
return type;
}
}
Class is a powerful class, it has various methods like getName() and getMethods(). More on it here.
Note:
Class is commonly used for reflection which has major drawbacks, it can basically break encapsulation and also involves some performance overhead.
But if you merely use this additional field for the purpose of storing type information, it shouldn't be a big deal.
** This is in assumption that you want to map your column types to Java types!**
Related
I have a list of students studentList and i want to seperate them based on their class and section into a Map<CustomKey, List<Student>>.
class Student {
public String Name;
public String Class;
public String Section;
}
I have a custom Key Class
class CustomKey {
public String Class;
public String Section;
}
I'm trying to group them using this, but we can clearly see what I'm doing wrong.
studentList.stream().collect(Collectors.groupingBy(x -> new CustomKey(x.Class, x.Section)));
The expression inside groupingBy is incorrect.
Extending the Student Class is not allowed :(
I want to create this output
{
{"ClassA", "SectionA"}: [{name, class, section}, {name, class, section}],
{"ClassA", "SectionB"}: [{name, class, section}],
{"ClassB", "SectionA"}: [{name, class, section}, {name, class, section}],
}
My knowledge is very limited on JAVA. And any help/pointers are welcomed.
Also, Apologies for the messed-up cases.
Well, CustomKey is matched by its equals method, which you don't have overridden. If you implement equals (and also hashCode) properly, then it'll work.
What's even better, is to create a record for CustomKey:
public record CustomKey(String className, String section) { }
This'll let the compiler auto-generate its equals and hashCode implementations for free, as well as getters, the canonical constructor and the toString method. The above record definition is equivalent to this:
public final class CustomKey {
private final String className;
private final String section;
public CustomKey(String className, String section) {
this.className = className;
this.section = section;
}
public String className() {
return className;
}
public String section() {
return section;
}
#Override
public boolean equals(Object o) {
return o instanceof CustomKey ck
&& Objects.equals(className, ck.className)
&& Objects.equals(section, ck.section);
}
#Override
public int hashCode() {
return Objects.hash(className, section);
}
#Override
public String toString() {
return "CustomKey[className=" + className + ", section=" + section + "]";
}
}
i used your code exactly like below and it works for me
public void testCustomGroupBy(){
List<Student> studentList = new ArrayList<>();
studentList.add(new Student("name_1","class_1","section_1"));
studentList.add(new Student("name_2","class_1","section_1"));
studentList.add(new Student("name_3","class_2","section_1"));
studentList.add(new Student("name_4","class_2","section_2"));
studentList.add(new Student("name_5","class_2","section_2"));
Map<CustomKey, List<Student>> studentsBySection = studentList.stream().collect(Collectors.groupingBy(x -> new CustomKey(x.getClassName(), x.getSection())));
studentsBySection.entrySet().forEach(System.out::print);
}
create your pojo like below
class Student {
public String name;
public String className;
public String section;
// getters and setters
public Student(String name, String className, String section) {
this.name = name;
this.className = className;
this.section = section;
}
}
class CustomKey {
public String className;
public String section;
// getters and setters
public CustomKey(String className, String section) {
this.className = className;
this.section = section;
}
}
output
CustomKey(className=class_2, section=section_2)=[Student(name=name_4, className=class_2, section=section_2), Student(name=name_5, className=class_2, section=section_2)]
CustomKey(className=class_2, section=section_1)=[Student(name=name_3, className=class_2, section=section_1)]
CustomKey(className=class_1, section=section_1)=[Student(name=name_1, className=class_1, section=section_1), Student(name=name_2, className=class_1, section=section_1)]
I have a the below class:
#Data
public class PagedSchoolDto {
private final Integer count;
private final Map<String, List<School>> content;
private final String pagingState;
private final Boolean hasNext;
public PagedSchoolDto(final Slice<School> slice) {
this.content = slice.getContent().stream().collect(
Collectors.groupingBy(School::getId, () -> new TreeMap<>(new UUIDComparator()), Collectors.toList()));
this.count = slice.getContent().size();
this.hasNext = slice.hasNext();
this.pagingState = getPagingState(slice);
}
#Nullable
private static String getPagingState(final Slice<School> slice) {
if (slice.hasNext()) {
CassandraPageRequest pageRequest = (CassandraPageRequest) slice.nextPageable();
return pageRequest.getPagingState().toString();
} else {
return null;
}
}
}
Now, I want to make my code generic so that I can use this class for other object types as well like below:
#Data
public class PagedDto<T> {
private final Integer count;
private final Map<String, List<T>> content;
private final String pagingState;
private final Boolean hasNext;
public PagedDto(final Slice<T> slice) {
this.content = slice.getContent().stream().collect(
Collectors.groupingBy(<T>::getId, () -> new TreeMap<>(new UUIDComparator()), Collectors.toList()));
this.count = slice.getContent().size();
this.hasNext = slice.hasNext();
this.pagingState = getPagingState(slice);
}
#Nullable
private static <T> String getPagingState(final Slice<T> slice) {
if (slice.hasNext()) {
CassandraPageRequest pageRequest = (CassandraPageRequest) slice.nextPageable();
return pageRequest.getPagingState().toString();
} else {
return null;
}
}
}
All my classes have a property called ID which is type UUID so the comparator should work fine all generics types. The issue is I'm not sure how to write the Collectors.groupingBy's function and supplier code as the second snippet is giving compilation error.
Your generic type T is unspecified, e.g. 'Object' which does not provide the method getId. In order to access getId in the generic code, T must extend an interface with the getId method, which is implemented by any class that is used with PagedDto:
public interface WithId {
UUID getId();
}
public class School implements WithId {
private UUID id;
#Override
public UUID getId() {
return id;
}
public class PagedDto<T extends WithId> {
...
I am trying to make my application more efficient by using Java generics, but I don't have a lot of generics experience. I am trying to do this:
My Two Model Objects:
public class Person {
private String firstName;
private String lastName;
private int age;
// ...
// Getters and Setters Here
}
public class Car {
private String make;
private String model;
private int year;
// ...
// Getters and Setters Here
}
My Result Objects:
private class PersonResult {
private boolean success;
private String notes;
private int score;
// Getters and Setters Here
}
private class CarResult {
private boolean success;
private String notes;
private int score;
private double price;
// Getters and Setters Here
}
My Main Class:
public class MyClass{
public mainMethod(){
List<Person> listOfPersons = new ArrayList<>();
List<Car> listOfCars = new ArrayList<>();
listOfPersons.add(// a Person object);
// ...
listOfCars.add(// a Car object);
// ...
PersonResult = resultMethod(listOfPersons);
CarResult = resultMethod(listOfCars);
}
private <E,T> E resultMethod(List<T> listOfData)
{
Engine engine = EngineFactory.getEngine();
// if(listOfData.contains(Person.class))
if (listOfData instanceof List<Person>)
{
PersonResult personResult = new PersonResult();
perosnResult = engine.getPersonResult(listOfData);
return personResult;
}
// else if(listOfData.contains(Car.class))
else if (listOfData instanceof List<Car>)
{
CarResult carResult = new CarResult();
carResult engine.getCarResult(listOfData);
return carResult;
}
return null;
}
}
However my IDE is complaining about:
1 Illegal generic type for instanceof
2 (for the return value) Incompatible types: Required: E - Found PersonResult
As you can see, I also tried listOfData.contains(Person.class), that took care of error #1, but not the return error.
You cannot test for generic parameters by instanceof. instanceof will be evaluated at runtime. But the compiler removes the generic types. They are not present at runtime. For example List<Integer> will be the same as List at runtime.
You can use a simple implementation without generics here:
private PersonResult resultMethodPerson(List<Person> listOfData) {
return EngineFactory.getEngine().getPersonResult(listOfData);
}
private CarResult resultMethodCar(List<Car> listOfData) {
return EngineFactory.getEngine().getCarResult(listOfData);
}
I'm using DynamoDB and I would like to store the enum's String values instead of the enum itself.
For instance, I have this enum:
public enum Source {
BREACH("breach"),
LEAKAGE("leakage");
private final String desc;
Source(String desc) { this.desc = desc; }
public String desc() { return desc; }
}
...and this "entity":
#DynamoDBTable(tableName = "Alerts")
public final class Alert implements Serializable {
private static final long serialVersionUID = 4012517315640518044L;
#DynamoDBHashKey(attributeName = "AlertId") // Partition Key or Hash Attribute
private String alertId;
#DynamoDBTypeConvertedEnum
#DynamoDBAttribute(attributeName = "Source")
private Source type;
// Constructor(s), Getter(s), Setter(s), ToString, etc...
}
With the #DynamoDBTypeConvertedEnum annotation, the value that gets saved is BREACH, but I want breach.
{
"AlertId": { "S": "a083168d-cb23-4ec8-ab80-a1c16955c4b8" },
"Source": { "S": "BREACH" },
...
"CreatedAt": { "S": "2017-05-03T14:07:36.395Z" }
}
Any clues? I did try "converters" (not fully, I couldn't make it work though) but I think I have to end up doing one for each enum type since they are all different.
You can code the Alert class like this i.e. define the attribute as String and design the getter and setter to send/receive enum object (i.e. Source).
Alert class:-
#DynamoDBTable(tableName = "Alerts")
public final class Alert implements Serializable {
private static final long serialVersionUID = 4012517315640518044L;
private String alertId;
private String type;
#DynamoDBHashKey(attributeName = "AlertId")
public String getAlertId() {
return alertId;
}
#DynamoDBAttribute(attributeName = "Source")
public Source getType() {
if (type != null)
return Source.valueOf(type);
else
return null;
}
public void setAlertId(String alertId) {
this.alertId = alertId;
}
public void setType(Source type) {
this.type = type.desc();
}
}
Create Alert:-
Stores the value as expected on database table. The get item from DynamoDB table also works fine.
public Boolean createAlert(String alertId, Source source) {
DynamoDBMapper dynamoDBMapper = new DynamoDBMapper(dynamoDBClient);
Alert alert = new Alert();
alert.setAlertId(alertId);
alert.setType(source);
dynamoDBMapper.save(alert);
return true;
}
Override toString() this should work.
public enum Source {
BREACH("breach"),
LEAKAGE("leakage");
private final String desc;
Source(String desc) { this.desc = desc; }
public String desc() { return desc; }
#Override
public String toString() { return desc; }
}
public class MyObject
{
public static enum Type {A, B, C, D;}
public static final int ID_MAIN = 1;
public static final int ID_MAIN_UK = 2;
public static final int ID_MAIN_US = 3;
public static final int ID_SUB = 4;
// lots more constants here
public static final String DESCRIPTION_1 = "Desc Full Name";
public static final String DESCRIPTION_2 = "Desc2 Full Name";
// lots more constants here
private int id;
public MyObject(final int id)
{
this.id = id;
}
//simple getter
public int getID() { return this.id;}
// real responsibility of the class is in the following two methods
public static String getDescription()
{
switch(id)
{
case MyObject.ID_MAIN:
case MyObject.ID_MAIN_UK:
return MyObject.DESCRIPTION_1;
case MyObject.ID_SUB:
return MyObject_Description_2;
default:
// throw IllegalArgException
}
}
public static Type getType(int id)
{
switch(id)
{
case MyObject.ID_MAIN:
case MyObject.ID_SUB:
return Type.A;
case MyObject.ID_MAIN_UK:
case MyObject.ID_MAIN_US:
return Type.B;
default:
return Type.Undefined;
}
}
}
Basically, there is an ID that maps to both a description and a type. This ID is passed in during construction of the class and it should map to a set of constants already contained in the class. If the id is not part of the list of constants, an error is thrown when trying to get the description that maps to the id and an 'Unknown' type is return if the type is queried. The ID maps a description to a set of constants. The same ID maps to a certain Type (defined as an enum).
This code is pretty ugly because there are tons of constants defined at the top, which makes the switch statements pretty bloated. Is there a simple way to refactor this without changing the public interface? It seems trivially simple, but it seems pretty ugly no matter how you slice it. How can I simplify these mappings to make the code more concise?
I was thinking about representing the mappings in a text file and having a manager class that held simple containers in a hashmap. When the manager class is constructed, it would create the objects by reading the text file and map them to an ID. When the manager is queried with the ID, it would just call the corresponding get method, for instance:
class Manager
{
private HashMap<int, MyObject> objectMap;
public Manager() {} //construct the object map
public String getDescription(int id) { return objectMap.get(id).getDescription();}
public Type getType(int id) { return objectMap.get(id).getType();}
}
class DataContainer
{
private String description;
private Type type;
public DataContainer(String desc, Type type) {//set mem vars}
public String getDescription() //simple getter
public Type getType() //simple getter
}
But this solution seems too complicated. Is there a better solution, preferably one that would keep everything in one class?
You can do something like following. This would be much cleaner and manageable.
public enum Type
{
MAIN(1, "Main Description"),
MAIN_UK(2, "Main UK Description"),
//....
//Define all the types
//....
UNKNOWN(-1, "Unknown Type");
private int id;
private String description;
private Type(int id, String description)
{
this.id = id;
this.description = description;
}
public static Type getById(int id)
{
for (Type type : Type.values())
{
if (id == type.getId())
{
return type;
}
}
return Type.UNKNOWN;
}
public final int getId()
{
return id;
}
public final String getDescription()
{
return description;
}
}
public class MyObject
{
private int id;
private Type type;
public MyObject(int id)
{
this.id = id;
this.type = Type.getById(id);
}
public int getId()
{
return id;
}
public Type getType()
{
return type;
}
public String getDescription()
{
return type.getDescription();
}
}
In Java enums can have methods. For example following one accepts ID and description and provides some accessors.
public enum Type {
MAIN(1, "desc1"),
UK(2, "desc2"),
SUB(4, "desc4");
private int id;
private String desc;
Type(int id, String desc) {
this.id = id;
this.desc = desc;
}
public String getDescription() {
return desc;
}
public int getType() {
//return id;
return 1+2 + 3+ id;
}
}
You could use that to improve design.