Что такое ошибка нестатического метода и как это работает?

У меня есть пара ЧРЕЗВЫЧАЙНО основных вопросов по Java, которые я хотел бы, наконец, понять раз и навсегда. У меня есть следующий короткий фрагмент кода:

public class VeryBasicJava{
    public static void main(String[] args){
        int x = 3;
        int y = 4;
        swapMe(x, y);
    }
    private void swapMe(int a, int b){
        int a;
        int b;
        int tmp = a;
        this.a = b;
        this.b = a;
    }
}

Когда я компилирую, я получаю ужасную ошибку «Нестатический метод swapMe (int, int) не может быть указан из статического контекста». Кроме того, я получаю «a уже определено в swapMe (int, int)» и «b уже определено в swapMe (int, int)»

Что мне нужно, наконец, пройти через мой толстый череп, так это ошибка «нестатического метода», как (почему) она вызвана и как ее избежать.

Кроме того, я исходил из того, что вы можете сделать то, что я пытаюсь сделать, с моими переменными «a» и «b» в методе «swapMe». Я подумал, что могу передать «a» и «b», но также создать новые переменные «a» и «b» и сослаться на них с помощью ключевого слова «this».

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

Спасибо всем, что нашли время, чтобы прочитать это. Хорошего дня.


person Brian    schedule 19.03.2012    source источник
comment
хороший вопрос, через который проходят многие ранние программисты Java :)   -  person Shawn    schedule 19.03.2012
comment
Хороший вопрос, что большинство людей просто предоставят код и не объяснят, что происходит.   -  person Nicholas    schedule 19.03.2012
comment
Могут ли люди перестать приводить примеры? Спрашивающему не нужен работающий код, ему нужен кто-то, кто объяснит концепции объектно-ориентированного программирования.   -  person deworde    schedule 20.03.2012


Ответы (7)


Это означает, что метод swapMe() является методом экземпляра, и вам нужен экземпляр класса VeryBasicJava для его вызова, например:

VeryBasicJava instance = new VeryBasicJava();
int x = 3; int y = 4;
instance.swapMe(x, y);

В качестве альтернативы вы можете объявить swapMe() как static, таким образом вам не нужно будет сначала создавать экземпляр:

private static void swapMe(int a, int b)
person Óscar López    schedule 19.03.2012
comment
Но swapMe относится к тому же классу, что и main, то есть к классу VeryBasicJava. Почему мне нужно создать экземпляр VeryBasicJava, чтобы вызвать метод изнутри? Для меня это эквивалентно тому, что instance.x = 3, а instance.y = 4. Думаю, я должен также сказать, что я процедурный человек, я изучал программирование на C. - person Brian; 19.03.2012
comment
Потому что вы вызываете его из статического метода. Если бы вы вызвали swapMe из другого нестатического метода, вам не понадобился бы экземпляр класса для его вызова. - person Raevik; 19.03.2012
comment
Java сбивает с толку, для меня это абсолютно бессмысленно. Я говорю вам, C НАМНОГО проще, чем Java когда-либо думал. - person Brian; 19.03.2012
comment
@Brian в Java (и большинстве объектно-ориентированных языков) мы делаем различие в отношении методов в классе: методы экземпляра, которые имеют доступ как к экземпляру, так и к < i>class являются членами класса и должны вызываться в экземпляре класса. И методы класса, которые имеют доступ только к членам класса класса и не нуждаются в вызове экземпляра. - person Óscar López; 19.03.2012
comment
@Brian в вашем коде метод swapMe() является методом экземпляра, и поэтому его нельзя вызывать, не обратившись сначала к экземпляру класса VeryBasicJava. С другой стороны, такие методы, как Math.sqrt(), являются static, что означает, что они принадлежат классу, а не экземпляру, и не требуют вызова экземпляра. - person Óscar López; 19.03.2012
comment
@Оскар Это не имело для меня никакого смысла. Поскольку swapMe() принадлежит VeryBasicJava, мы должны сделать что-то особенное, чтобы использовать его. Но поскольку Math.sqrt() не принадлежит VeryBasicJava, мы можем использовать его без какого-либо дополнительного кода? Это самая запутанная вещь, которую я когда-либо слышал. И я всегда слышу, как люди, работающие на Java, говорят о том, насколько запутанным и неинтуитивным является malloc. - person Brian; 19.03.2012
comment
@ Брайан, путаница понятна, поскольку у вас процедурный опыт. Вам нужно вернуться к основам, чтобы по-настоящему погрузиться в объектно-ориентированное программирование — пока я рекомендую вам прочитать это из руководства по Java. - person Óscar López; 19.03.2012
comment
@Brian, отчасти это связано с тем, что статические методы (включая, например, Math.sqrt) работают именно так, как вы ожидаете, но когда ваш класс имеет свои собственные поля и значения, тогда методы экземпляра позволяют вам делать то, что вы не могли не со статическими методами. - person Louis Wasserman; 19.03.2012
comment
-1: Это пример, а не объяснение. Вы используете такие термины, как instance и static, не объясняя, что вы имеете в виду, отсюда и затянувшийся чат комментариев. - person deworde; 20.03.2012

У вас всего несколько мелких проблем. Вы упомянули в комментарии, что у вас есть некоторый опыт работы с C, поэтому я попытаюсь провести некоторые основные аналогии. Метод static (такой как main) ведет себя как обычная функция C. Однако метод, отличный от static, принимает скрытый параметр: this, который ссылается на объект, над которым должен работать этот метод. Когда вы пишете такой метод:

private void swapMe(int a, int b) {
    // ...

Это действительно означает что-то вроде этого:

private void swapMe(VeryBasicJava this, int a, int b){
    //              ^^^^^^^^^^^^^^^^^^^^
    // ...

Поскольку параметр this обрабатывается особым образом, существует специальный синтаксис для вызова методов, отличных от static, для объектов:

    myInstance.swapMe(someA, someB);
//  ^^^^^^^^^^        ^^^^^  ^^^^^
//     this             a      b

И поскольку swapMe не объявлен как static, ожидается, что он будет вызываться, как указано выше.

Тот факт, что main объявлен внутри класса VeryBasicJava, не означает, что у вас автоматически есть VeryBasicJava объект. Опять же, поскольку main равно static, это похоже на обычную функцию C:

void VeryBasicJava_main(...) {
    // ...

Чтобы создать экземпляр объекта, вы используете new:

VeryBasicJava vbj = new VeryBasicJava();

Это аналогично malloc в C:

VeryBasicJava *vbj = malloc(sizeof(VeryBasicJava));
VeryBasicJava_construct(vbj);

С экземпляром вы можете вызвать метод:

vbj.swapMe(spam, eggs);

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

1: private void swapMe(int a, int b) {
2:     int a;
3:     int b;
4:     int tmp = a;
5:     this.a = b;
6:     this.b = a;
7: }

При вызове этого метода происходит следующее:

  1. a и b создаются с учетом значений, указанных в вызове swapMe.

  2. Создается новый a, локальный для swapMe, со значением по умолчанию 0. Это a скрывает параметр a, и их невозможно различить.

  3. Создается новый b, тоже строго локальный. Он также имеет значение по умолчанию 0 и скрывает параметр b.

  4. Создается tmp, и его значение устанавливается равным только что объявленному a, так что это тоже 0.

  5. [см. ниже]

  6. [см. ниже]

  7. Локальные переменные a и b перестают существовать, как и параметры a и b.

В строках 5 и 6 вы пытаетесь использовать синтаксис this.a для ссылки на локальный a, а не на параметр. Хотя этот синтаксис существует в Java, он не делает того, что вы имеете в виду. Параметры обрабатываются так же, как и локальные переменные, поэтому вместо того, чтобы различать эти две категории, this.a различает локальные переменные и члены. Итак, что такое члены? Ну, скажем, ваше объявление класса содержит объявления переменных:

class VeryBasicJava {

    private int a;
    private int b;

    // ...

}

Это похоже на объявления членов в C struct:

struct VeryBasicJava {
    int a;
    int b;
};

Это означает, что при создании экземпляра VeryBasicJava:

VeryBasicJava vbj = new VeryBasicJava();

Этот экземпляр имеет свои собственные переменные a и b, которые можно использовать в любом методе, отличном от static:

public void print() {
    System.out.println("a is " + a);
    System.out.println("b is " + b);
}

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

class VeryBasicJava {

    private int a;
    private int b;
    private int c;

    public static void main(String[] args) {
        VeryBasicJava vbj = new VeryBasicJava();
        vbj.a = 3;
        vbj.b = 4;
        vbj.c = 5;
        vbj.print(1);
    }

    public void print(int a) {
        int b = 2;
        System.out.println("a is " + a);
        System.out.println("b is " + b);
        System.out.println("c is " + c);
        System.out.println("this.a is " + this.a);
        System.out.println("this.b is " + this.b);
        System.out.println("this.c is " + this.c);
    }

}

Он будет производить этот вывод:

a is 1
b is 2
c is 5
this.a is 3
this.b is 4
this.c is 5

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

person Jon Purdy    schedule 19.03.2012

Ваши проблемы связаны с непониманием того, как работает объектно-ориентированная парадигма.

Идея состоит в том, что вы определяете класс, который является "типом вещи". Из этого класса вы создаете экземпляры, которые фактически являются примерами этого типа вещей.

Например, когда вы создаете класс «Кошелек», он определяет, как ваша система будет обрабатывать кошельки (т. е. что они могут делать и что вы можете с ними делать). Теперь вы можете создавать несколько разных экземпляров кошельков, которые хранят разные значения, но работают одинаково. (например, «myWallet» и «yourWallet» являются кошельками, поэтому вы можете снимать деньги и вкладывать деньги и т. д., но если я проверю деньги в yourWallet, я получу значение 50, тогда как если я проверю значение в myWallet, Я получаю значение 20)

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

Чтобы использовать мой пример, если у вас есть два объекта типа «Кошелек», и для одного значение «наличные» установлено на 30, а для другого — наличное значение равно «25», статическая функция не может получить доступ к обоим значениям. , поэтому он также не может получить доступ. Вы должны предоставить ему доступ к определенному кошельку.

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

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

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

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

person deworde    schedule 19.03.2012
comment
Хорошо, согласно этому (docs.oracle.com/javase/tutorial/ java/javaOO/classvars.html), статические значения являются общими для всех объектов, созданных из класса. Итак, почему [из вашего примера] статический метод не доступен для всех объектов, созданных из класса? Если наличные деньги статичны, а функция checkCash() статична, то она должна работать одинаково и давать один и тот же ответ для каждого созданного кошелька... верно??? - person Brian; 20.03.2012
comment
Да, тут ты прав. к статическим методам может обращаться любой экземпляр (см. numberOfBicycles в этом документе). Но то, что вы делаете в приведенном выше вопросе, - это доступ к НЕСТАТИЧЕСКОЙ (экземплярной) функции-члену swapMe из статического метода: main, что наоборот, и это не будет работать, потому что ничто не мешает swapMe получить доступ конкретные члены экземпляра, которые не будут существовать без экземпляра. Ошибка доступа к нестатическому методу из статического контекста, не доступ к статическому методу из нестатического контекста - person deworde; 21.03.2012

Классы являются шаблоном для ОБЪЕКТОВ. Экземпляр класса (т. е. и объект) имеет собственную версию переменных и нестатических методов.

статические методы и поля не привязаны ни к какому конкретному экземпляру класса.

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

person hvgotcodes    schedule 19.03.2012

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

    int a; //this is the first argument
    int b; //this is the second argument
    int a; //this is the first line of your function
    int b; //this is the second line of your function

Вот почему вы получаете проблему - a и b, которые вы объявляете после того, как ваши аргументы противоречат вашим аргументам, а не «this.a» и «this.b».

На ваш первый вопрос уже дан ответ.

person Marshall Conover    schedule 19.03.2012

Поскольку исправление было предоставлено, я хотел бы добавить:

статические методы могут вызывать только другие статические методы и напрямую обращаться только к статическим переменным (в java).

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

person P.P    schedule 19.03.2012

Для вызова swapMe требуется экземпляр VeryBasicJava. Статический нет.

Извне этого объекта вы можете вызвать: VeryBasicJava.main(args).

Извне объекта вам нужно будет сделать (new VeryBasicJava()).swapMe(a,b) для вызова этого метода.

Причина, по которой вы получаете ошибки внутри swapMe, заключается в том, что вы определяете эти переменные дважды. Строка подписи объявила их в этом пространстве имен для вас. В этом методе у вас уже есть int a. Затем в первой строке вы снова объявляете in a.

То, что вы хотите сделать в своем свопе, выглядит примерно так:

 private void swapMe(int a, int b){
    int tmp = a;
    a = b;
    b = tmp;
 }

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

person Raevik    schedule 19.03.2012