Google

Sep 30, 2011

Build tools (Maven and Ant) interview questions and answers

Related links:

In my view, open ended interview questions on tools help interviewer  assess a candidate's experience. A typical question would be to ask, what tools would you require to get your job down?

Q. What Java build tools are you experienced with? Which tool will you choose?
A. Ant + Ivy and Maven are the ones have been around for a while now.  Gradle is a less matured build tool that takes a hybrid approach of both Ant and Maven, but looks very promising. Buildr is a build system for Java-based applications, including support for Scala, Groovy and a growing number of JVM languages and tools.

  • Ant is more like a build language offering great flexibility as to how you build. You can write elegant and modular builds with it, but the main problem with this flexibility is that many don't write good build programs. For any non-trivial projects, over time it can lead to duplication of configuration between builds if proper care is not taken. If you need great flexibility in builds or you are working with some legacy code base where you have no control over the convention that Maven expects, then go for Ant. Ant is used for defining and executing the build steps. If you need dependency management to be integrated with Ant, then use Ivy as the dependency manager. 



  • Maven is a build framework. Maven takes the opposite approach to Ant and expects you to completely integrate with the Maven lifecycle. Maven's philosophy is  "Ant With Convention Over Configuration". For example conventions like the "Standard Directory Layout"  (e.g. src/main/java, src/main/resources, src/test/java, src/test/resources, etc) , build cycles, and phases. The Maven plugin mechanism allows for very powerful build configurations, and the inheritance model allows you to define a small set of parent POMs encapsulating your build configurations for the whole enterprise, and individual projects can inherit those configurations, leaving them lightweight. If you want to do anything that is "not the Maven way" you have to write a plugin or use the Ant integration.  Maven also has great tool support with IDEs like eclipse, NetBeans, and IntelliJ. Maven is very handy working with multiple-module projects  and in scenarios where your project consists of several teams working on dependent modules. These modules can be automatically versioned and composed in to the application regularly.
  • Gradle is more of a build language like Ant that incorporates some of the pluses of Maven. Gradle tries to provide a hybrid solution to meet in the middle between Ant and Maven. It uses Ivy's approach for dependency resolution. It allows for convention over configuration but also includes Ant tasks as first class citizens for greater flexibility. It also allows you to use existing Maven/Ivy repositories.
Gradle looks very promising by providing the best of both worlds, but only time will tell its adoption. There are also other factors to consider when choosing a tool like how comfortable your team is with a particular tool, standards and guidelines already in place from a consistency and governance perspective (especially in larger organizations), etc.





    Q. What features do you expect in a build tool?
    A.
    • Compile java code and build jar, war, and ear files for deployment and release.
    • Versioning and dependency management of relevant artifacts and modules.
    • Execute tests and report test results.
    • Run code quality check tools like Sonar, Checkstyle, Findbugs, etc.
    • Environment property substitution
    • Generation of files (e.g. Java source code from WSDL, AspectJ, XSL transformation, etc)
    • Support for cross-platform (UNIX, Windows, etc) and IDEs (e.g eclipse, NetBeans, IntelliJ, etc)
    • Proper documentation and support. 

    Q. What do you understand by Maven's build life cycle?
    A. The build life cycle has a number phases like validate, compile, test, package, integration-test, verify, install, and deploy. Each phase can have 0 or more goals (i.e. tasks). These build phases are executed sequentially to complete the default life cycle. Maven will first validate the project, then will try to compile the sources, run those against the tests, package the binaries (e.g. jar), run integration tests against that package, verify the package, install the verified package to the local repository, then deploy the installed package in a specified environment.

    If you want to do all the phases, you only need to call the last build phase to be executed, which is deploy: This is because if you call a build phase, it will execute not only that build phase, but also every build phase prior to the called build phase.

    A project specific customization of a build cycle involves binding specific goals to specific phases beyond the default settings in the configuration file (i.e. pom.xml). The code snippet below demonstrates how the maven-check-style plugin can be configured in the pom.xml file to execute the "checkstyle" task during the verify phase to evaluate code quality.


    <plugin>
       <groupid>org.apache.maven.plugins</groupid>
       <artifactid>maven-checkstyle-plugin</artifactid> 
       <version>2.2</version>
       <configuration>
          <failonviolation>true</failonviolation>
          <failsonerror>true</failsonerror>
          <consoleoutput>true</consoleoutput>
          <cachefile>true</cachefile>
          <configlocation>${project.parent.basedir}/checkstyle.xml</configlocation>
          <suppressionslocation>${project.parent.basedir}/checkstyle-suppressions.xml</suppressionslocation>
        </configuration>
        <executions>
           <execution>
               <phase>verify</phase>
               <goals>
                  <goal>checkstyle</goal>
               </goals>
            </execution>
         </executions>
    </plugin>
    


    Q. In your experience, what are some of the challenges you faced with maven, and how do you over come them?
    A.When it works which is 95% of the time it's quite nice, and when it doesn't, it can be a challenge. Some of the challenges can be resolved by keeping some tips in mind.

    For example, it might not automatically download a particular artefact from a repository. In this case, you can manually install it to your local repository using the following command.

    mvn install:install-file -DgroupId=javax.transaction -DartifactId=jta -Dpackaging=jar -Dversion=1.0.1B -Dfile=c:\Temp\my-commonservices-1.5.5-SNAPSHOT-tests.jar -DgeneratePom=true 
    

    You have no control over, and limited visibility into, the dependencies specified by your dependencies (i.e. transitive dependencies). Builds will break because different copies of Maven will download different artifacts at different times. Your local build can break again in the future when the dependencies of your dependencies accidentally release new, non-backwards compatible changes without remembering to bump their version number. You can use the following commands and tools to check the dependency tree.

    mvn dependency:tree
    mvn dependency:analyze
    

    The IDEs do provide very useful graphical tools to display. For example, the diagram below shows the plugin that opens a pom.xml file in eclipse.


    This is also very handy to resolve class loading issues due to jar conflicts. Also keep in mind that the dependencies are loaded in the order in which they are declared in the pom.xml. So look at the effective pom with mvn help:effective-pom.

    The following flags are handy for debugging any other maven issues:

    The -X flag shows debug logging, and tee it to a file which you then search for clues of what happened.

    mvn -X install | tee maven_out.txt 
    

    To show the logical pom.xml or settings.xml being used

    mvn help:effective-pom
    mvn help:effective-settings 
    

    Note: options like help:active-profiles and help:all-profiles also come in handy.

    To skip tests to resolve any other issues

    mvn install -DskipTests             //this will skip running tests, but still compile it. 
    mvn install -Dmaven.test.skip=true  //this will skip compilation and execution of tests
    mvn install -Dtest=FooTest          //runs only FooTest
    mvn -Dmaven.surefire.debug test     //to debug your tests 
     
    There are other handy options to resume your build from where maven failed in a multi module project. For example, if your build failed at proj-commons, you can run the build from this module by typing:


    mvn -rf proj-commons clean install   //Resume from proj-commons
    mvn -pl proj-commons, proj-service clean install  //build only 2 modules
    mvn -nsu clean install            //Don't update the snapshots
    

    To debug maven plugins via your IDE like eclipse,set the MAVEN_OPTS environmental variable as shown below

    MAVEN_OPTS="-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000";
    export MAVEN_OPTS
    

    rf --> resume from,  pl --> project list, nsu --> no snapshot update, am --> also make (If project list is specified, also build projects required by the list), amd --> also make dependents (If project list is specified, also build projects that depend on projects on the list)

    Now, use your IDEs remote debugging option by connecting to port 8000. In eclipse, you do this via Run --> open debug dialog and then select "Remote Java application".


    You may also find  useful:

    Labels: ,

    Sep 29, 2011

    Why train with 650+ Java and JEE Interview Questions and Answers?

    Do you have the time to go through 15+ books and 30+ online articles as Java interview preparation?

    This is how I used to prepare for job interviews a long while ago. This is very time consuming and not effective. This blog along with our published books strive to provide concise answers to frequently asked Java interview questions with lots of diagrams, examples, and code snippets. Preparation and practice will make you stand-out from your competition who are at the same level (freshers, intermediate, advanced, etc) or more qualified/experienced than you are. This is mainly because the majority won't bother preparing. By preparing, you are taking the road less traveled.
    Preparation breeds confidence and confidence breeds success.

    Preparation can help you communicate your thoughts more clearly with examples and illustrations. Preparing prior to each interview has immensely helped me fast-track my career. Even with 11+ years of hands-on experience in Java/JEE based design and development, I still brush up on the fundamentals and the 16 key areas using the career companions and the essentials prior to attending  job interviews or important meetings.

    Preparation has always resulted in multiple job offers and much needed job security as a contractor even in a much tougher job market. Understanding what problems are faced by the industry and what the prospective employers are looking for can make a huge difference to one's career. Being in this know how can also open more doors to take on more challenging tasks in varying capacities. This blog and books share my experience. It is quite encouraging from many personal emails and review comments as to how these materials have helped many others.


    What are these 16 key areas? 


    1. Language Fundamentals (LF)
    2. Specification Fundamentals (SF)
    3. Platform Fundamentals (PF)
    4. Design Considerations  (DC)
    5. Design Patterns (DP)
    6. Concurrency Management (CM)
    7. Performance Considerations  (PC)
    8. Memory/Resource Considerations  (MC)
    9. Transaction Management  (TM)
    10. Security (SE)
    11. Scalability  (SC)
    12. Best Practices (BP)
    13. Coding (CO)
    14. Exception Handling (EH)
    15. Software Development Processes (SDP)
    16. Quality of Service  (QoS)

    Not for those who just want to cram prior to job interviews

    Like many good developers who are really bad at interviews for various reasons, many job interviewers are also not good at weeding out the real talents from those who memorized the answers. These resources are not meant for quick interview success by memorizing the answers. These resources are for brushing-up and proactively applying what you learn here on the job to impress your peers and superiors. Your key focus must be to take the time to understand the concepts and the 16 key areas. Nothing beats good hands-on experience and lots of coding. That is why my books and blogs are full of code and examples.


    How do these resources help you fast-track your Java career?

    If you rely only on your own experience, it will take you a lot longer to get a good handle on these 16 key areas. The best way to fast-track your career is to pro-actively learn these 16 key areas from others' experience, good books and online articles, and apply what you had learned to practice. This will help you earn a reputation as a "go to person" through your contributions and achievements at work.

    Why use these resources?

    Standout from the candidates who are more qualified than you are
     
    The resources provided in here are full of practical examples, diagrams, code snippets, and cross references to provide clear and concise answers to most of the very frequently asked Java interview questions. Each question in the book is tagged with one or more of the 16 key areas.

    The questions and answers approach also give a different perspective to clearing up your fundamentals in Java. The answers are detailed enough to learn the fundamentals while preparing for job interviews, code review sessions and technical team meetings. Depending on your level of experience, some answers may require additional research on Google to get a better understanding.

    Good caliber professionals are promoted and paid well for thinking, reasoning, solving business problems, and getting things done by drawing on their experiences and skills

    • to look at the big picture.
    • to pay attention to details.
    • for applying the fundamentals/key areas to solve business problems.
    • for complimenting their technical "know how" with good soft skills and attitude to get things done.

    Should you get certified in Java?

    Certifications combined with many other "know hows" can make you a better programmer. At the end of the day, it is what you know and can do makes the real difference and not what some documents declare what you know. Certified or not, you will be grilled in job interviews. In my view, favor continuous learning and hands-on experience. Learn more here Why Java certifications are alone not enough?. I never bothered getting certified. If it works for you, and employers are specifically asking for in the job advertisements, then go for it.



    How do you get your first break with Java?

    In summary, the best way for people to get their first break with Java is to:
    • Learn the core concepts and work on a Java programming project. Start a little Java-based project of your own. 
    • Participate in an open-source project - even if it's just submitting a little patch to see how things fit together.
    • Update your resume based on the experience and skills you acquired in the above steps to get a break by acquiring an entry level job, even volunteer work should help you open more doors. More on this here -- How to get the much needed experience in Java?
    As a fresher, here is my 8 hour full time job for you to land a job sooner.

    Note: Even though the certifications can be useful, don't wait until you've passed certifications before trying to get work. Recruiters don't hire just because someone has a certification. Certified or not, your Java knowledge and experience will be tested at job interviews.


    What Java questions are asked in job interviews?

    In career forums,  many ask for Java interview questions for 2 year experience, 4 year experience, etc. My advise is that if you brush up on the Java/JEE basics and the 16 key areas, you should be fine. You have no control over what questions are going to be asked. Don't get overwhelmed by the number of questions and answers covered in my books and here. The interviews are not technical contests to see who gets most questions right. It is all about convincing your prospective employer that you can get the job done. The open-ended questions give you a great opportunity to sell your strengths in these 16 key areas along with your soft skills, and personal attributes to make a good impression. 

    Having said this, there are some very basic "must know" Java  interview questions that can make or break the deal. These "must know" Java interview questions are covered in my books (tagged as FAQs) and blog entries along with many open-ended questions and answers. You hold the key to your Java career success, and hope this blog assists in your quest to succeed in your career.


    What gives the real job security?

    There is no such thing as real job security in IT. Keeping your knowledge and skills sharp and current along with good networking, marketing, and soft skills is the best way to achieve real job security. The jobs offered on a contract basis is on the rise, and can be more rewarding for some.  

    "The people who win are not necessarily the smartest people, but they are the people who are able to sustain drive, commitment, passion and engagement" -- by David Maiser


    Is Java still doing well?

    Java is still going very strong and here is the TIOBE index. This index also shows that JavaScript is growing strongly as well. So, it is really worth to brush up on JavaScript Interview Questions and Answers. Most applications built now a days are rich and have lots of JavaScript.


    Do you have a question?

    If you have any specific question or would like to provide any constructive criticisms, then contact me via email java-interview@hotmail.com. Hope this blog and books help you take the road less traveled.

    Labels:

    Sep 23, 2011

    Enterprise Java interview questions and answers

    The following  most popular enterprise Java (i.e. JEE or J2EE) interview questions and answers are frequently asked in job interviews. If you are a more experienced professional and have a good understanding of the high level basics, then try How does JEE 6 differ from JEE 5?


    Q. Explain 3-tier or n-tier architecture  used by JEE?
    A. This is a very commonly asked question. Be prepared to draw some diagrams on the board. The JEE platform is a multi-tiered system. A tier is a logical or functional partitioning of a system.


    Each tier is assigned a unique responsibility in a 3-tier system. Each tier is logically separated and loosely coupled from each other, and may be distributed.

    Client tier represents Web browser, a Java or other application, Applet, WAP phone etc. The client tier makes requests to the Web server who will be serving the request by either returning static content if it is present in the Web server or forwards the request to either Servlet or JSP in the application server for either static or dynamic content.

    Presentation tier encapsulates the presentation logic required to serve clients. A Servlet or JSP in the presentation tier intercepts client requests, manages logons, sessions, accesses the business services, and finally constructs a response, which gets delivered to client.

    Business tier provides the business services. This tier contains the business logic and the business data. All the business logic is centralized into this tier as opposed to 2-tier systems where the business logic is scattered between the front end and the backend. The benefit of having a centralized business tier is that same business logic can support different types of clients like browser, WAP (Wireless Application Protocol) client, other stand-alone applications written in Java, C++, C# etc.

    Integration tier is responsible for communicating with external resources such as databases, legacy systems, ERP systems, messaging systems like MQSeries etc. The components in this tier use JDBC, JMS, J2EE Connector Architecture (JCA) and some proprietary middleware to access the resource tier.

    Resource tier is the external resource such as a database, ERP system, Mainframe system etc responsible for storing the data. This tier is also known as Data Tier or EIS (Enterprise Information System) Tier.



    Note: On a high level J2EE can be construed as a 3-tier system consisting of Client Tier, Middle Tier (or Application Tier) and Data Tier. But logically or functionally J2EE is a multi-tier (or n-tier) platform.


    Q. What are the advantages of a 3-tiered or n-tiered application?
    A. 3-tier or multi-tier architectures force separation among presentation logic, business logic and database logic. Let us look at some of the key benefits:

    • Manageability: Each tier can be monitored, tuned and upgraded independently and different people can have clearly defined responsibilities.
    • Scalability: More hardware can be added and allows clustering (i.e. horizontal scaling). 
    • Maintainability: Changes and upgrades can be performed without affecting other components.
    • Availability: Clustering and load balancing can provide availability.
    • Extensibility: Additional features can be easily added.

    The following diagram gives you a bigger picture of  the logical tiers and the components.




    Q. Explain MVC architecture relating to J2EE?
    A. This is also a very popular interview question. MVC stands for Model-View-Controller architecture. It divides the functionality of displaying and maintaining of the data to minimize the degree of coupling (i.e. promotes loose coupling) between components. It is often used by applications that need the ability to maintain multiple views like HTML, WML, Swing, XML based Web service etc of the same data. Multiple views and controllers can interface with the same model. Even new types of views and controllers can interface with a model without forcing a change in the model design.


    A model represents the core business logic and state. A model commonly maps to data in the database and will also contain core business logic. 

    A view renders the contents of a model. A view accesses the data from the model and adds display logic to present the data.

    A controller acts as the glue between a model and a view. A controller translates interactions with the view into actions to be performed by the model. User interactions in a Web application appear as GET and POST HTTP requests. The actions performed by a model include activating business processes  or changing the state of the model. Based on the user interactions and the outcome of the model actions, the controller responds by selecting an appropriate view.

    Q. What are ear, war and jar files? What are J2EE Deployment Descriptors?
    A. The ear, war and jar are standard application deployment archive files. Since they are a standard, any application server (at least in theory) will know how to unpack and deploy them.

    An EAR file is a standard JAR file with an “.ear” extension, named from Enterprise ARchive file. A J2EE application with all of its modules is delivered in EAR file. JAR files can’t have other JAR files. But EAR and WAR (Web ARchive) files can have JAR files.

    An EAR file contains all the JARs and WARs belonging to an application. JAR files contain the EJB classes and WAR files contain the Web components (JSPs, Servlets and static content like HTML, CSS, GIF etc). The J2EE application client's class files are also stored in a JAR file. EARs, JARs, and WARs all contain one or more XML-based deployment descriptor(s).

    Deployment Descriptors

    A deployment descriptor is an XML based text file with an “.xml” extension that describes a component's deployment settings. A J2EE application and each of its modules has its own deployment descriptor.




    More related questions and answers at

    Labels:

    Java coding interview questions and answers

    Core Java Coding Questions and Answers for beginner to intermediate level

    Q1 Q2 Q3 Q4 Q5 - Q8 Q9 Q10 Q11 Q12 - Q14 Q15

    These Java coding questions and answers are extracted from the book " Core Java Career Essentials. Good interviewers are more interested in your ability to code rather than knowing the flavor of the month framework.


    Q. Can you write an algorithm to swap two variables?
    A.


    package algorithms;
    
    public class Swap {
        
        public static void main(String[ ] args) {
            int x = 5;
            int y = 6;
            
            //store 'x' in a temp variable
            int temp = x;
            x = y;
            y = temp;
            
            System.out.println("x=" + x + ",y=" + y);
       }
    }
    
    

    Q. Can you write  code to bubble sort { 30, 12, 18, 0, -5, 72, 424 }?
    A.

    package algorithms;
    import java.util.Arrays;
    
    public class BubbleSort {
    
        public static void main(String[ ] args) {
            Integer[ ] values = { 30, 12, 18, 0, -5, 72, 424 };
            int size = values.length;
            System.out.println("Before:" + Arrays.deepToString(values));
    
            for (int pass = 0; pass < size - 1; pass++) {
                for (int i = 0; i < size - pass - 1; i++) {
                    // swap if i > i+1
                    if (values[i] > values[i + 1])
                        swap(values, i, i + 1);
                }
            }
    
            System.out.println("After:" + Arrays.deepToString(values));
        }
    
        private static void swap(Integer[ ] array, int i, int j) {
            int temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    } 
    
    

    Q. Is there a more efficient sorting algorithm?
    A. Although bubble-sort is one of the simplest sorting algorithms, it's also one of the slowest. It has the O(n^2) time complexity. Faster algorithms include quick-sort and heap-sort. The Arrays.sort( ) method uses the quick-sort algorithm, which on average has O(n * log n) but can go up to O(n^2) in a worst case scenario, and this happens especially with already sorted sequences.

    Q. Write a program that will return whichever value is nearest to the value of 100 from two given int numbers?
    A. You can firstly write the pseudo code as follows:

    • Compute the difference to 100.
    • Find out the absolute difference as negative numbers are valid.
    • Compare the differences to find out the nearest number to 100.
    • Write test cases for +ve, -ve, equal to, > than and < than values.
    package chapter2.com;
    
    
    
    public class CloseTo100 {
    
        
    
        public static int calculate(int input1, int input2) {
    
             //compute the difference. Negative values are allowed as well 
    
            int iput1Diff = Math.abs(100 - input1);
    
            int iput2Diff = Math.abs(100 - input2);
    
            
    
            //compare the difference
    
            if (iput1Diff < iput2Diff) return input1;
            else if (iput2Diff < iput1Diff) return input2;
            else return input1;          //if tie, just return one
        }
        
        public static void main(String[ ] args) {
            //+ve numbers
            System.out.println("+ve numbers=" + calculate(50,90));
            
            //-ve numbers
            System.out.println("-ve numbers=" + calculate(-50,-90));
            
            //equal numbers
            System.out.println("equal numbers=" + calculate(50,50));
            
            //greater than 100
            System.out.println(">100 numbers=" + calculate(85,105));
    
            System.out.println("<100 numbers=" + calculate(95,110));
        } 
    }
    
    


    Output:

    +ve numbers=90
    -ve numbers=-50
    equal numbers=50
    >100 numbers=105
    <100 numbers=95


    Q. Can you write a method that reverses a given String?
    A.
    public class ReverseString {
    
        
    
        public static void main(String[ ] args) {
    
            System.out.println(reverse("big brown fox"));
    
            System.out.println(reverse(""));       
    
        }
    
    
    
        public static String reverse(String input) {
    
            if(input == null || input.length( ) == 0){
    
                return input;
    
            }
    
           
    
            return new StringBuilder(input).reverse( ).toString( ); 
    
        }
    
    }
    
    

    It is always a best practice to reuse the API methods as shown above with the StringBuilder(input).reverse( ) method as it is fast, efficient (uses bitwise operations) and knows how to handle Unicode surrogate pairs, which most other solutions ignore. The above code handles null and empty strings, and a StringBuilder is used as opposed to a thread-safe StringBuffer, as the StringBuilder is locally defined, and local variables are implicitly thread-safe.

    Some interviewers might probe you to write other lesser elegant code using either recursion or iterative swapping. Some developers find it very difficult to handle recursion, especially to work out the termination condition. All recursive methods need to have a condition to terminate the recursion.


    public class ReverseString2 {
        
        public String reverse(String str) {
            // exit or termination condition
            if ((null == str) || (str.length( )  <= 1)) {
                return str;
            }
            
            // put the first character (i.e. charAt(0)) to the end. String indices are 0 based. 
            // and recurse with 2nd character (i.e. substring(1)) onwards  
            return reverse(str.substring(1)) + str.charAt(0);
        }
    }
    
    
    There are other solutions like
    public class ReverseString3 {
        
        public String reverse(String str) {
            // validate
            if ((null == str) || (str.length( )  <= 1)) {
                return str;
            }
            
            char[ ] chars = str.toCharArray( );
            int rhsIdx = chars.length - 1;
            
            //iteratively swap until exit condition lhsIdx < rhsIdx is reached
            for (int lhsIdx = 0; lhsIdx < rhsIdx; lhsIdx++) {
                char temp = chars[lhsIdx];
                chars[lhsIdx] = chars[rhsIdx];
                chars[rhsIdx--] = temp;
            }
            
            return new String(chars);
        }
    } 
    
    


    Or
     
    public class ReverseString4 {
         
        public String reverse(String str) {
            // validate
            if ((null == str) || (str.length( )  <= 1)) {
                return str;
            }
             
            
            char[ ] chars = str.toCharArray( );
            int length = chars.length;
            int last = length - 1;
             
            //iteratively swap until reached the middle
            for (int i = 0; i < length/2; i++) {
                char temp = chars[i];
                chars[i] = chars[last - i];
                chars[last - i] = temp;
            }
             
            return new String(chars);
        }
        
        
        public static void main(String[] args) {
          String result = new ReverseString4().reverse("Madam, I'm Adam");
          System.out.println(result);
       }
    } 
    
    
    Relevant must get it right coding questions and answers


    Labels: , ,

    Java OO Interview Questions and Answers

    If you asked me to pick a section that is most popular with the interviewers, this is it. If you don't perform well in Object Oriented (i.e. OO) programming , your success rate in interviews will be very low. Good interviewers will be getting you to analyze or code for a particular scenario. They will be observing your decisions with interfaces and classes, and question your decisions to ascertain your technical skills, analytical skills, and communication skills. You can't memorize your answers. This section requires some level of experience to fully understand.

    In this blog, I cover some OO interview questions and answers. If you are interested in more questions and answers, the "Core Java Career Essentials" book has a whole chapter dedicated for OO questions and answers with enough examples to get through your OO interview questions with flying colors.

    Q. How do you know that your classes are badly designed?
    A.

    • If your application is fragile – when making a change, unexpected parts of the application can break.
    • If your application is rigid – it is hard to change one part of the application without affecting too many other parts.
    • If your application is immobile – it is hard to reuse the code in another application because it cannot be separated.

    Overly complex design is as bad as no design at all. Get the granularity of your classes and objects right without overly complicating them. Don't apply too many patterns and principles to a simple problem. Apply them only when they are adequate. Don't anticipate changes in requirements ahead of time. Preparing for future changes can easily lead to overly complex designs. Focus on writing code that is not only easy to understand, but also flexible enough so that it is easy to change if the requirements change.


    Q. Can you explain if the following classes are badly designed?
    The following snippets design the classes & interfaces for the following scenario. Bob, and Jane work for a restaurant. Bob works as manager and a waiter. Jane works as a waitress. A waiter's behavior is to take customer orders and a manager's behavior is to manage employees.


    package badrestaurant;
    
    public interface Person {}
    
    

    package badrestaurant;
    
    public interface Manager extends Person {
        public void managePeople( );
    }
    

    package badrestaurant;
    
    public interface Waiter extends Person {
        public void takeOrders( ); 
    }
    


    package badrestaurant;
    
    public class Bob implements Manager, Waiter {
    
        @Override
        public void managePeople( ) {
            //implementation goes here
        }
    
        @Override
        public void takeOrders( ) {
            //implementation goes here
        }
    }
    
    

    package badrestaurant;
    
    public class Jane implements Waiter {
    
        @Override
        public List<string> takeOrders( ) {
            //implementation goes here
        }
    }
    

    The Restaurant class uses the above classes as shown below.

    package badrestaurant;
    
    public class Restaurant {
        
        public static void main(String[ ] args) {
            
            Bob bob = new Bob( );
            bob.managePeople( );
            bob.takeOrders( );
            
            Jane jane = new Jane( );
            jane.takeOrders( );
        }
    }
    
    

    A. The above classes are badly designed for the reasons described below.

    The name should be an attribute, and not a class like Bob or Jane. A good OO design should hide non-essential details through abstraction. If the restaurant employs more persons, you don't want the system to be inflexible and create new classes like Peter, Jason, etc for every new employee.

    The above solution's incorrect usage of the interfaces for the job roles like Waiter, Manager, etc will make your classes very rigid and tightly coupled by requiring static structural changes. What if Bob becomes a full-time manager? You will have to remove the interface Waiter from the class Bob. What if Jane becomes a manager? You will have to change the interface Waiter with Manager.

    The above drawbacks in the design can be fixed as shown below by asking the right questions. Basically waiter, manager, etc are roles an employee plays. You can abstract it out as shown below.


    package goodrestuarant;
    
    public interface Role {
        public String getName( );
        public void perform( );
    }
    
    

    package goodrestuarant;
    
    public class Waiter implements Role {
        
        private String roleName;
        
        public Waiter(String roleName) {
            this.roleName = roleName;
        }
    
        @Override
        public String getName( ) {
           return this.roleName;
        }
    
        @Override
        public void perform( ) {
           //implementation goes here
        }
    }
    

    package goodrestuarant;
    
    public class Manager implements Role {
        
        private String roleName;   
        
        public Manager(String roleName) {
            this.roleName = roleName;
        }
    
        @Override
        public String getName( ) {
           return this.roleName;
        }
    
        @Override
        public void perform( ) {
           //implementation goes here
        }
    }
    
    


    The Employee class defines the employee name as an attribute as opposed to a class. This makes the design flexible as new employees can be added at run time by instantiating new Employee objects with appropriate names. This is the power of abstraction. You don't have to create new classes for each new employee. The roles are declared as a list using aggregation (i.e. containment), so that new roles can be added or existing roles can be removed at run time as the roles of employees change. This makes the design more flexible.

    package goodrestuarant;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Employee {
        
        private String name;
        private List<role> roles = new ArrayList<role>(10);
        
        public Employee(String name){
            this.name = name;
        }
    
        public String getName( ) {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public List<role> getRoles( ) {
            return roles;
        }
    
        public void setRoles(List<role> roles) {
            this.roles = roles;
        }
        
        public void addRole(Role role){
            if(role == null){
                throw new IllegalArgumentException("Role cannot be null");
            }
            roles.add(role);
        }
        
        public void removeRole(Role role){
            if(role == null){
                throw new IllegalArgumentException("Role cannot be null");
            }
            roles.remove(role);
        }
    }

    The following Restaurant class shows how flexible, extensible, and maintainable the above design is.

    package goodrestuarant;
    
    import java.util.List;
    
    public class Restaurant {
        
        public static void main(String[ ] args) {
            
            Employee emp1 = new Employee ("Bob");
            Role waiter = new Waiter("waiter");
            Role manager = new Manager("manager");
            
            emp1.addRole(waiter);
            emp1.addRole(manager);
            
            Employee emp2 = new Employee("Jane");
            emp2.addRole(waiter);
            
            List<role> roles = emp1.getRoles( );
            for (Role role : roles) {
                role.perform( );
            }
            
            //you can add more employees or change roles based on 
            //conditions here at runtime. More flexible.   
        }
    }
    
    


    Q. What do you achieve through good class and interface design?
    A.

    • Loosely coupled classes, objects, and components enabling your application to easily grow and adapt to changes without being rigid or fragile.
    • Less complex and reusable code that increases maintainability, extendability and testability.

    Q. What are the 3 main concepts of OOP?
    A. Encapsulation, polymorphism, and inheritance are the 3 main concepts or pillars of an object oriented programming. Abstraction is another important concept that can be applied to both object oriented and non object oriented programming. [Remember: a pie ? abstraction, polymorphism, inheritance, and encapsulation.]


    Q. What problem(s) does abstraction and encapsulation solve?
    A. Both abstraction and encapsulation solve same problem of complexity in different dimensions. Encapsulation exposes only the required details of an object to the caller by forbidding access to certain members, whereas an abstraction not only hides the implementation details, but also provides a basis for your application to grow and change over a period of time. For example, if you abstract out the make and model of a vehicle as class attributes as opposed to as individual classes like Toyota, ToyotaCamry, ToyotaCorolla, etc, you can easily incorporate new types of cars at runtime by creating a new car object with the relevant make and model as arguments as opposed to having to declare a new set of classes.


    Q. How would you go about designing a “farm animals” application where animals like cow, pig, horse, etc move from a barn to pasture, a stable to paddock, etc? The solution should also cater for extension into other types of animals like circus animals, wild animals, etc in the future.


    A.

    package subclass0;
    
    public abstract class Animal {
        private int id;                                // id is encapsulated
    
        public Animal(int id) {
            this.id = id;
        }
    
        public int getId( ) {
            return id;
        }
    
        public abstract void move(Location location);
    }
    
    

    package subclass0;
    
    public class FarmAnimal extends Animal {
    
        private Location location = null;                   // location is encapsulated
    
        public FarmAnimal(int id, Location defaultLocation) {
            super(id);
            validateLocation(defaultLocation);
            this.location = defaultLocation;
        }
    
        public Location getLocation( ) {
            return location;
        }
    
        public void move(Location location) {
            validateLocation(location);
            System.out.println("Id=" + getId( ) + " is moving from "
                    + this.location + " to " + location);
            this.location = location;
        }
    
        private void validateLocation(Location location) {
            if (location == null) {
                throw new IllegalArgumentException("location=" + location);
            }
        }
    }
    

    package subclass0;
    
    public enum Location  {
        Barn, Pasture, Stable, Cage, PigSty, Paddock, Pen
    }
    
    


    package subclass0;
    
    public class Example {
    
        public static void main(String[ ] args) {
            Animal pig = new FarmAnimal(1, Location.Barn);
            Animal horse = new FarmAnimal(2, Location.Stable);
            Animal cow = new FarmAnimal(3, Location.Pen);
    
            pig.move(Location.Paddock);
            horse.move(Location.Pen);
            cow.move(Location.Pasture);
        }
    }  
    
    

    Output:

    Id=1 is moving from Barn to Paddock
    Id=2 is moving from Stable to Pen
    Id=3 is moving from Pen to Pasture

    In the above example, the class FarmAnimal is an abstraction used in place of an actual farm animal like horse, pig, cow, etc. In future, you can have WildAnimal, CircusAnimal, etc extending the Animal class to provide an abstraction for wild animals like zebra, giraffe, etc and circus animals like lion, tiger, elephant, etc respectively. An Animal is a further abstraction generalizing FarmAnimal, WildAnimal, and CircusAnimal. The Location is coded as an enumeration for simplicity. The Location itself can be an abstract class or an interface providing an abstraction for OpenLocation, EnclosedLocation, and SecuredLocation further abstracting specific location details like barn, pen, pasture, pigsty, stable, cage, etc. The location details can be represented with attributes like “name”, “type”, etc.

    The FarmAnimal class is also well encapsulated by declaring the attribute “location” as private. Hence the “location” variable cannot be directly accessed. Assignment is only allowed through the constructor and move(Location location) method, only after a successful precondition check with the validateLocation(...) method. The validateLocation(...) itself marked private as it is an internal detail that does not have to be exposed to the caller. In practice, the public move(..) method can make use of many other private methods that are hidden from the caller. The caller only needs to know what can be done with an Animal. For example, they can be moved from one location to another. The internal details as to how the animals are moved is not exposed to the caller. These implementation details are specific to FarmAnimal, WildAnimal, and CircusAnimal classes.

    The above code does not satisfy the following questions.

    1) Why does the Employee class need to be mutable?
    2) Why aren't the roles defensive copied?
    3) Why would the Employee need to know how to add and remove roles?
    4) Waiter and Manager are placed in a collection but don't override hashcode and equals. That will cause the contains method on a List to not behave as expected.
    5) You check if the role is null then throw an IllegalArgumentException, that should instead be a NullPointerException.
    6) The code that checks for null roles being added is duplicated, thus defeating the DRY principle.


    Some of the above questions are answered in How to write immutable Java classes?

    Labels: ,

    Sep 21, 2011

    Java Collection Interview Questions and Answers

    Q. Can you name 2 Core Java APIs widely used in Java?
    A. (1) Java Collection API
         (2) String class

    You need data structures to store data retrieved from the database, manipulate by filtering, sorting, transforming, etc and then present it to the user via Web GUI, reports (e.g. PDF, online), etc.


    Other relevant Java Interview Questions and Answers on data structures



    Java Collections Framework (i.e. Data structures)  contains most commonly asked Java interview questions. A good understanding of Collections framework is required to understand and leverage many powerful features of Java technology. Here are a few important practical questions which can be asked in a Core Java interview.


    Q. What are the common data structures, and where would you use them?
    A.  Many leave out Trees and Graphs.  Trees and Graphs are very useful data structures as well. As you provide your answer,  the interviewer may further quiz you on.

    Q. How you would go about implementing your own List, Set, and Map?

    Whilst it is not recommended to write your own implementation when Java provides proven and tested implementations, the interviewer is testing your understanding on data structures. My book entitled "Core Java Career Essentials" covers this in more detail with diagrams, examples, and code.


    Here are the common data structures.

    Arrays are the most commonly used data structure. Arrays are of fixed size, indexed, and all containing elements are of the same type (i.e. a homogenous collection). For example, storing employee details just read from the database as EmployeeDetail[ ], converting and storing a string as a byte array for further manipulation or processing, etc. Wrap an array in a class to protect it from being inadvertently altered. This would be true for other data structures as well.


    Lists are known as arrays that can grow. These data structures are generally backed by a fixed sized array and they re-size themselves as necessary. A list can have duplicate elements. For example,  adding new line items to an order that stores its line items as a list, removing all expired products from a list of products, etc. Initialize them with an appropriate initial size to minimize the number of re-sizes

    Sets are like lists but they do not hold duplicate elements. Sets can be used when you have a requirement to store unique elements.

    Stacks allow access to only one data item, which is the last item inserted (i.e. Last In First Out - LIFO). If you remove this item, you can access the next-to-last item inserted, and so on. The LIFO is achieved through restricting access only via methods like peek( ), push( ), and pop( ). This is useful in many programing situations like parsing mathematical expressions like (4+2) * 3, storing methods and exceptions in the order they occur, checking your source code to see if the brackets and braces are balanced properly, etc.

    The LIFO access mechanism used by a stack has many practical uses.  For example, Evaluation of expressions / syntax Parsing, validating and parsing XML, undo sequence in a text editor, pages visited history in a web browser, etc. Java interview questions and answers on stack data structure


    Queues are somewhat like a stack, except that in a queue the first item inserted is the first to be removed (i.e. First In First Out – FIFO). The FIFO is achieved through restricting access only via methods like peek( ), offer( ), and poll( ). For example,  waiting in a line for a bus, a queue at the bank or super market teller, etc.

    Here is a code example of a blocking queue with multiple threads.


    LinkedLists are data structures made of nodes, where each node contains data and a reference to the next node, and possibly  to the previous node as well for a doubly linked list. For example, a stack or queue can be implemented with a linked list or a doubly linked list because you can insert and delete at both ends. There would also be other situations where data will be frequently inserted and deleted from the middle. The Apache library provides a TreeList implementation, which is a good replacement for a LinkedList as it performs much better than a LinkedList at the expense of using a little more memory.  This means a LinkedList is rarely a good choice of implementation.

    ArrayList is a good general purpose list implementation. An ArrayList is faster than a TreeList for most operations except inserting and removing in the middle of the list. A TreeList implementation utilizes a tree structure internally to ensure that all insertions and removals are O(log n). This provides much faster performance than both an ArrayList and a LinkedList where elements are inserted and removed repeatedly from anywhere in the list. 


    class Link {
       private int id;                    // data
       private String name;               // data
       private Link next;                 // reference to next link
    }
    


    HashMaps are amortized constant-time access data structures that map keys to values. This data structure is backed by an array. It uses a hashing functionality to identify a location, and some type of collision detection algorithm is used to handle values that hash to the same location. For example, storing employee records with employee number as the key, storing name/value pairs read from a properties file, etc. Initialize them with an appropriate initial size to minimize the number of re-sizes.

    Trees are the data structures that contain nodes with optional data elements and one or more child elements, and possibly each child element references the parent element to represent a hierarchical or ordered set of data elements.  For example, a hierarchy of employees in an organization, storing the XML data as a hierarchy, etc.  If every node in a tree can have utmost 2 children, the tree is called a binary tree. The binary trees are very common because the shape of a binary tree makes it easy to search and insert data. The edges in a tree represent quick ways to navigate from node to node.


    Java does not provide an implementation for this but it can be easily implemented as shown below. Just make a class Node with an ArrayList holding links to other Nodes.

    package bigo;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Node {
        private String name;
        private List<node> children = new ArrayList<node>( );
        private Node parent;
        
        public Node getParent( ) {
            return parent;
        }
    
        public void setParent(Node parent) {
            this.parent = parent;
        }
    
        public Node(String name) {
            this.name = name;
        }
        
        public void addChild(Node child) {
            children.add(child);
        }
        
        public void removeChild(Node child) {
            children.remove(child);
        }
       
        public String toString( ) {
            return name;
        }
     }
    


    Graphs are data structures that represent arbitrary relationships between members of any data sets that can be represented as networks of nodes and edges. A tree structure is essentially a more organized graph where each node can only have one parent node. Unlike a tree, a graph's shape is dictated by a physical or abstract problem. For example, nodes (or vertices)  in a graph may represent cities, while edges may represent airline flight routes between the cities.




    To make a Java graph class, you will have to work out the way in which the information can be stored and accessed. A graph will be using some of the data structures mentioned above. The Java API does not provide an implementation for this. But there are a number of third party libraries like  JUNG, JGraphT, and JDSL (does not seem to support generics).The book "Core Java Career Essentials" covers working examples using Java.

    Q. What do you know about the big-O notation and can you give some examples with respect to different data structures?
    A. The Big-O notation simply describes how well an algorithm scales or performs in the worst case scenario as the number of elements in a data structure increases.  The Big-O notation can also be used to describe other behavior such as memory consumption. At times you may need to choose a slower algorithm because it also consumes less memory. Big-o notation can give a good indication about performance for large amounts of data, but the only real way to know for sure is to have a performance benchmark with large data sets to take into account things that are not considered in Big-O notation like paging as virtual memory usage grows, etc. Although benchmarks are better, they aren't feasible during the design process, so Big-O complexity analysis is the choice.

    The algorithms used by various data structures for different operations like search, insert and delete fall into the following performance groups like constant-time O(1), linear O(n), logarithmic O (log n), exponential O (c to the power n), polynomial O(n to the power c), quadratic O (n to the power 2) and factorial O (n!) where n is the number of elements in the data structure. It is generally a tradeoff between performance and memory usage. Here are some examples.

    Example 1: Finding an element in a HashMap is usually a constant-time, which is  O(1) . This is a constant time because a hashing function is used to find an element, and computing a hash value does not depend on the number of elements in the HashMap.

    Example 2:  Linear search of an array, list, and LinkedList is linear, which is O(n). This is linear because you will have to search the entire list. This means that if a list is twice as big, searching it will take twice as long.

    Example 3: An algorithm that needs to compare every element in an array to sort the array has polynomial complexity, which is O (n2). A nested for loop is O (n2).  An example is shown under sorting algorithms.

    Example 4: Binary search of a sorted array or ArrayList is logarithmic, which is O(log n). Searching an element in a LinkedList normally requires O(n). This is one of the disadvantages of LinkedList over the other data structures like an ArrayList or array offering a O (log n) performance, which offers better performance than O(n)  as the number of elements increases. A logarithmic running times mean, if 10 items take at most x amount of time, 100 items will take say at most 2x amount of time, and 10,000 items will take at most  4x. If you plot this on a graph, the time decreases as  n (i.e. number of items) increases.


    Q. What can you tell about the performance of a HashMap compared to a TreeMap? Which one would you prefer?
    A.  A balanced tree does have O (log n) performance. The TreeMap class in Java maintains key/value objects in a sorted order by using a red-black tree. A red-black tree is a balanced binary tree. Keeping the binary tree balanced ensures the fast insertion, removal, and look-up time of O (log n). This is not as fast as a HashMap, which is O(1) ,  but the TreeMap has the advantage of that the keys are in sorted order which opens up a lot of other capabilities.  



    Q. Which one to choose? 
    A.The decision as to using an unordered collection like a HashSet  or HasMap versus   using a sorted data structure like a TreeSet  or TreeMap depends mainly on the usage pattern, and to some extent on the data size and the environment you run it on.   The practical reason for keeping the elements in sorted order is for frequent and faster retrieval of sorted data if the inserts and updates are frequent. If the need for a sorted result is infrequent like prior to producing a report or running a batch process, then maintaining an unordered collection and sorting them only when it is really required with Collections.sort(...) could sometimes be more efficient than maintaining the ordered elements. This is only an opinion, and no one can offer you a correct answer. Even the complexity theories like Big-O notation like O(n) assume possibly large values of n.  In practice, a O(n) algorithm can be much faster than a O(log n) algorithm, provided the data set that is handled is sufficiently small. One algorithm might perform better on an AMD processor than on an Intel. If your system is set up to swap, disk performance need to be considered. The only way to confirm the efficient usage is to test and measure both performance and memory usage with the right data size. Measure both the approaches on your chosen hardware to determine, which is more appropriate.


    Q. What is the tradeoff between using an unordered array versus an ordered array?
    A. The major advantage of an ordered array is that the search times are much faster with O (log n) than an unordered array, which is O (n) for larger values of n. The disadvantage of an ordered array is that the insertion takes longer (i.e. O (n) ) because all the data with higher values need to be moved to make room. The insertion for an unordered array takes constant time of O(1). This means, it does not depend on the number of elements. The following code snippet demonstrates adding an element to both ordered and unordered array.

    Inserting an element into an unsorted array


    package bigo;
    
    import java.util.Arrays;
    
    public class InsertingElementsToArray {
    
        public static void insertUnsortedArray(String toInsert) {
    
            String[ ] unsortedArray = { "A", "D", "C" };
    
            String[ ] newUnsortedArray = new String[unsortedArray.length + 1];
            System.arraycopy(unsortedArray, 0, newUnsortedArray, 0, 3);
            newUnsortedArray[newUnsortedArray.length - 1] = toInsert;
            System.out.println(Arrays.toString(newUnsortedArray));
        }
    
        public static void main(String[ ] args) {
            insertUnsortedArray("B");
        }
    }
    
    

    Inserting an element into a sorted array


    package bigo;
    
    import java.util.Arrays;
    
    public class InsertingElementsToArray {
    
        public static void insertSortedArray(String toInsert) {
    
            String[ ] sortedArray = { "A", "C", "D" };
    
            /*
             * Binary search returns the index of the search item
             * if found, otherwise returns the minus insertion point. This example
             * returns index = -2, which means the elemnt is not found and needs to
             * be inserted as a second element.
             */
            int index = Arrays.binarySearch(sortedArray, toInsert);
    
            if (index < 0) {                                   // not found.
    
                // array indices are zero based. -2 index means insertion point of
                // -(-2)-1 = 1,  so insertIndex = 1
                int insertIndex = -index - 1;
    
                String[ ] newSortedArray = new String[sortedArray.length + 1];
                System.arraycopy(sortedArray, 0, newSortedArray, 0,  insertIndex); 
                System.arraycopy(sortedArray, insertIndex,
                        newSortedArray, insertIndex + 1,  sortedArray.length - insertIndex);
                newSortedArray[insertIndex] = toInsert;
                System.out.println(Arrays.toString(newSortedArray));
            }
        }
    
        public static void main(String[ ] args) {
            insertSortedArray("B");
        }
    }
    

    So the decision depends on the usage pattern. Ask yourself the following questions. Do I have more inserts/deletes or search? What is the maximum number of elements likely to be stored in this array? How often do I need the sorted data? And finally what does my performance benchmark show?

    Q. How do you get an immutable collection?
    A. This functionality is provided by the Collections class, which is a wrapper implementation using the decorator design pattern.


    public class ReadOnlyExample {
        public static void main(String args[ ]) {
            Set<string> set = new HashSet<string>( );
            set.add("Java");
            set.add("JEE");
            set.add("Spring");
            set.add("Hibernate");
            set = Collections.unmodifiableSet(set);
            set.add("Ajax");                                           // not allowed.
      }
    }
    

    Q. What does the following code do? Can the LinkedHashSet be replaced with a HashSet?

    import java.util.ArrayList;
    import java.util.LinkedHashSet;
    import java.util.List;
    
    public class CollectionFunction {
        public <e> List<e> function (List <e> list) {
              return new ArrayList<e>(new LinkedHashSet<e>(list));
        }
    }
    

    A. The above code removes duplicates from a supplied list by passing it through an implementation of a Set interface. In this case, a LinkedHashSet is used to honor the ordering. If ordering is not required, the LinkedHashSet can be replaced with a HashSet.

    Q. What are some of the best practices relating to the Java Collection framework?
    A.

    • Choose the right type of data structure based on usage patterns like fixed size or required to grow, duplicates allowed or not, ordering is required to be maintained or not, traversal is forward only or bi-directional, inserts at the end only or any arbitrary position, more inserts or more reads, concurrently accessed or not, modification is allowed or not, homogeneous or heterogeneous collection, etc. Also, keep multi-threading, atomicity, memory usage and performance considerations discussed earlier in mind.

    • Don't assume that your collection is always going to be small as it can potentially grow bigger with time. So your collection should scale well.

    • Program in terms of interface not implementation: For example, you might decide a LinkedList is the best choice for some application, but then later decide ArrayList might be a better choice for performance reason.

             Bad:
                      ArrayList list = new ArrayList(100);

            Good:
                     // program to interface so that the implementation can change
                    List list = new ArrayList(100);
                    List list2 = new LinkedList(100);


    • Return zero length collections or arrays as opposed to returning a null in the context of the fetched list is actually empty. Returning a null instead of a zero length collection is more error prone, since the programmer writing the calling method might forget to handle a return value of null.

              List emptyList = Collections.emptyList( );
              Set emptySet = Collections.emptySet( );

    • Use generics for type safety, readability, and robustness.

    • Encapsulate collections: In general, collections are not immutable objects. So care should be taken not to unintentionally expose the collection fields to the caller. The caller may not perform any necessary validation.

    Note: These Java interview questions and answers are extracted from my book "Core Java Career Essentials"


    Other relevant Java Interview Questions and Answers on data structures

    Labels:

    Sep 19, 2011

    Java Interview Questions & Answers: user defined key class


    This is one of my favorite core Java interview questions as not knowing this can cause subtle and intermittent issues in Java. The issues that arise from not understanding equals( ) and hashCode( ) contract can be hard to debug.  

    Q. When providing a user defined key class for storing objects in a Map (e.g. HashMap), what methods do you have to provide or override (i.e. method overriding)?

    A. You should override the equals( ... ) and hashCode( .. ) methods from the Object class. The default implementation of the equals( ... ) and hashCode( ... ) methods are inherited from the java.lang.Object class  uses an object instance’s memory location (e.g. MyObject@6c60f2ea). This can cause problems when two instances of car objects have the same color but the inherited equals( .. ) will return false because it uses the memory location as oppose to the color, which is different for the two instances.


    The hashCode and equals methods are used by the Map and Set interfaces to store and retrieve objects. The following diagram shows how these methods are used.


    As you can see from the above diagram, the hashCode(..) method is used to evaluate the bucket index to store the keys. Since more than one object like "John" and "Peter" can result with the same hashCode, a list of keys are maintained at that index to store all the keys.When retrieving a value with a key, the hashCode(..) method is called first to determine the hashValue for the bucket, and then the equals(...) method is invoked to loop through the keys like "John", "Peter", etc and pick the right key to retrieve the corresponding value.

    Q. What are the primary considerations when implementing a user defined key? 
    A. The user defined key class must consider the following.

    • If the class overrides the equals( ... ) method, it must also override the hashCode( ... ) method.
    • If 2 objects are equal, then their hashCode values must be equal as well. But, if two objects return the same hashCode value does not mean that those two objects are equal.
    • It is a best practice to implement the user defined key class as an immutable object.
    • If it is accessed often, the hashCode( ... ) method is a good candidate for caching to enhance performance.  

    Q. Why it is a best practice to implement the user defined key class as an immutable object?
    A.

    Problem: As per the code snippet shown below, if you use a mutable user defined class “UserKey” as a Map key and subsequently if you mutate the key after the object has been added to the Map via the setter method key.setName(“Sam”), you will not be able to access the object later on. The original key  will still be in the Map and you can still iterate through your Map and print it, but you cannot access it with map.get(key) and querying it with map.containsKey(key) will return false because the key “John” becomes “Sam” after being modified in the “List of keys”  at the key index with the hash value of “345678965” as shown in the diagram. When you print the keys, it will print "Sam" twice, once for index “345678965” and again for index "76854676". These types of errors are subtle and can be very hard to trace and fix without understanding this basic relating to how the map and the methods hashCode and equals work as demonstrated in the diagram.


    Map myMap = new HashMap(10);
    //add the key “John” 
    UserKey key = new UserKey(“John”);  //Assume UserKey class is mutable
    myMap.put(key, “Sydney”);
    //now key value "John" is modified to key value “Sam” 
    key.setName(“Sam”)// This line modifies the key value “John” to “Sam” in the “List of keys” 
       // as shown in the diagram above. This means that the key “John” cannot be
       // accessed. There will be two keys with “Sam” in positions with hash 
        // values 345678965 and 76854676. 
    myMap.put(key, “Melbourne”);
    
    myMap.get(new UserKey(“John”)); // key cannot be accessed. The key hashes to 
         // the same hash index position 345678965 
          // in the “Key index array” but cannot be found 
          // in the “List of keys”
    
    



    Solution: Generally you use a java.lang.Integer or a java.lang.String class as the key, which are immutable Java objects. If you define your own key class then it is a best practice to make the key class an immutable object. If a programmer wants to insert a new key, then he/she will always have to instantiate a new object as he/she cannot mutate an existing key.

    Map myMap = new HashMap(10);
    //add the key “John” 
    UserKey key1 = new UserKey(“John”);  //Assume UserKey is immutable
    myMap.put(key1, “Sydney”);
    
    //add the key “Sam” 
    UserKey key2 = new UserKey(“Sam”);  //Since UserKey is immutable, new instance is created.
    myMap.put(key2, “Melbourne”);
    
    myMap.get(new UserKey(“John”));     //Now the key can be accessed 

    Similar issues are possible with the Set (e.g. HashSet) as well. If you add an object to a “Set” and subsequently modify the added object and later on try to query the original object it may not be present. mySet.contains(originalObject) may return false.



    Q. If Java did not have a HashMap implementation, how would you go about implementing your own?
    A. Writing a HashMap is not a trivial task. It is also not a good practice to reinvent the wheel. The interviewer is trying to evaluate your level of technical knowledge and thought process. What really important here are, how you approach the problem?, how good your understanding of the data structures are?, and how well you can logically think and code?.

    • Firstly, decide on the backing data structure.  Arrays are fast and memory efficient. Hence an array can be used to back up the map. This will be an indexed bucket.
    • Decide on a hashing algorithm to index  the array and store the entries in a particular slot of the array.
    • Decide on a strategy to store two or more keys that result in the same hash code value. More objects can be linked with each other occupying the same index. This means each bucket can contain 0..N entries. This linking strategy is shown in the diagram.
    • Decide on an approach to evaluate the hash code, which determines the array bucket index to use.

                    int index =Math.abs(key.hashCode( ) % buckets.length);

    • Come up with a strategy for resizing the backing array when the capacity is reached. This process is known as rehashing whereby existing items are mapped to new bucket locations.
    • Consider implementing the Map interface and  fill in the empty methods that need to be implemented.




    Note: The above questions is explained in more detail with working code  and more core Java interview questions are discussed with clear answers in my book "Core Java Career Essentials".

    Labels: