> Java > java지도 시간 > 본문

JAVA의 간단한 for 루프에서 예외를 방지하는 방법은 무엇입니까?

WBOY
풀어 주다: 2023-04-26 12:58:07
앞으로
1101명이 탐색했습니다.

소개

실제 비즈니스 프로젝트 개발에서는 주어진 목록에서 조건에 맞지 않는 요소를 제거하는 작업을 누구나 숙지해야겠죠?

많은 학생들이 즉시 여러 가지 구현 방법을 떠올릴 수 있지만, 당신이 생각하는 이러한 구현 방법은 인간과 동물에게 무해합니까? 겉보기에 평범해 보이는 많은 작업이 실제로는 함정이며, 많은 초보자들이 조심하지 않으면 함정에 빠질 수 있습니다.

실수로 밟은 경우:

  • 코드가 실행 중일 때 직접 예외를 발생시키고 오류를 보고합니다. 이는 적어도 시간이 지나면 발견하고 해결할 수 있다는 점입니다.

    코드는 오류를 보고하지 않지만 비즈니스 로직은 설명할 수 없을 정도로 나타납니다. 이상한 문제는 이런 종류가 더 비극적입니다. 왜냐하면 이 문제에 주의를 기울이지 않으면 후속 비즈니스에 숨겨진 위험이 발생할 수 있기 때문입니다.
  • 그렇다면 이를 달성하는 방법은 무엇인가요? 어떤 구현 방법에 문제가 있을 수 있나요? 여기서 함께 논의해 봅시다. 여기서 논의되는 것은 회향콩에 "회향"이라는 단어를 어떻게 쓰는지에 대한 문제가 아니라 쉽게 무시할 수 있는 매우 심각하고 실용적인 기술적 문제라는 점을 유의하시기 바랍니다.
가설 수요 시나리오:

사용자 목록 allUsers가 주어지면 부서가 dev인 직원을 목록에서 제거하고 나머지 직원 정보를 반환해야 합니다.

추적 작업

foreach 루프 제거 방법

많은 초보자 첫 번째 아이디어는 for 루프를 하나씩 확인하고 조건에 맞는 것을 제거하는 것입니다~너무 쉽습니다...

1분 만에 코드 작성이 끝났습니다:

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    for (UserDetail user : allUsers) {
        // 判断部门如果属于dev,则直接剔除
        if ("dev".equals(user.getDepartment())) {
            allUsers.remove(user);
        }
    }
    // 返回剩余的用户数据
    return allUsers;
}
로그인 후 복사

그런 다음 자신감 있게 실행 버튼을 눌렀습니다:

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)
로그인 후 복사

뭐라고? 뭐하세요? 예외가 발생하는 이유는 무엇입니까?

무심코 함정에 빠졌는데 왜 예외가 발생했는지 분석해 보겠습니다.

원인 분석:

JAVA foreach 구문의 실제 처리는 반복자 Iterator를 기반으로 합니다.

루프가 시작될 때 먼저 반복 인스턴스가 생성되고 이 반복 인스턴스의 ExpectModCount에 컬렉션의 modCount가 할당됩니다. 반복자가 다음 요소를 순회하기 위해 hashNext() / next()를 사용할 때마다 modCount 변수와 예상ModCount 값이 동일한지 확인하고, 그렇지 않으면 순회가 반환됩니다. 순회를 종료합니다.

루프에서 요소를 추가하거나 삭제하는 경우 컬렉션의 add() 및 제거() 메서드를 직접 호출하여 modCount가 증가하거나 감소하지만 이러한 메서드는 반복 인스턴스에서 예상ModCount를 수정하지 않으므로 결과적으로 반복 인스턴스의 예상ModCount에서 modCount 값과 같지 않으면 ConcurrentModificationException 예외가 발생합니다.

아래 첨자 루프 연산JAVA의 간단한 for 루프에서 예외를 방지하는 방법은 무엇입니까?

흠? foreach 메서드가 작동하지 않으므로 원래의 첨자 루프 메서드를 사용하면 오류가 발생하지 않습니다. 여전히 매우 쉽습니다...

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);
        }
    }
    // 返回剩余的用户数据
    return allUsers;
}
로그인 후 복사

코드가 한 번에 완성되고, 실행하고 처리된 출력을 확인하세요:

{id=2, name='lee思', Department='dev'}

{id =3, name ='王五', Department='product'}
{id=4, name='Tiezhu', Department='pm'}



물론, 오류도 보고되지 않고 결과도 출력됩니다. , 완벽해요~

기타..? 정말 괜찮은 걸까요?

우리의 코드 로직은 "dev".equals(department)인지 판단하는 것인데, 출력 결과에는 왜 아직도 제거해야 할 Department=dev 같은 데이터가 있나요?

실제 비즈니스 프로젝트라면 개발 단계에서 오류가 보고되지 않고, 결과가 꼼꼼히 검증되지 않은 후 생산 라인으로 흘러가게 된다면 비정상적인 비즈니스 로직이 발생할 수 있습니다.

이 현상이 나타나는 구체적인 이유를 살펴보겠습니다.

원인 분석:

우리는 실제로 목록의 요소와 첨자 사이에 강력한 바인딩 관계가 없다는 것을 알고 있습니다. 이는 단지 목록의 요소가 변경된 후 해당 첨자의 대응 관계일 뿐입니다. 각 요소는 아래와 같이 아래 첨자가 변경될 수 있습니다.

그런 다음 목록에서 요소를 삭제한 후 목록에서 삭제된 요소 뒤에 있는 모든 요소의 아래 첨자는 앞으로 이동하지만 for의 포인터 i 루프는 항상 거꾸로 누적됩니다. 예, 다음 루프를 처리할 때 일부 요소가 누락되어 처리되지 않을 수 있습니다. JAVA의 간단한 for 루프에서 예외를 방지하는 방법은 무엇입니까?

예를 들어 아래 그림과 같이 i=0이면 A 요소를 삭제해야 한다고 판단하고, 재활용 시 i=1이면 바로 삭제됩니다. 목록의 요소가 앞으로 이동하면 B 요소가 원래 아래 첨자가 됩니다. 0의 위치가 직접 누락되었습니다.

여기서 위 코드가 실행 후 누락되는 이유를 알 수 있습니다~JAVA의 간단한 for 루프에서 예외를 방지하는 방법은 무엇입니까?

올바른 방법

위의 2가지 함정을 확인했습니다. 수술 후 올바르고 적절한 수술방법은 무엇인가요?

Iterator 메서드

어? 좋아요? foreach 메서드도 반복자를 사용한다고 방금 말하지 않았는데 실제로는 트랩 작업인가요? 여기서 반복자 패턴이 올바른 방식이라고 말하는 이유는 무엇입니까?

虽然都是基于迭代器,但是使用逻辑是不一样的,看下代码:

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();
        }
    }
    // 返回剩余的用户数据
    return allUsers;
}
로그인 후 복사

执行结果:

{id=3, name='王五', department='product'}
{id=4, name='铁柱', department='pm'}

这次竟然直接执行成功了,且结果也是正确的。为啥呢?

在前面foreach方式的时候,我们提过之所以会报错的原因,是由于直接修改了原始list数据而没有同步让Iterator感知到,所以导致Iterator操作前校验失败抛异常了。

而此处的写法中,直接调用迭代器中的remove()方法,此操作会在调用集合的remove(),add()方法后,将expectedModCount重新赋值为modCount,所以在迭代器中增加、删除元素是可以正常运行的。,所以这样就不会出问题啦。

JAVA의 간단한 for 루프에서 예외를 방지하는 방법은 무엇입니까?

Lambda表达式

言简意赅,直接上代码:

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    allUsers.removeIf(user -> "dev".equals(user.getDepartment()));
    return allUsers;
}
로그인 후 복사

Stream流操作

作为JAVA8开始加入的Stream,使得这种场景实现起来更加的优雅与易懂:

public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    return allUsers.stream()
            .filter(user -> !"dev".equals(user.getDepartment()))
            .collect(Collectors.toList());
}
로그인 후 복사

中间对象辅助方式

既然前面说了不能直接循环的时候执行移除操作,那就先搞个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;
}
로그인 후 복사

위 내용은 JAVA의 간단한 for 루프에서 예외를 방지하는 방법은 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:yisu.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿