Head First Java 十 十一

第十章 Swing使用

上章看到的所有小东西,小widget,都应叫做component,组件,都继承自javax.swing.JComponent包。

在Swing中,几乎所有的组件,都是能够嵌套的。但一般是把小组件,嵌到一个大的比如panel上,然后嵌入frame。但是,如果你非要把panel,嵌到一个button里,也是可以的。

谁来做交互组件,谁来当背景都是人为定的,panel也可以与人交互,只是很少用。

Layout Manager

一个组件的布局管理器,只管理嵌在该组件上的组建的布局问题,即大小、位置等,无法越级管理。比如下面:
Panel嵌在panelA上,panelB上又有三个button。这时候,panelA的布局管理器只能管理panelB的大小,和位置,而对于三个button的大小和位置它无能为力。

最常用的三个布局管理器:
BorderLayout,它是Frame的默认布局管理器。
FlowLayout,它是panel的默认布局管理器
BoxLayout

对于BorderLayout

它就只关心上下左右和中间,开始这么大的button。
现在你要扩大它,Jbutton button = new JButton("Click like you mean it");
即在button上多写了些字,于是,button的大小发生了变化。
宽度是变了,但是高度必须受到Frame的制约。
注意:也就是说,对于BoarderLayout,在东西方向时候,可以得到想要的宽度,但是高度必须受限;而南北方向时候,可以尽可能得到想要的高度,但是宽度受限。
而在中间时候,剩下多少空间,就占多少空间。

对于FlowLayout

它是panel默认的布局管理器。

panel可以改颜色。
JPanel panel = new JPanel();
panel.setBackground(Color.darkGray);

我们先创建一个panel,然后嵌入frame,让panel背景色为灰色。
然后我们加入一个button,panel会立即变大,并满足button的需求,在宽度的需求。
然后在此基础上,再给panel添加一个按钮。
panel会自动排列这个按钮的位置,横着排列,而不是竖着。且发现FlowLayout是满足了宽度的需求,高度不管。

注意:看第二个按钮bliss,它的大小比第一个按钮小,因为内容的字少。即是说,FlowLayout会给够位置,但是只是刚好给够,不会再多。

对于BoxLayout

设置panel的Layout为BoxLayout。
这里的BoxLayout.Y_AXIS指的是纵着排列,即 BoxLayout横纵可控,不过一般是纵着。

这块也是,第二个button的宽度小一点,仅仅刚好满足。

注意:Frame的Layout不好更改,如果想换Frame的Layout的话,比较简单的方法是。把Panel调到你想要的Layout,然后把该放的组件都放好后,执行myFrame.setContentPane(myPanel);即把整个Pane换为了myPanel。

注意:可以通过setLayout(null)把Layout关掉,但是你就必须要手动画出各个组件的位置,还有大小了。

Swing中的其他组件

JTextField
Constructors
     JTextField field = new JTextField(20); 20表示20列,不是宽度,宽度自动的
     JTextField field = new JTextField("Your name");

怎么用:
1、取Text
     System.out.println(field.getText());
2、放Text
     field.setText("whatever");
     field.setText("");     clears the field 清除TextField
3、当用户按回车的时候,产生一个ActionEvent
     field.addActionListener(myActionListener);
4、选择/高亮一个选项
     field.selectAll();
5、将光标放置在TextField里,即用户可以立即打字
     field.requestFocus();

JTextArea
与JTextField区别在于,这个有多行,JTextField只有一行。

Constructor 
     JTextArea text = new JTextArea(10, 20); 10行 20列,高宽都是preffered,即自动定的。

怎么用:
1、设置滚动条只能纵向滚动
     JScrollPane scroller = new JScrollPane(text);   注意这里把Text放入ScrollPane
     text.setLineWrap(true);

     scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.
          VERTICAL_SCROLLBAR_ALWAYS);
     scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.
          HORIZONTAL_SCROLLBAR_NEVER);
看到上面两句话没,一个ALWAYS,一个NEVER,所以是只有纵滚动。
     
     panel.add(scroller);       注意,这里把scroller放入了panel
注意:小心顺序,非常重要!先通过scrollpane的构造函数,把TextArea放入ScrollPane,然后设置scrollpane,设置好了后,最后把scrollpane放入panel。
2、更换里面text的文本
     text.setText("Not all who are lost are wandering");
3、添加一个文本到Area末尾
     text.append("button clicked");
4、选择/高亮一个文本内容
     text.selectAll();
5、放置光标,让用户可以立即输入
     text.requestFocus();

JCheckBox
Constructor
     JCheckBox check = new JCheckBox("Goes to 11");

怎么用:
1、监听一个item的事件,被选中还是取消选中
     check.addItemListener(this);
2、处理事件,看是不是选中
     public void itemStateChanged(ItemEvent ev) {
          String onOrOff = "off";
          if (check.isSelected()) onOrOff = "on";
          System.out.println("Check box is" + onOrOff);
     }
3、用代码控制选中或取消
     check.setSelected(true);
     check.setSelected(false);

JList
Constructor
     String[] listEntries = {"alpha" , "beta" , "gamma" , "eta"};
     
     list = new JList(listEntries);

怎么用:
1、令其产生一个垂直的滚动条
     JScrollPane scroller = new JScrollPane(list);
     scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.
          VERTICAL_SCROLLBAR_ALWAYS);
     scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.
          HORIZONTAL_SCROLLBAR_NEVER;

     panel.add(scroller);
2、设置不滚动就能看到的行数
     list.setVisibleRowCount(4);
3、限制用户一次只能选择一项
     list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
4、注册选择后的事件
     list.addListSelectionListener(this);
5、事件处理
     public void valueChanged(ListSelectionEvent lse) {
          if (! lse.getValueIsAdjusting()){  如果没这个if 那你会得到两次事件
               String selection = (String) list.getSelectedValue(); 
注意,上面getSelectedValue()返回的是Object,不一定非要是String
               System.out.println(selection);
          }
     }

第十一章  串行化和文件IO

1、如果你的数据只会被你这个java程序生成和读取,那是用串行化比较好。
2、如果你的数据,还要被别的程序读取,那写成普通的text文件好。

串行化存的文件,可能不好被人阅读,但是它比较方便机器的存取。
而普通text文件,存的时候可能就用的人的语言,人好读,但是机器是别的时候就慢一些。

玩游戏的时候,创建的一个角色,玩了多少级之后,有了很多武器,存储时,是存储这个任务的状态。在存类的对象的时候,也是存的类的状态。

串行化

写入一个串行化了的对象,到一个文件。步骤:
1、创建FileOutputStream对象。
     FileOutputStream fileStream = new FileStream("MyGame.ser");
     最后那个是存的文件名,如果MyGame.ser文件不存在,就会自动创建。
注意:FileOutputStream对象,天生就知道该怎么去和一个文件发生关联,即创建文件,和写文件。

注意:什么是串行化了的对象下面会说,就是该对象所在类实现了Serializable接口。

2、创建ObjectOutputStream对象。
     ObjectOutputStream os = new ObjectOutputStream(fileStream);
     这个fileStream就是上面创建的FileOutputStream对象。
注意:ObjectOutputStream功能是让你写一个对象信息,但是这个类又做不到直接去和一个文件发生关联,所以需要FileOutputStream的帮助。

3、写入串行化了的对象。
     os.writeObject(characterOne);
     os.writeObject(characterTwo);
     os.writeObject(characterThree);
这就是正式写入对象了。

4、关闭ObjectOutputStream
     os.close();
注意:最后一定要记得关闭ObjectOutputStream,这个流处在最上层,将它关闭,下层的FileOutputStream流也会自动关闭。

注意:上面的ObjectOutputStream就叫做 chain stream,它无法直接与数据源或者目的地连接,所以需要connection stream,的帮助。这么做的原因就是OO,一个类只做好一件事情。这样做也可以解耦,重复利用代码。比如我写一个类,这里是写入文件,但如果是发到网上呢?就需要别的connection stream来帮忙。混合使用不同种的connection 和 chain stream,可以得到极大的柔性。我翻译不好。
The ability to mix and match different combinations of connection and chain stream gives you tremendous flexibility!


一个类对象的基本类型串行化后,就是将它的值存起来,这就是这个对象的状态,当然存的时候还会存一些和这个类相关的信息。

那么一个对象上的引用类型变量呢?
答:对象上引用类型变量的串行化,是要把它引用的对象也要串行化,然后这个被引用的对象里面如果还有引用类型的成员变量,那也要把他们引用的对象串行化,就这么循环往复下去,直到所有对象被串行化完毕。

注意:static变量是不支持串行化的,每次去串行化后,值都可能变化,不能依靠它! 重要

Serializable 接口

那么一个对象怎么能被串行化呢?它所属的类,必须 implement Serializable

Serializable接口常被称为标记或标签,因为 Serializable接口里面没有任何方法
它就是在宣称这个类能够通过串行化机制进行存储。且 父类实现了Serializable接口后,子类自动获得串行化能力。

注意:objectOutputStream.writeObject(myBox); 这里要写入的myBox对象的所属类,必须实现Serializable接口。

注意:Serializable接口在java.io包中。

transient关键字

如果一个类实现了Serializable接口,那它就能够进行串行化,但是如果它有一个引用类型的成员变量,然后这个成员变量所引用的对象的类型,不支持串行化,那他就会妨碍当前的整个类的串行化工作,导致这个类无法串行化,一个老鼠还一锅汤。

这时候,transient关键字就来了,如果你不想让某个成员变量串行化,那就把它前面加上transient。

注意:标记为transient的变量,去串行化,即读回来的时候,会直接赋值为null。这可能并不是你想要的,于是,有两个选择:
1、去串行化的时候,重新初始化这种被标为transient的变量。当然这只适合这个变量的值,与程序的运行状况无关的时候。
2、如果这个变量的值,与程序的执行状况相关,那你得在串行化的时候,就存一些重要的值,等去串行化时,用这些值去初始化被标为transient的变量。

为什么会有不想串行化的成员变量呢?
答:因为有时候,有的成员变量的值,必须根据当前程序执行的情况来定,也就是说可能每次运行,运行的不同时段,这个值都不同,那即说这个值不该被存,存了也没用,因为不会取用。比如网络连接、线程、文件对象等。

如果有两个不同类,这两个类中都有个引用型成员变量,引用了同一个对象,怎么办?
答:串行化的时候是可以检测出,两个引用类型,引用到同一个对象的,存的时候也只存一份,读的时候会复制。

串行化这么好,为什么不把所有类串行化?
答:这是给了一个选择权,如果所有类都实现类串行化,万一你不想要串行化,那就完蛋了,反悔不了的。

那为什么有类有可能不想串行化呢?
答:比如密码类,它串行化就把密码存下了。还有很多情况。

注意:如果一个类没有实现Serializable接口 我们可以自己写一个类,继承于这个不能串行化的类,然后让新类实现Serializable接口,该用原来类的地方,我们都改为用新类,这样可以避开一个没有串行化,但我们又非常想让它串行化的情形。不过,这个原始类可不能是final。

注意:如果一个类实现了Serializable接口,但是它的父类没有。那么这个类去串行化的时候,因为还是要调用父类的constructor,那它对象的父类部分会像创建新对象那样被创建。一般,如果没有很好的理由让一个类不能串行化,那么写一个新类,继承于它,然后让新类实现Serializable接口,不失为个好做法。

去串行化 deserialization

步骤与串行化相反:
1、File InputStream fileStream = new File InputStream("MyGame.ser");
如果文件不存在,直接报异常。

2、Object InputStream os = new Object InputStream(fileStream);

3、Object one = os.readObject();
     Object two = os.readObject();
     Object three = os.readObject();
如果没存那么多对象,读的比存得多,那也抛异常。
注意:读的顺序必须与写的时候一致。

4、GameCharater elf = (GameCharacter) one;
     GameCharater troll = (GameCharacter) two;
     GameCharater magician = (GameCharacter) three;
返回来的是Object类型,所以这里要强转回来。

5、os.close(); 一定要记得。

注意:仔细看啊,去串行化的时候可都是Input,ObjectInputStream等,串行化的时候是Output。


去串行化时发生的事情,非常重要!看完明白好多
注意:
1、第三条说如果找到这个类,会加载类。记得static吗,应该就是这时候赋值。
2、第三条,说会检查串行化存的类,然后前面去串行化的时候用的强转GameCharater elf = (GameCharacter) one;我疑问,既然存了类的信息,这里为什么还要强转。
3、去序列化的时候,也会在堆上创建对象,像new操作一样,但是 没有调用构造函数

注意:第五条说的就是前面讲transient 关键字的时候提到的问题。如果子类能够串行化,而父类不能,那么去串行化的时候,父类的构造函数是会被调用的,且 父类上面整个继承链上的类的构造函数都会被调用。

通过Text存储

前面讲了串行化,但是串行化适用于java程序存储读取数据,如果换一个程序要读你存的内容,那串行化就不行了,这时候,直接写人能懂的信息是个办法。

典型写法:
import java.io.*;

class WriteAFile {
     public static void main(String[] args) {
          try {
                FileWriter writer =new FileWriter("Foo.txt");

               writer.write("hello foo!");

               writer.close();
          } catch (IOException ex) {
               ex.printStackTrace();
          }
     }
}
注意:所有IO相关的东西,都要用try catch 包装,都可能抛异常。

JFileChooser

JFileChooser fileSave = new JFileChooser();
fileSave.showSaveDialog(frame);
saveFile(fileSave.getSelectedFile());

这个就是点保存时候的对话框,非常方便,所有的文件导航工作都由他完成。
注意:JFileChooser只完成文件导航,还有那个对话框,最后那个saveFile是你自己写的,某个类的成员函数。fileSave.getSelectedFile()来传递用户选了哪个文件。

java.io.File类

File对象,就代表了一个文件的路径,代表了一个文件。它要比直接写String安全。更好一点。

FileWriter FileInputStream等的构造函数,要求写一个文件的路径时,直接传File对象就行。

File f = new File("MyCode.txt");  创建File对象

File dir = new File("Chapter");
dir.mkdir();           创建新目录

打印一个目录下的所有内容,if判断是不是目录。
if (dir.isDirecotry()) {
     String[] dirContents = dir.list();
     for (int i = 0; i < dirContents.length; i++) {
          System.out.println(dirContents[i]);
     }
}

System.out.println(dir.getAbsolutePaht());  获取文件或目录的绝对路径

boolean isDeleted = f.delete();  删除一个文件或目录,返回是否成功

BufferedWriter

BufferedWriter是个chain stream,FileWriter是个connection stream,两者配合,效率很高。单单一个FileWriter也能用,但有了buffer就好多了。

chain stream 和 connection stream前面讲了。

public static void main(String[] args) {
     try {     
          File myFile = new File("MyText.txt");
          FileReader fileReader = new FileReader(myFile);
               
           BufferedWriter writer = new BufferedWriter(new FileWriter(aFile));

          String line = null;

          while ((line = reader.readLine()) != null){
               System.out.println(line);
          }
           reader.close();     一定别忘了关buffer
     } catch (Exception ex) {
          ex.printStackTrace();
     }
}
注意:绿色部分最后,直接new FileWriter()就行了,连FileWriter的对象的引用我们都不需要,后续操作的时候,只操作Buffer就可以,剩下的都由buffer来自动完成。

拆分String

String toTest = "What is blue + yellow? / green";

String[] result = toTest.split("/");

for (String token : result) {
     System.out.println(token);
}
给个设置个分隔符,将String拆分为许多小的String,上面的用法只是一种最简单的,split()函数十分强大,可以干好多事情。

版本问题

串行化后的文件,如果要去串行化,但是去串行化的类发生了改变,那就有可能影响到去串行化的进行,甚至完全不能读出以前存的内容。

类的改变中,会影响去串行化工作的:
删除一个成员变量

改变一个成员变量的类型

将一个非transient成员变量,改为transient的

把一个class,在继承链上发生移动

把本来允许串行化的类,改为了不能串行化,比如去掉implements Serializable

将一个成员变量改成static的

类的改变中,一般不会影响串行化的:
添加新的成员变量(这时,去串行化的时候,会把变量设为默认值)

在继承链上添加类

改变了一个成员变量的访问权限

将一个transient变量,改为非transient变量(去串行化的时候,这个值就直接拿默认值了)

注意:java会给每个类,一个serialVersionUID,这个ID是根据类变化的,只要类出现变化,这个值就自动变化。然后去串行化的时候,会检测这个类是不是变了,如果发生了改变,那么去串行化就会直接失败。

但是,注意,这个serialVersionUID值你自己可以控制,通过在类中明确写出成员变量:
static final serialVersionUID = 一个长长的数字,最后以L结尾。
这样就可以改变一个类的UID,且如果你不手动改,这个值再也不会变化。
也就是说,你这个类是否能够去串行化成功,完全取决于你了,JVM不会帮你检查。

于是,你可以这么做,运用 serialver工具

比如有个Dog类,我写:
然后把这个长长的数字,粘到我的类里面,
这里数字不一样,大概看一下就行了,就差了一位而已,你就理解为上面两个数一样。

这样粘到类里面以后,我再怎么改这个类,值都不变化,去串行化都能进行,但能不能成功就不一定了,就是不一定去串行化后就是你想要的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值