一、java基础知识
1.一个文件中只能有一个public的类,因为他的类名要求和文件名相同。 2.classpath变量可以设置其它目录下的类。 例如:类文件所在目录是:F:Javajdk,那么没有设置classpath变量时,在F:下是访问不到类文件的。可以通过以下方式设置classpath变量,就可以访问不同目录的文件(注意classpath变量中等号的左边和右边的目录文件名称中间是不能有空格,例如一个名为“my Java”的文件夹是不可以的): a.当前目录为F:的目录下,设置 set classpath=F:Java jdk,然后执行 F:java test,输出成功。 b.当前目录为F:Javajdk下,给 set classpath=c:myJava 随便设置了一个变量,在执行 F:Javajdk>java test时会失败,如果给它的变量后面加一个分号(set classpath=c:myJava; 或者给分号后面加一个“.”,他表示当前目录),就可以执行成功了。 3.对象的比较: == 表示:比较的是两个对象的在栈内存中的地址是否相等。 equals 表示:不进的是两个对象的值是否相等。 4. super和this不能同时出现在一个构造函数中,当他们在构造函数中独自出现时,必须在第一行出现。 5. final关键字: 6.implements关键字可以使一个类实现接口,接口之间的继承使用extends关键字,当一个类没有实现接口中所有的方法时,该类被声明为抽象类。 7.判断对象是否相同:使用关键字instanceof. 8.java中包package在磁盘中自动创建:javac -d . 文件名.java。其中d表示目录,.表示当前目录。缺省的访问修饰符在不同的包之间是不可以访问的,在同一个包中不同的类之间的private修饰符是不可以访问的。在不同的包中,如果一个包中的类从另一个包中的类继承而来,那么在子类可以访问方法的修饰符有protected,public。例如包是package obj.java; 那么要访问java目录下的文件时,通过包名+类名才可以,如访问Test类,就要使用obj.java.Test才可以。classpath应该指向包名所在的父目录上。在JCreator中如果要运行带包的文件时,从包名开始的目录进行访问。 9.jar 命令。 压缩:jar cvf myJava.jar test test2 :表示将当前目录下的test和test2文件夹的文件压缩的myJava.jar中。当文件找不到时,注意查看jar文件的目录结构。 jar -twf d:javaorg t.jar > 1.txt :表示当jar文件中的内容非常多时,那么就将它重定向到当前命令1.txt文件中;不过用rar来查看也是非常方便的。 10.多线程:使用多线程执行代码块时,一旦代码块中有暂停语句时,那么可能在这一瞬间,有多个线程进入,这会造成线程的不安全性。因此要使用一个synchronized(参数是一个对象)关键字,达到线程同步,但是该对象必须是同一个对象。显然,速度将会降低。也可以将该代码块设置成一个函数,并且在该函数前面加上synchronized关键字,也可以达到线程之间的同步。 11.Collection 、set 和 list 的区别: Collection: 各元素对象之间没有指定的顺序,允许有重复元素和多个null元素对象。 Set: 各元素对象之间没有指定的顺序,不允许有重复元素,最多允许有一个null元素对象。 List: 各元素对象之间没有指定的顺序,允许有重复元素和多个null元素对象。 12.Properties类的应用: 它继承自Hashtable类,增加了将Hashtable对象中的关键字和值保存到文件和从文件中读取关键字和值到Hashtable对象中的方法。用Properties.store方法存储Properties对象中的内容,每个属性的关键字和值都必须是String类型。用到的主要方法有getProperty,setProperty,load和store,其中后面两个方法FileInputStream和FileOutputStream。获取和设置系统属性System.getProperties和System.setProperties. 在Runtime类中可以启动进程。 13.时间和日期 Date,Calendar,DateFormat和SimpleDateFormat类 : Calendar类中的getInstance静态方法返回一个Calendar对象,通过get方法获取时间,set设置时间,add增加多少时间。SimpleDateFormat主要设置日期的格式。 14.Timer和TimeTask类: Timer类的schedule方法有一些重载方法。 其中一个参数就是TimerTask的实例,该类实现了Runnable接口,因此要设计一个TimerTask的子类,并且实现run方法。隔多长时间执行代码和定时执行代码。
1.位运算: int num = 2<<3; //左移n位,相等于该数字乘以2的n次方。结果是16 2.==和equal的区别: == : 专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符。 equals: 方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立的。 s1 = new String("hehe"); s2 = new String("hehe"); System.out.println(s1==s2);//false System.out.println(s1.equals(s2));//true 3.Integer与int的区别 int是java提供的8种原始数据类型之一。Java为每个原始类型提供了封装类,Integer是java为int提供的封装类。int的默认值为0,而Integer的默认值为null,即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况,例如,要想表达出没有参加考试和考试成绩为0的区别,则只能使用Integer。在JSP开发中,Integer的默认为null,所以用el表达式在文本框中显示时,值为空白字符串,而int默认的默认值为0,所以用el表达式在文本框中显示时,结果为0,所以,int不适合作为web层的表单数据的类型。 4.重载和重写: 重写:
1、覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果; 2、覆盖的方法的返回值必须和被覆盖的方法的返回一致; 3、覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类; 4、被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。 重载: 1、在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int,float),但是不能为fun(int,int)); 2、不能通过访问权限、返回类型、抛出的异常进行重载; 3、方法的异常类型和数目不会对重载造成影响; 4、对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果。 5. 接口可以继承接口。抽象类可以实现(implements)接口,抽象类可继承具体类。抽象类中可以有静态的main方法。 6. 封装,继承,多态和抽象: 封装: 封装的目标就是要实现软件部件的“高内聚、低耦合”,封装就是把描述一个对象的属性和行为的代码封装在一个“模块”中,也就是一个类中,属性用变量定义,行为用方法进行定义,方法可以直接访问同一个对象中的属性。 继承: 在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并可以加入若干新的内容,或修改原来的方法使之更适合特殊的需要,这就是继承。 抽象: 抽象就是找出一些事物的相似和共性之处,然后将这些事物归为一个类,这个类只考虑这些事物的相似和共性之处,并且会忽略与当前主题和目标无关的那些方面,将注意力集中在与当前目标有关的方面。 7.内部类 内部类就是在一个类的内部定义的类,内部类中不能定义静态成员,内部类可以直接访问外部类中的成员变量,内部类可以定义在外部类的方法外面,也可以定义在外部类的方法体中。静态内部类不能访问外部类的实例成员。 8. 匿名内部类: 匿名内部类继承其他类或者是实现接口指的是类本身定义的时候继承其他类和实现了接口。 interface Out{ public void save(); } class A { int i; } class Testadf{ public void get(){ new Thread() extends A implements Out{ //不管是继承A还是实现Out接口,都是编译不通过的。 public void run(){ System.out.println("run"); } }; } } 9. try catch finally执行顺序: 也许你的答案是在return之前,但往更细地说,我的答案是在return中间执行。 结论: finally中的代码比return 和break语句后执行。
1.静态导入: import static java.lang.Math.*; //表示将Math类下的所有静态方法全部导入,那么在程序中就可以直接省略Math.max(3,6)中的Math。 2.可变参数: 定义 public static int Add(int x,int ... args) { } 它是用三个点号来定义的,只能出现在列表的最后,点号位于类型和变量名之间,调用可变参数的方法时,编译器为可变参数隐含创建了一个数组。 3.增强for循环:for(变量类型 参数名称 : 集合,但它还要实现Iterable接口) for(int arg : args) { sum+=arg; } 4.枚举:枚举可以看成一个类,它的成员可以看成类的实例对象。 直接输出枚举对象时,已经调用了该枚举的toString方法。 ordinal方法: 返回的是数字,即在枚举中的位置 Direction.valueOf("EAST") :调用枚举的valueOf方法可以将字符串转换成枚举 5.如何获取字节码 类名.class , 实例对象.getClass 和 Class.forName("全名称")三种,但是对于同一类型来说,他们在内存中的字节码形同,比如String。 一共有9个基本类型 6.反射:就是讲java类中的各种成分映射成相应的java类。 7.ArrayList和HastSet的区别: ArrayList:添加的内容可以重复,按一定的顺序排列起来。 HastSet: 当放入一个对象时,它先判断有没有这个对象。如果有,则不放;没有则放。一旦存放到hashSet集合中,就不能修改参与存放字段的值。如果一改,然后删除对象,但是它的hashCode值将会改变,那么在这个区域就找不到刚才的对象,就没有删除,就会造成内存泄露。 hashCode: 它把集合分成若干个区域,每一个存进来的对象算出它的值,然后放到相应的区域。当查找某个值是,先算出hashCode值是属于哪个区域,然后就直接到这个区域中查找就可以了。前期是对象存储到hash算法的集合。例如:存放了3个对象他们的值分别是1,2,1,如果没有实现hashCode方法,尽管第一个和第三个对象相等,但是它们hashCode却不相等,因此,当存放第三个对象时根据他自己的hashCode存放到其它区域,结果就是在不同的区域中存放着两个相等的对象。 8.类加载器:可以加载配置文件 InputStream ips = ArrayListHashSet.class.getClassLoader().getResourceAsStream("Flection/config.properties"); InputStream ips = ArrayListHashSet.class.getResourceAsStream("resource/config.properties"); InputStream ips = ArrayListHashSet.class.getResourceAsStream("config.properties"); 9.javaBean的取值操作: Person per1 = new Person(20,"tom"); String propertyName = "age"; PropertyDescriptor pd = new PropertyDescriptor(propertyName,per1.getClass()); Method methodAge = pd.getReadMethod(); System.out.println(methodAge.invoke(per1)); BeanUtils.setProperty(per,"birthday.time","12321"); //这个工具包支持级联操作,其中birthday它的类型为Date类项。 10.java注解: /** * 注解有三个阶段:java源文件 -- class文件 -- 内存中的字节码 * 默认情况下是保存在class文件中 * 一个注解就是一个类,用这个注解就是创建这个类的实例对象 */ @SuppressWarnings("deprecation") //发出警告,我知道这个方法过时了 @Deprecated //它放在方法上,表示该方法已经过时了 @Retention(RetentionPolicy.RUNTIME) //表示该注解保存到运行时 @Target({ ElementType.METHOD, ElementType.TYPE}) //@Target表示将注解加在什么上,这里的Type包括class,enum和interface等。 @Override //可以检查是否覆盖父类方法 注解的类型及其用法: public @interface ItCastAnnotation { //给注解定义属性 String color() default "blue"; String value(); int[] arrayAttr() default {1,2,3}; //数组类型 //EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.RED; cn.itcast.one.EnumTest.TrafficLamp lamp() default cn.itcast.one.EnumTest.TrafficLamp.RED; //类型为注解,也就是注解的注解 AnnotationAttr annotationAttr() default @AnnotationAttr("annotation"); } @ItCastAnnotation(annotationAttr=@AnnotationAttr("annotationAttr"),color="red",value="abc",arrayAttr={ 3,4,5}) //用法
二、面向对象
1. 面向过程 在一个结构体中定义窗口的大小,位置,颜色,背景等属性,对窗口操作的函数与窗口本身的定义没有任何关系,如HideWindow,MoveWindow,MinimizeWindow,这些函数都需要接受一个代表要被操作的窗口参数 ,是一种谓语与宾语的关系 。 2. 面向对象 定义窗口时,除了要指定在面向过程中规定的那些属性,如大小,位置,颜色,背景等外,还要指定该窗口可能具有的动作 ,如隐藏,移动,最小化等。这些函数被调用时,都是以某个窗口要隐藏,某个窗口要移动的语法格式来使用的 ,这是一种主语与谓语的关系。 3. 类与对象 类是对某一类事物的描述,是抽象的、概念上的定义;对象是实际存在的该类事物的每个个体,因而也称实例(instance)。 4. 类的封装性 如果外面的程序可以随意修改一个类的成员变量,会造成不可预料的程序错误,就象一个人的身高,不能被外部随意修改,只能通过各种摄取营养的方法去修改这个属性。 在定义一个类的成员(包括变量和方法)时,使用private关键字说明这个成员的访问权限,这个成员成了类的私有成员,只能被这个类的其他成员方法调用,而不能被其他的类中的方法所调用。 为了实现良好的封装性,我们通常将类的成员变量声明为private,再通过public的方法来对这个变量进行访问。对一个变量的操作,一般都有读取和赋值操作,我们分别定义两个方法来实现这两种操作,一个是getXxx()(Xxx表示要访问的成员变量的名字),用来读取这个成员变量操作,另外一个是setXxx()用来对这个成员变量赋值。 一个类通常就是一个小的模块,我们应该让模块仅仅公开必须要让外界知道的内容,而隐藏其它一切内容。我们在进行程序的详细设计时,应尽量避免一个模块直接修改或操作另一个模块的数据,模块设计追求强内聚(许多功能尽量在类的内部独立完成,不让外面干预),弱耦合(提供给外部尽量少的方法调用)。用总统指挥一支军队的例子来说明这种效果。 隐藏类的实现细节; 让使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作; 便于修改,增强代码的可维护性; 5.构造函数的定义和作用 构造方法的特征 它具有与类相同的名称; 它不含返回值; 它不能在方法中用return语句返回一个值 注意:在构造方法里不含返回值的概念是不同于“void”的,在定义构造方法时加了“void”,结果这个方法就不再被自动调用了。 构造方法的作用:当一个类的实例对象刚产生时,这个类的构造方法就会被自动调用,我们可以在这个方法中加入要完成初始化工作的代码。这就好像我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的构造方法中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们要“洗澡”了。 6. 构造方法的重载 和一般的方法重载一样,重载的构造方法具有不同个数或不同类型的参数,编译器就可以根据这一点判断出用new 关键字产生对象时,该调用哪个构造方法了。产生对象的格式是:new 类名(参数列表) ; 重载构造方法可以完成不同初始化的操作, 如:p3=new Person(“Tom”,18);语句,会做这样几件事:创建指定类的新实例对象,在堆内存中为实例对象分配内存空间,并调用指定类的构造方法,最后将实例对象的首地址赋值给引用变量p3。 在java每个类里都至少有一个构造方法,如果程序员没有在一个类里定义构造方法,系统会自动为这个类产生一个默认的构造方法,这个默认构造方法没有参数,在其方法体中也没有任何代码,即什么也不做。 由于系统提供的默认构造方法往往不能满足编程者的需求,我们可以自己定义类的构造方法,来满足我们的需要,一旦编程者为该类定义了构造方法,系统就不再提供默认的构造方法了。 声明构造方法,如无特殊需要,应使用public关键字,在我们前面例子中,可以使用private访问修饰符吗? 7. this的用法 一个类中的成员方法可以直接调用同类中的其他成员,其实我们在一个方法内部使用“this.其他成员”的引用方式和直接使用“其他成员”的效果是一样的,那this还有多大的作用呢?在有些情况下,我们还是非得用this关键字不可的 : 让类的成员变量名和对其进行赋值的成员方法的形参变量同名是必要的,这样的代码谁看了都能明白这两个变量是彼此相关的,老手看到函数的定义,就能揣摩出函数中的代码,大大节省了别人和自己日后阅读程序的时间。 假设我们有一个容器类和一个部件类,在容器类的某个方法中要创建部件类的实例对象,而部件类的构造方法要接收一个代表其所在容器的参数。 构造方法是在产生对象时被java系统自动调用的,我们不能在程序中象调用其他方法一样去调用构造方法。但我们可以在一个构造方法里调用其他重载的构造方法,不是用构造方法名,而是用this(参数列表)的形式,根据其中的参数列表,选择相应的构造方法。 8. static 的静态变量 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。 静态方法里只能直接调用同类中其它的静态成员(包括变量和方法),而不能直接访问类中的非静态成员。这是因为,对于非静态的方法和变量,需要先创建类的实例对象后才可使用,而静态方法在使用前不用创建任何对象。 静态方法不能以任何方式引用this和super关键字(super关键字在下一章讲解)。与上面的道理一样,因为静态方法在使用前不用创建任何实例对象,当静态方法被调用时,this所引用的对象根本就没有产生。 main() 方法是静态的,因此JVM在执行main方法时不创建main方法所在的类的实例对象,因而在main()方法中,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在以后的例子中会多次碰到。 一个类中可以使用不包含在任何方法体中的静态代码块(static block ),当类被载入时,静态代码块被执行,且只被执行一次,静态块经常用来进行类属性的初始化。 类中的静态代码块被自动执行,尽管我们产生了类的多个实例对象,但其中的静态代码块只被执行了一次。当一个程序中用到了其他的类,类是在第一次被使用的时候才被装载,而不是在程序启动时就装载程序中所有可能要用到的类。 9. 单态设计模式 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式就想是经典的棋谱,不同的棋局,我们用不同的棋谱,免得我们自己再去思考和摸索。失败为成功之母,但是要以大量的时间和精力为代价,如果有成功经验可借鉴,没有人再愿意去甘冒失败的风险,我们没有理由不去了解和掌握设计模式,这也是Java开发者提高自身素质的一个很好选择。使用设计模式也许会制约你去创新,不过真正有意义的创新只能出自少数天才,即使你就是那个天才,虽不必因循守旧,但也不可能完全不去了解和借鉴前人的成功经验。 所谓类的单态设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造方法的访问权限设置为private,这样,就不能用new 操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。 10. 理解main方法的语法 由于java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数。 11. 内部类 嵌套类并非只能在类里定义,也可以在几个程序块的范围之内定义内部类。例如,在方法中,或甚至在for循环体内部,都可以定义嵌套类 。 在方法中定义的内部类只能访问方法中的final类型的局部变量,用final定义的局部变量相当于是一个常量,它的生命周期超出方法运行的生命周期。 12. 文档注释 档注释以“/**”开始,以“*/”标志结束,相应的信息和批注所对应的位置很重要! 类的说明应在类定义之前,方法的说明应在方法的定义之前。 批注参数来标记一些特殊的属性及其相应的说明 。 @author<作者姓名> @version<版本信息> @param<参数名称><参数说明> @return<返回值说明> 13. 类的继承 通过继承可以简化类的定义 。 Java只支持单继承,不允许多重继承。 可以有多层继承,即一个类可以继承某一个类的子类,如类B继承了类A,类C又可以继承类B,那么类C也间接继承了类A。 子类继承父类所有的成员变量和成员方法,但不继承父类的构造方法。在子类的构造方法中可使用语句super(参数列表) 调用父类的构造方法。 如果子类的构造方法中没有显式地调用父类构造方法,也没有使用this关键字调用重载的其它构造方法,则在产生子类的实例对象时,系统默认调用父类无参数的构造方法。 子类的实例化过程: 分配成员变量的存储空间并进行默认的初始化,就是用new关键字产生对象后,对类中的成员变量按第三章的表3.1中的对应关系对对象中的成员变量进行初始化赋值。 绑定构造方法参数,就是new Person(实际参数列表)中所传递进的参数赋值给构造方法中的形式参数变量。 如有this()调用,则调用相应的重载构造方法(被调用的重载构造方法又从步骤2开始执行这些流程),被调用的重载构造方法的执行流程结束后,回到当前构造方法,当前构造方法直接跳转到步骤6执行显式或隐式追溯调用父类的构造方法(一直到Object类为止,Object是所有Java类的最顶层父类,在本章后面部分有详细讲解),父类的构造方法又从步骤2开始对父类执行这些流程,父类的构造方法的执行流程结束后,回到当前构造方法,当前构造方法继续往下执行。 进行实例变量的显式初始化操作,也就是执行在定义成员变量时就对其进行赋值的语句,如:执行当前构造方法的方法体中的程序代码 14. 覆盖父类方法 覆盖方法必须和被覆盖方法具有相同的方法名称、参数列表和返回值类型。如果在子类中想调用父类中的那个被覆盖的方法,我们可以用super.方法的格式 。 覆盖方法时,不能使用比父类中被覆盖的方法更严格的访问权限 。 15. 抽象类 java中可以定义一些不含方法体的方法,它的方法体的实现交给该类的子类根据自己的情况去实现,这样的方法就是抽象方法,包含抽象方法的类就叫抽象类。 抽象类必须用abstract关键字来修饰;抽象方法也必须用abstract来修饰。 抽象类不能被实例化,也就是不能用new关键字去产生对象。 抽象方法只需声明,而不需实现。 含有抽象方法的类必须被声明为抽象类,抽象类的子类必须覆盖所有的抽象方法后才能被实例化,否则这个子类还是个抽象类。 16. 接口 如果一个抽象类中的所有方法都是抽象的,我们就可以将这个类用另外一种方式来定义,也就是接口定义。接口是抽象方法和常量值的定义的集合,从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。 接口中的成员都是public访问类型的。接口里的变量默认是用public static final标识的 。 我们可以定义一个新的接口用extends关键字去继承一个已有的接口 我们也可以定义一个类用implements关键字去实现一个接口中的所有方法,我们还可以去定义一个抽象类用implements关键字去实现一个接口中定义的部分方法。 一个类可以继承一个父类的同时,实现一个或多个接口,extends关键字必须位于implemnets关键字之前 。 17. 多态性 1).应用程序不必为每一个派生类(子类)编写功能调用,只需要对抽象基类进行处理即可。这一招叫“以不变应万变”,可以大大提高程序的可复用性。 2). 派生类的功能可以被基类的引用变量引用,这叫向后兼容,可以提高程序的可扩充性和可维护性。以前写的程序可以被后来程序调用不足为奇,现在写的程序(如callA方法)能调用以后写的程序(以后编写的一个类A的子类, 如类D)就了不起了。 18. 异常 异常定义了程序中遇到的非致命的错误, 而不是编译时的语法错误,如程序要打开一个不存的文件、网络连接中断、操作数越界、装载一个不存在的类等。 try,catch语句 throws关键字 自定义异常与Throw关键字 如何对多个异常作出处理 我们可以在一个方法中使用throw,try…catch语句来实现程序的跳转 一个方法被覆盖时,覆盖它的方法必须扔出相同的异常或异常的子类。 如果父类扔出多个异常,那么重写(覆盖)方法必须扔出那些异常的一个子集,也就是说不能扔出新的异常。 19. 包 package语句及应用 package语句作为Java源文件的第一条语句。如果没有package语句,则为缺省无名包。 import语句及应用 jdk中常用的包 java.lang----包含一些Java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能。 java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。 java.applet----包含applet运行所需的一些类。 java.net----包含执行与网络相关的操作的类。 java.io----包含能提供多种输入/输出功能的类。 java.util----包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数。 本身也有访问控制,即在定义类的class关键字前加上访问控制符,但类本身只有两种访问控制,即public 和默认,父类不能是private 和 protected,否则子类无法继承。public修饰的类能被所有的类访问,默认修饰(即class关键字前没有访问控制符)的类,只能被同一包中的所有类访问。
三、IO通信
1.RandomAccessFile类: 它不仅提供了众多的文件访问方法,并且还支持“随机访问”方式。它在随机读写等长记录格式的文件时有很大的优势。仅限于操作文件,不能访问其他的IO设备。有两种构造方法:new RandomAccessFile(path,"rw")和new RandomAccessFile(path,"r"),"rw"和"r"分别表示读写方式和只读方式。 2.FileInputStream和FileOutputStream类操作文件: 它们分别调用read方法和write方法向文件读取和写入数据,通过close关闭流。通过String的getBytes方法将字符串转换成字节数组,通过实例化String对象将作为参数传入的字节数组可以转化成字符串。String result = new String(buf,0,len); 3.FileReader和FileWriter类: 操作比FileInputStream和FileOutputStream类简单。 它的write方法里面没有调用flush方法,因此必须要关闭流才能使写入的内容从缓冲区中刷新到硬盘上,然而FileOutputStream类里面调用了flush方法,因此它就是没有关闭流,也能将数据写入到文件中。 4.PipedInputStream和PipedOutputStream类: 用于在应用程序中的创建管道通信。 5.ObjectOutputStream和ObjectInputStream类: 它们所读写的对象必须实现了Serializable接口,对象中的transient和static类型的成员变量不会被读取和写入。 6.InputStreamReader和OutputStreamWriter: 是将字节流转成字符流来读写的两个类。但是,为了避免频繁地在字符与字节间进行转换,最好不要直接使用它们来读写数据,应尽量使用BufferedWriter类包装OutputStreamWriter类,用BufferedReader类包装InputStreamReader。
1.UDP通信: DatagramSocket类有三个构造函数: close方法: 用于关闭DatagramSocket send方法: 用于发送数据,参数是DatagramPacket对象 receive方法: 用于接收数据,参数是DatagramPacket对象 DatagramPacket对象:用于发送和接收数据的集装箱 getInetAddress //获取IP地址 getPort //获取端口号 getData //获取数据 getLength //获取实际数据的长度 InetAddre类:用于表示计算机IP地址的一个类 getByName //返回相应的InetAddre实例对象 getHostAddress //将ip地址转换成正常的字符串格式 2.Tcp通信: 服务器端: ServerSocket类:用于监听端口 accept方法: 通过accept方法返回一个客户端的Socket类,这个Socket类负责和客户端通信。 Socket类: 是通过ServerSocket类返回的客户端对象 InputStream和OutputStream方法: Tcp之间的通信是通过InputStream和OutputStream两个流对象进行信息的传递 客户端:Socket类 PrintWriter类:可以讲数据在不同的应用程序窗口进行传递。 InputStream和OutputStream方法:用来保存数据和发送数据的流对象 3.对象通过TCP进行传递: 首先,该对象需要实现Serializable接口; 其次,要用到Tcp通信的类,比如ServerSocket,Socket等; 最后,需要ObjectOutputStream和ObjectInputStream类对getInputStrea和getOutputStrea方法进行包装,然后通过他们的writeObject和readObject方法写入和读取对象。
一、传统线程: synchronized 关键字可以使线程同步。如果它包含一个代码块,那么跟在括号后面的参数必须保证唯一,不能使可变得。如果它作用在方法上,那么默认它的锁对象是this关键字。如果它作用在静态方法上,它的锁对象是当前类字节码。 synchronized 包含的代码块里面不能同时在run方法里面和synchronized里面出现while(true){},否则就会进入死循环。 线程同步案例: class SwitchThread { private boolean isSub=true; //表示到子线程执行,可以称为控制器,控制那个线程执行。 public synchronized void subThread() { while(!isSub){ //不是子线程,就等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i = 0; i < 10; i++) { System.out.println("sub thread: " + i); } this.isSub=false; this.notify(); //唤醒主线程 } public synchronized void mainThread() { //这里使用while而不使用if的原因是,线程有可能是假唤醒。使用while时,它还要检查一次。 while(isSub){ //是主线程 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i = 0; i < 50; i++) { System.out.println("=========main thread: " + i); } this.isSub=true; //交给子线程处理 this.notify();//唤醒子线程 } } 二、Java5中的线程并发库 Lock&Condition实现线程同步通信,其中Lock中的lock方法类似synchronized关键字。Condition相当于wait和nofify两个方法。 Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。 读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁! 在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。 一个锁内部可以有多个Condition,即有多路等待和通知,可以参看jdk1.5提供的Lock与Condition实现的可阻塞队列的应用案例,从中除了要体味算法,还要体味面向对象的封装。在传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。(如果只用一个Condition,两个放的都在等,一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走。) class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }
四、其他
4.1 HashSet详解:
java中HashSet详解 程序人生 2010-08-25 14:45:19 阅读1013 评论0 字号:大中小 订阅 HashSet 的实现 对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,查看 HashSet 的源代码,可以看到如下代码: Java代码 public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { // 使用 HashMap 的 key 保存 HashSet 中所有元素 private transient HashMap<E,Object> map; // 定义一个虚拟的 Object 对象作为 HashMap 的 value private static final Object PRESENT = new Object(); ... // 初始化 HashSet,底层会初始化一个 HashMap public HashSet() { map = new HashMap<E,Object>(); } // 以指定的 initialCapacity、loadFactor 创建 HashSet // 其实就是以相应的参数创建 HashMap public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<E,Object>(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap<E,Object>(initialCapacity); } HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<E,Object>(initialCapacity , loadFactor); } // 调用 map 的 keySet 来返回所有的 key public Iterator<E> iterator() { return map.keySet().iterator(); } // 调用 HashMap 的 size() 方法返回 Entry 的数量,就得到该 Set 里元素的个数 public int size() { return map.size(); } // 调用 HashMap 的 isEmpty() 判断该 HashSet 是否为空, // 当 HashMap 为空时,对应的 HashSet 也为空 public boolean isEmpty() { return map.isEmpty(); } // 调用 HashMap 的 containsKey 判断是否包含指定 key //HashSet 的所有元素就是通过 HashMap 的 key 来保存的 public boolean contains(Object o) { return map.containsKey(o); } // 将指定元素放入 HashSet 中,也就是将该元素作为 key 放入 HashMap public boolean add(E e) { return map.put(e, PRESENT) == null; } // 调用 HashMap 的 remove 方法删除指定 Entry,也就删除了 HashSet 中对应的元素 public boolean remove(Object o) { return map.remove(o)==PRESENT; } // 调用 Map 的 clear 方法清空所有 Entry,也就清空了 HashSet 中所有元素 public void clear() { map.clear(); } ... } 由上面源程序可以看出,HashSet 的实现其实非常简单,它只是封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。 HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的。 掌握上面理论知识之后,接下来看一个示例程序,测试一下自己是否真正掌握了 HashMap 和 HashSet 集合的功能。 Java代码 class Name { private String first; private String last; public Name(String first, String last) { this.first = first; this.last = last; } public boolean equals(Object o) { if (this == o) { return true; } if (o.getClass() == Name.class) { Name n = (Name)o; return n.first.equals(first) && n.last.equals(last); } return false; } } public class HashSetTest { public static void main(String[] args) { Set<Name> s = new HashSet<Name>(); s.add(new Name("abc", "123")); System.out.println( s.contains(new Name("abc", "123"))); } } 上面程序中向 HashSet 里添加了一个 new Name("abc", "123") 对象之后,立即通过程序判断该 HashSet 是否包含一个 new Name("abc", "123") 对象。粗看上去,很容易以为该程序会输出 true。 实际运行上面程序将看到程序输出 false,这是因为 HashSet 判断两个对象相等的标准除了要求通过 equals() 方法比较返回 true 之外,还要求两个对象的 hashCode() 返回值相等。而上面程序没有重写 Name 类的 hashCode() 方法,两个 Name 对象的 hashCode() 返回值并不相同,因此 HashSet 会把它们当成 2 个对象处理,因此程序返回 false。 由此可见,当我们试图把某个类的对象当成 HashMap 的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的 equals(Object obj) 方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。 如下程序就正确重写了 Name 类的 hashCode() 和 equals() 方法,程序如下: Java代码 class Name { private String first; private String last; public Name(String first, String last) { this.first = first; this.last = last; } // 根据 first 判断两个 Name 是否相等 public boolean equals(Object o) { if (this == o) { return true; } if (o.getClass() == Name.class) { Name n = (Name)o; return n.first.equals(first); } return false; } // 根据 first 计算 Name 对象的 hashCode() 返回值 public int hashCode() { return first.hashCode(); } public String toString() { return "Name[first=" + first + ", last=" + last + "]"; } } public class HashSetTest2 { public static void main(String[] args) { HashSet<Name> set = new HashSet<Name>(); set.add(new Name("abc" , "123")); set.add(new Name("abc" , "456")); System.out.println(set); } } 上面程序中提供了一个 Name 类,该 Name 类重写了 equals() 和 toString() 两个方法,这两个方法都是根据 Name 类的 first 实例变量来判断的,当两个 Name 对象的 first 实例变量相等时,这两个 Name 对象的 hashCode() 返回值也相同,通过 equals() 比较也会返回 true。 程序主方法先将第一个 Name 对象添加到 HashSet 中,该 Name 对象的 first 实例变量值为"abc",接着程序再次试图将一个 first 为"abc"的 Name 对象添加到 HashSet 中,很明显,此时没法将新的 Name 对象添加到该 HashSet 中,因为此处试图添加的 Name 对象的 first 也是" abc",HashSet 会判断此处新增的 Name 对象与原有的 Name 对象相同,因此无法添加进入,程序在①号代码处输出 set 集合时将看到该集合里只包含一个 Name 对象,就是第一个、last 为"123"的 Name 对象。
4.2 代理:
Collection proxy1 = (Collection) Proxy.newProxyInstance( Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() { ArrayList targer = new ArrayList(); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object retVal = method.invoke(targer, args); System.out.println(method.getName() +" running"); return retVal; } });
4.3 泛型:
ArrayList<String> arrList1 = new ArrayList<String>(); ArrayList<Integer> arrList2 = new ArrayList<Integer>(); arrList2.add(2); //在代码运行时,在字节码中取出了声明泛型的类型,因此下面代码返回true System.out.println(arrList1.getClass() == arrList2.getClass()); //true //因为运行时去除了类型,因此可以通过反射绕过编译器将字符串类型放到ArrayList<Integer>中 arrList2.getClass().getMethod("add", Object.class).invoke(arrList2, "abc"); System.out.println(arrList2.get(0)); //2 System.out.println(arrList2.get(1)); //abc /** * 由于泛型类型在编译时去类型化,因此不能获取它的泛型类型。 * 但是可以通过反射获取方法的参数化类型,因此可以定义一个方法来获取泛型类型 */
4.4 集合:
1. ArrayList和Vector的区别: 共同点:这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,并且其中的数据是允许重复的。 不同点: (1) Vector是线程安全的,也就是说是它的方法之间是线程同步的,而ArrayList是线程序不安全的,它的方法之间是线程不同步的。 (2) 数据增长方式:即Vector增长原来的一倍,ArrayList增加原来的0.5倍。 备注: 对于Vector&ArrayList、Hashtable&HashMap,要记住线程安全的问题,记住Vector与Hashtable是旧的,是java一诞生就提供了的,它们是线程安全的,ArrayList与HashMap是java2时才提供的,它们是线程不安全的。 2. HashMap和Hashtable的区别: 共同点: 都实现了Map接口。 不同点: (1).历史原因:Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现 (2).同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的 (3).值:只有HashMap可以让你将空值作为一个表的条目的key或value 3. List 和 Map 区别: List是单列数据的集合,Map是双列数据的集合,key和value两部分组成。List中存储的数据是有顺序,并且允许重复;Map中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的 4. List、Map、Set三个接口区别: (1) List与Set具有相似性,它们都是单列元素的集合,所以,它们有一个功共同的父接口,叫Collection。Set里面不允许有重复的元素。Set取元素时,没法说取第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素。 (2) List表示有先后顺序的集合。 (3) Map与List和Set不同,它是双列的集合,其中有put方法。 (4) HashSet : 首先看hashcode方法是否相等,然后看equals方法是否相等。new 两个Student插入到HashSet中,看HashSet的size,实现hashcode和equals方法后再看size。 同一个对象可以在Vector中加入多次。往集合里面加元素,相当于集合里用一根绳子连接到了目标对象。往HashSet中却加不了多次的。 5. Collection 和 Collections的区别: Collection是集合类的上级接口,继承与他的接口主要有Set 和List. Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
4.5 枚举深入:
// 枚举里面的每一项可以看成是这个枚举的一个实例。定义了一个枚举: public enum TrafficLamp{ RED(5000){ public TrafficLamp nextLamp() { try { Thread.sleep(time); }catch(Exception ex){ ex.printStackTrace();} return GREEN; } }, GREEN(3000){ @Override public TrafficLamp nextLamp() { try { Thread.sleep(time); }catch(Exception ex){ ex.printStackTrace();} return YELLOW; } }, YELLOW(4000){ @Override public TrafficLamp nextLamp() { try { Thread.sleep(time); }catch(Exception ex){ ex.printStackTrace();} return RED; } }; public abstract TrafficLamp nextLamp(); //time变量要在子类中访问,因此将它设置成public public int time=0; private TrafficLamp(int time) { this.time = time; } } // 测试代码:通过下面的代码就可以模拟交通灯的切换 TrafficLamp currentLamp = TrafficLamp.RED; int i = 4; while(i>0) { currentLamp = currentLamp.nextLamp(); System.out.println(currentLamp); i--; } // 枚举还可以实现单例模式