Атрибуты / переменные-члены в интерфейсах?
Я хотел бы знать, есть ли способ сделать обязательным для класса реализатора объявлять дескрипторы / примитивы объектов, как они делают с методами. например:
public interface Rectangle {
int height = 0;
int width = 0;
public int getHeight();
public int getWidth();
public void setHeight(int height);
public void setWidth(int width);
}
public class Tile implements Rectangle{
@Override
public int getHeight() {
return 0;
}
@Override
public int getWidth() {
return 0;
}
@Override
public void setHeight(int height) {
}
@Override
public void setWidth(int width) {
}
}
В приведенном выше методе, как мы можем заставить класс Tile объявлять атрибуты высоты и ширины, используя интерфейс? Почему-то я хочу сделать это только с интерфейсом!
Я изначально думал об использовании его с наследованием. Но дело в том, что я имею дело с 3 классами!
- Прямоугольник
- Плитка
- JLabel.!
class Tile extends JLabel implements Rectangle {}
должно сработать.!
но
class Tile extends JLabel extends Rectangle {}
не очень!
10 ответов
Смысл интерфейса заключается в указании публичного API. Интерфейс не имеет состояния. Любые переменные, которые вы создаете, действительно являются константами (поэтому будьте осторожны при создании изменяемых объектов в интерфейсах).
В основном интерфейс говорит, что здесь все методы, которые класс, который его реализует, должен поддерживать. Вероятно, было бы лучше, если бы создатели Java не разрешали использование констант в интерфейсах, но уже слишком поздно, чтобы избавиться от этого сейчас (и в некоторых случаях константы имеют смысл в интерфейсах).
Поскольку вы просто указываете, какие методы должны быть реализованы, отсутствует представление о состоянии (нет переменных экземпляра). Если вы хотите, чтобы у каждого класса была определенная переменная, вам нужно использовать абстрактный класс.
Наконец, вы, вообще говоря, не должны использовать публичные переменные, поэтому идея помещения переменных в интерфейс - плохая идея для начала.
Короткий ответ - вы не можете делать то, что вы хотите, потому что это "неправильно" в Java.
Редактировать:
class Tile
implements Rectangle
{
private int height;
private int width;
@Override
public int getHeight() {
return height;
}
@Override
public int getWidth() {
return width;
}
@Override
public void setHeight(int h) {
height = h;
}
@Override
public void setWidth(int w) {
width = w;
}
}
альтернативная версия будет:
abstract class AbstractRectangle
implements Rectangle
{
private int height;
private int width;
@Override
public int getHeight() {
return height;
}
@Override
public int getWidth() {
return width;
}
@Override
public void setHeight(int h) {
height = h;
}
@Override
public void setWidth(int w) {
width = w;
}
}
class Tile
extends AbstractRectangle
{
}
Интерфейсы не могут требовать определения переменных экземпляра - только методы.
( Переменные могут быть определены в интерфейсах, но они не ведут себя так, как ожидалось: они рассматриваются как final static
.)
Удачного кодирования.
Java 8 представила методы по умолчанию для интерфейсов, используя которые вы можете привязать методы. Согласно ООП интерфейсы должны действовать как контракт между двумя системами / сторонами.
Но все же я нашел способ добиться сохранения свойств в интерфейсе. Я признаю, что это довольно уродливая реализация.
import java.util.Map;
import java.util.WeakHashMap;
interface Rectangle
{
class Storage
{
private static final Map<Rectangle, Integer> heightMap = new WeakHashMap<>();
private static final Map<Rectangle, Integer> widthMap = new WeakHashMap<>();
}
default public int getHeight()
{
return Storage.heightMap.get(this);
}
default public int getWidth()
{
return Storage.widthMap.get(this);
}
default public void setHeight(int height)
{
Storage.heightMap.put(this, height);
}
default public void setWidth(int width)
{
Storage.widthMap.put(this, width);
}
}
Этот интерфейс ужасен. Для хранения простого свойства потребовалось два хеш-карты, и каждый хеш-файл по умолчанию создает 16 записей по умолчанию. Кроме того, когда реальный объект разыменовывается, JVM дополнительно необходимо удалить эту слабую ссылку.
Вы можете сделать это только с абстрактным классом, а не с интерфейсом.
декларировать Rectangle
как abstract class
вместо interface
и объявить методы, которые должны быть реализованы подклассом как public abstract
, Тогда класс Tile
расширяет класс Rectangle
и должны реализовать абстрактные методы из Rectangle
,
В Java вы не можете. Интерфейс связан с методами и сигнатурами, он не имеет отношения к внутреннему состоянию объекта - это вопрос реализации. И это тоже имеет смысл - я имею в виду, просто потому, что существуют определенные атрибуты, это не означает, что они должны использоваться реализующим классом. getHeight может фактически указывать на переменную ширины (при условии, что разработчик является садистом).
(Как примечание - это не относится ко всем языкам, ActionScript допускает объявление псевдоатрибутов, и я верю, что C# тоже)
Как все говорят, нельзя добавлять атрибуты к интерфейсу.
Но я следую этому ответу: /questions/15875583/atributyi-peremennyie-chlenyi-v-interfejsah/15875590#15875590 Вы можете использовать службу с WeakHashMap для хранения атрибутов.
Чтобы уменьшить количество карт, используйте такую карту ключей:
В java 11:
public interface Rectangle {
class Storage {
private static final Map<Rectangle, Map<String, Object>> attributes = new WeakHashMap<>();
}
private Map<String, Object> getAttributes(){
return Storage.attributes.computeIfAbsent(this, k -> new HashMap<>());
}
default public int getHeight() {
return (int) getAttributes().getOrDefault("height", 0);
}
default public int getWidth() {
return (int) getAttributes().getOrDefault("width", 0);
}
default public void setHeight(int height) {
getAttributes().put("height", height);
}
default public void setWidth(int width) {
getAttributes().put("width", width);
}
}
Поля в интерфейсах неявно public static final
, (Также методы неявно общедоступны, так что вы можете удалить public
ключевое слово.) Даже если вы используете абстрактный интерфейс вместо интерфейса, я настоятельно рекомендую сделать все неконстантным (public static final
ссылки на примитивный или неизменный объект) private
, В более общем смысле "предпочитаю композицию наследованию" - Tile
это не Rectangle
(конечно, вы можете играть в словесные игры с "is-a" и "has-a").
Том сказал что-то важное:
если вы используете концепцию has-a, вы избегаете этой проблемы.
Действительно, если вместо использования extends и реализует, вы определяете два атрибута, один из типа прямоугольник, один из типа JLabel
в вашем Tile
класс, то вы можете определить Rectangle
быть или интерфейсом или классом.
Кроме того, я бы обычно поощрял использование интерфейсов в связи с has-a, но я полагаю, что это будет излишним в вашей ситуации. Тем не менее, вы единственный, кто может принять решение по этому вопросу (компромиссная гибкость / чрезмерная инженерия).
Вспомогательный пункт:
Я всегда думал, что объявление атрибутов в интерфейсе — это своего рода антишаблон, поскольку интерфейсы не имеют состояния. И у меня не было практического варианта его использования, пока мне не потребовалось внедрение зависимостей во все конкретные классы, реализующие интерфейс. Хотя я мог бы вручную внедрить зависимости в каждый класс, было бы проще наследовать их от родительского класса.
Итак, как уже упоминалось многими, использование
Для интерфейсов с переменными-членами используйте абстрактный класс:
public abstract class Rectangle {
int height = 0;
int width = 0;
public abstract int getHeight();
public abstract int getWidth();
public abstract void setHeight(int height);
public abstract void setWidth(int width);
}