// 问题一:
// java.awt.Rectangle类有2个很有用的方法translate和grow,
// 但可惜的是像java.awt.geom.Ellipse2D这样的类中没有,
// 在scala中, 你可以解决掉这个问题, 定义一个RectangleLike的特质, 假如具体的translate和grow方法,
// 提供任何你需要用来实现的抽象方法, 以便你可以像如下代码这样混入该特质
val egg = new java.awt.geom.Ellipse2D.Double(5, 10, 20, 30 ) with RectangleLike
egg.translate(10, -10)
egg.grow(10, 20)
trait RectangleLike{
//自身类型,表明该特质只能混入java.awt.geom.Ellipse2D.Double的子类
this : java.awt.geom.Ellipse2D.Double =>
translate(x: Double, y: Double){
... //
}
grow(x: Double, y: Double){
...
}
}
import java.awt.Point
/*
* 问题二:
* 通过把scala.math.Ordered[Point]混入java.awt.Point的方式, 定义OrderedPoint类,
* 按辞典方式排序, 也就是说, 如果x < x' 或者x = x'且 y < y' 则 (x,y) < (x', y')则(x,y) < (x',y')。
*/
class OrderedPoint extends java.awt.Point with scala.math.Ordered[Point] {
override def compare(that: Point): Int = {
if ((this.x < that.x) || (this.x == that.x && this.y < that.y)) -1
else if(this.x == that.x && this.y == that.y) 0
else 1
}
}
/*
* 10.3
* 查看BitSet类,将它的所有超类和特质绘制成一张图, 忽略类型参数, 然后给出该特质的线性化规格说明
* 略,等待补充
*/
/*
* 10.4
* 提供一个CryptoLogger类, 将日志消息以凯撒密码加密, 缺省情况下密钥为3, 不过使用者也可以重写它, 提供缺省密钥和-3作为密钥时的使用示例
*/
package example.traits.exercise
trait Logger {
val key = 3
def log(message: String): String
}
// 凯撒密码加密
class CryptoLogger extends Logger {
override def log(message: String): String = {
for( ch <- message) yield if(key >= 0) (97 + (ch - 97 + key) % 26).toChar else (97 + (ch - 97 + key + 26 ) % 26).toChar
}
}
//Test
class TraitTest extends FunSuite with BeforeAndAfter {
before {
println("---------")
}
test("Trait-Exercise-CryptoLogger") {
var logger = new CryptoLogger
var result = logger.log("abc")
println(result)
assert(result === "def")
logger = new CryptoLogger {
override val key: Int = -3
}
result = logger.log("abc")
println(result)
assert(result === "xyz")
}
}
/* 问题五:
* JavaBeans规范里有一种提法叫做属性变更监听器(property change listener),
* 这是bean用来通知其属性变更的标准方式,
* PropertyChangeSupport类对于任何想要支持属性变更监听器的bean而言是个便捷的超类,
* 但可惜已有其他超类的类, 比如JComponent , 必须重新实现相应的方法,
* 将PropertyChangeSupport重新实现为一个特质, 然后把它混入到java.awt.Point类中
* */
import java.beans.{PropertyChangeEvent, PropertyChangeListener}
trait PropertyChangeSupportLike {
lazy val pcs = new java.beans.PropertyChangeSupport(this)
def addPropertyChangeListener(listener: PropertyChangeListener): Unit = pcs.addPropertyChangeListener(listener)
def removePropertyChangeListener(listener: PropertyChangeListener): Unit = pcs.removePropertyChangeListener(listener)
def getPropertyChangeListeners: Array[PropertyChangeListener] = pcs.getPropertyChangeListeners
def addPropertyChangeListener(propertyName: String, listener: PropertyChangeListener): Unit = pcs.addPropertyChangeListener(propertyName, listener)
def removePropertyChangeListener(propertyName: String, listener: PropertyChangeListener): Unit = pcs.removePropertyChangeListener(propertyName, listener)
def getPropertyChangeListeners(propertyName: String): Array[PropertyChangeListener] = pcs.getPropertyChangeListeners(propertyName)
def firePropertyChange(propertyName: String, oldValue: Object, newValue: Object): Unit = pcs.firePropertyChange(propertyName, oldValue, newValue)
def firePropertyChange(propertyName: String, oldValue: Int, newValue: Int): Unit = pcs.firePropertyChange(propertyName, oldValue, newValue)
def firePropertyChange(propertyName: String, oldValue: Boolean, newValue: Boolean): Unit = pcs.firePropertyChange(propertyName, oldValue, newValue)
def firePropertyChange(event: PropertyChangeEvent): Unit = pcs.firePropertyChange(event)
def fireIndexedPropertyChange(propertyName: String, index: Int, oldValue: Object, newValue: Object): Unit = fireIndexedPropertyChange(propertyName, index, oldValue, newValue)
def fireIndexedPropertyChange(propertyName: String, index: Int, oldValue: Int, newValue: Int): Unit = fireIndexedPropertyChange(propertyName, index, oldValue, newValue)
def fireIndexedPropertyChange(propertyName: String, index: Int, oldValue: Boolean, newValue: Boolean): Unit = fireIndexedPropertyChange(propertyName, index, oldValue, newValue)
def hasListeners(propertyName: String): Boolean = pcs.hasListeners(propertyName)
}
//Test
class TraitTest extends FunSuite with BeforeAndAfter {
before {
println("---------")
}
test("Trait-Exercise-PropertyChangeSupport") {
val point = new java.awt.Point(2, 3) with PropertyChangeSupportLike
point.addPropertyChangeListener("x", (evt: PropertyChangeEvent) => {
println(evt)
})
point.x = 3
point.firePropertyChange("x", 2, 3)
}
}
/*
* 问题六:
* 在Java AWT类库中, 我们有一个Container类, 一个可以用于各种组件的Component子类, 举例来说,
* Button是一个Component, 但Panel是Container, 这是一个运转中的组合模式,
* Swaing有JComponent和JContainer,但如果你仔细看的话,你会发现一些奇怪的细节,
* 尽管把其他组件添加到比如JButton中毫无意义, JComponent依然扩展自Container,
* Swing的设计者们理想情况下应该会更倾向于图10-4中的设计,
* 但在Java中那是不可能的, 请解释这是为什么?Scala中如何用特质来设计出这样的效果呢?
*/
// 答:Java语言、Scala语言,仅仅单继承。JContainer不能同时继承JComponent和Container
//使用trait
trait Componet {}
trait JComponet extends Componet{}
class JButton extends JComponet{}
trait Container extends Component{}
trait JContainer extends JComponent with Container{}
class JPanel extends JContainer{}
// 问题八:
// 在java.io类库中,你可以通过BufferedInputStream修饰器来给输入流增加缓冲机制。
// 用特质来重新实现缓冲。简单起见, 重写read方法
import java.io.{IOException, InputStream}
trait BufferedInputStreamLike {
this: InputStream =>
protected val bufSize: Int = BufferedInputStreamLike.DEFAULT_BUFFER_SIZE
protected var pos: Int = 0
protected var count: Int = 0
protected val buf = new Array[Byte](bufSize)
override def read(): Int = synchronized{
if (pos >= count) {
fill()
if (pos >= count)
return -1
}
val buffer = getBufIfOpen
pos += 1
buffer(pos - 1)
}
private def fill(): Unit = {
count = 0
pos = 0
val n = this.read(getBufIfOpen, 0, buf.length)
if (n > 0)
count += n
}
private def getBufIfOpen: Array[Byte] = {
if (buf == null) new IOException("Stream closed")
buf
}
}
object BufferedInputStreamLike {
val DEFAULT_BUFFER_SIZE = 8192
}
class TraitTest extends FunSuite with BeforeAndAfter {
before {
println("---------")
}
test("BufferedInputStreamLike") {
var start = System.nanoTime()
val fileInputStream = new FileInputStream("/home/jack/top")
for (i <- 1 to 100000) fileInputStream.read()
var end = System.nanoTime()
println(s"Without\tBufferedInputStreamLike, cost time:${end -start} ns" )
fileInputStream.close()
// Part II
start = System.nanoTime()
val bufferedInputStream: FileInputStream with BufferedInputStreamLike = new {val bufferSize = 100000} with FileInputStream("/home/jack/top") with BufferedInputStreamLike
for (i <- 1 to 100000) bufferedInputStream.read()
end = System.nanoTime()
println(s"With\tBufferedInputStreamLike, cost time:${end -start} ns" )
bufferedInputStream.close()
}
// sbt shell
// testOnly Trai* -- -z "InputStreamLike"
/*
---------
Without BufferedInputStreamLike, cost time:54517999 ns
With BufferedInputStreamLike, cost time:19849703 ns
[info] TraitTest:
[info] - BufferedInputStreamLike
[info] Run completed in 191 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 0 s, completed Aug 2, 2018 5:01:07 PM
*/
package example.traits
trait Logger {
def log(msg: String): Unit ={}
}
package example.traits
trait ConsoleLogger extends Logger {
override def log(msg: String): Unit = {
println(msg)
}
}
package example.traits.exercise
import java.io.{IOException, InputStream}
trait BufferedInputStreamLike {
/** trait指定自类型 **/
this: InputStream with example.traits.Logger =>
protected val bufSize: Int = BufferedInputStreamLike.DEFAULT_BUFFER_SIZE
protected var pos: Int = 0
protected var count: Int = 0
protected val buf = new Array[Byte](bufSize)
override def read(): Int = synchronized {
if (pos >= count) {
fill()
if (pos >= count)
return -1
}
val buffer = getBufIfOpen
pos += 1
buffer(pos - 1)
}
private def fill(): Unit = {
count = 0
pos = 0
val n = this.read(getBufIfOpen, 0, buf.length)
if (n > 0) {
count += n
log("buffered %d bytes: %s".format(n, buf.mkString(", "))) /**添加日志功能**/
}
}
private def getBufIfOpen: Array[Byte] = {
if (buf == null) new IOException("Stream closed")
buf
}
}
object BufferedInputStreamLike {
val DEFAULT_BUFFER_SIZE = 8192
}
test("BufferedInputStreamLike - ConsoleLogger") {
// Part II
val start = System.nanoTime()
val bufferedInputStream = new {
override val bufSize = 20
} with FileInputStream("/home/jack/top") with BufferedInputStreamLike with ConsoleLogger
for (i <- 1 to 10) bufferedInputStream.read()
val end = System.nanoTime()
println(s"With\tBufferedInputStreamLike, cost time:${end - start} ns")
bufferedInputStream.close()
}
/** sbt shell 日志
[IJ]sbt:scala-tutorial> testOnly Trai* -- -z "ConsoleLogger"
---------
buffered 20 bytes: 85, 83, 69, 82, 32, 32, 32, 32, 32, 32, 32, 80, 73, 68, 32, 37, 67, 80, 85, 32
With BufferedInputStreamLike, cost time:774421 ns
[info] TraitTest:
[info] - BufferedInputStreamLike - ConsoleLogger
[info] Run completed in 107 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 0 s, completed Aug 2, 2018 5:32:07 PM
*/
package example.traits.exercise
// java.io.FileInputStream 存在native方法:
// private native int read0() throws IOException;
// private native int readBytes(byte b[], int off, int len) throws IOException;
// private native int available0() throws IOException;
// SocketInputStream也存在类似native方法
// 但是本例中IterableInputStream,缺少相关的native方法。
// 本题,仅是了解概念,IterableInputStream 无法实例化。
abstract class IterableInputStream extends java.io.InputStream with Iterable[Byte] {
class IterableInputStreamIterator(inputStream: IterableInputStream) extends Iterator[Byte] {
override def hasNext: Boolean = inputStream.available() > 0
override def next(): Byte = inputStream.read().toByte
}
override def iterator: Iterator[Byte] = new IterableInputStreamIterator(this)
}
Github 答案参考
《快学scala》习题解答-第十章-特质