2017-07-12

Косяки 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…

dbms_stats.import_table_stats

Если процедура импорта статистики dbms_stats.import_table_stats долго думала и в итоге ничего не сделала, возможно следует проапдейтить колонки C1 (название объекта) и C5 (имя схемы) таблицы статистики
Исследования тут

2017-07-10

Философия performance tuning

Из Expert Oracle SQL: Optimization and Deployment
1. Давать стабильное, предсказуемое, гарантируемое время выполнения повторяющихся запросов. Все изменения в оптимизаторе делают его всё более непредсказуемыми. Тут автор не учитывает генерируемые различными тулами запросы, которые так же должны выполняться быстро
2. Парадокс Dave Ensor: Единственный момент когда безопасно собирать статистику – когда это ничего не изменит. У сбора статистики может быть 2 исхода: ничего не поменяется и мы этим ничего не сломаем или что-то поменяется, но мы не знаем в лучшую или худшую сторону. Это unpredictable, см. пункт первый.
3. Причина, по которой мы собираем статистику в PROD – предотвратить изменение планов запросов, а не убедиться, что они поменяются. Пример – time-based таблицы, в которых без сбора статистики предикаты рано или поздно начнут превышать HIGH_VALUE и Oracle будет ожидать возврат 1 строки. Сбор статистики (а также удаление HIGH_VALUE или, всвязи с указанным автором изменением поведения в 12с, установка HIGH_VALUE в очень большое значение) предотвращает изменение планов.
4. Правильная версия парадокса David Ensor: правильное время для сбора статистики, когда это предотвратит изменение планов
5. Подход Wolfgang Breitling: для того, что бы получить оптимальный план, необходимо и достаточно дать CBO правильные cardinality. CBO будет ошибаться, только если кардинальность различается в несколько порядков
6. Bind variables and histograms should be considered mutually exclusive
7. Средства для стабилизации планов: Outline, SQL Profile (почему-то автор считает, что ошибочно), Baseline
8. Две стороны одной монеты: stability и adaptability. Например в baseline stability это сами baseline, adaptability - возможность планов меняться.
9. TSTATS – запретить планам меняться (stability) без использования каких бы то ни было репозиториев хранения, а корректировкой статистики на таблицах. В основе лежит идея Adrian Billington удаления time-based данных из статистики. Остальные идеи
- сбор статистики на специально сэмулированной тестовой среде с последующим переносом на прод. Такие среды следует использовать для тестирования планов запросов
- Полная генерация статистики для темповых таблиц
- залочить статистику на всех системах для всех объектов
10. Управлять статистикой так же, как и исходным кодом: хранить в SVN, разворачивать вместе с приложением
11. Разные техники хорошо применимы для разных случаев: нужно поправить 1 запрос или несколько запросов. К техникам можно отнести: хинтование, переписывание запроса, физические изменения в базе (параметры, индексы и т.д.) корректировка статистики

2017-07-04

Когда CBO берет информацию из data dictionary

Еще интересный факт всё из той же книги:

Normally, the inputs to the CBO’s estimating process are the object
statistics, system statistics, and initialization parameters. One
exception to this rule is the degree of parallelism specified in the
data dictionary for a table or index. The only other case that I know
of where the CBO uses data dictionary information, other than
statistics, is when it costs full table scans for partitions.

CBO считает стоимость FULL скана к партиции как число блоков / число партиций

2017-07-03

Generate create user statement

BEGIN
dbms_METADATA.SET_TRANSFORM_PARAM(DBMS_METADATA.SESSION_TRANSFORM,'SQLTERMINATOR',true);
END;
/

WITH usr AS ( 
  SELECT ',USER1,USER2,' usr FROM dual
  ),
statements(txt, username, ord) AS (  
  SELECT dbms_metadata.get_ddl('USER', username), username, 1 AS ord
  FROM dba_users u, usr WHERE usr LIKE '%,' || username || ',%'
  UNION ALL
  SELECT dbms_metadata.GET_GRANTED_DDL('ROLE_GRANT', username), username, 2 AS ord
  FROM dba_users u, usr WHERE usr LIKE '%,' || username || ',%' 
    AND EXISTS (SELECT NULL FROM dba_role_privs WHERE grantee = username)
  UNION ALL
  SELECT dbms_metadata.GET_GRANTED_DDL('SYSTEM_GRANT', username), username, 3 AS ord
  FROM dba_users u, usr WHERE usr LIKE '%,' || username || ',%'
   AND EXISTS (SELECT NULL FROM dba_sys_privs WHERE grantee = username)
  UNION ALL
  SELECT dbms_metadata.GET_GRANTED_DDL('OBJECT_GRANT', username), username, 4 AS ord
  FROM dba_users u, usr WHERE usr LIKE '%,' || username || ',%'
   AND EXISTS (SELECT NULL FROM dba_tab_privs WHERE grantee = username)
  )
SELECT txt
FROM statements
ORDER BY username, ord
;