LINUX.ORG.RU

Java vs Python: поля классов.

 


1

1

Python (3.6):

class A:
    a: int = 6
    def class_print(self):
        print(self.a)

class B(A):
    a: int = 7

def main():
    a = A()
    a.class_print()
    b = B()
    b.class_print()

if __name__ == "__main__":
    main()

Output:

6
7

А в Java (8)

package temp;

class A {
    int a = 5;
    public void printMethod(){ System.out.println(a); }
}

class B extends A {
    int a = 6;
}

public class main {
    public static void main(String[] args) {
        A a = new A();
        a.printMethod();

        B b = new B();
        b.printMethod();
    }
}

Output:

5
5

Лично для меня поведение Python в данном случае является очевидным, но почему в Java иначе и как добиться (если возможно) поведения аля Python в данном случае? Почему?

class A {
    int a = 5;
    public void printMethod(){ System.out.println(a); }
}

class B extends A {
    public B(){
        super.a = 6;
    }
}

public class Main {

    public static void main(String[] args) {
        A a = new A();
        a.printMethod();

        B b = new B();
        b.printMethod();
    }
}
5
6
fsb4000 ()
Последнее исправление: fsb4000 (всего исправлений: 1)

В java ты описал класс A с переменной этого класса a и метод, который выводит значение переменной a класса A. Затем описал класс B, который наследник A, и у которого есть своя переменная a, которая никак нн связана с переменной a класса A. А затем вызываешь метод, который печатает значение переменной a класса A. Значение переменой a класса B нигде не печатается. Работает именно так, как ты описал. Выше товарищ написал, как тебе в классе B переопределить значение переменной a родительского класса A.

ma1uta ★★ ()
Ответ на: комментарий от WitcherGeralt

Да, такое решение мне нравиться больше.

package temp;

class A {
    static int a = 5;
    public void printMethod(){ System.out.println(a); }
}

class B extends A {
    static {
        a = 6;
    }

}

public class main {
    public static void main(String[] args) {
        A a = new A();
        a.printMethod();

        B b = new B();
        b.printMethod();
    }
}

Спасибо! На сколько оно Java-way?

galliley ()
Ответ на: комментарий от WitcherGeralt

Как помогу геттеры-сеттеры в данном случае?

package temp;

class A {
    int a = 5;
    public int getA() { return a; }
    public void printMethod(){ System.out.println(getA()); }
}

class B extends A {
    int a = 6;
}

public class main {
    public static void main(String[] args) {
        A a = new A();
        a.printMethod();

        B b = new B();
        b.printMethod();
    }
}

Результат работы тот же.

galliley ()
Ответ на: комментарий от galliley

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

bvn13 ★★★★★ ()

Это вопрос для ru.stackoverflow.

По существу: поля в java не переопределяются, полагаю как в СИ++, потому как в том языке есть такое понятие как таблица виртуальных методов, а не полей. Эта таблица нужна для позднего связывания, т.е. для нахождения метода который нужно вызвать в момент исполнения (в рантайме). Так как поля не переопределяются никакого поиска по иерархии нужного поля не происходит.

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

Aber ★★ ()
Ответ на: комментарий от galliley

Спасибо! На сколько оно Java-way?

Совсем не javaway.

1. Сделва переменную статической ты шаришь состояние хронящеется в статической переменной между всеми экземплярами класса A

A a1 =  new A();
A a2 =  new A();

a1.a = 10

assertEquals(a1, a2)
assertEquals(10, a2)

Можно было бы сделать так:

class A {
    int a = 5;
    public void printMethod(){ System.out.println(a); }
}

class B extends A {
    {
        a = 6;
    }
}
Но...
2. Все поля в java должны быть приватными, как раз потому как поле не переопределить, не задекларировать в интерфейсе, не проксировать.

Java-way:

class A {
    int getA() {
        return 5;
    }
}

class B extends A {
    @Override
    int getA() {
        return 6;
    }
}

Aber ★★ ()
Последнее исправление: Aber (всего исправлений: 1)
Ответ на: комментарий от galliley

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

тебе нужен конструктор:

class A {
    int a = 5;
    void say() {
        System.out.println("a="+a);
    }
}

class B extends A {
    B() {
        a = 6;
    }
}


public class Test1 {
    public static void main(String... args) {
        B b = new B();
        b.say();
    }
}

bvn13 ★★★★★ ()
Ответ на: комментарий от galliley

Со статиком же оно будет только на класс в целом, а не на каждый объект. Поэтому я на твоём месте так делать бы точно не стал. Только если оно и не должно быть на класс в целом.

turtle_bazon ★★★ ()

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

Если копнуть глубже и разобраться в деталях реализаций, которые уже не изменятся коренным образом по причине популярности обоих основных реализаций (JVM и CPython), то окажется, что в питоне все объекты изменяемы, и даже неизменяемые объекты изменяемы, просто ссылаются на один и тот же объект, и изменение этого объекта приведет к плохой предсказуемости поведения, потому не приветствуется. Вот эта штука, например, не так давно была отправлена лесом:
https://www.python.org/dev/peps/pep-0416/ - PEP 416 — Add a frozendict builtin type
Классы в питоне сделаны тупо последовательным проходом по неймспейсам объектов, и это приводит порой к очень веселым багам, поскольку, как указано выше, у классов можно свободно изменять атрибуты, ведь методы и поля объекта и класса - это четыре одинаковых вещи.

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

byko3y ()
Ответ на: комментарий от NeXTSTEP

Это такое «поле объекта»?

Это вот такое поле объекта, да:

class A:
    a = 6
    def class_print(self):
        print(self.a)
 
def main():
    x = A()
    x.class_print()
    x.a = -123
    x.class_print()
    A.a = 10
    x.class_print()
    x.a = 42
    x.class_print()
6
-123
-123
42
korvin_ ★★★★★ ()
Ответ на: комментарий от korvin_

Ну а что, все же логично:

class A:
    a = 6

    @classmethod
    def cls_a(cls):
        return cls.a

    def class_print(self):
        print(self.a, self.cls_a(), self.__dict__.get('a'))

def main():
    x = A()
    x.class_print()
    x.a = -123
    x.class_print()
    A.a = 10
    x.class_print()
    x.a = 42
    x.class_print()
    del x.a
    x.class_print()
6 6 None
-123 6 -123
-123 10 -123
42 10 42
10 10 None
KillTheCat ★★★★★ ()
Ответ на: комментарий от galliley

Потому что в питоне dynamic dispatch в широком смысле. Минусом этого подхода является например вот что. Кто-то написал класс, который имеет «публичные» методы foo() и bar(), которые иногда вызывают друг друга, чисто как деталь реализации. А в субклассе ты кардинально изменил bar() в своих целях. Теперь bar() делает то, что ты хочешь, но также это влияет и на вызов foo(), чего ты возможно не хочешь. А способа заставить foo() вызывать старый bar() - нет. В [более] статическом языке, если бы у bar() был признак virtual, то автор был бы в курсе, что его могут заменить, а если бы virtual не было, он был бы уверен, что bar() не изменится. А ты мог бы этим управлять.

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

anonymous ()
Ответ на: комментарий от KillTheCat

Ну а что, все же логично:

del x.a
print(x.a)

=> 10

х.а.х.а.х.а

Если ты по сути используешь два поля: одно статическое, второе у объекта - так и напиши два поля. Зачем им одинаковое имя понадобилось?

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

Kokonavtuz ()
Ответ на: комментарий от korvin_

Впрочем, спасибо. Оказывается, Питон ещё хуже, чем я думал.

Нет, просто твои архитектурные идеи не соответствуют архитектуре питона.
Фундаментом питона является duck-typing, например, когда ты добавляешь к объекту методы __iter__ и __next__, то он становится типа «итератор», если же добавил __get__, __set__, __delete__ - это дескриптор.
Классы питона, помимо довольно бесполезной формальной идентификации, исполняют роль удобного инициализатора, поскольку для добавления обычной функции в качестве метода нужно выполнять некрасивое действие вроде obj.func = func.__get__(obj), хотя, последнее - тоже вариант.
В случае же примитивных полей все эти прыгания с методами бессмысленны, а если хочется сделать константу - делай функцию, возвращающую константу. По иронии, синтаксически аналогичный подход предлагали здесь предлагали для жабы.

byko3y ()
Ответ на: комментарий от anonymous

Просто в Java статическое связывание идёт на этапе компиляции. В путоне же динамическое во время вызова. Из-за этого Java быстрее.

Ява быстрее не из-за этого.

Если индус будет строить иерарахии классов вместо использования простых конструкций питона, то будет именно из-за этого. Пройти по нескольким объектам с поиском по имени атрибута - это весьма затратная операция. Так-то для питона есть PyPy с трассирующим JIT (как в очень похожем JS), который превращает операции над динамическими типами в операции со статикой, но он не сможет переварить иерархии из книжки про ООП, как этого не сумеет сделать и V8 в случае JS.

byko3y ()
Ответ на: комментарий от turtle_bazon

Я думаю, что он имел в виду динамическую типизацию. Даже на C прога будет работать медленно, если ты сделаешь все переменные одного универсального типа. Собсна, привет Cython-у - это ведь тоже компилятор.
К тому же, все языки со сбором мусора так или иначе проигрывают явному управлению памятью в разы. И в итоге какая-нибудь жава на хэш-таблицах и со сложной структурой класов будет по производительности равна питону, у которого те же функции сделаны из коробки на голом си.

byko3y ()