-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathanotacoes.txt
More file actions
1147 lines (610 loc) · 73.9 KB
/
anotacoes.txt
File metadata and controls
1147 lines (610 loc) · 73.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
A orientação a objetos tem essa ideia de que dados e comportamentos (funcionalidades) devem estar centralizados, ou seja, se precisar de algum comportamento novo, basta mudar em um lugar e em todos códigos que usam esse comportamento já estarão atualizados.
============REFERÊNCIAS============
Quando criamos uma variável que recebe uma nova instância de uma classe, ela nunca será realmente esse objeto criado, mas sim uma referência na memória para aquele objeto.
============NullPointerException============
Essa exception acontece quando tentamos acessar algum atributo de um objeto que ainda não foi iniciado. Ela acontece pois quando um objeto não é iniciado, ele recebe o valor null como padrão.
============ENCAPSULAMENTO============
Encapsulamento é manter o que não precisa ser acessado fora do acesso.
Por exemplo, nunca é bom deixar possível fazer a alteração e a visualização de atributos de um objeto por meio de atribuição, o correto é que essas ações sejam feitas por meio de métodos com as regras que criamos, para evitar falhas e etc.
Quando definimos atributos privados e métodos para a manipulação desses atributos estamos aplicando o encapsulamento.
============GETTERS / SETTERS============
Nem sempre é bom termos getters e setters na nossa classe como único meio de visualização e modificação de atributos. Quando fazemos sempre dessa forma, corremos o risco de criarmos classes fantoches/Value Objects, que são classes que apenas guardam dados e não possuem os comportamentos/lógicas na classe em si. Em alguns casos usamos muito essas classes fantoches, mas na maioria das vezes não é uma boa ideia, pois podemos acabar tendo um Modelo Anêmico sem querer, ou seja, uma classe que deveria ter os comportamentos definidos nela mesma, mas tem apenas os getters e setters como forma de manipulação dos atributos, sendo necessário fazer a lógica necessária fora da classe.
============CONSTRUTORES============
Podemos ter vários construtores que dependem dos parâmetros que forem passados. O java cria um construtor que não recebe nenhum argumento por padrão quando não criamos nenhum construtor, mas quando criamos algum, ele passa a ser o único disponível, a não ser que criemos mais de um, isso nos permite criar regras para a instanciação da nossa classe.
============HERANÇA============
Herança nada mais é do que uma classe que herda todas as características de uma outra classe, e a sintaxe que utilizamos para fazer essa herança é escrever "extends <nome da classe a ser herdada>" depois da declaração da nova classe que estamos criando. Essa classe que está herdando de outra classe poderá ter atributos, métodos e lógicas diferentes da classe que ela herda, mas também terá tudo o que a classe pai tem. Podemos sobrescrever métodos para mudar eles, mas as vezes é necessário tornarmos um atributo "protected" em vez de privado.
============protected============
É o meio termo entre "public" e "private". Ele deixa o atributo acessível apenas para os filhos da classe pai, ou seja, podemos acessar o atributo diretamente sem um getter ou setter na classe que está herdando. Isso é muito útil quando temos algum método que irá aplicar uma lógica que necessita fazer ações com o atributo herdado da classe pai. Normalmente quando vamos acessar um atributo da classe pai, usamos a palavra chave super antes de passarmos o caminho para o atributo.
============super============
super é parecido com o this, mas em vez de representar a instância da classe que estamos criando, representa a classe pai que a classe filha herdou.
Essa palavra chave funciona tanto com atributos quanto com métodos, inclusive, podemos acessar um método da classe pai que foi modificado na classe filha só que antes de ocorrer essa modificação.
============SOBRECARGA/SOBRESCRITA============
Sobrecarga é quando temos duas implementações do mesmo método que variam a quantidade ou os tipos de parâmetros que recebem. O que acontece é que dependendo do que passarmos para o método, ele irá se comportar de uma forma diferente, conforme as implementações que fizemos.
Sobrescrita é quando passamos o mesmo método de forma idêntica mas mudando a lógica que está dentro das chaves. Isso normalmente é usado quando estamos tratando de herança e queremos mudar o comportamento de um método declarado na classe mãe quando acessado a partir da classe filha.
============POLIMORFISMO============
Podemos atribuir uma referência de uma nova instância de uma classe filha para uma variável com o tipo da classe mãe. Isso não funciona quando fazemos o procedimento inverso.
Isso ocorre porque toda clase filha também é a classe mãe, mas a classe mãe não é toda classe filha.
Para isso ficar mais claro, basta imaginar que sempre podemos faer esse processo para qualquer classe acima da que estamos instanciando.
Isso é polimorfismo.
Vantagem do polimorfismo:
Imagine que tem uma porta que controla a entrada de funcionários em uma sala de uma empresa para falar com um contador que irá peencher a bonificação de cada funcionário em uma planilha. Nessa porta irá entrar diversos funcionários com cargos diferentes, ou seja, com regras de bonificação diferentes. O que irá diferenciar esses funcionários na hora de preencher a bonificação na planilha é a informação que eles passam para o contador, pois não existe uma porta para cada cargo de funcionário.
Levando em consideração a premissa a cima, o mesmo vale se fôssemos aplicar essa regra em código usando o polimorfismo. O nosso método que seria o contador receberia como parâmetro apenas o tipo "Funcionario" para não ficarmos repetindo código para cada tipo que herda de funcionário. Assim o polimorfismo seria aplicado e qualquer instância que passassemos receberia o tipo "Funcionario", mas tem um detalhe muito importante, os métodos de bonificação continuariam com as alterações feitas na classe filha, pois houve uma sobrescrita e o Java saberá o método certo a ser chamado, mesmo que o tipo tenha mudado para a superclass.
Se esse método não existir também na superclass talvez não funcione, tenho que fazer uns testes.
============CONSTRUTOR============
Quando estamos criando uma nova classe que herda de outra classe e precisamos de um construtor para instanciar o nosso objeto, o java sempre vai executar um construtor sem argumentos na classe mãe utilizando o super();. Esse construtor pode não existir se escrevermos outro construtor que recebe argumentos e não criarmos explicitamente o padrão.
Para evitar o erro que isso causa, basta chamarmos o super(<...args>); que ele executará o construtor certo da classe super.
============CLASSES ABSTRATAS============
Quando se trata de herança, é comum que uma classe mãe como "Funcionario", por exemplo, não seja instanciada nenhuma vez e sirva apenas de base para as classes filhas. Esse caso é um exemplo de classe abstrata, mas ainda é possível instanciarmos um objeto da classe "Funcionario".
Para que o Java entenda que a classe abstrata em questão é realmente abstrata, utilizamos a palavra chave "abstract" antes da palavra chave class. Dessa forma não é mais possível criar novos objetos da classe em questão.
============MÉTODOS ABSTRATATOS============
Métodos abstratos são métodos que não possuem um corpo, ou seja, eles estão declarado e podem ser usados quando o polimorfismo é aplicado, mas eles realmente precisam ser sobrescritos nas classes filhas, pois não fazem nada originalmente.
============CLASSES INTERMEDIÁRIAS============
Podemos ter uma classe, seja abstrata ou não, que herda de outra classe e que, por sua vez, vai ser herdada por outra classe.
Isso pode ser muito útil quando teremos algumas classes que herdam de uma classe mãe, mas necessitam de mais coisas em comum que a classe mãe não tem.
É importante lembrar que todas classes que herdarem da classe intermediária também herdarão da classe mãe das classes intermediária, ou seja, terão todas as propriedades e métodos que ela.
============HERANÇA MÚLTIPLA============
Herança múltipla é quando uma classe estende mais de uma classe.
Isso é propositalmente proibido no Java, pois pode causar uma bagunça, mas existem linguagens que aceitam isso como Python e C++.
============INTERFACE============
Podemos pensar em interfaces como classes abstratas com apenas métodos abstratos, ou seja, sem nada concreto dentro (atributos são concretos).
Uma interface é como um contrato, isto é, se uma classe *implements* essa interface, ela deve obrigatoriamente sobrescrever todos os métodos declarados nessa interface, assim fazendo com que a classe possua todos os métodos necessários sem que necessite da herança deles pela classe mãe.
Vale a pena lembrar:
Interfaces também são um tipo válido para referências, assim conseguimos atribuir um objeto de qualquer tipo que implemente essa interface para essa referência.
Qualquer classe pode assinar esse contrato e, inclusive, pode assinar mais de um contrato.
Sempre que implementamos uma interface, teremos que implementar os métodos declarados na interface na nossa classe, o que gera bastante repetição de código. Para combater isso, podemos criar uma classe que terá os mesmos métodos que a interface, mas não implementará ela, e nas classes que implementamos a interface apenas criamos um atributo com uma instância dessa nova classe com a lógica dos métodos e no @Override dos métodos da interface passamos apenas a chamada para os métodos dessa instância que criamos. Isso se chama composição.
Para manter isso em mente, basta lembrarmos que se tivermos uma lógica que se repete, o melhor é que coloquemos ela emum lugar só, isso citado acima é meio que como uma gambiarra inteligente para fazer isso.
Utilizamos interfaces para podermos utilizar o polimorfismo sem a herança (não é o único caso de uso, mas podemos pensar assim).
============COMPOSIÇÃO============
Composição é quando uma classe depende de uma outra classe para alguma lógica ou algo do tipo. Isso ocorre quando temos uma classe que recebe uma instância de uma outra classe para fazer o uso de alguns métodos que tem alguma lógica que seria repetida se não fizessemos isso.
============HERANÇA || INTERFACES || COMPOSIÇÃO============
Herança:
- Reutilização de código
- Polimorfismo
Interfaces:
- Polimorfismo
Composição:
- Reutilização de código
============ENUM============
Enums são classes que não podem ser instanciadas e com o construtor privado.
A grande diferença delas é que todas as intâncias que poderão existir dessa classe são instanciadas dentro da própria classe, podendo acessar o construtor, atributos, métodos e etc.
A vantagem de ter algo assim é que como temos apenas essas instâncias da classe, os valores que essas instâncias vão ter serão constantes, ou seja, nunca vão mudar, e isso é muito valioso as vezes.
Um exemplo de uso inteligente dos enums é o da classe Thread que tem um método que define a prioridade de execução das threads. Esse método aceita números inteiros para definir essa prioridade, mas aceita literalmente qualquer número inteiro, o que muitas vezes deixa a legibilidade muito ruim. Para isso, a classe Thread utiliza os enums que tem valores inteiros fixos que representam o nível de prioridade sem que nós precisemos saber o valor inteiro em si, mas sabemos qual é a prioridade pelo nome.
Como todo enum extende a classe Enum, eles sempre têm vários atributos e métodos que dão acesso a diversas informações.
============ANNOTATION============
Annotations são anotações feitas com um "@" antes e que definem uma série de regras no nosso projeto.
As annotations vieram para diminuir o uso excessivo e maçante dos arquivos .xml, que eram os responsáveis por fazer a grande maioria das configurações a um tempo atrás.
Com as annotations, conseguimos definir regras de um jeito muito mais fácil e rápido.
Normalmente, outra biblioteca lê a annotation que colocamos no nosso código e assim entende o que deve fazer a partir disso.
As annotations mais comuns são aquelas que são lidas (a partir da API Reflection) pela JVM quando ela roda nosso código, mas também existem annotations que são lidas pelo próprio compilador, como a @Override, por exemplo.
Podemos criar novas anotações, mas isso só faz sentido quando queremos criar ou modificar alguma anotação para alguma biblioteca que vai lê-la, ou quando estamos criando alguma biblioteca nova que vai precisar ler algumas configurações do código de quem for utilizá-la.
Quando criamos uma nova annotation, utilizamos o "@interface".
Assim como a interface, ela não pode ter nenhuma implementação de nada.
Ela precisa ter, pelo menos, duas anotações. Isso ocorre porque é necessário informar onde a annotation funciona, seja método, atributo, etc. e se ela deve funcionar para o compilador ou em runtime.
============STACK============
Stack é a pilha de execução. Sempre que executa algo novo entra na pilha e quando termina de ser executado sai da pilha. Sempre volta pro main no final, até o main sair da pilha.
============EXCEÇÕES============
Quando acontece uma exceção no nosso stack, essa exceção vai voltando para tudo que tinha no stack e lançando ela até finalizar tudo e chegar no console. Para driblar isso, podemos tratar as exceções em algum ponto da stack em que o erro vai cair.
Para fazer esse tratamento, podemos usar o:
try {
<código que pode dar erro>
} catch(<tipo da exceção> ex) {
<ação para lidar com o erro>
}
Podemos fazer isso no próprio código que pode dar o erro ou em qualquer lugar da stack que o erro possa cair.
A exceção que o catch captura é uma referência, podemos usar ela e os métodos dela para mostrarmos o stack e a mensagem de erro no console, por exemplo.
Podemos ter vários "catch" encadeados para lidarmos com vários tipos de exceções, mas também podemos usar o "|", para lidarmos com mais de um tipo de exceção, o que ficaria mais ou menos assim:
catch(<tipo 1> | <tipo 2> exception) {
<código para lidar com a exceção>
}
Os tipos de exceções são classes que podem ser instanciadas, ou seja, podemos criar o nosso próprio objeto de erro, mas isso não lançará o erro no nosso stack. Para lançarmos esse erro, temos que usar a palavra chave throw e passando a referência criada na frente do throw.
O throw só funciona com exceções.
============HEAP============
HEAP é o nome dado para o lugar onde os objetos criados ficam na memória.
============HIERARQUIA EXCEÇÕES============
As exceções nativas do java que aparecem para a gente herdam da classe RuntimeException, que herda da classe Exception, que herda da classe Throwable. Essa classe Throwable é a classe mãe das exceptions que tem os métodos e etc que todas exceptions herdam.
Podemos criar a nossa própria exceção, para isso basta criarmos uma classe que herda de RuntimeException e criarmos alguns construtores.
============EXCEPTION x ERROR============
No Java existe um tipo Error, que é semelhante a Exception, mas é focado em erros que serão disparados pela JVM, ou seja, erros de falta de memória, alguma falta de recurso, etc. Error normalmente é utilizado apenas internamente na JVM, por isso nós não o utilizamos.
A classe Error também herda de Throwable.
============CHECKED x UNCHECKED============
Por que devemos herdar da RuntimeException?
A RuntimeException é uma classe de exceções unchecked, ou seja, quando escrevemos um throw para uma exceção que herda dela, o compilador não reclama de nenhuma forma e conseguimos compilar o nosso código corretamente sem nenhum problema.
Agora, quando temos uma exceção que herda diretamente da classe Exception, o compilador irá reclamar quando tentarmos dar um throw nessa exceção. Isso ocorre porque essa exceção é checked, pois ela herda diretamente de exception e no Java existe essa regra de que as exceções podem ser checked e unchecked.
Para conseguirmos compilar um código que da um throw em uma exceção checked, precisamos informar diretamente no método que vai gerar essa exceção que ele pode gerar essa exceção. Fazemos isso com a sintaxe:
public String <meu método>() throws <exceção checked> {}
Dessa forma o código compilaria corretamente, mesmo com a exceção sendo checked.
Se fazemos um try / catch no método, não precisamos especificar que ele throws alguma exceção.
O checked serve para deixar explícito que aquele método em específico é perigoso e precisa ser tratado por um try / catch ou especificado no método que chama ele que ele throws aquele tipo de Exception também.
Já que todas as exceções no Java herdam de Exception, podemos fazer um catch polimorfico, mas isso não é uma boa prática de desenvolvimento.
============FINALLY============
O finally é um outro bloco que entra no try / catch e serve para executar uma ação final após o try / catch. Não importa se dá erro ou não, sempre caímos na ação do finally. Isso pode ser muito útil no exemplo de abrirmos uma conexão com alguma aplicação e banco de dados, pois independente de se der erro ou não, queremos fechar essa conexão, pois ela pode continuar consumindo recursos desnecessários.
============TRY WITH RESOURCES============
O try with resources nada mais é do que um try que inicializa um objeto em um referência na abertura do bloco try e fecha ele automaticamente quando termina a execução ou dá erro. Sintaxe:
try (<tipo> <nome referência> = new <tipo>()) {
// implementar código
}
Dessa forma, a referência já estará disponível para uso dentro do bloco try e ela se auto fechará quando terminar a execução do bloco ou der erro, evitando com que a gente escreva códigos gigantes para fazer coisas simples.
Um detalhe importante é que para o try with resources fazer esse fechamento automático que ele faz, a nossa classe precisa *obrigatoriamente* implementar uma interface chamada AutoCloseable. Ela exigirá que a gente sobrescreva o método close() que vem dela, que é o método utilizado para fechar essa nossa intância (normalmente uma conexão).
O que ele realmente faz além de instanciar o objeto na abertura do bloco try é criar automaticamente um bloco finally que sempre irá chamar o método close() da classe.
============EXCEÇÕES PADRÕES============
- NullPointerException:
A referência da qual estamos chamando métodos tem o valor null, ou seja, não foi inicializada.
- IllegalStateException:
A referência tem um estado inconsistente.
- IllegalArgumentException:
Um argumento que não se encaixa nas regras foi passado para um método ou construtor.
============PACKAGES============
Packages são diretórios como os que temos no explorador de arquivos do Windows, mas não são apenas isso. Eles também mudam o jeito como vamos acessar as nossas classes, assim fazendo com que nós passemos o Full Qualified Name (FQN) delas. Isso significa chamar as classes com a sintaxe:
<nome do package>.<nome da classe>
Utilizamos os packages para organizar o nosso projeto e manter as nossas classes separadas seguindo um padrão.
O padrão de nomeação de packages é colocar uma url ao contrário sem o https:// de início, aí sim começamos a criar nossos pacotes com os nomes normais que daríamos.
É uma convenção fazer dessa forma para os pacotes terem nomes exclusivos e não gerarem conflitos quando importamos pacotes externos e nem se alguém for usar os nossos pacotes em algum projeto externo.
Os pacotes sempre devem ficar na pasta src, pois é assim que eles são localizados pelo compilador, ou seja, essa pasta src é a pasta root do nosso projeto. Podemos criar outra pasta para testes e afins.
Para não termos que usar o FQN quando utilizamos as nossas classes, podemos usar o:
import <caminho do pacote>.*;
Assim importamos todas as classes do nosso pacote e podemos usar os nomes simples delas.
É bom lembrar que não precisamos importar o pacote inteiro sempre, podemos importar classes específicas também, basta trocar o "*" pelo nome da classe que queremos, assim somente ela será importada.
============MODIFICADORES DE ACESSO============
Os modificadores de acesso são aquelas palavras chaves que colocamos em classes, atributos e métodos, como public, private, protected e etc.
Eles também mexem diretamente com os nossos pacotes. Por exemplo, se temos uma classe que não está definida como "public", o modificador de acesso vai para default e apenas conseguiremos acessar aquela classe dentro do pacote ao qual ela pertence.
public:
Qualquer classe de qualquer pacote pode ter acesso.
protected:
Visível dentro do pacote e público somente para os filhos (atributos/métodos).
default (package private):
Visível somente dentro do pacote.
Não existe palavra chave para isso, apenas deixamos class.
private:
Visível e acessível somente para a classe em sí que possui o atributo/método private.
============JAVADOC============
Funciona mais ou menos como um package.json do node e serve para colocarmos informações sobre a nossa classe e etc. Podemos ter Javadoc em qualquer classe que quisermos e ela vai servir justamente para dar mais informações sobre as classes para as pessoas.
Essa Javadoc é como um comentário, mas com uma sintaxe um pouco diferente:
/**
*
* Classe que faz algo bem interessante
*
* @author Raphael Righetti
* @version 1.0
*/
Com essa sintaxe isso não é apenas um comentário e sim informações com valor semântico.
A partir desses comentários especiais, podemos gerar uma página completa do nosso javadoc com todas as nossas classes públicas e com as informações que passamos nos comentários. (no vídeo o Nico usou o eclipse para fazer isso, mas com certeza é possível fazê-lo no intellij ou por linha de comando.)
As classes que não são públicas não aparecem pois são consideradas detalhes de implementação e não podem ser utilizadas por outras classes.
Tags / Anotações do javadoc (lista completa):
@author (usado na classe ou interface)
@version (usado na classe ou interface)
@param (usado no método e construtor)
@return (usado apenas no método)
@exception ou @throws (no método ou construtor)
@see
@since
@serial
@deprecated
============GERANDO E UTILIZANDO JAR============
Um arquivo .jar nada mais é do que um arquivo .zip com toda a estrutura dos nossos pacotes e todas as nossas classes compiladas, mas com essa extensão diferente para conseguirmos utilizar como uma biblioteca java.
Podemos gerar um .jar manualmente, mas normalmente fazemos isso com a ajuda da nossa IDE. No IntelliJ, basta criarmos um artefato no Project Structure e buildarmos o artefato na aba build.
O Maven facilita isso para a gente e não ficamos mais dependentes da IDE, basta colocarmos umas configurações de build no nosso pom.xml que quando executarmos os comandos de buildar pelo maven o nosso projeto já gerará um jar com as dependências certas (se não colocamos essas configurações com o assembly no pom.xml, o nosso jar não terá as dependências necessárias e dará um erro).
Para utilizarmos uma biblioteca externa (dependência), devemos pegar o .jar dessa biblioteca em questão e adicionarmos ao Build Path do nosso projeto, assim poderemos realmente utilizar as classes da biblioteca (No IntelliJ, basta abrir o Project Structure, ir em Modules, clicar na aba Dependencies e adicionar o nosso .jar).
O Maven facilita muito isso para a gente, com ele não precisamos ficar fazendo esse processo para adicionarmos as dependências, pois ele faz isso para nós, mas ainda não sei direito como ele funciona, só sei usar no IntelliJ.
Podemos ter um arquivo jar executável. Para isso, basta específicar uma classe que tem o método main quando gerarmos o jar, isso colocará uma configuração no MANIFEST que faz com que essa classe seja executada quando chamarmos o jar via "java -jar".
============java.lang============
java.lang é o único pacote do Java que não precisamos importar nada para utilizarmos as classes, por exemplo, String, System, Exception, e por aí vai.
============String============
Quando criamos uma referência do tipo String, esse valor que passamos para ela se torna um objeto do tipo String.
String é a única classe que não precisa de um new para criar uma instância, apenas passamos a string mesmo.
String é um object literal, ou seja, é um objeto que tem métodos, mas mesmo assim é muito parecido com um literal.
Strings são objetos imutáveis, ou seja, nascem e morrem sem mudar nada neles. Os métodos que alteram a String, na verdade retornam uma nova String modificada.
javadoc String:
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html
Como a String é imutável, isso pode causar problemas de desempenho, pois cada String que criamos, inclusive com os métodos de String, é outro objeto imutável que ocupa o heap.
Para passar por cima desse problema que temos a classe StringBuilder, que diferentemente da String, precisa ser instanciada com o new e não é imutável. Com o método append dela podemos mudar o objeto instanciado e não ter que criar outro, o que ajuda muito no desempenho (pensando em grandes escalas).
Importante lembrar que as duas classes implementam a interface CharSequence.
============Object============
Object é o tipo mais genérico possível do Java e tudo estende ele (incluindo os tipos "primitivos", que coloquei entre aspas por que também são objetos, a única diferença é que não são referências, e sim valores).
Se temos um método ou um construtor que recebe uma referência do tipo Object, elá aceitará literalmente qualquer coisa (por conta do polimorfismo), o que é o exemplo do System.out.println();, que não importa o que você passar, ele vai imprimir no console.
============toString();============
toString(); é um método de Object que retorna o FQN da classe e uns números estranhos. Ele dessa forma não serve para muita coisa, mas a ideia dele é que ele pode ser sobrescrito pela nossa classe para ter o comportamento que quisermos.
Quando passamos uma referência para o sout, o que ele faz é executar o método toString(); e mostrar o retorno dele no console, ou seja, se sobrescrevemos esse método e passamos a referência para o sout, é o nosso toString(); que será chamado.
============java.util============
java.util é o pacote que contém as classes que nos permitem trabalhar com estruturas de dados como listas e etc.
============ARRAY============
List<> não é apenas um array, é uma classe de java.util que faz mais coisas além de criar uma lista, mas também podemos criar arrays no Java.
Para isso, basta usarmos a sintaxe:
<tipo>[] lista = new <tipo>[<quantidade de itens possíveis no array>];
// int[] intLista = new int[5];
Aquele String[] args do main é um array.
De resto, o array no Java é muito parecido com as outras linguagens, só muda que quando iniciamos o array da forma que mostrei acima, ele já inicializa todas as posições com um valor default, ou seja, se for int, será 0 para todas as posições, se for uma referência de um objeto, será null para todas as posições. A partir daí cabe a nós fazermos a atribuição para cada posição.
Importante lembrar que quando se trata de um array de referências, as referências que serão guardadas no array não são exatamente as referências passadas, mas sim cópias dessas referências que apontam para o mesmo objeto (podemos ter quantas referências quisermos para o mesmo objeto).
============DECLARANDO ARRAY FORMA LITERAL============
Exemplo:
int[] refs = {1,2,3,4,5};
Em vez de fazer um array que inicia com os valores zerados, podemos criar um array com valores já definidos na sua inicialização, o que chamamos de literal.
Basta passar os valores entra chaves, como no exemplo mostrado acima.
============POLIMORFISMO EM ARRAYS============
Um array aceita o polimorfismo normalmente, mas se quisermos transformar as referências novamente para o tipo específico que elas tinham quando entraram no array, precisamos usar o type casting:
<tipo especifico> ref = (<tipo específico>) array[<index>];
se não passarmos o tipo certo e a referência não puder virar esse tipo específico que passamos, isso gerará uma ClassCastException.
Só precisamos fazer esse cast explícito quando estamos transformando uma referência mais genérica em uma mais específica, pois isso pode dar problema e precisar informar pro compilador que queremos mesmo fazer aquilo. No caso contrário, quando queremos tornar uma referência mais específica em uma mais genérica, o cast acontece, mas fica implicito, ou seja, não somos obrigados a escrevermos ele.
============String[] args============
É um array que sempre especificamos no método main que recebe parâmetros quando a nossa classe é executada pelo terminal que ficam à nossa disposição para usarmos.
Podemos fazer isso pelas IDEs também.
É muito útil para passarmos opções de execução, debug e etc quando executamos a nossa classe.
============java.util.ArrayList============
É uma estrutura de dados que nos permite trabalhar com arrays de forma de mais alto nível, respeitando as boas práticas da orientação a objetos.
Não precisamos colocar um tamanho fixo para a nossa lista, por padrão ele fica dinâmico, mas podemos, se quisermos. Para isso, basta colocar o tamanho da lista no contrutor quando estamos criando uma nova lista:
ArrayList<String> lista = new ArrayList<String>(5);
Tem métodos para adicionar, remover, pegar um item, enfim. Sempre bom consultar a documentação.
Como a ArrayList trabalha com um array por baixo dos panos, algumas coisas como remover itens do array, modificar o tamanho do array criado criando um novo quando exceder o limite padrão e etc. são muito custosas.
============GENERICS============
Exemplo:
ArrayList<String> lista = new ArrayList<String>();
Generics são esses sinaizinhos "<>" que recebem um tipo no meio (fica parecido com uma tag xml) e servem para Garantir que uma lista só receba um tipo de referências. O polimorfismo ainda se aplica.
============ENHANCED FOR============
Exemplo:
for (Object : lista) {
// implementar código
}
É basicamente a mesma coisa que o for in do python.
============Object.equals(<referência>);============
Quando comparamos uma referência com outra usando o operador "==", nunca vai dar true, a não ser que as referências apontem para o mesmo objeto. Isso ocorre mesmo que as duas referências sejam identicas.
A mesma coisa ocorre quando usamos o .equals();. Ele checa se é o mesmo objeto.
A diferença do equals é que, como ele é um método de Object, ele pode ser sobrescrito, assim podemos fazer outra lógica de verificação nas nossas classes, assim podendo fazer a verificação pelos atributos.
Algumas classes já fazem o override desse método, como a classe String, por exemplo, que quando chamamos o .equals();, ela checa o texto escrito na String, não a referência.
============ArrayList.contains();============
Checa se a lista contém tal referência. Para isso, chama o método .equals(); de Object que podemos sobrescrever.
============java.util.LinkedList============
Possui todos os métodos que ArrayList possui. Isso por que as duas classes implementam a mesma interface java.util.List, mas vou falar sobre ela depois.
Diferentemente da ArrayList, a LinkedList não usa um array por baixo dos panos, mas sim implementa uma outra estrutura de dados chamada:
"lista duplamente encadeada"
que é extensão da "lista simplesmente encadeada".
Essa estrutura consiste em vários elementos que se lembram do elemento anterior e do elemento seguinte, o que torna as ações de remoção e adição de itens muita mais performática, mas também torna as ações de iteração pelos elementos da lista muito costusas, pois não existe forma de acessar diretamente um elemento, essas iterações sempre vão ir no primeiro item, que vai perguntar pro segundo, e assim por diante até chegar no elemento solicitado.
============java.util.LinkedList x java.util.ArrayList============
ArrayList tem mais performance para iterações e pegar itens específicos de uma lista, enquanto LinkedList tem mais performance para fazer ações de remoção e adição de itens.
As duas listas são poderosas, cabe a nós sabermos qual é a mais adequada a ser utilizada conforme o contexto.
Na dúvida, escolhemos ArrayList.
Podemos usar ambas em referências do tipo List, pois ambas sempre terão os mesmos métodos dessa interface.
============java.util.List============
É a interface que todos os tipos de lista (ArrayList, LinkedList e Vector) usam, ou seja, para esses três podemos utilizar o polimorfismo a partir dessa interface.
============java.util.Arrays============
É uma classe que possui vários métodos estáticos para trabalharmos com arrays (não ArrayList). Muito útil quando estamos trabalhando com o args do main.
============java.util.Arrays.asList();============
É um método que recebe um array e transforma em uma lista com todos os métodos de List, inclusive podendo usar o benefício do polimorfismo.
============java.util.Vector============
Assim como o ArrayList, usa um array por baixo dos panos, mas é uma opção threadsafe, ou seja, consegue ser acessada por 2 threads ao mesmo tempo, mas isso é meio raro de acontecer.
============java.util.Collection============
É uma interface que a interface List estende, assim como a interface Set.
Tem alguns métodos que a interface List também tem, mas não tem nenhum dos métodos que mexem com índices, pois isso é uma coisa das listas, e não são só as listas que implementam essa método.
============List x Set============
Listas aceitam referências duplicadas sem nenhum problema, o que as vezes pode ser um problema quando temos uma lista de todas as contas no nosso sistema, por exemplo, onde não queremos nenhuma repetida.
Listas usam índices.
Sets não permitem referências duplicadas, e fazem isso por meio do hashCode(). de Object.
Sets não usam índices.
Sets = conjuntos.
============AUTOBOXING x UBOXING============
Primitivos não deveriam mais existir no Java, mas existem.
Algumas coisas não funcionariam com primitivos, como colocá- los em uma lista, pois listas aceitam apenas referências, mas funcionam.
Isso ocorre por que o Java transforma esses inteiros em referências, como "Integer" para int, o que faz com que eles possam ser usados nesses casos.
O nome desse processo é Autoboxing, e Unboxing é o processo inverso, que consiste em tranformar uma referência de uma classe Wrapper em um primitivo novamente. Exemplo de código que usa os dois:
public int sumEvenNumbers(List<Integer> intList ) {
int sum = 0;
for (Integer i: intList )
if ( i % 2 == 0 )
sum += i;
return sum;
}
Existem várias classes como alternativa para os primitivos. Essas são as classes Wrappers
============MÉTODOS Integer============
ref<Integer>.intValue();
É um método que faz o unboxing de uma referência Integer devolvendo um primitivo a partir dela.
Possui várias versões para transformar em diversos tipos primitivos.
Integer.valueOf(int || String || ...);
É um método que faz o autoboxing a partir de um tipo primitivo int ou uma String com apenas caracteres numéricos.
Integer.parseInt(String);
É um método que faz a conversão de uma String para o tipo primitivo int.
O Integer possui algumas constantes (que vem de um Enum) que apresentam algumas informações sobre int para a gente. Por exemplo:
- MIN_VALUE: menor valor possível de int (negativo)
- MAX_VALUE: maior valor possível de int
- SIZE: número de bits de int
- BYTES: número de bytes de int
ref<Integer>++ funciona!!!!
O que acontece unboxing, incrementação e autoboxing, assim incrementando normalmente.
Os outros Wrappers são quase a mesma coisa, só mudam algumas coisas específicas para cada um.
============java.lang.Number============
Todos os Wrappers numéricos herdam de Number, ou seja, number tem todos os métodos de Unboxing que os Wrappers numéricos tem e todas as referências de algum Wrapper numérico podem ser vistas como Number.
============ORDENAÇÃO DE LISTAS============
ref<List>.sort();
É um método que ordena os itens da nossa lista a partir de um Comparator.
java.util.Comparator:
Comparator é uma interface que possui alguns métodos de comparação entre itens, como compare(); e reversed();. Esses métodos são bem parecidos com o Object.equals();, ou seja, podemos ter nossas próprias classes que implementam esses métodos para fazer a comparação.
Recebe Generics para saber com que tipo de objeto está lidando ao fazer as comparações.
java.util.Comparator.compare();
É um método que compara dois itens e retorna um int:
- 0 se for igual
- Número negativo se for menor
- Número positivo se for maior
Note que podemos não respeitar essas regras na implementação, já que somos nós que fazemos a lógica de comparação, mas é sempre uma boa ideia seguir as boas práticas.
Note também que a lógica pode ser, no caso de comparação numérica, apenas o objeto 1 menos o objeto 2, pois se o objeto 2 for maior que o objeto 1, o resultado será negativo, se o objeto 2 for menor que o objeto 1, o resultado será positivo e se os dois objetos tiverem o mesmo valor, o resultado será 0.
Note também que a lógica não precisa ser complexa e nem ter ifs, podemos simplesmente usar os métodos compareTo(); ou compare(); dos Wrappers e de String.
============COMPARANDO ORDEM ALFABÉTICA============
String já possui um método de comparação de strings seguindo a ordem alfabética, que é o ref<String>.compareTo(ref<String>).
Ele retorna um int seguindo as mesmas regras do java.util.Comparato.compare();, mas diferentemente dele não precisa ser implementado pela nossa classe, ele funciona perfeitamente do jeito que é.
Para criarmos um comparator que compara em ordem alfabética, basta criarmos uma classe que implementa o método compare(); e fazer a lógica com base nesse compareTo(); de String (importante lembrar que não temos que fazer a lógica de retorno do int, pois o compareTo(); já faz isso para nós).
Wrappers também tem o compareTo(); além de também terem um método estático compare(); que recebe por parâmetro dois doubles.
============java.lang.Comparable============
É uma interface que faz com que a classe que implementa seja comparável, ou seja, ter o método compareTo();. Nós que implementamos a lógica desse método, mas nada nos impede de usar os compareTo(); e compare(); dos Wrappers e de String.
Usa Generics para saber com qual tipo se compara, normalmente recebendo o tipo da própria classe.
É a interface que os Wrappers e String implementam.
============java.util.Collections============
É uma classe com vários métodos estáticos, inclusive para ordenação de coleções.
É um jeito antiga e errado (se seguirmos as boas práticas) de fazer a ordenação, pois ter muitos métodos estáticos não é OO.
============ORDEM NATURAL============
Nos métodos de ordenação, na falta de um comparator, é usada a ordem natural da classe que a lista armazena.
É a ordem natural da nossa classe, especificamos ela implementando o Comparable. Ela é usada na falta de um Comparator nos métodos de ordenação.
Para usarmos os métodos de comparação sem um Comparator, nossa classe deve necessariamente ter implementado a interface Comparable, pois os métodos usarão os métodos compareTo(); ou compare(); da nossa classe por baixo dos panos, justamente por não ter um Comparator.
No sort(); da classe Collections, basta passarmos apenas a lista para o método, sem o segundo parâmetro (que é o Comparator).
No sort de List fica meio diferente. Em vez de só chamarmos o método sort(); sem passar nenhum parâmetro, temos que passar null como parâmetro.
============Collections.rotate(<lista>, int);============
É um método que muda a posição de um número de itens, começando do começo, para o final do array.
============ORDENAR ARRAYS============
Utilizando a classe Arrays, temos vários métodos para trabalhar com arrays, inclusive métodos de ordenação.
============final============
É uma palavra chave que significa que um atributo de uma classe pode ser atribuido apenas uma vez, ou seja, no construtor.
No caso de listas, ainda podemos adicionar itens nela, o que não podemos mesmo fazer é mudar a referência.
============FUNCTION OBJECTS============
São objetos que se servem para trazer alguma funcionalidade, como uma classe que estende Comparator, que muitas vezes vai server apenas para fazer a comparação. Isso não segue o padrão de classes de que os dados e comportamentos devem estar agrupados, por isso esse nome diferente.
============CLASSES ANÔNIMAS============
Podemos criar classes anônimas no Java, ou seja, sem todo aquele processo de criar um novo arquivo .java, implementar a interface que queremos utilizar, enfim. A ideia de classe anônima é criar uma instância de um classe mesmo que não tenhamos dado nome e nem nada do tipo, justamente para evitar o uso dos Function Objects. Sintaxe:
lista.sort(
new Comparator<Conta>() {
// implementação do método compare();
}
);
Como mostrado acima, não criaríamos uma nova classe para ser esse nosso comparator, mas sim colocaríamos uma classe anônima para fazer isso.
É importante saber que mesmo parecendo que estamos criando uma instância da interface Comparator, não é isso o que ocorre. Na verdade, o compilador cria uma classe automaticamente com um nome gerado pelo próprio compilador e que implementa a interface Comparator (quando escrevemos o método compare();, devemos passar o @Override.
Podemos atribuir classes anônimas à atibutos.
============LAMBDA EXPRESSIONS============
São bem parecidas com arrow functions no JavaScript e podemos usá-las como nossos Comparators, pois ela já irá saber que é um Comparator e funcionará como tal, sem mesmo termos que implementar a interface ou dar um @Override no compare();. Sintaxe:
lista.sort((m1, m2) -> Integer.compare(m1.getIdade(), m2.getIdade));
Note que não precisamos colocar o tipo das referências recebidas por parâmetro (mas podemos), pelo tipo do Generics da nossa lista ela já saberá qual é o tipo.
Importante saber que podemos abrir chaves para fazermos lógicas maiores e etc. Também é bom lembrar que lambdas não são usadas apenas em listas, podemos fazer muitas coisas com lambdas, assim como uma arrow function no JavaScript. Mais exemplos:
- (int a, int b) -> { return a + b; }
- () -> System.out.println("Hello World");
- (String s) -> { System.out.println(s); }
- () -> 42
- () -> { return 3.1415 };
- a -> a > 10
Por baixo dos panos, o compilador transforma tudo isso em classes, assim como ele faz com as nossas classes anônimas.
Elas não são auto-executáveis.
============.forEach();============
É um método das listas que permite fazermos um for iterando por cada item da lista de forma mais enxuta e mais legível.
Recebe um Consumer como parâmetro, ou seja, uma classe que implementa essa interface que permite consumirmos referências por meio do método accept(); e usarmos métodos delas, etc.
Como sabemos da existência de Lambdas, podemos muito bem utilizá-las que vai ficar um código muito mais simples e bonito e não teremos que criar nenhuma classe anônima para isso, tornando as coisas muito mais enxutas.
Expressões Lambda são um jeito de usar callback no Java.
Pelo Java ser muito rígido em questão das regras que as vezes parece meio confuso ou meio complexo. No Java não existem funções fora de classes, todas as funções precisam ser métodos, então essa coisa de passar uma função de callback seria impossível sem as lambdas, que são as responsáveis por deixar mais simples a nossa escrita, mas que ainda criam as classes anônimas e fazem tudo que precisa ser feito por baixo dos panos.
============java.util.Iterator============
É uma interface que nos permite trabalhar com loops em qualquer collection, seja ela uma lista, um conjunto, um mapa, enfim.
Ela possui os métodos hasNext();, que retorna um boolean se tiver um próximo elemento na coleção, e next();, que vai para o próximo item da coleção.
Para usarmos, basta criar uma referência do tipo Iterator<> (usa Generics) e passarmos lista.iterator(); para ela. Esse método irá retornar um Iterator específico da nossa lista.
============INSTANCIAÇÃO DE INTERFACES============
É impossível instanciar interfaces no Java, sempre devemos implementar elas nas nossas classes. Isso muda apenas em classes anônimas, o que pode confundir bastante.
Basicamente, quando usamos:
new <interface>(){}
o Java gera uma classe anônima que implementa a interface passada no new. Já se escrevemos:
new <interface>();
gera um erro, pois não podemos instanciar uma interface.
Importante lembrar que Lambdas fazem isso por baixo dos panos, por isso que elas funcionam.
Podemos atribuir classes anônimas para atributos.
============java.io============
É o pacote de Input e Output que serve muito para trabalhar com fluxo de dados, leitura de arquivos, dados que vem pela rede ou pelo usuário, enfim.
============java.io.FileInputStream============
FileInputStream é uma classe que respresenta um fluxo de entrada por meio de um arquivo externo. Para passarmos o arquivo que queremos que seja lido nesse fluxo, temos que fazer isso no construtor da classe quando estamos instanciando. Esse construtor tem algumas implementações, algumas aceitando uma instância do tipo File (representa um arquivo) e uma que aceita uma String que é o caminho do arquivo (normalmente usamos esse).
Essa classe tem alguns métodos, incluindo alguns de leitura, mas que são bem chatos de utilizar, pois retornam um inteiro que representa um byte.
Joga uma exceção checked (FileNotFoundException), por isso precisamos ou tratar com try/catch, ou assinar o método com throws IOException.
============java.io.InputStreamReader============
É uma classe de leitura de InputStreams que deixa relativamente mais fácil de ler eles, mas ainda assim é meio chato, pois retorna um array de caracteres e um int que mostra quantos caracteres conseguiu preencher. Normalmente não queremos isso.
Recebe um InputStream no construtor.
============java.io.BufferedReader============
Basicamente, um Reader que usa buffer, ou seja, vai guardando os caracteres no buffer até chegar em um limite. Usamos o método readLine(); para fazer a leitura de uma linha, sendo a quebra da linha o limite desse buffer. Se não tiver mais linhas para ler, retorna null.
Recebe um StreamReader no construtor.
============DECORATOR============
É um padrão de desenvolvimento bastante usado no Java que consiste em ter várias classes que decoram as funcionalidades de outra, assim fazendo um encadeamento de chamada dos métodos, passando sempre do último e passando por todas. Isso ocorre nos Readers, como visto acima. O BufferedReader lê os arrays de caracteres do InputStreamReader, que por sua vez lê as informações do FileInputStream, cada uma melhorando as funcionalidades das outras.
============FileNotFoundException============
É uma exceção que ocorre quando o arquivo passado para o construtor do InputStream não é achado.
============IOException============
É a exceção mãe de todas as exceções do java.io. É checked, pois herda de Exception.
============InputStream x Reader============
São as duas categorias que existem nesse mundo de fluxo de entrada e saída, InputStreams sendo realmente o fluxo de entrada dos dados, que são bytes, no caso, e os Readers sendo os leitores desse fluxo, que representam esses bytes em caracteres e frases que nós podemos entender.
Tanto InputStream quanto Reader são classes abstratas mães de todos os InputStreams, como FileInputStream, e Readers, como BufferedReader.
Importante lembrar que temos Readers específicos para arquivos também, ou seja, não precisamos ficar fazendo todo esse processo de passar uma referência para a outra.
Stream:
Dados binários.
Reader / Writer:
Textos.
============OutputStream x Writer============
Basicamente o mesmo conceito de quando estamos trabalhando com InputStreams e Readers, mas no sentido contrário, passando para fora o fluxo de dados.
Praticamente todas as classes que temos na entrada, temos na saída também, com diferença em métodos, pois precisamos de métodos de escrita e etc.
============System.in============
É uma propriedade de System que nos permite receber inputs do console no tempo de execução, inclusive podendo ser utilizada em loops para escrevermos diversas vezes.
============BufferedWriter.flush();============
É um método que despeja o que foi guardado no BufferedWriter e limpa ele.
============FLEXIBILIDADE EM STREAMS============
Como toda Stream é uma Stream, não importa de onde vem o nosso fluxo, poderemos usar uma lógica muito semelhante para tratar esses fluxos de dados, não importa se estão vindo do console, da rede, de um arquivo, enfim.
Importante lembrar que temos muitas classes de mais alto nível que trabalham com tudo isso falado até agora por baixo dos panos, mas é muito importante entender esses conceitos para saber realmente o que está acontecendo.
============Socket============
Socket é uma classe que representa uma conexão com algum servidor ou outra aplicação que também tem um InputStream e OutputStream, podendo ser pegos com os métodos getInputStream(); e getOutputStream();. Essas Streams funcionam como qualquer outra Stream, por exemplo FileInputStream, e podemos utilizar um código genérico para tratar esses fluxos.
============FileWriter============
É uma classe de mais alto nível que simplifica muito o processo de fazermos um Writer que escreve em um arquivo, sem ter que ficar criando desde o OutputStream e etc. Isso acontece pois a classe uso todo esse esquema que a gente viu por baixo dos panos, tirando essa responsabilidade da gente.
É Closeable (possui o método close(); que é chamado automaticamente no try with resources) e tem quase todos os métodos que um BufferedWriter normal tem, mas não possui o método newLine();. Entretanto, podemos sim colocar linhas novas na nossa saída, para isso basta colocarmos os caracteres especiais de quebra de linha ("\r\n" ou "\n").
Como esses caracteres podem mudar dependendo do sistema operacional que estamos usando, o System tem um método estático específico para retornar essa quebra de linha certa, que é o System.lineSeparator();. Bem intuitivo.
Podemos também passar a nossa referência do FileWriter para o construtor do BufferedWriter, que muitas vezes pode ser melhor de se utilizar.
Sempre bom lembrar que não precisamos passar uma instância para uma variável ou atributo, se precisamos passar para um construtor, é muito normal que criemos o objeto diretamente no construtor.
============PrintStream || PrintWriter============
PrintStream existe desde a primeira versão do Java, e era o único meio possível de escrever em algum output.
Apesar de já ser bem antigo, funciona muito bem e é muito intuitivo, tendo um método println(); que funciona exatamente como o println(); do sout, só que funciona para qualquer OutputStream.
PrintWriter é mais novo, mas é praticamente a mesma coisa que o PrintStream, a única coisa que muda é que não usa OutputStream por baixo dos panos, e sim Writers.
Os dois podem receber uma String com o nome do arquivo no construtor, mas possuem diversos construtores diferentes para trabalharmos com outputs diferentes.
(System.out é um PrintStream)
============java.util.Scanner============
É uma classe que vem de java.util, mas que server justamente para ler arquivos, contendo vários métodos específicos de leitura, inclusive dando a possibilidade de trabalharmos com arquivos .csv e etc.
Diferentemente das classes de mais alto nível de escrita, o Scanner pode receber uma String no construtor, mas essa String não representa o nome do arquivo a ser lido, e sim será a fonte a ser escaneada. Se queremos ler arquivos com o Scanner, precisamos passar uma instância de File.
============ref<Scanner>.hasNextLine();============
Retorna um boolean que diz se a fonte ainda tem um próxima linha.
============ref<Scanner>.nextLine();============
Vai para a próxima linha (se ainda não tiver sido executado, vai pra primeira) e retorna o texto que está na linha.
============SEPARANDO VALORES DAS LINHAS============
Para separarmos os valores com base em algum separador, como a vírgula, no caso do csv, podemos tanto usar o método split(regex); de String, que retorna um array, quanto usar outro Scanner que irá escanear as linhas em si, não o arquivo.
Quando instanciamos um Scanner, podemos usar alguns métodos a partir da referência que mudam algumas regras sobre esse Scanner.
Um desses métodos é o useDelimiter(regex);, que irá aplicar um caractere ou mais de delimitador da nossa String que está sendo escaneada. Assim quando usamos o método next();, o que será retornado por ele é o valor após o delimitador (se não for o primeiro).
Existem outros métodos next();, como nextInt();, nextDouble();, por exemplo, cada um retornando um valor do tipo especificado no nome.
Isso pode gerar um problema na JVM, pois ela segue um padrão Locale baseado nas regras do país que está a língua do nosso sistema operacional, como a separação da casa inteira e da decimal, que no Brasil é separado por vírgula, já em outros países é separado por ponto. Para combater isso e usarmos um Locale específico, podemos usar o método useLocale(locale). É um método que recebe uma instância de Locale que nós mesmos podemos configurar, mas a classe Locale já tem várias constantes de Locales reais, como o Locale.US, que retorna uma instância de Locale que tem todas as configurações de um sistema estadunidense.
============String.format(String template, valores...);============
É o método de formatação de String do Java, usa aqueles trecos como %d, %s, etc. para definir que tipo é a variável que vai aparecer ali naquele lugar do template. tem várias coisas legais, mas devemos olhar sempre na documentação, é muita coisa para decorar.
Pode receber um terceiro parâmetro que é o Locale.
Podemos usar o .format(); diretamente no System.out.
============java.util.Properties============
É a classe que representa as propriedades definidas em arquivos .properties.
Podemos criar propriedades com o método:
setProperty("chave", "valor");
Essas propriedades ficarão gravadas na nossa instância, e pegamos elas com o método:
getProperty("chave_da_propriedade");
Podemos criar arquivos .properties com o método:
store(new FileWriter("nomearquivo.properties"), "comentarios...");
Assim o arquivos será criado exatamente como precisamos para um arquivo .properties.
Podemos ler arquivos .properties com o método:
load(new FileReader("nomearquivo.properties"));
Depois basta acessar as propriedades com o getProperty();
============UNICODE CODEPOINT============
A tabela unicode é uma tabela que tenta ter todos os símbolos do mundo, e surgiu como uma alternativa ao ASCII, que sempre teve limitações quanto à isso.
Codepoint é o número que representa o caractere e podemos achar o caractere por ele, como 0233, que é o símbolo "é". Usamos isso no Java.
Encoding é a tabela que pega os caracteres do Unicode e traduz eles para dados binários. Existem vários encodings que funcionam de vários jeitos diferentes, mas como normalmente escrevemos em portguês com acentos e etc, o Encoding UTF-8 é o que sempre vamos utilizar.
Os encodings podem variar dependendo do nosso sistema operacional.
============ref<String>.codePointAt(index);============
É um método de String que nos retorna o número do CodePoint do caractere que está no índice passado por parâmetro.
============ARRAY DE BYTES NO CONSTRUTOR STRING============
Podemos passar um array de bytes para criarmos uma nova String a partir desses bytes, mas para isso precisamos usar o construtor da classe String. Também podemos passar o Encoding como segundo parâmetro.
============Charset============
É uma classe abstrata que tem alguns métodos estáticos que podemos usar para pegar informações sobre os encodings disponíveis, qual é o padrão que está sendo utilizado, etc.
============ref<String>.getBytes();============
É um método de String que retorna os bytes de uma String e pode receber como parâmetro um Charset, ou uma String com o nome de um Charset.
============java.nio.StandarCharsets============
É uma classe que contém várias constantes que são os Encodings mais usados, assim podemos passar eles em vez de ficar passando o nome do encoding por String.
============IMPORTÂNCIA CHARSETS============
Os Charsets são muito importantes, pois sempre que temos um fluxo de entrada na nossa aplicação, estamos recebendo bytes que precisam ser interpretados (ou não, às vezes) por algum Encoding, o que pode dar um problemão se usarmos o errado.
============ENCODING NO Scanner============
No construtor do Scanner conseguimos definir o encoding que ele usará para interpretar os bytes. Fazemos isso no próprio construtor do nosso Scanner, normalmente sendo o segundo parâmetro que funciona com vários tipos de primeiro parâmetro.
Não conseguimos passar encodings diretamente para Streams, pois eles leem justamente bits e bytes, só conseguimos passar encodings para Readers e Writers.
Funciona de forma muito parecida para PrintWriter e PrintStream.
As IDEs normalmente têm opções que nos permitem mudar o encoding padrão para tudo no nosso projeto, o que nos possibilita trabalhar em conjunto de qualquer sistema operacional para qualquer sistema operacional, basta sabermos ajustar isso.
============SERIALIZAÇÃO============
Serialização é o processo de transformar um objeto Java em um fluxo de bits e bytes e transformar um fluxo de bits e bytes em um objeto Java, o que é muito comum no mundo da programação, onde temos diversas entradas e saídas.
Surgiu desde o começo no mundo Java para dar possibilidade de reutilizarmos funcionalidades que estão em outra JVM de outro serviço que temos e precisamos pegar por meio da rede.
Podemos usar o ObjectOutputStream e o ObjectOutputStream para trabalhar com esses objetos serializados, ambos têm métodos muito bons para se trabalhar com isso.
É importante lembrar que quando lemos um objeto serializado, ele sempre será lido com o tipo mais genérico possível, o Object, mas podemos usar o type cast para modificar para os tipos mais específicos que forem compatíveis com o objeto.
Também é importante lembrar que o método readObject(); pode retornar uma ClassNotFoundException, pois se não tivermos a classe que o objeto pertence disponível, não será possível ter aquela instância na nossa aplicação.
Quando tentamos serializar um objeto que não é de uma classe padrão do Java, podemos receber a exceção NotSerializableException, pois nossa classe não implementa a interface Serializable, que é uma interface vazia (sem nenhum método), mas que serve para fazer essa marcação. Essa interface só não é uma annotation por que não existiam annotations na época que ela foi criada. Só de implementarmos ela, já conseguimos serializar, mas é importante lembrar que sempre devemos colocar o serialVersionUID, pois sem ele o Java cria um automáticamente e pode ser que dê problemas.
É muito raro usar serialização "na unha", mas é muito importante entender os conceitos para saber o que está acontecendo por baixo dos panos.
============serialVersionUID============
É um atributo que devemos inserir em nossa classe quando ela é Serializable.
Esse atributo é o número da versão de serialização da nossa classe, e serve para que se o objeto que tentamos importar para nosso projeto não tiver a mesma versão, mesmo que seja da mesma classe, gere uma InvalidClassException. Isso é importante, pois podemos ter um objeto serializado que não possui todos os métodos e atributos da nossa classe atual por ser de uma versão antiga da classe.
============SERIALIZAÇÃO HERANÇA/COMPOSIÇÃO============
Se queremos aplicar a serialização em classes que herdam de outras classes, precisamos fazer com que todas as classes nessa hierarquia implementem Serializable, o que podemos fazer simplesmente implementando Serializable na classe mãe. Devemos lembrar que mesmo implementando Serializable por conta da mãe implementar, devemos criar o serialVersionUID individual para todas as classes da hierarquia.
Quando se trata de composição, o objeto usado na composição também precisa ser Serializable, a não ser que ele seja transient, que é uma palavra chave que colocamos no atributo que vai fazer com que ele seja ignorado na serialização e sempre seja null.
============default METHODS============
Interfaces podem ter métodos concretos a partir do Java 8, basta serem declarados com a palavra chave "default" na frente.
============Collections.unmodifiableList(List);============
É um método de Collections que retorna uma lista apenas de leitura a partir de uma lista que já temos. Muito útil quando queremos encapsular uma lista nossa e fazer com que só seja possível modificar os itens dela a partir dos métodos da referência que possui essa lista.
É importante lembrarmos que também não poderemos ordenar a nossa lista imutável, pois os métodos de ordenação mexem diretamente na lista, não retornam uma lista nova ordenada. Para fazermos isso, basta criarmos uma nova lista (outra instância) a partir do construtor de listas, passando a nossa lista imutável como argumento.
Também existe o unmodifiableSet(Set);, que funciona de maneira muito semelhante.
============SETS============
Não temos garantia de que ordem vão ficar os nossos itens dentro de um Set, ou seja, mesmo colocando em uma ordem específica, normalmente essa ordem não vai ser respeitada. Devido a essa característica, nenhum método de ordenação ou de acessar um item por um índice funciona em um Set. Mesmo assim, ainda podemos checar se um item existe em um Set utilizando o método contains(ref);. Creio que o método contains(ref); utilize o método equals(ref); por baixo dos panos.
Qualquer tipo de Set não aceita elementos duplicados, nem mesmo Strings com valor igual. Mesmo que a gente adicione o mesmo elemento mais de uma vez em um Set, ele manterá apenas um.
É muito utilizado por conta da velocidade surpreendemente maior com métodos de busca, remoção e inserção (Especialmente no HashSet). Essa velocidade maior ocorre por conta do uso de uma estrutura de dados diferete, que é a Hash Table(Tabela de Dispersão), que consiste em armazenar itens com uma chave (hash) que podem ter os valores acessados a partir delas, sem ter que fazer muitas iterações para essas ações.
É uma coleção, herda de Collection assim como List, por isso tem vários métodos parecidos.
(A diferença de performance é muito absurda em pesquisas!!)
============Collections.emptySet();============
Retorna um Set vazio e imutável, pode ser útil para quando alguém dá um get em algum set que não existe mais.
(temos vários métodos que retornam sets especiais na classe Collections, inclusive um para trabalhar com um set em threads múltiplas.)
============Collection.contains(ref);============
O método contains(); checa se um elemento passado por parâmetro está dentro de uma coleção.
Precisamos ter em mente que devemos passar referências do tipo que a coleção armazena, não passar uma String com o valor que estária em um atributo de nossa referência.
O jeito que esse método funciona por baixo dos panos é que ele retorna true se algum objeto dentro da coleção for equals(); ao objeto passado por parâmetro, ou seja, podemos manipular a lógica para saber se contém ou não. Por padrão será usado o equals(); de Object, que checa se realmente é a mesma referência.
No entanto, o contains não funciona dessa forma quando estamos trabalhando com Sets e HashSets, especificamente. Isso ocorre por que não é necessário passar por vários elementos do conjunto perguntando se ele é igual a outro elemento, o contains no Set simplesmente pesquisa pelo código hash (hashCode) e responde se achou o elemento ou não. Podemos mudar a lógica do método hashCode();, que é o método que retorna o hashCode do objeto, para ele não ser um aleatório, e sim, ter uma lógica de criação. A classe String já tem um método hashCode que gera um hashCode bom que sempre será o mesmo se a String for igual, podemos utilizar esse método quando formos sobrescrever o hashCode(); da nossa classe, usando algum atributo que será fixo para gerar esse hashCode. Assim o contains funcionará perfeitamente quando quisermos comparar um objeto que não é literalmente o mesmo, mas que é igual (na nossa definição de igual).
É uma boa prática sempre que sobrescrevermos o método equals();, também sobrescrevermos o método hashCode();.
============Objects.hash(args...);============
É um método estático da classe Objects (que possui vários métodos estáticos para trabalhar com objetos) que retorna um hashcode a partir de uma sequencia de argumentos, normalmente sendo atributos de alguma classe.
Muito útil para gerarmos hashcodes compatíveis com outras lógicas de contains();.
============LinkedHashSet============
Diferentemente do HashSet, guarda a ordem na qual os itens foram adicionados no set, mas mesmo assim não tem os métodos de acesso por índice e etc, só irá mudar a ordem percorrida no forEach(); e etc.