Interface (Interface) and Class (Class)?
Once, I attended a meeting of a Java user group. At the conference, James Gosling (the father of Java) gave the initiator speech. In that memorable Q&A segment, he was asked: "If you refactored Java, what would you change?". "I want to get rid of classes" he replied. After the laughter subsided, it was explained that the real problem was not the class itself, but the implementation of the inheritance relationship. Interface inheritance (implements relationship) is better. You should avoid implementing inheritance as much as possible.
Loss of flexibility
Why should you avoid inheritance? The first problem is that explicitly using concrete class names locks you into a specific implementation, making underlying changes unnecessarily difficult.
In the current agile programming method, the core is the concept of parallel design and development. You start programming before you design the program in detail. This technique differs from the traditional approach - where design should be completed before coding begins - but many successful projects have proven that you can develop high-quality code more quickly than with traditional step-by-step methods. method. But at the core of parallel development is flexibility. You have to write your code in a way so that newly discovered requirements can be merged into existing code as painlessly as possible.
Rather than implementing features you may need, you only need to implement features you clearly need, and be moderately tolerant of change. If you don't have this kind of flexible, parallel development, it's simply impossible.
Programming for Inteface is the core of flexible structures. To illustrate why, let's take a look at what happens when they are used. Consider the following code:
f()
{
LinkedList list = new LinkedList();
//...
g( list );
}
g(LinkedList list)
{
list.add(...);
g2(list)
}
Assume a need for fast query is raised so that this LinkedList cannot be resolved. You need to use HashSet instead. In existing code, changes cannot be localized, because you need to modify not only f() but also g() (which takes a LinkedList parameter), and any code that g() passes the list to. Rewrite the code as follows:
f()
{
Collection list = new LinkedList();
//...
g( list );
}
g( Collection list )
{
list.add( ... );
g2( list )
}
Modify the Linked list like this hash, may simply use new HashSet() instead of new LinkedList(). that's all. There is nothing else to modify.
As another example, compare the following two pieces of code:
f()
{
Collection c = new HashSet();
//...
g( c );
}
g( Collection c )
{
for( Iterator i = c.iterator(); i.hasNext() )
do_something_with ( i.next() );
}
and
f2()
{
Collection c = new HashSet();
//.. .
g2( c.iterator() );
}
g2( Iterator i )
{
while( i.hasNext() )
do_something_with( i. next() );
}
The g2() method can now iterate over Collection derivation, just like you can get key-value pairs from a Map. In fact, you can write iterators that generate data instead of traversing a Collection. You can write iterators that get information from the test framework or file. This allows for tremendous flexibility.
Coupling
For implementing inheritance, a more critical issue is coupling---annoying dependence, that is, the dependence of one part of the program on another part. Global variables provide a classic example of why strong coupling can cause trouble. For example, if you change the type of a global variable, then all functions that use that variable may be affected, so all of this code will have to be inspected, changed, and retested. Moreover, all functions that use this variable are coupled to each other through this variable. That is, if a variable value is changed at a time when it is difficult to use, one function may incorrectly affect the behavior of another function. This problem is significantly hidden in multi-threaded programs.
As a designer, you should strive to minimize coupling relationships. You can't eliminate coupling altogether, because method calls from objects of one class to objects of another class are a form of loose coupling. You can't have a program without any coupling. However, you can minimize some coupling by following OO rules (most importantly, the implementation of an object should be completely hidden from the objects that use it). For example, an object's instance variables (fields that are not constants) should always be private. I mean for a certain period of time, without exception, continuously. (You can occasionally use protected methods effectively, but protected instance variables are an abomination.) For the same reason you shouldn't use get/set functions --- they just feel overly complex for being public to a domain (despite the return modifier The reason for accessing functions of objects rather than primitive values is in some cases, and in that case the returned object class is a key abstraction in the design).
Here, I am not bookish. In my own work, I find a direct correlation between the rigor of my OO approach, rapid code development, and easy code implementation. Whenever I violate central OO principles, such as implementation hiding, I end up rewriting that code (usually because the code is not debuggable). I don't have time to rewrite the code, so I follow those rules. Am I concerned about purely practical reasons? I'm not interested in clean reasons.
The fragile base class problem
Now, let’s apply the concept of coupling to inheritance. In an implementation system that uses extends, the derived class is very tightly coupled to the base class, and this tight coupling is undesirable. Designers have applied the nickname "fragile base class problem" to describe this behavior. Base classes are considered fragile because you modify the base class seemingly safely, but when inheriting from a derived class, the new behavior may cause the derived class to become dysfunctional. You can't tell whether changes to a base class are safe by simply inspecting the base class in isolation; rather, you must also look at (and test) all derived classes. Furthermore, you must check all code that is also used in base and derived class objects, because this code may be broken by the new behavior. A simple change to a base class can render the entire program inoperable.
Let’s examine the problem of fragile base classes and base class coupling. The following class extends Java's ArrayList class to make it behave like a stack:
class Stack extends ArrayList
{
private int stack_pointer = 0;
public void push ( Object article )
{
add( stack_pointer , article );
}
public Object pop()
{
return remove( --stack_pointer );
}
public void push_many(Object[] articles)
{
for( int i = 0; i push( articles[i] ) ;
}
}
Even a simple class like this has problems. Consider when a user balances inheritance and uses ArrayList's clear() method to pop the stack:
Stack a_stack = new Stack();
a_stack.push("1");
a_stack. push("2");
a_stack.clear();
This code compiles successfully, but because the base class does not know about the stack pointer stack, the stack object is currently in an undefined state . The next call to push() puts the new item at index 2. (the current value of stack_pointer), so the stack effectively has three elements - the bottom two are garbage. (Java's stack class has this problem, don't use it).
The solution to this annoying inherited method problem is to override all ArrayList methods for Stack, which can modify the state of the array. , so overwrite the correct operation of the Stack pointer or throw an exception. (The removeRange() method is a good candidate for throwing an exception).
This method has two disadvantages. First, if you cover everything, the base class should really be an interface, not a class. If you don't use any inherited methods, there is no point in implementing inheritance. Second, and more importantly, you cannot have a stack support all ArrayList methods. For example, the annoying removeRange() does nothing. The only reasonable way to implement a useless method is to make it throw an exception, since it should never be called. This method effectively turns compilation errors into runtime errors. The bad thing is that if the method is simply not defined, the compiler will print a method not found error. If a method exists but throws an exception, you won't find out about the calling error until the program is actually running.
A better solution to this base class problem is to encapsulate the data structure instead of using inheritance. This is the new and improved version of Stack:
class Stack
{
private int stack_pointer = 0;
private ArrayList the_data = new ArrayList();
public void push ( Object article )
{
the_data.add( stack_poniter , article );
}
public Object pop()
{
return the_data.remove( --stack_pointer );
}
public void push_many( Object[] articles )
{
for( int i = 0; i push( articles [i] );
}
}
So far, so good, but considering the fragile base class problem, let's say you want to create a variable in the stack and use it in a section Tracks the maximum stack size during the cycle. A possible implementation might look like this:
class Monitorable_stack extends Stack
{
private int high_water_mark = 0;
private int current_size;
public void push( Object article )
{
if( current_size > high_water_mark )
high_water_mark = current_size;
super.push( article );
}
publish Object pop()
{
--current_size;
return super.pop();
}
public int maximum_size_so_far()
{
return high_water_mark;
}
}
This new class worked just fine, at least for a while. Unfortunately, this code exploits the fact that push_many() operates by calling push(). First of all, this detail doesn't seem like a bad choice. It simplifies the code, and you can get the derived version of push() even when Monitorable_stack is accessed through a Stack reference, so that high_water_mark is updated correctly.
The above is the detailed content of Inheritance example analysis in Java. For more information, please follow other related articles on the PHP Chinese website!

The class loader ensures the consistency and compatibility of Java programs on different platforms through unified class file format, dynamic loading, parent delegation model and platform-independent bytecode, and achieves platform independence.

The code generated by the Java compiler is platform-independent, but the code that is ultimately executed is platform-specific. 1. Java source code is compiled into platform-independent bytecode. 2. The JVM converts bytecode into machine code for a specific platform, ensuring cross-platform operation but performance may be different.

Multithreading is important in modern programming because it can improve program responsiveness and resource utilization and handle complex concurrent tasks. JVM ensures the consistency and efficiency of multithreads on different operating systems through thread mapping, scheduling mechanism and synchronization lock mechanism.

Java's platform independence means that the code written can run on any platform with JVM installed without modification. 1) Java source code is compiled into bytecode, 2) Bytecode is interpreted and executed by the JVM, 3) The JVM provides memory management and garbage collection functions to ensure that the program runs on different operating systems.

Javaapplicationscanindeedencounterplatform-specificissuesdespitetheJVM'sabstraction.Reasonsinclude:1)Nativecodeandlibraries,2)Operatingsystemdifferences,3)JVMimplementationvariations,and4)Hardwaredependencies.Tomitigatethese,developersshould:1)Conduc

Cloud computing significantly improves Java's platform independence. 1) Java code is compiled into bytecode and executed by the JVM on different operating systems to ensure cross-platform operation. 2) Use Docker and Kubernetes to deploy Java applications to improve portability and scalability.

Java'splatformindependenceallowsdeveloperstowritecodeonceandrunitonanydeviceorOSwithaJVM.Thisisachievedthroughcompilingtobytecode,whichtheJVMinterpretsorcompilesatruntime.ThisfeaturehassignificantlyboostedJava'sadoptionduetocross-platformdeployment,s

Containerization technologies such as Docker enhance rather than replace Java's platform independence. 1) Ensure consistency across environments, 2) Manage dependencies, including specific JVM versions, 3) Simplify the deployment process to make Java applications more adaptable and manageable.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

DVWA
Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

MantisBT
Mantis is an easy-to-deploy web-based defect tracking tool designed to aid in product defect tracking. It requires PHP, MySQL and a web server. Check out our demo and hosting services.

SublimeText3 Chinese version
Chinese version, very easy to use

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),