Coinbase Disabled

When you are victimization the Coinbase you came up with completely different queries like — however, Coinbase Disable the account? What happens once Coinbase Disable the account? What may be the…

Smartphone

独家优惠奖金 100% 高达 1 BTC + 180 免费旋转




Java Best Coding Practices

Efficient code refers to code that achieves optimal performance, uses minimal resources, and executes quickly. Inefficient code, on the other hand, is code that requires more resources and time to execute.

While it is true that writing efficient code is important, it is not accurate to classify all programmers into two categories based on their coding efficiency. Programmers come from different backgrounds and experiences, and they may have different priorities when writing code.

This article provides 50 code samples that aim to improve the performance of Java code. These samples are based on the author’s experience and research and are intended to serve as a reference for Java programmers who want to write efficient code. By following these examples, programmers can learn how to optimize their code and improve its efficiency.

When you assign a constant value directly in a programming language, you are not actually creating a new value, but rather creating a reference to an existing value. This means that the variable you create simply points to the location in memory where the constant value is stored, rather than holding its own unique copy of the value. This is similar to a bookshelf containing multiple copies of the same book — each copy is a reference to the same book, rather than a unique copy of the book.

Bad code:

Good code:

In each object instance of a class, each member variable has a copy, and each member static constant has only one instance.

Bad code:

Good code:

The elementary data types in Java are double, float, long, int, short, char, and boolean, which correspond to the packing classes Double, Float, Long, Integer, Short, Character, and Boolean, respectively. Java virtual machines (JVMs) support automatic conversion between elementary data types and packing classes, which is called automatic packing and unpacking. Packing and unpacking consume CPU and memory resources, so you should avoid automatic packing and unpacking as much as possible.

Bad code:

Good code:

Bad code:

Good code:

When we use simple types like numbers or boolean values as parameters or temporary variables in a function, they are stored in stacks and are easy to access. However, if we use more complex object types, the references to these objects are stored in stacks, but the actual content of the objects is stored in the heap. This makes it slower to access these objects.

When we define variables inside a class, whether they are simple or complex types, they are all stored in the heap. This means that accessing these variables is slower compared to accessing variables stored in stacks.

In summary, simple types are stored in stacks and are easy to access, while complex types are stored in heaps and are slower to access. When defining variables inside a class, regardless of type, they are all stored in the heap and are accessed relatively slower.

Bad code:

Good code:

In earlier versions of the Java Development Kit (JDK), it was difficult to define variables outside a loop. However, newer versions of the JDK have improved in this area. After examining the compiled bytecode, it has been found that code with variables defined inside the loop and code with variables defined outside the loop have the same performance efficiency.

The principle of minimizing the scope of local variables suggests that defining variables inside the loop can prevent delays in recycling large objects that have a long lifespan. This means that it’s better to define variables within the smallest scope possible to avoid unnecessary delays in recycling large objects.

Bad code:

Good code:

Non-thread security classes are applicable to unchanged static constants with support for multithread access.

Bad code:

Good code:

Non-thread security classes are applicable to unchanged member variables with support for multithread access.

Bad code:

Good code:

JSON can convert between objects and JSON strings, so it may be used by some programmers to convert objects. Such object conversion works, but results in low performance.

Bad code:

Good code:

Using reflection to assign values to objects may require less code, but it can result in lower code performance.

Bad code:

Good code:

For someone who is new to JDK 8, they may think that lambda expressions are just a different way of writing anonymous classes. However, this is not entirely accurate.

In reality, lambda expressions are implemented using the invokeDynamic instruction on many virtual machines. This means that they are implemented differently from internal anonymous classes, and as a result, they can be more efficient.

This is because the invokeDynamic instruction allows lambda expressions to be dynamically linked and optimized at runtime, which can result in better performance compared to anonymous classes. So, while lambda expressions may look similar to anonymous classes, they are actually implemented differently and can be more efficient in certain situations.

Bad code:

Good code:

Additional class loading is required once a class is added.

Bad code:

Good code:

You can use the “final” modifier in Java to specify that a class cannot be inherited. This means that no other class can extend it or inherit from it. When a class is marked as final, all of its methods become final as well.

The Java compiler can take advantage of the final modifier and inline all final methods during compilation. Inlining is a technique that replaces method calls with the actual method code. This can greatly improve Java runtime efficiency since it reduces the overhead of method calls.

In fact, inlining final methods can improve performance by up to 50% on average. This improvement is because the method calls are eliminated, and the JVM can optimize the code further.

So, if you have a class that should not be extended, marking it as final can not only prevent inheritance but also improve performance through inclining.

Bad code:

Good code:

A static method can be directly called without creating a class instance. A static method does not belong to an object, but to the class where it is located. A static method can be accessed by using its class name, avoiding resource consumption due to repeated object creation. Declare a private method in a class as a static method even if the method does not use any class member variables.

Bad code:

Good code:

Using elementary data types as method parameters can help you avoid unnecessary packing, unpacking, and null pointer checks.

In Java, elementary data types are basic data types such as int, float, double, boolean, and char. When you use these types as method parameters, you can pass the actual value instead of creating an object that encapsulates the value. This avoids the overhead of packing and unpacking the value, which can improve performance.

In addition, using elementary data types can also help avoid null pointer checks. When you use object types as parameters, you have to check for null values to avoid null pointer exceptions. With elementary data types, null values are not possible, so you don’t have to perform these checks.

Overall, using elementary data types as method parameters can lead to faster and more efficient code by avoiding unnecessary overhead and null pointer checks.

Bad code:

Good code:

In Java, elementary data types are basic data types such as int, float, double, boolean, and char. When you use these types as method return values, you can return the actual value instead of creating an object that encapsulates the value. This avoids the overhead of packing and unpacking the value, which can improve performance.

In addition, using elementary data types can also help avoid null pointer checks for return values. When you use object types as return values, you have to check for null values to avoid null pointer exceptions. With elementary data types, null values are not possible, so you don’t have to perform these checks.

Many of the methods in the JDK class library use elementary data types for return values. For example, the Collection.isEmpty() method returns a boolean value, and the Map.size() method returns an int value. These methods avoid unnecessary overhead and null pointer checks by using elementary data types for their return values.

Overall, using elementary data types as method return values can lead to faster and more efficient code by avoiding unnecessary overhead and null pointer checks.

Bad code:

Good code:

Protocol programming is a programming paradigm where the interaction between components is governed by a set of rules or protocols. In this paradigm, parameters of protocol methods can be tagged with @NonNull or @Nullable annotations to indicate whether the parameter can or cannot be null.

Bad code:

Good code:

Protocol programming is a programming paradigm where the interaction between components is governed by a set of rules or protocols. In this paradigm, parameters of protocol methods can be tagged with @NonNull or @Nullable annotations to indicate whether the parameter can or cannot be null.

Bad code:

Good code:

Bad code:

Good code:

When you call a method, it may cause new data items to be pushed into or pulled from a stack, which can consume CPU and memory resources. Therefore, it is important to avoid unnecessary function encapsulation to improve performance.

Function encapsulation is the process of breaking down a program into smaller, more manageable parts, which are then implemented as functions or methods. While this can make code more concise and easier to understand, it can also lead to a performance loss if too many method calls are made.

However, there are cases where adding method calls can actually improve code readability and clarity, and in those cases, the performance loss may be worth it. For example, if you have a complex algorithm that needs to be executed multiple times, it may be more efficient to encapsulate that algorithm in a separate method, even if it adds some overhead.

Overall, it is important to strike a balance between function encapsulation and performance. Avoid unnecessary method calls when possible, but don’t sacrifice code clarity and readability for the sake of performance.

Bad code:

Good code:

To prevent a method from being overwritten, you can use the final modifier in the method declaration. This will ensure that the method cannot be overridden in any subclass, and the Java compiler may even inline all final methods. Inlining can greatly improve the efficiency of the Java runtime, resulting in an average performance improvement of around 50%.

It’s worth noting that all private methods in Java are implicitly specified with the final modifier, which means you don’t need to explicitly specify the final modifier for them.

Using final methods can have several benefits. For example, it can improve code performance by allowing the Java compiler to inline the method calls. This means that instead of making a function call to the method, the compiler can replace the function call with the actual code of the method, resulting in faster code execution.

In addition, final methods can also prevent unintended method overriding in subclasses, which can help maintain code stability and prevent unexpected behavior.

Overall, using the final modifier for methods can improve code performance and stability, and is especially useful for private methods or methods that should not be overridden in subclasses.

Bad code:

Good code:

Note: When you add a dynamic proxy to beans in Spring AOP, methods with the final modifier are not proxied.

Bad code:

Good code:

Bad code:

Good code:

The shift operation can greatly improve performance. You can use the shift operation to calculate a positive integer multiplied or divided by 2^n (n is a positive integer).

Bad code:

Good code:

Only one value is calculated and used repeatedly when a public expression is fetched.

Bad code:

Good code:

If ! is used in the non-operator position, one more calculation is performed. Do not use ! unless it is necessary.

Bad code:

Good code:

In an if-else statement, each if conditional statement performs calculation until the true if statement appears. A switch statement optimizes jumps and is executed by the tableswitch or lookupswitch instruction in Java. It can process multi-constant selection branches more efficiently than an if-else statement. According to experiments, an if-else statement is more efficient than a switch statement when processing less than five branches, whereas a switch statement is more efficient than an if-else statement when processing more than five branches, assuming that the occurrence probability of each branch is the same.

Bad code:

Good code:

Note: You can use Map to implement the strategy pattern for complex services.

Regular expression matching is inefficient. Use string matching instead.

Bad code:

Good code:

Note: The StringUtils.split function does not retain empty strings.

The string length is variable, whereas the character length is fixed to 1, so it is more efficient to query and match using characters.

Bad code:

Good code:

When you concatenate strings using the “+” operator, a new string object is created with the concatenated content. This new object is immutable, which means its content cannot be modified.

However, when you use a StringBuilder to concatenate strings, the StringBuilder object requests memory upfront and concatenates subsequent strings in that allocated memory space. This means that no new string objects are created during the concatenation process and no additional memory is allocated. This approach can be more efficient than repeatedly creating new string objects for each concatenation.

Bad code:

Good code:

When you use the “+” operator to concatenate strings, it creates a new string object every time, which can be inefficient if you need to do a lot of concatenation.

Instead, you can use the String.valueOf method, which is more efficient. This method converts other data types to strings, and you can use it to concatenate strings too. It works by creating a string representation of the input, rather than creating a new object each time.

So, using String.valueOf to concatenate strings can be more efficient than using the “+” operator because it avoids the creation of unnecessary objects.

Bad code:

Good code:

The Pattern.compile method is used to create a regular expression pattern object, which can be used for matching or searching for text within strings. However, this method has a high performance overhead, which means it can be slow and resource-intensive.

This method may be hidden in other methods considered convenient, such as the String.matches, String.replaceAll, and String.split functions. These methods use regular expressions to perform operations on strings, but they may use the Pattern.compile method under the hood to create the necessary pattern object.

As a result, if you are performing a lot of string operations that use regular expressions, it may be more efficient to explicitly create a pattern object using the Pattern.compile method and reuse it in your operations, rather than relying on the convenience methods that may hide the performance overhead. This can help improve the performance of your code and reduce resource usage. Example:

Bad code:

Good code:

Use System.arraycopy or Arrays.copyOf to copy arrays.

Bad code:

Good code:

If you have a collection of objects and you need to convert it into an array, there are two common ways to do it: using toArray(new T[n]) or toArray(new T[0]).

In earlier versions of Java, it’s better to use toArray(new T[n]) because the reflection required to create an array can be slow. This means that you should specify the size of the array you want to create by passing the desired size “n” as a parameter to the toArray method.

However, in OpenJDK 6 and later versions that use internal reflection, toArray(new T[0]) is more efficient and performs better than toArray(new T[n]). This is because toArray(new T[n]) needs to calculate the list size one more time than toArray(new T[0]), which can be inefficient if calculating the list size takes a lot of time.

So, if you’re using a newer version of Java, it’s generally better to use toArray(new T[0]) as it can be more efficient. However, if you’re using an older version of Java, it’s better to use toArray(new T[n]) to avoid the performance overhead of reflection.

Bad code:

Good code:

Note: A toArray(Class\<T\> clazz) method must be provided for a collection to avoid useless empty array initialization (new T[0]).

When you want to convert a collection to an array, using toArray() is generally more efficient than using toArray(new Object[0]).

This is because toArray() doesn’t require you to create an empty array of objects and then cast it to the type you want. Instead, it creates a new array of the correct type and size and populates it with the elements from the collection.

Using toArray() is also more efficient because it avoids the need for type inference and empty array creation. When you use toArray(new Object[0]), Java needs to create an empty array of objects to infer the type of the collection, which can be inefficient if you have a large collection.

So, if you need to convert a collection to an array, it’s generally better to use toArray() as it can be more efficient and avoids unnecessary type inference and empty array creation.

Bad code:

Good code:

When you initialize a Java collection, it has a default size that’s specified by the implementation of the collection class. If the amount of data you need to store exceeds the default size, the collection needs to be resized to accommodate the additional data.

Resizing a collection can be a costly operation, especially if the collection is large, as it may require copying all the elements to a new array. The time complexity of a resize operation may be O(n), which means it could take a long time to complete for large collections.

To avoid the cost of resizing, it’s a good practice to specify the expected size of the collection when you create it, especially if you know in advance how much data you’ll be storing. This allows the collection to allocate enough memory upfront, reducing the need for resizing and improving performance.

If you’re unsure about the size of the collection, you can try to estimate it based on the amount of data you expect to store or by using heuristics. You can also monitor the size of the collection during runtime and adjust it if necessary to reduce the number of resizes. By minimizing the number of resizes, you can improve the performance of your code and reduce its resource usage.

Bad code:

Good code:

The Java Development Kit (JDK) provides methods that allow you to specify the capacity of a collection in a single step, which can save time and space by avoiding the need for repeated resizing.

These methods are often provided by collection classes and allow you to specify the initial capacity of the collection when you create it. By specifying the initial capacity, you can ensure that the collection has enough space to hold the data you need to store, which can avoid the need for resizing.

The JDK’s implementation of these methods typically uses the System.arraycopy method to efficiently copy data into the collection. This method is designed for efficient batch data copy, which can improve the performance of your code when working with large collections.

By using these methods to specify the initial capacity of your collections, you can save time and space by avoiding repeated resizing and benefit from the efficient data copy provided by the JDK.

Bad code:

Good code:

The Arrays.asList method is a convenient way to convert an array to a list in Java. It takes an array as input and returns a fixed-size list backed by the original array.

One of the benefits of using Arrays.asList is that it allows you to easily convert an array to a list without needing to write a loop or use any other external libraries. It’s a built-in method provided by Java, so it’s readily available for use in your code.

Another benefit of using Arrays.asList is that it’s memory-efficient. It doesn’t create a new list object, but instead provides a view of the existing array, which saves memory and reduces the amount of copying required.

However, it’s important to note that Arrays.asList has some limitations. The list returned by Arrays.asList is a fixed-size list, which means you can’t add or remove elements from it. Additionally, changes made to the original array are reflected in the list, and vice versa.

Therefore, if you need a modifiable list, you should create a new list and add the elements from the array to it using a loop or other methods provided by the Collections API.

In summary, Arrays.asList is a convenient and memory-efficient way to convert an array to a list in Java. However, it’s important to be aware of its limitations and use it appropriately depending on your specific use case.

Bad code:

Good code:

This operation allows you to get data directly. No other operations are required.

Bad code:

Good code:

When checking whether a collection or string is empty in Java, it is common to use the size() method to get the number of elements in the collection or the length of the string. However, it is also possible to use the isEmpty() method, which is specifically designed for this purpose.

Using the isEmpty() method can make your code more readable, as it clearly conveys your intent to check whether a collection or string is empty. Additionally, using isEmpty() can also result in better performance, as the time complexity of implementing any isEmpty() method is always O(1), which means it performs the same regardless of the size of the collection or string.

In contrast, some size() methods may have a time complexity of O(n), which means their performance is proportional to the number of elements in the collection or the length of the string. In these cases, using size() instead of isEmpty() could result in slower performance for large collections or strings.

In summary, it is reasonable to detect empty values using the size() method, but using the isEmpty() method can make your code more readable and perform better in some cases, especially when working with large collections or strings.

Bad code:

Good code:

In Java, lists can be classified into two types: random-access lists and non-random-access lists. You can determine the type of a list by checking whether it implements the RandomAccess interface.

Random-access lists, such as ArrayList, implement the RandomAccess interface and provide efficient access to elements by index. This means that retrieving elements from a random-access list using the get() method is very efficient, with a time complexity of O(1).

Non-random-access lists, such as LinkedList, do not implement the RandomAccess interface and do not provide efficient access to elements by index. Retrieving elements from a non-random-access list using the get() method can be inefficient, with a time complexity of O(n), where n is the number of elements in the list.

To efficiently retrieve elements from a non-random-access list, it is better to use an iterator or the list’s specific methods, such as poll() or remove(), depending on the list’s specific implementation.

In summary, it is important to consider whether a list is random-access or non-random-access when choosing a method to retrieve elements. Using the get() method is efficient for random-access lists, but can be inefficient for non-random-access lists.

Bad code:

Good code:

Use iteration to traverse random-access and non-random-access lists.

In Java, the contains() method is used to check if a collection contains a specific element. However, the time complexity of the contains() method can vary depending on the type of collection.

For example, for a List, the time complexity of the contains() method is typically O(n), where n is the number of elements in the list. This means that the time taken to check if an element is in the list increases linearly with the number of elements in the list.

In contrast, for a HashSet, the time complexity of the contains() method is O(1), which means that it takes the same amount of time to check if an element is in the HashSet, regardless of the number of elements it contains.

Therefore, if you need to frequently check if a collection contains specific elements, it is recommended to use a HashSet instead of a List. By converting a List to a HashSet, you can improve the performance of the contains() method, as it will take constant time to check if an element is in the HashSet, regardless of the size of the HashSet.

In summary, if you need to frequently call the contains() method to search data, it is better to use a HashSet instead of a List, as the time complexity of the contains() method is O(1) for HashSet, whereas it is typically O(n) for List.

Bad code:

Good code:

You can directly retrieve data and determine whether the data is empty rather than determine whether the data exists before retrieving it. This avoids repeated searching.

Bad code:

Good code:

It is recommended to capture exceptions directly rather than using instanceof for judgment, to improve the readability and efficiency of your code.

Bad code:

Good code:

If the loop body throws an exception and you do not need to repeat the loop, you do not need to capture the exception in the loop body. This is because too many exceptions may reduce the program execution efficiency.

Bad code:

Good code:

It is recommended to use exceptions for exceptional conditions, such as unexpected errors or invalid input, and to use conditional expressions or other control flow constructs for business process control, to improve the efficiency and readability of your code.

Bad code:

Good code:

When you create a buffer or an array in Java, you should specify an initial capacity that matches the expected size of the data that will be stored in the buffer or array. This is because Java may need to resize the buffer or array if it runs out of space, which can be a time-consuming and memory-intensive operation.

To avoid wasting time and space due to repeated resizing, it is recommended to specify the expected buffer capacity during initialization. This can be done by passing the expected capacity as a parameter to the constructor of the buffer or array. By doing this, Java will allocate enough memory to store the expected amount of data, and avoid the need for frequent resizing.

Specifying the expected capacity during initialization can help improve the performance and efficiency of your code, especially if you are working with large amounts of data or performance-critical applications. It can also help avoid unnecessary memory usage and reduce the risk of memory-related errors, such as out of memory exceptions.

Bad code:

Good code:

When working with buffers in Java, it is important to consider the overhead of object creation and garbage collection in the JVM. Creating and discarding objects repeatedly can be time-consuming and memory-intensive, which can impact the performance of your code.

To minimize the overhead of object creation and garbage collection, it is recommended to reuse the same buffer whenever possible. This can be done by resetting the buffer and overwriting its contents instead of creating a new buffer each time it is needed.

By reusing the same buffer, you can improve the performance and efficiency of your code, especially if you are working with large amounts of data or performance-critical applications. It can also help reduce the risk of memory-related errors, such as out of memory exceptions, by minimizing the number of objects that need to be created and discarded.

Bad code:

Good code:

You can use the setLength method to reset the buffer size to 0.

Buffer reuse is a design technique that can be used to improve the runtime efficiency of a program. The idea behind buffer reuse is to minimize the overhead associated with object creation and garbage collection by reusing the same buffer for multiple operations.

To implement buffer reuse in your program, you need to carefully manage the lifecycle of your buffers. Instead of creating a new buffer every time you need to perform an operation, you can reuse an existing buffer by resetting its contents and overwriting them with new data.

The key to effective buffer reuse is to ensure that your buffers are properly initialized and managed throughout the program’s lifecycle. This involves keeping track of which buffers are in use, resetting them when they are no longer needed, and properly disposing of them when the program is finished.

By implementing buffer reuse in your program, you can improve its runtime efficiency and reduce the risk of memory-related errors. However, buffer reuse requires careful design and management, and should only be used in situations where it is appropriate and feasible.

Bad code:

Good code:

Buffered streams, such as BufferedReader, BufferedWriter, BufferedInputStream, and BufferedOutputStream, can significantly improve the performance of input and output (I/O) operations in Java.

These streams use an internal buffer to store data, which reduces the number of I/O operations needed to read or write data. Instead of reading or writing one byte or character at a time, buffered streams read or write a block of data at a time, which reduces the overhead associated with I/O operations.

Buffered streams can be particularly useful when dealing with large amounts of data or when performing I/O operations over slow network connections. By using buffered streams, you can reduce the time and resources needed to perform I/O operations and improve the overall performance of your Java application.

It’s important to note that while buffered streams can improve I/O performance, they also introduce some overhead due to the need to manage the internal buffer. Therefore, you should carefully consider the size of the buffer when using buffered streams, and adjust it as needed to find the optimal balance between performance and memory usage.

Bad code:

Good code:

You can manually specify the buffered stream size as needed to maximize the buffering effect of buffered streams.

Classes that are not designed to be thread-safe can avoid unnecessary synchronization overhead because they do not need to worry about coordinating access between multiple threads.

In Java, synchronization is used to ensure that multiple threads can safely access shared resources without interfering with each other. However, synchronization can introduce performance overhead due to the need to acquire and release locks, which can be expensive in terms of time and resources.

Non-thread-safe classes, on the other hand, do not need to worry about synchronization, as they are designed to be used in a single-threaded environment. This means that they can avoid the overhead associated with synchronization and potentially provide better performance.

However, it’s important to note that non-thread-safe classes should not be used in a multi-threaded environment without proper synchronization mechanisms in place. In such cases, the classes may introduce data races, deadlocks, and other concurrency issues that can cause serious problems for your application. Therefore, it’s important to carefully consider the thread-safety requirements of your application and choose the appropriate classes accordingly.

Bad code:

Good code:

Using thread-safe classes is generally more efficient and concise than manually synchronizing code. Thread-safe classes have built-in mechanisms to handle concurrent access to shared resources, such as locks or atomic operations, which can avoid the overhead of manual synchronization. Additionally, using thread-safe classes reduces the likelihood of errors and simplifies code, as you do not have to worry about manually managing locks or ensuring proper synchronization.

Bad code:

Good code:

It is generally a good practice to minimize the scope of synchronized blocks to only the code that requires synchronous control. This is because applying synchronization to a larger scope than necessary can impact code efficiency, as other threads may be blocked unnecessarily.

To achieve this, you should identify the specific portion of your code that requires synchronous control, and only apply synchronization to that portion. This can be achieved by enclosing only the critical section of code with the synchronized keyword. This approach ensures that other threads can continue to execute other parts of the code without being blocked, improving overall code efficiency.

Bad code:

Good code:

Synchronized blocks incur performance overhead. Merge multiple synchronized blocks into one if possible.

Bad code:

Good code:

When a new thread is created, there is an overhead associated with allocating memory for the thread stack and other resources, which can slow down the program. Additionally, when multiple threads are executing concurrently, there is overhead associated with switching between them, which can also reduce performance.

To avoid this overhead, you can use a thread pool, which maintains a group of threads that can be reused to execute multiple tasks. The threads in the pool are created once and can be used repeatedly to execute different tasks. This eliminates the overhead associated with creating and destroying threads, and can improve the overall performance of the program.

Bad code:

Good code:

Thank You !!!!

Add a comment

Related posts:

Love Lives

I pity the person who’s ears are deaf and eyes blind to Love’s presence inside. Maybe Love and the dance of courtship is dead for those that are dead inside. However, for the living Love has never…

Let ChatGPT to Be Your Pair Programmer

Unlick Github Copilot, OpenAI does not offer an IDE plugin to help devs for coding, but it does not mean, we cannot use ChatGPT for free as a coding assistant. It was mentioned the color schema in…

A Deep Dive Into CustomPaint in Flutter

If you are starting out with Flutter, you may not have heard of or used the CustomPaint widget a lot. Most widgets in the framework take care of common features and functionality and even when they…