Переключить instanceof?
У меня есть вопрос об использовании коммутатора для instanceof
объект:
Например: моя проблема может быть воспроизведена на Java:
if(this instanceof A)
doA();
else if(this instanceof B)
doB();
else if(this instanceof C)
doC():
Как бы это было реализовано с помощью switch...case
?
29 ответов
Это типичный сценарий, в котором помогает полиморфизм подтипа. Сделайте следующее
interface I {
void do();
}
class A implements I { void do() { doA() } ... }
class B implements I { void do() { doB() } ... }
class C implements I { void do() { doC() } ... }
Тогда вы можете просто позвонить do()
на this
,
Если вы не можете изменить A
, B
, а также C
Вы можете применить шаблон посетителя для достижения того же.
Если вы абсолютно не можете кодировать интерфейс, вы можете использовать enum в качестве посредника:
public A() {
CLAZZ z = CLAZZ.valueOf(this.getClass().getSimpleName());
switch (z) {
case A:
doA();
break;
case B:
doB();
break;
case C:
doC();
break;
}
}
enum CLAZZ {
A,B,C;
}
Java теперь позволяет переключаться в порядке OP. Они называют это Pattern Matching for switch. В настоящее время он находится на драфте, но, видя, сколько работы они вкладывают в коммутаторы в последнее время, я думаю, что он пройдет Пример, приведенный в JEP:
String formatted;
switch (obj) {
case Integer i: formatted = String.format("int %d", i); break;
case Byte b: formatted = String.format("byte %d", b); break;
case Long l: formatted = String.format("long %d", l); break;
case Double d: formatted = String.format("double %f", d); break;
case String s: formatted = String.format("String %s", s); break
default: formatted = obj.toString();
}
или используя их лямбда-синтаксис и возвращая значение
String formatted =
switch (obj) {
case Integer i -> String.format("int %d", i)
case Byte b -> String.format("byte %d", b);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> obj.toString();
};
так или иначе они делали классные вещи с выключателями.
Просто создайте карту, где класс является ключом, а функциональность, т.е. лямбда или аналогичная, является значением.
Map<Class,Runnable> doByClass = new HashMap<>();
doByClass.put(Foo.class, () -> doAClosure(this));
doByClass.put(Bar.class, this::doBMethod);
doByClass.put(Baz.class, new MyCRunnable());
// конечно, рефакторинг это только один раз инициализировать
doByClass.get(getClass()).run();
Если вам нужно проверить исключения, реализуйте FunctionalInterface, который выдает исключение и использует его вместо Runnable.
На всякий случай, если кто-то прочтет это:
Лучшее решение в Java это:
public enum Action {
a{
void doAction(...){
// some code
}
},
b{
void doAction(...){
// some code
}
},
c{
void doAction(...){
// some code
}
};
abstract void doAction (...);
}
БОЛЬШИМИ преимуществами такой модели являются:
Вы просто делаете это как (никаких переключателей вообще):
void someFunction ( Action action ) { action.doAction(...); }
В случае, если вы добавляете новое действие с именем "d", вы ДОЛЖНЫ реализовать метод doAction(...)
ПРИМЕЧАНИЕ. Этот шаблон описан в блоке Джошуа "Эффективная Java (2-е издание)".
Ты не можешь switch
заявление может содержать только case
операторы, которые являются константами времени компиляции и которые оцениваются как целое число (до Java 6 и строка в Java 7).
То, что вы ищете, называется "сопоставление с образцом" в функциональном программировании.
Смотрите также Как избежать instanceof в Java
Как обсуждалось в верхних ответах, традиционный подход ООП заключается в использовании полиморфизма вместо переключения. Для этого трюка есть даже хорошо документированный шаблон рефакторинга: Заменить Условный на Полиморфизм. Всякий раз, когда я достигаю этого подхода, мне также нравится реализовывать объект Null для обеспечения поведения по умолчанию.
Начиная с Java 8, мы можем использовать лямбды и дженерики, чтобы дать нам кое-что, с чем функциональные программисты хорошо знакомы: сопоставление с образцом. Это не базовая языковая функция, но библиотека Javaslang предоставляет одну реализацию. Пример из Javadoc:
Match.ofType(Number.class)
.caze((Integer i) -> i)
.caze((String s) -> new BigDecimal(s))
.orElse(() -> -1)
.apply(1.0d); // result: -1
Это не самая естественная парадигма в мире Java, поэтому используйте ее с осторожностью. Хотя универсальные методы избавят вас от необходимости типизировать совпадающее значение, мы упускаем стандартный способ декомпозиции сопоставленного объекта, как , например, в классах case Scala.
К сожалению, это невозможно из коробки, поскольку оператор switch-case ожидает константное выражение. Чтобы преодолеть это, можно использовать значения перечислений с именами классов, например:
public enum MyEnum {
A(A.class.getName()),
B(B.class.getName()),
C(C.class.getName());
private String refClassname;
private static final Map<String, MyEnum> ENUM_MAP;
MyEnum (String refClassname) {
this.refClassname = refClassname;
}
static {
Map<String, MyEnum> map = new ConcurrentHashMap<String, MyEnum>();
for (MyEnum instance : MyEnum.values()) {
map.put(instance.refClassname, instance);
}
ENUM_MAP = Collections.unmodifiableMap(map);
}
public static MyEnum get(String name) {
return ENUM_MAP.get(name);
}
}
С этим можно использовать оператор switch следующим образом
MyEnumType type = MyEnum.get(clazz.getName());
switch (type) {
case A:
... // it's A class
case B:
... // it's B class
case C:
... // it's C class
}
Это будет работать быстрее и иметь смысл в случае
- вы не можете изменить классы модели (внешняя библиотека)
- процесс выполняется в контексте чувствительности производительности
- у вас относительно много "дел"
public static <T> T process(Object model) {
switch (model.getClass().getSimpleName()) {
case "Trade":
return processTrade();
case "InsuranceTransaction":
return processInsuranceTransaction();
case "CashTransaction":
return processCashTransaction();
case "CardTransaction":
return processCardTransaction();
case "TransferTransaction":
return processTransferTransaction();
case "ClientAccount":
return processAccount();
...
default:
throw new IllegalArgumentException(model.getClass().getSimpleName());
}
}
Я знаю, что это очень поздно, но для будущих читателей...
Остерегайтесь вышеприведенных подходов, основанных только на названии класса A, B, C...:
Если вы не можете гарантировать, что A, B, C... (все подклассы или реализации Base) являются окончательными, то подклассы A, B, C... не будут рассматриваться.
Хотя подход if, elseif, elseif.. медленнее для большого числа подклассов / реализаторов, он более точен.
Нет, нет способа сделать это. Однако вы, возможно, захотите рассмотреть полиморфизм как способ решения подобных проблем.
Мне лично нравится следующий код Java 1.8:
mySwitch("YY")
.myCase("AA", (o) -> {
System.out.println(o+"aa");
})
.myCase("BB", (o) -> {
System.out.println(o+"bb");
})
.myCase("YY", (o) -> {
System.out.println(o+"yy");
})
.myCase("ZZ", (o) -> {
System.out.println(o+"zz");
});
Будет выводить:
YYyy
В примере кода используются строки, но вы можете использовать любой тип объекта, включая класс. например .myCase(this.getClass(), (o) -> ...
Требуется следующий фрагмент:
public Case mySwitch(Object reference) {
return new Case(reference);
}
public class Case {
private Object reference;
public Case(Object reference) {
this.reference = reference;
}
public Case myCase(Object b, OnMatchDo task) {
if (reference.equals(b)) {
task.task(reference);
}
return this;
}
}
public interface OnMatchDo {
public void task(Object o);
}
Вы не можете переключаться только с байтовым, коротким, типом char, int, String и перечислимым типом (и объектными версиями примитивов, это также зависит от вашей версии Java, строки могут быть switch
издан в Java 7)
Использование таких операторов переключения не является объектно-ориентированным способом. Вместо этого вы должны использовать силу полиморфизма. Просто пиши
this.do()
Предварительно настроив базовый класс:
abstract class Base {
abstract void do();
...
}
который является базовым классом для A
, B
а также C
:
class A extends Base {
void do() { this.doA() }
}
class B extends Base {
void do() { this.doB() }
}
class C extends Base {
void do() { this.doC() }
}
Если вы можете манипулировать общим интерфейсом, вы можете добавить add в enum и заставить каждый класс возвращать уникальное значение. Вам не понадобится instanceof или шаблон посетителя.
Для меня логика должна быть записана в операторе switch, а не сам объект. Это было мое решение:
ClassA, ClassB, and ClassC implement CommonClass
Интерфейс:
public interface CommonClass {
MyEnum getEnumType();
}
Enum:
public enum MyEnum {
ClassA(0), ClassB(1), ClassC(2);
private int value;
private MyEnum(final int value) {
this.value = value;
}
public int getValue() {
return value;
}
Impl:
...
switch(obj.getEnumType())
{
case MyEnum.ClassA:
ClassA classA = (ClassA) obj;
break;
case MyEnum.ClassB:
ClassB classB = (ClassB) obj;
break;
case MyEnum.ClassC:
ClassC classC = (ClassC) obj;
break;
}
...
Если вы используете Java 7, вы можете поместить строковые значения для enum, и блок регистра переключателей все равно будет работать.
Как насчет этого?
switch (this.name)
{
case "A":
doA();
break;
case "B":
doB();
break;
case "C":
doC();
break;
default:
console.log('Undefined instance');
}
В недавнем выпуске Java 19 это было очень просто и полезно: создайте интерфейс и используйте метод makesomenoise(), который принимает параметр животного.
void makeSomeNoise (Animal animal) {
switch (animal) {
case Dog dog → dog.bark();
case Cat catcat.meow();
default throw new RuntimeException ("WTH is it???");
}
}
Несмотря на то, что невозможно написать оператор switch, возможно перейти к определенной обработке для каждого данного типа. Один из способов сделать это - использовать стандартный механизм двойной отправки. Примером, в котором мы хотим "переключиться" на основе типа, является сопоставитель исключений Jersey, где нам необходимо сопоставить множество исключений с ответами об ошибках. В то время как для этого конкретного случая, вероятно, есть лучший способ (то есть использование полиморфного метода, который переводит каждое исключение в ответ об ошибке), использование механизма двойной отправки все еще полезно и практично.
interface Processable {
<R> R process(final Processor<R> processor);
}
interface Processor<R> {
R process(final A a);
R process(final B b);
R process(final C c);
// for each type of Processable
...
}
class A implements Processable {
// other class logic here
<R> R process(final Processor<R> processor){
return processor.process(this);
}
}
class B implements Processable {
// other class logic here
<R> R process(final Processor<R> processor){
return processor.process(this);
}
}
class C implements Processable {
// other class logic here
<R> R process(final Processor<R> processor){
return processor.process(this);
}
}
Затем, где бы ни понадобился "переключатель", вы можете сделать это следующим образом:
public class LogProcessor implements Processor<String> {
private static final Logger log = Logger.for(LogProcessor.class);
public void logIt(final Processable base) {
log.info("Logging for type {}", process(base));
}
// Processor methods, these are basically the effective "case" statements
String process(final A a) {
return "Stringifying A";
}
String process(final B b) {
return "Stringifying B";
}
String process(final C c) {
return "Stringifying C";
}
}
Я думаю, что есть причины использовать оператор switch. Если вы используете сгенерированный xText код, возможно. Или другой вид классов, генерируемых EMF.
instance.getClass().getName();
возвращает строку с именем реализации класса. то есть: org.eclipse.emf.ecore.util.EcoreUtil
instance.getClass().getSimpleName();
возвращает простое представление, то есть: EcoreUtil
Начиная с Java 17, вы можете использовать сопоставление с образцом для выражений переключения (предварительная функция) JEP-406.
public void doAction(Object o) {
return switch (o) {
case A a -> doA(a);
case B b -> doB(b);
case C c -> doC(c);
default -> log.warn("Unrecognized type of {}", o);
};
}
Также доступен шаблон защиты:
public void doAction(Object o) {
return switch (o) {
case String s && !s.isBlank() -> handle(s);
};
}
Вам необходимо включить функции предварительного просмотра, чтобы использовать его:java --enable-preview
Если вы хотите избежать многословия if(){} else if{}
, вы можете подумать о том, чтобы переключить этот единственный файл на kotlin и использовать выражение, подобное переключателю when, в сочетании сis
оператор.
В любом случае файлы Kotlin и java могут сосуществовать в проекте.
when (this) { //switch-like statement in kotlin supporting class-pattern-matching and smart casts via `is` operator.
is A -> doA()
is B -> doB()
is C -> doC()
}
Существует еще более простой способ эмуляции структуры переключателя, который использует instanceof, вы делаете это, создавая блок кода в своем методе и называя его меткой. Затем вы используете структуры if для эмуляции операторов case. Если дело истинно, тогда вы используете разрыв LABEL_NAME, чтобы выйти из своей структуры временного переключателя.
DEFINE_TYPE:
{
if (a instanceof x){
//do something
break DEFINE_TYPE;
}
if (a instanceof y){
//do something
break DEFINE_TYPE;
}
if (a instanceof z){
// do something
break DEFINE_TYPE;
}
}
Если вам нужно "переключиться" через тип класса "this" объекта, этот ответ лучше всего /questions/7586495/pereklyuchit-instanceof/7586510#7586510
Но если вам нужно применить "переключатель" к любой другой переменной. Я бы предложил другое решение. Определите следующий интерфейс:
public interface ClassTypeInterface {
public String getType();
}
Реализуйте этот интерфейс в каждом классе, который вы хотите "переключить". Пример:
public class A extends Something implements ClassTypeInterface {
public final static String TYPE = "A";
@Override
public String getType() {
return TYPE;
}
}
После этого вы можете использовать его следующим образом:
switch (var.getType()) {
case A.TYPE: {
break;
}
case B.TYPE: {
break;
}
...
}
Единственное, о чем вы должны заботиться - сохранять уникальность "типов" во всех классах, реализующих ClassTypeInterface. Это не большая проблема, потому что в случае любого пересечения вы получаете ошибку времени компиляции для оператора switch-case.
Вот функциональный способ сделать это в Java 8, используя http://www.vavr.io/
import static io.vavr.API.*;
import static io.vavr.Predicates.instanceOf;
public Throwable liftRootCause(final Throwable throwable) {
return Match(throwable).of(
Case($(instanceOf(CompletionException.class)), Throwable::getCause),
Case($(instanceOf(ExecutionException.class)), Throwable::getCause),
Case($(), th -> th)
);
}
Создайте Enum с именами классов.
public enum ClassNameEnum {
A, B, C
}
Найдите имя класса объекта. Напишите регистр переключателя над перечислением.
private void switchByClassType(Object obj) {
ClassNameEnum className = ClassNameEnum.valueOf(obj.getClass().getSimpleName());
switch (className) {
case A:
doA();
break;
case B:
doB();
break;
case C:
doC();
break;
}
}
}
Надеюсь это поможет.
Прежде чем мы получим стабильное сопоставление с образцом для
if (req instanceof UserRequest userReq) {
userReq.doUserStuff();
} else if (req instanceof AdminRequest adminReq) {
adminReq.doAdminStuff();
}
Это освобождает вас от дополнительных операторов кастинга.
Eclipse Modeling Framework имеет интересную идею, которая также учитывает наследование. Основная концепция определена в интерфейсе Switch: переключение выполняется с помощью метода doSwitch.
Что действительно интересно, так это реализация. Для каждого типа интереса,
public T caseXXXX(XXXX object);
метод должен быть реализован (с реализацией по умолчанию, возвращающей ноль). Реализация doSwitch попытается вызвать все методы caseXXX для объекта для всей его иерархии типов. Что-то в строках:
BaseType baseType = (BaseType)object;
T result = caseBaseType(eAttribute);
if (result == null) result = caseSuperType1(baseType);
if (result == null) result = caseSuperType2(baseType);
if (result == null) result = caseSuperType3(baseType);
if (result == null) result = caseSuperType4(baseType);
if (result == null) result = defaultCase(object);
return result;
Фактическая структура использует целочисленный идентификатор для каждого класса, поэтому логика на самом деле является чистым переключателем:
public T doSwitch(Object object) {
return doSwitch(object.class(), eObject);
}
protected T doSwitch(Class clazz, Object object) {
return doSwitch(getClassifierID(clazz), object);
}
protected T doSwitch(int classifierID, Object theObject) {
switch (classifierID) {
case MyClasses.BASETYPE:
{
BaseType baseType = (BaseType)object;
...
return result;
}
case MyClasses.TYPE1:
{
...
}
...
Вы можете посмотреть на полную реализацию ECoreSwitch, чтобы получить лучшую идею.
В Java 21 вы можете использовать «Сопоставление шаблонов для коммутатора». См. https://openjdk.org/jeps/441 . Это больше не функция предварительного просмотра.
Я думаю, мы можем сделать это более естественно без каких-либо изменений, подобных этим:
switch (true) {
case X instanceof A:
// do something;
break;
case X instanceof B:
// do something;
break;
default:
// do something;
break;
}
Надеюсь, поможет! :)