diff --git a/3-0-java-core/3-6-4-random-field-comparator/README.MD b/3-0-java-core/3-6-4-random-field-comparator/README.MD
new file mode 100644
index 000000000..c361e6aa4
--- /dev/null
+++ b/3-0-java-core/3-6-4-random-field-comparator/README.MD
@@ -0,0 +1,17 @@
+#
Random Field Comparator
+#### Improve your reflection-related skills implementing a random field comparator ๐ช
+
+### Objectives
+* implement a logic of choosing a random field to use it for comparison of objects of provided type โ
+* implement a mechanism to check if field type is `Comparable` โ
+* implement a method `compare` that compares two objects by randomly-provided field โ
+* extend a method `compare` to manage null field values following condition when null value grater than a non-null value โ
+* implement method `getComparingFieldName` that retrieves the name of randomly-chosen comparing fieldโ
+* implement method `toString` โ
+
+---
+#### ๐ First time here? โ [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction)
+#### โก๏ธ Have any feedback? โ [Please fill the form ](https://forms.gle/u6kHcecFuzxV232LA)
+
+##
+

\ No newline at end of file
diff --git a/3-0-java-core/3-6-4-random-field-comparator/pom.xml b/3-0-java-core/3-6-4-random-field-comparator/pom.xml
new file mode 100644
index 000000000..557dc1f36
--- /dev/null
+++ b/3-0-java-core/3-6-4-random-field-comparator/pom.xml
@@ -0,0 +1,15 @@
+
+
+
+ 3-0-java-core
+ com.bobocode
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ 3-6-4-random-field-comparator
+
+
+
\ No newline at end of file
diff --git a/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java b/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java
new file mode 100644
index 000000000..ee33f4159
--- /dev/null
+++ b/3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java
@@ -0,0 +1,85 @@
+package com.bobocode.se;
+
+import static java.util.Objects.requireNonNull;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Objects;
+import lombok.SneakyThrows;
+
+/**
+ * A generic comparator that is comparing a random field of the given class. The field is either primitive or
+ * {@link Comparable}. It is chosen during comparator instance creation and is used for all comparisons.
+ *
+ * If no field is available to compare, the constructor throws {@link IllegalArgumentException}
+ *
+ * @param the type of the objects that may be compared by this comparator
+ *
+ * TODO: to get the most out of your learning, visit our website
+ *
+ *
+ * @author Stanislav Zabramnyi
+ */
+public class RandomFieldComparator implements Comparator {
+
+ private final Class targetType;
+ private final Field fieldToCompare;
+
+ public RandomFieldComparator(Class targetType) {
+ this.targetType = requireNonNull(targetType);
+ this.fieldToCompare = chooseFieldToCompare(targetType);
+ }
+
+ /**
+ * Compares two objects of the class T by the value of the field that was randomly chosen. It allows null values
+ * for the fields, and it treats null value grater than a non-null value.
+ *
+ * @param o1
+ * @param o2
+ * @return positive int in case of first parameter {@param o1} is greater than second one {@param o2},
+ * zero if objects are equals,
+ * negative int in case of first parameter {@param o1} is less than second one {@param o2}.
+ */
+ @Override
+ public int compare(T o1, T o2) {
+ Objects.requireNonNull(o1);
+ Objects.requireNonNull(o2);
+ return compareFieldValues(o1, o2);
+ }
+
+ /**
+ * Returns the name of the randomly-chosen comparing field.
+ */
+ public String getComparingFieldName() {
+ return fieldToCompare.getName();
+ }
+
+ /**
+ * Returns a statement "Random field comparator of class '%s' is comparing '%s'" where the first param is the name
+ * of the type T, and the second parameter is the comparing field name.
+ *
+ * @return a predefined statement
+ */
+ @Override
+ public String toString() {
+ return String.format("Random field comparator of class '%s' is comparing '%s'", targetType.getSimpleName(),
+ getComparingFieldName());
+ }
+
+ private Field chooseFieldToCompare(Class targetType) {
+ return Arrays.stream(targetType.getDeclaredFields())
+ .filter(f -> Comparable.class.isAssignableFrom(f.getType()) || f.getType().isPrimitive())
+ .findAny().orElseThrow(() -> new IllegalArgumentException("There are no fields available to compare"));
+ }
+
+ @SneakyThrows
+ @SuppressWarnings("unchecked")
+ private > int compareFieldValues(T o1, T o2) {
+ fieldToCompare.setAccessible(true);
+ var value1 = (U) fieldToCompare.get(o1);
+ var value2 = (U) fieldToCompare.get(o2);
+ Comparator comparator = Comparator.nullsLast(Comparator.naturalOrder());
+ return comparator.compare(value1, value2);
+ }
+}
diff --git a/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java b/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java
new file mode 100644
index 000000000..1733568e5
--- /dev/null
+++ b/3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java
@@ -0,0 +1,253 @@
+package com.bobocode.se;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+import lombok.SneakyThrows;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+
+@TestMethodOrder(OrderAnnotation.class)
+class RandomFieldComparatorTest {
+
+ private final RandomFieldComparator randomFieldComparator = new RandomFieldComparator<>(Account.class);
+
+ @Test
+ @Order(1)
+ @DisplayName("Constructor throws an exception when parameter is null")
+ void classDoesNotApplyNullInConstructor() {
+ assertThrows(NullPointerException.class, () -> new RandomFieldComparator<>(null));
+ }
+
+ @Test
+ @Order(2)
+ @SneakyThrows
+ @DisplayName("Constructor throws an exception when the target type has no Comparable fields")
+ void constructorThrowsExceptionIfNoComparableFieldsInProvidedType() {
+ assertThrows(IllegalArgumentException.class, () -> new RandomFieldComparator<>(ClassWithNotComparableField.class));
+ }
+
+ @Test
+ @Order(3)
+ @DisplayName("Method 'compare' throws an exception when any parameter is null")
+ void compareWhenFirstParameterAreNull() {
+
+ assertThrows(NullPointerException.class, () -> randomFieldComparator.compare(null, new Account()));
+ assertThrows(NullPointerException.class, () -> randomFieldComparator.compare(new Account(), null));
+ }
+
+ @Test
+ @Order(4)
+ @DisplayName("Method 'compare' returns 0 when field values of both objects are null")
+ void compareWhenBothFieldValuesIsNull() {
+ setFieldToCompare("lastName", Account.class);
+ int compareResult = randomFieldComparator.compare(new Account(), new Account());
+
+ assertThat(compareResult).isZero();
+ }
+
+ @Test
+ @Order(5)
+ @DisplayName("Method compare returns positive int when the first field value is null")
+ void compareWhenFieldValuesOfFirstObjectIsNull() {
+ Account emptyAccount = new Account();
+ Account account = new Account("Sibma", "LoinKing", "simba-bimba@gmail.com", 14);
+ setFieldToCompare("email", Account.class);//set field to compare explicitly as there are int field which has default value 0
+ int compareResult = randomFieldComparator.compare(emptyAccount, account);
+
+ assertThat(compareResult).isPositive();
+ }
+
+ @Test
+ @Order(6)
+ @DisplayName("Method compare returns negative int when the second field value is null")
+ void compareWhenFieldValuesOfSecondObjectIsNull() {
+ Account account = new Account("Mufasa", "LoinKing", "simba-bimba@gmail.com", 47);
+ Account emptyAccount = new Account();
+ setFieldToCompare("firstName", Account.class);
+ int compareResult = randomFieldComparator.compare(account, emptyAccount);
+
+ assertThat(compareResult).isNegative();
+ }
+
+ @Test
+ @Order(7)
+ @SneakyThrows
+ @DisplayName("Method 'compare' returns positive int when the first value is greater")
+ void compareWhenFieldValueOfFirstObjectIsGrater() {
+ var fieldToCompareName = "firstName";
+ Account account1 = new Account();
+ Account account2 = new Account();
+ Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName);
+ fieldToCompareAccount.setAccessible(true);
+
+ fieldToCompareAccount.set(account1, "Bob");
+ fieldToCompareAccount.set(account2, "Alice");
+
+ setFieldToCompare(fieldToCompareName, Account.class);
+ int compareResult = randomFieldComparator.compare(account1, account2);
+
+ assertThat(compareResult).isPositive();
+ }
+
+ @Test
+ @Order(8)
+ @SneakyThrows
+ @DisplayName("Method 'compare' returns negative int when the first value is smaller")
+ void compareWhenFieldValueOfSecondObjectIsGrater() {
+ var fieldToCompareName = "firstName";
+ Account account1 = new Account();
+ Account account2 = new Account();
+ Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName);
+ fieldToCompareAccount.setAccessible(true);
+
+ fieldToCompareAccount.set(account1, "Alice");
+ fieldToCompareAccount.set(account2, "Bob");
+
+ setFieldToCompare(fieldToCompareName, Account.class);
+ int compareResult = randomFieldComparator.compare(account1, account2);
+
+ assertThat(compareResult).isNegative();
+ }
+
+ @Test
+ @Order(9)
+ @SneakyThrows
+ @DisplayName("Method 'compare' returns zero when the field values are equal")
+ void compareWhenFieldValuesOfObjectsAreEqual() {
+ var fieldToCompareName = "firstName";
+ Account account1 = new Account();
+ Account account2 = new Account();
+ Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName);
+ fieldToCompareAccount.setAccessible(true);
+
+ fieldToCompareAccount.set(account1, "Carol");
+ fieldToCompareAccount.set(account2, "Carol");
+
+ setFieldToCompare(fieldToCompareName, Account.class);
+ int compareResult = randomFieldComparator.compare(account1, account2);
+
+ assertThat(compareResult).isZero();
+ }
+
+ @Test
+ @Order(10)
+ @SneakyThrows
+ @DisplayName("Method 'compare' returns positive int when the first primitive value is greater")
+ void comparePrimitivesWhenFieldValueOfFirstObjectIsGrater() {
+ var fieldToCompareName = "age";
+ Account account1 = new Account();
+ Account account2 = new Account();
+ Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName);
+ fieldToCompareAccount.setAccessible(true);
+
+ fieldToCompareAccount.setInt(account1, 7);
+ fieldToCompareAccount.setInt(account2, 3);
+
+ setFieldToCompare(fieldToCompareName, Account.class);
+ int compareResult = randomFieldComparator.compare(account1, account2);
+
+ assertThat(compareResult).isPositive();
+ }
+
+ @Test
+ @Order(11)
+ @SneakyThrows
+ @DisplayName("Method 'compare' returns zero when the primitive field values are equal")
+ void comparePrimitivesWhenFieldValuesOfObjectsAreEqual() {
+ var fieldToCompareName = "age";
+ Account account1 = new Account();
+ Account account2 = new Account();
+ Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName);
+ fieldToCompareAccount.setAccessible(true);
+
+ fieldToCompareAccount.setInt(account1, 15);
+ fieldToCompareAccount.setInt(account2, 15);
+
+ setFieldToCompare(fieldToCompareName, Account.class);
+ int compareResult = randomFieldComparator.compare(account1, account2);
+
+ assertThat(compareResult).isZero();
+ }
+
+ @Test
+ @Order(12)
+ @SneakyThrows
+ @DisplayName("Method 'compare' returns negative int when the first primitive value is smaller")
+ void comparePrimitivesWhenFieldValueOfSecondObjectIsGrater() {
+ var fieldToCompareName = "age";
+ Account account1 = new Account();
+ Account account2 = new Account();
+ Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName);
+ fieldToCompareAccount.setAccessible(true);
+
+ fieldToCompareAccount.setInt(account1, 4);
+ fieldToCompareAccount.setInt(account2, 8);
+
+ setFieldToCompare(fieldToCompareName, Account.class);
+ int compareResult = randomFieldComparator.compare(account1, account2);
+
+ assertThat(compareResult).isNegative();
+ }
+
+ @Test
+ @Order(13)
+ @SneakyThrows
+ @DisplayName("Method 'getComparingFieldName' returns the name of randomly-chosen field to be compared")
+ void getComparingFieldName() {
+ var fieldToCompareName = "lastName";
+ setFieldToCompare(fieldToCompareName, Account.class);
+
+ assertEquals(fieldToCompareName, randomFieldComparator.getComparingFieldName());
+ }
+
+ @Test
+ @Order(14)
+ @SneakyThrows
+ @DisplayName("Method toString is properly overridden")
+ void toStringOverriding() {
+ var expectedString = "Random field comparator of class 'Account' is comparing 'email'";
+ var fieldToCompareName = "email";
+ setFieldToCompare(fieldToCompareName, Account.class);
+
+ assertEquals(expectedString, randomFieldComparator.toString());
+ }
+
+ @SneakyThrows
+ private void setFieldToCompare(String fieldName, Class classType) {
+ Field fieldToCompare = Arrays.stream(randomFieldComparator.getClass().getDeclaredFields())
+ .filter(f -> f.getType().equals(Field.class))
+ .findAny()
+ .orElseThrow();
+ fieldToCompare.setAccessible(true);
+ fieldToCompare.set(randomFieldComparator, classType.getDeclaredField(fieldName));
+ }
+
+ private static class EmptyClass {
+
+ }
+
+ @AllArgsConstructor
+ private static class ClassWithNotComparableField {
+
+ private Object field;
+ }
+
+ @NoArgsConstructor
+ @AllArgsConstructor
+ private static class Account {
+
+ private String firstName;
+ private String lastName;
+ private String email;
+ private int age;
+ }
+}
\ No newline at end of file
diff --git a/3-0-java-core/pom.xml b/3-0-java-core/pom.xml
index 792d82a8b..917e0cf17 100644
--- a/3-0-java-core/pom.xml
+++ b/3-0-java-core/pom.xml
@@ -14,6 +14,7 @@
3-6-1-file-reader
3-6-2-file-stats
3-6-3-crazy-regex
+ 3-6-4-random-field-comparator