Introduction

Chapter 1

Ce chapitre parle des concepts de base et classes de base, pas complet, pour plus de détail, une recherche google devrait vous trouver la réponse.

Types de données

Lorsque l'on programme, il faut manipuler des données (des nombres, des chaines de caractères, des "listes de trucs"). Et le java est un language qu'on appelle strictement typé c'est a dire qu'il faut explicitement toujours dire avec quoi on est entrain de travailler (nombres, ...) et il faut donc pour ça connaitre les différents types de données.

Les types primitifs

int : Le type int fait référence a un nombre entier

float : Le type float fait référence a un nombre décimal (ex: 5,2)

long : Le type long contient un nombre entier, mais qui peut être bien plus grand que dans un int

byte : Le type byte contient un byte, autrement dit 8 bits (0 ou 1) et peut donc stocker un nombre allant de -128 a 127 ( = 256 possibilités dans 8 bits)

double : Le type double peut contenir un nombre décimal plus grand que le nombre maximum d'un float.

boolean : Le type boolean est un "vrai ou faux" (true: vrai et false: faux)

char : Le type char fait référence a un caractère

Ces types ne font que contenir une donnée. Tout ce que l'on peut faire avec ce sont les différentes opérations (cf: Chapitre 1.2).

Comment les utiliser ?

En programmation, il existe ce qui s'appelle une variable qui peut être comparé a une boite dans laquelle on met une valeur. Voici comment en créer une en java:

int life_answer = 42;
  

Vous remarquerez qu'il est OBLIGATOIRE de spécifier le type (int ici en l'occurence) de la variable que l'on est entrain de créer.

Il est possible de les utiliser par après en utilisant leur nom:


int double_life_answer = life_answer * 2;

Les classes au dessus des types primitifs

Ces types primitifs ne sont pas pratiques du tout, par exemple comment vous convertiriez un char en int facilement ? c'est vite compliqué.

Le language contient donc différentes classes (qui seront expliquées en détails plus tard dans le livre) permettant de faciliter ce genre d'opérations.

Attention

Les variables utilisant ces types ne seront plus de simples valeur mais seront ce que l'on appelle des objets (instances).

La classe String

Stocker un caractère (ex: 'a' ) dans un char c'est bien, mais généralement on voudra toujours stoquer des chaines de caractères (ex: "Hello World"). C'est à ça que sert la classe String !

String ma_chaine = "Hello, World !";

L'avantage du fait que la variable soit un objet est qu'il est possible d'appeler des méthodes sur celle ci, ce qui n'est pas possible sur les types primitifs:


ma_chaine.contains("Hello"); //Retourne true

La classe Integer

La classe Integer permet principalement de faire des conversions entre les types. Il est par exemple possible de faire:

char test = '5';
Integer i_am_cool = Integer.parseInt(test);
// i_am_cool vaudra 5

Operateurs

Exposant

Pour créer un exposant comme on utilise

Math.pow(a,b);

Racine carrée

Pour un racine carrée on utilisera

Math.sqrt(a);

Division

En utilisant la division avec des entiers, le résultat sera aussi un entier, par exemple

7/2 //-> 3

Boucles

Une boucle est une portion de code permettant d'éxécuter une tâche plusieurs fois.

Les types de boucles

Il existe 2 types de boucles, les voici:

La boucle while

Cette boucle est la plus simple, elle prends une condition en paramètre,et va l'évaluer avant chaque itération. Si c'est true, elle répète, si c'est false elle arrête.

while (list.length > 0){
    list.remove('smth')
    //Do smth
}

For

La boucle for est quand a elle un peu plus compliquée... Elle peut prendre en paramètre soit une notation spécifique permettant d'itérérer sur un nombre:

for (int i=0:i < n; i++){
    //Do smth
}

ici le paramètres i est un nombre qui va de à , pour , utiliser

(int i=n; i<0; i--)

aurait créer un nombre i de à , pour

Ou bien prendre en paramètre un élement qui est soit un Iterable soit un Array avec une autre notation présente ci dessous. (documentation)

Element[] array = Element[]{Element,Element}
for (Element element : array){
    //Do smth
}

Arrays

Ici on peut séparer deux types, les primitifs et ceux basés sur les structures

Primitif

S'écris avec une classe et deux [], la principale différence ici est que la taille est fixée et ne change pas

int[] array = new int[]{1,2,3,4,5};
int[] voidarray = new int[5];

On accède à la longeur de l'array en utilisant .length

On peut soit lui donné des valeurs soit donné une taille fixe pour accéder à des valeurs on utilise :

array[0] //=> 1
voidarray[0] = 0;

Pour copier un array, on doit soit utiliser une boucle for pour avoir les valeurs soit utiliser

int[] sourceArray = {1, 2, 3, 4, 5};
int[] destinationArray = new int[sourceArray.length];
System.arraycopy(sourceArray, 0, destinationArray, 0, sourceArray.length);

pour copier les références

De manière générale, on peut également l'utiliser pour n'importe quelle classe comme apr exemple :

Element[] elements = new Element[1];

Structural

Tout d'abord, il doit être importé,

import java.util.ArrayList;

Sa taille est variable et ne change pas, on la construit en précisant le contenu des classes dans des <>

ArrayList<Integer> myArrayList = new ArrayList<>();

Les trois méthodes principales sont

myArrayList.add(42);            // Add a value
int value = myArrayList.get(0);  // Access a value
myArrayList.remove(0);          // Remove a value

De manière similaire on peut utiliser la superclasse List, en réalité il y a des différences entre les deux et certains cas ou on préférera l'un par rapport à l'autre

Pour copier deux arrays on peut soit utiliser une boucle for pour avoir les valeurs copié soit utiliser des valeurs copiées

ArrayList<Integer> array = new ArrayList<>();
ArrayList<Integer> array2 = new ArrayList<>(array);

pour avoir les références

Formatage

Structure de fichier

Tout fichier java doit respecter une certaine structure, premièrement le nom de fichier doit etre similaire à la classe publique principale, tout fonctionne par classe en java Il faut également que la première ligne indique l'emplacement du fichier dans votre projet,

.
└── src/
    ├── MyClass.java
    └── folder/
        └── TestClass.java

ici TestClass.java ressemblera à

package folder;
public class TestClass {
    // functions here
}

A noté qu'il faut indiqué par class qu'il s'agit d'une classe Ensuite, il faut pour que vote fichier puisse être exécuté, c'est à dire pas depuis des tests, qu'il possède un fonction main comme

public class MyClass {
    public static void main(String[] args){
        // do smth
    }
}

La partie fonction en elle même consiste à

void main(String[] args){
        // do smth
    }

Elle nous indique que la fonction main qui prend en argument String[] args retourne void c'est à dire rien, si la fonction terminait par

return new String(" ");

sa déclaration serait

String main(String[] args){}

En java également, les {} ont de l'importance, ils délimitent le début et la fin du corp d'une fonction/classe

De même chaque ligne qui est dans le corps d'une fonction doit terminer par ; pour signifier que la ligne est finie

On peut alors rajouter des fonctions et des classes (non publique)

public class MyClass {
    public static void main(String[] args){
        // do smth
    }
    public static String operation(String input){
        return ThirdClass.function(input);
    }
    class ThirdClass{
        public static String function(String input){
            return SecondClass.function(input);
        }
    }
}
class SecondClass {
    public static String function(String input){return null;}
}

Modifieurs d'accès

Comme déjà utilisé plus haut, on utilise des mot pour désigné des aspects d'une classe ou d'une fonction

static : accroche une méthode ou une variable à une classe public : rend la méthode /variable publique ce qui veut dire qu'il n'y a pas de restriction pour l'appeler private: rend la méthode /variable privée, seul une instance de la classe peut l'appeler final : indique qu'une variable ne peut pas ête modifiée

Déclaration du type

En java comme dans d'autre language, il faut déclarer le type d'un objet quand on en crée ou reçois un, ainsi que dans une méthode, pour savoir ce qu'elle devrait retourner comme ici

public static void main(String[] args){
        int n = 0;
    }

la fonction doit retourner void c'est à dire rien, ce qui peut arriver avec une fonction qui créer un fichier par exemple ou bien qui utilise System.out, on peut aussi voir qu'elle prend String[] args en argument, String[] est le type de args soit une liste de String, n aussi est déclaré, il est ici un entier (´int´), la variable déclarée doit être une classe

public static void main(String[] args){
        int[] n = new int[1];
    }

Ici on peut voir que pour créer une liste on utilise le mot new qui est propre aux classes qu'on initialise

Tests

Avant toute chose, vous devez avoir Junit sur votre projet

Pour créer un test Junit, votre fonction doit commencer par test et devrait être de type void, car elle teste et n'est pas censé interagir après avec d'autre fonction

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class Test{
    @Test
    public void testCountAlphabet() {
        assertEquals(0,testedfunc(input))
    }
}

On utilise ici les Assertions comme moyen de comparaison entre un résultat attendu et le resultat d'une fonction Il y a également un décorateur à mettre devant chaque fonction, celui est indispensable (@Test)

Lambda

Attention

Cette section est facultative à la réussite du cour et assez avancée

Programmation Objet

Cette partie est consacrée plus en particulier à la programmation orientée objet, il est conseillé d'être familier avec les concepts de formatage java pour comprendre la syntaxe

De manière générale, lorsqu'on veut accéder à une méthode depuis l'extérieur d'une classe, on utilise le .

// Dans un fichier Main.java
public class Main {
    public static void main(String[] args){
    }
    public String stringMethod(){
        return new String(" ");
    }
}
class SecondClass {
    public static void main(String[] args){
        Main.main(args);
        Main main = new Main();
        main.stringMethod();
    }
}

Ici il est important de voir la différence entre la méthode d'appel d'une fonction static et non-static. Une méthode static est liée à la classe auquel elles appartient c'est à dire Main, tandis qu'une méthode qui ne l'est pas est liée à l'instance de la classe, on doit d'abord l'initialisée puis appelé la méthode

On peut également définir une classe qui dépend d'une autre classe et a des paramètres

public class Main<E> { // la classe Main dépend d'une classe inconnue , E
    public E e; // variable de classe, de type E et nommée e
    public Main(E e){ // fonction pour initialisé la variable de classe, 
                      //appelée lors de la construction
        this.e = e; // parle de la variable de classe en utilisant `this`
    }
    public static void main(String[] args){
        Main<Integer> main = new Main<>(0);
    }

}

Ici le <E> specicifie la classe de E, on ne sait pas avant qu'on initialise cette classe, tout la classe Main est bassée sur E qui est inconnu

Pour initialiser il faut alors indiqué à la classe principale, la classe E, comme suis : Main<Integer> et lorsque l'on veut créer des variables propres à la classe, on créer une fonction constructeur ici public Main(E e) qui va donner une valeur à la variable de classe e déclarée par public E e;

Interface

Une interface est un moyen abstrait de définir ce qui devrait être dans une classe, on créer une interface avec des méthodes et si on veut créer une classe basée sur cette interface, on doit l'implémenter avec implements et utiliser un décorateur pour signifier que l'on créer une methode de l'implementation que l'on va écraser

Le but est de mettre des fonctions communes dans une interface pour pouvoir les appeler depuis l'interface, comme une superclasse

interface Interface{
    public String InterfaceMethod();
}
public class Main implements Interface{
    public static void main(String[] args){

    }
    @Override
    public String InterfaceMethod() {
        return " ";
    }
}

Attention

Il faut utiliser le décorateur @Override et implémenter la méthode

Certaines méthodes peuvent prendre des arguments en paramètres, il faut alors l'indiquer dans l'interface, cela peut aussi être des constantes et des variables propres à la classe

interface Interface{
    public String InterfaceMethod();
    public String returnString(String message);

}
public class Main implements Interface{
    public static void main(String[] args){

    }
    @Override
    public String InterfaceMethod() {
        return " ";
    }
    @Override
    public String returnString(String message) {
        return null;
    }
}

Dans cet exemple, pour accéder à la méthode InterfaceMethod on doit créer une instance de Main

String result = new Main().InterfaceMethod();

Le saviez-vous?

On peut définir des constantes et des paramètres dans une interface qui peuvent être accedée depuis celle ci

interface Interface{
    public String InterfaceMethod();
    public String returnString(String message);
    int constant = 0; // Use Interface.constant to access 0
}

Afin d'éviter de devoir à chaque implémentation rajouter des méthodes qui ne change pas de l'interface, on peut utiliser la signature default

interface Interface {
    default String defaultMethod() {
        return "default";
    }   
}
public class Main implements Interface {
    public static void main(String[] args) {
        Main main = new Main();
        System.out.println(main.defaultMethod()); // Output: "default"
    }
}

On peut bien sur l'écraser et changer le corp de la méthode

interface Interface {
    default String defaultMethod() {
        return "default";
    }
}

public class Main implements Interface {
    public static void main(String[] args) {
        Main main = new Main();
        System.out.println(main.defaultMethod()); // Output: "overrided"
    }
    @Override
    public String defaultMethod(){
        return "overrided";
    }
}

Le saviez-vous?

Si dans la construction d'une interface on utilise une méthode static, cette méthode n'est pas accessible depuis l'implémentation mais depuis l'interface


interface Interface{
    public static String returnString(String message){
        return message;
    }
}
public class Main implements Interface{
    public static void main(String[] args){}
}
class Test{
    public static void main(String[] args){
        //==========================================
        //  Main.returnString(" "); returns an error
        //==========================================
        Interface.returnString(" ");
    }
}

On peut également implémenter plusieurs interfaces en même temps, ce qui nous force à ajouter plusieurs méthodes

interface Interface{
    public String InterfaceMethod();
}
interface Interface2{
    public String InterfaceMethod2();
}
public class Main implements Interface, Interface2{
    public static void main(String[] args){}
    @Override
    public String InterfaceMethod() {return " ";}

    @Override
    public String InterfaceMethod2() {return null;}
}

Abstraction

Cette partie est en lien avec les interfaces, il est conseillée d'avoir déjà lu la partie sur celles-ci

L'abstraction peut apparaitre sous forme de classe, méthode, le but ici est de définir des méthodes de base et un corp sans devoir le réécrire pour chaque classe qui l'utilise

Il ne faut le confondre avec une interface qui elle veut forcer l'implementation de méthode dans un classe alors qu'ici on veut insérer des méthodes dans une classe

A la manière des interfaces, on applique une classe abstraite sur une autre classe avec extends, bien que ce soit dans l'autre sens, notre classe ajoute du contenu a une classe abstraite

abstract class AbstractClass{
    public String stringMethod(){
        return "method";
    }
}
public class Main extends AbstractClass{
    public static void main(String[] args){}
}

Ceci nous permet d'accéder à stringMethod par

new Main().stringMethod();

Comme dans les interfaces, on peut les écraser avec un décorateur

abstract class AbstractClass{
    public String stringMethod(){
        return "method";
    }
}
public class Main extends AbstractClass{
    public static void main(String[] args){}
    @Override
    public String stringMethod(){
        return "overrided";
    }
}

On peut forcer une extension à utiliser une méthode en la déclarant abstraite, elle doit alors être implémentée

abstract class AbstractClass{
    public abstract void abstractMethod();
}
public class Main extends AbstractClass{
    public static void main(String[] args){}
    @Override
    public void abstractMethod() {}
}

Attention

Dans ce cas il faut utiliser le décorateur @Override et implémenter la méthode

Héritage

L'heritage permet de prendre des variables et éléments d'une classe et de les avoir dans une sous-classe sans devoir les redéfinir

Pour se faire on utilise extends, contrairement à l'abstraction, ici toutes les méthode doivent être définie, et on peut en créer une nouvelle tandis qu'un classe abstraite ne peut être initialisée

class SuperClass{
    public String stringMethod(){
        return "method";
    }
}
public class Main extends SuperClass{
    public static void main(String[] args){
        Main main = new Main();
        System.out.println(main.stringMethod()); // Returns "method"
    }
}

On peut également écraser une méthode une méthode de la classe mère

class SuperClass{
    public String stringMethod(){
        return "method";
    }
}
public class Main extends SuperClass{
    public static void main(String[] args){
        Main main = new Main();
        System.out.println(main.stringMethod()); // Returns "new method"
    }
    public String stringMethod(){
        return "new method";
    }
}

Classe utiles

Cette partie est liée aux structures de données et les méthodes associée à chaque type

Il est conseillé de connaitre la programmation orientée objet avant de continuer

Itérateur

Cette partie est basée sur le concept d'iterateur

Un itérateur est basée sur la classe Collections qui est une classe très utilisée qui contient deux valeurs, une donnée à la classe et un pointer vers une autre Collections qui contient aussi une valeur etc

Pour l'importer, on a

import java.util.Iterator;

Il permet de lire d'itérer sur des classes de Collections, comme des Arraylist, LinkedList

Pour en créer un, il faut définir deux méthodes :

Iterator<Integer> iterator = new Iterator<Integer>() {
    private int value = 0;
    @Override
    public boolean hasNext() {
        return value<10;
    }

    @Override
    public Integer next() {
        if (hasNext()){
            value++;
            return value;
        } else {
            return null;
        }
    }
};

Ici on peut définir les corps des méthodes hasnext() et next() pour avoir ce que l'on désire, dans ce cas ci, en utilisant un appel comme

while (iterator.hasNext()){
    Integer val = iterator.next();
}

ici on itère de 1 à 9

Stream

Les Streams en java sont un moyen de base de 'accéder à des valeurs de Collections, et de performer des operations sur des données et éléments de la structure

Tout d'abord on peut les importers avec

import java.util.stream.Stream;

On peut les créer sur base de valeurs finies

Stream<Integer> stream1 = Stream.of(1,2,3,5);

Stream infini

On peut aussi faire des streams infinis avec .iterate()

UnaryOperator<Integer> operator = new UnaryOperator<Integer>() {
    @Override
    public Integer apply(Integer integer) {
        return integer/2;
    }
};
Stream<Integer> stream2 = Stream.iterate(1,operator);
Stream<Integer> stream3 = Stream.iterate(1,n -> n/2); // same as stream2

Ici le stream va donner comme résultat :

1
0
0
0
...

car on l'a défini comme suit : on commence à 1 pour et est défini comme , comme 1 est un entier,

Il est important de remarqué que ce stream est infini, car on défini en fonction de , un autre exemple avec

Stream<Integer> stream = Stream.iterate(1,n -> n+1);

qui nous donne comme résultat

1
2
3
4
...

Itérateur

La manière utilisée pour accéder aux valeurs est de passé par un itérateur

Stream<Integer> stream = Stream.iterate(1,n -> n+1);
Iterator<Integer> iterator = stream.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

Stream fini

Si l'on désire obtenir un nombre fini d'itération, on peut utiliser la méthode .limit()

Stream<String> stream = Stream.iterate("",n->n+"#").limit(5);

retourne


#
##
###
####

Filtre

En plus de pouvoir choisir une limite, on peut choisir un filtre avec la méthode .filter()

Predicate<String> predicate = new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.length()/2 == ((float) s.length())/2;
    }
};
Stream<String> stream = Stream.iterate("",n->n+"#").limit(5).filter(predicate);

qui accepte un Predicate ou bien une fonction de tri

class Main {
    public static boolean parNumberOfHashs(String s){
        return s.length()/2 == ((float) s.length())/2;
    }
    public static void main(String[] args){
        Stream<String> stream = Stream.iterate("",n->n+"#").limit(5)
                .filter(Main::parNumberOfHashs);
        Iterator<String> iterator = stream.iterator();

        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

retourne en out


##
####

Génération

On peut également généré des Streams avec des Supplier en utilisant la méthode .generate()

Supplier<String> supplier = new Supplier<String>() {
    int index = 0;
    String past = "";
    @Override
    public String get() {
        index ++;
        if (index % 2 ==0) {
            past += "#";
        }
        return past;
    }
};
Stream<String> stream = Stream.generate(supplier).limit(5);

retourne


#
#
##
##

Collections

Un autre moyen d'accéder au stream est d'utiliser la méthode .collect() et de l'utiliser pour print le stream ou le transformer en array comme ici

Stream<Integer> stream = Stream.iterate(1,n -> n+1).limit(10);
List<Integer> list =  stream.collect(Collectors.toCollection(ArrayList::new));
System.out.println(list); //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Ici on a utilisé la classe Collectors pour stocker toutes les valeurs sur lesquels le stream itère

Attention

Si la méthode .iterator() est appelée, on ne peut pas utiliser .collect() après et vice-versa, car ces méthodes consomment le stream