How to avoid exceptions in simple for loops in JAVA?
Apr 26, 2023 pm 12:58 PMIntroduction
In actual business project development, everyone should be familiar with the operation of removing elements that do not meet the conditions from a given list, right?
Many students can immediately think of many ways to achieve it, but are these implementation methods you think of harmless to humans and animals? Many seemingly normal operations are actually traps, and many novices may fall into them if they are not careful.
If you accidentally step on it:
The code will directly throw an exception and report an error when running. This is a blessing in misfortune. At least it can be discovered and solved in time
The code runs without error, but various strange problems appear inexplicably in the business logic. This is more tragic, because if this problem is not paid attention to, it may cause hidden dangers for subsequent business.
So, what are the implementation methods? Which implementation methods may have problems? Let’s discuss it together here. Please note that what is discussed here is not the issue of how to write the word "fennel" in fennel beans, but a technical issue that is very serious, practical and easy to be ignored.
Hypothetical demand scenario:
Given a user list allUsers, it is necessary to remove the personnel whose subordinate department is dev from the list, and return the remaining personnel information
Step on the pit Operation
foreach loop elimination method
The first thought of many novices is to check the for loop one by one and then eliminate those that meet the conditions~ so easy...
I finished writing the code in 1 minute:
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { for (UserDetail user : allUsers) { // 判斷部門如果屬于dev,則直接剔除 if ("dev".equals(user.getDepartment())) { allUsers.remove(user); } } // 返回剩余的用戶數(shù)據 return allUsers; }
Then I clicked the execute button with confidence:
java.util.ConcurrentModificationException: null at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at com.veezean.demo4.UserService.filterAllDevDeptUsers(UserService.java:13) at com.veezean.demo4.Main.main(Main.java:26)
Eh? what are you doing? Why is the exception thrown?
If you don’t pay attention, you will step into a trap. Let’s analyze why an exception is thrown.
Cause analysis:
The actual processing of JAVA’s foreach syntax is based on the iterator Iterator.
At the beginning of the loop, an iteration instance will first be created, and the expectedModCount of this iteration instance is assigned the modCount of the collection. Whenever the iterator uses hashNext() / next() to traverse the next element, it will check whether the modCount variable and expectedModCount values ??are equal. If they are equal, the traversal will be returned; otherwise, a ConcurrentModificationException will be thrown to terminate the traversal.
If you add or delete elements in a loop, you directly call the add() and remove() methods of the collection, causing the modCount to increase or decrease, but these methods will not modify the expectedModCount in the iteration instance, resulting in If the values ??of expectedModCount and modCount in the iteration instance are not equal, a ConcurrentModificationException exception is thrown.
Subscript loop operation
Huh? Since the foreach method doesn't work, then use the original subscript loop method to do it. You won't get an error, right? It's still very easy...
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { for (int i = 0; i < allUsers.size(); i++) { // 判斷部門如果屬于dev,則直接剔除 if ("dev".equals(allUsers.get(i).getDepartment())) { allUsers.remove(i); } } // 返回剩余的用戶數(shù)據 return allUsers; }
The code is completed in one go. Execute it and see the processed output:
{id=2, name='李思', department=' dev'}
{id=3, name='王五', department='product'}
{id=4, name='Tiezhu', department='pm'}
Sure enough, no error is reported, the result is also output, perfect~
Wait? Is this really OK?
The logic of our code is to determine if "dev".equals(department), but in the output result, why is there still data like department=dev that should be eliminated?
If this is a real business project, if no errors are reported during the development phase, and the results are not carefully verified, and then flow to the production line, it may cause abnormal business logic.
Let’s take a look at the specific reasons for this phenomenon.
Cause analysis:
We know that there is actually no strong binding relationship between the elements in the list and the subscripts. It is just a corresponding relationship of position order. After the elements in the list are changed, , the subscript corresponding to each element may change, as shown below:
Then, after deleting an element from the List, all elements after the deleted element in the List The subscripts are moved forward, but the pointer i of the for loop is always accumulated backwards. When processing the next one, some elements may be missed and not processed.
For example, as shown in the figure below, when i=0, it is judged that the A element needs to be deleted, and it is deleted directly; when recirculating, i=1, at this time, because the position of the element in the list moves forward, the B element becomes The original position with the subscript 0 was directly missed:
So here you can know why the above code will be missed after execution. La~
Correct way
After seeing the above two pitfalls, what should be the correct and appropriate way to operate?
Iterator method
Eh? That's right? Didn't I just say that the foreach method also uses iterators, but is it actually a trap operation? Why is it said here that the iterator pattern is the correct way?
雖然都是基于迭代器,但是使用邏輯是不一樣的,看下代碼:
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { Iterator<UserDetail> iterator = allUsers.iterator(); while (iterator.hasNext()) { // 判斷部門如果屬于dev,則直接剔除 if ("dev".equals(iterator.next().getDepartment())) { // 這是重點,此處操作的是Iterator,而不是list iterator.remove(); } } // 返回剩余的用戶數(shù)據 return allUsers; }
執(zhí)行結果:
{id=3, name='王五', department='product'}
{id=4, name='鐵柱', department='pm'}
這次竟然直接執(zhí)行成功了,且結果也是正確的。為啥呢?
在前面foreach方式的時候,我們提過之所以會報錯的原因,是由于直接修改了原始list數(shù)據而沒有同步讓Iterator感知到,所以導致Iterator操作前校驗失敗拋異常了。
而此處的寫法中,直接調用迭代器中的remove()方法,此操作會在調用集合的remove(),add()方法后,將expectedModCount重新賦值為modCount,所以在迭代器中增加、刪除元素是可以正常運行的。,所以這樣就不會出問題啦。
Lambda表達式
言簡意賅,直接上代碼:
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { allUsers.removeIf(user -> "dev".equals(user.getDepartment())); return allUsers; }
Stream流操作
作為JAVA8開始加入的Stream,使得這種場景實現(xiàn)起來更加的優(yōu)雅與易懂:
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { return allUsers.stream() .filter(user -> !"dev".equals(user.getDepartment())) .collect(Collectors.toList()); }
中間對象輔助方式
既然前面說了不能直接循環(huán)的時候執(zhí)行移除操作,那就先搞個list對象將需要移除的元素暫存起來,最后一起剔除就行啦 ~
嗯,雖然有點挫,但是不得不承認,實際情況中,很多人都在用這個方法 —— 說的就是你,你是不是也曾這么寫過?
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { List<UserDetail> needRemoveUsers = new ArrayList<>(); for (UserDetail user : allUsers) { if ("dev".equals(user.getDepartment())) { needRemoveUsers.add(user); } } allUsers.removeAll(needRemoveUsers); return allUsers; }
或者:
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { List<UserDetail> resultUsers = new ArrayList<>(); for (UserDetail user : allUsers) { if (!"dev".equals(user.getDepartment())) { resultUsers.add(user); } } return resultUsers; }
The above is the detailed content of How to avoid exceptions in simple for loops in JAVA?. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

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

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

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

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

To correctly handle JDBC transactions, you must first turn off the automatic commit mode, then perform multiple operations, and finally commit or rollback according to the results; 1. Call conn.setAutoCommit(false) to start the transaction; 2. Execute multiple SQL operations, such as INSERT and UPDATE; 3. Call conn.commit() if all operations are successful, and call conn.rollback() if an exception occurs to ensure data consistency; at the same time, try-with-resources should be used to manage resources, properly handle exceptions and close connections to avoid connection leakage; in addition, it is recommended to use connection pools and set save points to achieve partial rollback, and keep transactions as short as possible to improve performance.

TheJVMenablesJava’s"writeonce,runanywhere"capabilitybyexecutingbytecodethroughfourmaincomponents:1.TheClassLoaderSubsystemloads,links,andinitializes.classfilesusingbootstrap,extension,andapplicationclassloaders,ensuringsecureandlazyclassloa

Use classes in the java.time package to replace the old Date and Calendar classes; 2. Get the current date and time through LocalDate, LocalDateTime and LocalTime; 3. Create a specific date and time using the of() method; 4. Use the plus/minus method to immutably increase and decrease the time; 5. Use ZonedDateTime and ZoneId to process the time zone; 6. Format and parse date strings through DateTimeFormatter; 7. Use Instant to be compatible with the old date types when necessary; date processing in modern Java should give priority to using java.timeAPI, which provides clear, immutable and linear

Pre-formanceTartuptimeMoryusage, Quarkusandmicronautleadduetocompile-Timeprocessingandgraalvsupport, Withquarkusoftenperforminglightbetterine ServerLess scenarios.2.Thyvelopecosyste,

Java's garbage collection (GC) is a mechanism that automatically manages memory, which reduces the risk of memory leakage by reclaiming unreachable objects. 1.GC judges the accessibility of the object from the root object (such as stack variables, active threads, static fields, etc.), and unreachable objects are marked as garbage. 2. Based on the mark-clearing algorithm, mark all reachable objects and clear unmarked objects. 3. Adopt a generational collection strategy: the new generation (Eden, S0, S1) frequently executes MinorGC; the elderly performs less but takes longer to perform MajorGC; Metaspace stores class metadata. 4. JVM provides a variety of GC devices: SerialGC is suitable for small applications; ParallelGC improves throughput; CMS reduces

Networkportsandfirewallsworktogethertoenablecommunicationwhileensuringsecurity.1.Networkportsarevirtualendpointsnumbered0–65535,withwell-knownportslike80(HTTP),443(HTTPS),22(SSH),and25(SMTP)identifyingspecificservices.2.PortsoperateoverTCP(reliable,c

defer is used to perform specified operations before the function returns, such as cleaning resources; parameters are evaluated immediately when defer, and the functions are executed in the order of last-in-first-out (LIFO); 1. Multiple defers are executed in reverse order of declarations; 2. Commonly used for secure cleaning such as file closing; 3. The named return value can be modified; 4. It will be executed even if panic occurs, suitable for recovery; 5. Avoid abuse of defer in loops to prevent resource leakage; correct use can improve code security and readability.

Gradleisthebetterchoiceformostnewprojectsduetoitssuperiorflexibility,performance,andmoderntoolingsupport.1.Gradle’sGroovy/KotlinDSLismoreconciseandexpressivethanMaven’sverboseXML.2.GradleoutperformsMaveninbuildspeedwithincrementalcompilation,buildcac
