--------------------- android培训、java培训、java学习型技术博客、期待与您交流! -------------------
Java基础知识 补充
1 单例模式
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
单例模式要点:
|---单例模式的要点有三个;
|----是某个类只能有一个实例;
|----是它必须自行创建这个实例;
|----是它必须自行向整个系统提供这个实例。
|----从具体实现角度来说,就是以下三点:
|----是单例模式的类只提供私有的构造函数,
|----是类定义中含有一个该类的静态私有对象,
|----是该类提供了一个静态的共有的函数用于创建或获取它本身的静态私有对象。
饿汉式:
在对象加载时就创建了一个对象实例,饿汉式的好处代码简单易懂,避免了多线程的同步问题。不足之处是当还没用到该对象时就创建了占用内存。
演示:
class SingleE
{
private static final SingleE s= new SingleE();
private SingleE(){}
public static SingleE getInstance()
{
return s;
}
}
懒汉式:
SingleL类进内存,对象还没有加载,只有调用了getInstance方法时,才建立对象
对象被延迟加载节省空间,但是要注意多线程下的同步问题。
演示:
class Single
{
private static SingleL s= null;
private SingleL(){}
public static SingleL getInstance()
{
if(s==null)
s=new SingleL();
return s;
}
}
懒汉式:在多线程中会出现问题,讲解如下:
当线程A加载对象,用-->A标识A线程执行所到的地方
当线程B加载对象,用-->B标识B线程执行所到的地方
class Single
{
private static SingleL s= null;
private SingleL(){}
public static SingleL getInstance()
{
if(s==null)
-->A 当线程A执行到此处时,线程A失去CPU的执行权
此时线程B进入: -->B 接下来线程B 创建了一个SingleL 对象,之后线程A获得执行权,也会 创建了一个SingleL 对象,那么就达不到单例模式的功能了!
s=new Single();
return s;
}
}
那么如何解决"懒汉式"的这种问题呢?
不难相处线程的同步,请看下面方法:
class Single
{
private static SingleL s= null;
private SingleL(){}
public static synchronized SingleL getInstance()
{
if(s==null)
s=new SingleL();
return s;
}
}
这样问题就可以解决了。但是当程序中的线程较多时就会使程序运行效率,因为,每个线程执行到synchronized时都要判断一下锁;
我们看一下下面的解决方法:
class SingleL
{
private static SingleL s= null;
private SingleL(){}
public static SingleL getInstance()
{
while(s==null)
{
synchronized(SingleL.class)
{ //这里的锁是不可以用this 的原因是 该方法是static方法 ,所以用了该类所属的字节码对象SingleL.class
if(s==null)
s=new SingleL();
}
}
return s;
}
}
这样做的好处是,一避免了产生多个SingleL对象,二,提高了程序的执行效率,因为当一个线程创建了SingleL对象后就在while语句被否定,不会在进行锁的判断,
从而减少了锁的判断次数。
单例模式总结:
饿汉式 :
是类一加载进内存就创建好了对象;
懒汉式则是类才加载进内存的时候,对象还没有存在,只有调用了getInstance()方法时,
对象才开始创建。
懒汉式:
是延迟加载,如果多个线程同时操作懒汉式时就有可能出现线程安全问题,解决线程安全问题;可以加同步来解决。但是加了同步之后,每一次都要比较锁,效率就变慢了, 所以可以加双重判断来提高程序效率。
注:开发常用饿汉式,因为饿汉式简单安全。懒汉式多线程的时候容易发生问题
2 装饰设计模式
在前面的IO中已经说过装饰设计模式,演示了例子,在这里再说一下。
装饰设计模式:对已有的对象功能进行增强。可以定义类,将已有对象传入,基于已有的功能,
并提供加强功能。 装饰类通常会通过构造方法接收被装饰的对象,并基于被装饰的对象的功能,提供更强的功能。
举例说明:在IO流中BufferedReader 的readLine()就是用到了装饰类的思想。
面我们自己写一个装饰类类模仿readLine():
例:为了简化代码只用了抛出处理异常:
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class MyBufReaLine
{
FileReader r;
MyBufReaLine(FileReader r)
{ //记住传入的类
this.r=r;
}
public String myReaderLine() throws IOException
{
StringBuilder sb=new StringBuilder();//在创建StringBuilder对象,该对象比StringBuffer效率高
int ch=0;
while((ch=r.read())!=-1)
{
if(ch=='\r') continue; //在windows中换行符是“\r\n”
if(ch=='\n') return sb.toString(); //在"\r\n”后换行
else sb.append((char)ch);
}
if(sb.length()!=0) return sb.toString();
return null;
}
public void MyClose() throws IOException
{
r.close();
}
public static void main(String[] args) throws IOException
{
FileReader fr=new FileReader("buf.txt");
MyBufReaLine mybfr= new MyBufReaLine(fr);//创建自己的MyBufRealine对象
String line=null;
while((line=mybfr.myReaderLine())!=null)
{ //在用自己写得读取一行的语句读取数据
System.out.print(line);
}
mybfr.MyClose();
}
}
装饰类与继承的区别:
假设我们有各类MyReader专门用于读取数据的类
为了描述不同的数据,之后基于MyReader建立专门的读取类。
下面就是继承实现的体系:
|--MyReader
|--MyTextReader //当法相该类读取效率低下的时候
|--MyBufferTextReader //为了提高读取的效率,于是衍生了该子类
|--MyMediaReader //当法相该类读取效率低下的时候
|--MyBufferMediaReader //为了提高读取的效率,于是衍生了该子类
而MyReader还可能产生新的子类。去子类还要扩展子类提升读取效率。即
|--MyPicReader //当法相该类读取效率低下的时候
|--MyBufferPicMediaReader//为了提高读取的效率,于是衍生了该子类
……
却如此下去是不是扩展性不好而且很臃肿呢?因为所用提升读取效率的子类都应用了相同的技术。那么可不可以优化呢?
于是设想单独设置一个缓冲区类。作用谁需要用缓冲技术提升效率,就为谁服务
Class myBufferReader
{
myBufferReader(MyTextReader text){}
myBufferReader(MyMediaReader media){}
……
}
上面的类扩展性很差。
优化找到参数的共同性类型。通过多态的形式,可以提高扩展性。
Class myBufferReader
{
myBufferReader(MyReader r){}
}
于是上面的体系就优化为下面这种形式:
装饰类形成的体系:
|--MyReader
|--MyTextReader
|--MyMediaReader
|--MyPicReader
……
|--myBufferReader//就是装饰模式的应用
所以装饰模式比继承要灵活。避免了集成体系的臃肿,同时降低了,类与类之间的关系
装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强的功能。
所以装饰类和被装饰类通常都属于一个体系中。
举例说明:在IO流中BufferedReader 的直接子类LineNumberReader也用到了包装类模式
下面是利用包装类模仿LineNumberReader功能的例子:
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class MyLineNumberReader {
private Reader r;
private int lineNumber;
MyLineNumberReader(Reader r){//记住传入的类
this.r=r;
}
public String myReadLine() throws IOException{
lineNumber++;
StringBuilder sb=new StringBuilder();//在创建StringBuilder对象,该对象比StringBuffer效率高
int ch=0;
while((ch=r.read())!=-1){
if(ch=='\r') continue; //在windows中换行符是“\r\n”
if(ch=='\n') return sb.toString() //在"\r\n”后换行
else sb.append((char)ch);
}
if(sb.length()!=0) return sb.toString();
return null;
}
public void setLineNumber(int lineNumber){
this.lineNumber =lineNumber;
}
public int getLineNumber(){
return lineNumber;
}
public void myclose() throws IOException{
r.close();
}
public static void main(String[] args) throws IOException {
FileReader fr=new FileReader("C:\\Users\\Desktop/T9.java");
MyLineNumberReader mybfr= new MyLineNumberReader(fr);//创建MyLineNumberReader对象
mybfr.setLineNumber(100);//设置编号的开始值
String line=null;
while((line=mybfr.myReadLine())!=null){/ /在用自己写得读取一行的语句读取数据
System.out.println(mybfr.lineNumber+":"+line);/ /读取一行的语句前加上了编号
}
mybfr.myclose();
}
}
装饰模式总结:
装饰模式的主要目的就是:动态的为某个类型添加新的职责装饰模式更侧重的是某个类型的功能经常的动态的。增加的情况装饰模式就好像是穿了一层层的外壳,这样的方式避免了通过继承来为类型添加新的职责的形式可取,通过继承的方式容易造成子类的膨胀,装饰模式记录操作模式的状态,可以进行有效的回滚操作,以完成撤销操作。
3 正则表达式
正则表达式是专门操作字符串的,对字符串既简单又便捷的表达式就是正则表达式
正则表达式:符合一定规则的表达式。
作用:用于专门操作字符串。
例子:
class RegexDemo
{
public static void main(String[] args)
{
checkQQ();
}
public static void checkQQ()//正则表达式的书写
{
Stringqq = "1as5423";
Stringregex="[1-9][0-9]{4,14}";
boolean flag = qq.matches(regex);
if(flag)
System.out.println("qq:"+qq);
else
System.out.println(qq+"错误数字");
}
}
特点:用于一些特定的符号来表示一些代码操作。这样就简化书写。
所以学习正则表达式,就是在学习一些特殊符号的使用。
好处:可以简化对字符串的复杂操作。
弊端:符号定义越多,正则越长,阅读性越差。
常用表达式组成:
字符类
[abc] a、b 或 c(简单类)
[^abc] 任何字符,除了 a、b 或 c(否定)
[a-zA-Z] a 到 z 或 A 到 Z,两头的字母包括在内(范围)
[a-d[m-p]] a 到 d 或 m 到 p:[a-dm-p](并集)
[a-z&&[def]] d、e 或 f(交集)
[a-z&&[^bc]] a 到 z,除了 b 和 c:[ad-z](减去)
[a-z&&[^m-p]] a 到 z,而非 m 到 p:[a-lq-z](减去)
预定义字符类
. 任何字符(与行结束符可能匹配也可能不匹配)
\d 数字:[0-9] (想要使用的格式就是\\d)
\D 非数字: [^0-9]
\s 空白字符:[ \t\n\x0B\f\r]
\S 非空白字符:[^\s]
\w 单词字符:[a-zA-Z_0-9] (邮箱时可以使用)
\W 非单词字符:[^\w]
注意:要出现以上字符一定要有双反斜杠\\d
Greedy 数量词
X? X,一次或一次也没有
X* X,零次或多次 (注意只有x类型的才可能多次)
X+ X,一次或多次(可以判定空格+,这样可以取出有效字符,去处空格)
X{n} X,恰好 n 次
X{n,} X,至少 n 次
X{n,m} X,至少 n 次,但是不超过 m 次
具体操作功能:
1 匹配:matches方法
String matches方法。用规则匹配整个字符串,只要有一处不符合规则,就匹配结束,返回false。
手机号段只有 13xxx 15xxx 18xxxx
public static void checkTel()
{
String tel = "16900001111";
String telReg = "1[358]\\d{9}";//第一位是1,第二位是3、5、8,剩下的数字随意,但只能有9位。
System.out.println(tel.matches(telReg));
}
2 切割:String split();
特殊点:切割点 . 要用\\. 如果直接用 . 这个代表的是任意字符。
切割\\,要用\\\\
public staticvoid splitDemo(String str,String reg)
{
//String reg = " +";//按照多个空格来进行切割
String[] arr = str.split(reg);
System.out.println(arr.length);
for(String s : arr)
{
System.out.println(s);
}
}
叠词的切割:按照叠词完成切割。
为了可以让规则的结果被重用,可以将规则封装成一个组。用()完成,这就是捕获组。组的出现都有编号。从1开始。 想要使用已有的组可以通过 \n(n就是组的编号)的形式来获取。
String reg = “(.)\\1”
如果想捕获多个重叠的词,设置为String reg = “(.)\\1” 即可。
捕获组可以通过从左到右计算其开括号来编号。例如,在表达式 ((A)(B(C))) 中,存在四个这样的组:
1 | ((A)(B(C))) |
2 | \A |
3 | (B(C)) |
4 | (C) |
组零始终代表整个表达式。
所以正则表达式的弊端:符号定义的越多,正则越长,阅读性越差
3 替换:StringreplaceAll(regex,str);如果regex中有定义组,可以在第二参数中通过$符号获取正则表达式中的已有的组。
String str = "wer1389980000ty1234564uiod234345675f";//将字符串中的数字替换成#。
replaceAllDemo(str,"\\d{5,}","#");//将连续超过5个以上的数字,替换成#
String str2 = "erkktyqqquizzzzzo";//将叠词替换成& //将重叠的字符替换成单个字母。zzzz->z
replaceAllDemo(str1,"(.)\\1+","&");//将重叠的词替换成&符号
replaceAllDemo(str1,"(.)\\1+","$1");//$1的意思就是获取前面组的自身,并且,替换成1个的那个组。用$符号代表前面组的获取
public static void replaceAllDemo(Stringstr,String reg,String newStr)
{
str = str.replaceAll(reg,newStr);
System.out.println(str);
}
4 获取:
将字符串中的符合规则的子串取出。
操作步骤:
1 将正则表达式封装成对象。
2 让正则对象和要操作的字符串相关联。
3 关联后,获取正则匹配引擎。
4 通过引擎对符合规则的子串进行操作,比如取出。
Pattern类,用来描述正则表达式,没有构造函数,利用自身方法返回对象。
Pattern p =Pattern.compile(reg);
Pattern 正则表达式的封装类,其实也就是将一个字符串包装一下罢了。有点多余这个类。
//静态方法将参数指定的字符串封装成正则对象。
public static Pattern compile(String regex)
//将一个正则对象与需要操作的字符串绑定,并返回一个匹配器,绑定的字符串可以是以下任意一种。
//CharBuffer, Segment, String, StringBuffer, StringBuilder 使用这种方式可以对Buffer与Builder使用正则。
public Matcher matcher(CharSequence input)
完成以上两步后,使用得到的Matcher匹配器对象即可对绑定的字符串经行多种正则匹配操作。如:
//匹配
public boolean matches()
//替换
public String replaceAll(String replacement)
//获取,即反向切割
public String group()
需要注意的是,当使用group时,应该先使用public boolean find()方法对绑定的字符串内容经行查找,使Matches匹配器内部维护的索引指针指向符合正则规则的正确位置。每一次对匹配器使用匹配替换获取等操作时,都会影响到索引指针,如匹配后索引指针会移动到。
//可以重围当前匹配器的索引指针,参数可以用来指定一个新的绑定字符串
public Matcher reset(CharSequence input)
当使用find方法查找到正确的匹配结果后可以使用start与end方法获取当前匹配正确结果后的子串在父串中索引前后位置。
例子:
1 static void getAllTele() {
2
3 String tele = "kajsdf;wlke10884324909fjoisdf15983230098asdfasd13909876655asdfe13823455sf";
4
5 Pattern pat = Pattern.compile("1[358]\\d{9}");
6
7 Matcher mat = pat.matcher(tele);
8
9 while(mat.find()) {
10 System.out.println(mat.group());
11 }
12
13 }
14
15 static void checkEmail() {
16 String email = "slatop@qq.com";
17 //\\w&&[^_]&&[^\\d]其实相当于a-zA-Z
18 Pattern pat = Pattern.compile("[a-zA-Z][\\w]{5,18}@[\\w&&[^_]]{2,8}(\\.[\\w&&[^_]&&[^\\d]]+)+");
19
20 Matcher mat = pat.matcher(email);
21
22 System.out.println(mat.matches());
23 }
24
25 static void deleteDirty() {
26 String text = "你妈如果这样可以不你爸这个不知道是不是滚蛋有这样一天操了为为为为什么不操操!";
27
28 Pattern pat = Pattern.compile("[妈爸[滚蛋]操]");
29
30 Matcher mat = pat.matcher(text);
31
32 text = mat.replaceAll("*");
33
34 System.out.println(text);
35 }
JDK1.5新特性
1 静态导入
导入时写到方法名,调用时不需要写类名,直接调用方法名即可,注意:需要JDK1.5以上版本,否则会报错
[java] view plaincopy
1 import static java.lang.Math.*;
2 class Import{
3 public static void main(String[] args) {
4 System.out.println(abs(7));
5 }
6 }
2 可变参数
可变参数的特点: public static int add(int x,int ...args )
a.可变参数只能出现在参数列表的最后
b...位于变量类型和变量名之间,前后有无空格都可以
调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数
[java] view plaincopy
7 class VariableParameter
8 {
9 public static void main(String[] args)
10 {
11 System.out.println(add(5,9));
12 System.out.println(add(6,1,3,5));
13 }
14 public static int add(int x,int...args)
15 {
16 int sum=x;
17 for(int i=0;i<args.length;i++)
{
18 sum+=args[i];
19 }
20 return sum;
21 }
22 }
3 增强for循环
语法:for(type 变量名:集合变量名){}
type前可加修饰符;迭代变量必须在()中定义;集合变量可以是数组或实现了Iterable接口的集合类
[java] view plaincopy
23 class VariableParameter
24 {
25 public static void main(String[] args)
26 {
27 System.out.println(add(5,9));
28 System.out.println(add(6,1,3,5));
29 }
30 public static int add(int x,int...args)
31 {
32 int sum=x;
33 for(int arg:args)
34 {
35 sum+=arg;
36 }
37 return sum;
38 }
39 }
4 基本数据类型自动拆箱装箱
装箱是将基本数据类型转化为包装类
拆箱是将包装类转化为基本数据类型
基本数据类型:
byte--->Byte
short--->Short
int--->Integer
long--->Long
float--->Float
double--->Double
char--->Character
boolean--->Boolean
例子:
1.装箱:自动把一个基本数据类型的数据装箱成一个该类型数据的对象引用
Integer i = 3;(jdk1.5之前这样写是不行的,编译报错)
2.拆箱:自动把一个基本数据类型的对象引用拆箱成一个基本数据类型的数据,再参与运算
Integer i = 12;
Syetem.out.println(i+4);
享元模式:
Integer num1 = 12;
Integer num2 = 12;
System.out.println(num1 == num2);//打印true
Integer num5 = Integer.valueOf(12);
Integer num6 = Integer.valueOf(12);
System.out.println(num5 == num6);//打印true
Integer num3 = 129;
Integer num4 = 129;
System.out.println(num3 == num4);//打印false
为什么前面的返回true而后面的运算返回false呢?
对于基本数据类型的整数,装箱成Integer对象时,如果该数值在一个字节内,(-128~127),一旦装箱成Integer对象后,就把它缓存到磁里面,当下次,又把该数值封装成Integer对象时会先看磁里面有没有该对象,有就直接拿出来用,这样就节省了内存空间。因为比较小的整数,用的频率比较高,就没必要每个对象都分配一个内存空间。这就是享元模式!
比如26个英文字母,10个阿拉伯数字
例子:
[java] view plaincopy
40 class AutoBox
41 {
42 public static void main(String[] args)
43 {
44 Integer iObj=3;//基本数据类型的自动装箱
45 System.out.println(iObj+12);//基本数据类型的自动拆箱
46 //当Integer封装的数据在一个字节(-128-127)之间,则只建立一个对象,实现享元模式(属于设计模式).
47 Integer i1=10;
48 Integer i2=10;
49 System.out.println(i1==i2);//true
50 Integer i3=130;
51 Integer i4=130;
52 System.out.println(i3==i4);//false
53 Integer i5=Integer.valueOf(3);
54 Integer i6=Integer.valueOf(3);
55 System.out.println(i5==i6);//true
56 }
57 }
5 枚举
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则编译器就会报错。枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。枚举只有一个成员时就可以作为一种单例的实现方式。
私有的构造函数
每个元素分别用一个公有的静态成员变量表示
可以有若干个公有方法或抽象方法,如nextDay()方法必须是抽象的,采用抽象方法定义nextDay就是将大量的if else语句转移成一个个独立的类
为什么要有枚举?
问题:要定义星期几或性别的变量,该怎么定义?假设用1-7分别表示星期一到星期日,
但有人可能会写成int weekday = 0;或即使使用常量方式也无法阻止意外。
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。
枚举可以让编译器在编译时就可以控制源程序中填写的非法值,
普通变量的方式在开发阶段无法实现这一目标。
用普通类如何实现枚举的功能?
定义一个Weekday类来模拟实现:
步骤:
1.私有化构造方法
2.每个元素分别用一个公有的静态成员变量表示(public static final)
3.可以有若干公有方法或抽象方法。采用抽象方法定义nextDay就将大量的if.else语句
转移成了一个个独立的类。
枚举的应用:
举例:定义一个Weekday的枚举。
扩展:枚举类的values,valueOf,name,toString,ordinal等方法
总结:枚举是一种特殊的类,其中的每个元素都是该类的一个实例对象。
例如可以调用WeekDay.SUN.getClass().getName和WeekDay.class.getName()。
枚举的高级应用:
1枚举就相当于一个类,其中也可以定义构造方法、成员变量、普通方法和抽象方法。
2枚举元素必须位于枚举体中的最开始部分,枚举元素列表的后要有分号与其他成员分隔。把枚举中的成员方法或变量等放在枚举元素的前面,编译器报告错误。
3带构造方法的枚举
构造方法必须定义成私有的
如果有多个构造方法,该如何选择哪个构造方法?
枚举元素MON和MON()的效果一样,都是调用默认的构造方法。
4带方法的枚举
定义枚举TrafficLamp
实现普通的next方法
实现抽象的next方法:每个元素分别是由枚举类的子类来生成的实例对象,
这些子类采用类似内部类的方式进行定义。增加上表示时间的构造方法
5枚举只有一个成员时,就可以作为一种单例的实现方式。
例子:
58 public class EnumTest
59 {
60 public static void main(String[] args)
61 {
62 WeekDay weekDay=WeekDay.MON;
63 System.out.println(weekDay.nextDay());
64 }
65 }
66
67 abstract class WeekDay
68 {
69 private WeekDay(){}
70 public final static WeekDay SUN=new WeekDay()
71 {
72 public WeekDay nextDay()
73 {
74 return MON;
75 }
76 };
77 public final static WeekDay MON=new WeekDay()
78 {
79 public WeekDay nextDay()
80 {
81 return SUN;
82 }
83 };
84 public abstract WeekDay nextDay();
85 public String toString()
86 {
87 return this==SUN?"SUN":"MON";
88 }
89 }
6 泛型
1
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入, 编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。
2
ArrayList<E>类定义和ArrayList<Integer>类引用中涉及如下术语:
整个称为ArrayList<E>泛型类型
ArrayList<E>中的E称为类型变量或类型参数
整个ArrayList<Integer>称为参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
ArrayList<Integer>中的<>念着typeof
ArrayList称为原始类型
3
参数化类型与原始类型的兼容性:
参数化类型可以引用一个原始类型的对象,编译报告警告,
例如:
Collection<String> c = new Vector();//可不可以,不就是编译器一句话的事吗?
原始类型可以引用一个参数化类型的对象,编译报告警告,
例如:
Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去
4
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); //错误!///不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //也错误!
编译器不允许创建泛型变量的数组。即在创建数组实例时,
数组的元素不能使用参数化的类型,
例如:
下面语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];
5
泛型限定:
|----限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();//Integer是 Number的子类, 实际类型参数可以使Number和Number的子类
错误:Vector<? extends Number> x = new Vector<String>();//String不是 Number的子类
|----限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();//Number是Integer 的父类,实际类型参数可以使Integer的父类Number和Integer
错误:Vector<? super Integer> x = new Vector<Byte>();//Byte不是Integer 的父类
|----提示:
限定通配符总是包括自己。
?只能用作引用,不能用它去给其他变量赋值
Vector<? extends Number> y = new Vector<Integer>();
Vector<Number> x = y;
上面的代码错误,原理与Vector<Object > x11 = new Vector<String>();相似,
只能通过强制类型转换方式来赋值。
7 StringBuilder
和StringBuffer的功能是一样的,但是有区别:
|---StringBuffer(JDK1.0)是线程安全的。
|---- StringBuilder(JDK1.5)不保证线程安全。
一般来说,我们写的程序都是单线程的,所以,用StringBuilder,效率高。
8 多线程的升级
Lock和Condition:
实现提供比synchronized方法和语句可获得的更广泛的锁的操作,可支持多个相关的Condition对象
Lock是个接口
锁是控制多个线程对共享数据进行访问的工具。
这是JDK1.5中提供的多线程升级的解决方案:
将同步synchonized替换成了显示的Lock操作,将Object中的wait、notify、notifyAll替换成了Condition对象。 该对象可以Lock锁进行获取
Lock的方法摘要:
|----void lock() 获取锁。
|----Condition newCondition() 返回绑定到此 Lock 实例的新 Condition 实例。
|----void unlock() 释放锁。
|----Condition方法摘要:
|----void await() 造成当前线程在接到信号或被中断之前一直处于等待状态。
|----void signal() 唤醒一个等待线程。
|----void signalAll() 唤醒所有等待线程。
应用举例:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Resource
{
private String name;
private int count=1;
boolean flag=false;
private Lock lock=new ReentrantLock();//获取 lock对象通过new ReentrantLock();
private Condition condition_P=lock.newCondition();//获取Condition对象通过lock.newCondition();
private Condition condition_C=lock.newCondition();
public void set(String name) throws InterruptedException
{
lock.lock();//上锁
try
{
while(flag)
condition_P.await();//使当前线程等待
this.name = name+"^"+count++;
System.out.println(Thread.currentThread().getName()+"*生产者*"+this.name);
flag=true;
condition_C.signal(); //唤醒持有另一把锁的线程
}
finally
{
lock.unlock();
}
}
public void out() throws InterruptedException
{
lock.lock();//上锁
try
{
while(!flag)
condition_C.await();//使当前线程等待
System.out.println(Thread.currentThread().getName()+"^^^^消费者^^^^"+this.name);
flag=false;
condition_P.signal();//唤醒持有另一把锁的线程
}
finally
{
lock.unlock();
}
}
}
class Producer implements Runnable
{
//实现Runnable接口以至于将需要同步的代码封装进去
private Resource r;
Producer(Resource r)
{
this.r=r;
}
public void run()
{
//将需要同步的代码封装进去
while(true)
{
try
{
r.set("+商品+");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable
{
//实现Runnable接口以至于将需要同步的代码封装进去
private Resource r;
Consumer(Resource r)
{
this.r=r;
}
public void run()
{
//将需要同步的代码封装进去
while(true)
{
try
{
r.out();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
class RCThread
{
public static void main(String[] args)
{
Resource r=new Resource();//创建一个资源
Producer p =new Producer(r);//创建两个操作者中的生产者
Consumer c =new Consumer(r);//创建两个操作者中的消费者
new Thread(p).start();//开启线程
new Thread(p).start();
new Thread(c).start();
new Thread(c).start();
}
}
在该实例中实现了只唤醒对方的操作。
总结:
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器
总结JDK版本的升级原则:
|----提高效率
|----提高安全性
|----简化书写