调试模式下不存在的错误,但就在我 运行 项目时

A bug that is not present in debug mode, but just when i run the project

我正在做一个学校项目,我们正在制作一本电子成绩册,这只是一个学习更好的 OOP 和 UML 的项目,并不是真正的。我们在一个奇怪的错误中受阻,我们花了很多时间试图解决它,但我们找不到解决方案。读取学生姓名、日期、class 等的函数。当我们调试它时有一个奇怪的错误,它读取所有信息并保存到 class 数组没有任何问题但是当我们 运行 它时,有些学生没有添加到 class 虽然输出似乎是不确定的。我确实找到了一种方法来克服这个错误,方法是在函数中添加 1 毫秒的线程睡眠,并且它起作用了。但是我们不知道为什么以及有什么问题,这毫无意义。我们花了大约 10 个小时来寻找原因和真正的解决方案。

这是函数:

/**
     * Import the studend list from a text file and load them into an array
     * @param nomeFile file name of the studend 
     * @param classi array name of all the classes 
     * @param nclassi number of classes 
     * @return number of student imported 
     * @throws FileNotFoundException, IOException problemi con la lettura del file
     *         IndexOutOfBoundsException problemi di inserimento dell'alunno nella classe specificata
     *         Exception problemi con la data di nasciita dello studente
     */
    public static int importaIscrizioniAlunni(String nomeFile, Classe[] classi, int nclassi) throws FileNotFoundException,IOException,IndexOutOfBoundsException,Exception {
        
        BufferedReader br = null;
        String riga = "";  
        int iscrizioni=0;

        // Carico le iscrizioni effettuate dagli alunni dal file
        try 
        {
            br = new BufferedReader(new FileReader(nomeFile)); 

            //PROBLEM STARTS
            while ((riga = br.readLine()) != null) 
            {
                String[] campi = riga.split(",");
                // campi[1] => cognome
                // campi[2] => nome
                // campi[3] => sesso
                // data nascita: campi[4] => giorno campi[5] => mese campi[6] => anno
                // campi[7] => classe campi[8] => sezione 
                Studente nuovo = new Studente(campi[0], campi[1], campi[2], campi[3].charAt(0),  Integer.valueOf(campi[4]), Integer.valueOf(campi[5]), Integer.valueOf(campi[6]));

                Classe classe_scelta = new Classe(Integer.parseInt(campi[7]), campi[8].charAt(0), null);
                
                //print the new student and new class using toString just for debug purposes
                System.out.println("input: " + nuovo .toString()+ " " + classe_scelta.toString());
                for(int i = 0; i < nclassi; i++)
                {
                    //trova la classe
                    if(classi[i].equals(classe_scelta))
                    {
                        try 
                        {
                            classi[i].aggiungi(nuovo);
                            //print the new student and new class just for debug purposes
                            System.out.println("Iscrizione effettuata: " + nuovo.getCognome() + " " + nuovo.getNome() + " " + classi[i].getSezione() + " " + classi[i].getPercorso().getannoCorso() + "\n\n\n");
                            break;
                        } 
                        catch(IndexOutOfBoundsException e) 
                        {
                            throw new IndexOutOfBoundsException();
                        }
                    }
                }
                iscrizioni++;
                //Thread.sleep(1); Solve the bug but it's not the best solution we couldn't find what is causing the bug
                
                //print the class using toString just for debug purposes
                for(int i = 0; i < nclassi; i++)
                {
                    System.out.println(classi[i]);
                }
            }
            //PROBLEM ENDS
    } 
    catch (FileNotFoundException e) 
    {
            throw new FileNotFoundException();
        } catch (IOException e) {
            throw new IOException("Errore di accesso al file");
    } catch (Exception e) {
            throw new Exception("Errore generico durante importazione alunni (probabile data non valida nel file)");
    }finally {
            if (br != null) {
        try {
                    br.close();
        } catch (IOException e) {
                    throw new IOException("Errore durante chiusura del file");  
                }
            } 
        }
        return iscrizioni;
    }

如果变量是意大利语,我很抱歉,但由于是一个团队学校项目,我们必须适应每个人,正如你可以看到这种方法阅读学生找到他的 class并将学生添加到其中。

“aggiungi”方法,意思是添加:

    public void aggiungi(Studente nuovo) throws IllegalArgumentException 
    { 
        if (this.numeroStudenti + 1 <= MAX_STUDENTI)
        {
            this.elenco.add(nuovo);
            nuovo.classe = this;
            nuovo.assegnaMatricola();
            numeroStudenti++;
        }
        else
            throw new IllegalArgumentException();
    }

我们将学生添加到内部容器中,这是一个Set,然后我们将学生的class设置为这个学生,然后分配学生ID,最后增加学生人数。

非常感谢任何帮助,我们很生气,因为代码在我们调试时可以正常工作,如果您需要有关代码的更多信息甚至完整的存储库,请提出要求。谢谢指教。

我们尝试调试了几次,它成功了,我们尝试使用多个输出进行调试,我们尝试了单个添加功能,我们检查了问题是否是通过读取文件造成的,但事实并非如此似乎是,我们还看到,如果我们在方法调用之后调试时放置断点,问题仍然存在,并且一切正常。

这是 运行 模式下的输出示例:

这是学生的文本文件:

RSSMRA00R05A519L,Rossi,Mario,m,10,5,2000,3,A
VRDLGU99A01E423D,Verdi,Luigi,m,1,1,1999,3,A
BNCPLA00S63D848E,Bianchi,Paola,f,23,11,2000,3,A
CRLNRE99D45E458M,Neri,Carlo,m,5,4,1999,4,A
GLIRTT99D45D852Z,Rossetto,Giulia,f,2,2,1998,4,A
RSORSO99D45D852K,Rosa,Rosa,f,25,4,1998,4,A
GLLDRA99E04A182Y,Gialli,Dario,m,4,5,1999,4,A
VLIVLI98C43A182B,Viola,Viola,f,3,3,1998,4,B
RSSMRC98M14A182A,Rossi,Marco,m,14,8,1998,4,B

这是输出:

input: Matricola: 0, Rossi, Mario Classe 3^A
Registration completed: Rossi Mario A 3

classes:
Classe 3^A
1: Matricola: 1, Rossi, Mario

Classe 4^A

Classe 4^B



input: Matricola: 0, Verdi, Luigi Classe 3^A
Registration completed: Verdi Luigi A 3

classes:
Classe 3^A
1: Matricola: 1, Rossi, Mario
2: Matricola: 2, Verdi, Luigi

Classe 4^A

Classe 4^B


input: Matricola: 0, Bianchi, Paola Classe 3^A
Registration completed: Bianchi Paola A 3

classes:
Classe 3^A
1: Matricola: 1, Rossi, Mario
2: Matricola: 2, Verdi, Luigi

Classe 4^A

Classe 4^B


input: Matricola: 0, Neri, Carlo Classe 4^A
Registration completed: Neri Carlo A 4

classes:
Classe 3^A
1: Matricola: 1, Rossi, Mario
2: Matricola: 2, Verdi, Luigi

Classe 4^A
1: Matricola: 4, Neri, Carlo

Classe 4^B


input: Matricola: 0, Rossetto, Giulia Classe 4^A
Registration completed: Rossetto Giulia A 4

classes:
Classe 3^A
1: Matricola: 1, Rossi, Mario
2: Matricola: 2, Verdi, Luigi

Classe 4^A
1: Matricola: 4, Neri, Carlo

Classe 4^B


input: Matricola: 0, Rosa, Rosa Classe 4^A
Registration completed: Rosa Rosa A 4

classes:
Classe 3^A
1: Matricola: 1, Rossi, Mario
2: Matricola: 2, Verdi, Luigi

Classe 4^A
1: Matricola: 4, Neri, Carlo

Classe 4^B

input: Matricola: 0, Gialli, Dario Classe 4^A
Registration completed: Gialli Dario A 4

classes:
Classe 3^A
1: Matricola: 1, Rossi, Mario
2: Matricola: 2, Verdi, Luigi

Classe 4^A
1: Matricola: 4, Neri, Carlo
2: Matricola: 7, Gialli, Dario

Classe 4^B


input: Matricola: 0, Viola, Viola Classe 4^B
Registration completed: Viola Viola B 4

classes:
Classe 3^A
1: Matricola: 1, Rossi, Mario
2: Matricola: 2, Verdi, Luigi

Classe 4^A
1: Matricola: 4, Neri, Carlo
2: Matricola: 7, Gialli, Dario

Classe 4^B
1: Matricola: 8, Viola, Viola


input: Matricola: 0, Rossi, Marco Classe 4^B
Registration completed: Rossi Marco B 4

classes:
Classe 3^A
1: Matricola: 1, Rossi, Mario
2: Matricola: 2, Verdi, Luigi

Classe 4^A
1: Matricola: 4, Neri, Carlo
2: Matricola: 7, Gialli, Dario

Classe 4^B
1: Matricola: 8, Viola, Viola

据我所知,只有余数的输出是不确定的,所以另一个 运行 也会在经过大量尝试后的一段时间内给出不同的输出,它将给出正确的答案。如您所见,并非所有学生都在注册。

感谢 MCVE。没有它,就不可能找出问题所在,因为问题出在您没有显示的部分代码中。

问题始于用户群 class Utente extends GregorianCalendar。用户 不是 日历。也许用户使用一个或多个日历.如果你的 class 是关于 OOP 原则的,你可能想研究一下 generalisation (inheritance) vs. composition vs. aggregation vs. association.

之间的区别

Utente 扩展 GregorianCalendar 的相当技术性的问题是它的父 class Calendar 已经实现了 Comparable<Calendar>。所以如果你想覆盖compareTo,你不能像你尝试的那样覆盖compareTo(Object)(但被注释掉),甚至不能覆盖compareTo(Studente)以实现TreeSet中的排序,但你需要覆盖 compareTo(Calendar)。其他两个变体将永远不会被调用。

背景:由于type erasure,你不能为不同的泛型类型实现相同的接口两次,即类似Studente extends Utente implements Comparable<Studente>的东西会产生编译错误"java.lang.Comparable cannot be inherited with different arguments: <scuola.Studente> and <java.util.Calendar>"

上述结果是,您的代码现在在 GitHub 上的显示方式,您的 TreeSet<Studente> 将被排序为一组 Calendar 个对象。但是 Utente 不会通过在其自己的构造函数中调用 super(...) 来初始化父日历实例,即日历将未初始化,因为只会调用其没有参数的默认构造函数。但是那个被 Object 继承并且没有被任何日历或用户 class 覆盖,即 Calendar.compareTo,当注意到没有为自己设置时间时,将简单地总是使用 System.currentTimeMillis().结果很难预测并且看起来 non-deterministic,尽管它们当然是完全确定的,但取决于何时为哪个 Studente 对象调用 compareTo,以及哪些在左边和比较的右侧。

因此,由于 Calendar.compareTo(Calendar) 不适合 class StudenteTreeSet 将出现不一致的排序,例如A < B 和 B < C 并不可靠地表示 A < C。甚至可能是你一次得到 A < B 并且同时得到 B < A。您的 TreeSet,如果您只是打印其大小或将其 toString() 值打印到控制台,将显示正确的大小和正确的 Studente 元素集。但是当像在 Classe.toString() 方法中那样对其进行迭代时,由于伪造的排序,您会看到奇怪的结果,因为它扰乱了 set 迭代器。顺便说一下,我会推荐一种 toString 方法,它只打印一个没有换行符的简单字符串。如果你想打印多行的东西,你应该在实用方法中这样做。

所以你有几个选择:

  1. Studente 中实现 compareTo(Calendar)(如果你需要的话,也可以在 Utente 中实现)。但是你需要确保它也对传入的其他 Calendar 实例做一些有意义的事情,或者如果传入 Student 以外的其他东西,至少会抛出有意义的异常。但是就像我说的,一个学生或者用户不是日历,所以这感觉很不对。

  2. Utente 中删除 extends GregorianCalendar 并让 Studente implements Comparable<Studente> 代替。然后实现一个与equals(Object)一致的compareTo(Studente)方法。因为您的 equals 方法首先按 ID (matricola) 进行比较,然后按姓氏 (cognome) 和名字 (nome) 进行比较,所以您应该在 compareTo 而不是使用 mediaVoti() 等其他标准。如果你想按平均票数排序(无论这在你的上下文中意味着什么),你仍然可以稍后使用 Collections.sort(List, Comparator) 左右,使用自定义比较器。

  3. 根本不实施 Comparable,使用正常的未排序集合,并使用 Collections.sort(List, Comparator).

    按需对学生进行排序

我给你看没有。 2,所以你可以学习一些关于ComparablecompareTo的东西:

public class Utente /*extends GregorianCalendar*/ {
    // ...
}
public class Studente extends Utente implements Comparable<Studente> {
    // ...
    /**
     * Confronta per matricola, cognome, nome
     */
    @Override
    public int compareTo(Studente altroStudente) {
        if (this.equals(altroStudente))
            return 0;
        int result = Integer.compare(matricola, altroStudente.matricola);
        if (result != 0)
            return result;
        result = cognome.compareTo(altroStudente.cognome);
        if (result != 0)
            return result;
        return nome.compareTo(altroStudente.nome);
    }
    // ...
}

这个解决方案也不完美,因为如果名字或姓氏是 null,它会抛出异常。但我想对于您的简单解决方案来说,这是可以接受的。

在你的申请中还有许多其他大大小小的问题,但我知道你们都刚刚开始,我认为总的来说你们作为一个团队已经取得了很多成就。只是一些小东西:

  • Classe.equals(Object) 包含一个错误:if (altro == this) return false; 应该是 if (altro == this) return true;.

  • PercorsoDidattico.equals(Object) 也包含一个问题:当比较字符串时,你应该使用 equals,而不是 ==,即你应该使用 an.indirizzo == this.indirizzo使用 an.indirizzo.equals(this.indirizzo).

我可以写更多,但答案已经很长了。

修复两个 equals 方法并正确实现 Comparable<Studente> 接口后,您的程序打印:

...
input: Matricola: 0, Rossi, Marco Classe 4^B

Iscrizione effettuata: Rossi Marco B 4

Classe 3^A
1: Matricola: 0, Bianchi, Paola
2: Matricola: 0, Rossi, Mario
3: Matricola: 0, Verdi, Luigi

Classe 4^A
1: Matricola: 0, Gialli, Dario
2: Matricola: 0, Neri, Carlo
3: Matricola: 0, Rosa, Rosa
4: Matricola: 0, Rossetto, Giulia

Classe 4^B
1: Matricola: 0, Rossi, Marco
2: Matricola: 0, Viola, Viola

如您所见,新的 compareTo(Studente) 方法导致每个 class 按学生姓氏排序,然后是名字,因为 matricola 在您的程序中仍未使用。


更新: 顺便说一下,为什么它在调试器或附加 Thread.sleep() 中正常工作的解释可能与 Calendar.compareTo 使用有关不同的时间戳,这意外地导致至少迭代器找到了l tree-set中的元素。根据您的代码,在其他情况下仍然会失败,您在那里很幸运。