包装类
为什么要使用包装类
目的:就是提供一个对象实例作为壳,将基本类型包装到这个对象中,然后通过面向对象,操作数据。
装箱和拆箱
在javase 5.0之前,要进行操作后,才能将int包装成一个interger类,在jdk5.0之后,为基本数据类型提供了自动装箱和拆箱工作。
Integer i =222;
Integer b = 222;
System.out.println(i==b);//false
Integer i =100;
Integer b = 100;
System.out.println(i==b);//true
在自动装箱对于一个-128到127之间的值,他们封装成对象,会在内存中重用。
字符串
String s = "hello";//这种方式申明了就是一个对象
对于一些可以共享的字符串对象,会先在String池中查找存在相同的String内容,有,就同一指向一个对象。
String s = "hello";
String s1 = "world";;
String s2 = "helloworld";
String s3 = s+s1;
System.out.println(s2==s3);
//首先s3是通过两个变量拼接的,由于字符串不可变,故肯定指向一个新对象
String s4 = s3.intern();
System.out.println(s4==s2);
使用intern方法会首先在字符串常量池中寻找,有没有相同内容的地址,如果找到了,就指向那个地址,没有找到的就创建一个字符串并加入常量池。
(1)只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。
(2)对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。
异常
需要注意的是,非受检异常,在编译的时候,不强制处理,如果有问题,在运行时候会报错;但是受检异常在编译的时候一定要处理,否则不通过。
try、catch
try后面跟的是可能会发生异常的代码,而catch后面是处理异常的代码,当有异常发生的时候,会执行,没有发生异常是不会执行catch后面的语句。
throw和throws
throw是指明抛出申明异常,后面必须跟一个异常实例,而throws 在跟在方法后面,如果一个方法可能会引起异常,而它本身并不打算处理,那就会把这个异常抛出给它的执行者,去处理。
在同一个方法中,如果使用throws和throw的组合,那么一定要注意就是throws抛出的异常一定要比throw抛出异常大。
throw语句执行,整个执行流程全部停止。
finally
当一个执行过程无论是否发生异常,最后都要执行的语句,我们是放在finally中。比如:数据库连接的关闭。
try {
Thread.sleep(1000);
return ;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
System.out.println("我是finally");
}
如果在异常处理的时候,正好在try中有return,同时也有finally,那么finally执行是在return之前。
集合框架
set接口中存放的是无序且不重复的;list是存放的有序而且是允许重复。
set
set的实现:是根据每个对象的哈希码值用固定的算法算出它的存储索引,把对象存放在一个散列表的相应位置,如果这个位置没有其他元素,就直接存入;如果该位置有元素,就会跟那句新对象和该位置对象比较调用equals,如果相等,就直接引用。如果set存入自己定义的对象,一定要重写equals和hashcode,来保证自定义对象在什么情况是认为是相同的。
package com.example.test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CeShi implements Comparable {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public CeShi() {
super();
// TODO Auto-generated constructor stub
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((age == null) ? 0 : age.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CeShi other = (CeShi) obj;
if (age == null) {
if (other.age != null)
return false;
} else if (!age.equals(other.age))//这里比较是否相等,是采
//用的间接比较方法,是用的Interger的equals,还是要注意要想使用equals必须是类型包装类。
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
这里java直接提供根据属性来判断对象是否相等,非常方便,自己要懂。
上文写的比较细,自己写的,功能一样,简单点:
CeShi c = (CeShi)obj;
if(c!=null) {
if(c.getAge().equals(this.getAge())&&c.getName().equals(this.getName())) {
return true;
}else {
return false;
}
}else {
return false;
}
hashset
hashset根据元素的哈希码进行存放,所以取出一页可以根据哈希码快速找到。
TreeSet
TreeSet是使用红黑书结构对加入的元素进行排序存放。
如果利用TreeSet添加元素时,没有排序会报错。TreeSet强行对实现它的类的对象进行整体排序。
可以通过Collection.sort或者Array.sort进行排序,还有一些已经实现Comparable的类,如下图:
下面我自定义类进行排序:
实现Comparable接口
public class CeShi implements Comparable {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public CeShi() {
super();
// TODO Auto-generated constructor stub
}
@Override
public int compareTo(Object o) {
// TODO Auto-generated method stub
CeShi s = (CeShi)o;
if(s.getName().compareTo(this.getName())>0) {//这里排序是利用的String的
//排序方式,间接来排序的,这里还要注意实现compare接口,在类型中都是包装类,所以在定义类型就要注意。
return -1;
}else if(s.getName().compareTo(this.getName())==0) {
return 0;
}else {
return 1;
}
}
自定义比较器
class AgeCompare implements Comparator<CeShi> {
@Override
public int compare(CeShi o1, CeShi o2) {
// TODO Auto-generated method stub
int i = o1.age-o2.age;
return i;
}
一定要指明泛型对象类型。调用的时候,直接通过构造器传进去。
Map
Map是用来维护键值对,Map中不允许有重复的键,可以有重复的值,另外map的键值度可以为null,Map实现类存储的是键值的映射,是通过键唯一来表示,Map底层的键是用set来存放,所以作为键的类一定要重写hashcode和equals。
Map的遍历
Set<Entry<String,Integer>> s = map.entrySet();
Iterator it1 = s.iterator();
while(it1.hasNext()) {
Entry< String,Integer> e = (Entry<String, Integer>) it1.next();
//将map中每一对键值封装到entry中
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key+" "+value);
}//推荐使用这种。
Collection c = map.values();//获取map中value的set集合
Iterator it = c.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
Set s = map.keySet();//获取map中键的set集合
Iterator it2 = s.iterator();
while(it2.hasNext()) {
System.out.println(it2.next());
}
实现Map的实现类:HashMap、TreeMap、Properties、LinkedHashMap
HashMap和Map用法差不多,自己看,它是以散列的方式组织,所以遍历的时候,没有什么顺序。
LinkedHashMap由于是链表来实现的,它是按照插入的顺序来进行组织,具有高效的增删操作。
TreeMap,在前面讲过的,基于这种红黑数结构的,需要进行排序后,才能存放,而在Map中,需要对键去排序。
Properties类表示一个持久的属性集,它可以保存在六中或者从流中进行加载,属性列表中每一个键其对应值都是一个字符串,主要实现存取的方法:setProperties、getproperties。
例子:
首先在你的工程下,添加.properties并且填写键值对。
InputStream in = null;
try {
in = new FileInputStream("D:\\eclipse work\\Test\\src\\com\\example\\test\\config.properties");//这里有个小技巧,就是文件路径,直接在工程空间,打开这个工程的文件,一致打开到这个属性文件,然后复制,在加上文件名,就ok
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Properties p = new Properties();
try {
p.load(in);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String name = p.getProperty("name");
System.out.println(name);
泛型
泛型只是编译时的概念,是编译器进行语法检查,就是在定义的时候(类、方法、形式参数、成员变量),指定它为通用类型,在具体运行的时候,将通用类型转成指定类型。
泛型的通配符
K 键
V 值
E 元素
T 类型 type
? 不确定类型,
这里这些不是一定规定这么定义,这是大家约定俗成的,你也可以自己用,26字母随便你。
我们常用 用来申明类的泛型, 和方法的泛型。
用<?>来限定形式参数的类型。
泛型方法
格式:修饰符 泛型 返回类型 方法名 参数表
静态方法中,不要使用类的泛型,原因是泛型类中在创建类的对象被替换为确定,静态方法可以通过类名直接访问,而java是强语言,没有类型是不允许。
进程
现在的应用程序可以同时支持一边写代码,以便敲代码。在多任务系统中,每一个独立执行的程序为进程,进程就是正在进行中的程序。
线程
进程可以进一步细化为线程,线程就是程序内部一条执行路径。如果一个程序中可以在同一个时间内执行多个线程,我们就说支持多线程。
线程和进程区别
每一个进程都有独立的代码和数据空间,进程间切换开销大。
同一个进程内的多个线程共享相同的代码和数据控件,每一个线程有独立的运行栈和程序计数器,线程间切换开销小。
使用多线程的情况:
- 同时之心两个以上任务。
- 程序需要实现等待任务,如用户输入,文件读写,网络操作。
- 需要后台运行的程序。
线程的创建和启动
当开始编译一个类的时候,JVM会启动一个线程:将main(放在这个线程执行),该线程会从程序入口main一行一行的往下执行。类似这种程序叫单线程程序,这个运行main()方法叫主线程,主线程都是由JVM启动。
java中有关线程操作,都是在Thread中。
- 每一个线程通过特定的Thread对象的run方法来玩曾其操作,run()主体称为线程体。
- 通过Thread对象的start()方法来调用。
创建线程的两种方式
- 实现runnable接口
- 继承Thread类
public class CeShi implements Runnable {
public static void main(String[] args) {
Thread t = new Thread(new CeShi());//传入runable对象
t.start();
}
@Override
public void run() {
// TODO Auto-generated method stub
}
}
建议使用第一种,因为java是单继承,拓展性差。
我们发现JVM在执行多线程的时候,在某一个时刻,其实也只能运行一个,但JVM用时间片的调度机制,时间间隔非常短,这样就好像是多个线程同时执行。
线程常用方法
线程分类
- 守护线程
- 用户线程
他们之间的区分:是判断虚拟机什么时候离开。
- 用户线程:java虚拟机在所有非守护线程离开后后自动离开。
- 守护线程:守护线程是服务用户线程,如果没有用户线程,那么就没有服务对象,也没有理由在进行下去。
目前,在java中垃圾回收器就是一个典型的守护线程,当程序中不再有任何运行中的线程,程序就不会产生垃圾,垃圾回收器就没有事可做,所以垃圾回收器线程是java虚拟机仅剩的线程,java虚拟机会自动离开。
java生命周期
- NEW 未启动的线程,称之为“新建”。
当创建一个线程,就意味着该线程处于新建的状态,他的线程代码未得到JVM执行。
- RUNNABLE 线程处于运行的状态,称之为“运行”
一旦调用了start(),该线程就是一个可运行的线程,但可运行的线程并不一定就在执行,这需要操作系统为该线程赋运行时间,一旦线程开始运行,它不一定时钟保持运行,因为操作系统的时间片分配,会打断它的执行,以便其他线程得到执行。
- BLOCKED 受阻塞并等待某个监视器锁的线程,称之为“阻塞”
- WAITING 无期限等待另一个线程来执行某一特定操作的线程,称之为“等待”。
- TIMED_WAITING 等待另一个线程来执行取决于指定等待时间操作的线程,称之为“超时等待”。
在实际开发中,线程的阻塞和等待的区别不大,当一个线程被阻塞或者处于等待的时候,它暂时会停止,不会执行线程体代码。
当一个可运行的线程在获取某个对象锁的时候,若该对象锁被别的线程占用,则JVM会把该线程放入锁池,这种叫做同步阻塞。
当一个线程要另一个线程来通知它的调度,就是进入等待,这时通过object.wait和Thread.join( ) 方法来进入等待。
有几种方法可以使线程进入到超时等待,object.wait(long timeout)、Thread.join(long timeout)、Thread.sleep(long millis)。
- TERMINATED 已推出的线程处于这种状态,称之为“中止”状态。
线程被终止的两种情况:
- 线程自然执行完毕。
- 由于没有捕获到异常而结束,或者突然死亡。
判断线程是否终止可以用isAlive方法。
线程让步
使用这个方法或把正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
Thread.yield();
线程加入
就是让子线程加入到主线程中运行。
thread.join();
线程的优先级
java将线程分成10个等级,用1~10,数字越大优先权最高,用三个静态变量表示,MIN_PRIORITY、MAX_PRIORITY 、NORMAL_PRIORITY,对应数字等级就是1、10、5,当一个线程默认被创建的时候,就是普通5。
线程同步
当多个线程,需要对同一共享数据进行访问。
案例:就是火车票卖票,可能会出现一张票同时被两个不同的销售点卖??????
package com.example.test;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
public class CeShi implements Runnable {
private int tickets = 0;
private boolean flag = true;
public static void main(String[] args) {
CeShi c = new CeShi();
Thread t = new Thread(c);
t.setName("售票1");
t.start();
Thread t1 = new Thread(c);
t1.start();
t1.setName("售票2");
Thread t2 = new Thread(c);
t2.start();
t2.setName("售票3");
}
@Override
public void run() {
// TODO Auto-generated method stub
while(flag) {
flag = sell();
}
}
private boolean sell() {
// TODO Auto-generated method stub
if(tickets<100) {
++tickets;
System.out.println(Thread.currentThread().getName()+"正在卖"+tickets);
return true;
}else {
return false;
}
}
}
问题主要出现在tickets++;JVM在执行这句的时候不是就是一条指令就可以,它要分成几步:
- 将成员变量tickets转入当前线程的寄存器。
- 取出当前寄存器的tickets加1。
- 讲结果在赋给tickets。
假如一个线程在进行过程中,完成步骤1、2,而这时分配时间到了,执行权就被其他的拿走了,这时还没有来的及改变值,所以就出现卖出同一张票。
为解决上面的问题,引出关键字synchronized,将方法申明成一个同步方法。
public synchronized boolean sell() {
// TODO Auto-generated method stub
if(tickets<100) {
++tickets;
System.out.println(Thread.currentThread().getName()+"正在卖"+tickets);
return true;
}else {
return false;
}
}
如果一个线程调用了synchronized修饰方法,它就能保证该方法在执行完毕前不会被另一个线程打断,这种运行机制叫同步线程机制,而一个线程在执行过程中可能会被其他线程打断,这种运行机制叫异步线程机制。
上面的用到了同步代码块,也可以将共享的语句封装在{ }之内,然后用synchronized放在某个对象前面修饰这个代码块。
synchronized (this) {
if(tickets<100) {
++tickets;
System.out.println(Thread.currentThread().getName()+"正在卖"+tickets);
return true;
}else {
return false;
}
}
对象锁
同步机制的实现主要利用到了”对象锁”。在JVM为每一个对象,都关联了一个锁,这个锁代表任何时候只允许一个线程拥有的特权。通常情况下,线程访问当前对象的实例变量时不需要锁,但是如果线程获取当前对象的锁,那么在释放之前其他线程是不能访问这个兑现给的实例变量,在某个时间点,一个对象的锁只能被一个线程拥有。
public boolean sell() {
// TODO Auto-generated method stub
lock.lock();//获取锁
if(tickets<100) {
++tickets;
System.out.println(Thread.currentThread().getName()+"正在卖"+tickets);
return true;
}else {
return false;
}
lock.unlock();//释放锁
}
wait和notify的使用(消费者和生产者)
public class Test6 {
public static void main(String[] args) {
Test6 t6 = new Test6();
Clecker c = t6.new Clecker();
Thread t = new Thread(t6.new Consumer(c));
t.start();
Thread t1 = new Thread(t6.new Produceer(c));
t1.start();
}
class Consumer implements Runnable{
Clecker c = null;
public Consumer(Clecker c) {
super();
this.c = c;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
c.get();
}
}
}
class Produceer implements Runnable{
Clecker c = null;
public Produceer(Clecker c) {
super();
this.c = c;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
c.add();
}
}
}
class Clecker{
private int productnumber;//生产数
public synchronized void add(){
if(productnumber>=20) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else {
++productnumber;
notifyAll();
System.out.println("正在生产"+productnumber);
}
}
public synchronized void get() {
if(productnumber<=0) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else {
System.out.println("消费者正在消费"+productnumber);
productnumber--;
notifyAll();
}
}
}
}
死锁
public static void main(String[] args) {
DeadLock dl = new DeadLock();
TestThread tt =new TestThread();
TestThread tt1 = new TestThread();
tt1.flag= false;
Thread t = new Thread(tt);
Thread t1 = new Thread(tt1);
t.start();
t1.start();
}
}
class TestThread implements Runnable{
public boolean flag = true;
static Object o = new Object();//资源1
static Object o1 = new Object();//资源2
//为什么要加static,是因为要保证只有一个对象
@Override
public void run() {
// TODO Auto-generated method stub
if(flag) {
synchronized(o) {
System.out.println("锁定资源1,等待资源2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(o1) {
System.out.println("complete");
}
}
}else {
synchronized(o1) {
System.out.println("锁定资源2,等待资源1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(o) {
System.out.println("complete");
}
}
}
}
}
死锁形成原因:线程1锁住资源a,等待b,线程2锁住b,在等待a,两个线程都在等待自己的资源,水也不愿意自己让出资源。
使用集合工具类同步化集合类对象
使用collection这个集合工具类中提供几个静态方法把非线程集合对象包装成支持同步的集合类对象。
但在进行迭代遍历的时候,还是需要用synchoronized加锁。
public static void main(String[] args) {
List list = Collections.synchronizedList(new ArrayList<>());
list.add("111");
list.add("222");
list.add("113");
list.add("222");
list.add("115");
list.add("227");
synchronized (list) {
Iterator it = list.iterator();
while(it.hasNext()) {
Object obj = it.next();
System.out.println(obj.toString());
}
}
}
jdk1.5之后提供并发集合类
在jdk1.5之后,新增了java.unil.concurrent这个包,其中包括了一些确保线程安全的并发集合类,如CopyOnWriteArraySet、CopyOnWriteArrayList、ConcurrentHashMap,他们在效率和安全上取得较好的平衡。
public static void main(String[] args) {
List list =new CopyOnWriteArrayList<>();
list.add("111");
list.add("222");
list.add("113");
list.add("222");
list.add("115");
list.add("227");
Iterator it = list.iterator();
while(it.hasNext()) {//在这和之前的就不一样,不需要手动的同步了。
Object obj = it.next();
System.out.println(obj.toString());
}
}
Timer
这个类主要用来就是执行定时任务的,相当于一个定时器,与每一个Timer对象相对应的是单个后台线程。
定义任务类
TimerTask是一个抽象类,在创建的时候,继承它,并且实现run方法。
执行任务方法
代码如下:
public class Test9 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new MyTask(), 1000,1000);
}
}
class MyTask extends TimerTask {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("起床啦");
}
}