Fiz dessa forma para você entender mais simples.
Bom, para cada dimensão que eu for criar ele está criando objetos.
Se eu defini Object[][] dog = new Object[1][];
Na verdade criei dois objetos, um para referenciar a variável dog, que liga com um elemento que será uma posição da primeira dimensão. Para o compilador você não definiou a segunda, portanto não criou objeto algum para ela, se tentar acessar dará o NullPointerException.
Se eu fizer:
Object [][] dog = new Object [3][];
dog [0] = new Object[2];
dog [1] = new Object[4];
dog [2] = new Object[1];

Defini na primeira linha a quantidade do meu primeiro array, como são 3 posições e a posição começa pelo zero, então terei 0, 1 e 2 e não 1, 2 e 3 como alguns iniciantes podem pensar.
depois criei na segunda linha em diante objetos para a segunda dimensão, o elemento 0 irá apontar para 0 e 1, o elemento 1 irá apontar para 0, 1, 2 e 3, e o elemento 2 da primeira dimensão irá apontar para o 0.
Veja nesse código o tamanho total:
System.out.println(dog[0].length);
System.out.println(dog[1].length);
System.out.println(dog[2].length);