How To Think Of... Aspects
public class AspectSets
{
/**
* Create a new Set which is strong on duplicates
*
* @param <E> the type of the {@link Set}
* @param elements the elements to create the set from
* @return a new Set which is only keen on holding added
* elements more than once.
*/
public static <E> Set<E> newSetOnDuplicates(final E[] elements)
{
final Aspect<E> isUnique = Aspects.uniqueAspect();
/*
* Using a set to handle multiple duplicates
* (elements that occur more than 2 times)
*/
return new HashSet<E>( Lists.newArrayList(elements) )
{
private static final long serialVersionUID =
-8073479479226481874L;
/**
* Add the element only if its a duplicate
*/
@Override
public boolean add(E e)
{
if(isDuplicate(e))
return super.add(e);
return false;
}
private boolean isDuplicate(E e) {
return !isUnique.apply(e);
}
};
}
}
public class Aspects
{
/**
* @param <E> the type of the objects
* @return a new Aspect on unique objects
*/
public static <E> Aspect<E> uniqueAspect()
{
return new Aspect<E>()
{
/*
* Holding all unique elements
*/
private final Set<E> uniques = new HashSet<E>();
/**
* @return true if element is unique
*/
@Override
public boolean apply(E e) {
return this.uniques.add(e);
}
};
}
}
/**
* Survival of the fittest
*/
public class AspectSetsTest
{
@Test
public void newSetOnDuplicates() throws Exception
{
String[] animals = {"cat", "cat", "cat",
"dog", "dog",
"parrot",
"lion",
"zebra", "zebra"};
Set<String> duplicatesOnly = AspectSets.newSetOnDuplicates(animals);
Assert.assertEquals(3, duplicatesOnly.size());
Assert.assertEquals(
Sets.newHashSet(
new String[] {"cat", "dog", "zebra"}),
duplicatesOnly);
Assert.assertTrue(duplicatesOnly.add("lion"));
Assert.assertEquals(4, duplicatesOnly.size());
Assert.assertEquals(
Sets.newHashSet(
new String[] {"cat", "dog", "zebra", "lion"}),
duplicatesOnly);
Assert.assertFalse(duplicatesOnly.add("monkey"));
Assert.assertEquals(4, duplicatesOnly.size());
Assert.assertEquals(
Sets.newHashSet(
new String[] {"cat", "dog", "zebra", "lion"}),
duplicatesOnly);
Assert.assertTrue(duplicatesOnly.add("monkey"));
Assert.assertEquals(5, duplicatesOnly.size());
Assert.assertEquals(
Sets.newHashSet(
new String[] {"cat", "dog", "zebra", "lion", "monkey"}),
duplicatesOnly);
}
}
As inspired by @sealabcore’s “Return only duplicate values from ruby array.”
Instead of extending from HashSet (Disclaimer)
public class DuplicatesOnlySet<E> extends HashSet<E>
{
private final Set<E> uniques = new HashSet<E>();
public DuplicatesOnlySet(Collection<? extends E> c)
{
super(c);
}
@Override
public boolean add(E e)
{
/*
* This is getting called at the constructor
* thus you will get a NPE since
* {@link #uniques} isn't initialised yet.
*/
if(!this.uniques.add(e))
return super.add(e);
return false;
}
}
and falling into the trap of a NullPointerException.
Or using composition forcing you to create delegate calls to the backing Set and making sure all necessary method calls go through your modified #add(E e) method, you can use the notion of aspects.
Note that the constructor
public HashSet(Collection<? extends E> c)
is actually calling the #addAll(Collection<? extends E> c) method which in return calls #add(E e) thus filtering the elements on construction.
What’s cool is that the way its integrated, any method used to traverse the returned Set will give you the expected elements. (Note)
Although we haven’t effectively changed the contract of the Set (“A collection that contains no duplicate elements”), its implementation certainly differs, so make sure you document that
Disclaimer
Technically speaking we do extend from HashSet only anonymously