Что на самом деле делает ключевое слово «новое» в Java и следует ли избегать создания новых объектов?

Я зарегистрировался несколько минут назад, хотя я активно пользуюсь этим сайтом с тех пор, как занялся компьютерным программированием, чему я научился сам и считаю своим маленьким хобби.

Я искал похожие вопросы, но на самом деле я не мог найти ответ, который искал. Теперь, зная, что в Java (это язык, с которого мне предложили начать) считается хорошей практикой программирования объявлять и создавать экземпляры переменных по мере необходимости, рассмотрите следующие строки:

class MyClass {
    void myMethod() {
        AnotherClass myObject = new AnotherClass();
        myObject.doStuff();
    }
}

Теперь предположим, что я вызываю myMethod(), скажем, 10 раз во время выполнения моей программы, как это работает? Каждый раз создается новый объект? Каждый раз перераспределяется ли переменная myObject? Пропускает ли компилятор подобный код, поскольку видит, что объект уже создан и переменная myObject уже присвоена такому объекту? В двух словах: должен ли я писать такой код только в том случае, если я планирую вызывать этот метод только один раз? Я знаю... стыдно за то, что задаю такой глупый вопрос, но, пожалуйста, дайте мне шанс! Заранее спасибо!

--------------------------- отредактировал ---------------------- -------

Итак, теперь я должен редактировать этот пост после того, как получу новые ответы? Кстати... черт возьми, это было быстро, большое спасибо! И вау, это меня сильно смутило, я думаю, это из-за того, что я так себя учил... В любом случае, не бесполезно ли каждый раз создавать объект new AnotherClass для переменной myObject? Я имею в виду, если я хочу использовать переменную myObject во всей своей программе, не должен ли я объявить ее раз и навсегда? может быть, в другом методе, который я буду вызывать только один раз? Потому что, насколько я понимаю, каждый раз, когда я вызываю myMethod(), создается новый объект, тем самым переопределяя собственные свойства myObject, также известные как переменные, или я просто говорю ерунду?

--------------------------- отредактировал ---------------------- -------

Мои сомнения возникли после прочтения этого кода с какого-то веб-сайта, который я сейчас не могу вспомнить:

    public class DataBase {

    private static String buf, retString = "\n";
    private static File file = new File("test.txt");

    public static void readText(JTextArea area) {   
        try {
            FileReader fr = new FileReader (file);
            BufferedReader br = new BufferedReader(fr);
            while ((buf = br.readLine()) != null) {
                area.append(buf); 
                area.append(retString);
            }
            br.close(); 
            fr.close();
        }
        catch (IOException e) {
            System.out.println("Exception: " + e);
        }
    }

    public static void writeText(JTextArea area) {
        try {
            FileWriter fw = new FileWriter (file);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(area.getText());
            bw.close(); 
            fw.close();
        }
        catch (IOException e) {
            System.out.println("Exception: " + e);
        }
    }
}

Я имею в виду, почему бы не объявить FileWriter, FileReader, BufferedReader и BufferedWriter вверху класса, как это было сделано для других переменных? и почему бы не инициализировать их, может быть, в конструкторе? Зачем делать это каждый раз при вызове метода, а не использовать, возможно, одну и ту же переменную экземпляра?


person zeme    schedule 11.08.2011    source источник
comment
Глупых вопросов не бывает...   -  person Tom    schedule 11.08.2011
comment
Итак, теперь я должен редактировать этот пост после того, как получу новые ответы? Не всегда, только если есть обновление исходного вопроса или что-то, что остается неясным.   -  person Tom    schedule 11.08.2011
comment
Я спросил это, потому что я не могу идти в ногу! Вы довольно часто редактируете свои ответы, что, безусловно, здорово, просто я не ожидал такого отличного ответа и не знаю, к какому сообщению прокомментировать или оставить отзыв!   -  person zeme    schedule 11.08.2011
comment
если вы хотите использовать один и тот же экземпляр каждый раз, вам нужно где-то его сохранить. обычно для этого и нужны члены экземпляра.   -  person andrew cooke    schedule 11.08.2011


Ответы (6)


Да, если вы вызвали myMethod() 10 раз, это создаст 10 уникальных и отдельных объектов.

Ключевое слово new делает именно то, что написано на банке, оно создает совершенно новый объект, независимо от того, существует ли он уже. Он создает новый объект и помещает ссылку на этот объект в переданную ему переменную, перезаписывая любое предыдущее значение (объект), хранящееся в переменной.

Каждый раз перераспределяется ли переменная myObject?

Опять же, да, он будет перераспределяться с новым объектом каждый раз, когда вызывается метод. Интересное замечание по этому поводу заключается в том, что переменная не будет «на самом деле» перераспределена, поскольку вы определяете переменную в самом теле метода, поэтому каждый раз, когда метод завершается, он будет удалять переменные, которые были определены в его области видимости. . Так что на самом деле он создает 10 отдельных переменных и назначает 10 отдельных объектов, хотя, как я уже сказал, остальные должны были быть удалены автоматически, чтобы не использовать дополнительную память.

В двух словах: должен ли я писать такой код только в том случае, если я планирую вызывать этот метод только один раз?

Как я уже сказал, в приведенном выше примере каждый объект будет уничтожен в конце выполнения метода (при условии, что вы не присвоили ссылку на объект переменной вне области действия метода), поэтому в вашем примере вы могли бы с радостью вызвать метод сколько угодно раз, но каждый раз никак не будет связан с предыдущими звонками.

Я понимаю, что моя манера письма может сбивать с толку, поэтому, если вы хотите, чтобы я что-то прояснил, просто спросите.

Обновлен ответ, чтобы отразить отредактированный вопрос

'почему бы не объявить FileWriter, FileReader, BufferedReader и BufferedWriter вверху класса, как это было сделано для других переменных?'

Хорошо, я полагаю, вы понимаете, что переменные на самом деле не называются FileWriter, FileReader, BufferedReader и BufferedWriter, а это тип переменной. Их имена fw, fr, br и bw. Если вы не понимаете, что я имею в виду, просто спросите. С этого момента я буду ссылаться на переменные по именам, которые вы сделали, чтобы облегчить чтение, в конце концов, fw в любом случае просто означает FileWriter, поэтому не должно быть слишком большой путаницы.

Ключ к этому вопросу скрыт в именах самих переменных. Обратите внимание, как они заканчиваются на Reader или Writer, это может дать нам тонкую подсказку об их использовании. Ясно, что FileWriter и BufferedWriter каким-то образом связаны с выводом. Просматривая код, мы видим, что наши подозрения были верны и что эти переменные не появляются нигде, кроме как в методе writeText(JTextArea area). Поэтому, если переменная больше нигде в коде не используется, было бы логично определить и инициализировать их в методе, в котором они используются, это не только облегчает чтение кода, потому что мы тогда «знаем» эти переменные. связаны только с этим методом, но также имеют то преимущество, что эти переменные удаляются в конце выполнения метода, таким образом, не оставляя переменных, которые использовались очень недолго. По этим правилам мы можем сказать, что то же самое верно для FileReader и BufferedReader.

Обратите внимание на этот пример с областью видимости переменных. (Посмотрите на комментарии, которые я добавил к коду)

public class DataBase {

private static String buf, retString = "\n"; // buf & retString - created
private static File file = new File("test.txt"); // file - created

public static void readText(JTextArea area) {   
    try {
        FileReader fr = new FileReader (file); // fr (FileReader) - created
        BufferedReader br = new BufferedReader(fr); // br (BufferedReader) - created
        while ((buf = br.readLine()) != null) {
            area.append(buf); 
            area.append(retString);
        }
        br.close();
        fr.close();
    } // fr (FileReader & br (BufferedReader) - destroyed
    catch (IOException e) {
        System.out.println("Exception: " + e);
    }
}

public static void writeText(JTextArea area) {
    try {
        FileWriter fw = new FileWriter (file); // fw (FileWriter) - created
        BufferedWriter bw = new BufferedWriter(fw); // bw (BufferedWriter) - created
        bw.write(area.getText());
        bw.close(); 
        fw.close();
    } // fw & bw - destroyed
    catch (IOException e) {
        System.out.println("Exception: " + e);
    }
}
} // buf, retString and file - Still exist as long as the object exists

Из этого примера становится более понятно, почему переменные определяются в методах, а не как переменные экземпляра и инициализируются в конструкторе. Это позволяет сделать код намного чище, а также более читабельным.

Зачем делать это каждый раз при вызове метода, а не использовать, возможно, одну и ту же переменную экземпляра?

Ну, этот вопрос связан с типами переменных. Мы не могли повторно использовать одну переменную для всей информации, поскольку типы должны были быть разными.

Если взять все переменные из кода

private static String buf, retString = "\n"; // valid
private static File file = new File("test.txt"); // valid

FileReader fr = new FileReader (file); // valid
BufferedReader br = new BufferedReader(fr); // valid
FileWriter fw = new FileWriter (file); // valid
BufferedWriter bw = new BufferedWriter(fw); // valid

Теперь мы знаем, что мы не можем поместить значение, которое не того же типа, что и переменная, в эту переменную, поэтому что-то вроде

FileReader fr = new BufferedReader(fr); // Is not valid!

Потому что типы просто не совпадают.

Есть смысл?

person linuscash    schedule 11.08.2011
comment
Эй, чувак, ты был на самом деле ясен в своем объяснении, но для меня это звучит ново, когда ты говоришь, что объект будет уничтожен в конце выполнения метода... Это была какая-то сногсшибательная информация, которую ты мне дал! Итак, когда я должен написать код, почему я это сделал? И что изменилось с точки зрения создания объекта и размещения переменных в примере с @Tom? - person zeme; 11.08.2011
comment
Я не совсем уверен в том, о чем вы спрашиваете, поэтому дайте мне знать, если я ошибаюсь. Нет ничего плохого в том, как вы написали код, и это полностью зависит от того, что вы разрабатываете, в отношении того, когда вы хотите сохранить объект в существовании после выполнения метода, а когда нет, нет определенного ответа на вопрос, когда следует используйте так или иначе, кроме как если бы вы хотели, чтобы объект (переменная) все еще существовал после завершения метода, например, вы планируете использовать объект в другом месте, где или независимо от того, выполнил ли он свою работу, и вы счастливы, что он будет уничтожен. Это правильный путь вашего вопроса? - person linuscash; 11.08.2011
comment
Да, и вы на самом деле помогаете мне, много. Я хотел бы, чтобы вы еще раз прочитали мой вопрос, я отредактировал его, добавив новый код, который может помочь вам понять мои сомнения. - person zeme; 11.08.2011
comment
Извиняюсь за поздний ответ, был на работе. Я кратко отвечу в отдельном посте, так что следите за ним, мой друг. - person linuscash; 12.08.2011
comment
Эй, чувак, спасибо за повтор, я прочитал его и понял. это было полезно, мир. - person zeme; 16.08.2011

Да, каждый раз создается новый объект. Ссылка на каждый myObject размещается в стеке.

В двух словах: должен ли я писать такой код только в том случае, если я планирую вызывать этот метод только один раз?

Если вы хотите, чтобы myObject исчезло после завершения выполнения метода, то да. Если по какой-то причине вам нужно сохранить ссылку на него, вы можете объявить его как член класса.

class MyClass {
    AnotherClass myObject;
    void myMethod() {
        myObject = new AnotherClass();
        myObject.doStuff();
    }
}

Таким образом, он все еще будет создаваться каждый раз, когда вы вызываете myMethod(), но он все еще будет существовать после завершения myMethod. Это может быть удобно или нет, в зависимости от ситуации.

Пропускает ли компилятор подобный код, поскольку видит, что объект уже создан и переменная myObject уже присвоена такому объекту?

Этого не произойдет при использовании new. Гарантируется, что он создаст новый экземпляр. Его можно реализовать с помощью FactoryMethods (компилятор не пропускает строки кода, но предотвращает создание нового объекта). Например, класс Integer реализует это: если вы попытаетесь получить целое число между -128 и 127, оно всегда будет возвращать один и тот же экземпляр (не будет создавать новый объект) при использовании своего фабричного метода valueOf

 Integer five = Integer.valueOf("5");//Will always return the same instance.
 Integer otherFive = Integer.valueOf("5");

 assert(five==otherFive);//true

Конечно, использование new не возвращает тот же экземпляр, а всегда новый.

 Integer five = new Integer("5");//Will create a new object each time.
 Integer otherFive = new Integer("5");

 assert(five==otherFive);//false

после обновления вопроса

О коде, который вы добавили, сказать особо нечего. Однако, если вы посмотрите, вы заметите два метода. Судя по названиям, один вроде пишет, другой вроде читает. Это поведение специфично для каждого метода, поэтому метод, который writeFile не заботится об объектах, используемых для чтения. И метод readFile не заботится об объектах, используемых для записи. Так что нет смысла делать fileReader доступным для метода writeFile и т.д.

Возвращаясь к вашему первоначальному вопросу, да, это создает новый объект каждый раз, когда вызывается метод. Это не важно. Это предпочтительнее, чем спрашивать себя: «Почему метод readFile имеет доступ к экземпляру FileWriter?

person Tom    schedule 11.08.2011
comment
что ты имеешь в виду под этой строкой? The reference to each myObject is allocated in the stack. выделение в стеке или куче? - person Asif Mushtaq; 19.09.2015
comment
Ссылка на объект или место в памяти, так сказать, выделяется в стеке, однако содержимое объекта хранится в куче, а ссылка указывает на то, где в куче его найти. Подобно тому, как адрес человека можно найти в крошечной телефонной книге (стеке), в то время как адрес относится к месту в большом мире (куча). Другими словами, ваше ИМЯ есть в телефонной книге, и пусть люди найдут ваш АДРЕС, ВАШЕ ТЕЛО находится в вашем доме, которое люди найдут, если они будут следовать этому АДРЕСУ. - person Chexxor; 23.05.2016

Теперь предположим, что я вызываю myMethod(), скажем, 10 раз во время выполнения моей программы, как это работает? Каждый раз создается новый объект?

Да!

Повторное использование или отказ от повторного использования созданного объекта зависит от дизайна и ситуации. Есть случаи, когда лучше повторно использовать объекты, и в этом случае вы можете создать поле класса для хранения ссылки, а есть случаи, когда лучше каждый раз создавать новый объект (например, изучить неизменность).

person Bala R    schedule 11.08.2011

если вы вызываете 10 раз, в вашем java-стеке будет 10 фреймов методов, каждый фрейм будет выполнять действие new(), и когда кадр завершится, он будет выпущен.

введите здесь описание изображения

person dongjk    schedule 11.08.2011
comment
В стеке будет только 10 фреймов, если каждый метод вызывает следующий. Как только один из методов завершает работу, его кадр стека освобождается. - person Paŭlo Ebermann; 14.08.2011
comment
которые зависят от реализации виртуальной машины. - person dongjk; 17.08.2011

Каждый раз, когда вы вызываете метод myMethod, код выполняется сверху без памяти о том, что он делал при предыдущих выполнениях (если, конечно, вы не меняли некоторые поля объекта MyClass. Это означает, что каждый раз, когда вы запускаете метод, вы создаст новый объект AnotherClass и сохранит его в myObject. В более общем случае каждое выполнение метода будет запускать код сверху и не будет избегать повторного вычисления значений, даже если они могли быть кэшированы с предыдущих итераций, если вы явно не сохраните значения где-то .

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

person templatetypedef    schedule 11.08.2011

Каждый раз при вызове метода создается новый объект. Если управление достигает строки кода с «новым» оператором, то будет создан объект; нет никакого закулисного кэширования или другой магии.

person Ernest Friedman-Hill    schedule 11.08.2011