Рейтинговые книги
Читем онлайн Философия Java3 - Брюс Эккель

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 77 78 79 80 81 82 83 84 85 ... 132

Обратите внимание: в некоторых местах нам по-прежнему приходится проверять объекты на определенное состояние, что принципиально не отличается от проверки null, но в других местах, скажем, при преобразованиях toStringO), лишние проверки не нужны; мы просто считаем, что ссылка на объект действительна.

Если вместо конкретных классов используются интерфейсы, для автоматического создания объектов с неопределенным состоянием можно воспользоваться динамическим посредником. Допустим, имеется интерфейс Robot, определяющий имя и модель робота, а также список List<Operation>, определяющий, какие операции выполняет робот. Операция состоит из описания и команды:

//: typeinfo/Operation java

public interface Operation { String description): void commandO; } ///.-

Чтобы воспользоваться услугами робота, следует вызвать метод operations():

//: typeinfo/Robot.java import java.util *; import net.mindview util.*:

public interface Robot { String nameO, String model О; List<Operation> operationsO: class Test {

public static void test(Robot r) { if(r instanceof Null)

System.out.pri ntin("[Nul1 Robot]"). System.out.println("Ha3BaHMe- " + r.nameO); System, out. pri nti n( "Модель " + r.model О). for(Operation operation : r.operationsO) {

System, out. pri nti n(operati on description)): operation.commandO;

}

}

}

} ///.-

При этом используется вложенный класс, выполняющий проверку. Теперь мы можем создать робота для уборки снега:

// typeinfo/SnowRemovalRobot java import java util.*.

public class SnowRemovalRobot implements Robot { private String name.

public SnowRemovalRobot(String name) {this name = name,} public String nameО { return name; } public String model О { return "SnowBot Series 11". } public List<Operation> operations О { return Arrays.asListC

new OperationO {

public String description) {

return name + " может убирать снег",

}

public void commando {

System out.println(name + " убирает снег"),

}

}.

new OperationO {

public String descriptionO {

return name + " может колоть лед".

}

public void commando {

System out println(name + " колет лед");

}

ь

new OperationO {

public String descriptionO {

return name + " может чистить крышу";

}

public void commando {

System out.println(name + " чистит крышу"),

}

}

);

}

public static void main(String[] args) {

Robot Test.test(new SnowRemovalRobot("SIusher"));

}

} /* Output: Название: Slusher Модель: SnowBot Series 11 Slusher может убирать снег Slusher убирает снег Slusher может колоть лед-Si usher колет лед Slusher может чистить крышу Slusher чистит крышу *///:-

Предполагается, что существуют разные типы роботов, и для каждого типа Robot объект с неопределенным состоянием должен делать что-то особенное — в нашем примере выдавать информацию о конкетном типе Robot, представленном объектом. Эта информация перехватывается динамическим посредником:

//: typeinfo/NullRobot.java

// Использование динамического посредника для создания

// объекта с неопределенным состоянием import java.lang.reflect *, import java util *, import net.mindview util.*;

class NullRobotProxyHandler implements InvocationHandler { private String null Name; private Robot proxied = new NRobotO. NullRobotProxyHandler(CIass<? extends Robot> type) {

nullName = type getSimpleNameO + " NullRobot";

}

private class NRobot implements Null, Robot {

public String nameO { return nullName; } public String model О { return nullName; } public List<Operation> operations О {

return Col lections.emptyList();

}

}

public Object

invoke(Object proxy, Method method, Object[] args) throws Throwable {

return method.invoke(proxied, args);

public class NullRobot { public static Robot

newNullRobot(CIass<? extends Robot> type) {

return (Robot)Proxy.newProxyInsta nee(

NullRobot class.getClassLoaderO, new Class[]{ Null.class, Robot.class }, new Nul1RobotProxyHandler(type));

}

public static void main(String[] args) { Robot[] bots = {

new SnowRemova1 Robot("SnowBee"), newNul1 Robot(SnowRemova1 Robot.class)

}:

for(Robot bot : bots)

Robot.Test.test(bot),

}

} /* Output-Название: SnowBee Модель: SnowBot Series 11 SnowBee может убирать снег SnowBee убирает снег SnowBee может колоть лед SnowBee колет лед SnowBee может чистить крышу SnowBee чистит крышу [Null Robot]

Название. SnowRemova1 Robot NullRobot Модель: SnowRemovalRobot NullRobot *///.-

Каждый раз, когда вам требуется объект Robot с неопределенным состоянием, вы вызываете newNuURobotQ и передаете тип Robot, для которого создается

посредник. Посредник выполняет требования о поддержке интерфейсов Robot и Null, а также предоставляет имя опосредованного типа.

Интерфейсы и информация о типах

Одной из важных целей ключевого слова interface является изоляция компонентов и сокращение привязок. Использование интерфейсов вроде бы позволяет добиться этой цели, однако RTTI позволяет обойти ограничения — интерфейсы не обеспечивают стопроцентной изоляции. Начнем следующий пример с интерфейса:

//: typeinfo/interfacea/A.java package typeinfo.interfacea;

public interface A {

void f(); } ///:-

Затем интерфейс реализуется, и выясняется, что можно «в обход» добраться до фактического типа реализации:

//• typei nfo/1nterfaceVi olati on.java // Интерфейс можно обойти import typeinfo.interfacea.*;

class В implements A { public void f() {} public void g() {}

}

public class InterfaceViolation {

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

// a.gO; // Ошибка компиляции System.out.pri ntln(a.getClass().getName()); if(a instanceof B) { В b = (B)a; b.gO;

}

}

} /* Output: В

*///:-

Используя RTTI, мы выясняем, что объект а реализован в форме В. Преобразование к типу В позволяет вызвать метод, не входящий в интерфейс А.

Все это абсолютно законно и допустимо, но, скорее всего, вы предпочли бы оградить клиентских программистов от подобных выходок. Казалось бы, ключевое слово interaface должно защищать вас, но на самом деле этого не происходит, а факт использования В для реализации А становится известен любому желающему.

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

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

//. typeinfo/packageaccess/HiddenC.java package typeinfo.packageaccess; import typeinfo interfacea.*; import static net mindview util.Print *.

class С implements A {

public void f() { print("public С f()"), } public void g() { printCpublic С g()"); } void u() { print ("package C.uO"); } protected void v() { print("protected С v()"). } private void wO { printC'private C.wO"), }

}

public class HiddenC {

public static A makeAO { return new CO; } } ///:-

Единственная открытая (public) часть пакета, HiddenC, выдает интерфейс А при вызове. Интересно отметить, что, даже если makeA() будет возвращать С, за пределами пакета все равно удастся использовать только А, потому что имя С недоступно.

Попытка нисходящего преобразования к С тоже завершается неудачей:

II: typeinfo/Hiddenlmplementation.java // Пакетный доступ тоже можно обойти import typeinfo.interfacea.*; import typeinfo.packageaccess *; import java.lang.reflect *;

public class Hiddenlmplementation {

public static void main(String[] args) throws Exception { . A a = Hi ddenC. makeAO; a.fO;

System.out.pri ntin(a.getClass О.getName()); // Ошибка компиляции, символическое имя 'С' не найдено /* if(a instanceof С) { С с = (С)а; с.дО;

} */

// Однако рефлексия позволяет вызвать д(): callHiddenMethod(a, "д"); // ... И даже еще менее доступные методы! callHiddenMethod(a, "и"); са11 Hi ddenMethod С а, "v"); callHiddenMethod(a, "w");

}

static void call HiddenMethod(Object a, String methodName)

throws Exception { продолжение &

Method g = a getClassO getDeclaredMethod(methodName), g.setAccessible(true). g.invoke(a),

}

} /* Output public C.fO

typeinfo.packageaccess С public С gO package С uO protected C.vO private C.wO */// ~

Как видите, рефлексия позволяет вызвать все методы, даже приватные! Зная имя метода, можно вызвать setAccessible(true) для объекта Method, чтобы сделать возможным его вызов, как видно из реализации caUHiddenMethod().

Можно подумать, что проблема решается распространением только откомпилированного кода, но и это не так. Достаточно запустить javap — декомпилятор, входящий в JDK. Командная строка выглядит так:

javap -private С

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

А если реализовать интерфейс в виде приватного внутреннего класса? Вот как это выглядит:

//: typeinfo/Innerlmplementation java

// Приватные внутренние классы не скрываются от рефлексии

import typeinfo interfacea *;

import static net mindview.util Print.*;

class InnerA {

private static class С implements A {

public void f() { printC'public C.fO"); } public void gO { printC'public C.gO"); } void uO { print("package C.uO"); } protected void v() { print ("protected C.vO"), } private void w() { printC'private С w()"). }

}

public static A makeAO { return new CO; }

}

public class Innerlmplementation {

public static void main(String[] args) throws Exception { A a = InnerA makeAO; a f().

System out. pri ntl n(a getClassO .getNameO); // Рефлексия все равно позволяет добраться до приватного класса: Hiddenlmplementation callHiddenMethod(a. "g"); HiddenImplementation.callHiddenMethod(a, "u"), HiddenImplementation.callHiddenMethod(a, "v"), HiddenImplementation.callHiddenMethod(a. "w");

}

public С f() InnerASC public С g() package С u() protected С v() private С w() */// ~

He помогло. Как насчет анонимного класса?

// typeinfo/AnonymousImplementation java

// Анонимные внутренние классы тоже не скрыты от рефлексии

import typeinfo.interfacea *.

import static net.mindview util Print *,

class AnonymousA {

public static A makeAO { return new AO {

public void fO { printCpublic С f()"), } public void gO { printCpublic С g()n); } void uO { print (package C.uO"), } protected void vO { print ("protected C.vO"). } private void wО { printOprivate С wO"). }

public class AnonymousImplementation {

public static void main(String[] args) throws Exception { A a = AnonymousA.makeAO; a.fO;

System.out.pri ntin(a.getCl ass О.getName()).

// Рефлексия все равно позволяет добраться до приватного класса.

Hiddenlmplementation callHiddenMethod(a, "g"),

Hiddenlmplementation.call HiddenMethod(a, "u");

Hi ddenlmpl ementati on callHiddenMethod(a, Y);

HiddenImplementation.callHiddenMethod(a, "w");

}

} /* Output: public C.fO AnonymousA!1 public C.gO package C.uO protected C.vO private C.wO *///•-

Похоже, не существует никакого способа предотвратить обращение и вызов методов с уровнем доступа, отличным от public, посредством рефлексии. Сказанное относится и к полям данных, даже к приватным:

1 ... 77 78 79 80 81 82 83 84 85 ... 132
На этой странице вы можете бесплатно читать книгу Философия Java3 - Брюс Эккель бесплатно.
Похожие на Философия Java3 - Брюс Эккель книги

Оставить комментарий