一 构造函数的相关注意
1 构造函数的修饰符
默认构造器的修饰符只跟当前类的修饰符有关。 比如B如果是public的,则默认构造方法是public的。
如果B是默认的访问权限,则构造方法相同。
当B是内部类是,前面也可以有protected,private等,默认添加的构造方法仍然和类的修饰符一致。
2 构造函数的调用
1:在构造调用另一个构造函数,调用动作必须置于最起始的位置。
2:不能在构造函数以外的任何函数内调用构造函数。
3:在一个构造函数内只能调用具体某个类的一个构造函数。
4 子类调用父类的构造函数的注意:
1.子类的构造过程中必须调用父类的构造方法。
2.子类可在自己的构造方法中使用super()来调用父类的构造方法。
(1)使用this来调用本类的另外的构造方法。
(2)如果调用super必须写在子类构造方法的第一行。
3.如果子类的构造方法中没有显示的调用父类的构造方法,则系统默认的调用父类的无参的构造方法。
4.如果子类的构造方法中既没有显示调用父类的构造方法,而父类中又没有无参的构造方法,则编译出错。
3 构造函数不能被重写
首先说明,构造器也就是我们所说的构造方法. 也就是方法名和类名一致的那个方法.
例如:
class Person
{
String name;
int age;
Person(){} //这个方法就是无参数的构造方法.
Person(String name, int age) //这个方法就是有参数的构造方法.
{
this.name = name;
this.age = age;
}
}
这个问题,看起来非常简单,但是很多初学者学完继承后,都认为子类可以继承父类的一切,事实上,构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload。
希望大家一定注意,不是父类所有的东西都要继承.
override(重写)
1、方法名、参数、返回值相同。
2、子类方法不能缩小父类方法的访问权限。
3、子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
4、存在于父类和子类之间。
5、方法被定义为final不能被重写。
overload(重载)
1、参数类型、个数、顺序至少有一个不相同。
2、不能重载只有返回值不同的方法名。(返回值不会引起重载)
3、存在于父类和子类、同类中。
ProcessBuilder.start() 和 Runtime.exec() 方法都被用来创建一个操作系统进程(执行命令行操作),并返回 Process 子类的一个实例,该实例可用来控制进程状态并获得相关信息。
Process 类
提供了
执行从进程输入、执行输出到进程、等待进程完成、检查进程的退出状态以及销毁(杀掉)进程
的方法。创建进程的方法
可能无法针对某些本机平台上的特定进程很好地工作
,比如,本机窗口进程,守护进程,Microsoft Windows 上的 Win16/DOS 进程,或者 shell 脚本。创建的子进程没有自己的终端或控制台。它的所有标准 io(即 stdin、stdout 和 stderr)操作都将通过三个流 (getOutputStream()、getInputStream() 和 getErrorStream())
重定向到父进程
。父进程使用这些流来提供到子进程的输入和获得从子进程的输出。因为有些本机平台仅针对标准输入和输出流
提供有限的缓冲区大小,如果读写子 进程的输出流或输入流迅速出现失败,则可能导致子进程阻塞,甚至产生死锁
。 当没有 Process 对象的更多引用时,不是删掉子进程,而是继续异步执行子进程。 对于带有 Process 对象的 Java 进程,没有必要异步或并发执行由 Process 对象表示的进程。
每个
ProcessBuilder实例
管理一个进程属性集。ProcessBuilder的start()方法利用这些属性创建一个新的Process实例。start()方法可以
从同一实例重复调用,以利用相同或者相关的属性创建新的子进程。
不同点:
ProcessBuilder.start() 和 Runtime.exec()
传递的参数有所不同
,Runtime.exec()可接受一个单独的字符串,这个字符串是通过空格来分隔可执行命令程序和参数的;也可以接受字符串数组参数。而ProcessBuilder的构造函数是一个字符串列表或者数组。列表中第一个参数是可执行命令程序,其他的是命令行执行是需要的参数。
通过查看JDK源码可知,Runtime.exec最终是
通过调用ProcessBuilder来真正执行操作的。
- import java.io.BufferedReader;
- import java.io.File;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.util.Map;
-
- public class ProcessBuilderTest {
- public static void restart() throws IOException {
-
- Process p;
-
- String cmd = "c:\\test\\test.bat";
-
- try {
-
- p = Runtime.getRuntime().exec(cmd);
-
- InputStream fis = p.getInputStream();
-
- InputStreamReader isr = new InputStreamReader(fis);
-
- BufferedReader br = new BufferedReader(isr);
- String line = null;
-
- while ((line = br.readLine()) != null) {
- System.out.println(line);
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
-
-
-
- ProcessBuilder pb = new ProcessBuilder("java", "-jar", "Test3.jar");
-
-
- pb.directory(new File("F:\\dist"));
-
-
- Map<String, String> map = pb.environment();
- Process p1 = pb.start();
-
-
- System.exit(0);
- }
- }
二 Timer TimerTask
java.util.Timer定时器,实际上是个线程,定时调度所拥有的TimerTasks。
一个TimerTask实际上就是一个拥有run方法的类,需要定时执行的代码放到run方法体内,TimerTask一般是以匿名类的方式创建。
java.util.Timer timer = new java.util.Timer(true);
// true 说明这个timer以daemon方式运行(优先级低,
// 程序结束timer也自动结束),注意,javax.swing
// 包中也有一个Timer类,如果import中用到swing包,
// 要注意名字的冲突。
TimerTask task = new TimerTask() {
public void run() {
... //每次需要执行的代码放到这里面。
}
};
//以下是几种调度task的方法:
timer.schedule(task, time);
// time为Date类型:在指定时间执行一次。
timer.schedule(task, firstTime, period);
// firstTime为Date类型,period为long
// 从firstTime时刻开始,每隔period毫秒执行一次。
timer.schedule(task, delay)
// delay 为long类型:从现在起过delay毫秒执行一次
timer.schedule(task, delay, period)
// delay为long,period为long:从现在起过delay毫秒以后,每隔period
// 毫秒执行一次。
schedule(TimerTask task, long delay, long period)
schedule(TimerTask task, Date time, long period)
scheduleAtFixedRate(TimerTask task, long delay, long period)
scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
当计划反复执行的任务时,如果你注重任务执行的平滑度,那么请使用schedule方法,如果你在乎的是任务的执行频度那么使用scheduleAtFixedRate方法。 例如,这里使用了schedule方法,这就意味着所有beep之间的时间间隔至少为1秒,也就是说,如果有一个beap因为某种原因迟到了(未按计划执行),那么余下的所有beep都要延时执行。如果我们想让这个程序正好在3秒以后终止,无论哪一个beep因为什么原因被延时,那么我们需要使用scheduleAtFixedRate方法,这样当第一个beep迟到时,那么后面的beep就会以最快的速度紧密执行(最大限度的压缩间隔时间)。
-
- 每一个Timer仅对应唯一一个线程。
- Timer不保证任务执行的十分精确。
- Timer类的线程安全的。
try/catch/finally语句下,finally子句是肯定会执行的。但是很多人做不同的测试,却得出了不同的结论。
具体的原理最好是去看《深入java虚拟机》,里面对jsr、ret等几个指令做了详细的说明。这里不深入分析,而仅仅是从表现形式上看一下finally的特征。
代码:
-
-
-
-
- public class TestFinal {
- public static void main(String[] args) {
- System.out.println("test1:" + testFinal1());
- System.out.println("test2:" + testFinal2());
- System.out.println("test3:" + testFinal3());
- System.out.println("test4:" + testFinal4());
- }
-
- static int testFinal1() {
- int i = 1;
- try {
- return i;
- } finally {
- System.out.println("in testFinal1():finally 肯定会被执行的!");
- i = 48;
- }
- }
-
- static String testFinal2() {
- String str = "try";
- try {
- return str;
- } finally {
- System.out.println("in testFinal2():finally 肯定会被执行的!");
- str = "finally";
- }
- }
-
- static StringBuilder testFinal3() {
- StringBuilder build = new StringBuilder("try ");
- try {
- return build;
- } finally {
- System.out.println("in testFinal3():finally 肯定会被执行的!");
- build.append("finally");
- build = new StringBuilder("你猜我是谁!");
- }
- }
-
- static String testFinal4() {
- try {
- return "return in try";
- } finally {
- System.out.println("in testFinal4():finally 肯定会被执行的!");
- return "return in finally";
- }
- }
- }
输出是:
in testFinal1():finally 肯定会被执行的!
test1:1
in testFinal2():finally 肯定会被执行的!
test2:try
in testFinal3():finally 肯定会被执行的!
test3:try finally
in testFinal4():finally 肯定会被执行的!
test4:return in finally
结论很明显,finally的语句确实执行了,而且肯定是在方法return之前执行的,而且,如果finally中有return语句的话,方法直接结束。这里需要注意的只有一点:在try中的return语句会将返回结果值压栈,然后转入到finally子过程,等到finally子过程执行完毕之后(没有return),再返回。
下面具体看4个例子:
在testFinal1()中,return i;会将结果i的值,也就是1压入栈。即使在finally中将i修改了(i=48),也不回对已经压入栈里的1造成任何影响。
在testFinal2()中,return str;将str的内容压入栈,比如我们假设str的内容为0x108(只是一个地址值),通过这个地址值我们能找到"try",那栈里的内容就是0x108。执行str = "finally",这时候str这个变量的内容可能变为0x237了,这是串"finally"的地址。方法调用结束后,返回的是什么?return时压入栈里的0x108。所以在打印结果时,我们打印的是通过0x108找到的字符串"try"。
在testFinal3()中,return 压栈的是build这个变量的值,比如是0x3579,通过这个值我们可以找到StringBuilder对象。finally语句块中对这个对象的内容进行了修改。build = new StringBuilder("你猜我是谁!");让build变量指向了一个新的对象,这时候build的值可能是0x4579了。但是,别忘了,原来的StringBuilder对象仍然在0x3579处,而我们压栈的正是0x3579啊!方法返回后,我们得到的返回值0x3579,通过这个引用值找到相应的StringBuilder对象,所以打印的结果是test3:try finally。
在testFinal4()中,finally有return语句,直接返回,方法结束。
为什么不同的人有不同的结论?关键是没有正确理解压栈的是什么东西。其实初学java的时候,如果理解了变量是什么,并区分引用和对象本身就不会得到错误的结论了。再有,如果理解java中,方法调用都是采用传值模式的话,这里也就类似的可以明白了。
关于 Java 虚拟机是如何编译 finally 语句块
finally在控制转移语句之前执行(控制 转移语句有:return-throw-continue-break)
四 java反射回顾
1.java反射机制的作用:
使java具备了动态性,可以在运行时构造任意一个类的对象,可以在运行时获取任意类的任意成员,并修改其成员变量或者调用其方法。写程序时(比如框架)无法知道将来要被调用的类名,所以,无法new对象。所以,反射机制解决了框架要解决的核心问题。反射就是把java类中的各个成分映射成相应的java类。比如:成员变量映射到Field类。
2.反射机制的主要成员类。
Class类:位于java.lang包中,Class类的实例对象是某个类的字节码。
以下类均位于java.lang.reflect包中:
Field 类:代表类的成员变量。该类提供一系列方法操作成员变量 Method类:代表类的方法。提供invoke方法,用以调用方法。
Constructor 类:Constructor 提供关于类的单个构造方法的信息以及对它的访问权限
Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
Modifier 类提供了 static 方法和常量,对类和成员访问修饰符进行解码。
3.简单示例。
⑴.获取Class字节码的三种方式:
- // 1.Class类中提供的forName()方法,接受完整类名,获取字节码。
- Class cls1 = Class.forName("java.lang.String");
- // 2.使用类名.class
- Class cls2 = String.class;
- // 3.使用对象.getClass();该方法位于Object类中,返回运行时类。
- Class cls3 = new String("abc").getClass();
复制代码
⑵.获取类中成员,属性和方法:
- // 获取所有成员变量,包括私有、和受保护的。用数组接收并遍历打印。
- Field[] fields = cls1.getDeclaredFields();
- for (Field field : fields)
- {
- System.out.println(field);
- }
- System.out.println("-----------------------以下为方法-------------------------------");
- // 获取所有成员方法,包括私有、和受保护的。用数组接收并遍历打印。
- Method[] methods = cls2.getDeclaredMethods();
- for (Method method : methods)
- {
- System.out.println(method);
- }
复制代码
运行结果如图:
⑶.反射获取构造函数,并构建对象:
- // 获取带一个String类型参数的构造函数,并使用该方法构建对象。
- Constructor constructor = cls3.getDeclaredConstructor(String.class);
- String str1 = (String)constructor.newInstance("hys");
- System.out.println(str1);
- // 如果只需要用到无参构造函数,可以使用以下方法:
- String str2 =(String)Class.forName("java.lang.String").newInstance();
复制代码
⑷.获取所需方法,并调用,以String类中concat()方法为例:
- // 获取String类中的concat()方法。
- Method method1 = String.class.getDeclaredMethod("concat", String.class);
- // 调用concat()方法。
- String newString = (String) method1.invoke(str1, "--->syh");
- // 输出结果为:hys--->syh
- System.out.println(newString);
复制代码
⑸.数组的反射及应用。
具有相同类型和相同维数的数组,字节码相同。如:
- int[] arr1 = new int[3];
- int[] arr2 = new int[4];
- // 输出:true
- System.out.println(arr1.getClass()==arr2.getClass());
复制代码
使用数组:
- Class str = Class.forName("java.lang.String");
- // 创建String类的数组,长度为10.
- Object arr = Array.newInstance(str, 10);
- // 设置第七个对象的值。
- Array.set(arr, 7, "Hello,itcast!");
- String str7 = (String)Array.get(arr, 7);
- // 输出结果为:Hello,itcast!
- System.out.println(str7);
复制代码
五 java注解回顾
注解,顾名思义,注解,就是对某一事物进行添加注释说明,会存放一些信息,这些信息可能对以后某个时段来说是很有用处的。 Java注解又叫java标注,java提供了一套机制,使得我们可以对方法、类、参数、包、域以及变量等添加标准(即附上某些信息)。且在以后某个时段通过反射将标注的信息提取出来以供使用。二、自定义Java标注
1 为什么要自定义注解
Java从1.5版本以后默认内置三个标注:
- @Override:只能用在方法之上的,用来告诉别人这一个方法是改写父类的。
- @Deprecated:建议别人不要使用旧的API的时候用的,编译的时候会用产生警告信息,可以设定在程序里的所有的元素上.
- @SuppressWarnings:这一个类型可以来暂时把一些警告信息消息关闭.
但是,仅仅这三个标注是不能满足我们开发时一些需求的。所以java允许我们自定义注解来使用。
2 如何自定义注解
自定义步骤大致分为两步:
- 通过@interface关键字(注意,不是interface,是@interace)声明注解名称,以及注解的成员属性或者叫做注解的参数。
- 使用java内置的四个元注解对这个自定义标注的功能和范围进行一些限制
问题来了,什么是元注解?
3 什么是元注解
元注解,就是定义注解的注解,也就是说这些元注解是的作用就是专门用来约束其它注解的注解。
请区别上面那三个注解,他们也是通过元注解定义而来的。
元注解有哪些呢,主要有四个@Target,@Retention,@Documented,@Inherited?
1 元注解有:@Target,@Retention,@Documented,@Inherited
2
3 @Target 表示该注解用于什么地方,可能的 ElemenetType 参数包括:
4 ElemenetType.CONSTRUCTOR 构造器声明
5 ElemenetType.FIELD 域声明(包括 enum 实例)
6 ElemenetType.LOCAL_VARIABLE 局部变量声明
7 ElemenetType.METHOD 方法声明
8 ElemenetType.PACKAGE 包声明
9 ElemenetType.PARAMETER 参数声明
10 ElemenetType.TYPE 类,接口(包括注解类型)或enum声明
11
12 @Retention 表示在什么级别保存该注解信息。可选的 RetentionPolicy 参数包括:
13 RetentionPolicy.SOURCE 注解将被编译器丢弃
14 RetentionPolicy.CLASS 注解在class文件中可用,但会被VM丢弃
15 RetentionPolicy.RUNTIME VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。
16
17 @Documented 将此注解包含在 javadoc 中
18
19 @Inherited 允许子类继承父类中的注解
4 自定义及使用注解示例
自定义一个类级别的标注Description
1 package lighter.javaeye.com;
2 import java.lang.annotation.Documented;
3 import java.lang.annotation.ElementType;
4 import java.lang.annotation.Retention;
5 import java.lang.annotation.RetentionPolicy;
6 import java.lang.annotation.Target;
7
8 @Target(ElementType.TYPE)//这个标注应用于类
9 @Retention(RetentionPolicy.RUNTIME)//标注会一直保留到运行时
10 @Documented//将此注解包含在javadoc中
11 public @interface Description {
12 String value();
13 }
再定义个方法级别的注解Name
1 package lighter.javaeye.com;
2 import java.lang.annotation.Documented;
3 import java.lang.annotation.ElementType;
4 import java.lang.annotation.Retention;
5 import java.lang.annotation.RetentionPolicy;
6 import java.lang.annotation.Target;
7
8 //注意这里的@Target与@Description里的不同,参数成员也不同
9 @Target(ElementType.METHOD)
10 @Retention(RetentionPolicy.RUNTIME)
11 @Documented
12 public @interface Name {
13 String originate();
14 String community();
15 }
然后使用以上两个注解
1 package lighter.javaeye.com;
2
3 @Description(value="javaeye,做最棒的软件开发交流社区")
4 public class JavaEyer {
5 @Name(originate="创始人:robbin",community="javaEye")
6 public String getName()
7 {
8 return null;
9 }
10
11 @Name(originate="创始人:江南白衣",community="springside")
12 public String getName2()
13 {
14 return "借用两位的id一用,写这一个例子,请见谅!";
15 }
16 }
17
18 说明:其中标注“@Description(value="javaeye,做最棒的软件开发交流社区")”,可以写成“@Description("javaeye,做最棒的软件开发交流社区") ”,结果也是一样的。
因为Description标注定义的时候其参数(或者说属性)为value。
而value比较特殊,它在被指定参数的时候可以不用显示的写出来。
当然如果定义的时候参数名不是value而是其它的比如des,那么使用注解的时候,必须显示写出参数名,然后再赋值:@Description(Des=”xxx”)*/
提取出注解的信息
1 package lighter.javaeye.com;
2
3 import java.lang.reflect.Method;
4 import java.util.HashSet;
5 import java.util.Set;
6
7 public class TestAnnotation {
8 /**
9 * author lighter
10 * 说明:具体关天Annotation的API的用法请参见javaDoc文档
11 */
12 public static void main(String[] args) throws Exception {
13 String CLASS_NAME = "lighter.javaeye.com.JavaEyer";
14 Class test = Class.forName(CLASS_NAME);
15 Method[] method = test.getMethods();
16 boolean flag = test.isAnnotationPresent(Description.class);
17 if(flag)
18 {
19 Description des = (Description)test.getAnnotation(Description.class);
20 System.out.println("描述:"+des.value());
21 System.out.println("-----------------");
22 }
23
24 //把JavaEyer这一类有利用到@Name的全部方法保存到Set中去
25 Set<Method> set = new HashSet<Method>();
26 for(int i=0;i<method.length;i++)
27 {
28 boolean otherFlag = method[i].isAnnotationPresent(Name.class);
29 if(otherFlag) set.add(method[i]);
30 }
31 for(Method m: set)
32 {
33 Name name = m.getAnnotation(Name.class);
34 System.out.println(name.originate());
35 System.out.println("创建的社区:"+name.community());
36 }
37 }
38 }
注意事项:
所有的
Annotation
会自动继承
java.lang.annotation
这一个接口
,
所以不能再去继承别的类或是接口
.
最重要的一点
,Annotation
类型里面的参数该怎么设定
:
第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型.
第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String,Enum,Class,annotations等数据类型,
以及这一些类型的数组.例如,String value();这里的参数成员就为String.
六 java字符集和编码
1.为什么要编码
计算机本质上只能处理数值,每个字符在计算机中只能用一个整数来表示,这种整数称为字符编码。
2.编码格式
ASCII码
总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。
ISO-8859-1
ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,其中 ISO-8859-1 涵盖了大多数西欧语言字符,
所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符。
GB2312
它的全称是《信息交换用汉字编码字符集 基本集》,只收录简体汉字,共有7445个字符。
机内码使得汉字编码的空间为0xA1A1到0xF7FE,每个汉字的编码为两个字节,每个字节的编码在0xA1到0xFE之间
ASCII的编码是一个字符一个字节,每个字节的编码在0x00到0x7F之间,这样就使得英文和汉字的编码空间不再重叠.
注:区号加位号可以作为一种编码,这种编码称为区位码.区位码不能直接在机器中使用,因为它的编码空间与ASCII重叠,
为了解决这个问题GB2312的字符在机器中实际采用的是机内码,通过一个简单的对应关系来实现两者之间的转换。
GBK
全称叫《汉字内码扩展规范》,GBK包含了GB2312的全部字符,同时还包括了Big5中的全部字符以及日文、韩文中使用的汉字。
对于原GB2312中的字符,GBK的编码就是原来的机内码。
GBK的编码空间从0x8140到0xFEFE,每个汉字的编码仍为两字节,其中第一个字节8位二进制数的最高位始终是1,
这使得GBK的编码空间可以避开ASCII的编码空间。
注:<问题>:虽然GB2312和GBK都有效的避开了和ASCII编码空间的重叠,但是并没有和其他一些编码(比如Big5)避开重叠,
这样的话有可能我的一段文字到别人那边就变成另外一段文字,并不是很明显的出现乱码。
解决这种问题的最好办法是统一编码,不管简体字还是繁体字,还有日文、韩文中使用的汉字,全部统一编码,
使得每个编码对应的汉字是唯一的,并且与世界上所有其它语言的字符编码不重叠,这就是Unicode。
Unicode编码:
编码方法是首先将所有的字符排列在若干个平面上,每个平面划分为256行,每行256列,从而每个平面存放64K(1K=1024)个字符。
每个字符所在的平面号、行号和列号称为码位(code point),码位可作为字符的编码。
平面0是最重要的平面,称为BMP(Basic Multilingual Plane),其中基本上包括了当今世界上各种语言使用的字符,
当然也包含中、日、韩三种文字中使用的所有汉字。
理论上ISO/IEC 10646预留了64K个平面,这样平面号的编码范围将为0x0000~0xFFFF,加上平面内的行号和列号,
字符码位的范围将是0x00000000~0xFFFFFFFF,也就是说,必须用4个字节来存放一个字符的码位。 Unicode仅使用编号0x00到0x10的17个平面,
其码位范围为0x000000~0x10FFFF,也需要用3个字节来存放。 但是我们实际使用的字符绝大多数都在BMP中,
只是偶尔使用其它平面的字符,因此可以设计一种两字节的编码,使得BMP中的字符编码就是它们在BMP中的码位,
而其它平面的字符用一种代替的方法来表示,这样的编码就是UTF-16。
UTF-16
UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,
不论什么字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。
UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,
这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。
UTF-8
UTF-8采取不定长的编码方法:
ASCII字符的编码只用一个字节,并且其编码就是原来的ASCII码, 其它字符的编码从2个字节到4个字节不等。
每个字符编码的第一字节指出该字符编码的字节数:其中第一个二进制数0的左边有多少个1,该字符编码就有多少个字节;
如果第一位就是0,则该字符编码只有一个字节。
例如,某个字符编码第一字节是0xxxxxxx(x表示任意的二进制数),则该字符的UTF-8编码只有一个字节。
若某个字符编码第一字节为110xxxxx,则该字符的编码将有两个字节。对于两个字节及其以上的UFT-8编码,
从第二字节开始,每个字节开头两个二进制数都是10。
java中常用到编码的地方
用一句话描述一下编码:任何我们输入的字符当需要被计算机保存或者用于传输时都需要进行编码。
问题在于需不需要你去显示的编码<不知道准不准确>
1.内存中操作的编码
情况1:Java 中用 String 表示字符串,所以 String 类就提供转换到字节的方法,也支持将字节转换为字符串的构造函数。
2 | byte [] b = s.getBytes( "UTF-8" ); |
3 | String n = new String(b, "UTF-8" ); |
情况2:Charset 提供 encode 与 decode 分别对应 char[] 到 byte[] 的编码和 byte[] 到 char[] 的解码
1 | Charset charset = Charset.forName( "UTF-8" ); |
2 | ByteBuffer byteBuffer = charset.encode(string); |
3 | CharBuffer charBuffer = charset.decode(byteBuffer); |
2.I/O 操作中存在的编码:I/O 包括磁盘 I/O 和网络 I/O (在转换流上注意编码的设置)
Reader 类是 Java 的 I/O 中读字符的父类,而 InputStream 类是读字节的父类,InputStreamReader 类就是关联字节到字符的桥梁, 它负责在 I/O 过程中处理读取字节到字符的转换,而具体字节到字符的解码实现它由 StreamDecoder 去实现,
在 StreamDecoder 解码过程中必须由用户指定 Charset 编码格式。值得注意的是如果你没有指定 Charset,将使用本地环境中的默认字符集
写的情况也是类似,字符的父类是 Writer,字节的父类是 OutputStream,通过 OutputStreamWriter 转换字符到字节。
02 | FileOutputStream outputStream = new FileOutputStream( "c:/stream.txt" ); |
03 | OutputStreamWriter writer = new OutputStreamWriter( |
11 | FileInputStream inputStream = new FileInputStream(file); |
12 | InputStreamReader reader = new InputStreamReader( |
13 | inputStream, charset); |
14 | StringBuffer buffer = new StringBuffer(); |
15 | char [] buf = new char [ 64 ]; |
18 | while ((count = reader.read(buf)) != - 1 ) { |
19 | buffer.append(buffer, 0 , count); |
七 枚举关键知识
枚举类
(1)使用enum关键字定义枚举类。枚举类一样可以有自己的Field、方法,可以实现一个或多个接口,也可以有自己的构造器。
(2)使用eunm定义的枚举类直接继承了java.lang.Enum类,间接继承Object类。
(3)使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派送子类。(并不是所有的枚举类都使用final修饰,如抽象枚举类)
(4)枚举类所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远不能产生实例。
(5)所有枚举类都提供一个values方法,该方法可以方便地遍历所有枚举值。
DK1.5引入了新的类型——枚举。在 Java 中它虽然算个“小”功能,却给我的开发带来了“大”方便。
用法一:常量
在JDK1.5 之前,我们定义常量都是: public static fianl.... 。现在好了,有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法。
- public enum Color {
- RED, GREEN, BLANK, YELLOW
- }
用法二:switch
JDK1.6之前的switch语句只支持int,char,enum类型,使用枚举,能让我们的代码可读性更强。
- enum Signal {
- GREEN, YELLOW, RED
- }
- public class TrafficLight {
- Signal color = Signal.RED;
- public void change() {
- switch (color) {
- case RED:
- color = Signal.GREEN;
- break;
- case YELLOW:
- color = Signal.RED;
- break;
- case GREEN:
- color = Signal.YELLOW;
- break;
- }
- }
- }
用法三:向枚举中添加新方法
如果打算自定义自己的方法,那么必须在enum实例序列的最后添加一个分号。而且 Java 要求必须先定义 enum实例。
- public enum Color {
- RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
-
- private String name;
- private int index;
-
- private Color(String name, int index) {
- this.name = name;
- this.index = index;
- }
-
- public static String getName(int index) {
- for (Color c : Color.values()) {
- if (c.getIndex() == index) {
- return c.name;
- }
- }
- return null;
- }
-
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getIndex() {
- return index;
- }
- public void setIndex(int index) {
- this.index = index;
- }
- }
用法四:覆盖枚举的方法
下面给出一个toString()方法覆盖的例子。
- public enum Color {
- RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
-
- private String name;
- private int index;
-
- private Color(String name, int index) {
- this.name = name;
- this.index = index;
- }
-
- @Override
- public String toString() {
- return this.index+"_"+this.name;
- }
- }
用法五:实现接口
所有的枚举都继承自java.lang.Enum类。由于Java 不支持多继承,所以枚举对象不能再继承其他类。
- public interface Behaviour {
- void print();
- String getInfo();
- }
- public enum Color implements Behaviour{
- RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
-
- private String name;
- private int index;
-
- private Color(String name, int index) {
- this.name = name;
- this.index = index;
- }
-
- @Override
- public String getInfo() {
- return this.name;
- }
-
- @Override
- public void print() {
- System.out.println(this.index+":"+this.name);
- }
- }
用法六:使用接口组织枚举
- public interface Food {
- enum Coffee implements Food{
- BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO
- }
- enum Dessert implements Food{
- FRUIT, CAKE, GELATO
- }
- }
用法七:关于枚举集合的使用
java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的key是enum类型,而value则可以是任意类型。关于这个两个集合的使用就不在这里赘述,可以参考JDK文档。
关于枚举的实现细节和原理请参考:
二 枚举类型的使用介绍和静态导入
1、枚举(Enum):
JDK5.0中加入了枚举类型,使用enum关键字定义,可以按照如下定义:
public enum Weather{
Sunny,
Rainy,
Cloudy,
}
1.1、枚举类型中的两个静态方法:
valueOf(Class<T> enumType, String name)
返回带指定名称的指定枚举类型的枚举常量。
1.2、values方法的使用:
for(Weather weather : Weather.values()){
System.out.println(weather);
}
System.out.println(Weather.valueOf(Weather.class, "Sunny"));
1.2、values方法的使用:
for(Weather weather : Weather.values()){
System.out.println(weather);
}
System.out.println(Weather.valueOf(Weather.class, "Sunny"));
1.3、创建包含私有成员变量的枚举常量:
public enum Weather{
Sunny("晴天"),
Rainy("雨天"),
Cloudy("多云");
private String value;
public String getValue() {
return value;
}
Weather(String value){
this.value = value;
}
}
public static void main(String[] args) {
Weather weather1 = Weather.Sunny;
System.out.println(weather1.getValue());
}
1.4、枚举类型详细说明:
enum关键字的作用类似于class或interface,本质上是在定义一个类别,细节的实现由编译器完成。
自定义的枚举类型实质上继承自java.lang.Enum抽象类。而每一个成员常量其实就是自己定义的枚举类型的一个实例,都被定义为final,所有无法改变他们,另外他们是static,pulibc的,即:
public static final 枚举常量;
在运行期间我们无法再使用该枚举类型创建新的实例,这些实例是在编译期间就完全确定下来了的。
1.5、枚举的比较:
compareTo
public final int compareTo(E o)
比较此枚举与指定对象的顺序。在该对象小于、等于或大于指定对象时,分别返回负整数、零或正整数。 枚举常量只能与相同枚举类型的其他枚举常量进行比较。该方法实现的自然顺序就是声明常量的顺序。
指定者:
接口 Comparable<E extends Enum<E>> 中的 compareTo
参数:
o - 要比较的对象。
返回:
负整数、零或正整数,根据此对象是小于、等于还是大于指定对象。
Weather[] arrays = Weather.values();
for(Weather weather2 : arrays){
System.out.println((Weather.Sunny).compareTo(weather2));
}
1.6、枚举的相关方法:
ordinal
public final int ordinal()
返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。 大多数程序员不会使用此方法。它被设计用于复杂的基于枚举的数据结构,比如 EnumSet 和 EnumMap。
返回:
枚举常量的序数
for(Weather weather2 : arrays){
System.out.println(weather2.ordinal() + ":" + weather2);
}
2、EnumSet:
public abstract class EnumSet<E extends Enum<E>>
extends AbstractSet<E>
implements Cloneable, Serializable
与枚举类型一起使用的专用 Set 实现。枚举 set 中所有键都必须来自单个枚举类型,该枚举类型在创建 set 时显式或隐式地指定。枚举 set 在内部表示为位向量。此表示形式非常紧凑且高效。此类的空间和时间性能应该很好,足以用作传统上基于 int 的“位标志”的替换形式,具有高品质、类型安全的优势。如果其参数也是一个枚举 set,则批量操作(如 containsAll 和 retainAll)也应运行得非常快。
此类可以帮助我们建立枚举值的集合,里面提供了一系列的静态方法,可以指定不同的集合建立方式。
2.1、of方法
public static <E extends Enum<E>> EnumSet<E> of(E first,
E... rest)
创建一个最初包含指定元素的枚举 set。此工厂方法的参数列表使用变量参数功能,该方法可以创建一个最初包含任意个元素的枚举 set,但是这样很可能比不使用变量参数的重载运行得慢。
参数:
first - 此 set 最初要包含的元素
rest - 此 set 最初要包含的其余元素
返回:
最初包含指定元素的枚举 set
抛出:
NullPointerException - 如果任意参数为 null,或 rest 为 null
EnumSet<Weather> enumSet = EnumSet.of(Weather.Sunny, Weather.Rainy);
for(Iterator<Weather> iter = enumSet.iterator(); iter.hasNext();){
System.out.println(iter.next());
}
2.2、noneOf方法
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType)
创建一个具有指定元素类型的空枚举 set。
参数:
elementType - 此枚举 set 的元素类型的 class 对象
抛出:
NullPointerException - 如果 elementType 为 null
EnumSet<Weather> enumSet2 = EnumSet.noneOf(Weather.class);
enumSet2.add(Weather.Sunny);
enumSet2.add(Weather.Rainy);
for(Iterator<Weather> iter = enumSet2.iterator(); iter.hasNext();){
System.out.println(iter.next());
}
3、List保存枚举类型:
List<Weather> list = new ArrayList<Weather>();
list.add(Weather.Sunny);
list.add(Weather.Cloudy);
for(Iterator<Weather> iter = list.iterator(); iter.hasNext();){
System.out.println(iter.next());
}
4、EnumMap类:
public class EnumMap<K extends Enum<K>,V>
extends AbstractMap<K,V>
implements Serializable, Cloneable
与枚举类型键一起使用的专用 Map 实现。枚举映射中所有键都必须来自单个枚举类型,该枚举类型在创建映射时显式或隐式地指定。枚举映射在内部表示为数组。此表示形式非常紧凑且高效。
枚举映射根据其键的自然顺序 来维护(该顺序是声明枚举常量的顺序)。在 collection 视图(keySet()、entrySet() 和 values())所返回的迭代器中反映了这一点。
EnumMap
public EnumMap(Class<K> keyType)
创建一个具有指定键类型的空枚举映射。
参数:
keyType - 此枚举映射的键类型的 class 对象
抛出:
NullPointerException - 如果 keyType 为空
Map<Weather, String> enumMap = new EnumMap<Weather, String>(Weather.class);
enumMap.put(Weather.Sunny, "晴天");
enumMap.put(Weather.Rainy, "雨天");
5、枚举在实际开发中的使用:
public static String getString(Weather weather){
if(weather == Weather.Sunny){
return Weather.Sunny.getValue();
} else if(weather == Weather.Rainy){
return Weather.Rainy.getValue();
} else if(weather == Weather.Cloudy){
return Weather.Cloudy.getValue();
}
return "不符合的天气情况";
}
调用上面的方法:
Weather weather3 = Weather.Sunny;
System.out.println(getString(weather3));
6、静态导入:
如果要使用静态变量或者方法,必须给出该方法所在的类。而使用静态导入可以使被导入类的所有静态变量和静态方法在当前类直接可见,这样就无需给出他们的类名了:
如加入以下导入语句:
import static java.util.EnumSet.noneOf;
就可以直接使用这个静态方法了:
EnumSet<Weather> enumSet2 = noneOf(Weather.class);
enumSet2.add(Weather.Sunny);
enumSet2.add(Weather.Rainy);
1.一个类的实例是有限且固定的,这个类称为枚举类。比如季节类,只有四个对象(春、夏、秋、冬)
2.手动实现一个枚举类
(1)通过private将构造器隐藏起来
(2)把这个类的所有可能实例都使用private static final修饰的类变量来保存。
(3)如果有必要,可以提供一些静态方法。
package cn.it.lsl;
public class Season {
private final String name;
private final String desc;
private Season(String name, String desc){
this.name = name;
this.desc = desc;
}
public static final Season SPRING = new Season("春天","踏青");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season FAIL = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","白雪皑皑");
public String getName(){
return this.name;
}
public String getDesc(){
return this.desc;
}
}
package cn.it.lsl;
public class SeasonTest {
public SeasonTest(Season s){
System.out.println(s.getName() + "," + s.getDesc());
}
public static void main(String[] args) {
new SeasonTest(Season.FAIL);
}
}
Season类是一个不可变类。Season类中包含了4个static final常量的Field,这4个常量Field就代表了该类所能创建的对象。当程序需要调用Season对象时,就可以通过Season.SPRING的方式来取得Season对象。
这里顺便复习一下不可变类
不可变类:创建该类的实例后,该实例的Field是不可改变的。
如果要创建自定义的不可变类,需遵循如下规则:
(1)使用private和final修饰符来修饰该类的Field。
(2)提供带参数的构造函数,用于根据传入参数来初始化类里的Field。
(3)仅为该类的Field提供getter方法,不要为该类的Field提供setter方法。
(4)如果有必要,重写Object类的hashCode和equals方法。
3.枚举类
(1)使用enum关键字定义枚举类。枚举类一样可以有自己的Field、方法,可以实现一个或多个接口,也可以有自己的构造器。
(2)使用eunm定义的枚举类默认继承了java.lang.Enum类,而不是继承Object类。
(3)使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派送子类。(并不是所有的枚举类都使用final修饰,如抽象枚举类)
(4)枚举类所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远不能产生实例。
(5)所有枚举类都提供一个values方法,该方法可以方便地遍历所有枚举值。
package cn.lsl;
public enum SeasonEnum {
SPRING,SUMMER,FALL,WINTER;
}
package cn.lsl;
public class EnumTest {
public void judge(SeasonEnum s){
switch(s){
case SPRING:
System.out.println("春天");
break;
case SUMMER:
System.out.println("夏天");
break;
case FALL:
System.out.println("秋天");
break;
case WINTER:
System.out.println("冬天");
break;
}
}
public static void main(String[] args) {
//列出所有枚举类的实例
for(SeasonEnum s : SeasonEnum.values()){
System.out.println(s);
}
new EnumTest().judge(SeasonEnum.FALL);
}
}
4.枚举类的Field、方法
枚举类可以定义自己的Field和方法。
package cn.lsl;
public enum Gender {
MALE,FEMALE;
public String name;
}
package cn.lsl;
public class GenderTest {
public static void main(String[] args) {
//通过Enum的valueOf方法来获取指定枚举类的枚举值
Gender g = Enum.valueOf(Gender.class, "FEMALE");
g.name = "女";
System.out.println(g + "," + g.name);
}
}
在上面程序中产生Gender对象的方式与普通类不同,而是采用通过Enum的valueOf方法来获取指定枚举类的枚举值,枚举类的实例只能是枚举值,而不是随意通过new来创建的。
其实上面程序中,没有实现java的良好封装,因为name的访问权限是public,这样就会出现随意对name赋值的情况,应该通过方法来控制对name的访问。
改造上面的程序
package cn.lsl;
public enum Gender {
MALE,FEMALE;
public String name;
public void setName(String name){
switch(this){
case MALE:
if(name.equals("男")){
this.name = name;
}else{
System.out.println("参数错误");
return;
}
break;
case FEMALE:
if(name.equals("女")){
this.name = name;
}else{
System.out.println("参数错误");
return;
}
break;
}
}
public String getName(){
return this.name;
}
}
package cn.lsl;
public class GenderTest {
public static void main(String[] args) {
//通过Enum的valueOf方法来获取指定枚举类的枚举值
Gender g = Enum.valueOf(Gender.class, "FEMALE");
g.setName("女");
System.out.println(g + "," + g.getName());
g.setName("男");
System.out.println(g + "," + g.getName());
}
}
定义枚举类,以上的做法还是做的不够好,枚举类通常应该设计不可变类。其Field值不应该允许改变,这样会更安全。
所以枚举类的Field都应该使用private final修饰。
package cn.lsl;
public enum Gender {
MALE("男"),FEMALE("女");
private final String name;
private Gender(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
5.实现接口的枚举类
枚举类可以实现一个或多个接口。
package cn.it.lsl;
public interface GenderDesc {
void info();
}
package cn.it.lsl;
public enum Gender implements GenderDesc{
MALE("男"),FEMALE("女");
private final String name;
private Gender(String name){
this.name = name;
}
public String getName(){
return this.name;
}
@Override
public void info() {
// TODO Auto-generated method stub
System.out.println("枚举类实现接口");
}
}
以上在枚举类里面实现接口的方法时,每个枚举值在调用该方法的时候都会有相同的行为(即方法体相同)。
如果需要每个枚举值在调用该方法时表现出不同的行为方式,应该让每个枚举值分别实现该方法。
package cn.it.lsl;
public enum Gender implements GenderDesc{
MALE("男"){
public void info() {
// TODO Auto-generated method stub
System.out.println("枚举类实现接口,男");
}
},
FEMALE("女"){
public void info() {
// TODO Auto-generated method stub
System.out.println("枚举类实现接口,女");
}
};
private final String name;
private Gender(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
6.包含抽象方法的枚举类
在枚举类中定义抽象方法的时候不能使用abstract关键字将枚举类定义成抽象类(因为系统会自动为它添加abstract关键字),因为枚举类需要显示创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现。
以下一个程序在枚举类中定义了一个抽象方法,这个抽象方法由不同的枚举值提供不同的实现
package cn.it.lsl;
public enum Operation {
PLUS{
@Override
public double eval(double x, double y) {
// TODO Auto-generated method stub
return x + y;
}
},
MINUS{
@Override
public double eval(double x, double y) {
// TODO Auto-generated method stub
return x - y;
}
},
TIMES{
@Override
public double eval(double x, double y) {
// TODO Auto-generated method stub
return x * y;
}
},
DIVIDE{
@Override
public double eval(double x, double y) {
// TODO Auto-generated method stub
return x / y;
}
};
public abstract double eval(double x, double y);
public static void main(String[] args) {
System.out.println(Operation.PLUS.eval(2, 3));
System.out.println(Operation.MINUS.eval(2, 3));
System.out.println(Operation.TIMES.eval(2, 3));
System.out.println(Operation.DIVIDE.eval(2, 3));
}
}
八 泛型学习
1 泛型类
1.1普通泛型
01 | package test.lujianing; |
08 | public void setValue(T obj){ |
12 | System.out.println(obj.getClass().getName()); |
19 | public class TestOne { |
20 | public static void main(String[] args) { |
22 | Test<Integer> t1 = new Test<Integer>(); |
24 | Integer i = t1.getValue(); |
25 | System.out.println(i); |
27 | Test<Double> t2 = new Test<Double>(); |
29 | Double d = t2.getValue(); |
30 | System.out.println(d); |
32 | Test<String> t3 = new Test<String>(); |
33 | t3.setValue( "hello world" ); |
34 | String str =t3.getValue(); |
35 | System.out.println(str); |
输出结果:
1.2 K/V泛型
01 | package test.lujianing; |
02 | import java.util.HashMap; |
06 | * Created by Administrator on 14-3-30. |
09 | private Map<K,V> map= new HashMap<K, V>(); |
10 | public void put(K k, V v) { |
17 | public class TestFour{ |
18 | public static void main(String[] args) { |
19 | TestKV<String,String> t = new TestKV<String, String>(); |
20 | t.put( "name" , "jianing" ); |
21 | System.out.println(t.get( "name" )); |
23 | TestKV<String,Integer> t2 = new TestKV<String, Integer>(); |
25 | System.out.println(t2.get( "age" )); |
输出结果:
2.泛型接口
1 | package test.lujianing; |
6 | public interface TestImpl<T> { |
7 | public void setValue(T t); |
01 | package test.lujianing; |
06 | class One implements TestImpl<Integer>{ |
08 | public void setValue(Integer integer) { |
12 | public Integer getValue() { |
20 | class Two implements TestImpl<String>{ |
22 | public void setValue(String s) { |
26 | public String getValue() { |
34 | public class TestTwo { |
35 | public static void main(String[] args) { |
36 | TestImpl one = new One(); |
38 | System.out.println(one.getValue()); |
40 | TestImpl two = new Two(); |
41 | two.setValue( "hello word" ); |
42 | System.out.println(two.getValue()); |
输出结果:
3.泛型方法
01 | package test.lujianing; |
10 | public <T>T getValue(Object s,Class<T> clazz) { |
11 | System.out.println(clazz.getName()); |
13 | if (clazz.getName().equals( "java.lang.Integer" )){ |
14 | Double d = Double.parseDouble(s.toString()); |
18 | if (clazz.getName().equals( "java.lang.Double" )){ |
19 | t=(T) new Double(s.toString()); |
28 | public class TestThree { |
29 | public static void main(String[] args) { |
30 | TestMethod t = new TestMethod(); |
31 | int i =t.getValue( "30.0011" ,Integer. class ); |
32 | System.out.println(i); |
33 | double d =t.getValue( "40.0022" ,Double. class ); |
34 | System.out.println(d); |
输出结果:
4.限制泛型
在上面的例子中,没有限制class Test<T>类型持有者T的范围,默认限定类型相当于Object。比如我们要限制T为数字接口类型。只需要这么做:class Test<T extends Number>,这样类中的泛型T只能是Number接口的实现类,传入非Number接口编译会出错。
5.通配泛型
01 | package test.lujianing; |
02 | import java.util.HashMap; |
07 | public class TestFive { |
08 | public static void main(String[] args) { |
09 | Map<String,Class<? extends Number>> map = new HashMap<String,Class<? extends Number>>(); |
10 | map.put( "Integer" ,Integer. class ); |
11 | map.put( "Double" ,Double. class ); |
13 | for (Map.Entry<String,Class<? extends Number>> entry : map.entrySet()) { |
14 | System.out.println( "key:" + entry.getKey() + " value:" + entry.getValue()); |
输出结果:
1 | key:Double value: class java.lang.Double |
2 | key:Integer value: class java.lang.Integer |
简单的例子:针对1.1的例子
01 | public static void main(String[] args) { |
03 | Test<Integer> t1 = new Test<Integer>(); |
07 | Test<Double> t2 = new Test<Double>(); |
11 | public static void fun(Test<?> t){ |
12 | System.out.println( "通配泛型" +t.getValue()); |
输出结果:
6.补充
在泛型中,可能会遇到<T><E>等,T和E分别是Type和Element的首字母。E一般用来表示集合类型中的元素的类型,例如List接口的定义,public interface List<E> extends Collection<E>。这只是一种命名习惯,两者没有本质区别。
发现这片博文也很好:推荐一下http://my.oschina.net/caiyuan/blog/14109