Java基础

一 Java中的值传递和引用传递

            当一个对象被当作参数传递到一个方法后,在此方法内可以改变这个对象的属性,那么

     这里到底是“按值传递”还是“按引用传递”?

     答:是按值传递。Java 语言的参数传递只有“按值传递”。当一个实例对象作为参数被传递到

            方法中时,参数的值就是该对象的引用的一个副本。指向同一个对象,对象的内容可以

            在被调用的方法内改变,但对象的引用(不是引用的副本) 是永远不会改变的。

    2.一些例题

     (1)参数为基本数据类型

public static void main(String[] args) {
    int num1 = 10;
    int num2 = 20;

    swap(num1, num2);

    System.out.println("num1 = " + num1);
    System.out.println("num2 = " + num2);
}

public static void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;

    System.out.println("a = " + a);
    System.out.println("b = " + b);
}

运行结果是:

a = 20
b = 10
num1 = 10
num2 = 20

               解析:在swap方法中,a、b的值进行交换,并不会影响到num1、num2。因为,

                          a、b中的值,只是从num1、num2的复制过来的。 也就是说,a、b相当

                          于num1、num2的副本,副本的内容无论怎么修改,都不会影响到原件本

                          身。

     (2)参数为数组 

public static void main(String[] args) {
    int[] arr = {1,2,3,4,5};

    change(arr);

    System.out.println(arr[0]);
}

//将数组的第一个元素变为0
public static void change(int[] array) {
    int len = array.length;
    array[0] = 0;
}

运行的结果是:

0

                解析:调用change()的时候,形参array接收的是arr地址值的副本。并在

                           change方法中,通过地址值,对数组进行操作。change方法弹栈以后,

                           数组中的值已经改变。main方法中,打印出来的arr[0]也就从原来的1变

                           成了0。

     (3)参数为string

public static void main(String[] args) {
    String str = "AAA";

    change(str);

    System.out.println(str);
}   
public static void change(String s) {
    s = "abc";
}


运行的结果是:

AAA

                解析:String对象做为参数传递时,走的依然是引用传递,只不过String这个类

                           比较特殊。 String对象一旦创建,内容不可更改。每一次内容的更改都

                           是重现创建出来的新对象。当change方法执行完毕时,s所指向的地址

                           值已经改变。而s本来的地址值就是copy过来的副本,所以并不能改变

                           str1的值。

                更细致的说明:

                       String的API中有这么一句话:“their values cannot be changed after they

                       are created”, 意思是:String的值在创建之后不能被更改。

                       API中还有一段: 

                       String str = "abc"; 

                       等效于: 

                       char data[] = {'a', 'b', 'c'}; 

                       String str = new String(data); 

                       也就是说:对String对象str的任何修改 等同于 重新创建一个对象,并将新

                       的地址值赋值给str。

二.java中一些自己搞不明白的东西

    1.Java代码的执行顺序

     (1)Java程序初始化的3个原则

            (a)静态对象(变量) > 非静态对象(变量):静态对象(变量)只是初始化

                     一次,而非静态对象(变量)可以初始化很多次;

            (b)父类 >子类;

                  (i)父类的静态对象(成员变量)和静态代码块;

                  (ii)子类的静态对象(成员变量)和静态代码块;

                  (iii)父类非静态对象(成员变量)和非静态代码块;

                  (iv)父类构造函数;

                  (v)子类的非静态对象(成员变量)和非静态代码块;

                  (vi)按照成员变量的定义顺序进行初始化

            (c)执行顺序的优先级:

                     静态块>main()>构造块>构造方法。

                     这里没说非main的静态方法,是因为它和普通方法一样,只有在调用时候

                     才执行(这部门需要做些例题)。

              关于一些概念的解释:

                     静态块:用static声明;JVM加载类的时候执行,仅会执行一次(静态代码

                                   块按照声明顺序执行);静态代码块的作用完成类的初始化; 

                     构造块:类中直接用{}定义;每一次创建对象的时候执行; 

     (2)关于java代码执行顺序的一些例题: 

        例1:  

package com.niuke;

public class Test {
    public static Test t1 = new Test();

    {
        System.out.println("111");
    }

    static{
        System.out.println("222");
    }

    public static void main(String[] args){
        Test t2 = new Test();
    }
}

 运行结果:


 111


 222


 333

 

        解释:

                 第一步,在调用main方法前先装载Test类,装载Test.class,装载时按顺序做

                               静态成员初始化,即先实例化t1,实例化t1的子过程是“执行构造代码

                               块,打印111(由于静态方法只会在第一次加载类时初始化,无论实

                               例化多少次,静态方法只初始化一次,所以只有方法块blockA会打

                               印)”。

                 第二步,执行静态代码块,打印222

                 第三步,后面实例化 t2,执行构造代码块,打印111

        例2:

class Code{
    {
      System.out.println("Code的构造块");
    }
    
    static{
        System.out.println("Code的静态代码块");
        }
        
    public Code(){
        System.out.println("Code的构造方法");
        }
    }
    
    
public class CodeBlock03{
     {
      System.out.println("CodeBlock03的构造块");    
     }
     
     static{
        System.out.println("CodeBlock03的静态代码块");
        }
        
        public CodeBlock03(){
             System.out.println("CodeBlock03的构造方法");
            }
        
      public static void main(String[] args){
            System.out.println("CodeBlock03的主方法");
            new Code();
            new Code();
            new CodeBlock03();
            new CodeBlock03();
          }
    }

        运行结果:

               CodeBlock03的静态代码块

               CodeBlock03的主方法

               Code的静态代码块

               Code的构造块

               Code的构造方法

               Code的构造块

               Code的构造方法

               CodeBlock03的构造块

               CodeBlock03的构造方法

               CodeBlock03的构造块

               CodeBlock03的构造方法

        解析:

               执行顺序的优先级:静态块>main()>构造块>构造方法

        下面看几个由例2演变出来的例子

        例2.1    

public class CodeBlock01{
       public static void main(String[] args){
           
             {
               int x=3;
               System.out.println("1,普通代码块内的变量x="+x);    
             }
             
             int x=1;
             System.out.println("主方法内的变量x="+x);
             
             {
                int y=7;
                System.out.println("2,普通代码块内的变量y="+y);    
             }
           }
     }

        运行结果:

               1,普通代码块内的变量x=3

               主方法内的变量x=1

               2,普通代码块内的变量y=7

        例2.2

public class CodeBlock02{
    {
      System.out.println("第一代码块");    
    }
    
    public CodeBlock02(){
        System.out.println("构造方法");
        }
        
        {
          System.out.println("第二构造块");
        }
      public static void main(String[] args){
          new CodeBlock02();
          new CodeBlock02();
          new CodeBlock02();
           
    }
} 

        运行结果:

               第一代码块

               第二构造块

               构造方法

               第一代码块

               第二构造块

               构造方法

               第一代码块

               第二构造块

               构造方法

        例3:

class Test2_Extends {
     public static void main(String[] args) {
         Zi z = new Zi();
     }
     /*
     1. jvm调用了main方法,main方法进栈,因为存在继承关系,所以父类要先加载
     2. new Zi();会先将Fu.class和Zi.class分别加载进内存,再创建对象.当Fu.class加载进内存,父类的静态代码块会随着Fu.class一起加载,
     当Zi.class加载进内存,子类的静态代码块会随着Zi.class一起加载
     --> 第一个输出,静态代码块Fu,第二个输出静态代码块Zi
     3,走Zi类的构造方法,因为java中是分层初始化的,先初始化父类,再初始化子类,所以先走的父类构造,
     但是在执行父类构造时,发现父类有构造代码块,构造代码块是优先于构造方法执行的
     -->所以第三个输出构造代码块Fu,第四个输出构造方法Fu
     4,Fu类初始化结束,子类初始化,第五个输出的是构造代码块Zi,构造方法Zi
     */
 }
 class Fu {
     static {
         System.out.println("静态代码块Fu");
     }
     {
         System.out.println("构造代码块Fu");
     }
     public Fu() {
         System.out.println("构造方法Fu");
     }
 }
 class Zi extends Fu {
     static {
         System.out.println("静态代码块Zi");
     }
     {
         System.out.println("构造代码块Zi");
     }
     public Zi() {
         System.out.println("构造方法Zi");
     }
 }

        另外一种说明,如果第一种看不懂代码注释中的解释,可以看下面的解释,代码

        注释中的若看懂就不用看下面的解释

            (a)在初次new一个Child类对象时,发现其有父类,则先加载Parent类,再

                     加载Child类。

            (b)加载Parent类:

                     初始化Parent类的static属性,赋默认值;

                     执行Parent类的static初始化块;

           (c)加载Child类:

                    初始化Child类的static属性,赋默认值;

                    执行Child类的static初始化块;

           (d)创建Parent类对象:

                    初始化Parent类的非static属性,赋默认值;

                    执行Parent类的instance初始化块;

                   执行Parent类的构造方法;

           (e)创建Child类对象:

                    初始化Child类的非static属性,赋默认值;

                    执行Child类的instance初始化块;

                    执行Child类的构造方法;

                    若 后面再创建Child类对象时,就按照顺序执行(4)(5)两步。

     (3)Java对象的创建过程

            (a)使用new关键字,具体过程如上面所讲

            (b)运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类

                     的newInstance()实例方法。

            (c)调用对象的clone()方法

            (d) 运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()

                      方法。

                      序列化:将对象状态转化为可保持或传输的格式的过程,被序列化的对

                                    象必须implments Serializable

                      反序列化:将流转化成对象的过程

                                        当两个进程在进行远程通信时,彼此可以发送各种类型的数

                                        据。无论是何种类型的数据,都会以二进制序列的形式在网

                                        络上传送。发送方需要把这个Java对象转换为字节序列,即

                                        java对象序列,才能在网络上传送,即序列化过程;接收方

                                        则需要把字节序列再恢复为java对象,即反序列化。

    2.static

       static可以修饰:属性,方法,代码段,内部类(静态内部类或嵌套内部类)

    (1)static变量和非static变量的区别

           (a)类变量(static修饰的成员变量)在类加载的时候初始化;非static修饰的

                    成员变量是在对象new出来的时候初始化。

           (b)static变量与类关联,不与具体的对象绑定在一起,该存储空间被类的所

                    有实例共享,在方法区仅加载一次,而非static在创建对象时会加载很多

                    次,每次创建都会拷贝一份。     

           (c)在类外,对象引用static变量是通过类名.变量名调用,对象在引用非static

                    变量时通过对象名.方法名调用;在类内调用static变量时以类名.变量名

                    的方式调用,调用非sttatic变量时用this或直接调用。

    (2)static方法和非static方法

           (a)static方法是加载一次,被所有的对象所共享。而非静态方法是有多少个

                    对象就拷贝多少次,每个对象只能调用自己的拷贝的方法。

          (b)对象调用非静态的方法时,不考虑线程安全性的问题,而调用静态方法

                   时,要考虑安全性的问题。因为静态方法只有一份,所有对象共享。而

                   非静态方法是每个对象有一份。

          (c)static方法可以用对象.方法名来调用,也可以用类名.方法名来调用。而

                   非static方法只能创建对象后时调用。

          (d)同一个类中,静态方法中只能访问类中的静态成员。而非静态方法可以

                   访问非静态的方法。

    (3)Java中没有static局部变量

    (4)static方法能否被重写

             答不能。

             对于属性和静态方法来说,调用取决于声明的类型,而对于其他的取决于运行时的类型。 

    3.final

      (1)final可以修饰:属性,方法,类,局部变量(方法中的变量)。

      (2) final标记的类不能被继承

      (3)final标记的方法不能被子类重写。

      (4) final标记的变量(成员变量或局部变量)即成为常量,只能赋值一次。

                final 标记的成员变量必须在声明的同时赋值,如果在声明的时候没有赋

                值,那么只有一次赋值的机会,而且只能在构造方法中显式赋值,然后

                才能使用;final标记的局部变量可以只声明不赋值,然后再进行一次性

                的赋值。

                基本类型:基本类型的值不能改变。

                引用类型:引用类型的地址不能发生改变,但是堆内存的值是可以改变

                                  的。

    4.static final和final static

     (1)static final和final static没什么区别,一般static写在前面。

     (2)static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访

              问。

     (3)static final也可以修饰方法,表示该方法不能重写,可以在不new对象的

              情况下调用。

    5. equals和"=="

        Object的equals的源码如下

public boolean equals(Object obj) {
    return (this == obj);
}

        由equals的源码可以看出这里定义的equals与==是等效的(Object类中的

        equals没什么区别),不同的原因就在于有些类(像String、Integer等类)

        对equals进行了重写,但是没有对equals进行重写的类(比如我们自己写

        的类)就只能从Object类中继承equals方法,其equals方法与==就也是等

        效的,除非我们在此类中重写equals。

package com.jsonmappertest.jsonmappertest;

public class bb {
  Person p=new Person();
  public static void main(String[] args)
  {
      Person p1=new Person();
      Person p2=new Person();
      Person p3=p1;
      String s1=new String("aa");
      String s2=new String("aa");
      String t1="aa";
      String t2="aa";

      System.out.println("p1.equalse(p2):"+p1.equals(p2));
      System.out.println("p1==p2:"+(p1==p2));
      System.out.println("(s1.equals(s2):"+s1.equals(s2));
      System.out.println("s1==s2:"+(s1==s2));
      System.out.println("t1.equals(t2):"+(t1.equals(t2)));
      System.out.println("t1==t2:"+(t1==t2));
      System.out.println("p3.equals(p1):"+p3.equals(p1));
      System.out.println("p3==p1:"+(p3==p1));

  }
}
执行结果:
p1.equalse(p2):false
p1==p2:false
(s1.equals(s2)true
s1==s2:false
t1.equals(t2):true
t1==t2:true
p3.equals(p1):true
p3==p1:true

    5.Java中的线程安全

       静态变量:线程非安全。静态变量即类变量,位于方法区,为所有对象共享,

                         共享一份内存,一旦静态变量被修改,其他对象均对修改可见,

                         故线程非安全。

       实例变量:单例模式(只有一个对象实例存在)非线程安全,非单例线程安

                         全。实例变量为对象实例私有,在虚拟机的堆中分配,若在系统

                         中只存在一个此对象的实例,在多线程环境下,“犹如”静态变量

                         那样,被某个线程修改后,其他线程对修改均可见,故线程非安

                         全;如果每个线程执行都是在不同的对象中,那对象与对象之间

                         的实例变量的修改将互不影响,故线程安全。

       局部变量:线程安全。每个线程执行时将会把局部变量放在各自栈帧的工

                         作内存中,线程间不共享,故不存在线程安全问题。

       补充一点:成员变量就是实例变量+静态变量

       有状态的对象和无状态的对象:

              有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例

              变量的对象 ,可以保存数据,是非线程安全的。

              无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),

              就是没有实例变量的对象。不能保存数据,是不变类,是线程安全的。

    6.内部类

      Java 中允许一个类的内部定义另一个类,后者成为内部类。

   (1)内部类可体现逻辑上的从属关系,同时对于其他类可以控制内部类对外

            不可见。

   (2)外部类的成员变量的作用域是整个外部类,包括内部类。但外部类不能

           访问内部类的 private 成员

   (3)逻辑上相关的类可以在一起,可以有效的实现信息隐藏。)内部类可以

            直接访问外部类的成员,可以用此实现多继承。

   (4)匿名内部类

public void t1(){
    final int a = 15;
    String a = “t1”;
    new Aclass(){
        public void testA(){
            System.out.println(TOTAL_NUMBER);
            System.out.println(id);
            System.out.println(a);
        }
    }.testA();
    }
    public void t1(){
    Class B extends Aclass{
        public void testA(){
            System.out.println(TOTAL_NUMBER);
            System.out.println(id);
            System.out.println(a);
        }
        new B().testA();
    }
}

    5.Java中的线程安全

       静态变量:线程非安全。静态变量即类变量,位于方法区,为所有对象共享,

                         共享一份内存,一旦静态变量被修改,其他对象均对修改可见,

                         故线程非安全。

       实例变量:单例模式(只有一个对象实例存在)非线程安全,非单例线程安

                         全。实例变量为对象实例私有,在虚拟机的堆中分配,若在系统

                         中只存在一个此对象的实例,在多线程环境下,“犹如”静态变量

                         那样,被某个线程修改后,其他线程对修改均可见,故线程非安

                         全;如果每个线程执行都是在不同的对象中,那对象与对象之间

                         的实例变量的修改将互不影响,故线程安全。

       局部变量:线程安全。每个线程执行时将会把局部变量放在各自栈帧的工

                         作内存中,线程间不共享,故不存在线程安全问题。

       补充一点:成员变量就是实例变量+静态变量

       有状态的对象和无状态的对象:

              有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例

              变量的对象 ,可以保存数据,是非线程安全的。

              无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),

              就是没有实例变量的对象。不能保存数据,是不变类,是线程安全的。

    6.内部类

      Java 中允许一个类的内部定义另一个类,后者成为内部类。

   (1)内部类可体现逻辑上的从属关系,同时对于其他类可以控制内部类对外

            不可见。

   (2)外部类的成员变量的作用域是整个外部类,包括内部类。但外部类不能

           访问内部类的 private 成员

   (3)逻辑上相关的类可以在一起,可以有效的实现信息隐藏。)内部类可以

            直接访问外部类的成员,可以用此实现多继承。

   (4)匿名内部类

public void t1(){
    final int a = 15;
    String a = “t1”;
    new Aclass(){
        public void testA(){
            System.out.println(TOTAL_NUMBER);
            System.out.println(id);
            System.out.println(a);
        }
    }.testA();
    }
    public void t1(){
    Class B extends Aclass{
        public void testA(){
            System.out.println(TOTAL_NUMBER);
            System.out.println(id);
            System.out.println(a);
        }
        new B().testA();
    }
}

             匿名内部类规则:

                   (a)匿名类没有构造方法;

                   (b)匿名类不能定义静态的成员;

                   (c)匿名类不能用4 种权限、static、final、abstract修饰;

                   (d)只可以创建一个匿名类实例

    7.枚举

     (1)枚举的一些简介

              在JDK1.5 之前,我们定义常量都是: public static fianl.... 。现在好了,

              有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了

              比常量更多的方法。就是说以前定义的常量都能写到枚举里面了。

              使用常量的缺陷:

                    (i)类型不安全。若一个方法中要求传入季节这个参数,用常量的

                             话,形参就是int类型,开发者传入任意类型的int类型值就行,

                             但是如果是枚举类型的话,就只能传入枚举类中包含的对象。

                    (ii)没有命名空间。开发者要在命名的时候以SEASON_开头,这

                             样另外一个开发者再看这段代码的时候,才知道这四个常量分

                             别代表季节。

            (a)先看一个简单的枚举类:

package enumcase;

public enum SeasonEnum {
    SPRING,SUMMER,FALL,WINTER;
}

                    (i)enum和class、interface的地位一样

                    (ii)使用enum定义的枚举类默认继承了java.lang.Enum,而不是继

                             承Object类。枚举类可以实现一个或多个接口。

                    (iii)枚举类的所有实例都必须放在第一行展示(就是上例中的

                              SPRING,SUMMER,FALL,WINTER;),不需使用new 关键字,

                              不需显式调用构造器。自动添加public static final修饰。

                    (iv)使用enum定义、非抽象的枚举类默认使用final修饰,不可以被

                              继承。枚举类的构造器只能是私有的。

            (b)枚举类内也可以定义属性和方法,可以是静态的和非静态的。

package enumcase;

public enum SeasonEnum {
    SPRING("春天"),SUMMER("夏天"),FALL("秋天"),WINTER("冬天");
    
    private final String name;
    
    private SeasonEnum(String name)
    {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

                     此处需要传入参数,因为没有显式申明无参构造器,只能调用有参数

                     的构造器。构造器需定义成私有的,这样就不能在别处申明此类的对

                     象了(只能在该枚举类型的内部)。枚举类通常应该设计成不可变

                     类,它的Field不应该被改变,这样会更安全,而且代码更加简洁。所

                     以我们将Field用private final修饰。

            (c)枚举类可以实现一个或多个接口。与普通类一样,实现接口的时候需

                     要实现接口中定义的所有方法,若没有完全实现,那这个枚举类就是

                     抽象的,只是不需显式加上abstract修饰,系统化会默认加上。

package enumcase;

public enum Operation {
    PLUS{

        @Override
        public double eval(double x, double y) {
            return x + y;
        }
        
    },
    MINUS{

        @Override
        public double eval(double x, double y) {
            return x - y;
        }
        
    },
    TIMES{

        @Override
        public double eval(double x, double y) {
            return x * y;
        }
        
    },
    DIVIDE{

        @Override
        public double eval(double x, double y) {
            return x / y;
        }
        
    };
    
    /**
     * 抽象方法,由不同的枚举值提供不同的实现。
     * @param x
     * @param y
     * @return
     */
    public abstract double eval(double x, double y);
    
    public static void main(String[] args) {
        System.out.println(Operation.PLUS.eval(10, 2));
        System.out.println(Operation.MINUS.eval(10, 2));
        System.out.println(Operation.TIMES.eval(10, 2));
        System.out.println(Operation.DIVIDE.eval(10, 2));
    }
}

            (d)switch语句里的表达式可以是枚举值

                     Java5新增了enum关键字,同时扩展了switch。

package enumcase;

public class SeasonTest {
    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) {
        SeasonEnum s = SeasonEnum.SPRING;
        SeasonTest test = new SeasonTest();
        test.judge(s);
    }
}

     (2)总结一下枚举的用法

            (a)常量      

public enum Color {  
  RED, GREEN, BLANK, YELLOW  
} 

            (b)switch

 
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;          
        }      
    }  
}  

            (c)向枚举中添加新方法

 
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;    
    }      
    // get set 方法      
    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;    
    } 
} 

            (d)覆盖枚举的方法,下面给出一个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;   
    } 
}

            (e)实现接口

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

            (f)使用接口组织枚举

 
public interface Food {      
    enum Coffee implements Food{          
        BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO      
    }      
    enum Dessert implements Food{          
        FRUIT, CAKE, GELATO      
    }  
}  

            (g)关于枚举集合的使用

                     java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保

                    证集合中的元素不重复;EnumMap中的 key是enum类型,而value则

                    可以是任意类型。关于这个两个集合的使用就不在这里赘述,可以参

                    考JDK文档。

    8.I/O流

     (1)流的分类

            (a)输入流和输出流

                     我们把从外部设备流向程序的流成为输入流

                     把从程序流向外部设备的流称为输出流。

            (b)字符流和字节流

                      根据数据在Stream里的最小传输单位, 我们也可以把流分为两类:

                      字符流:最小传输单位为1个字符(java里的字符不再用ASCII码表示,

                                    而是用万国码, 所以1个字符

                                    (char) = 2个字节(byte) = 16bit(位)).

                      字节流:最小传输单位为1个字节(byte)。

                      它们最大的区别就是字符流只能读写文本格式的外部设备,而字节

                      流可以读写所有格式的外部设备(例如2进制文件,多媒体文件等).

            (c)节点流和处理流(原始流和包裹流)

                     Java里的stream还可以嵌套,按照流的功能还可以分为节点流和处

                     理流。

                     节点流:也叫原始流, 用于传输基本数据的流。

                     处理流:也叫包裹流, 包裹在节点流之上, 对节点流的数据作进一步

                                   处理的流, 处理流的前提是具有节点流。处理流可以多重嵌

                                   套。

     (2)Java里四个基本流

              InputStream:输入字节流,也就是说它既属于输入流,也属于字节流。

              OutputStream:输出字节流,既属于输出流,也属于字节流。

              Reader: 输入字符流,既属于输入流,又属于字符流。

              Writer: 输出字符流,既属于输出流,又属于字符流。

              这个4个流都是虚拟类, 也就是说它们不能直接被实例化。

            (a)Reader流及其常用方法

                     Reader流属于输入流和字符流,也就是说Reader流的作用是从外部设

                     备传输数据到程序,而且最小单位是1个字符。

                   (i)int read() throws IOException

                           read()方法可以讲是Reader最基础的一个方法, 它的作用是从流中

                           读取1个字符, 并把这个字符存储到1个整形(int)变量中。如果读到

                           了输入流的末尾(最后1个字符) 则返回 -1。

                           我们知道字符类型是char, 为何返回的是1个int类型?

                           原因就是为了接口统一,  实际上read()方法会将接收到的char类型

                           的数据存储在Int类型的较低位2个字节中。

                           注意,因为网络, 设备等外部原因可能会导致读取失败, 这个read()

                           方法throws IOException,使用时要捕捉。

                   (ii)int read(char[] charbuffer) throws IOException

                            是不是觉得上面的方法一次读1个字符是不是很蛋疼,  觉得有点浪

                            费内存的嫌疑啊。所以Reader 流提供了另1个方法,可以1次个读

                            取若干个字符。该方法返回的是参数数组接受到的字符个数,假

                            如读取到外部设备的结尾,则返回-1。

                            例如执行一次下面的语句:

                            len = ReaderA.read(cbuffer);

                            那么程序就会从ReaderA这个流中读取一次若干(len)个字符放入

                            到字符数组cbuffer中,len就是具体读取的字符数据个数。

                            通常来讲,  这个len就是参数字符串的长度. 也就是说一般来讲

                            int read(char[])方法会填满这个字符串。但是也有例外,如:

                            内存紧张

                            读取到最后的部分, 例如外部设备中有89个字符,  参数字符串的

                            长度是20, 那么前面4次, 都是读20个字符, 最后那次就只读9个字

                            符。

                            读到外部设备的结尾, 返回-1。

                            返回值len的意义?

                            int read(char[]) 执行一次新的读取时,并不会擦除参数字符数组的

                            原有数据,而读取的字符数不是确定的(上面解析了3个原因),所以

                            我们需要返回值len来确定,数组内那些字符是有效的, 那些字符是

                            之前读取的,所以,我们需要返回值len来在参数字符串中提取有效

                            数据。

                   (iii) int read(char[] charbuffer, int offset, int length) throws IOException

                              参数charbuffer, 也是用于存放接收到的字符数据;

                              参数offset,表示是从第offset个位置开始存放接收到的数据。也就

                              是说在那一次读取方法中, 该数组中前offset的数据很可能是以前的

                              数据。每次接受的个数不能大于第三个参数length,也就是执行1次

                              read,最多读取length,当然,也不能大于数组参数charbuffer的长

                              度,所以 length设置大于charbuffer的长度是无意义的。

                              返回值:实际接受到的字符个数。

                  (iv) long skip(long n) throws IOException

                             尝试跳过n个字符不读,返回实际跳过的字符数,比较少用啦。

                  (v) void close() throws IOException

                            每次用完流时都调用该方法,用于关闭流(注:应该写到finally语句

                            块里)。

            (b)Writer流及其常用方法

                     Writer流属于输入流和字符流,也就是说Writer流的作用是从程序到外部

                     文本文件,而且最小单位是1个字符。

                   (i) void write(int c) throws IOException

                           将1个字符通过输出流写到输出流(未到达为外部设备)中。值得注意

                           的是,传入的参数字符(char)数据是存放到1个整形(int)变量中。而

                           且是放在整形变量的低位2个字节中(实际上在jdk1.7中提供了以

                           char类型作为参数的重载方法)。

                           未到达为外部设备中意思是:当执行了这个方法,字符数据并没有

                           立即写入到外部设备,而是保存在输出流的缓冲区中(还在内存中)。

import java.io.*;

public class Writer1 {
    public static void f() {
        int i;
        String s = new String("Just a testing for writer stream!\n");
        File fl = new File("/home/gateman/tmp/testwriter1.txt");
        if (fl.exists()) {
            fl.delete();
        }
        try {
            fl.createNewFile();
        } catch (IOException e) {
            System.out.println("File created failed!");
        }
        System.out.println("File created!");
        FileWriter fw = null;
        try {
            fw = new FileWriter(fl);
        } catch (IOException e) {
            System.out.println("Create filewriter failed!");
            if (null != fw) {
                try {
                    fw.close();
                } catch (IOException e1) {
                    System.out.println("Close the filewriter failed!");
                    return;
                }
                System.out.println("Close the filewriter successfully!");
            }
            return;
        }
        for (i = 0; i < s.length(); i++) {
            try {
                fw.write((int) s.charAt(i));
            } catch (IOException e) {
                System.out.println("error occurs in fw.write()!");
            }
        }
        System.out.println("done!");
    }
}

                           可以见到实际场程序能正常执行完最后一行代码,文件也创建成功

                           了,但是文件是空的。因为输出流在缓冲区的数据并未写入到文件

                           那么如何把输出流缓冲区的数据写入到外部设备答案很简单,执行

                           流的关闭close()方法即可。加上close()的代码如下:

import java.io.*;

public class Writer1 {
    public static void f() {
        int i;
        String s = new String("Just a testing for writer stream!\n");
        File fl = new File("/home/gateman/tmp/testwriter1.txt");
        if (fl.exists()) {
            fl.delete();
        }
        try {
            fl.createNewFile();
        } catch (IOException e) {
            System.out.println("File created failed!");
        }
        System.out.println("File created!");
        FileWriter fw = null;
        try {
            fw = new FileWriter(fl);
        } catch (IOException e) {
            System.out.println("Create filewriter failed!");
            if (null != fw) {
                try {
                    fw.close();
                } catch (IOException e1) {
                    System.out.println("Close the filewriter failed!");
                    return;
                }
                System.out.println("Close the filewriter successfully!");
            }
            return;
        }
        for (i = 0; i < s.length(); i++) {
            try {
                fw.write((int) s.charAt(i));
            } catch (IOException e) {
                System.out.println("error occurs in fw.write()!");
            }
        }
        try {
            fw.close();
        } catch (IOException e) {
            System.out.println("Close the filewriter failed!");
            return;
        }
        System.out.println("Close the filewriter successfully!");
        System.out.println("done!");
    }

}

                   (ii)void flush() throws IOException

                            根据上面的例子会知道, write()方法写入的数据未必回立即写入到外

                            部设备,而是有1个缓冲区存储着它们。当close()方法成功时, 才会

                            保证所有缓冲区的内容都写入到了外部设备。但是close()方法一般

                            放在1个事务的最后部分执行,那么中间出现了异常或错误导致程序

                            跳出时,就很可能回丢失缓冲区的数据了。那么有无1个类似与保存

                            的功能,让程序猿在这个期间强制把缓冲区的数据写入到外部设备

                            中?答案就是有的,这个flush()方法的作用就是把当前缓冲区所有的

                            数据写入到外部设备。

                   (iii)void write(char[] cbuffer) throws IOException

                             把1个字符数组的所有数据写入到输出流缓冲区。

                   (iv)void write(String s) throws IOException

                             把整个字符串的内容写入到输出流缓冲区。

                   (v)void write(String s, int offset, int length) throws IOException

                            把字符串的一部分写入输出流缓冲区

            (c)InputStream及其常用方法

                     字节输入流,与Reader最大的区别就是它不但支持文本外部设备,还支

                     持2进制外部设备。这里用的例子是FileInputStream,顾名思义,就是

                     搭向磁盘文件的字节输入流

                   (i)int read() throws IOException

                           读取1个字节(2进制),并以整数形式(10进制)返回,如果读到流的末

                           尾,则返回-1。

                           例子:

import java.io.*;
 
public class InputStream1{
    public static void f(){
       FileInputStream fis = null;
       try{
           fis = new FileInputStream("/home/gateman/tmp/build.xml");
       }catch(FileNotFoundException e){
           System.out.println("file not found!");
           return;
       }
 
 
       int bt; //byte
       try{
            bt = fis.read();
            while(bt > -1){
                System.out.printf("%c",(char)bt);
                bt = fis.read();
            }
       }catch(IOException e){
           System.out.println("IOException!");
           e.printStackTrace();
       }finally{
           if (null != fis){
               System.out.println("============");
               try{
                   fis.close();
               }catch(IOException e){
                   System.out.println("Stream close failed!");
                   return;
               }
               System.out.println("Stream close successfully!");
           }
       }
    }
}

                   (ii) int available() throws IOException

                             返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取

                           (或跳过)的估计剩余字节数。下一次调用可能是同一个线程,也可

                             能是另一个线程。一次读取或跳过此数量个字节不会发生阻塞,但

                             读取或跳过的字节可能小于该数。简答地讲,这个方法返回输入流

                             中还有多少剩余字节数。如果是在连接磁盘文件的输入流中,一般

                             就是返回磁盘文件的(剩余)大小。但是这个方法在网络流中更有意

                            义,例如网络上1个流连接两个程序传输,1个程序往往要等待另1个

                            程序向流发出数据,那么这个方法就可以用于判断流中是否存在数据

                            了!这个方法用在从本地文件读取数据时,一般不会遇到问题,但如

                            果是用于网络操作,就经常会遇到一些麻烦。比如,Socket通讯时,

                            对方明明发来了1000个字节,但是自己的程序调用available()方法却

                            只得到900,或者100,甚至是0,感觉有点莫名其妙,怎么也找不到

                            原因。其实,这是因为网络通讯往往是间断性的,一串字节往往分几

                            批进行发送。本地程序调用available()方法有时得到0,这可能是对方

                            还没有响应,也可能是对方已经响应了,但是数据还没有送达本地。

                            对方发送了1000个字节给你,也许分成3批到达,这你就要调用3次

                            available()方法才能将数据总数全部得到。

                   (iii)int read(byte[] b) throws IOException

                             读取一定数量的字节,并存储在字节数组b中,返回实际读取的字

                             节数,如果读到输出流的末尾,则返回-1

                   (iii) int read(byte[] b, int offset, int length) throws IOException

                              上面的方法的重载,读取一定量的字节,存放在数组b的特定位置

            (d)OutputStream及其常用方法

                      输出字节流

                    (i)void write(int b) throws IOException

                            向输出流缓冲区中写入1个byte的数据, 该字节数据为参数整型b的

                            低8位。

                    (ii)void write(byte[] b) throws IOException

                             将1个字节数组b的内容写入输入流缓冲区

                    (iii)void write(byte[] b, int offset, int length) throws IOException

                             将字节数组b的部分内容写入输入流缓冲区

                    (iv)void flush() throws IOException

                              立即将缓冲区的数据写入到外部设备。

    9.socket

       socket是对TCP/IP协议的封装,它的出现只是使得程序员更方便地使用TCP/IP

       协议栈而已。socket本身并不是协议,它是应用层与TCP/IP协议族通信的中间

       软件抽象层,是一组调用接口(TCP/IP网络的API函数)

    10.泛型

         E - Element (在集合中使用,因为集合中存放的是元素)

         T - Type(Java 类)

         K - Key(键)

         V - Value(值)

         N - Number(数值类型)

         ? -  表示不确定的java类型

三.Java函数

     1.SimpleDateFormat

      (1)setLenient(true/false)

               此方法用来标志是否严格解析日期。其实很简单,就是如果你输入的日期

               不合法,它会不会先进行一定的计算,计算出能有合法的值,就以计算后

               的值为真正的值。比如说当你使用的时候有2012-02-31,2012-14-03这样

               数据去format,如果让setLenient(true),那么它就会自动解析为

               2012-03-02和2013-02-03这样的日期。如果改为setLenient(false)就会让

               这样出现解析异常,因为去掉了计算,而这样的数据又是不合法的,所以

               出现异常也是合理的。

 

posted @ 2019-06-22 17:10  jialanshun  阅读(196)  评论(0编辑  收藏  举报