Skip to content

Commit c9aa3b4

Browse files
committed
Array-Ausgabe wie in Java: Referenz statt Inhalt, Arrays.toString zeigt den Inhalt
System.out.println(zahlen) gibt in der Simulation und in Programmieren jetzt wie echtes Java eine Referenz wie [I@… aus. Arrays.toString und Arrays.deepToString zeigen den Inhalt; Arrays braucht import java.util.Arrays (Code-Export ergänzt ihn). Lektion 15 erklärt das in de/en/pt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ugz28VVb2pkKNmmHKgsfxu
1 parent a8d0e79 commit c9aa3b4

11 files changed

Lines changed: 1361 additions & 1260 deletions

File tree

‎web/lessons/de/15-arrays.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ for (int i = 0; i < zahlen.length; i++) {
146146
- Ein leeres Array mit 5 Fächern legst du mit `new int[5]` an.
147147
- `zahlen[i]` ist das Fach mit dem Index `i`, `zahlen.length` die Länge.
148148

149+
Gibst du das ganze Array aus, also `System.out.println(zahlen);`, zeigt Java nicht die Zahlen. Du siehst nur einen Text wie `[I@1b6d3586`. Das ist die **Referenz**, also die Stelle, an der das Array im Speicher liegt. Den Inhalt zeigt erst `Arrays.toString(zahlen)`, das ergibt `[2, 4, 6, 8, 10]`. Dafür schreibst du ganz oben `import java.util.Arrays;`.
150+
149151
::: übung l15-java-noten
150152
titel: Notensumme in Java
151153
code: java

‎web/lessons/en/15-arrays.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ for (int i = 0; i < numbers.length; i++) {
146146
- You create an empty array with 5 slots with `new int[5]`.
147147
- `numbers[i]` is the slot with index `i`, `numbers.length` is the length.
148148

149+
If you output the whole array, that is `System.out.println(numbers);`, Java does not show the numbers. You only see a text like `[I@1b6d3586`. This is the **reference**, the place where the array is stored in memory. `Arrays.toString(numbers)` shows the content: `[2, 4, 6, 8, 10]`. For this you write `import java.util.Arrays;` at the very top.
150+
149151
::: übung l15-java-noten
150152
titel: Sum of grades in Java
151153
code: java

‎web/lessons/pt/15-arrays.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ for (int i = 0; i < numeros.length; i++) {
146146
- Um array vazio com 5 posições cria-se com `new int[5]`.
147147
- `numeros[i]` é a posição com o índice `i`, `numeros.length` é o comprimento.
148148

149+
Se mostrares o array inteiro, ou seja `System.out.println(numeros);`, o Java não mostra os números. Só vês um texto como `[I@1b6d3586`. É a **referência**, o sítio onde o array está guardado na memória. O conteúdo só aparece com `Arrays.toString(numeros)`, que dá `[2, 4, 6, 8, 10]`. Para isso escreves `import java.util.Arrays;` logo no início.
150+
149151
::: übung l15-java-noten
150152
titel: Soma das notas em Java
151153
code: java

‎web/src/core/codeGenerator.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,27 +1085,31 @@ function documentContainsJavaInput(doc: VSDocument): boolean {
10851085
return found
10861086
}
10871087

1088+
const arraysImport = 'import java.util.Arrays;'
1089+
10881090
/**
10891091
* Imports für Dateien: `Files`/`Path` aus `java.nio.file`, `File`, `PrintWriter`, `FileReader` und `BufferedReader` aus
1090-
* `java.io`, dazu `java.util.Scanner` für einen Scanner auf einer Datei.
1092+
* `java.io`, dazu `java.util.Scanner` für einen Scanner auf einer Datei und `java.util.Arrays` für `Arrays.toString(…)`.
10911093
*/
10921094
export function fileImports(doc: VSDocument): string[] {
10931095
let nio = false
10941096
let io = false
10951097
let scanner = false
1098+
let arrays = false
10961099
walkElements(doc.elements, (e) => {
10971100
for (const l of e.textLines) {
1101+
if (/\bArrays\s*\./.test(l)) arrays = true
10981102
if (/\bFiles\s*\./.test(l)) nio = true
10991103
if (/\bnew\s+(?:File|PrintWriter|FileReader|FileWriter|BufferedReader)\s*\(/.test(l)) io = true
11001104
if (/\bnew\s+Scanner\s*\(\s*new\s+File\b/.test(l)) scanner = true
11011105
}
11021106
})
1103-
return [...(io ? ['import java.io.*;'] : []), ...(nio ? ['import java.nio.file.*;'] : []), ...(scanner ? ['import java.util.Scanner;'] : [])]
1107+
return [...(io ? ['import java.io.*;'] : []), ...(nio ? ['import java.nio.file.*;'] : []), ...(scanner ? ['import java.util.Scanner;'] : []), ...(arrays ? [arraysImport] : [])]
11041108
}
11051109

11061110
/** Arbeitet das Programm mit Dateien? Dann kann eine IOException kommen, und Java verlangt `throws` an der Methode. */
11071111
export function documentReadsFiles(doc: VSDocument): boolean {
1108-
return fileImports(doc).length > 0
1112+
return fileImports(doc).some((i) => i !== arraysImport)
11091113
}
11101114

11111115
function documentContainsJavaMathRandom(doc: VSDocument): boolean {
@@ -1794,7 +1798,7 @@ export function generateJavaClassFile(
17941798
const scannerField = methods.length > 0 && (hasInput || methodInput)
17951799
// `Files.readString` kann eine IOException werfen; Java verlangt dann `throws` an der Methode.
17961800
const imports = [...new Set([document, ...methodDocs.map((m) => m.d)].flatMap(fileImports))]
1797-
const files = imports.length > 0
1801+
const files = imports.some((i) => i !== arraysImport)
17981802
if ((hasInput || methodInput) && !imports.includes('import java.util.Scanner;')) imports.push('import java.util.Scanner;')
17991803
for (const line of imports) out += line + '\n'
18001804
if (imports.length) out += '\n'

‎web/src/core/javaParser.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,14 +223,15 @@ class Parser {
223223

224224
/**
225225
* Wie javac: `Scanner` ohne `import java.util.Scanner;` (oder `java.util.*`) ist ein Fehler, ebenso `Files` und `Path` ohne
226-
* `java.nio.file` und `File`, `PrintWriter`, `FileReader`, `FileWriter`, `BufferedReader` ohne `java.io`.
226+
* `java.nio.file`, `File`, `PrintWriter`, `FileReader`, `FileWriter`, `BufferedReader` ohne `java.io` und `Arrays` ohne
227+
* `java.util.Arrays`.
227228
*/
228229
checkImports() {
229230
const io = ['File', 'PrintWriter', 'FileReader', 'FileWriter', 'BufferedReader', 'IOException', 'FileNotFoundException'].map((n) => [n, 'java.io'])
230-
for (const [name, pkg] of [['Scanner', 'java.util'], ['Files', 'java.nio.file'], ['Path', 'java.nio.file'], ['Paths', 'java.nio.file'], ...io]) {
231+
for (const [name, pkg] of [['Scanner', 'java.util'], ['Arrays', 'java.util'], ['Files', 'java.nio.file'], ['Path', 'java.nio.file'], ['Paths', 'java.nio.file'], ...io]) {
231232
const known = this.imports.some((i) => i === `import${pkg}.${name};` || i === `import${pkg}.*;`)
232233
// `Files.readString(…)`: nur als Klasse vor einem Punkt, damit eine Variable `path` o. Ä. nicht zählt.
233-
const use = this.toks.find((t, k) => t.kind === 'id' && t.text === name && (pkg !== 'java.nio.file' || this.toks[k + 1]?.text === '.'))
234+
const use = this.toks.find((t, k) => t.kind === 'id' && t.text === name && (name !== 'Arrays' && pkg !== 'java.nio.file' || this.toks[k + 1]?.text === '.'))
234235
if (!known && use) {
235236
this.problem('web_parse_missing_import', use, pkg === 'java.util' ? `import ${pkg}.${name};` : `import ${pkg}.*;`)
236237
return

‎web/src/core/simulation.ts‎

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,38 @@ function elementDefault(type: SimValueType): number | string | boolean {
234234
return type === 'string' ? '' : type === 'boolean' ? false : 0
235235
}
236236

237+
/**
238+
* Was `System.out.println(zahlen)` in Java ausgibt: nicht den Inhalt, sondern Typ und Kennung der Referenz,
239+
* z. B. `[I@1b6d3586`. Den Inhalt zeigt erst `Arrays.toString(zahlen)`. Die Kennung bleibt für dasselbe Array gleich.
240+
*/
241+
const arrayIds = new WeakMap<object, string>()
242+
let nextArrayId = 0
243+
244+
export function javaReference(v: SimValue): string {
245+
const code = (t: SimValueType) => ({ int: 'I', double: 'D', boolean: 'Z', string: 'Ljava.lang.String;' })[t]
246+
const prefix =
247+
v.kind === 'grid' ? '[[' + code(v.elementType)
248+
: v.kind === 'intArray' ? '[I'
249+
: v.kind === 'doubleArray' ? '[D'
250+
: v.kind === 'booleanArray' ? '[Z'
251+
: v.kind === 'stringArray' ? '[Ljava.lang.String;'
252+
: undefined
253+
if (!prefix || !('value' in v)) return describeValue(v)
254+
const key = v.value as object
255+
let id = arrayIds.get(key)
256+
if (!id) {
257+
id = ((Math.imul(++nextArrayId, 0x9e3779b1) >>> 0) & 0x7fffffff).toString(16)
258+
arrayIds.set(key, id)
259+
}
260+
return prefix + '@' + id
261+
}
262+
263+
/** Ein Fach wurde geändert und das Array dabei kopiert: Es ist in Java aber dasselbe Array, also dieselbe Kennung. */
264+
function keepArrayId(from: object, to: object): void {
265+
const id = arrayIds.get(from)
266+
if (id) arrayIds.set(to, id)
267+
}
268+
237269
function isArrayValue(v: SimValue): boolean {
238270
return v.kind === 'intArray' || v.kind === 'doubleArray' || v.kind === 'stringArray' || v.kind === 'booleanArray' || v.kind === 'grid'
239271
}
@@ -1815,7 +1847,10 @@ export class SimulationEngine {
18151847
}
18161848
}
18171849

1818-
/** `Integer.parseInt(…)`, `Double.parseDouble(…)`, `String.valueOf(…)`, `Files.readString(Path.of(…))`. */
1850+
/**
1851+
* `Integer.parseInt(…)`, `Double.parseDouble(…)`, `String.valueOf(…)`, `Files.readString(Path.of(…))`,
1852+
* `Arrays.toString(…)` und `Arrays.deepToString(…)`.
1853+
*/
18191854
private evaluateStaticCall(text: string, call: { target: string; method: string; args: string[] }): SimValue | undefined {
18201855
const one = () => {
18211856
if (call.args.length !== 1) throw invalid(text)
@@ -1841,6 +1876,17 @@ export class SimulationEngine {
18411876
}
18421877
case 'String.valueOf':
18431878
return { kind: 'string', value: this.evaluateOutputExpression(one()) }
1879+
case 'Arrays.toString':
1880+
case 'Arrays.deepToString': {
1881+
const array = this.state.variables.get(one().trim())
1882+
if (!array || !isArrayValue(array)) throw invalid(text)
1883+
// `Arrays.toString` zeigt bei einem 2D-Array nur die Referenzen der Zeilen, `deepToString` alles.
1884+
if (array.kind === 'grid' && call.method === 'toString') {
1885+
const rows = array.value.map((row) => javaReference({ kind: `${array.elementType}Array`, value: row } as SimValue))
1886+
return { kind: 'string', value: '[' + rows.join(', ') + ']' }
1887+
}
1888+
return { kind: 'string', value: describeValue(array) }
1889+
}
18441890
case 'Files.readString': {
18451891
const path = /^(?:Path\s*\.\s*of|Paths\s*\.\s*get)\s*\(\s*("(?:[^"\\]|\\.)*")\s*\)$/u.exec(one().trim())
18461892
if (!path) throw invalid(text)
@@ -1953,6 +1999,7 @@ export class SimulationEngine {
19531999
if (raw === undefined || isArrayValue(value)) throw invalid(trimmed)
19542000
const rows = old.value.map((row) => [...row])
19552001
rows[indices[0]][indices[1]] = raw as number | string | boolean
2002+
keepArrayId(old.value, rows)
19562003
this.state.variables.set(access.arrayName, { ...old, value: rows })
19572004
return
19582005
}
@@ -1972,6 +2019,7 @@ export class SimulationEngine {
19722019
}
19732020
if (index >= (updated.value as unknown[]).length) throw invalid(trimmed)
19742021
;(updated.value as unknown[])[index] = value.value
2022+
keepArrayId(old.value, updated.value)
19752023
this.state.variables.set(access.arrayName, updated)
19762024
}
19772025

@@ -1992,7 +2040,8 @@ export class SimulationEngine {
19922040
else sum = sum.kind === 'int' && value.kind === 'int' ? int(sum.value + value.value) : dbl((sum as { value: number }).value + (value as { value: number }).value)
19932041
continue
19942042
}
1995-
text = (text ?? (sum ? describeValue(sum) : '')) + describeValue(value)
2043+
// Ein ganzes Array gibt Java nur als Referenz aus, z. B. `[I@1b6d3586`.
2044+
text = (text ?? (sum ? describeValue(sum) : '')) + javaReference(value)
19962045
sum = undefined
19972046
}
19982047
return text ?? (sum ? describeValue(sum) : '')

‎web/test/javaParser.test.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@ describe('Java tippen → Struktogramm', () => {
152152
expect(keys('System.out.println("Scanner");')).toEqual([])
153153
})
154154

155+
it('Arrays ohne import ist ein Fehler, wie bei javac', () => {
156+
const keys = (src: string) => parse(src).problems.map((p) => p.key)
157+
expect(keys('int[] a = {1, 2};\nSystem.out.println(Arrays.toString(a));')).toEqual(['web_parse_missing_import'])
158+
expect(keys('import java.util.Arrays;\nint[] a = {1, 2};\nSystem.out.println(Arrays.toString(a));')).toEqual([])
159+
expect(keys('int[] a = {1, 2};\nSystem.out.println(a);')).toEqual([])
160+
})
161+
155162
it('Aufruf eines Unterprogramms aus einem anderen Reiter', () => {
156163
expect(parse('int y = quadrat(4);', new Set(['quadrat'])).elements[0].type).toBe(ElementType.aufruf)
157164
expect(parse('int y = quadrat(4);').elements[0].type).toBe(ElementType.anweisung)

‎web/test/simulation.test.ts‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,42 @@ describe('Simulation', () => {
173173
expect(engine.message).toBe('Simulation finished.')
174174
})
175175

176+
it('gibt ein ganzes Array wie Java als Referenz aus, den Inhalt erst mit Arrays.toString', () => {
177+
const state = runSimulation(
178+
doc([
179+
stmt('int[] zahlenreihe = {1, 2, 3}'),
180+
stmt('String[] namen = {"Ada", "Bob"}'),
181+
stmt('double[][] t = new double[2][2]'),
182+
stmt('output: zahlenreihe'),
183+
stmt('zahlenreihe[0] = 9'),
184+
stmt('output: "Reihe: " + zahlenreihe'),
185+
stmt('output: namen'),
186+
stmt('output: t'),
187+
stmt('output: Arrays.toString(zahlenreihe)'),
188+
stmt('output: Arrays.toString(namen)'),
189+
stmt('output: Arrays.deepToString(t)'),
190+
stmt('output: Arrays.toString(t)'),
191+
]),
192+
)
193+
const [ref, refAgain, names, grid, ...rest] = state.outputLines
194+
expect(ref).toMatch(/^\[I@[0-9a-f]+$/u)
195+
expect(refAgain).toBe('Reihe: ' + ref)
196+
expect(names).toMatch(/^\[Ljava\.lang\.String;@[0-9a-f]+$/u)
197+
expect(grid).toMatch(/^\[\[D@[0-9a-f]+$/u)
198+
expect(rest.slice(0, 3)).toEqual(['[9, 2, 3]', '[Ada, Bob]', '[[0.0, 0.0], [0.0, 0.0]]'])
199+
expect(rest[3]).toMatch(/^\[\[D@[0-9a-f]+, \[D@[0-9a-f]+\]$/u)
200+
// Die Variablentabelle zeigt weiter den Inhalt.
201+
expect(state.variables.get('zahlenreihe')).toEqual({ kind: 'intArray', value: [9, 2, 3] })
202+
})
203+
176204
it('Werte und Arrays', () => {
177205
const state = runSimulation(
178206
doc([
179207
stmt('double d = 7.0 / 2'),
180208
stmt('int[] a = {3, 4, 5}'),
181209
stmt('a[1] = a[0] * 10'),
182210
stmt('String s = "x"'),
183-
stmt('print: "len=" + a.length + " a=" + a + " d=" + d'),
211+
stmt('print: "len=" + a.length + " a=" + Arrays.toString(a) + " d=" + d'),
184212
stmt('d += 1'),
185213
]),
186214
)
Lines changed: 1254 additions & 1248 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)