Násedují úkoly ze cvičení (2019/2020), kde to bylo možné tak se správnou odpovědí a vysvětlením. Důležité příklady, které bývají na zkouškách, jsou označeny jako zkouškový příklad.
Co se vypíše?
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
if (i1 == i2)
System.out.println("ANO");
else
System.out.println("NE");Řešení: NE
== porovnává pro referenční typy (což jsou všechny objekty, i Integer) pouze ukazatele do paměti, ne hodnoty. i1 a i2 jsou v tomto případě různé objekty s různými pozicemi v paměti.
Co se vypíše?
public class Overflow {
public static void main(String[] argv) {
int b = 2147483647;
System.out.println(b);
b = b + 1;
System.out.println(b);
}
}Řešení: 2147483647 -2147483648
2147483647 je Integer.MAX_VALUE, když k ní přičteme jedničku, dojde k overflow (jede se opět "od začátku" intu).
Co se vypíše?
class URL {
public static void main(String[] argv) {
System.out.println("url:");
http://google.com/
System.out.println(":url");
}
}Řešení: url: :url
V http://google.com/ se vše po // bere jako komentář a http: se bere jako label. Labely fungují podobně jako v Pascalu, lze na ně skočit pomocí continue.
Co se vypíše?
public class Swap {
public static void main(String[] argv) {
int x = 10;
int y = 20;
x ^= y ^= x ^= y;
System.out.println(x);
System.out.println(y);
}
}Řešení: 0 10
a ^= b odpovídá a = a ^ b. a ^ b je operace bitwise XOR, při níž se a a b berou jako bity (čili pokud to jsou integery, berou se převedené do binární soustavy). Kratší z nich se zepředu doplní nulami, aby byla obě čísla stejně dlouhá. Poté se porovná i-tý bit z a s i-tým bitem z b pomocí operace XOR.
x ^= y ^= x ^= y lze pak přepsat jako x = x ^ (y = y ^ (x = x ^ y)), což je výraz, který se vyhodnocuje zleva. Po dosazení vyjde x = 10 ^ (y = 20 ^ (x = 10 ^ y)) atd.
Co se vypíše?
class ForCycle {
public static void main(String[] argv) {
int j = 0;
for (int i = Integer.MAX_VALUE - 10; i <= Integer.MAX_VALUE; i++) {
j++;
}
System.out.println(j);
}
}Řešení: Nekonečný cyklus (nevypíše se nic)
int i nikdy nebude větší než Integer.MAX_VALUE, protože je to jeho horní hranice.
Doplňte deklaraci i, aby se vypsalo ANO
if (i == -i && i != 0) {
System.out.println(“ANO“);
} else {
System.out.println(“NE“);
}Řešení: i = Integer.MIN_VALUE
Když ho znegujeme, dostaneme Integer.MAX_VALUE + 1, což je zase Integer.MIN_VALUE.
Co se vypíše?
public class LoopTest {
public static void main(String[] argv) {
int START = 2000000000;
int count = 0;
for (float f = START; f < START + 50; f++) {
count++;
}
System.out.println(count);
}
}Řešení: 0
Má to co dočinění s floating-point čísly. START je sice na začátku int, kvůli porovnání s f je však převeden na float. START jen o málo menší než float čísla jsou sice 32 bitová, ale 1 bit z toho je na znaménko, dalších 8 je na exponent a tak na samotné číslo zbude pouze 23 bitů.
Protože k uložení start potřebujeme mít 31. bit nastavený na 1, při převodu na float si ze START necháme následujících 23 bitů: [31., 30., 29., ..., 8.] — jinými slovy prvních sedm bitů ze START odstřihneme, neboť se nám do floatu nevlezou. Protože přičtená 50 se vleze do prvních sedmi bitů, a my těchto sedm bitů poté odstřihneme, přičtení 50 se vůbec neprojeví a platí START == f == START + 50 (pokud je START převeden na float).
Co se vypíše?
public class Test01 {
public static void main(String[] argv) {
System.out.println(test());
}
public static boolean test() {
try {
return true;
} finally {
return false;
}
}
}Řešení: false
finally přebíjí většinu věcí, i return.
Co se vypíše?
public class Test01 {
public static void main(String[] argv) {
try {
System.out.println("Hello world!");
System.exit(0);
} finally {
System.out.println("Goodbye");
}
}
}Řešení: "Hello world!"
finally přebíjí většinu věcí, ale System.exit() je jedna z výjimek — prostě rovnou ukončí průběh programu. Kromě něj takto funguje i halt a pak už většinou jen pády JVM nebo samotného operačního systému.
Co se vypíše?
class ParamsTest {
public ParamsTest(Object o) {
System.out.println("ParamsTest(Object o)");
}
public ParamsTest(long[] a) {
System.out.println("ParamsTest(long[] a)");
}
public static void main(String[] argv) {
new ParamsTest(null);
}
}Řešení: ParamsTest(long[] a)
Podle skutečných parametrů se vybere ta nejspecifičtější vyhovující implementace (zde long[] a).
Co se vypíše? (zkouškový příklad)
class A {
int x = 1;
}
class B extends A {
int x = 2;
public void foo() {
System.out.println(this.x);
System.out.println(super.x);
System.out.println(((A)this).x);
}
public static void main(String[] args) {
B b = new B();
b.foo();
}
}Řešení: 2 1 1
Statické metody se koukají pouze na typ objektu, ne na jeho hodnotu; podle toho se pak zavolá konkrétní implementace. Viz také tato otázka na Stack Overflow.
Co program udělá? (zkouškový příklad)
public class Null {
public static void main(String[] argv) {
((Null) null).hello();
}
public static void hello() {
System.out.println("Hello world!");
}
}Řešení: vypíše se "Hello world!"
Že se třída jmenuje Null je v pořádku. Poté sice voláme null.hello(), ale hello() je statická metoda, čili vůbec nekouká na reálnou hodnotu objektu — pouze na jeho typ (a to je Null). Vše tedy funguje, jak má.
Co program udělá?
class Test01 {
public static void main(String[] argv) {
run();
System.out.println("Konec");
}
public static void run() {
try {
run();
} finally {
run();
}
}
}Řešení: Myslím, že vyhodí StackOverflowError.
Co program vypíše?
class A {
public String className = "A";
}
class B extends A {
private String className = "B";
}
public class Test02 {
public static void main(String[] argv) {
System.out.println(new B().className);
}
}Řešení: Nepřeloží se, ve třídě Test02 je chyba.
Deklarace className v B schová atribut className z A (doslova se to jmenuje field hiding). To, že je sama private na věci nic nemění. Protože původní className z A je schovaná a className z B je zase private, je chyba vůbec se snažit tohoto atributu dosáhnout.
Co se vypíše?
public interface Test {
public static void main(String[] argv) {
System.out.println("Hello");
}
}Řešení: Je to úplně v pořádku, vypíše se Hello.
V interfacu mohou totiž být i statické metody.
Co se vypíše? (zkouškový příklad)
public enum Test {
RED, GREEN, BLUE;
public static void main(String[] argv) {
System.out.println("Hello");
}
}Řešení: Je to úplně v pořádku, vypíše se Hello.
enum je v podstatě jen vylepšená třída, proto může obsahovat statické metody. Nemůže však dědit, neboť implicitně již dědí od java.lang.Enum. Může dokonce obsahovat i abstraktní metody, pak je ale musí implementovat každý z prvků enumu.
Co se vypíše?
public class Greeter {
public static void main (String[] args) {
String greeting = "Hello world";
for (int i = 0; i < greeting.length(); i++) {
System.out.write(greeting.charAt(i));
}
}
}Řešení: Nevypíše se nic.
PrintStream.write() sice na stream přidá dané znaky, ale stream pak automaticky nevyprázdní do konzole — proto nejde nic vidět. Naproti tomu println() se o toto stará, čili kdybychom za loop přidali System.out.println("");, vypsalo by se už Hello world.
Co se vypíše?
class Slasher {
public static void main(String[] argv) {
String fullClassName = "cz.cuni.mff.java.io.Slasher";
String fileName = fullClassName.replaceAll(".", "/") + ".java";
System.out.println("Trida " + fullClassName + " musi byt v souboru " + fileName);
}
}Řešení: Trida cz.cuni.mff.java.io.Slasher musi byt v souboru ///////////////////////////.java
String.replaceAll() bere jako první argument regex, ne obyčejný string. Proto . je interpretováno regex enginem jako "jakýkoli znak" a všechny znaky jsou proto nahrazeny lomítkem.
Co program udělá?
public class TestString {
public static void main(String[] args) {
String s = new String("Hello world");
System.out.println(s);
}
}
class String {
private final java.lang.String s;
public String(java.lang.String s) {
this.s = s;
}
public java.lang.String toString() {
return s;
}
}Řešení: Přeloží se, ale při runtimu vyhodí chybu, že mu chybí metoda main.
main má totiž v této chvíli mít hlavičku public static void main(java.lang.String[] args), protože String je teď ta naše nová třída a ne původní String.
Lze třídu B nadeklarovat tak, aby program vypsal false? (bez přepsání equals)
public class A {
public static void main(String[] args) {
B b = new B();
System.out.println(b.equals(b));
}
}Řešení: Ano, ale je to dost podvod.
class B {
public B() {
System.out.println(false);
System.exit(0);
}
}Co se vypíše?
public class Test01 {
public static synchronized void main(String[] a) {
Thread t = new Thread() {
public void run() {
pong();
}
};
t.start();
System.out.println("Ping");
}
static synchronized void pong() {
System.out.println("Pong");
}
}Řešení: Ping Pong
Obě metody jsou static synchronized, čili berou zámek přímo od třídy Test01. Když už jsme v main, má současné vlákno zámek (protože vstoupilo do synchronized bloku) a tak se vlákno t (potažmo metoda pong()) spustí až poté, co skončí to naše vlákno.
Co se vypíše?
class SelfInterruption {
public static void main(String[] args) {
Thread.currentThread().interrupt();
if (Thread.interrupted()) {
System.out.println("Interrupted: " + Thread.interrupted());
} else {
System.out.println("Not interrupted: " + Thread.interrupted());
}
}
}Řešení: Interrupted: false
Thread.interrupted() totiž nejen vrátí současnou hodnotu .interrupted(), ale také ji resetuje na false. Kdyby se místo toho použilo this.isInterrupted(), který po vrácení žádný reset neprovádí, vypsalo by se Interrupted: true.
Co se vypíše?
public class Test01 {
private static java.util.Random rnd = new java.util.Random();
public static void main(String[] args) {
StringBuffer word = null;
switch (rnd.nextInt(2)) {
case 1:
word = new StringBuffer('P');
case 2:
word = new StringBuffer('G');
default:
word = new StringBuffer('M');
}
word.append('a');
word.append('i');
word.append('n');
System.out.println(word);
}
}Řešení: ain
V case nejsou breaky, takže word je vždy nakonec nastaven na new StringBuffer('M'). Protože StringBuffer dostal char (potažmo int) bere to jako hranici kapacity (ne jako první písmeno — to by musel dostat "M").
Co se vypíše?
public class Test02 {
public static void main(String args[]) {
System.out.println("H" + "a");
System.out.println('H' + 'a');
}
}Řešení: Ha 169
char + char je v tomto případě interpretováno jako součet intů. Pro zajímavost, println("" + 'H' + 'a') by vypsalo skutečně Ha [laughs in javascript].
Lze definovat i tak, aby byl cyklus nekonečný?
while (i != i) { }Řešení: Ano, Double.NaN se nerovná ničemu, ani sám sobě.
Co se vypíše?
public class Increment {
public static void main(String[] args) {
int j = 0;
for (int i = 0; i < 100; i++) {
j = j++;
}
System.out.println(j);
}
}Řešení: 0
(Myslím, že) j++ vrátí 0 a nastaví j na 1; j = j++ pak vezme 0 z j++ a uloží jí do j.
Lze od této třídy (bez použití reflection API) vytvořit další instance (kromě instance v atributu INSTANCE)?
public class Dog implements Serializable {
public static final Dog INSTANCE = new Dog();
private Dog() { }
public String toString() {
return "Woof";
}
}Řešení: Leda bychom přidali něco do třídy samotné (jinak máme příklad tzv. singleton pattern, což je třída pouze s jednou instancí, zde konkrétně INSTANCE).
public class Dog implements Serializable {
public static final Dog INSTANCE = new Dog();
private Dog() { }
public String toString() {
return "Woof";
}
public Dog getDog() {
return new Dog();
}
}
Dog d = Dog.getDog();V tomto případě naopak používám tzv. factory pattern (máme metody, které nejsou konstruktory, ale přesto vyrábějí a vrací objekt své třídy).
Lze napsat deklaraci proměnné i tak, aby následující cyklus byl nekonečný?
while (i != i + 0) { }Řešení: Ano, např. String i = "a", potom i + 0 == "a0".
Lze napsat deklaraci proměnných i a j tak, aby následující cyklus byl nekonečný?
while (i <= j && j <= i && i != j) { }Řešení: Ano, například i = new Integer(1) a j = new Integer(1).
Protože se jedná o referenční typy (objekty), výraz i != j je pravda, protože porovnává umístění v paměti a ne samotné hodnoty i a j.