2005
6
7
8
9
10
11
12
2006
1
2
3
4
5
6
7
8
9
10
11
12
2007
1
2
3
4
5
6
7
8
9
10
11
12
2008
1
2
3
4
5
6
7
8
9
10
11
12
2009
1
2
3
4
5
6
7
8
9
10
11
12
2010
1
2
3
4
5
6
7
8
9
10
11
12
2011
1
2
3
4
5
6
7
8
9
10
11
12
2012
1
2
3
4
5
6
7
8
9
10
11
12
2013
1
2
3
4
5
6
7
8
9
10
11
12
2014
1
2
3
4
5
6
7
8
9
10
11
12
2015
1
2
3
4
5
6
7
8
9
10
11
12
2016
1
2
3
4
5
6
7
8
9
10
11
12
2017
1
2
3
4
5
6
7
8
9
10
11
12
2018
1
2
3
4
5
6
7
8
9
10
11
12
2019
1
2
3
4
5
6
7
8
9
10
11
12
2020
1
2
3
4
5
6
7
8
9
10
11
12
2021
1
うわ、大失態。 jrubyで『JavaのクラスのスーパークラスとしてRubyの サブクラスを作ったときに、Javaクラスのコンストラクタが 呼び出されない』って書いたけど、真っ赤なウソでした。 申し訳ありませんでした。 つーか、オレがRubyのことをよくわかってなかった。 class Parent def initialize p :parent end end class Child < Parent def initialize p :child end end Child.new このとき、出力は: :child となって、これはRuby的にも、jruby的にもまったく 正しい動作。 Rubyでは、スーパークラスのコンストラクタは明示的に 呼ばないといけない: class Parent def initialize p :parent end end class Child < Parent def initialize super() p :child end end Child.new この出力は: :parent :child となる。 サーセンっした。
わかりやすい例で書くと: @x = [1,2,3] @y = [1,2,3] みたいなコードをよく見るんだよね。こんなのは: class Point def initialize(x, y) @x = x @y = y end attr_accessor :x, :y end @points = [Point.new[1, 1], Point.new[2, 2], Point.new[3, 3]] みたいにくくり出す。 PGみたくリスト至上主義だとしても: @points = [[1, 1], [2, 2], [3, 3]] みたいに書く。 ひとつの塊で表現されるべきものはひとつの塊として 表現する。それなのにバラバラにしてたら: @x.size.times do |i| p [@x[i], @y[i]] end みたいなコードを書くハメになる。まず、2つの配列が 必ず同じ要素数を持つという暗黙の前提がある点が気に 入らない。次に、配列のインデックスを何回も使わないと いけない点が気に入らない。 ひとつの塊にまとめておけば: @points.size.times do |i| point = @points[i] p [point.x, point.y] end ちょっとこれはヤラセっぽくって、モダンな言語なら イテレータを使って、インデックスを使わずに済ませる: @points.each do |point| p [point.x, point.y] end こういうのはオブジェクト指向以前の話で。C言語なら 構造体があるし、古典的なPascalにもレコード型がある。 -- と、ここまで書いて、C言語の構造体とか、Pascalの レコード型とかを使った設計のことを何と呼ぶかWikipediaで 調べてみたんだけど、どうもはっきりしない。 例によって日本語ページは貧弱すぎて話にならない。 で、英語ページだと、構造化プログラミングには主に 2つあって、ひとつはダイクストラのヤツで、もうひとつは ジャクソンのヤツ。 http://en.wikipedia.org/wiki/Structured_programming ダイクストラのヤツは、例のgoto害悪論が出てくる話。 これは構造体とは直接は関係なさげ。 で、ジャクソンのほうは、データの話は出てくるんだけど、 これまた構造体の話とは直接関係なさげ。こっちは、 データの流れに沿ってプログラムの構造を決めましょう っていう話。ぶっちゃけ、よくわかんない。 そもそも、ここで構造体と呼んでるものの学術的な 名称がはっきりしない。それを調べてみると、レコード というのが一番通りがいいようだ: http://en.wikipedia.org/wiki/Record_%28computer_science%29 でも、レコードと設計を結びつけるような話は出てこない。 個人的にはレコードからADTへ、そしてOOPへっていうのが スッキリした流れだと思うんだけど、歴史はもっとモヤモヤと したものなのかもしれない。
「東洋経済 2010/1/30号」、「知の技法、出世の作法」 (佐藤優)より: 繰り返しになるが、数学は頭だけでは理解できない。 語学同様に体で覚える技法(テクネー)の性格がある からだ。覚え込むためにはノートとペン(もしくは 鉛筆、シャーペン)が必要だ。 Radiumさんのページ: http://d.hatena.ne.jp/KZR/20080808/p1 に限ったことではなく、最近ひとつの単位として 一万時間というのを耳にする。 10,000時間とはどのくらいの時間だろうか。 1日8時間そのことに携わるとして1250日。 そのうち、1年で250日そのことに携わるとして5年。 実際は1日5時間くらいしか携われないかもしれない。 同様に1年が250日だとすれば8年という時間になる。 この計算でもわかるように、10,000時間というのは、 詰められる数字でもある。もし不眠不休で一心不乱に やったとしたら1年ちょっとで達成できてしまう。 もちろん、健康を害してしまっては元も子もない。 しかし、詰められる数字であることに変わりはない。
例によってSwing Tutorialをjrubyに移してたんですけど、 Javaのほうでメソッドが多重定義されていることに 気づかなくって、延々悩んでました。 もともと、多重定義、好きじゃないんですよね。名前 変えたほうが無難なことが多い気がするんです。 ま、Haskellとかが流行ったらそうもいってられなく なるのかな。
java.util.Collections#binarySearchって面白いメソッド ですね。 Listの中にkeyが見つかったら、そのインデックスを返す っていうのは、よくある検索メソッドと同じなんですけど。 keyが見つからなかったときは、そのkeyをListに挿入したら 何番目になるかを返します。この説明はかなり不正確で、 正しくは: http://java.sun.com/javase/7/docs/api/java/util/Collections.html#binarySearch%28java.util.List,%20T%29 で、サンプル: import java.util.*; class Jv { public static void main(String[] args) { ArrayList list = new ArrayList(); list.add("b"); list.add("c"); list.add("d"); int ret = Collections.binarySearch(list, "c"); System.out.println(ret); ret = Collections.binarySearch(list, "a"); System.out.println(ret); ret = Collections.binarySearch(list, "e"); System.out.println(ret); } } 出力: 1 -1 -4 -- で、Swing TutorialでそのbinarySearchを使ってるとこが あるんだけど、どうしようか悩んだんですよね。でも、 試してみたら何のことはない、jrubyのArrayはListを implementsしてて、Collections#binarySearchにそのまま 渡せたという: include Java java_import java.util.Collections array = ["b", "c", "d"] p Collections.binarySearch(array, "c") p Collections.binarySearch(array, "a") p Collections.binarySearch(array, "e") 出力: 1 -1 -4
include Java java_import java.awt.Color java_import javax.swing.JFrame class ElevatorButtonTest include java.awt.event.ItemListener include java.lang.Runnable def initialize @floorButtons = [] @selectAllButton = nil @floorStatuses ={} end def setContentPane(frame) contentPane = javax.swing.JPanel.new contentPane.setOpaque(true) addFloorButtons(contentPane) addSelectAllButton(contentPane) frame.setContentPane(contentPane) end def addFloorButtons(contentPane) (1..5).each do |i| @floorButtons << addFloorButton(contentPane, i) end end def addFloorButton(contentPane, floor) return addButton(contentPane, floor.to_s) end def addButton(contentPane, title) button = javax.swing.JCheckBox.new(title) button.addItemListener(self) contentPane.add(button) return button end def addSelectAllButton(contentPane) @selectAllButton = addButton(contentPane, "Select All") end def show frame = JFrame.new("Elevator") frame.setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE) setContentPane(frame) frame.pack frame.setVisible(true) end def run show end def itemStateChanged(event) button = event.getSource if button == @selectAllButton doSelectAllAction(event) end end def doSelectAllAction(event) selected = (event.getStateChange == java.awt.event.ItemEvent::SELECTED) if selected saveFloorStatuses @floorButtons.each{|button| button.setSelected(selected)} else restoreFloorStatuses end end def saveFloorStatuses @floorButtons.each do |button| @floorStatuses[button] = button.isSelected end end def restoreFloorStatuses @floorStatuses.each do |button, selected| button.setSelected(selected) end end end javax.swing.SwingUtilities.invokeLater(ElevatorButtonTest.new)
jrubyなんですけど、やっぱり、Javaのクラスから Rubyのサブクラス作るときに、コンストラクタを オーバーライドできないのは、致命的な欠陥ですよね。 たとえば、こんなコードが意図したとおりに動かない: class TabButton < JButton include MouseListener def initialize size = 17 setPreferredSize(Dimension.new(size, size)) setToolTipText("close this tab") setUI(BasicButtonUI.new) setContentAreaFilled(false) setFocusable(false) setBorder(BorderFactory.createEtchedBorder) setBorderPainted(false) addMouseListener(self) setRolloverEnabled(true) end ... 仕方ないから: class TabButton < JButton include MouseListener def init size = 17 setPreferredSize(Dimension.new(size, size)) setToolTipText("close this tab") setUI(BasicButtonUI.new) setContentAreaFilled(false) setFocusable(false) setBorder(BorderFactory.createEtchedBorder) setBorderPainted(false) addMouseListener(self) setRolloverEnabled(true) end ... みたいに初期化用のメソッドを用意して: button = TabButton.new button.init みたいに書くしかない。
う〜む: ~/jruby$ jirb irb(main):001:0> fmt = java.text.DateFormatSymbols.new => #<Java::JavaText::DateFormatSymbols:0xadf5be> irb(main):002:0> months = fmt.getMonths => #<#<Class:01x10d95cd>:0xe5f46e> irb(main):003:0> months[-1] ArgumentError: index out of bounds for java array (-1 for length 13) from (irb):4:in `[]' from (irb):4 結局、Javaの配列はRubyの配列とは別扱いってことなん だろうねぇ。こないだもto_sym使ってRubyの配列を Javaの配列に変換してJava APIに渡すこと書いたけど。 今度は逆のパターンだね。Java APIから返ってくるのは Javaの配列で、それはRubyの配列とは違うと。 あ、でも、Javaの配列はto_aすりゃいいのか: irb(main):004:0> months = months.to_a => ["1\346\234\210", "2\346\234\210", "3\346\234\210", "4\346\234\210", "5\346\2 34\210", "6\346\234\210", "7\346\234\210", "8\346\234\210", "9\346\234\210", "10 \346\234\210", "11\346\234\210", "12\346\234\210", ""] irb(main):005:0> months[-1] => "" ちなみにUTF-8の出力で文字化けしてます。
jrubでJavaクラスのサブクラスを作るとき、 コンストラクタの扱いがよくわかんないんだよね。 Javaクラスのコンストラクタと違うコンストラクタを 作ることはできないみたいだし: class Rule < JComponent def initialize(o, m) @orientation = o @isMetric = m @increment = 0 @units = 0 setIncrementAndUnits end ... initializeもコンストラクタとは微妙に違う扱いに なってて: class DrawingPane < JPanel include MouseListener def initialize @circles = [] @area = Dimension.new(0, 0) addMouseListener(self) end ... みたいのだとaddMouseListenerでエラーになる。 だから、結局、newしてさらに初期化するみたいな 書き方になっちゃう: pane = DrawingPane.new pane.init
JPasswordFieldのTutorialなんだけど: http://java.sun.com/docs/books/tutorial/uiswing/components/passwordfield.html ここにこう書いてある: Security note: Although the JPasswordField class inherits the getText method, you should use the getPassword method instead. Not only is getText less secure, but in the future it might return the visible string (for example, "******") instead of the typed string. To further enhance security, once you are finished with the character array returned by the getPassword method, you should set each of its elements to zero. The preceding code snippet shows how to do this. 最初の段落の話はわかるんだけど、次の段落が何を いってるかわかんない。 ソースコードで確かめたんだけど、JPasswordField#getPasswordが 返すchar[]は、新しくnewされた配列で、JPasswordFieldが 実際に抱えているものじゃない。実際に入力された 文字列を抱えてるのはDocumentオブジェクトだし。 だったら、getPasswordで返された配列をクリアしたって、 あんま意味ない気がするんだけど。 まぁ、あれかな。GCに消されるまでに時間があるかも しれないから、いらなくなったらとっととクリア しときなさいっていう程度の話かな。 いや、でも、それだったら、JPasswordField#clearか なんかを用意したほうがいいと思うけど。 -- 上の中で``zero''ってあるけど、それは'0'のことね。 少なくともTutorialのサンプルではそうなってる。
Swing Tutorialでこんなコードがあった: NumberFormat paymentFormat = NumberFormat.getCurrencyInstance(); JFormattedTextField paymentField = new JFormattedTextField(paymentFormat); これを素直にjrubyで書き直した: paymentFormat = NumberFormat.getCurrencyInstance paymentField = JFormattedTextField.new(paymentFormat) ところが、これがうまく動かない。いろいろ調べてみると、 JFormattedTextFieldのコンストラクタが原因のようだ。 JFormattedTextFieldのコンストラクタはいくつかあるが、 1つの引数を取るものが複数ある: JFormattedTextField(Format format) JFormattedTextField(JFormattedTextField.AbstractFormatter formatter) JFormattedTextField(JFormattedTextField.AbstractFormatterFactory factory) JFormattedTextField(Object value) このうち、上のjrubyのコードで呼び出したコンストラクタは、 もちろん、最初のFormatを受け取るコンストラクタの つもりだった。しかし、それがどういうわけかObjectを 受け取るコンストラクタを呼び出しているようだ。 そこで、TextFieldのコンストラクトとFormatのセットを 分けてやることにした: paymentField = JFormattedTextField.new paymentField.??? しかし、JFormattedTextFieldのAPIドキュメントを見ると、 setFormatなるメソッドは存在しない。あるのはsetFormatter とsetFormatterFactoryだけ。そこでまず、setFormatterを 使ってみた: paymentField = JFormattedTextField.new paymentFormat = NumberFormat.getCurrencyInstance paymentField.setFormatter(NumberFormatter.new(paymentFormat)) ところが、これで実際に動かしてみると、Javaのコードと 動きが異なることがわかった。そこで、JFormattedTextField のソース・コードを見ることにした: public JFormattedTextField(java.text.Format format) { this(); setFormatterFactory(getDefaultFormatterFactory(format)); } というわけで、setFormatterFactoryを使うように修正した: paymentField = JFormattedTextField.new paymentFormat = NumberFormat.getCurrencyInstance factory = DefaultFormatterFactory.new(NumberFormatter.new(paymentFormat)) paymentField.setFormatterFactory(factory) と、まぁ、こういったわけで、jrubyからSwingを使えると いっても、少ないながらも落とし穴はあって、それを 避けるにはJavaの知識が多少なりとも必要になる。 ただ、これはどのGUIライブラリを使うにしてもいえる ことで、Ruby/TkならTclの知識が必要になるし、Gtkなら C言語の知識が必要になる。そこらへんもGUIライブラリを 選ぶときのポイントではある。
Swing TutorialのFileChooserDemo.javaをjrubyで書き 直してたんだけど、こういうメソッドがあった: def createContentPane contentPane = JPanel.new(BorderLayout.new) @log = javax.swing.JTextArea.new(5, 20) @log.setMargin(java.awt.Insets.new(5, 5, 5, 5)) @log.setEditable(false) logScrollPane = javax.swing.JScrollPane.new(@log) @fc = JFileChooser.new @openButton = JButton.new("Open a File...", createImageIcon("images/Open16.gif")) @openButton.addActionListener(self) @saveButton = JButton.new("Save a File...", createImageIcon("images/Save16.gif")) @saveButton.addActionListener(self) buttonPanel = JPanel.new buttonPanel.add(@openButton) buttonPanel.add(@saveButton) contentPane.add(buttonPanel, BorderLayout::PAGE_START) contentPane.add(logScrollPane, BorderLayout::CENTER) return contentPane end まぁ、このままでもよかったんだけど、長いと感じたんで extract methodすることにした。その結果: def createContentPane @fc = JFileChooser.new contentPane = JPanel.new(BorderLayout.new) addButtonPane(contentPane) addLogPane(contentPane) return contentPane end def addButtonPane(contentPane) buttonPanel = JPanel.new @openButton = JButton.new("Open a File...", createImageIcon("images/Open16.gif")) @openButton.addActionListener(self) buttonPanel.add(@openButton) @saveButton = JButton.new("Save a File...", createImageIcon("images/Save16.gif")) @saveButton.addActionListener(self) buttonPanel.add(@saveButton) contentPane.add(buttonPanel, BorderLayout::PAGE_START) end def addLogPane(contentPane) @log = javax.swing.JTextArea.new(5, 20) @log.setMargin(java.awt.Insets.new(5, 5, 5, 5)) @log.setEditable(false) logScrollPane = javax.swing.JScrollPane.new(@log) contentPane.add(logScrollPane, BorderLayout::CENTER) end こういうふうに、大きな部品を先に作って、次にそこに ハマる部品を作って、すぐにハメていくっていう。まぁ、 徹底できてるわけじゃないけどね。 一方で、小さい部品をぜんぶ作って、それがハマる大きな 部品作って、それからハメるっていうのもあるんだけど。 前者がトップダウンなら、後者ボトムアップといえるん だけど。こないだから書いてるように、自分はトップ ダウンのほうが好きなのね。 部品が増えてくると、たくさんある小さい部品をかき 集めて組み立てるっていうのは煩雑になるんだよね。 トップダウンだとコードを見て『あ、ボタン載っける パネルは終わったな、じゃあ、次はテキストエリア 載っけるパネルだな』っていうのがわかりやすい。 新しい部品を追加する場合でも、ボトムアップだと、 生成するところとハメるところと分散しがちなんだけど、 トップダウンだと『ここ』っていうのが特定しやすい。 まぁ、トップダウンっていうより深さ優先っていった ほうがいいかもしれない。
Java Tutorialは、自分の気に入らない書き方してるのが 多くって、たとえば: ButtonGroup group = new ButtonGroup(); final int numButtons = 4; JRadioButton[] radioButtons = new JRadioButton[numButtons]; radioButtons[0] = new JRadioButton("OK (in the L&F's words)"); radioButtons[0].setActionCommand(defaultMessageCommand); radioButtons[1] = new JRadioButton("Yes/No (in the L&F's words)"); radioButtons[1].setActionCommand(yesNoCommand); radioButtons[2] = new JRadioButton("Yes/No " + "(in the programmer's words)"); radioButtons[2].setActionCommand(yeahNahCommand); radioButtons[3] = new JRadioButton("Yes/No/Cancel " + "(in the programmer's words)"); radioButtons[3].setActionCommand(yncCommand); for (int i = 0; i < numButtons; i++) { group.add(radioButtons[i]); } radioButtons[0].setSelected(true); みたいなコード。こういうボタンを配列に突っ込むのは マズい場合が多くって。まぁ、今回はラジオ・ボタン だから、それほどマズくないっちゃないんだけど。 でも、最後に: for (int i = 0; i < numButtons; i++) { group.add(radioButtons[i]); } ってやってるのはダメなのね。 前にも書いたけど、こんなものはループで回さずにベタに 書いたほうがいい: radioButtons[0] = new JRadioButton("OK (in the L&F's words)"); radioButtons[0].setActionCommand(defaultMessageCommand); group.add(radioButtons[0]); radioButtons[1] = new JRadioButton("Yes/No (in the L&F's words)"); radioButtons[1].setActionCommand(yesNoCommand); group.add(radioButtons[1]); radioButtons[2] = new JRadioButton("Yes/No " + "(in the programmer's words)"); radioButtons[2].setActionCommand(yeahNahCommand); group.add(radioButtons[2]); radioButtons[3] = new JRadioButton("Yes/No/Cancel " + "(in the programmer's words)"); radioButtons[3].setActionCommand(yncCommand); group.add(radioButtons[3]); radioButtons[0].setSelected(true); なぜ、こっちのほうがいいかっていうと、関数を切り出し やすくなるから: JRadioButton createRadioButton(String title, String command, ButtonGroup group) { JRadioButton result = new JRadioButton(title); result.setActionCommand(command); group.add(result); } で、これを使って書き直すと: radioButtons[0] = createRadioButton("OK (in the L&F's words)", defaultMessageCommand, group); radioButtons[1] = createRadioButton("Yes/No (in the L&F's words)", yesNoCommand, group); radioButtons[2] = createRadioButton("Yes/No " + "(in the programmer's words)", yeahNahCommand, group); radioButtons[3] = createRadioButton("Yes/No/Cancel " + "(in the programmer's words)", yncCommand, group); っていう具合になる。 ところで、関数の切り出し方は他にも考えられる。 たとえば: JRadioButton[] creareRadioButtons(String[] titles) { JRadioButton[] results = new JRadioButton[titles.length]; for (int i = 0; i < titles; ++i) { results[i] = new JRadioButton(titles[i]); } return results; } void setActionCommands(JRadioButton[] buttons, String[] commands) { for (int i = 0; i < buttons.length; ++i) { buttons[i].setActionCommand(commands); } } けれども、これは悪い書き方だと自分は思ってるのね。 1つのボタンを作るんなら1つのボタンに集中して一気に 作り上げたほうがいい。 GUI作るときは基本的にシーケンシャルなんだよね。 パーツを1つずつ完成させてハメ込んでいくっていう。 そうでないと、パーツを動かしにくくなっちゃう。動かす っていうのは、それをハメ込む場所を移すっていう意味で。
jrubyはむつかしい。 panels = [CrayonPanel.new] symbol = "javax.swing.colorchooser.AbstractColorChooserPanel".to_sym @tcc.setChooserPanels(panels.to_java(symbol)) とか。これは: http://java.sun.com/docs/books/tutorial/uiswing/components/colorchooser.html をjrubyで書き直したときのもんだけど。これやってた とき、他にもClassLoaderがnilになるっていう現象が 出て。前までは: imgURL = self.java_class.class_loader.getResource(path) で動いてたんだけど。どうも親クラスがJavaのクラスの サブクラスだとダメなのかな? 結局: imgURL = java.lang.ClassLoader.getSystemClassLoader.getResource(path) で動いた。これが一番いいのかな。まぁ、ClassLoader なんて、jruby使ってるときは替わるもんじゃないだろうし。 Swingのチュートリアル、書き溜まったら公開してみっかなぁ。
Copyright © 1905 tko at jitu.org