русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

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


Дата добавления: 2015-06-12; просмотров: 718; Нарушение авторских прав


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

//: typeinfo/interfacea/A.java

package typeinfo.interfaces;

public interface A {

void f();

}

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

//: typeinfo/InterfaceViolation.java

// Интерфейс можно обойти

import typeinfo.interfacea.*;

 

class B implements A {

public void f() {}

public void g() {}

}

 

public class InterfaceViolation {

public static void main(String[] args) {

A a = new B();

a.f();

// a.g(); // // Ошибка компиляции

System.out.println(a.getClass().getName());

if(a instanceof B) {

B b = (B)a;

b.g();

}

}

}

<spoiler text="Output:">

В

</spoiler> Используя RTTI, мы выясняем, что объект а реализован в форме В. Преобразование к типу В позволяет вызвать метод, не входящий в интерфейс А. Все это абсолютно законно и допустимо, но, скорее всего, вы предпочли бы оградить клиентских программистов от подобных выходок. Казалось бы, ключевое слово interaface должно защищать вас, но на самом деле этого не происходит, а факт использования В для реализации А становится известен любому желающему. Одно из возможных решений: просто скажите программистам, что если они будут использовать фактический класс вместо интерфейса, то пускай сами разбираются со всеми возникающими проблемами. Вероятно, во многих случаях этого достаточно, но если «вероятно» вас не устраивает — можно применить более жесткие меры. Проще всего установить для реализации пакетный уровень доступа, чтобы она оставалась невидимой для клиентов за пределами пакета:



//: typeinfo/packageaccess/HiddenC.java

package typeinfo.packageaccess;

import typeinfo.interfacea.*;

import static net.mindview.util.Print.*;

 

class C implements A {

public void f() { print("public C.f()"); }

public void g() { print("public C.g()"); }

void u() { print("package C.u()"); }

protected void v() { print("protected C.v()"); }

private void w() { print("private C.w()"); }

}

 

public class HiddenC {

public static A makeA() { return new C(); }

}

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

//: typeinfo/HiddenImplementation.java

// Пакетный доступ тоже можно обойти

import typeinfo.interfacea.*;

import typeinfo.packageaccess.*;

import java.lang.reflect.*;

 

public class HiddenImplementation {

public static void main(String[] args) throws Exception {

A a = HiddenC.makeA();

a.f();

System.out.println(a.getClass().getName());

// Ошибка компиляции, символическое имя 'С' не найдено

/* if(a instanceof C) {

C c = (C)a;

c.g();

} */

// Однако рефлексия позволяет вызвать g():

callHiddenMethod(a, "g");

// ... И даже еще менее доступные методы!

callHiddenMethod(a, "u");

callHiddenMethod(a, "v");

callHiddenMethod(a, "w");

}

static void callHiddenMethod(Object a, String methodName)

throws Exception {

Method g = a.getClass().getDeclaredMethod(methodName);

g.setAccessible(true);

g.invoke(a);

}

}

<spoiler text="Output:">

public C.f()

typeinfo.packageaccess.C

public C.g()

package C.u()

protected C.v()

private C.w()

</spoiler> Как видите, рефлексия позволяет вызвать все методы, даже приватные! Зная имя метода, можно вызвать setAccessible(true) для объекта Method, чтобы сделать возможным его вызов, как видно из реализации callHiddenMethod(). Можно подумать, что проблема решается распространением только откомпилированного кода, но и это не так. Достаточно запустить javap — декомпилятор, входящий в JDK. Командная строка выглядит так:

javap -private С

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

//: typeinfo/InnerImplementation.java

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

import typeinfo.interfacea.*;

import static net.mindview.util.Print.*;

 

class InnerA {

private static class C implements A {

public void f() { print("public C.f()"); }

public void g() { print("public C.g()"); }

void u() { print("package C.u()"); }

protected void v() { print("protected C.v()"); }

private void w() { print("private C.w()"); }

}

public static A makeA() { return new C(); }

}

 

public class InnerImplementation {

public static void main(String[] args) throws Exception {

A a = InnerA.makeA();

a.f();

System.out.println(a.getClass().getName());

// Reflection still gets into the private class:

HiddenImplementation.callHiddenMethod(a, "g");

HiddenImplementation.callHiddenMethod(a, "u");

HiddenImplementation.callHiddenMethod(a, "v");

HiddenImplementation.callHiddenMethod(a, "w");

}

}

<spoiler text="Output:">

public C.f()

InnerA$C

public C.g()

package C.u()

protected C.v()

private C.w()

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

//: typeinfo/AnonymousImplementation.java

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

import typeinfo.interfacea.*;

import static net.mindview.util.Print.*;

 

class AnonymousA {

public static A makeA() {

return new A() {

public void f() { print("public C.f()"); }

public void g() { print("public C.g()"); }

void u() { print("package C.u()"); }

protected void v() { print("protected C.v()"); }

private void w() { print("private C.w()"); }

};

}

}

 

public class AnonymousImplementation {

public static void main(String[] args) throws Exception {

A a = AnonymousA.makeA();

a.f();

System.out.println(a.getClass().getName());

// Reflection still gets into the anonymous class:

HiddenImplementation.callHiddenMethod(a, "g");

HiddenImplementation.callHiddenMethod(a, "u");

HiddenImplementation.callHiddenMethod(a, "v");

HiddenImplementation.callHiddenMethod(a, "w");

}

}

<spoiler text="Output:">

public C.f()

AnonymousA$1

public C.g()

package C.u()

protected C.v()

private C.w()

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

//: typeinfo/ModifyingPrivateFields.java

import java.lang.reflect.*;

 

class WithPrivateFinalField {

private int i = 1;

private final String s = "I'm totally safe";

private String s2 = "Am I safe?";

public String toString() {

return "i = " + i + ", " + s + ", " + s2;

}

}

 

public class ModifyingPrivateFields {

public static void main(String[] args) throws Exception {

WithPrivateFinalField pf = new WithPrivateFinalField();

System.out.println(pf);

Field f = pf.getClass().getDeclaredField("i");

f.setAccessible(true);

System.out.println("f.getInt(pf): " + f.getInt(pf));

f.setInt(pf, 47);

System.out.println(pf);

f = pf.getClass().getDeclaredField("s");

f.setAccessible(true);

System.out.println("f.get(pf): " + f.get(pf));

f.set(pf, "No, you're not!");

System.out.println(pf);

f = pf.getClass().getDeclaredField("s2");

f.setAccessible(true);

System.out.println("f.get(pf): " + f.get(pf));

f.set(pf, "No, you're not!");

System.out.println(pf);

}

}

<spoiler text="Output:">

i = 1, I'm totally safe, Am I safe?

f.getInt(pf): 1

i = 47, I'm totally safe, Am I safe?

f.get(pf): I'm totally safe

i = 47, I'm totally safe, Am I safe?

f.get(pf): Am I safe?

i = 47, I'm totally safe, No, you're not!

</spoiler> Впрочем, final-поля защищены от изменений. Система времени выполнения спокойно воспринимает любые попытки их изменения, но при этом ничего не происходит. В действительности все эти нарушения уровня доступа не так уж страшны. Если кто-то захочет вызывать методы, которым вы назначили приватный или пакетный доступ (тем самым ясно показывая, что вызывать их не следует), вряд ли он станет жаловаться на то, что вы изменили некоторые аспекты этих методов. С другой стороны, «черный ход» к внутреннему устройству класса позволяет решить некоторые проблемы, нерешаемые другими средствами, и в общем случае преимущества рефлексии неоспоримы.

Резюме

Динамическое определение типов (RTTI) позволяет вам получить информацию о точном типе объекта тогда, когда у вас для него имеется лишь ссылка базового типа. Таким образом, оно открывает широкие возможности для злоупотреблений со стороны новичков, которые еще не поняли и не успели оценить всю мощь полиморфизма. У многих людей, ранее работавших с процедурными языками, возникает сильное желание разбить свою программу на множество конструкций switch при помощи RTTI. Однако при этом они лишаются всех преимуществ полиморфизма, относящихся к разработке программы в целом и ее дальнейшей поддержке. В Java рекомендуется использовать именно полиморфные методы, а к услугам RTTI следует прибегать только в крайнем случае.

Впрочем, при использовании полиморфных методов требуется полный контроль над базовым классом, поскольку в некоторой точке программы, после наследования очередного класса, вы можете обнаружить, что базовый класс не со­держит нужного вам метода, и тогда RTTIвас выручит: при наследовании вы расширяете интерфейс класса, добавляя в него новые методы. Особенно верно это при использовании в качестве базовых классов библиотек, которые вы не можете изменить. Далее в своем коде в подходящий момент вы обнаруживаете новый тип и вызываете для него нужный метод. Такой подход не противоречит основам полиморфизма и расширяемости программы, так как добавление в программу нового типа не требует изменения бесчисленного множества конструкций switch. Но чтобы извлечь пользу из дополнительной функциональности нового класса, придется использовать RTTI.

Включение некоторого метода в базовый класс будет выгодно только одному производному классу, который действительно реализует его, но все остальные производные классы будут вынуждены использовать для этого метода какую-либо бесполезную «заглушку». Интерфейс базового класса «размывается» и раздражает тех, кому приходится переопределять ненужные абстрактные методы при наследовании от базового класса. Например, рассмотрим иерархию классов, представляющих музыкальные инструменты. Предположим, что вы хотите прочистить мундштуки духовых инструментов своего оркестра. Конечно, можно поместить в базовый класс Instrument (общее представление музыкального инструмента) еще один метод clearSpitValve() (прочистка мундштуков), но тогда получится, что и у синтезатора, и у барабана есть мундштук! С помощью RTTI можно получить гораздо более верное решение данной задачи, поскольку этот метод уместно поместить в более конкретный класс (например, в класс Wind, базовый для всех духовых инструментов). Однако еще более разумным стало бы включение в класс Instrument метода prepareInstrument() (подготовить инструмент к игре), который подошел бы всем инструментам без исключения. На первый взгляд можно было бы ошибочно решить, что в данном случае без RTTI не обойтись.

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

Мы также видели, что рефлексия открывает перед программистом множество новых возможностей и делает возможным более динамичный стиль программирования. Пожалуй, динамическая природа рефлексии кому-то покажется пу­гающей. Для тех, кто привык к безопасной статической проверке типов, сама возможность выполнения действий, правильность которых проверяется только на стадии выполнения, а для выдачи информации используются исключения, выглядит шагом в неверном направлении. Некоторые доходят до утверждений, будто сама возможность исключения на стадии выполнения свидетельствует о том, что такого кода лучше избегать. На мой взгляд, чувство безопасности весьма иллюзорно — неожиданности и исключения возможны всегда, даже если программа не содержит блоков try и спецификации исключений. Предпочитаю думать, что существование логически целостной модели выдачи информации об ошибках дает возможность писать динамический код с использованием рефлексии. Конечно, всегда желательно писать код со статической проверкой... когда это возможно. И все же динамический код является одной из важнейших особенностей, отделяющих Java от таких традиционных языков, как C++.


 



<== предыдущая лекция | следующая лекция ==>
Объекты с неопределенным состоянием | Глава 14 ПАРАМЕТРИЗАЦИЯ


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.336 сек.