null и классы

Последнее обновление: 05.04.2023

nullable-типы

Как и все остальные встроенные типы, классы по умолчанию представляют тип, которые не допускает значение null. Однако иногда бывает необходимо, чтобы объект класса мог хранить значение null. Например, один из сценариев - мы отправляем запрос к сетевому ресурсу и в ответ мы можем получить какие-то конкретные данные, либо null, если запрошенные данные на ресурсе не найдены. Для определения объекта класса, который может хранить значение null, можно использовать nullable-класс, то есть добавить к определению типа оператор ?:

void main (){
     
	Person? sam;
	print(sam);  // null	- sam допускает значение null
	Person tom;
	// print(tom);	// ! Ошибка, tom не может принимать null
} 

В случае с вызовом print(tom), как и в случае с переменными других типов, которые не могут принимать значение null, необходимо присвоить переменной начальное значение перед ее использованием.

В тоже время при использовании nullable-классов мы можем попасть в следующую ситуацию. Как писалось выше, для обращения к полям и методам объекта используется оператор точка (.), после которой указывается имя поля/метода. Однако, как мы знаем, если переменным nullable-классов не присвоено значение, то они по умолчанию имеют значение null. Это необходимо учитывать. Например, обратимся к полям объекта через переменную, которая не инициализирована:

class Person{ 

    String name = "";
	int age = 0; 

    void display() => print("Name: $name \tAge: $age");
}
void main (){
     
	Person? tom;
	tom.age = 38;   // Ошибка, tom = null
} 

При выполнении строки кода tom.age = 38 программа сгенерирует ошибку и завершит свое выполнение. Потому что мы не создали объект класса Person c помощью конструктора. Поэтому переменная tom имеет значение null, то есть фактически ничего, объекта нет. Соответственно обратиться к поля несуществующего объекта мы не можем. Чтобы избежать подобной проблемы для обращения к полям мы можем проверять переменную на null:

if(tom != null){	// если tom не равна null
	tom.age = 38;	// то присваиваем полю age число
}

Но для упрощения проверки Dart позволяет использовать другой оператор - ?. (null-aware access operator):

class Person{ 

    String name = "";
	int age = 0; 
    void display() => print("Name: $name \tAge: $age");
}
void main (){
     
    Person? tom;
	tom?.age =  38;     // не выполняется
	tom?.display();     // не выполняется
    
    tom = Person();     // объект создан
    tom.age =  38;     // выполняется
    tom.name = "Tom";   // выполняется
	tom.display();     // выполняется
} 

Оператор ?. проверят значение переменной, если переменная не равна null, то происходит обращение к ее полям и методам. Если же она равна null, то обращение к переменной игнорируется. Например, в следующем случае:

Person? tom;
tom?.age =  38;

переменная tom равна null, поэтому строка кода tom?.age = 38 не окажет никакого влияния - значение age не изменится, а ошибки не будет, программа продолжит выполнение.

Обратите внимание, что после создания объекта оператор ?. не примеряется:

tom = Person();     // объект создан
tom.age =  38;     // выполняется

Дело в том, что анализатор Dart может автоматически вывести, что здесь не требуется проверка на null, так как объект Person создан. Данная ситуация называется type promotion или продвижение типа - Dart "продвигает" тип Person? в Person.

Однако не во всех ситуациях Dart может вывести, что объект не null:

void printPerson(Person? person){
    person?.display();
}

class Person{ 

     String name = "";
	int age = 0;
    void display() => print("Name: $name \tAge: $age");
}
void main (){
	
	Person? tom;
    printPerson(tom);  
    tom = Person();
    printPerson(tom);  
} 

В функции printPerson получаем в качестве параметра значение Person? и вызываем у него метод display. Поскольку для анализатора Dart неизвестно, какое же значение будет передано в функцию - null или Person, то при вызове метода display у Person применяется оператор ?.:

person?.display();

Если person будет равен null, то вызова метода просто не будет, и программа продолжит свою работу. Если person не равен null, то будет вызываться метод display.

?..

Особую форму этого оператора представляет оператор ?.. (null-aware cascade operator), который позволяет использовать каскадную нотацию, если переменная не равна null.

void setDefaultValues(Person? person){
    person
        ?..name = "Tom"
		..age = 38
		..display();
}

class Person{ 

    String name = "";
	int age = 0;
    void display() => print("Name: $name \tAge: $age");
}
void main (){
	
	Person? tom;
    setDefaultValues(tom);  

    tom = Person();
    setDefaultValues(tom);  
} 

Здесь функция setDefaultValues принимает некоторое значение Person? и, если это значение не равно null, с помощью каскадной нотации устанавливает для его полей некоторые значения и вызывает метод display.

Оператор !

В некоторых ситуациях переменная может быь определена как переменная nullable-типа, тем не менее при этом могут быть исключены ситуации, что данная переменная будет хранить null. И если мы точно уверены, что эта переменная в процессе работы программы не получит значение null, то в этом случае мы можем принимать оператор ! (null assertion operator), который ставится после названия объекта.

Например, возьмем следующий код:

class Password{ 

    String? text; // текст пароля

    String get(){
        if(text == null){
            text = "qwerty";
        }
        return text;   // ! Ошибка
    }
}
void main (){
	
	Password myPassword = Password();
    print(myPassword.get());  
} 

Здесь в классе Password поле text, которое хранит текст пароля, определено как значение String?, то есть по умолчанию оно имеет значение null. В методе get(), если text равно null, определяем для него некоторое начальное значение. Причем логика метода get() такова, что метод в любом случае возвратить значение String - строку, а не null. Ведь даже если text равно null, мы все равно присваиваем ему строку. И кажется, что пробелем никаких быть не должно. Но при выполнении программы мы столкнемся с ошибкой:

main.dart:9:16: Error: A value of type 'String?' can't be returned from a function with return type 'String' because 'String?' is nullable and 'String' isn't.
        return text;
               ^

Здесь мы уверены, что метод в любом случае возвратит строку String, поэтому мы можем после названия объекта поставить оператор !:

return text!;

Полный код:

class Password{ 

    String? text;

    String get(){
        if(text == null){
            text = "qwerty";
        }
        return text!;   // метод возвращает String
    }
}
void main (){
	
	Password myPassword = Password();
    print(myPassword.get());  
} 

Но следует еще раз отметить, что данный оператор следует использовать, когда у нас есть стопроцентная уверенность, что значение НЕ равно null. Если же такой уверенности нет, то лучше не злоупотреблять данным оператором и не использовать его.

Оператор !.

Дополнительный оператор !. позволяет обратиться к полям или методам объекта, который представляет nullable-тип, но не равен null. Например:

class Password{ 

    String? text;

    bool isValid(){
        if(text == null){
            text = "";
        }
        return text.length > 6;   // ! Ошибка
    }
}
void main (){
	
	Password myPassword = Password();
    bool isValid = myPassword.isValid();
    print("Is Valid: $isValid");
} 

Здесь та же ситуация, что и в предыдущем примере про оператор !. В методе isValid() мы проверяем длину текста пароля, используя поле length класса String, и возвращаем true, если длина пароля больше 6 символов (то есть пароль действителен). Причем мы уверены, что текст пароля - поле text не равно null, так как мы также устанавливаем для него значение, если о вдруг равен null. Но компилятор в этом не уверен. И чтобы компилятор тоже был уверен, для доступа к полям и методам объекта, который может быть равен null, используем оператор !.:

return text!.length > 6;

Полный код:

class Password{ 

    String? text;

    bool isValid(){
        if(text == null){
            text = "";
        }
        return text!.length > 6;   // больше 6 символов
    }
}
void main (){
	
	Password myPassword = Password();
    bool isValid = myPassword.isValid(); // false
    print("Is Valid: $isValid");  // Is Valid: false
} 
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850