Spec-Zone .ru
спецификации, руководства, описания, API
|
Интерфейс | Описание |
---|---|
CloseableStream <T> |
A
CloseableStream a Stream это может быть закрыто. |
Коллектор <T, R> |
Работа сокращения, которая поддерживает входные элементы сворачивания в совокупный результат.
|
DoubleStream |
Последовательность примитивных двойных элементов, поддерживающих последовательные и параллельные объемные операции.
|
IntStream |
Последовательность примитивных целочисленных элементов, поддерживающих последовательные и параллельные объемные операции.
|
LongStream |
Последовательность примитивных длинных элементов, поддерживающих последовательные и параллельные объемные операции.
|
Поток <T> |
Последовательность элементов, поддерживающих последовательные и параллельные объемные операции.
|
StreamBuilder <T> |
Изменчивый разработчик для a
Stream . |
StreamBuilder. OfDouble |
Изменчивый разработчик для a
DoubleStream . |
StreamBuilder. OfInt |
Изменчивый разработчик для
IntStream . |
StreamBuilder. OfLong |
Изменчивый разработчик для a
LongStream . |
Класс | Описание |
---|---|
Коллекторы |
Реализации
Collector та реализация различные полезные операции сокращения, такие как накапливающиеся элементы в наборы, суммируя элементы согласно различным критериям, и т.д. |
DelegatingStream <T> |
A
Stream реализация, которая делегирует операции другому Stream . |
StreamSupport |
Низкоуровневые методы утилиты для создания и управления потоками.
|
Перечисление | Описание |
---|---|
Коллектор. Характеристики |
Характеристики, указывающие на свойства a
Collector , который может использоваться, чтобы оптимизировать реализации сокращения. |
int sumOfWeights = blocks.stream().filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();
Здесь мы используем blocks
, который мог бы быть a Collection
, как источник для потока, и затем выполняют "карту фильтра, уменьшают" (sum()
пример работы сокращения) на потоке, чтобы получить сумму весов красных блоков.
Ключевая абстракция, используемая в этом подходе, Stream
, так же как его примитивные специализации IntStream
, LongStream
, и DoubleStream
. Потоки отличаются от Наборов несколькими способами:
Stream
производит новое Stream
, вместо того, чтобы удалять элементы из базового источника.String
соответствие образца" не должно исследовать все строки ввода.) Операции с потоками делятся на промежуточное звено (Stream
- производящий) операции и терминальные (производящие значение) операции; все промежуточные операции ленивы.limit(n)
или findFirst()
может позволить вычислениям на бесконечных потоках завершаться в конечный промежуток времени.Потоки используются, чтобы создать конвейеры операций. У полного потокового конвейера есть несколько компонентов: источник (который может быть a Collection
, массив, функция генератора, или канал IO); нуль или больше промежуточных операций такой как Stream.filter
или Stream.map
; и терминальная работа такой как Stream.forEach
или java.util.stream.Stream.reduce
. Операции с потоками могут взять в качестве значений функции параметров (которые часто являются лямбда-выражениями, но могли быть ссылками метода или объектами), которые параметризовали поведение работы, такой как a Predicate
переданный к Stream#filter
метод.
Промежуточные операции возвращают новое Stream
. Они ленивы; выполнение промежуточной работы такой как Stream.filter
фактически не выполняет фильтрации, вместо этого создавая новое Stream
это, когда пересечено, содержит элементы начальной буквы Stream
то соответствие данный Predicate
. Потребление элементов из потокового источника не начинается, пока терминальная работа не выполняется.
Терминальные операции используют Stream
и приведите к результату или побочному эффекту. После того, как терминальная работа выполняется, поток больше не может использоваться, и следует возвратиться к источнику данных, или выбрать новый источник данных, чтобы получить новый поток. Например, получение суммы весов всех красных блоков, и затем всех синих блоков, требует, чтобы "карта фильтра уменьшила" на двух различных потоках:
int sumOfRedWeights = blocks.stream().filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();
int sumOfBlueWeights = blocks.stream().filter(b -> b.getColor() == BLUE)
.mapToInt(b -> b.getWeight())
.sum();
Однако, есть другие методы, которые позволяют Вам получать оба результата в единственной передаче, если многократный обход непрактичен или неэффективен. TODO обеспечивают ссылку
Промежуточная операция с потоками (такой как filter
или sorted
) всегда производите новое Stream
, и alwayslazy. Выполнение ленивые операции не инициировало обработку потокового содержания; вся обработка задерживается, пока терминальная работа не начинается. Обработка потоков лениво учитывает существенные полезные действия; в конвейере, таком как пример суммы карты фильтра выше, фильтрация, отображение, и дополнение могут быть сплавлены в единственную передачу с минимальным промежуточным состоянием. Лень также позволяет нам избежать исследовать все данные, когда это не необходимо; для операций тех, которые "находят первую строку дольше чем 1000 символов", один не должен исследовать все строки ввода, как раз чтобы найти тот, у которого есть требуемые характеристики. (Это поведение становится еще более важным, когда входной поток является бесконечным и не просто большим.)
Промежуточные операции далее делятся на и stateful операции не сохраняющие состояние. Операции не сохраняющие состояние не сохраняют состояния от ранее замеченных значений, обрабатывая новое значение; примеры промежуточных операций не сохраняющих состояние включают filter
и map
. Операции Stateful могут включить состояние от ранее замеченных элементов в обработке новых значений; примеры stateful промежуточных операций включают distinct
и sorted
. Операции Stateful, возможно, должны обработать весь ввод прежде, чем привести к результату; например, нельзя произвести следствия сортировки потока, пока каждый не видел все элементы потока. В результате при параллельном вычислении, некоторые конвейеры, содержащие stateful промежуточные операции, должны быть выполнены в многократных передачах. Конвейеры, содержащие промежуточные операции исключительно не сохраняющие состояние, могут быть обработаны в единственной передаче, или последовательный или параллельный.
Далее, некоторые операции считают, закорачивая операции. Промежуточная работа закорачивает, если, когда подарено бесконечный ввод, она может произвести конечный поток в результате. Терминальная работа закорачивает, если, когда подарено бесконечный ввод, она может завершиться в конечный промежуток времени. (Наличие работы замыкания накоротко является необходимым, но не достаточное, условие для обработки бесконечного потока, чтобы обычно завершаться в конечный промежуток времени.) Терминальные операции (такой как forEach
или findFirst
) всегда нетерпеливы (они выполняются полностью прежде, чем возвратиться), и произведите не -Stream
результат, такой как примитивное значение или a Collection
, или имейте побочные эффекты.
Переделывая совокупные операции как конвейер операций на потоке значений, много совокупных операций могут быть более легко параллелизированы. A Stream
может выполниться или в последовательном или параллельно. Когда потоки создаются, они или создаются как последовательные или параллельные потоки; параллельные из потоков могут также быть переключены Stream#sequential()
и BaseStream.parallel()
операции. Stream
реализации в JDK создают последовательные потоки, если параллелизм явно не требуют. Например, Collection
имеет методы Collection.stream()
и Collection.parallelStream()
, которые производят последовательные и параллельные потоки соответственно; другие переносящие поток методы такой как java.util.stream.Streams#intRange(int, int)
произведите последовательные потоки, но они могут быть эффективно параллелизированы, вызывая parallel()
на результате. Набор операций на последовательных и параллельных потоках идентичен. Чтобы выполнить "сумму весов блоков" запрашивают параллельно, мы сделали бы:
int sumOfWeights = blocks.parallelStream().filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();
Единственной разницей между последовательными и параллельными версиями этого примера кода является создание начальной буквы Stream
. Ли a Stream
выполнится в последовательном, или параллельное может быть определено Stream#isParallel
метод. Когда терминальная работа инициируется, весь потоковый конвейер или выполняется последовательно или параллельно, определяется последней работой, которая влияла на последовательно-параллельную ориентацию потока (который мог быть потоковым источником, или sequential()
или parallel()
методы.)
Для результатов параллельных операций быть детерминированными и непротиворечивыми с их последовательным эквивалентом, значения функции, которые передают в различные операции с потоками, должны быть не сохраняющими состояние.
Потоки могут или, возможно, не имеют встретиться порядка. Встретиться порядок определяет порядок, в котором элементы обеспечиваются потоком для конвейера операций. Есть ли встретиться порядок, зависит от источника, промежуточных операций, и терминальной работы. Определенные потоковые источники (такой как List
или массивы), свойственно упорядочиваются, тогда как другие (такой как HashSet
) не. Некоторые промежуточные операции могут наложить встретиться порядок на иначе неупорядоченный поток, такой как Stream.sorted()
, и другие могут представить упорядоченный неупорядоченный поток (такой как BaseStream.unordered()
). Некоторые терминальные операции могут проигнорировать, встречаются с порядком, такой как Stream.forEach(java.util.function.Consumer<? super T>)
.
Если Поток упорядочивается, большинство операций ограничивается работать на элементах в их встречающееся с порядком; если источник потока является a List
содержа [1, 2, 3]
, тогда результат выполнения map(x -> x*2)
должен быть [2, 4, 6]
. Однако, если источник имеет не определенный, встречаются с порядком, чем любая из шести перестановок значений [2, 4, 6]
был бы допустимый результат. Много операций могут все еще быть эффективно параллелизированы даже под упорядочиванием ограничений.
Для последовательных потоков упорядочивание только относится к детерминизму операций, выполняемых неоднократно на том же самом источнике. ( ArrayList
ограничивается выполнить итерации элементов в порядке; a HashSet
не, и повторенная итерация могла бы произвести различный порядок.)
Для параллельных потоков, ослабляя ограничение упорядочивания может включить оптимизированной реализации для некоторых операций. Например, двойная фильтрация на упорядоченном потоке должна полностью обработать первый раздел прежде, чем это сможет возвратить любые элементы из последующего раздела, даже если те элементы доступны ранее. С другой стороны, без ограничения упорядочивания, двойная фильтрация может быть сделана более эффективно при использовании совместно используемого ConcurrentHashSet
. Будут случаи, где поток структурно упорядочивается (источник упорядочивается, и промежуточные операции являются сохранением порядка), но пользователь особенно не заботится о встретиться порядке. В некоторых случаях, явно де-упорядочивая поток с BaseStream.unordered()
метод может привести к улучшенной параллельной производительности для некоторого stateful или терминальных операций.
java.util.stream
пакет позволяет Вам выполнить возможно параллельные операции объемных данных по множеству источников данных, включая даже неориентированные на многопотоковое исполнение наборы такой как ArrayList
. Это возможно, только если мы можем предотвратить интерференцию с источником данных во время выполнения потокового конвейера. (Выполнение начинается, когда терминальная работа вызывается, и заканчивается, когда терминальная работа завершается.) Для большинства источников данных, предотвращая интерференцию означает гарантировать, что источник данных не изменяется вообще во время выполнения потокового конвейера. (Некоторые источники данных, такие как параллельные наборы, специально предназначены, чтобы обработать параллельную модификацию.) Соответственно, лямбда-выражения (или другие объекты, реализовывая соответствующий функциональный интерфейс) передали, чтобы передать методы потоком, никогда не должен изменять источник данных потока. Реализация, как говорят, вмешивается в источник данных, если это изменяет, или вызывает, чтобы быть измененным, источник данных потока. Потребность в невмешательстве применяется ко всем конвейерам, не только параллельным. Если потоковый источник не параллелен, изменяя источник данных потока во время выполнения потокового конвейера может вызвать исключения, неправильные ответы, или несовместимые результаты.
Далее, результаты могут быть недетерминированными или неправильными, если лямбда-выражения, которые передают к операциям с потоками, являются stateful. stateful лямбда (или другой объект, реализовывая соответствующий функциональный интерфейс) является той, результат которой зависит от любого состояния, которое могло бы измениться во время выполнения потокового конвейера. Пример stateful лямбды:
Set<Integer> seen = Collections.synchronizedSet(new HashSet<>());
stream.parallel().map(e -> { if (seen.add(e)) return 0; else return e; })...
Здесь, если отображающаяся работа выполняется параллельно, результаты для того же самого ввода могли бы измениться от выполненного, чтобы работать, из-за различий в планировании потоков, тогда как, с лямбда-выражением не сохраняющим состояние результатами всегда будет то же самое. Конечно, такие операции могут быть с готовностью реализованы как простые последовательные циклы, как в:
int sum = 0;
for (int x : numbers) {
sum += x;
}
Однако, может быть существенное преимущество для предпочтения a reduce operation
по изменчивому накоплению такой, поскольку вышеупомянутые - должным образом созданный уменьшают работу, по сути parallelizable пока reduction operaterator
имеет правильные характеристики. Определенно оператор должен быть ассоциативным. Например, учитывая поток чисел, для которых мы хотим найти сумму, мы можем записать:
int sum = numbers.reduce(0, (x,y) -> x+y);
или более кратко:
int sum = numbers.reduce(0, Integer::sum);
(Примитивные специализации Stream
, такой как IntStream
, даже имейте методы удобства для общих сокращений, такой как sum
и max
, которые реализуются, поскольку простые обертки вокруг уменьшают.)
Сокращение parallellizes хорошо начиная с реализации reduce
может работать на подмножествах потока параллельно, и затем объединить промежуточные результаты получить заключительный корректный ответ. Даже если Вы должны были использовать форму parallelizable forEach()
метод вместо исходного цикла foreach выше, необходимо бы все еще обеспечить ориентированные на многопотоковое исполнение обновления для совместно используемой переменной накопления sum
, и необходимая синхронизация, вероятно, устранила бы любое увеличение производительности из параллелизма. Используя a reduce
метод вместо этого удаляет все бремя параллелизации работы сокращения, и библиотека может обеспечить эффективную параллельную реализацию без дополнительной необходимой синхронизации.
"Блочные" примеры, показанные более ранние шоу, как сокращение объединяется с другими операциями, чтобы заменить для циклов объемными операциями. Если blocks
набор Block
объекты, у которых есть a getWeight
метод, мы можем найти самый тяжелый блок с:
OptionalInt heaviest = blocks.stream()
.mapToInt(Block::getWeight)
.reduce(Integer::max);
В его более общей форме, a reduce
работа на элементах типа <T>
приведение к результату типа <U>
требует трех параметров:
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumlator,
BinaryOperator<U> combiner);
Здесь, нейтральный элемент является и начальным семенем для сокращения, и результатом значения по умолчанию, если нет никаких элементов. Функция аккумулятора берет частичный результат и следующий элемент, и приведите к новому частичному результату. Функция объединителя комбинирует частичные результаты двух аккумуляторов привести к новому частичному результату, и в конечном счете окончательному результату. Эта форма является обобщением формы с двумя параметрами, и является также обобщением карты - уменьшают конструкцию, иллюстрированную выше. Если мы хотели переделать простое sum
пример используя более общую форму, 0
был бы нейтральный элемент, в то время как Integer::sum
был бы и аккумулятор и объединитель. Для примера суммы весов это могло быть переделано как:
int sumOfWeights = blocks.stream().reduce(0,
(sum, b) -> sum + b.getWeight())
Integer::sum);
хотя карта - уменьшает форму, более читаемо и обычно предпочтителен. Обобщенная форма обеспечивается для случаев, где существенная работа может быть оптимизирована далеко, комбинируя отображение и сокращение в единственную функцию. Более формально, identity
значение должно быть идентификационными данными для функции объединителя. Это означает это для всех u
, combiner.apply(identity, u)
равно u
. Дополнительно, combiner
функция должна быть ассоциативной и должна быть совместимой с accumulator
функция; для всех u
и t
, следующее должно содержать:
combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
Collection
или StringBuilder
, поскольку это обрабатывает элементы в потоке. Например, если бы мы хотели взять поток строк и связать их в единственную длинную строку, то мы могли достигнуть этого с обычным сокращением:
String concatenated = strings.reduce("", String::concat)
Мы получили бы требуемый результат, и он будет даже работать параллельно. Однако, мы не могли бы быть довольными производительностью! Такая реализация сделала бы большое строковое копирование, и время выполнения будет O (n^2) в числе элементов. Более производительный подход должен был бы накопить результаты в a StringBuilder
, который является изменчивым контейнером для того, чтобы накопить строки. Мы можем использовать тот же самый метод, чтобы параллелизировать изменчивое сокращение, как мы делаем с обычным сокращением. Изменчивую работу сокращения вызывают collect()
, поскольку это собирает вместе требуемые результаты в контейнер результата такой как StringBuilder
. A collect
работа требует трех вещей: функция фабрики, которая создаст новые экземпляры контейнера результата, накапливающаяся функция, которая обновит контейнер результата, включая новый элемент, и объединяющуюся функцию, которая может взять два контейнера результата и объединить их содержание. Форма этого очень подобна общей форме обычного сокращения:
<R> R collect(Supplier<R> resultFactory,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
Как с reduce()
, преимущество выражения collect
этим абстрактным способом то, что это непосредственно поддается parallelization: мы можем накопить частичные результаты параллельно и затем объединить их. Например, чтобы собрать Строковые представления элементов в потоке в ArrayList
, мы могли записать очевидное последовательное для - каждая форма:
ArrayList<String> strings = new ArrayList<>();
for (T element : stream) {
strings.add(element.toString());
}
Или мы могли использовать parallelizable, собирают форму:
ArrayList<String> strings = stream.collect(() -> new ArrayList<>(),
(c, e) -> c.add(e.toString()),
(c1, c2) -> c1.addAll(c2));
или, замечание, что мы проложили отображающуюся работу под землей в функции аккумулятора, более кратко как:
ArrayList<String> strings = stream.map(Object::toString)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
Здесь, наш поставщик только ArrayList constructor
, аккумулятор добавляет stringified элемент к ArrayList
, и объединитель просто использует addAll
скопировать строки с одного контейнера в другой. Как с регулярной работой сокращения, только прибывает возможность параллелизировать, если условие ассоциативности соблюдают. combiner
ассоциативно если для контейнеров результата r1
, r2
, и r3
:
combiner.accept(r1, r2);
combiner.accept(r1, r3);
эквивалентно
combiner.accept(r2, r3);
combiner.accept(r1, r2);
где эквивалентность означает это r1
оставляется в том же самом состоянии (согласно значению equals
для типов элемента). Точно так же resultFactory
должен действовать как идентификационные данные относительно combiner
так, чтобы для любого контейнера результата r
:
combiner.accept(r, resultFactory.get());
не изменяет состояние r
(снова согласно значению equals
). Наконец, accumulator
и combiner
должно быть совместимым так, что для контейнера результата r
и элемент t
:
r2 = resultFactory.get();
accumulator.accept(r2, t);
combiner.accept(r, r2);
эквивалентно:
accumulator.accept(r,t);
где эквивалентность означает это r
оставляется в том же самом состоянии (снова согласно значению equals
). Три аспекта collect
: поставщик, аккумулятор, и объединитель, часто очень сильно связан, и удобно представить понятие a Collector
как являющийся объектом, который воплощает все три аспекта. Есть a collect
метод, который просто берет a Collector
и возвращает получающийся контейнер. Вышеупомянутый пример для того, чтобы собрать строки в a List
может быть переписан, используя стандарт Collector
как:
ArrayList<String> strings = stream.map(Object::toString)
.collect(Collectors.toList());
Map
, такой как:
Map<Buyer, List<Transaction>> salesByBuyer
= txns.parallelStream()
.collect(Collectors.groupingBy(Transaction::getBuyer));
(где Collectors.groupingBy(java.util.function.Function<? super T, ? extends K>)
служебная функция, которая возвращает a Collector
для того, чтобы сгруппировать наборы элементов, основанных на некотором ключе), может фактически быть контрпроизводительно выполнить работу параллельно. Это то, потому что объединяющийся шаг (объединяющийся один Map
в другого ключом), может быть дорогим для некоторых Map
реализации. Предположите, однако, что контейнер результата, используемый в этом сокращении, был одновременно поддающимся изменению набором - таким как a ConcurrentHashMap
. В этом случае параллельные вызовы аккумулятора могли фактически внести свои результаты одновременно в тот же самый совместно используемый контейнер результата, избавляя от необходимости объединитель объединить отличные контейнеры результата. Это потенциально обеспечивает усиление до параллельной производительности выполнения. Мы вызываем это параллельное сокращение.
A Collector
это поддерживает параллельное сокращение, отмечается с Collector.Characteristics.CONCURRENT
характеристика. Наличие параллельного коллектора является необходимым условием для того, чтобы выполнить параллельное сокращение, но что один не достаточно. Если Вы воображаете многократные аккумуляторы, вносящие результаты в совместно используемый контейнер, порядок, в котором депонируются результаты, недетерминирован. Следовательно, параллельное сокращение только возможно, если упорядочивание не важно для обрабатываемого потока. Stream.collect(Collector)
реализация только выполнит параллельное сокращение если
Collector.Characteristics.CONCURRENT
характеристика, и;Collector.Characteristics.UNORDERED
характеристика.
Map<Buyer, List<Transaction>> salesByBuyer
= txns.parallelStream()
.unordered()
.collect(groupingByConcurrent(Transaction::getBuyer));
(где Collectors.groupingByConcurrent(java.util.function.Function<? super T, ? extends K>)
параллельный компаньон к groupingBy
). Отметьте, что, если важно, чтобы элементы для данного ключа появились в порядке, они появляются в источнике, тогда мы не можем использовать параллельное сокращение, поскольку упорядочивание является одними из жертв параллельной вставки. Мы были бы тогда ограничены реализовать или последовательное сокращение или основанное на слиянии параллельное сокращение.
op
ассоциативно, если следующее содержит:
(a op b) op c == a op (b op c)
Важность этого, чтобы быть параллельной оценке может быть замечена, если мы разворачиваем это до четырех сроков:
a op b op c op d == (a op b) op (c op d)
Таким образом, мы можем оценить (a op b)
параллельно с (c op d)
и затем вызовите op
на результатах. TODO, что делает ассоциативное среднее значение для изменчивых функций объединения? FIXME: мы описали изменчивую ассоциативность выше. Конвейер первоначально создается из spliterator (см. Spliterator
) предоставленный потоковым источником. spliterator покрывает элементы источника и обеспечивает операции обхода элемента для возможно параллельного вычисления. См. методы на <коде> Потоки </код> для конструкции конвейеров, используя spliterators.
Источник может непосредственно предоставить spliterator. Если так, spliterator пересекается, разделяется, или запрашивается для предполагаемого размера после, и никогда прежде, терминальная работа начинается. Строго рекомендуется, чтобы spliterator сообщили о характеристике IMMUTABLE
или CONCURRENT
, или будьте позднее связывание и не свяжите с элементами это покрываете пока не пересечено, разделите или запрошенный для предполагаемого размера.
Если источник не может непосредственно предоставить рекомендуемый spliterator тогда, он может косвенно предоставить spliterator, использующий a Supplier
. spliterator получается от поставщика после, и никогда прежде, терминальная работа потокового конвейера начинается.
Такие требования значительно уменьшают контекст потенциальной интерференции к интервалу, запускающемуся с открытия терминальной работы и окончания созданием результата или побочного эффекта. См. Невмешательство для большего количества деталей. XXX - перемещают следующий в раздел невмешательства
Источник может быть изменен прежде, чем терминальная работа начинается, и те модификации будут отражены в покрытых элементах. Впоследствии, и в зависимости от свойств источника, дальнейшие модификации не могли бы быть отражены и бросок a ConcurrentModificationException
может произойти.
Например, рассмотрите следующий код:
List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
l.add("three");
String s = sl.collect(toStringJoiner(" ")).toString();
Сначала список создается состоящий из двух строк: "один"; и "два". Затем поток создается из того списка. Затем список изменяется, добавляя третью строку: "три". Наконец элементы потока собираются и объединялись. Так как список был изменен перед терминалом collect
работа, начатая результат, будет строкой "один два три". Однако, если список изменяется после того, как терминальная работа начинается, как в:
List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
String s = sl.peek(s -> l.add("BAD LAMBDA")).collect(toStringJoiner(" ")).toString();
тогда a ConcurrentModificationException
будет брошен начиная с peek
работа попытается добавить строку "ПЛОХАЯ ЛЯМБДА" к списку после того, как терминальная работа началась.
Для дальнейшей ссылки API и документации разработчика, см. Java Документация SE. Та документация содержит более подробные, предназначенные разработчиком описания, с концептуальными краткими обзорами, определениями сроков, обходных решений, и рабочих примеров кода.
Авторское право © 1993, 2013, Oracle и/или его филиалы. Все права защищены.
Проект сборка-b92