« poprzedni punkt  następny punkt »

3. Operatory i wyrażenia logiczne

Do zapisywania złożonych warunków służą (m.in) warunkowe operatory logiczne:

  • jednoargumentowy operator negacji logicznej oznaczany przez ! ("nie"),
  • dwuargumentowy operator koniunkcji oznaczany przez && ("i"),
  • dwuargumentowy operator alternatywy oznaczany przez || ("albo").

Przypuścmy, że chcemy sprawdzić czy zachodzi warunek: wielkość n znajduje się w przedziale [1, 10] - to znaczy, czy n jest większe lub równe 1 i jednocześnie mniejsze lub rowne 10.
Jak już wiemy, zapis: if (1 <= n <= 10) będzie błędny (bo operatory relacyjne są stosowane tylko wobec typów numerycznych, a wyrażenie 1 <= n ma wynik typu boolean).
Nie znając operatorów logicznych i wiedząc, że w instrukcji if możemy umieścić inną instrukcję if, warunek moglibyśmy sprawdzić za pomocą takiego zapisu:
if (n >= 1)
if (n <= 10) ... ;

Nie jest to jednak ani elegancka, ani zbyt czytelna forma.
Na szczęście mamy operator koniunkcji logicznej,
Chodzi nam o to czy równocześnie spelnione są dwa warunki

n >= 1 I n <= 10.

W Javie zapisujemy to jako:

n >= 1 && n <= 10

i stosujemy np. w instrukcji if:

if (n >= 1 && n <= 10) ...;

Podobnie możemy chcieć sprawdzić czy wartość n znajduje się poza przedziałem [1, 10]. Aby tak było n musi być mniejsze od 1 lub większe od 10.
Do zapisu takiego warunku stosuje się operator logicznej alternatywy ||:

if (n < 1 || n > 10) ..

Trzeci operator logiczny - negacji - służy do zaprzeczania warunkom.
Na przykład, jeżeli chcemy podjąc jakieś działania, jeżeli łańcuch znakowy txt nie jest napisem "Ala", to piszemy:

if (!txt.equals("Ala")) ... ;

Argumentami operatorów logicznych mogą być tylko wyrażenia typu boolean, zaś rezultatem jest wartość typu boolean, która może być równa true lub false, zgodnie z następującą tablicą:

Warunkowe operatory logiczne
a
b
a && b
a || b
!a
true
true
true
true
false
false
true
false
true
true
true
false
false
true

false
false
false
false


Operator && daje wartość true tylko wtedy, gdy oba jego argumenty są wartościami true, a wartość false zawsze, gdy którykolwiek z argumentów jest false.
Operator || daje wartość true zawsze, gdy którykolwiek z jego argumentów jest true i wartość false tylko wtedy, gdy oba argumenty są false

Operatory logiczne mają niższy priorytet od operatorów relacyjnych i równości-nierówności, zatem bez użycia dodatkowych nawiasów możemy pisać np:

if ( a == 1 && b > 3) c = a + b;

uzyskując efekt, o który nam chodzi (tu: wyliczenie c, gdy a równe 1 i b > 3).

Uwaga: operator negacji logicznej ma wyższy priorytet niż inne operatory logiczne, wobec tego należy używać nawiasów dla zapewnienia właściwej kolejności obliczania wartości wyrażeń

Sprawne posługiwanie się warunkami logicznymi wymaga podstawowej wiedzy z zakresu logiki matematycznej. Warto więc sięgnąć po jakiś prosty, początkowy opis rachunku zdań logicznych i przeczytać z niego kilka pierwszych stron.
Nota bene, zadziwiająco często mylone są w programowaniu operacje koniunkcji i alternatywy logicznej. Zawsze więc - wybierając operację do zastosowania - zastanówmy się troszkę dłużej nad tym czy ma to być koniukcja czy alernatywa

Np. jeżeli chcemy wykonać jakieś czynności jeżeli nie zachodzi warunek: "łańcuch znakowy nie jest napisem "Ala" i - jednocześnie - zmienna lata ma wartość większą od 1", to nie wolno nam napisać:

if (! txt.equals("Ala") && lata > 1) ...

bo to oznaczałoby konieczność równoczesnego spełnienia dwóch warunków:
- napis txt nie jest ala,
- i lata są większe od 1
(a przecież nie o to nam chodziło)

Piszemy za to:

if (!( txt.equals("Ala") && lata > 1)) ...

Użycie dodatkowych nawiasów zmienia kolejność opracowania wyrażenia i uzyskujemy pożądany efekt.

Omawiane dotąd operatory nazywają się warunkowymi operatorami logicznymi, bowiem
obliczanie wartości wyrażeń logicznych, konstruowanych za ich pomocą ma bardzo ważną właściwość: obliczane są one od lewej do prawej (wiązania operatorów logicznych - oprócz operatora negacji - są lewostronne), ale proces obliczeń kończy się już w momencie gdy tylko wiadomo jaki będzie rezultat całego wyrażenia.
Oznacza to, że wyrażenie nie musi być wcale opracowywane do końca; wystarczy, że jego część jednoznacznie wskazuje wynik. Na przykład w wyrażeniu:

a && (b > 0 || c == 1)

jeśli a jest rowne false, to wyrażenie w nawiasach nie będzie obliczone, bowiem wynik całego wyrażenia nie zależy już od tego co jest w nawiasach (przy a = false zawsze będzie false).
Ma to ogromnie ważne konsekwencje, bowiem jeśli oprzemy logike programu na efektach ubocznych generowanych przy obliczaniu wyrażeń, które - właśnie ze względu na tę właściwość operatorów logicznych - mogą w ogóle nie podlegać opracowaniu, to może nas spotkać bardzo przykra niespodzianka.

ProblemZobaczmy to na przykładzie poniższego programu. Programista, który go napisał chciał osiagnąć następujący efekt: zapytać w dialogach wejściowych o nazwisko i imię użytkownika, jeśli podano obie informacje - wyprowadzić połączone nazwisko i imię, jeśli zaś zabrakło ktorejś z nich - wyprowadzić napis "Niepełna informacja"; następnie pokazać dokładnie co jest nazwiskiem (może być null) a co imieniem (też może być null, jeśli nie wprowadzone). Pokusa zwięzłego napisania kodu sprawiła, że nasz programista sięgnął po połączenie warunków za pomocą operatora &&: jeżeli wprowadzono nazwisko i wprowadzono imię to txt = nazwisko + imie.


import javax.swing.*;

public class EfUb {

  public static void main(String[] args) {

    String nazwisko;
    String imie = null;
    String txt;

    if ((nazwisko = JOptionPane.showInputDialog("Podaj nazwisko")) != null
         && (imie = JOptionPane.showInputDialog("Podaj imie")) != null
       )
       txt = nazwisko + " " + imie;
    else txt = "Niepełna informacja";
    System.out.println(txt);
    System.out.println("Imie :" + imie);
    System.out.println("Nazwisko :" + nazwisko);
    System.exit(0);
  }

}

Jednak raczej wbrew intencjom programisty w tym programie może nie dojść do zapytania o imię (bowiem wartość pierwszego składnika wyrażenia połączonego koniunkcją && może być false - jeśli przy pytaniu o nazwisko użytkownik anulował dialog).

W Javie są również bezwarunkowe operatory logiczne (zwane czasem po prostu operatorami logicznymi):

  • & koniunkcja
  • | alternatywa
  • ^ "wyłączające albo"
  • ~ negacja

Bezwarunkowe operatory logicznej koniunkcji (&) i alternatywy ( |) mają te same właściwości co operatory && i || z jednym wyjątkiem: oba wyrażenia-argumenty operatorów są zawsze opracowywane (dlatego mówimy, że operatory są bezwarunkowe).

Dodatkowo, wśród logicznych operatorów bezwarunkowych, zdefiniowano operator "wykluczające albo" (znak ^), który daje wynik true gdy wartości obu argumentów operatora są różne (czyli true i false lub false i true) oraz false w każdym innym przypadku.

Operatory logiczne (bezwarunkowe) są stosowane wyłącznie wobec argumentow typu boolean.
Niestety, te same symbole operatorów ( &, |, ^, ~) sa również używane w operatorach bitowych, stosowanych wobec liczb całkowitych (i umożliwiających operowanie na bitach liczb całkowitych), co powoduje trochę zamieszania.
Problemem są także błędy: np. często początkujący (i nie tylko) programiści zamiast && piszą &, a zamiast || - |. Kompilator nie wykryje takich błędów, a efekt opracowywania takich wyrażeń logicznych może być inny od spodziewanego przez programistę.

Generalnie więc:
nie należy mieszać w wyrażeniach użycia operatorów warunkowych i bezwarunkowych (ze względu na inny sposób działania oraz inne priorytety (por. tablicę priorytetów operatorów)).

Dobrą regułą "na początek" jest:
  • stosować wyłącznie operatory warunkowe,
  • a przy tym nie zakładać, że wyrażenia logiczne będą opracowywane w sposób warunkowy (!)


« poprzedni punkt  następny punkt »