Передавання 'цього' у прийнятій практиці виклику методу в Java


93

Це хороша / погана / прийнятна практика передавати поточний об'єкт у виклику методу. А саме:

public class Bar{
    public Bar(){}

    public void foo(Baz baz){
        //  modify some values of baz
    }
}

public class Baz{
    //constructor omitted

    public void method(){
        Bar bar = new Bar();
        bar.foo(this);
    }
}

Зокрема, чи bar.foo(this)прийнятна лінія ?


60
Чому це не прийнятно? Це часто.
Denys Séguret

3
Отже ... це 8 так :) (І так, я постійно його
оновлюю

2
нестатичний анонімний клас автоматично передає це посилання супер класу, тому це буде прийнятно. єдиною обережністю буде обережність щодо циркулярних посилань.
Mehul Rathod

5
Однак є одне застереження: ви не повинні передавати це в конструктор, оскільки це призведе до викриття вашого об'єкта у несумісному стані. Люди зазвичай роблять це, коли створюють зворотні виклики (наприклад, ActionListener) як анонімні внутрішні класи, а потім передають це іншому об’єкту.
Tamas Rev

3
@dystroy, хоча я погоджуюсь, що це прийнятно, маючи на увазі, що це прийнятно, оскільки це загальноприйнята, це насправді погана логіка. Виконання чогось, тому що його загальне може призвести до численних неприємностей
Керрі Кендалл

Відповіді:


155

Немає причин не використовувати його, thisце поточний екземпляр, і він цілком законний для використання. Насправді часто немає чистого способу пропустити це.

Тож використовуйте його.

Оскільки важко переконатись, що це прийнятно без прикладу (негативну відповідь на таке запитання завжди легше аргументувати), я щойно відкрив один із найпоширеніших java.langкласів String, і, звичайно, знайшов випадки такого використання, наприклад

1084        // Argument is a String
1085        if (cs.equals(this))
1086            return true;

Шукайте (thisу великих "прийнятих" проектах, ви не зможете його знайти.


6
Ідеальна відповідь.
maxf130

15
-1 тому, що не згадується той факт, що двонаправлені відносини класу є складнішими, ніж однонаправлені відносини. Життєво важливо забезпечити ясне використання програмного забезпечення. У наведеному конкретному прикладі було б більш розумно перемістити метод foo до класу Baz, щоб уникнути двонаправленого посилання між двома класами та звести поведінку та дані разом.
JW.

35
-1 до відповіді, оскільки ви знайшли у питанні додаткову незначну деталь, щодо якої ви можете коментувати? Справді?
Denys Séguret

13
@dystroy Так, я не погоджуюся з вашою початковою фразою: "Немає причин не використовувати її".
JW.

4
На практиці в реальному коді (на відміну від спрощеного прикладу OP) передача thisне означає, що ви додаєте двонаправлене посилання, наприклад, через успадкування та інтерфейси.
Denys Séguret

165

У цьому немає нічого поганого. Що НЕ є хорошою практикою, це робити те саме всередині конструкторів, оскільки ви б давали посилання на ще не повністю ініціалізований об’єкт.

Тут є подібний пост: Java просочує це в конструкторі, де вони дають пояснення, чому останній є поганою практикою.


18
+1: добре вказати на небезпеку, посилаючись на "це" у конструкторах.
Вірсавія,

Це не обов'язково погана практика. Наприклад, Carконструктор може створювати Wheelвипадках, CarНЕ Wheelбуде повністю инициализирован, в той час як Wheelбез відповідного Carбуде також повністю инициализирован. У цьому випадку в конструкторі автомобіля може бути прийнятним перейти thisдо конструктора колеса. Іншою альтернативою було б зробити так, щоб і Автомобіль, і Колесо мали приватний конструктор і використовували фабричну функцію, яка створює Автомобіль, Колесо та встановлює Колесо на Автомобіль; але це повинен бути статичний метод на автомобілі чи статичний метод на колесі?
Lie Ryan

Очевидно, ви повинні створити такий, CarFactoryWheelInstallerProxyякий встановлює колеса для вас.
Кевін

6
@LieRyan Wheelповністю підпорядкований Car, і ІМО про це взагалі не повинен знати Car.
Izkata

3
ЄДИНА погана річ у використанні thisзсередини конструктора полягає в тому, що якщо thisвін передається у метод або контекст, з якого ще не повністю виконане посилання на об’єкт публікується ненадійним або невідомим клієнтам (або коду клієнта, який передбачає, що він має вигляд на повністю побудований об’єкт). На thisмій погляд, перехід від конструктора до методу private-package, який виконує загальну ініціалізацію, є не тільки прийнятним, але і бажаним.
scottb

42

Так , але ви повинні бути обережними щодо двох речей

  1. Передача цього, коли об’єкт ще не побудований (тобто в його конструкторі)
  2. Переходячи це долгоживущий об'єкт, який буде тримати посилання живий і запобігти цей об'єкт від збирача сміття.

1
Зверніть увагу, що конструктор у Java насправді не є конструктором, можливо, доцільніше називати конструктор Java "ініціалізатором". У конструкторі Java об'єкту фактично виділена пам'ять, цей об'єкт насправді вже існував / побудований в конструкторі.
Lie Ryan

1
Неправда, об’єкт може мати деякі змінні екземпляра, які ще не ініціалізовані, так що об’єкт ще не повністю функціонує. Отже, у конструкторі ви можете передати це другому об'єкту, який може викликати метод до об'єкта, який ще не ініціалізував усі свої змінні екземпляра.
Стефанос Т.

однак, поки другий об'єкт знає, що переданий об'єкт не ініціалізований, і розглядає його як непрозорий об'єкт або лише викликає методи, які були оголошені безпечними в такому стані, це не спричинить жодних проблем для передачі thisйому. Зробити це було б неможливо, якби об’єкт не був виділений.
Lie Ryan


5

це означає поточний об'єкт. Те, що ви робите, є симетрично правильним, але я не бачу в цьому потреби, якщо ви викликаєте метод у тому ж класі.


2
У прикладі коду Baz.method () є методом екземпляра, який викликає Bar.foo () із екземпляром Baz як параметром. Отже, OP не викликає метод у тому самому класі.
CVn

@ MichaelKjörling Я думаю, що Juned каже, що, перемістивши метод foo () у клас Baz, не буде потреби переходити thisміж двома класами. Отже, немає потреби додавати зайву складність.
JW.

4

Поганою практикою є передача поточного об'єкта у виклик методу, якщо є менш складні альтернативи для досягнення тієї ж поведінки.

За визначенням, двонаправлена ​​асоціація створюється, як тільки thisпередається від одного об'єкта до іншого.

Процитувавши рефакторинг, Мартін Фаулер:

Змінити двонаправлену асоціацію на односпрямовану (200)

Двонаправлені асоціації корисні, але вони мають свою ціну. Ціна - це додаткова складність підтримки двосторонніх посилань та забезпечення належного створення та видалення об’єктів. Двонаправлені асоціації не є природними для багатьох програмістів, тому вони часто є джерелом помилок

...

Ви повинні використовувати двонаправлені асоціації, коли це потрібно, але не тоді, коли цього не потрібно. Як тільки ви побачите, що двонаправлена ​​асоціація більше не тягне свою вагу, скиньте непотрібний кінець.

Отже, теоретично, ми повинні чути дзвіночки тривоги, коли виявляємо, що нам потрібно проїхати, thisі по-справжньому намагатись думати про інші способи вирішення сучасної проблеми. Звичайно, бувають випадки, коли, в крайньому випадку, має сенс це зробити.

Крім того, часто доводиться тимчасово пошкоджувати свій дизайн, роблячи "погані практики", під час тривалої рефакторингу коду для загального поліпшення. (Крок назад, два кроки вперед).

На практиці я знайшов мій код покращився масивно , уникаючи двонаправлені зв'язку , як від чуми.


Ви плутаєте спрощений приклад з необхідністю встановлення двостороннього зв'язку. Передача цього параметра як параметра, як повинно бути зрозуміло з багатьма прикладами вихідного коду java.lang (наприклад, того, який ви бачили у моїй відповіді), не означає, що ви додаєте двонаправлену залежність. Ця відповідь, на мій погляд, мала бути коментарем.
Denys Séguret

@dystroy Дякуємо, що додали коментар, щоб пояснити, чому ви проголосували. Це завжди добре знати. Я зміню свою відповідь, щоб пояснити, що за визначенням двонаправлена ​​асоціація створюється одразу після thisїї прийняття.
JW.

1
"за визначенням, двонаправлена ​​асоціація створюється, як тільки це прийнято" . Це дає зрозуміти, де ви не розумієте. Подивіться на приклад, який я наводжу. Немає двонаправленого зв’язку, оскільки тип аргументу в equalsє Object. Це дуже часто: метод отримання визначає свій аргумент як більш загальний клас або як інтерфейс. Однією з причин використання такого шаблону в Java є уникнення небажаних залежностей. Перш ніж іти довше, я пропоную вам поглянути на багато випадків передачі thisяк аргументу в поважних бібліотеках Java.
Denys Séguret

Давайте просто погодимось не погодитися. Моє життя стало набагато простішим, оскільки я уникав передавати thisсвій код, де це було можливо. Я рекомендую іншим зробити це.
JW.

2
@JW: міркування, наведені у цій відповіді, не мають значення. Завжди погана ідея робити X, якщо є щось інше, що є простішим, для будь-якого значення X.
Lie Ryan

4

Так. Ви можете його використовувати. Це просто поширене в програмуванні передавання. thisАле є плюси і мінуси щодо його використання. Однак це не небезпечно.


Побічних ефектів досить багато. Це додає складності.
JW.

Якщо існує така кількість побічних ефектів, ми не можемо знайти жодного подібного доказу у нашому прикладі вихідного коду Java .see @destroys із вихідного коду.
Suresh Atta

2

Просто додамо ще один приклад, коли передача thisправильна і відповідає хорошому дизайну: Шаблон відвідувача . У шаблоні дизайну відвідувачів метод, accept(Visitor v)як правило, реалізований таким чином, що він просто викликає v.visit(this).


1

Прийнятний

Фрагмент із документів Oracle JAVA:

У методі екземпляра або конструкторі це посилання на поточний об'єкт - об'єкт, метод чи конструктор якого викликається. За допомогою цього ви можете звернутися до будь-якого члена поточного об’єкта з допомогою методу екземпляра або конструктора.

Використовуючи це з полем

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


2
"Ви можете посилатися на будь-якого члена поточного об'єкта " - це, схоже, не відповідає на питання "чи прийнятно передавати його thisяк параметр? ".
CVn

2
Це означає, що ви можете зробити this.some_variableпосилання на змінну класу, а не на локальну змінну. Це не має нічого спільного з передачею thisяк параметром.
Jose Salvatierra

0

Все в Java передається за значенням. Але об'єкти НІКОЛИ не передаються методу!
Коли java передає об'єкт методу, він спочатку робить копію посилання на об'єкт, а не копію самого об'єкта. Отже, це ефективно застосовуваний метод у Java. І найчастіше використовуване використання.


9
Це виглядає з теми.
Denys Séguret

4
"Усе в Java передається за значенням." - це дуже оманливий початковий коментар. Насправді всі об'єкти передаються за посиланням, а всі примітивні типи передаються за значенням. У вас НІКОЛИ немає thisпосилання на примітивний тип, і тому я думаю, що ваші "додаткові відомості" вносять плутанину.
Стюарт

1
@Stewart: Він відразу дає зрозуміти, що не має на увазі копіювання цілих об’єктів.
LarsH

10
@Stewart ні, об'єкти не передаються за посиланням, а посилання на об'єкти передаються за значенням. Це важлива відмінність - передача за посиланням означала б, що якщо у мене є локальна змінна, яка посилається на об’єкт, і передає цю змінну іншому методу, метод зможе змінити, на який об’єкт посилається моя змінна, і це точно не щось ви можете зробити на Java. Метод може мутувати стан об'єкта за допомогою власної копії посилання, але він не може змінити мою копію посилання, щоб вказувати на щось інше.
Ian Roberts

2
@Stewart yoda.arachsys.com/csharp/parameters.html - хороша стаття, яка пояснює різницю між передачею за допомогою посилання та передачею посилань за значенням у контексті C #, який підтримує обидва.
Ian Roberts
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.