среда, 12 июля 2017 г.

Косяки Java

1) switch реализован совершенно непонятно. От использования его без break и размещения default не в конце лучше воздержаться.

switch (1) {
    case 2: System.out.print("y");
    case 3: System.out.print("икраткое");
    default: System.out.print("x");
} //вполне ожидаемый х

switch (1) {
    default: System.out.print("x");
    case 2: System.out.print("y");
    case 3: System.out.print("икраткое");
} //внезапное xyикраткое

switch (1) {
    case 2: System.out.print("y");
    default: System.out.print("x");
    case 3: System.out.print("икраткое");
} // печальное xикраткое

Написать case с переменными невозможно – все варианты должны быть разрешены в момент компиляции.

2) Куча запутанных преобразований типов. Но всех переплюнуло уменьшение разрядности – неявное не работает, а явное наоборот, вместо того, что бы выкинуть ошибку сделает большое отрицательное число

int i = Short.MAX_VALUE - 10;
short s = i; //Ошибка компилятора: incompatible types: possible lossy conversion from int to short
short s = (short) (Short.MAX_VALUE + 1);
System.out.print("s=" + s + "; MAX_VALUE+1=" + (Short.MAX_VALUE+1));//s=-32768; MAX_VALUE+1=32768

3) Еще преобразование типов. Приведенный ниже пример конечно не компилируется

short s1 = 0;
short s2 = 1;
short s3 = s1 + s2;
//Error:(6, 15) java: incompatible types: possible lossy conversion from int to short

потому что внезапно

System.out.println(((Object)(s1+s2)).getClass().getName());
//возвращает java.lang.Integer

4) Присваивание это еще и функция, которая возвращает правую часть. Не ошибись в if, не пропусти второе =

boolean b = false;
if (b = true) System.out.print("На самом деле я уже true, посмотри сам " + b);//На самом деле я уже true, посмотри сам true

5) Сравнение строк и StringBuilder – отдельная боль
вот, например, строки одинаковые, а конструкторы вызваны были разные

String s1 = "Я томат";
String s2 = new String("Я томат");
if (s1 == s2)
    System.out.print("По мнению джавы строки '" + s1 + "' и '" + s2 + "' равны");
else
    System.out.print("По мнению джавы строки '" + s1 + "' и '" + s2 + "' НЕ равны");

Рано печалилсь, для StringBuilder это не работает уже из коробки :)

StringBuilder s1 = new StringBuilder("Я томат");
StringBuilder s2 = new StringBuilder("Я томат");

if (s1 == s2)
    System.out.print("По мнению джавы строки '" + s1 + "' и '" + s2 + "' равны");
else
    System.out.print("По мнению джавы строки '" + s1 + "' и '" + s2 + "' НЕ равны");

А вот ударили монтировкой по голове – шнурки развязались

StringBuilder s1 = new StringBuilder("Я томат");
StringBuilder s2 = s1;
s2.append(" очень рад");

if (s1 == s2)
    System.out.print("По мнению джавы строки '" + s1 + "' и '" + s2 + "' равны");
else
    System.out.print("По мнению джавы строки '" + s1 + "' и '" + s2 + "' НЕ равны");
// Мы случайно поменяли и первую строку: По мнению джавы строки 'Я томат очень рад' и 'Я томат очень рад' равны. 

В общем сравнение со строками не совместимо, надо использовать equals

String s1 = "Я томат";
String s2 = " Я томат".trim();
if (s1 == s2)
    System.out.print("По мнению джавы строки '" + s1 + "' и '" + s2 + "' равны");
else
    System.out.print("По мнению джавы строки '" + s1 + "' и '" + s2 + "' НЕ равны");
//С томатами явная проблема: По мнению джавы строки 'Я томат' и 'Я томат' НЕ равны

if (s1.equals(s2))
    System.out.print("По мнению джавы строки '" + s1 + "' и '" + s2 + "' эквальны");
else
    System.out.print("По мнению джавы строки '" + s1 + "' и '" + s2 + "' НЕ эквальны");
//Хоть с эквальностью проблем нету)): По мнению джавы строки 'Я томат' и 'Я томат' эквальны

Но wtf, если вместо строк случайно оказался StringBuilder

StringBuilder s1 = new StringBuilder("Я томат");
StringBuilder s2 = new StringBuilder("Я томат");

if (s1.equals(s2))
    System.out.print("По мнению джавы строки '" + s1 + "' и '" + s2 + "' эквальны");
else
    System.out.print("По мнению джавы строки '" + s1 + "' и '" + s2 + "' НЕ эквальны");
//По мнению джавы строки 'Я томат' и 'Я томат' НЕ эквальны

оказывается разрабочтики метод не реализовали (facepalm)

6) Унарный инкремент и декремент давно пора бы убрать. Польза только в синтаксисе for-цикла, а непонятности вносит много. Вот, например что выведет?

int i = 0;
while (i <= 3) {
    i = i++;
    System.out.println(i);
}

7) Реализация substring по-моему чудовищная, хотя с этих можно смириться.

8) Инициализация переменных. Она зависит от контекста

public class Main {
float gf;
int gi;
public static void main(String[] xxx) {
    int li;
    float lf;
    Main c = new Main();
    System.out.println("Global float default: " + c.gf + "; Global integer default " + c.gi);
    // Global float default: 0.0; Global integer default 0
    System.out.println("Local float default: " + lf + "; Local integer default " + li);
    // не компилируется, т.к. переменные не инициализированы
}
}

9) Игра Угадай-ка

public class Simple {
    public Simple() {/*Угадай, кто из нас конструктор*/}
    public void Simple() {/*Угадай, кто из нас конструктор*/}
    public static void main(String[] args) {}
}

10) Дурацкие типы для литералов

long x = 123;//Компилируется
System.out.println(Integer.MAX_VALUE);//Выводит 2147483647
long x = 2147483999; //не компилируется Error:(6, 10) java: integer number too large: 2147483999
long x = 2147483999L;//Должно быть так

все потому что литерал по-умолчанию имеет тип int

11) “Чудесно” работающая процедура поиска по несортированным массивам

int numbers[] = new int[] {3,2,1};
System.out.println(Arrays.binarySearch(numbers, 2)); //правильное 1
System.out.println(Arrays.binarySearch(numbers, 3)); //странное -4

12) Protected access – вообще отдельная песня. Кажется, что понять скомпилируется код или нет можно уже только постфактум. Примера не привожу ибо длинно и непонятно.

13) Использование пустых ссылок не всегда приводят к ошибкам:

public class Main {
    static int x;
public static void main(String[] xxx) {
    Main m = new Main();
    System.out.println("Static var = " + m.x);//0, инициализирована которым по-умолчанию
    m = null;
    System.out.println("Static var via null link = " + m.x);//и тут 0
}

14) By reference or by value? Или все таки By reference? Вроде как заявляется, что параметры передаются By Value, т.е. изменение параметра внутри функции не влияет на переменную, которая была между скобками в вызывающем коде. Никаких спец конструкций, вроде NOCOPY или by reference в Java нет.
Вот небольшой примерчик:

static void byValueOrByReference(StringBuilder s1, String s2, String s3, String[] many_s) {// а попробуй угадай, что тут по ссылке, а что по значению :)?
    s1.append("но");
    s2 = s1 + "но";
    s3 = s2 + "но";
    for(String s: many_s) {
        s = s + "нейшество";
    }
}

public static void main(String[] xxx) {
    StringBuilder s1 = new StringBuilder("Гов");
    String s2 = "гов";
    String s3 = new String("New гов");
    String[] sarr = {"гов", "нище"};

    byValueOrByReference(s1, s2, s3, sarr);

    System.out.println(s1);//Говно
    System.out.println(s2);//гов
    System.out.println(s3);//New гов -- не смотря на то, что они обещали обычный объект
    System.out.println(sarr[0] + "-" + sarr[1]);//гов-нище
}

Допустим объяснение, что примитивы должны вести себя как-будто их передают по значению, а объекты по ссылке. Но что же со строками, которые объявлены с new и просто? Они по разному вели себя в сравнении, а тут опять выдают себя за примитивы? Непоследовательно как-то…

15) Цепочки периодов работаю немного по разному

public static void main(String[] xxx) {
    LocalDate d = LocalDate.of(2017, 1, 1);

    Period p1 = Period.of(0,0,0);
    p1 = p1.plusYears(1).plusMonths(1).plusDays(1);

    Period p2 = Period.ofYears(1).ofMonths(1).ofDays(1);

    System.out.println(d.plus(p1));//2018-02-02 - как и просили добавился день, месяц и год
    System.out.println(d.plus(p2));//2017-01-02 полнейшая печаль
}

В первом случае создаем пусто период и пользуемся функциями plus* - все работает как часы. А вот во втором, более коротком варианте добавляется только последний вызов. Лучше бы исключение кидали.

16) C StringBuilder.substring нельзя делать цепочки вызовов

StringBuilder s = new StringBuilder("опаньки");
System.out.println(s.insert(0,"ж").substring(0,2).append("пища"));// не компилируется
System.out.println(s.insert(0,"ж").append("пища"));// компилируется

17) Волшебная перезагрузка статических методов, вызов которых работает совсем не так, как в перегружегнных обычных.
В примере ниже: из дочернего объекта вызываем процедуру печати. В первом случае перегруженную, во втором родную.
Процедура печати почти одинаковая – дергает статический и нестатический метод из ?своего? класса. Смотрим на результаты

class Parent {
    static String stat() {return "СТАТИЧЕСКИЙ Parent";}
    String nonStat(){return "Parent";}
    void printInherited(){
        System.out.println("Вызов статической процедуры из Parent вызвал: " + stat());
        System.out.println("Вызов НЕстатической процедуры из Parent вызвал: " + nonStat());
        }
        }
public class Main extends Parent{
    static String stat() {return "СТАТИЧЕСКИЙ Child";}
    String nonStat(){return "Child";}
    void printLocal(){
        System.out.println("Вызов статической процедуры из Child вызвал: " + stat());
        System.out.println("Вызов нестатической процедуры из Child вызвал: " + nonStat());
    }
    public static void main(String... args) {
        Main m = new Main();
        System.out.println("Вызываем процедуру печати из родительского класса. Она наследовалась и не перегружалась");
        m.printInherited();
        System.out.println("Вызываем процедуру печати из дочернего класса");
        m.printLocal();
    }
}
Вызываем процедуру печати из родительского класса. Она наследовалась и не перегружалась
Вызов статической процедуры из Parent вызвал: СТАТИЧЕСКИЙ Parent
Вызов нестатической процедуры из Parent вызвал: Child

Вызываем процедуру печати из дочернего класса
Вызов статической процедуры из Child вызвал: СТАТИЧЕСКИЙ Child
Вызов нестатической процедуры из Child вызвал: Child

Нижняя часть с вызовом для дочернего класса прошла без неожиданностей – обе процедуры дернулись из самого дочернего класса. В верхней части волшебство – поведение то разное. Статический метод вызвался из одного места, а обычный из другого…

18) Немного об абстракных классах и станном компиляторе. Сначала о странном компиляторе: недостижимый код в if(false) и while(false) по-разному недостижимый

Integer i = 1;
Short x = 1;
if(b == x) {System.out.println("Equal");} //Не компилируется, разные типы же
while(false) {System.out.println("False");} //Не компилируется, недостижимый код
if(false){System.out.println("False");} //Компилируется ))

Компилятор на столько умен, что даже запрещает сравнивать переменные разных типов

А что-же он будет делать, когда мы пытаемся объявить и использовать переменную абстрактного класса? (вопрос подчерпнут из тестов для подготовки к экзаменам)

public abstract class Main{
    abstract void calculate();
    public static void main(String[] args) {
        System.out.println("calculating");
        Main x = null;
        x.calculate();
    }
}

Думаете компилятор ругается на создание переменной абстрактного класса (это наверное слишком полезная функциональность)? Хрен, результат прогона такой

calculating
Exception in thread "main" java.lang.NullPointerException
    at Main.main(Main.java:10)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

19) Добавим немного полиморфизма :). Вот переменная и метод, возвращающий эту переменную:

public abstract class Main{
    public static void main(String[] args) {
        A o = new B( );
        System.out.println(o.m1( ) );//20 -- метод вызывается из класса справа от равно и возвращает переменную из класса справа от равно
        System.out.println(o.i ); //10 -- переменная возвращается из класса слева от равно
    }
}

class A { int i = 10;  int m1( ) { return i; } }
class B extends A { int i = 20;  int m1() { return i; } }

20) null…

char[] myCharArr = {'x', 'y'};

 String newStr = null;
 for(char ch : myCharArr){
     newStr = newStr + ch;
     System.out.println(newStr);
 }
Выводит
nullx
nullxy

StringBuilder sb = new StringBuilder("12345");
CharSequence cs = null;
System.out.println(sb.append(cs));//пишет 12345null

no comments

21) Инициализация примитивов и типов-оберток. Говорить о последовательности разработчиков вообще не приходится - тут работает, тут перестало, а тут мы рыбу заворачивали. Ну а инициализация типов-оберток - вообще страшно написана.

//Почти правильная и естественная инициализация. Целочисленные типы без точки, с плавающей точкой через 1.0
double d = 1.0;
float f = 1.0; // из этого блока не работает только это волшебным образом
float f11 = 1.0f; // вот так работает
long l = 1;
int i = 1;
short s = 1;
byte b = 1;
char c = 1;

//неправильная инициализация
double d2 = 1; //это работает
int i2 = 1.0; // а это уже нет

//Инициализация объектов. Конструктор? Какой конструктор?
//И перестал работать Long внезапно
Double d3 = 1.0;
Float f3 = 1.0; //не осилил преобразовать константу
Float f31 = 1.0f;
Long l3 = 1; // Полнейшее рукалицо -- не преобразовал целочисленный в целочисленный
Long l31 = 1L; // Так надо
Integer i3 = 1;
Short s3 = 1;
Byte b3 = 1;
Character c3 = 1;

//неправильная инициализация -- сломалось уже всё
Double d4 = 1; //не работает
Integer i4 = 1.0; //не работает

//Инициализация враперов через конструкторы
//Как всегда непоследовательно -- на этот раз float работает, а вот типы ниже int нет
Double d5 = new Double(1.0);
Float f5 = new Float(1.0);//а про этот в этот раз не забыли -- он работает
Float f51 = new Float(1.0f);
Long l5 = new Long(1);
Integer i5 = new Integer(1);
Short s5 = new Short(1);//забыли написать конструкторы
Short s51 = new Short((short) 1);//надо так
Byte b5 = new Byte(1);//забыли написать конструкторы
Byte b51 = new Byte((byte) 1);//надо так
Character c5 = new Character(1);//забыли написать конструкторы
Character c51 = new Character((char) 1);//надо так
Character c52 = new Character('x');//или так      

To be continued…

Комментариев нет: