继承下来的方法可以被覆盖掉,但是实例变量不能;
运用多态时,引用类型可以是实际对象类型的父类,
用final修饰类表示它是继承树的末端,不能被继承,修饰方法表示不能被覆盖
覆盖的规则:无论父类声明的返回类型是什么,子类必须要声明返回一样的类型或者该类型的子类,不能降低方法的存取权限,存取权限必须相同或者更为开放.
关于方法的重载:返回类型可以不同,不能只改变返回类型,可以更改存取权限
接口是百分之百的抽象类,抽象类就是无法初始化的类.
设计抽象的类--在类的声明前面加上抽象类关键字abstract,抽象类代表没有人能够创建出该类的实例,但是还是可以使用抽象类来声明为引用类型给多态使用.,即可以赋值给父类即使父类是抽象的.
抽象类除了被继承外,是没有用途,没有值,没有目的的
不是抽象的类就被称为具体类
方法也可以标记为abstract,抽象的方法代表此方法一定要被覆盖过,抽象的方法没有实体,直接以分号结束: public abstract void eat(); ,不能在非抽象类中拥有抽象方法.,就算只有一个抽象的方法,此类也必须标记为抽象的.
private Animal[ ] animals = new Animal[5], 这是个保存animals的数组对象
在JVAV中的所有类都是从Object这个类继承出来的,Object这个类是所有类的源头,是所有类的父类.
将Dog类型的引用对象存储在ArrayList<Object>,有方法将其转换回原来的类型,如果真的确定他是一个Dog类型可以用强制类型转换:Object o = al.get(index); Dog d = (Dog) o;,如果不能确定它是Dog,可以使用instanceof这个运算符来检查.若是类型转换错了,会在执行器遇到ClassCastExpection异常并终止:if(o instanceof Dog) { Dog d = (Dog) o; }
Java非常注重引用变量的类型
JAVA不允许多重继承,因为多重继承会存在"致命方块"的问题
接口的定义:public interface Pet {...} ,使用interface来替代class
接口的实现:public class Dog extends Canine implement Pet {...},使用implement这个关键字
接口方法带有public和abstract而当意思,这两个修饰符属于选择性的,可以打出来,实际上不需要.
接口的方法一定是抽象的,所以必须以分号结尾,一个类可以有多个接口,以逗号","隔开
super这个关键字可以在子类中调用父类的方法:super.runReptor();.super是用来引用父类对象
栈和堆:在JAVA中,所有的对象存储在堆中,方法的调用的局部变量存储在栈上.堆又称为可垃圾回收的堆
局部变量和方法的参数都是被声明在方法中.他们是暂时的,且生命周期只限于被放在栈上的这段时间(也就是方法被调用至执行完毕为止).显式的调用父类的构造函数: super().,如果我们没有调用super(),编译器会帮我们加上super()的调用,父类的构造函数会调用它的父类的构造函数,一直到Object的构造函数为止.然后再一路执行,弹回到原来的构造函数为止.
每个子类的构造函数会立即调用父类的构造函数,如此一路网上直到Object.因为子类对象可能需要从父类继承下来的东西,所以那些东西必须要先完成,父类的构造函数必须在子类的构造函数之前结束.
如果用super()来显示调用父类的构造函数,必须将其放在第一个命令的位置.
可以使用this()来从某个构造函数调用同一个类的另外一个构造函数.this()只能用在构造函数中,且必须是第一行语句.super()与this()不能兼得,不能同时调用.this和super()都必须是第一行语句
当最后一个引用消失时,对象就会变成可回收的,有三种方法可以释放对象的引用:1.引用永久性的离开它的范围(不会再回来);2.引用被赋值到其他的对象上;3.直接将引用设定为null.
对象的状态就是保存在实例变量中的值.
在Math这个类中的所有方法都是静态的.它的构造函数被标记为私有的.
静态项目的初始化有两项保证:1.静态变量会在该类的任何对象创建之前就完成初始化;2.静态变量会在该类的任何静态方法执行之前就初始化
如果没有给静态变量赋初值,他就会被设定为默认值.
一个被标记为final的变量代表他一旦被初始化之后就不会改动.final的方法代表不能覆盖它,final修饰的类表示不能创建的该类的子类,即不能继承该类.
常量变量的名称应该都要大写字母
静态final变量的初始化有两种方式:1.声明的时候:public static final int X = 13;
2.
public class Bar {
public static final double BAR_SIGN;
static {
BAR_SIGN = (double)Math.random();
}
}
final 也可以修饰非静态变量,都代表它的值不能动
Math.random()返回介于0.0~1.0的双精度浮点数
Math.abs()返回绝对值
Math.round()返回四舍五入后的整形或长整型
primitive主数据类型的包装类型即对象类型,声明对应的ArrayList和HashMap时对应的只能是对象类型即:Integer,Character等,不能是primitive主数据类型
引入autoboxing之后,primitive主数据类型和包装类型可以自动转换.
将String类型转换成primitive主数据类型:有方法:Integer.parseInt() Double.parseDouble()
boolean b = new Boolean("true").booleanValue().没有parseBoolean(),到那时Boolean的构造函数可以用Sring来创建对象
有好几种方法将primitive主数据类型值转换成String:
最简单的是将数字接上现有的String: double d = 13.1; String doubleString = "" + d; "+"这个操作数是JAVA中唯一重载过的运算符
方法2: double d = 13.1; String doubleString = Double.toString(d);是Double这个类的静态方法%
数字的格式化:格式化说明最多有五个部分(不包括%):%[argument number] [flags] [width] [.precision] type %代表一项变量
在格式化指令中一定要给类型,是唯一必填的项目
超过一项以上的参数,会按照顺序应用在%上数值与日期格式化的主要差别在于日期格式的类型是用 "t"开头的两个字符来表示:完整的日期与时间:%tc 只有时间:%tr 周月日: %tA %tB %td
"<"这个符号用来告诉格式化程序重复利用之前用过参数: Date today = newDate(); String.format("%tA, %<tB, %<td", today);
操作日期: 要去的当前日期时间就用Date,其余功能就可以从Calendar上面找.实际上Calendar是个抽象的类,只能取得它的具体子类的实例: Calendar cal = Calendar.getInstance();
Calendar重要的方法:add(),get(),getInstance(),getTimeLnMillis(),roll(),set(),setTimeInMillis()
关键字段:DATE / DAY_OF_MONTH HOUR / HOUR_OF_DAY MILLISECOND MINUTE MONTH YEAR ZONE_OFFSET
静到最高点: 静态的import,可以省略一些字词
创建MIDI音乐播放器: 其中用来处理可能会出现异常的方法为 try/catch块
有风险\会抛出异常的代码程序代码必须声明它会抛出:
public void takeRisk() throws BadException()
调用该方法程序必须用try/catch或者直接duck
finally是无论如何都要执行的部分,如果try或者catch有return指令,finally还是会执行,流程会调到finally.然后再回到return指令
异常也是多态的,有多个catch块时,要从小到大排
duck很容易,只要表示你会再throw此异常就好
异常处理规则: catch与finally不能没有try try一定要有catch或者finally try与catch之间不能有语句 只带有finally的try必须要声明异常
MIDI:
1.取得Sequencer并将他打开: Sequencer player = MidiSystem.getSequencer(); palyer.open();
2.创建新的Sequence: Sequence seq = new Sequence(timing, 4);
3.从Sequence中创建新的Track: Track t = seq.creatTrack();
4.填入MidiEvent并让Sequencer播放: t.add(myMidiEvent1); player.setSequence(seq);
5.player.start();//开始播放
Sequencer对象会将所有的MIDI数据送到正确的装置上,由装置来产生音乐.
MidiEvent是组合乐曲的指令,一连串的MidiEvent就好像是乐谱一样,MIDI实际指令会放在Message对象中(ShortMessage) MidiEvent描述何时做:MidiEvent是由Message加上发音时机所组成的
将MidiEvent加到Track中,Track带有全部的MidiEvent, Sequence会根据事件时间组织他们,然后Sequencer会根据此顺序来播放,同一时间可以执行多个操作.
图形用户接口:
JFrame是个代表屏幕上window的对象, 可以把组件加到上面.有很多的Swing组件可以使用.
package GUI.First;
import javax.swing.*;
public class SimpleGui1 {
public static void main(String[] args) {
JFrame frame = new JFrame();
JButton button = new JButton("Click Me");//创建JFrame和Button
//这一行的程序会在window关闭时把程序结束掉
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(button);//把button加到frame的pane上
frame.setSize(300,300);//设定frame的大小
frame.setVisible(true);//最后把frame显示出来
}
}
事件源会在用户作出相关动作时产生事件对象,每个事件类型都有相对应的监听者接口
事件只是通知有实现ActionListener的类
package GUI.Second;
import javax.swing.*;
import java.awt.event.ActionListener;
import java.awt.event.*;
public class SimpleGuiB implements ActionListener {
JButton button;
public static void main(String[] args) {
SimpleGuiB gui = new SimpleGuiB();
gui.go();
}
public void go() {
JFrame frame = new JFrame();
button = new JButton("Click Me");
button.addActionListener(this);//注册监听者
frame.getContentPane().add(button);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.setVisible(true);
}
//实现接口方法
public void actionPerformed(ActionEvent e) {
button.setText("I've been clicked!");
}
}
当按钮的addActionListener()方法被调用时,它的参数会被存到清单中.当用户按下按钮时,按钮会通过调用清单上每个监听的actionPerformed()来启动事件
事件源是一种会根据用户操作而触发事件的机制;
监听GUI事件才能知道用户对接口做了什么事情;
监听接口让事件源能够调用给你;
在widget上绘制JPEG图;
在widget上绘制2D图形:使用graphics(图像,图案)对象
创建JPanle子类,并覆盖掉paintComponent()这个方法,把这个方法想象成会向系统告知要把自己画出来的方法. 当你的panel所处的frame显示的时候,paintComponent()就会被调用,你不会自己调用这个方法,它的参数是哦跟实际屏幕相关的Graphics对象,你无法取得这的对象,它必须由系统来交给你.然而,还是可以调用reapint()来要求系统重新绘制显示装置,然后才会产生paintComponent()的调用
实现接口的方法,然后将类的实例传给待检测事件的类似addActionListener来注册监听用户,当用户按下按钮或产生其他事源时,会产生事件对象,传入监听者实现的类似actionPerformed方法,并调用.
布局管理器: BorderLayout, FlowLayout:依次从左至右,从上到下, BoxLayout:垂直排列
BorderLayout, FlowLayout是默认的框架和面板布局管理器
BorderLayou中南北会先占位,然后东西,最后center
把面板的布局管理器从默认的FlowLayout布局改成BoxLayout布局:
LPanel panel = new LPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
它的构造函数需要知道管理哪个组件,以及使用哪个轴.
操作Swing组件:
JTextField :一行文字的框 JTextField field= new JTextField(20);//20代表字宽而不是像素
JTextField field= new JTextField("your name");
取得文本内容:System.out.println(field.getText());
设定内容: field.setText("whatever"); 设为空 : field.setText("");
JTextArea:可以超过一行 JTextArea text = new JtextArea(10,20); 代表10行高20字宽
JScrollPane scroller = newJScrollPane(text);//将文本赋值给新创建的JScrollPane
text.setLineWrap(true);//启动自动换行
panel.add(scroller);加入的是带有文本域的滚动条而不是文本域
加入文字: text.append("button clicked");
JCheckBox
JList: String [ ] listEntries = {"alpha", "beta"};
JList list = new JList(listEntries);JList需要一个任意类型的数组,不一定是String,但会用String来表示项目.
list.setVisibleRowCount(4);设置显示的行数,可以和滚动条搭配
序列化和文件的输入/输出: 保存对象
对象可以被序列化,也可以展开
将序列化对象写入文件:
1.创建出FileOutStream :FileOutStream fileStream = new FileOutStream("MyGame.ser");
如果文件不存在,会被自动创建出来.FileOutStream为存取文件的对象
2.创建ObjectOutStream: ObjectOutStream os = new ObjectOutStream(fileStream);
它能让你写入对象,但无法直接的连接文件,所以需要参数的指引
3.写入对象: os.writeObject(characterOne);
os.writeObject(characterTwo);
os.writeObject(characterThree);
将变量所引用的对象序列化并写入MyGame.ser这个文件
4.关闭ObjectOutputStreeam: os.close();//关闭所关联的输出串流
当对象被序列化时,该对象引用的实例变量也会被序列化.且所有被引用的对象也会被序列化,这些操作都是自动进行的!
如果要让类能够被序列化,就实现Serializable,Serializable接口又被称为marker或tag类的标记用接口,因此此接口并没有任何方法需要实现的.如果某类的=是可序列化的,则它的子类也自动地可以序列化(接口的本意就是如此)
整个对象版图都必须正确地序列化,不然就得全部失败
如果某实例变量不能或不应该被序列化,就把他标记为transient(顺时)的.
解序列化: 还原对象
1.创建FileInputStream: FileInputStream fileStream = new FileInputStream("MyGame.ser");
如果文件不存在就会抛出异常.这个对象知道如何连接文件
2.创建ObjectInputStream: ObjectInputStream os = new ObjectInputStream(fileStream);
他知道如何读取对象,但要靠链接的Stream提供文件存取.
3.读取对象: Object one = os.readObject();
Object two = os.readObject();
Object three = os.readObject();
每次调用readObject()都会从Stream中读出下一个对象,读取顺序与写入顺序相同,次数超过会抛出异常.
4.转换对象类型: GameCharacter elf = (GameCharacter) one;
GameCharacter troll= (GameCharacter) two;
GameCharacter magician = (GameCharacter) three;
返回值是object类型,因此必须要转换类型
5.关闭ObjectInputStream : os.close(); // FileInputStream会自动跟着关掉
File这个类代表磁盘上的文件,但并不是文件中的内容.可以把File对象想象成文件的路径,而不是文件本身.他不能读取或者代表文件中的数据
FIle的操作:
1.创建出代表现存盘文件的File对象: File f = new File("Mycode.txt");
2.建立新的目录: File dir = new File("Chapter7"); dir.mkdir();
3.列出目录下的内容: if(dir.isDirectory()) {String[] dirContents = dir.list(); for(int i = 0; i < dirContents.length; ++I) System.out.println(dirContenes[i]; } }
4.取得文件或目录的绝对路径: System.out.println(dir.getAbsolutePath());
5.删除目录或者文件(成功会返回true): boolean isDeleted = f.delete();
缓冲区: 可以提高效率: BufferedWriter writer = BufferedWriter(new FileWriter(aFile));
如果想要强制缓冲区立即写入,重要调用writer.flush()这个方法就可以要求缓冲区马上把内容写下去
读取文本文件: FileReader readLine()来一行一行的读取,可以和BufferedReader搭配是使读取更有效率.读完时, readLine()的返回结果为null.
String 的split()解析:String 的split()可以把字符串拆开,split可以将字符串拆开成String的数组
会损坏解序列化的修改: 删除实例变量 改变实例变量的类型 将非瞬间的实例变量改为瞬时的 改变类的继承层次 将类从可序列化改成不可序列化 将实例变量改为静态的
通常不会有事的修改: 加入新的实例变量(还原时会使用默认值) 在继承层次中加入新的类 从继承层次中删除类 不会影响解序列化程序设定变量值的存取层次修改 将实例变量从瞬时改成非瞬时(会使用默认值).
使用serialVersionUID:每当对象被序列化的同时,该对象(以及所有在其版图上的对象)都会被"盖"上一个类的版本识别ID.这个ID被称为serialVersionUID,它是根据类的结构信息计算出来的.在对象被解序列化时,如果在对象被序列化之后类有了不同的serialVersionUID,则还原操作会失败,但你还可以有控制权. 如果你认为类有可能会演化,就把版本识别ID放在类中
网络与线程:
聊天程序概述: 客户端必须要认识服务器, 服务器必须要认识所有的客户端
工作方式 : 1.客户端连接到服务器 2.服务器建立连接并把客户端加到来宾清单中 3.另外一个用户连接上来 4.用户A发出信息到聊天服务器上 5.服务器将信息送给所有的来宾
建立Socket连接: 要连接到其他的机器上,我们会需要Socket的连接.Socket是个代表两台机器之间网络连接的对象 要创建Socket的连接得知道两项关于服务器的信息: 它在哪里以及用哪个端口来收发数据 ,也就是IP地址与端口号
Socket连接的建立代表两台机器之间存有对方的信息,包括网络地址和TCP的端口号.
TCP端口号是用来识别服务器上特定程序的数字.如果没有端口号,服务器就无法分辨用户端是要连接到哪个应用程序的服务.每个应用程序可能都有独特的工作交谈方法.
不同程序不可以共享一个端口: 如果你想用使用(技术上叫绑定)某个已经被占用的端口,就会收到BindException.绑定一个端口就代表程序要在特定的端口上面执行
使用BufferedReader从Socket上读取数据
JAVA的好处就在于大部分的输入/输出工作并不在乎链接串流的上游实际上是什么,也就是说你可以使用BufferedReader而不管是串流来自文件或Socket
1.建立对服务器的Socket的连接:
Socket chatSocket = new Socket("127.0.0.1", 5000);
2.建立连接到Socket上底层输入串流的InputStreamReader:
InputStreamReader stream = new InputStreamReader(chatSocket.getInputStream());
InputStreamReader是底层和高层串流之间的桥梁
chatSocket.getInputStream(): 从Socket取得输入串流
3.建立BufferedReader来读取:
BufferedReader reader = new BufferedReader(stream);
String message = reader.reader.readLine();
用PrintWriter写数据到Socket上:
1.对服务器建立Socket连接:
Socket chatSocket = new Socket("127.0.0.1", 5000);
2.建立链接到Socket的PrintWriter
PrintWriter writer = new PrintWriter(chatSocket.getOutputStream());
3.写入数据:
writer.println("message to send");
writer.print("another message");
编写简单的服务器程序:
工作方式:1.服务器应用程序对特定端口创建出ServerSocket: ServerSocket serverSocket = new ServerSocket(4242); 这会让服务器应用程序开始监听来自4242端口的客户端请求.
ServerSocket会等待用户请求
2.客户端对服务器应用程序建立Socket连接: Socket sock = new Socket("190.165.1.103", 4242);
客户端得知道IP地址和端口号
3.服务器创建出与客户端通信的新的Socket: Socket sock = serverSock.accept();
accept()方法会在等待用户的Socket连接时闲置着.当用户连上来时,此方法会返回一个Socket(在不同的端口上)以便与客户端通信.Socket与ServerSocket的端口不相同,因此ServerSocket可以空出来等待其他的用户
要点:
1.客户端与服务器的应用程序通过Socket连接来沟通
2.Socket代表两个应用程序之间的连接,它们可能会是在不同的机器上执行的
3.客户端必须知道服务器应用程序的IP地址(或网域名称)和端口号
4.TCP端口号是个16位的值,用来指定特定的应用程序.它能让用户连接到服务器上各种不同的应用程序
5.从0~1023的端口号是保留给HTTP,FTP,SMTP等已知的服务
6.客户端通过建立Socket来连接服务器: Socket s = new Socket("127.0.0.1", 4200);
7.一旦建立的连接,客户端就可以从Socket取得底层串流: sock.getInputStream();
8.建立BufferedReader链接InputStream与来自Socket的输入串流以读取服务器的文本数据
9.建立直接链接Socket输出串流的PrintWriter请求print()方法或者println()方法来送出String给服务器
10.服务器可以使用ServerSocket来等待用户对特定端口的请求
11.当ServerSocket接到请求时,他会做一个Socket连接来接受客户端的请求
线程是独立的线程,他代表独立的执行空间. Thread是JAVA中用来表示线程的类,要建立线程就得创建Thread
Thread有启动线程, 连接线程和让线程闲置的方法
当有超过一个以上的执行空间时,看起来会像是有好几件事同时发生.实际上,只有真正的多处理器系统能够同时执行好几件事,但使用JAVA的线程可以让他看起来好像同事都在执行中.也就是说,执行动作可以在执行空间非常快速地来回交换.
如何启动新的线程:
1.建立Runnable对象(线程的任务): Runnable threadJob = new MyRunnable();
每个Thread需要一个任务来执行,一个可以放在执行空间的任务,对于Thread来说,他是个工人,而Runnable就是这个工人的工作.Runnable带有会放在执行空间的第一项方法:run()
2.建立Thread对象(执行工人)并赋值Runnable(任务): Thread myThread = new Thread(threadJob);
把Runnable对象传给Thread的构造函数.这会告诉Thread对象要把哪个方法放在执行空间去运行--Runnable的run()方法.
3.启动Thread: myThread.start();
在还没有调用Thread的start()方法之前什么也不会发生.这是你在只有一个Thread实例来建立新的线程时会发生的事情. 当新的线程启动之后,他会把Runnable对象的方法摆到新的执行空间中
Runnable这个接口只有一个方法.
接口可以当做变量类型
新建线程的三个状态:
1.新建:Thread t = new Thread(r); Thread的实例已经创建,但还没有启动,也就是说,有Thread对象,没有执行中的线程.
2.可执行:t.start(); 当你启动线程时,它会变成可执行的状态.意思就是他准备好要开始执行了,只要轮到他就可以开始.这是,该线程已经布置好执行空间.
3.执行中: 所有的线程都在等这一刻,成为执行中的那一个! 这只能靠JAVA虚拟机的线程调度机制来决定.你有时也能对JAVA虚拟机选择执行线程给点意见,但是无法强迫它把线程从可执行状态转移到执行中.
一旦线程进入到可执行状态,他会在可执行与执行中两种状态之间来来去去,同时也有另外一种状态:暂时不可执行(又称为堵塞状态).
要点:
1.run()会是新线程所执行的第一项方法;
2.要把Runnable传给Thread的构造函数才能启动新的线程
3.线程在初始化以后还没有调用start()之前处于新建立的状态
4.调用start()之后,会建立新的执行空间,它处于可执行状态等待被挑出来执行
5.单处理器的机器只能有一个执行中的线程
6.有些线程会因为某些原因而被堵塞
7.调度不能保证任何的执行时间和顺序,所以你不能期待它会完全地平均分配执行,你最多也只能影响sleep的最小保证时间.
如果想要确保其他的线程有机会执行的话,就把线程放进睡眠状态.当线程醒来的时候,他会进入可执行状态等待被调度器挑出来执行 Thread.sleep(200); 睡个200毫秒,这可能会抛出异常,所以:
try{
Thread.sleep(200);
}catch(InterruptionException ex){ex.printStackTrace()};
线程会产生并发性的问题:
这一切都来自于可能发生的一种状况: 两个或两个以上的线程存取单一对象的数据.也就是说,两个不同执行空间上的方法都在堆上对同一个对象执行getter和setter
使用synchronized这个关键词来修饰方法会使它每次只能被单一的线程存取
synchronized关键词代表线程需要一把钥匙来存取被同步化(synchronized)过的线程
要保护数据,就把作用在数据上的方法给同步化
每个对象都有个锁. 大部分时间都没有锁上,并且你可以假设有个虚拟的钥匙随时等待.对选哪个的锁是会在有同步化的方法上起作用. 当对象有一个或多个同步化的方法时,线程只有在取得对象锁的钥匙时才能进入同步化的方法.
每个JAVA对象都有一个锁,每个所只有一把钥匙. 通常对象都没上锁,也没有人在乎这件事, 但如果对象有同步化的方法,则线程只能在取得钥匙的情况下才能进入线程.也就是说并没有其他线程已经进入的情况下才能进入.
同步化也可以只修饰一行或数行的指令而不必整个方法都同步化:
public void run(){
doStuff();
synchronized(this) {
criticalStuff();
moreCriticalStuff();
}
}
criticalStuff(); moreCriticalStuff(); 只有这两个调用要被组合成原子单位,同步化的这种用法不会将这个方法设定为成需要同步化,只会使用参数对象的锁来做同步化
死锁会发生是因为两个线程互相持有对方正在等待的东西.没有办法可以脱离这个情况,所以两个线程只好停下来等,一直等.
在JAVA中枚举enum:
枚举是一种特殊的类,它用于表示固定数量的常量. 枚举类型在编译时会隐式地创建一组常量,这些常量都是该枚举类型的实例.由于枚举类型的实例在编译时就已经确定,所以不能像普通类那样直接实例化枚举
集合和泛型:
数据结构:
JAVA中有一大堆现成的工具可供收集和操作数据
集合举例:
TreeSet: 以有序状态保持并可防止重复;
HashMap: 可用成对的name/value来保存和取出;
LinkedList:针对经常插入或删除中间元素所设计的高效集合(实际上还是ArrayList比较实用);
HashSet: 防止重复的集合,可快速地找寻相符的元素;
LinkedHashMap:类似HashMap,但是可以记住元素插入的顺序,也可以设定成按照元素上次存取的先后来排序;
Collections这个类有sort()方法,他会用到List这个接口,sort()为public static void sort(List list);
toString()是定义在Object()这个类中的,所以JAVA的每个类都有继承到,且因为对象被System.out,println(anObject)列出来时会被调用toString(),所以当你要把list列出来时,每个对象的toString()都会被调用一次.
sort()的声明: public static <T extend Comparable<? super T>> void sort(List<T> list)
sort()方法只能接受Comparable对象的list, Comparable是个接口,以泛型的观点来看,extends代表extends或implement
所以T必须实现Comparable,才能把ArrayList<T>传给sort()方法.Comparable只有一个方法:compareTo(T o)主要任务就是判断T类型对象的先后:他自己是高于,低于,等于所传入的对象,用征服零来表示.
sort()有重载的版本,可以取用称为Comparator的参数:
sort(List<T> list, Comparator<? super T> c);
public interface Comparator<T> {
int compare(T o1, T o2);
}
使用compareTo()方法时,list中的元素只能有一种将自己与同类型的另外一个元素作比较的方法.但是Comparator是独立于所比较元素类型之外的---它是独立的类. 因此你可以有各种不同的比较方法.
所以规则是: 调用sort(List o, Comparator c)方法意味着list元素不需要实现Comparable
泛型意味着更好的类型安全性: 在泛型功能出现之前,编译器无法注意到你加入集合中的东西是什么,因为所有的集合都写成处理Object类型.你可以把所有东西都放进ArrayList中,有点像是ArrayList<Object>.
运用泛型你就可以创建类型安全更好的集合,让问题尽可能在编译期就能抓到,而不会等到执行期才冒出来.如果没有泛型,编译器会很快地接受你把绵羊对象送到老虎集合中.
关于泛型:
1.创建被泛型化类的实例:
创建ArrayList时你必须要指定他所容许的对象,就像单纯的数组那样;
2.声明与指定泛型类型的变量:
3.声明(与调用)取用泛型类型的方法:
运用泛型的方法:
1.使用定义在类声明的类型参数: public class ArrayList<E> extends AbstractList<E>... {
public boolean add(E o);//只能在此使用E,因为他已经被定义成类的一部分
当你声明类的类型参数时,你就可以直接把该类或接口类型用在任何地方.参数的类型声明基本上会用初始化类的类型来取代.
2.使用未定义在类声明的参数类型:public <T extends Animal> void takeThing(ArrayList<T> list);//因为在前面声明T,所以后面参数部分就可以使用<T>
如果类本身没有使用类型参数,你还是可以通过在一个不寻常但可行的位置上指定位置----在返回类型之前.这个方法意味着T可以是"任何一种Animal".
public <T extends Animal> void takeThing(ArrayList<T> list):
首先,<T extends Animal> 是方法声明的一部分,表示任何被声明为Animal或其子类类型的(像是Cat,Dog)ArrayList是合法的.因此你可以使用ArrayList<Dog>来调用上面的方法
public void takeThing(ArrayList<Animal> list);
参数ArrayList<Animal> list,代表只有ArrayList<Animal>是合法的,也就是说上一个可以使用任何一种Animal的ArrayList,而第二个方法只能使用Animal的ArrayList.看起来貌似有点违反动态绑定的精神.
LIST: 对付顺序的好帮手
是一种直到索引位置的集合,,可以有多个元素引用相同的对象
SET:注重独一无二的性质 不允许重复的集合
MAP:用可以来搜索的专家,使用成对的键值和数据值,key不能重复,两个key可以引用相同的对象
对象要怎样才算相等:
引用相等性和对象相等性:
引用相等性: 堆上同一对象的两个引用
堆上同一对象的两个引用是相等的.就这样, 如果对两个引用调用hashCode(),你会得到相同的结果.如果没有被覆盖的话,hashCode()默认的行为会返回每个对象特有的序号(大部分的JAVA版本是依据内存位置计算此序号,所以不会有相同的hashcode). 如果想要知道两个引用是否相等,可以用==来比较变量上的字节组合.如果引用到相同的对象,字节组合也会一样.
对象相等性: 堆上的两个不同对象在意义上是相同的
如果你想要把两个不同的对象视为相等的,就必须覆盖过从Object上继承下来的hashCode()方法与equals()方法.
就因为上面所说的内存计算问题,所以你必须覆盖过hashCode()才能确保两个对象有相同的hashcode,也要确保以另一个对象为参数的equals()调用会返回true.
foo和bar两个对象,如果这两个相等,则foo.equals(bar)会返回true,且两者的hashCode()也会返回相同的值. 要让Set能把对象视为重复的,就必须让他们符合上面的条件被视为是相同的.
HashSet()如何检查重复: hashCode()与equals():
当你把对象加入HashSet时,他会使用对象的hashcode来判断对象加入的位置.但同时也会与其他已经加入的对象的hashcode作对比,如果没有相符的hashcode,HashCode就会假设新对象没有重复出现.也就是说,如果hashcode是相异的,则HashSet会假设对象不可能是相同的.因此你就必须override过hashCode()来确保对象有相同的值.
有相同的hashcode的对象也不一定相等,如果HashCode找到相同的hashcode的两个对象:新加入的和本来存在的,他会调用其中一个的equals()来检查hashcode相等的两个对象是否真的相同.
TreeSet在防止重复上面和HashSet是一样的.但他还会一直保持集合处于有序状态.如果使用TreeSet默认的构造函数,他工作起来就会像sort()一样使用对象的compareTo()方法来排序.但也可以选择传入Comparator给TreeSet的构造函数.缺点是如果不需要排序时就会浪费处理能力,损耗不大.
TreeSet的元素必须是Comparable,你必须指出对象应该如何排序
要使用TreeSet,下面其中一项必须为真:
1.集合中的元素必须是有实现Comparable的类型
如果没有,再加入第二个元素的时候,会因为无法调用对象的compareTo()而失败
2.使用重载,取用Comparator参数的构造函数来创建TreeSet,就像sort(),你可以选择使用元素的compareTo,假设元素的类型都有实现Comparable,或者自定义的Comparator.要使用自定义的Comparator,你可以调用取用Comparator的构造函数:
BookCompare bCompare = new BookCompare();
TreeSet<Book> tree = new TreeSet<Book>(bCompare);
Map:
如果你需要用名称来取得值的情况,虽然key通常都是String,但是也可以用任意的Java对象(或通过autoboxing的primitive).
如果方法的参数是Animal的数组,他也能够取用Animal的次类型的数组: void foo(Animal[ ]);
如果你把方法声明成取用ArrayList<Animal>,他就只会取用ArrayList<Animal>参数,ArrayList<Dog>和ArrayList<Cat>都不行
数组的类型检查是在运行期间检查的,但集合的类型检查只会发生在编译期间.
万用字符: public void takeAnimals(ArrayList<? extendsAnimal> animals)
在使用带用<?>的声明时,编译器是不会让你加入任何东西到集合中.
在方法参数中使用万用字符时,编译器会阻止任何可能破坏引用参数所指集合的行为
你能调用list中任何元素的方法,但不能加入元素
也就是说,你可以操作集合元素,但是不能新增元素,如此才能保障执行期间的安全性,因为编译器会阻止执行期的恐怖行动
相同功能的另一种语法:
public void takeThing(ArrayList<? extends Animal> list);
public <T extends Animal> void takeThing(ArrayList<T> list);
在你会用到T来决定时,下面这种会比较有效率,比如:
public <T extends Animal> void takeThing(ArrayList<T> lone, ArrayList<T> two);