Подтвердить что ты не робот

Почему вы можете создать класс в пределах своего определения?

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

class XCopy {

    public static void main(String[] args) {
        XCopy x = new XCopy(); // 1
        x.doIt();
    }

    public void doIt() {
        // Some code...
    }
}

Он был сбит с толку в строке 1. То, что он хотел знать, было причиной того, что новый экземпляр XCopy может быть создан в определении класса XCopy. Он думал, что это дало бы какую-то ошибку прямого реферирования. В конце концов, мы еще не закончили объявлять, что такое класс XCopy, так как мы можем создать его?

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

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

4b9b3361

Ответ 1

Вы определяете класс, все его поля и методы и т.д. во время компиляции. Экземпляр не создается до выполнения. Таким образом, нет противоречия, класс полностью определяется временем, когда вы достигнете линии № 1.

Как отмечают другие, поскольку метод main равен static, вы достигнете строки # 1 без создания экземпляра объекта, но вы можете сделать это без проблем. Я все время использую этот шаблон для одноклассных экспериментов.

Ответ 2

Потому что код сначала скомпилирован и выполняется позже. Весь компилятор должен знать, чтобы проверить, что строка состоит в том, что существует класс с именем XCopy и что он имеет конструктор без аргументов. Ему не нужно знать все о классе.

Ответ 3

Это не прямая ссылка в смысле C/С++. Ваш основной метод относится к классу как к типу внутри его собственного контекста. Вы ничего не "опережаете".

Тот факт, что main является статическим, не является родным, потому что он будет работать даже для нестатических методов:

public class Foo
{
   private String x;

   public Foo(String x) { this.x = x; }
   public Foo(Foo f) { this.x = f.x; }  // copy constructor; still compiles fine, even without static
}

Одно отличие - это компиляция и привязка. C/С++ имеют отдельные шаги компиляции и компоновки. Java имеет загрузчик классов. Я думаю, что компиляция в байтовый код и загрузка по мере необходимости во время выполнения с использованием загрузчика классов - это тонкая разница между Java и C/С++, которая объясняет, почему идея прямой ссылки не нужна, но я не уверен.

Ответ 4

Класс - это всего лишь синий шрифт, который описывает, что каждый экземпляр класса будет выглядеть и вести себя как. В зависимости от видимости класса и его конструкторов код в том же классе, в том же пакете или полные незнакомые люди могут создавать экземпляры.

Обычно используется метод factory в классах, где конструктор не должен быть общедоступным:

public class Foo {
    // only I get to create new instances
    private Foo() {
    }

    // but you can get instances through this factory method
    public static Foo createFoo() {
        return new Foo();
    }
}

Ответ 5

По той же причине вы можете вызвать метод в строке 42, который не определен до строки 78? Java не является языком сценариев, поэтому вещи не должны быть объявлены до их использования (это даже верно для некоторых языков сценариев, на самом деле). Определения классов рассматриваются как целое во время компиляции.

Вы даже можете создать экземпляр объекта класса в своем собственном конструкторе:

public class Test {
    Test a;

    Test() {
        a = new Test();
    }

    public static void main(String[] args) {
        System.out.println(new Test());
    }
}

Это вызывает... дождитесь его... a java.lang.StackOverflowError.

Ответ 6

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

// forward declaration
void doSomething(void);

void doSomethingElse(void) {
    doSomething();
}

// function definition
void doSomething(void) {
    ...
}

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

Итак, к тому моменту, когда тело метода вашего основного метода обработано, компилятор знает конструктор по умолчанию вашего класса и его метод doIt и может генерировать правильный байт-код для вызова именно этого метода.

Ответ 7

Потому что основной метод статичен. А по статичности это означает, что этот метод не относится к какому-либо конкретному экземпляру класса. То есть к нему можно получить доступ, не создавая экземпляр.

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

Ответ 8

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

Ответ 9

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

1) Экземпляр класса создается с использованием либо ключевого слова new(), либо использования отражения с использованием класса .forName(), которое может вызывать ClassNotFoundException в Java.

2) вызывается статический метод класса.

3) назначено статическое поле класса.

4) используется статическое поле класса, которое не является постоянной переменной.

5), если класс является классом верхнего уровня, и выполняется оператор утверждения, лексически вложенный в класс.

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

Но если ваш код такой,

class Test {
    Test test2 = new Test();
    public static void main(String[] args) {
        Test test1 = new Test();     
    }
}

the above code will result in stackoverflow exception.



class Test {
    public static void main(String[] args) {
        Test test = new Test();     
    }
}

In the above code creating an instance won't call main again. 
Remember, main is a static method, not tied to any particular instance.



class Test {
    static Test test2 = new Test();
    public static void main(String[] args) {
        Test test1 = new Test();     
    }
}

This code will also run fine.

Подробнее из https://javarevisited.blogspot.in/2012/07/when-class-loading-initialization-java-example.html