It may be a common question but I couldn't find nice explanation for it. I am trying to understand the encapsulation of reference variables in Java.In the below code:
class Special {
private StringBuilder s = new StringBuilder("bob");
StringBuilder getName() { return s; }
void printName() { System.out.println(s); }
}
public class TestSpecial {
public static void main (String[] args ) {
Special sp = new Special();
StringBuilder s2 = sp.getName();
s2.append("fred");
sp.printName();
}
}
Output: bobfred
At first I thought making our field private and providing a getter method, it's a good encapsulation technique. But when I took closer look on it, I saw that when I invoke getName(), I do in fact return a copy, just like Java always does. But, I am not returning a copy of the StringBuilder object. I am returning a copy of the reference variable that point to the one and only StringBuilder object. So, at the point that getName() returns, I have one StringBuilder object and two reference variables pointing to it ( s and s2).
What are the techniques to make it well encapsulated?? A good explanation with code example expected :) . Thanks in advance.
There are two basic approaches I can think of.
The first is to only return immutable values. The caller can then do what he wants without risking the integrity of your object. In your case, the immutable type would be String:
class Special {
private String s = "bob";
String getName() { return s; }
void printName() { System.out.println(s); }
}
public class TestSpecial {
public static void main (String[] args ) {
Special sp = new Special();
String s2 = sp.getName();
s2 += "fred";
// Alternatively: StringBuilder s2 = new StringBuilder(sp.getName()).append("fred");
sp.printName(); // prints "bob"
}
}
Note: If s needs to be a StringBuilder, you can return s.toString().
The other option is to return a mutable value, but create a defensive copy in your getter. In other words, return a separate reference with duplicate data:
class Special {
private StringBuilder s = new StringBuilder("bob");
StringBuilder getName() { return new StringBuilder(s); } // return a copy of s
void printName() { System.out.println(s); }
}
public class TestSpecial {
public static void main (String[] args ) {
Special sp = new Special();
StringBuilder s2 = sp.getName();
s2.append("fred");
sp.printName(); // prints "bob"
}
}
There can be multiple ways to apply encapsulation to mutable object.
By providing copy constructor (in above example new StringBuilder(oldBuilder.toString())
public class Student{
private String name;
public Student(Student s){
this.name = s.name;
}
}
Using prototype pattern with clone method. But copy Constructor is recommend over clone method.
public Student implements Cloneable{
private int rollNo;
private String name;
public Student clone(){
Student s = (Student)super.clone();
s.name = this.name;
s.rollNo = this.rollNo;
return s;
}
}
public class Clazz{
private Map students= new HashMap();
public student getStudent(int rollNo){
Student s = students.get(rollNo);
return s.clone();
}
}
Using immutable form of mutable object. e.g. Collections.unmodifiablecollection().
Whenever we return collection or array, always return read-only form. So modifications to collection will not impact the state of the object.
Related
I have a HashMap<CustomClass1, ArrayList<CustomClass2>> called map.
Is it possible to get the ArrayList<CustomClass2> using a String contained within CustomClass1? e.g. I would like to say map.get("zone1") and get the matching ArrayList. This is for a school project and I have to use the HashMap described above. This resembles my code:
public class Main {
public static void main(String [] args) {
HashMap<CustomClass1, ArrayList<CustomClass2>> map = new HashMap<CustomClass1, ArrayList<CustomClass2>();
CustomClass1 example = new CustomClass1("zone1");
map.put(example, new ArrayList<CustomClass2>());
//Later in the code where I don't have the objects in scope (except for map)...
ArrayList<CustomClass2> value = map.get("zone1");
}
class CustomClass1 {
private final String name;
//Additional variables
public CustomClass1 (String name) {
this.name = name;
}
#Override
public int hashCode() {
return name.hashCode();
}
#Override
public boolean equals(Object obj) {
...
String test = (String) obj;
if(test.equals(this.name))
return true;
...
}
}
}
So far I have tried overriding the hashCode() and equals() methods in my CustomClass1 so that it uses the String's hashCode. From my understanding, when I call the map.get(obj) method, it calls obj.hashCode() to find the correct bucket and then obj.equals() to see if it is the correct object. I think I am wrong in thinking this, since I keep getting null values as a result and the equals() method in CustomClass1 is never called.
See comments for workaround/solution.
We have to create a object of any class to use their funtionalities unless those are static functionalities. But why we dont need to create a ArrayList object to use its methods like add, contains etc..
ArrayList<Egg> myList = new ArrayList<Egg>();
myList.add(a);
According to my understanding, myList is just variable which holds ArrayList object's reference of type ArrayList class. So again how can we write following without passing object to myList.
ArrayList<Egg> myList;
myList.add(a);
Complete code:
import java.util.ArrayList;
public class DotCom {
private ArrayList<String> locationCells;
public void setLocationCells(ArrayList<String> loc)
{
locationCells = loc;
}
public String checkYourself(String userInput)
{
String result = "miss";
int index = locationCells.indexOf(userInput);
if (index >= 0) {
locationCells.remove(index);
if (locationCells.isEmpty()) {
result = "kill";
}
else
{
result = "hit";
}
}
return result;
}
//TODO: all the following code was added and should have been included in the book
private String name;
public void setName(String string) {
name = string;
}
}
PS
I am referring heads first java book.
The ArrayList reference is being set in the setter method:
public void setLocationCells(ArrayList<String> loc)
{
locationCells = loc;
}
If this method is not called, and the reference not set before trying to use the ArrayList, then the code will throw a NullPointerException.
Side note: This does not look to be safe code, since it can be easily run incorrectly and so a NPE is easy to create. Better perhaps to set the ArrayList (List is even better) in a constructor.
I have this code in Java.
public class CloneTest implements Cloneable{
String name;
int marks;
public CloneTest(String s, int i) {
name = s;
marks = i;
}
public void setName(String s) {
name = s;
}
public void setMarks(int i) {
marks = i;
}
#Override
public Object clone() {
return new CloneTest(this.name, this.marks);
}
}
I have created one object of this class, and then cloned it. Now, when I change the value of name in one object, the value of name remains unchanged in the other. The strange thing here is in the constructor, I am just using a simple reference for name, not creating a new String for name. Now, since Strings are reference types, I expected the String in the clone to be changed as well. Can anyone tell me what's going on? Thanks in advance!
EDIT
Code Testing
CloneTest real = new CloneTest("Molly", 22);
CloneTest clone = real.clone();
real.setName("Dolly");
I used the "Inspect Variables" feature of BlueJ to check the values.
Assume that original is the name of original CloneTest object, and cloned is the cloned object that you created from original using the clone() method.
This is what happened:
1. Your cloned.name and original.name are pointing at the same object, which in this case was a String.
2. Then you asked your original.name to point to a different String object ("Dolly"). This happens when you assign the new String object ("Dolly") to the reference original.name.
3. But, the cloned.name still points to the first String object ("Dolly").
Hence, cloned.name still prints the 1st String object.
Now, if you are able to change the content of the String object without reassigning the references, then the change in clone.name will reflect in original.name. But for String objects, this is not possible due to the immutability of Strings. However, you can reflect the change from the clone to original with StringBuffers which are mutable strings so to speak. Take a look at this example code for the same: https://gist.github.com/VijayKrishna/5967668
Each instance of your class has different references to an object. You you're just changing reference not modifying object. If you place your string in some holder object, then clone it and set string inside the holder (not a holder reference but string reference inside holder) then you'll have your changes in both of clones
So are you saying you are doing something like:
public void testSomeMethod() {
CloneTest a = new CloneTest("a", 1);
CloneTest b = (CloneTest) a.clone();
a.setName("b");
assertFalse(b.name.equals(a.name));
assertEquals("b", a.name);
assertEquals("a", b.name);
}
?
If so, then all these assertions should pass. Your clone method has reference types in it, and when initially cloned, they refer to the same object. But the setName("...") changes the value that instance points to, not the value of the referred to object.
Get some better clarity along with #vijay answer by looking hash code.
CloneTest real = new CloneTest("Molly", 22);
CloneTest clone = (CloneTest) real.clone();
int h1=real.name.hashCode();
int h2=clone.name.hashCode();
System.out.println("h1 " + h1 + " h2 " + h2); // same
real.setName("sak");
h1=real.name.hashCode();
h2=clone.name.hashCode();
System.out.println("h1 " + h1 + " h2 " + h2); //different
Output :
h1 74525175 h2 74525175
h1 113629 h2 74525175
package com.test;
class Manager implements Cloneable
{
String firstName;
String lastName;
int age;
public Manager(String fname,String lname,int a)
{
this.firstName=fname;
this.lastName=lname;
this.age=a;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
#Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
}
public class TestCloning {
public static void main(String[] args) throws CloneNotSupportedException {
Manager m1=new Manager("Sadik","Tahir",26);
Manager m_clone=(Manager)m1.clone();
Manager m2=m1;
System.out.println("M1 Details:::");
System.out.println("Fisrt Name:"+m1.getFirstName()+",LastName:"+m1.getLastName()+",Age:"+m1.getAge());
System.out.println("Hashcode:"+m1.hashCode());
System.out.println("M_Clone Details:::");
System.out.println("Fisrt Name:"+m_clone.getFirstName()+",LastName:"+m_clone.getLastName()+",Age:"+m_clone.getAge());
System.out.println("Hashcode:"+m_clone.hashCode());
System.out.println("M2 Details:::");
System.out.println("Fisrt Name:"+m2.getFirstName()+",LastName:"+m2.getLastName()+",Age:"+m2.getAge());
System.out.println("Hashcode:"+m2.hashCode());
m1.setFirstName("Afreen");
m1.setLastName("Khan");
m1.setAge(25);
System.out.println("M1 Details:::");
System.out.println("Fisrt Name:"+m1.getFirstName()+",LastName:"+m1.getLastName()+",Age:"+m1.getAge());
System.out.println("Hashcode:"+m1.hashCode());
System.out.println("M_Clone Details:::");
System.out.println("Fisrt Name:"+m_clone.getFirstName()+",LastName:"+m_clone.getLastName()+",Age:"+m_clone.getAge());
System.out.println("Hashcode:"+m_clone.hashCode());
System.out.println("M2 Details:::");
System.out.println("Fisrt Name:"+m2.getFirstName()+",LastName:"+m2.getLastName()+",Age:"+m2.getAge());
System.out.println("Hashcode:"+m2.hashCode());
}
}
So I have a List of Actors and I want to get each Actors dynamic class name.
For example here is my Actor list: People, Birds, Cows.
I want to get as result the same: "People, Birds, Cows" but without a name attribute in the Actors class. Is it possible?
Example code (here instead of list I used array) :
public Area map[][];
map[0][0] = new AntHillArea();
String name = map[0][0].getClass().getName(); //this results "Area" instead of AntHillArea
Edit: There was other problems with the code, getClass().getName() works fine. Thanks anyway.
String className = obj.getClass().getSimpleName();
Update:
public class Test {
public static void main(String[] args) {
Area map[][] = new Area[1][1];
map[0][0] = new AntHillArea();
String name = map[0][0].getClass().getSimpleName(); // returns "AntHillArea"
System.out.println(name);
}
}
class Area {
}
class AntHillArea extends Area {
}
Use getSimpleName method. It gives you only class and will remove any package if having.
You can do this:
class Dog
{
//code
public String getName()
{
return Dog.class.getName();
}
//better
#Override
public String toString()
{
return Dog.class.getName();
}
}
And similarly for each class. Or have a global one as mentioned in other answers as:
public static String getClassName(Class<?> clas){
return clas.getName();
}
To use Dog dog = new Dog(); getClassName(dog.class);
Immutable classes are great but there is one big problem i cant think of a sensible way to solve - cycles.
class Friend {
Set<Friend> friends();
}
How does one model Me having You as a friend who in turn has me as a Friend back ?
IMMUTABILITY
This class from the outside world should definitely be immutable. The value held internally should be constant for the purposes of equality checks.
[[[ Edit: Added code to demonstrate fully immutable concept ]]]
That's why builders are so nice for immutables - they allow mutability during construction to get everything set before you "freeze" it. In this case, I guess you need a Friend builder that supports creating cycles.
final FriendBuilder john = new FriendBuilder().setName("john");
final FriendBuilder mary = new FriendBuilder().setName("mary");
final FriendBuilder susan = new FriendBuilder().setName("susan");
john
.likes(mary)
.likes(susan);
mary
.likes(susan)
.likes(john);
susan
.likes(john);
// okay lets build the immutable Friends
Map<Friend> friends = FriendsBuilder.createCircleOfFriends(john, mary, susan);
Friend immutableJohn = friends.get("john");
Edit: Added immutable example below to demonstrate approach:
There was some discussion in the comments about whether an immutable version was possible.
Fields are final and immutable. A modifiable set is used in the constructor, but it only the unmodifiable reference is kept after construction.
I have another version that uses Guava ImmutableSet for a truly immutable set rather than JDK's unmodifiable wrapper. It works the same, but uses Guava's nice set builder.
Code:
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
/**
* Note: potentially cycle graph - be careful of deep equals/hashCode/toString/etc.
* Immutable
*/
public class Friend {
public static class Builder {
private final String name;
private final Set<Builder> friends =
new HashSet<Builder>();
Builder(final String name) {
this.name = name;
}
public String getName() {
return name;
}
public Set<Builder> getFriends() {
return friends;
}
void likes(final Builder... newFriends) {
for (final Builder newFriend : newFriends)
friends.add(newFriend);
}
public Map<String, Friend> createCircleOfFriends() {
final IdentityHashMap<Builder, Friend> existing =
new IdentityHashMap<Builder, Friend>();
// Creating one friend creates the graph
new Friend(this, existing);
// after the call existingNodes contains all the nodes in the graph
// Create map of the all nodes
final Map<String, Friend> map =
new HashMap<String, Friend>(existing.size(), 1f);
for (final Friend current : existing.values()) {
map.put(current.getName(), current);
}
return map;
}
}
final String name;
final Set<Friend> friends;
private Friend(
final Builder builder,
final Map<Builder, Friend> existingNodes) {
this.name = builder.getName();
existingNodes.put(builder, this);
final IdentityHashMap<Friend, Friend> friends =
new IdentityHashMap<Friend, Friend>();
for (final Builder current : builder.getFriends()) {
Friend immutableCurrent = existingNodes.get(current);
if (immutableCurrent == null) {
immutableCurrent =
new Friend(current, existingNodes);
}
friends.put(immutableCurrent, immutableCurrent);
}
this.friends = Collections.unmodifiableSet(friends.keySet());
}
public String getName() {
return name;
}
public Set<Friend> getFriends() {
return friends;
}
/** Create string - prints links, but does not traverse them */
#Override
public String toString() {
final StringBuffer sb = new StringBuffer();
sb.append("Friend ").append(System.identityHashCode(this)).append(" {\n");
sb.append(" name = ").append(getName()).append("\n");
sb.append(" links = {").append("\n");
for (final Friend friend : getFriends()) {
sb
.append(" ")
.append(friend.getName())
.append(" (")
.append(System.identityHashCode(friend))
.append(")\n");
}
sb.append(" }\n");
sb.append("}");
return sb.toString();
}
public static void main(final String[] args) {
final Friend.Builder john = new Friend.Builder("john");
final Friend.Builder mary = new Friend.Builder("mary");
final Friend.Builder susan = new Friend.Builder("susan");
john
.likes(mary, susan);
mary
.likes(susan, john);
susan
.likes(john);
// okay lets build the immutable Friends
final Map<String, Friend> friends = john.createCircleOfFriends();
for(final Friend friend : friends.values()) {
System.out.println(friend);
}
final Friend immutableJohn = friends.get("john");
}
}
Output:
Node 11423854 {
value = john
links = {
susan (19537476)
mary (2704014)
}
}
Node 2704014 {
value = mary
links = {
susan (19537476)
john (11423854)
}
}
Node 19537476 {
value = susan
links = {
john (11423854)
}
}
The correct way to model a cycle is with a Graph. And a single source code line comment can be enough to enforce inmutability: "can't touch this".
What kind of inmutable enforcement are you looking for? Do you want a a velociraptor to appear whenever you modify the inmutable Set? The difference between mutable and inmutable is just a convention. However, the bits on the RAM can be easily modified and with the Reflection API you can break any encapsulation and data hiding conventions.
Ignoring the velociraptor for a moment, Java does not support an inmutable type. As a workaround, you need to model a datatype that behaves like one.
And for the inmutable property to make sense you need to make Friend an interface, having one implementing class: InmutableFriend, and the construction of the object should fully happen inside the constructor.
Then, since the graph contains cycles, before creating the final inmutable instances you need to store the graph nodes in some mutable temporary structure. You also need to return an unmodifiableSet on the InmutableFriend.friends() method.
Finally, to clone the graph you need to implement a Deep-copy algorithm like Breadth-first search on the Mutable graph. One question though is what happens when the graph is not fully connected.
interface Friend {
public Set<Friend> friends();
}
class MutableFriend {
private Set<MutableFriend> relations = new HashSet<MutableFriend>();
void connect(MutableFriend otherFiend) {
if (!relations.contains(otherFriend)) {
relations.add(otherFiend);
otherFriend.connect(this);
}
}
Friend freeze() {
Map<MutableFriend, InmutableFriend> table = ...;
/*
* FIXME: Implement a Breadth-first search to clone the graph,
* using this node as the starting point.
*
* TODO: If the graph is not connected this won't work.
*
*/
}
}
class InmutableFriend() implements Friend {
private Set<Friend> connections;
public Set<Friend> friends() {
return connections;
}
public InmutableFriend(Set<Friend> connections) {
// Can't touch this.
this.connections = Collections.unmodifiableSet(connections);
}
}
Immutability doesn't need to be compiler-enforced to be valid architecturaly. You can have a legitimate immutable object that takes post-construction initialization parameters. For instance...
private Object something;
public void init( final Object something )
{
if( this.something != null )
{
throw new IllegalStateException();
}
this.something = something
}
The member field "something" isn't final, but it cannot be set more than once either.
A more complex variant based on discussion in comments...
private boolean initialized;
private Object a;
private Object b;
public void init( final Object a, final Object b )
{
if( this.initialized )
{
throw new IllegalStateException();
}
this.initialized = true;
this.a = a;
this.b = b;
}
public Object getA()
{
assertInitialized();
return this.a;
}
public Object getB()
{
assertInitialized();
return this.b;
}
private void assertInitialized()
{
if( this.initialized )
{
throw new IllegalStateException( "not initialized" );
}
}