LINUX.ORG.RU

java, возврат а-ля const значения

 


0

3

Итак, есть у меня класс, пусть будет Data, содержащий в себе одним из полей самопальное Tree<Object>. Иногда есть необходимость посмотреть снаружи ентое самое дерево целиком, при этом запретив изменение как его самого, так и его элементов. Поменять поле на final - не решение, ибо дерево таки меняется другими методами. Поскольку никаких возвратов const у нас языком не предусмотрено, я вижу только варианты:

- обернуть возвращаемое значение в геттере в интерфейс типа unmodifableFooBar из Collections, и пытаться сочинить то же самое для элементов дерева (каковые у меня в общем-то Object, и содержат разномастное нечто), что влечет некорое перетрахивание имеющегося кода и, возможно, огребание геморроя в дальнейшем;

- возвращать копию всего дерева - ну вы понели(с).

вопрос - есть ли все-таки способы сделать разумнее?

Спасибо за пример для объяснения пользы плюсового const в антикрестовых холиварах.

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

Вроде нет. Это практически единственное, что мне не нравится в scala. Они обязаны были сделать что-то большее, чем val/var...

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

Да и на каждый чих получать полную копию объекта со всеми нижележащими - ява и так память как не в себя жрет.

Если ты о предложении макскома, то иммутабельные структуры не подразумевают, что там надо делать полную копию объекта. Скорее, наоборот (см. persistent purely functional data).

Вот, возьмем явовский класс String. В общем, он иммутабельный. Когда ты создаешь подстроку, то ява не копирует строку целиком, а берет старую, но просто индексирует ее по-другому. Еще сбрасывает хеш-код, но это к делу не относится. То есть, копирования данных нет. Работает быстро.

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

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

И все эти проблемы от того, что разработчики языка поленились продумать const. Вон в D есть и логическая константность и иммутабельность(причем так же явная и контролируемая компилятором)... А в Java(как и в большинстве современных языков) в рамках мутабельного типа эту проблему не решить...

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

Убого. Там есть const, но его можно инитить только compile-time значениями. Там есть readonly но он скорее похож на скаловский val, чем на нужную в данном случае константность... Мне всегда было интересно, почему так делают...

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

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

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

Парадокс блаба во всей красе. А к С++ таких как ты подпускать близко нельзя. Кстати, можешь привести пример, когда ты говнокодил const_cast'ом? Когда и как тебе мешал const? Я не помню ни одного случае, кроме кэширования, что давно уже делаются через mutable.

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

const нужен не для того, чтоб баги искать. Это часть описания контракта.

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

К с++ вообще никого подпускать нельзя.

Конкретный пример не приведу, дело было слишком давно. Вроде приходилось совмещать два куска кода, один из которых возвращал const&, а второй принимал указатель на неконстантные данные, это как один из примеров. Никаких const_cast-ов, старый добрый (c-style-cast) :)

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

Умных людей можно подпускать к чему угодно. const - простая и логичная концепция. То, что в угоду быдлокодером ее не додумали во всяких явах, не повод ее критиковать.

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

Вот, возьмем явовский класс String. В общем, он иммутабельный. Когда ты создаешь подстроку, то ява не копирует строку целиком, а берет старую, но просто индексирует ее по-другому. Еще сбрасывает хеш-код, но это к делу не относится. То есть, копирования данных нет. Работает быстро.

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

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

Что такого особенного в данном случае? Код уже показывали. Возвращаем ссылку/указатель/умный указатель на константный объект. Проблема-то в чем?

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

Первый вариант - мы и сами не можем изменять данные(только создать новое дерево):

class Data
{
public:

    // ...

    shared_ptr<const Tree<Object>> GetTree() const
    {
        return tree;
    }

private:
    shared_ptr<const Tree<Object>> tree;
};
Какие проблемы ты видишь в этом случае?

Второй вариант - мы сами можем менять данные, но клиент это делать не сможет:

class Data
{
public:

    // ...

    shared_ptr<const Tree<Object>> GetTree() const
    {
        return tree;
    }

private:
    shared_ptr<Tree<Object>> tree;
};
Здесь есть только одна проблема - если мы меняем объект в одном потоке, в процессе чтения дерева в другом. Правда из вопроса TC этого не следует.

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

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

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

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

а что будет если дерево продолжит меняться сеттерами? ну не хорошо же.

оно и должно будет меняться, тут все нормально, клиент в данном случае просто «вьюшка», которой и нужно смотреть за текущим состоянием, но не менять его, если ему надо копия - пусть делает копию, из const Tree<Object> ее легко можно сделать

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

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

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

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

Это возможно. Я до сих пор использую JDK 1.6. Хотел было перейти на JDK 1.7, но тогда возникли проблемы с ProGuard.

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

а что будет если дерево продолжит меняться сеттерами? ну не хорошо же.

оно и должно будет меняться, тут все нормально, клиент в данном случае просто «вьюшка», которой и нужно смотреть за текущим состоянием, но не менять его

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

pseudo-cat ★★★
()
Ответ на: комментарий от pseudo-cat

можно, как я уже говорил, отдавать клиенту базовый класс с protected-сеттерами

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

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

Хм. Действительно. Прикольно они(Страуструп?) придумали. Как-то особо не задумывался над этим

anonymous
()

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

Яву не знаю.

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

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

arkhnchul ★★
() автор топика
Ответ на: комментарий от anonymous
#include<iostream>

class X {
    public:
    X(int * _i):i(_i) {}
    int * i;
};

class Y {
    public:
    Y(X * _x):x(_x) {}
    const X * const x;
};

int main(void) {
    Y y = new X(new int(10));
    std::cout << *(y.x->i) << std::endl;
    *(y.x->i) = 12;
    std::cout << *(y.x->i) << std::endl;
    return 0;
}

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

Вот, возьмем явовский класс String. В общем, он иммутабельный. Когда ты создаешь подстроку, то ява не копирует строку целиком, а берет старую, но просто индексирует ее по-другому. Еще сбрасывает хеш-код, но это к делу не относится. То есть, копирования данных нет. Работает быстро.

начиная со следующей jvm это утверждение станет неверным :/

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

Что ты этим сказать-то хотел? За публичные поля нужно расстреливать. Это раз. Если разработчик класса X разрешил менять свое поле - значит он решил, что изменение этого поля не скажется на логической константности класса X, на что имеет полное право. Это два. Если не хочешь, чтоб так было, пиши так:

class X {
    public:
        X(int * _i):i(_i) {}

        const int * get_i() const
        {
            return i;
        }

        int * get_i()
        {
            return i;
        }
    private:
        int * i;
};

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

Это ничему не мешает. Тут решение было за разработчиком класса X. const - это инструмент, если ты не умеешь им пользоваться, то и профита не получишь.

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

lol. Действительно, в яве же «память не ресурс» =)

Ну тогда да, или усложнять сборку мусора, или копировать строки.

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

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

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

За публичные поля нужно расстреливать

Да правда! Скажи еще слово «инкапсуляция» тут.

Если разработчик класса X разрешил менять свое поле - значит он решил, что изменение этого поля не скажется на логической константности класса X, на что имеет полное право.

И в жабе так - и слово специальное есть: final. И тут вернемся к самому первому комменту от макскома - не хотите чтобы данные мутировались - используйте иммутабельные структуры. А решение с get_i() ничем не лучше жабского ImmutableTree getTree(). И там и там все сводится к декларации иммутабельного интерфейса.

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

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

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

Ась? Как это в жабке так же? Покажи аналог кода анона на жабе.

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

В жабе не так. Т.е. совсем.

Что там не так? Суть моего примера сводилось к задаче топикстартера. X - это Tree. Y - это Data. Как в Data не извращайся если Tree сам по себе мутабельный - хоть в плюсах хоть в жабе ничего не сделаешь. Модификация с get_i эффективно создает иммутабельный интерфейс объекта Tree. Если там будут мутирующие методы - никакой const не спасет. У топикстартера проблема не с деревом а с Data. Если топикстартер может менять Tree то жабское аналогичное решение:

interface Tree {никаких мутирующих методов}
class GBTree implements Tree {мутирующие методы}
class Data { Tree getTree() {...}}

Эффект тот же самый.

Поинт моего примера был показать что если само Tree не соблюдает иммутабельных контрактов - то хоть в плюсах хоть в жабе один хрен топикстартер попал.

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

В плюсах все соблюдают контракт const. И тебе не нужно вводить какие-то левые интерфейсы для этого.

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

Модификация с get_i эффективно создает иммутабельный интерфейс объекта Tree. Если там будут мутирующие методы - никакой const не спасет.

Один из get_i «мутирующий» метод и это никому не мешает. Из-за const-контрактов.

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

Суть моего примера сводилось к задаче топикстартера. X - это Tree. Y - это Data. Как в Data не извращайся если Tree сам по себе мутабельный - хоть в плюсах хоть в жабе ничего не сделаешь.

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

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

Если топикстартер может менять Tree то жабское аналогичное решение:

interface Tree {никаких мутирующих методов} class GBTree implements Tree {мутирующие методы} class Data { Tree getTree() {...}}

Эффект тот же самый.

В С++ топикстартеру не нужно менять Tree.

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

Проблема в том, что X это не Tree, а твой надуманный пример.

Гениальный аргумент. А в реальном Tree мутирующих методов нет? А чего тогда топикстартер беспокоиться?

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

В плюсах все соблюдают контракт const. И тебе не нужно вводить какие-то левые интерфейсы для этого.

const методы это и есть интерфейс.

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

Один из get_i «мутирующий» метод и это никому не мешает.

Ну е мое - вы правда не понимаете проблему топикстартера? Если это сложный объект Tree то у него будет метод а ля add_node - и торба. Топикстартер хотел абракадабра в Data и Tree вдруг стал резко иммутабельным. Хоть плюсы хоть жаба - это фантасмагория.

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

Ну е мое - вы правда не понимаете проблему топикстартера? Если это сложный объект Tree то у него будет метод а ля add_node - и торба

~$ cat 1.cpp
struct Tree
{
	void add_node() {}
};

int main()
{
   const Tree t;
   t.add_node();
};
~$ g++ 1.cpp
1.cpp: In function ‘int main()’:
1.cpp:9:15: error: passing ‘const Tree’ as ‘this’ argument of ‘void Tree::add_node()’ discards qualifiers [-fpermissive]
wota ★★
()
Ответ на: комментарий от anonymous

В С++ топикстартеру не нужно менять Tree.

приведи пример как топикстартер может сделать мутабельное дерево иммутабельным не меняя дерево.

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

Правильно. То есть все что топикстартер может добиться - некомпилирующейся программы. Ему надо не неработающая программа а иммутабельное дерево. И для того чтобы его получить ему внешнего конста явно недостаточно.

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

Правильно. То есть все что топикстартер может добиться - неработающей программы

ты бредишь, отдаем const Tree - и клиент не будет вызывать add_node, т.к. уже на этапе компиляции ему надают по рукам, а «хозяин» будет работать с Tree - и делать все что захочем

wota ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.