JAVA学习笔记

继承下来的方法可以被覆盖掉,但是实例变量不能;

运用多态时,引用类型可以是实际对象类型的父类,

用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);

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lucky登

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值