Ответ 1
dI не думаю, что что-то особенно плохое в использовании классов Java, которые предназначены для работы в императивной моде в том виде, в каком они были разработаны. Idiomatic Scala включает в себя возможность использования идиоматической Java, как и предполагалось, даже если стили немного соприкасаются.
Однако, если вы хотите - возможно, как упражнение, или, возможно, потому, что он немного разъясняет логику, - чтобы сделать это более функциональным способом без var-free, вы можете это сделать. В 2.8 это особенно приятно, поэтому, хотя вы используете 2.7.7, я дам 2.8-й ответ.
Во-первых, нам нужно настроить проблему, которую вы сделали не полностью, но предположим, что у нас есть что-то вроде этого:
import java.io._
import java.util.zip._
import scala.collection.immutable.Stream
val fos = new FileOutputStream("new.zip")
val zipOut = new ZipOutputStream(new BufferedOutputStream(fos))
val zipIn = new ZipInputStream(new FileInputStream("old.zip"))
def entryIsValid(ze: ZipEntry) = !ze.isDirectory
Теперь, учитывая это, мы хотим скопировать zip файл. Уловкой, которую мы можем использовать, является метод continually
в collection.immutable.Stream
. То, что он делает, это выполнить лениво-оцененный цикл для вас. Затем вы можете взять и отфильтровать результаты, чтобы завершить и обработать то, что вы хотите. Это удобный шаблон для использования, когда у вас есть что-то, что вы хотите быть итератором, но это не так. (Если элемент обновляется, вы можете использовать .iterate
в Iterable
или Iterator
- это, как правило, даже лучше.) Здесь приложение в этом случае используется дважды: один раз, чтобы получить записи, и один раз для чтения/записи куски данных:
val buffer = new Array[Byte](1024)
Stream.continually(zipIn.getNextEntry).
takeWhile(_ != null).filter(entryIsValid).
foreach(entry => {
zipOut.putNextEntry(new ZipEntry("subdir/"+entry.getName))
Stream.continually(zipIn.read(buffer)).takeWhile(_ != -1).
foreach(count => zipOut.write(buffer,0,count))
})
}
zipIn.close
zipOut.close
Обратите внимание на .
в конце некоторых строк! Обычно я пишу это на одной длинной строке, но лучше сделать это, чтобы вы могли увидеть все это здесь.
На всякий случай неясно, разрешите распаковать одно из видов использования continually
.
Stream.continually(zipIn.read(buffer))
Это требует постоянного вызова zipIn.read(buffer)
столько раз, сколько необходимо, сохраняя полученное целое число.
.takeWhile(_ != -1)
Это указывает, сколько раз необходимо, возвращая поток неопределенной длины, но который будет закрыт, когда он достигнет значения -1
.
.foreach(count => zipOut.write(buffer,0,count))
Это обрабатывает поток, беря каждый элемент по очереди (счетчик) и используя его для записи буфера. Это работает немного проницательным способом, поскольку вы полагаетесь на то, что zipIn
только что был вызван, чтобы получить следующий элемент потока - если вы попытались сделать это снова, а не на одном проходе через поток, это будет терпеть неудачу, потому что buffer
будет перезаписан. Но здесь все в порядке.
Итак, вот он: немного более компактный, возможно, более понятный, возможно, менее понятный метод, более функциональный (хотя все еще есть побочные эффекты). В отличие от этого, в 2.7.7, я бы сделал это на Java, потому что Stream.continually
недоступен, а накладные расходы на создание пользовательского Iterator
не стоит в этом случае. (Было бы неплохо, если бы я собирался делать больше обработки файлов zip и мог повторно использовать код.)
Изменить: метод поиска в режиме "доступный к нулю" является нечетким для обнаружения конца zip файла. Я думаю, что "правильный" способ - подождать, пока вы не вернете null
из getNextEntry
. Имея это в виду, я отредактировал предыдущий код (был takeWhile(_ => zipIn.available==1)
, который теперь является takeWhile(_ != null)
) и предоставил версию 2.7.7 на итераторе ниже (обратите внимание, насколько мала основная петля, как только вы пройдете работа по определению итераторов, которые, по общему признанию, используют vars):
val buffer = new Array[Byte](1024)
class ZipIter(zis: ZipInputStream) extends Iterator[ZipEntry] {
private var entry:ZipEntry = zis.getNextEntry
private var cached = true
private def cache { if (entry != null && !cached) {
cached = true; entry = zis.getNextEntry
}}
def hasNext = { cache; entry != null }
def next = {
if (!cached) cache
cached = false
entry
}
}
class DataIter(is: InputStream, ab: Array[Byte]) extends Iterator[(Int,Array[Byte])] {
private var count = 0
private var waiting = false
def hasNext = {
if (!waiting && count != -1) { count = is.read(ab); waiting=true }
count != -1
}
def next = { waiting=false; (count,ab) }
}
(new ZipIter(zipIn)).filter(entryIsValid).foreach(entry => {
zipOut.putNextEntry(new ZipEntry("subdir/"+entry.getName))
(new DataIter(zipIn,buffer)).foreach(cb => zipOut.write(cb._2,0,cb._1))
})
zipIn.close
zipOut.close