JAVA从入门到精通(二)进阶篇

一、对象 和 类

对象

概念:万物皆对象,对象是事物存在的实体。

对象包含两个部分:静态部分(不能修改的部分,这个部分被称为"属性")、动态部分(对象具备的方法)

对象可以认为是一类事物中抽象出来的某一个特例,可以通过这个特例来处理这类事物出现的问题

//JAVA语言中使用new操作符调用构造方法创建对象
​
public Object(){  //构造方法
    System.out.println("创建对象");
}

访问对象的属性和方法:使用new操作符创建一个对象后,可以使用“对象.类成员”来获取对象的属性和行为。

对象的引用:类名 对象引用名称---例:Book book;引用只是存放一个对象的内存地址,并非存放一个对象

对象的比较:“==”运算符和equals()方法

对象的销毁:每个对象都有生命周期,当对象的生命周期结束时,分配给该对象的内存地址将会被回收(Java拥有一套完整的垃圾回收机制,垃圾回收器将回收无用的但占用内存的资源)

垃圾:对象引用超过其作用范围,这个对象将被视为垃圾;将对象赋值为null;

垃圾回收机制的局限性:只能回收那些由new操作符创建的对象。

JAVA提供System.gc()方法强制启动垃圾回收器

概念:类是同一类事物的统称,如果将现实世界中的一个事物抽象成对象,类就是 这类对象的统称。

类是封装对象的属性和行为的载体,也就是说具有相同属性和行为的一类实体被称为类。

Java语言中,类中对象的行为是以方法的形式定义的,对象的属性是以成员变量的形式定义的,而类中包括对象的属性和方法。

类和对象的关系

类实质就是封装对象属性和行为的载体,而对象则是类抽象出来的实例。

面向对象的三大特征是什么?

面向对象的程序设计方法具有三个基本特征:封装、继承、多态。

  • 封装:指的是将对象的属性和方法封装起来,其载体就是类,类通常将对象的实现细节隐藏起来,然后通过一些公用方法来暴露该对象的功能;(避免外部操作对内部数据的影响,提高程序的可维护性)

  • 继承:是面向对象实现软件复用的重要手段,当子类继承父类后,子类作为一种特殊的父类,将直接获得父类的属性和方法;

  • 多态:指的是子类对象可以直接赋给父类变量,但运行时依然表现出子类的行为特征,这意味着同一个类型的对象在执行同一个方法时,可能表现出多种行为特征。

类的构造方法

构造方法:在变量赋值,这样实例化一个本类的对象时,相应的成员变量也将被初始化 如果类中没有明确定义构造方法,编译器会自动创建一个不带参数的默认构造方法类中除了成员方法之外的一种特殊类型的方法,构造方法是一个与类同名的方法,对象的创建就是通过构造方法完成的 每当类实例化一个对象时,类都会自动调用构造方法。 特点:构造方法没有返回值、构造方法的名称要出本类的名称相同 在构造方法中可以为成员变量赋值,这样实例化一个本类的对象时,相应的成员变量也将被初始化 如果类中没有明确定义构造方法,编译器会自动创建一个不带参数的默认构造方法

//定义无参构造方法
public Lei(){   //public-构造方法修饰符,Lei--构造方法的名称
    //构造方法体
    this("this调用有参构造方法"); //使用this调用有参构造方法
    //在无参构造方法中可以使用this调用有参构造方法,但只可以在无参构造方法的第一句使用this调用
    System.out.println("无参构造方法");
}
//定义有参构造方法
public Lei(String name){
    System.out.println("有参构造方法");
}

Object类

Object类是所有类的父类,是Java类层中的最高层类
主要方法:clone()、finalize()、equals()、toString()---由于所有的类都是Object类的子类,所以任何类都可以重写Object类中的方法
getClass()方法 返回对象执行时的Class实例,然后使用此实例调用getName()方法可以取得类的名称---用法:getClass().getName()
toString()方法 将一个对象返回为字符串形式,返回一个String实例
equals()方法 在自定义类中使用equals()方法进行比较时,将返回false,因为equals()方法默认实现时使用”==“运算符比较两个对象的引用地址,而不是比较两个对象的内容,需要在自定义类中重写equals()方法

public class ObjectClass { //Object类

    public String toString(){  //重写toString()方法
        return "在"+getClass().getName()+"类中重写toString()方法";
    }
    public static void main(String[] args) {
        ObjectClass o = new ObjectClass();
        System.out.println(o.getClass().getName());
        System.out.println("打印本类对象:"+o); //自动调用了toString方法
    }
}

二、抽象类和接口

抽象类

一般将父类定义为抽象类,需要使用这个父类进行继承和多态处理。Java语言中设置抽象类不可以实例化对象 abstract是定义抽象类的关键字,由这个关键字定义的方法称为抽象方法,抽象方法没有方法体,这个方法本身没有意义,除非被重写。 抽象类被继承后需要实现其中所有的抽象方法,要保证相同的方法名称、参数列表、相同返回值类型创建出非抽象/抽象方法 继承抽象类的所有子类需要将抽象类中的抽象方法进行覆盖。这样又会出现多态问题,程序中有太多冗余的代码,同样这样的父类局限性很大, 也许某个不需要某个方法的子类也不得不重写父类的方法。如果把该方法放到另一个类中,但Java规定类不能同时继承多个类,面临这种问题,接口出现了...

接口

接口是抽象类的延伸,可以看作是纯粹的抽象类,接口中的所有方法都没有方法体。可以将多个子类都需要用到的方法封装到一个接口中, ​ 使用这个方法的类实现这个接口就可以使用此方法

  // 定义接口
  public interface 接口名{
        //接口内的方法,接口内的方法必须被定义为public和abstract形式
   }

接口可以实现多重继承: class 类名 implements 接口1,接口2,接口3,...,

抽象类和接口的区别

  • 含有abstract修饰符的class即为抽象类,abstract 类不能创建实例对象。含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。

  • 接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。

interface drawTest{  //定义接口
     void draw();  //定义方法
}
//定义AbstractClassAndInterface类,继承了四边形类,并实现了drawTest接口
class AbstractClassAndInterface  extends Quadrangle implements drawTest{
    //抽象类和接口

    public void draw(){ //因为该类实现了接口,所以要覆盖draw()方法
        System.out.println("draw方法实现");
    }

    public static void main(String[] args) {
        drawTest d = new AbstractClassAndInterface();
        d.draw(); //调用draw方法
    }

}

三、异常处理

获取异常信息

Exception是try代码块传递给catch代码块的变量类型,e是变量名

  • getMessage()函数:输出错误性质

  • toString()函数:给出异常的类型与性质

  • printStackTrace()函数:指出异常的类型、性质、栈层次及出现在程序中的位置

Java 内置异常类

Java 语言定义了一些异常类在 java.lang 标准包中。

标准运行时异常类的子类是最常见的异常类。由于 java.lang 包是默认加载到所有的 Java 程序的,所以大部分从运行时异常类继承而来的异常都可以直接使用。

Java 根据各个类库也定义了一些其他的异常,下面的表中列出了 Java 的非检查性异常。

异常 描述
ArithmeticException 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。
ArrayIndexOutOfBoundsException 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
ArrayStoreException 试图将错误类型的对象存储到一个对象数组时抛出的异常。
ClassCastException 当试图将对象强制转换为不是实例的子类时,抛出该异常。
IllegalArgumentException 抛出的异常表明向方法传递了一个不合法或不正确的参数。
IllegalMonitorStateException 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。
IllegalStateException 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。
IllegalThreadStateException 线程没有处于请求操作所要求的适当状态时抛出的异常。
IndexOutOfBoundsException 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
NegativeArraySizeException 如果应用程序试图创建大小为负的数组,则抛出该异常。
NullPointerException 当应用程序试图在需要对象的地方使用 null 时,抛出该异常
NumberFormatException 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
SecurityException 由安全管理器抛出的异常,指示存在安全侵犯。
StringIndexOutOfBoundsException 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。
UnsupportedOperationException 当不支持请求的操作时,抛出该异常。

下面的表中列出了 Java 定义在 java.lang 包中的检查性异常类。

异常 描述
ClassNotFoundException 应用程序试图加载类时,找不到相应的类,抛出该异常。
CloneNotSupportedException 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。
IllegalAccessException 拒绝访问一个类的时候,抛出该异常。
InstantiationException 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。
InterruptedException 一个线程被另一个线程中断,抛出该异常。
NoSuchFieldException 请求的变量不存在
NoSuchMethodException 请求的方法不存在

自定义异常

用户只需继承Exception类即可自定义异常

使用自定义异常类的步骤:

  • 创建自定义异常类

  • 在方法中通过throw关键字抛出异常对象

  • 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句块捕获并处理,否则在方法中的声明处通过throws关键字指明要抛出给方法调用者的异常

  • 在出现异常方法的调用者中捕获并处理异常

Java异常类结构体

Java类库的每个包中都定义了异常类,所有这些类都是Throwable类的子类。Throwable类派生出两个子类,Exception和Error类,Error类及其子类用来描述Java运行系统中出现的内部错误以及资源耗尽的错误。

运行时异常:RuntimeException异常是程序运行过程中产生的异常。

异常的使用原则

  • 在当前方法声明中使用try-catch语句捕获异常

  • 一个方法被覆盖时,覆盖它的方法必须抛出相同的异常或异常的子类

  • 如果父类抛出多个异常,则覆盖方法必须抛出那些异常的一个子集,不能抛出新异常

public class ExceptionP {   //异常处理
    public static void main(String[] args) {
        /*捕获异常:try、catch、finally
                  try存放的是可能存在异常的java语句
                  catch程序块在try之后用来激发被捕获的异常
                  finally是异常处理结构的最后执行部分
         */
        try{
            String str ="lili";
            System.out.println(str+"年龄是:");
            int age = Integer.parseInt("20L");
            System.out.println(age);
        }catch (Exception e){
            e.printStackTrace(); //输出异常性质
            /*
            Exception是try代码块传递给catch代码块的变量类型,e是变量名
            获取异常信息:getMessage()函数:输出错误性质
                        toString()函数:给出异常的类型与性质
                        printStackTrace()函数:指出异常的类型、性质、栈层次及出现在程序中的位置

            本行输出:java.lang.NumberFormatException: For input string: "20L"
	                    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	                    at java.lang.Integer.parseInt(Integer.java:580)
	                    at java.lang.Integer.parseInt(Integer.java:615)
	                    at Basic.ExceptionP.main(Basic.ExceptionP.java:13)
             */
        }
        System.out.println("program over");  //正常输出
        //java的异常处理是结构化的,当try代码块中的语句发生异常时,程序就会跳转到catch代码块中执行,执行完catch代码块中的程序代码后,
        //将继续执行 catch代码块后的其他代码,不会因为一个异常影响整个程序的运行

        /*finally语句块
             完整的异常处理语句一定要包含finally语句,无论程序中有无异常发生,并且无论之前的try-catch是否顺利执行完毕,都会执行finally语句
             四种特殊情况,finally块不会被执行:finally语句块发生了异常
                                           在前面的代码中使用了System.exit()退出程序
                                           程序所在的线程死亡
                                           关闭CPU

         */

    }
}
public class SelfDefinedExc extends Exception{  //自定义异常
    //该类继承Exception
    public SelfDefinedExc(String ErrorMesssage){
        super(ErrorMesssage); //父类构造方法
    }

}

四、集合类

集合vs数组

  • 数组长度是固定的,集合的长度是可变的;

  • 数组用来存取基本数据类型,集合用来存取对象的引用

集合类继承关系

Collection接口

List接口和Set接口继承Collection接口,一下方法是通用的

方法 功能
add(E e) 将指定的对象添加到该集合中
remove(Object o) 将指定的对象从该集合中移除
isEmpty() 返回boolean判断当前集合是否为空
iterator() 返回在此Collection的元素上进行迭代的迭代器,用于遍历集合中的对象
size() 返回该集合中的元素个数
public class iteratorP {
    public static void main(String[] args) {
        Collection<String> list = new ArrayList<>();  //实例化集合类对象
        list.add("a");
        list.add("b");
        list.add("c");
        //遍历集合都是通过Iterator迭代器实现的
        Iterator<String> it = list.iterator(); //创建迭代器,Collection接口的iterator方法返回此接口中的迭代器
        while(it.hasNext()){ //判断是否有下一个元素
            String str =(String) it.next();  //Iterator的next返回的是Object
            System.out.println(str);
        }
    }
}

List集合

List集合包括List接口以及List接口的所有实现类。List集合中的元素允许重复,各元素的顺序就是对象插入的顺序。类似数组,用户可以通过使用索引来访问集合中的元素。

List接口:继承了Collection接口,包含Collection所有的方法,此外还包括以下方法

  • get(int index) :获得指定索引位置的元素

  • set(int index,Object obj):将集合中的指定索引位置的对象修改为指定对象

实现类:ArrayList和LinkedList

ArrayList类:实现了可变的数组,允许保存所有元素,包括null,并可以根据索引位置对集合进行快速的随机访问;缺点是向指定的索引位置插入对象或者删除对象的速度较慢。

LinkedList类:采用链表结构保存对象,优点是便于向集合中插入和删除对象,随机访问对象效率较低

实例化:
List<E> list = new ArrayList<>();
List<E> list = new LinkedList<>();
//E是数据类型
public class ListC {  //List集合
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();  //创建集合对象
        list.add("a");
        list.add("b");
        list.add("c");
        int i = (int)(Math.random()*(list.size()-1));//随机获取索引
        System.out.println("随机获取数组中的元素:"+list.get(i));

        list.remove(2);  //将指定位置的元素从集合中移除
        System.out.println("数组中剩余元素:");
        for (int j =0;j<list.size();j++){
            System.out.println(list.get(j));
        }
    }
}

Set集合

Set集合中的对象不按特定的方式排序,只是简单的把对象加入集合中,但Set集合中不能包含重复对象。

继承了Collection接口,包含Collection所有的方法

实现类

HashSet类:实现Set接口,由哈希表(实际上是一个HashMap实例)支持,不保证Set的迭代顺序,特别是它不保证该顺序恒久不变,允许使用null元素。

TreeSet类:不仅实现了Set接口,还实现了java.util.SortedSet接口,TreeSet类实现的Set集合在遍历集合时按照自然顺序递增排序,也可以按照指定比较器递增排序。

TreeSet类增加的方法

方法 功能
first() 返回此Set中当前第一个元素
last() 返回此Set中当前最后一个元素
comparator() 返回对此Set中的元素进行排序的比较器。如果是自然顺序则返回null
headSet(E toElement) 返回一个新的Set集合,新集合包含toElement(不包含)之前的所有对象
subSet(E fromElement,E toElement) 返回一个新的Set集合,包含fromElement(包含)对象到toElement(不包含)对象之间的所有对象
tailSet(E fromElement) 返回一个新的集合,新集合包含对象fromElement(包含)之后的所有对象
public class SetC implements Comparable<Object>{  //创建类实现Comparable接口
    //存入TreeSet类实现的Set集合必须实现Comparable接口,该接口中的compareTo(object o)方法比较此对象与指定对象的顺序
    String name;
    long id;
    public SetC(String name,long id){  //构造方法
        this.id = id;
        this.name = name;
    }

    public void setId(long id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getId(){
        return this.id;
    }
    public String getName(){
        return this.name;
    }

    @Override
    public int compareTo(Object o) {
        SetC set = (SetC) o;
        int result = id>set.id?1:(id==set.id?0:-1);
        return result;
    }

    public static void main(String[] args) {
        SetC s1 = new SetC("李同学",01011);
        SetC s2 = new SetC("陈同学",01021);
        SetC s3 = new SetC("王同学",01051);
        SetC s4 = new SetC("沈同学",01012);
        TreeSet<SetC> tree = new TreeSet<>();
        tree.add(s1);
        tree.add(s2);
        tree.add(s3);
        tree.add(s4);
        Iterator<SetC> it = tree.iterator();//Set集合中所有对象的迭代器
        System.out.println("遍历集合:");
        while(it.hasNext()){
            SetC s = (SetC) it.next();
            System.out.println(s.getId()+" "+s.getName());
        }
        it = tree.headSet(s2).iterator();  //截取排在s2之前的对象
        System.out.println("截取前面部分的集合:");
        while(it.hasNext()){
            SetC s = (SetC) it.next();
            System.out.println(s.getId()+" "+s.getName());
        }
        it = tree.subSet(s2,s3).iterator();  //截取在s2和s3之间的对象
        System.out.println("截取中间的集合:");
        while(it.hasNext()){
            SetC s = (SetC) it.next();
            System.out.println(s.getId()+""+s.getName());
        }
    }
}

Map集合

Map集合提供了key到value的映射,Map不能包含相同的key,每个key只能映射一个value。key还决定了存储对象在映射中的存储位置,但不是由key对象本身决定的,而是通过一种“散列技术”进行处理,产生一个散列码的整数值,散列码通常用作一个偏移量,该偏移量对应分配给映射的内存区域的起始位置,从而确定存储对象在映射中的存储位置。

常用方法:

方法 功能
put(K key,V value) 向集合中添加指定的key与value的映射关系
containsKey(Object key) 如果此映射包含指定key的映射关系,则返回true
containsValue(Object value) 如果此映射将一个或多个key映射到指定值,则返回true
get(Object key) 如果存在指定的key对象,则返回该对象对应的值,否则返回null
keySet() 返回该集合中所有key对象形成的Set集合
values() 返回该集合中所有值对象形成的Collection集合

实现类

HashMap类:基于哈希表的Map接口的实现,此实现提供所有可选的映射操作,并允许使用null值和null键,但必须保证键的唯一性。HashMap通过哈希表对其内部的映射关系进行快速查找。此类不保证映射的顺序,特别是不保证该顺序恒久不变。

TreeMap类:不仅实现了Map接口,还实现了java.util.SortedMap接口,因此集合中的映射关系存在一定的顺序。但在添加、删除和定位映射关系时,TreeMap比HashMap类性能稍差。由于TreeMap类实现的Map集合中的映射关系是根据键对象按照一定的顺序排列的,因此不允许键对象是null。

public class MapC {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>(); //创建Map实例
        map.put("01","李同学");
        map.put("02","位同学");
        map.put("03","张同学");
        Set<String> set = map.keySet(); //构建Map集合中所有key对象的集合
        Iterator<String> it = set.iterator(); //创建集合迭代器
        System.out.println("key集合中的元素:");
        while(it.hasNext()){
            System.out.println(it.next());
        }
        Collection<String> coll = map.values(); //构建Map集合中所有的values值集合
        it = coll.iterator();
        System.out.println("values值集合中的元素:");
        while (it.hasNext()){
            System.out.println(it.next());
        }
    }
}

五、I/O输入输出

java.io包中存放许多负责各种方式的输入/输出的类,其中,输入流类是抽象类InputStream(字节输入流)或抽象类Render(字符输入流)的子类。所有的输出流都是抽象类OutputStream(字节输出流)或抽象类Writer(字符输出流)的子类。

输入流和输出流的类层次图

输入流

InputStream(字节输入流),是所有字节输入流的父类。该类中的所有方法遇到错误都会引发IOException异常

常用方法

  • read()方法--从输入流中读取数据的下一个字节。返回0~255范围内的int字节值。如果因为已经到达流末尾而没有可用的字节则返回-1

  • read(byte[] b)---从输入流中读入一定长度的字节,并以整数的形式返回字节数

  • mark(int readlimit)---在输入流的当前位置放置一个标记,readlimit参数告知此输入流在标记位置失效之前允许读取的字节数

  • reset()方法---将输入指针返回到当前所做的标记处

  • skip(long n)---跳过输入流上的n个字节并返回实际跳过的字节数

  • markSupported()---如果当前流支持mark()/reset()操作就返回true

  • close()--关闭此输入流并释放与该留关联的所有系统资源

并不是所有的InputStream类的子类都支持InputStream中定义的所有方法

Render类是字符输入流的抽象类,所有字符输入流的实现都是它的子类

输出流

OutputStream类是字节输出流的抽象类,此抽象类表示输出字节流的所有类的超类。

OutputStream类中的所有方法均返回void,在遇到错误时触发IOException异常。

常用方法

  • write(int b)---将指定的字节写入此输出流

  • write(byte[] b)--将b个字节从指定的byte数组写入此输出流

  • write(byte[],int off,int len)--将指定byte数组中从偏移量off开始的len个字节写入此输出流

  • flush()--彻底完成输出并清空缓存区

  • close()--关闭输出流

Writer类是字符输出流的抽象类,所有字符输出类的实现都是它的子类

File类

File类是java.io包中唯一代表哦磁盘文件本身的对象。File类定义了一些方法来操作文件,实现创建、删除、重命名文件等操作。

文件的创建和删除

3种构造方法

  • File(String pathname)---通过将给定路径名字符串转换为抽象路径来创建一个新的File实例

New File(String pathname)
​
File file = new File("d:/1.txt") //指定路径名称,包含文件名
  • File(String parent,String child)---根据定义的父路径和子路径字符串(包含文件名)创建一个新的File对象

new File(String parent,String child)
    
//parent:父路径字符串 D:/或D:/doc
    
//child:子路径字符串 letter.txt
  • File(File f,String child)---根据parent抽象路径名和child路径名字符串创建一个新的File实例

    new File(File f,String child)
    //f:父路径对象  D:/doc/
    //child:子路径字符串  letter.txt

获取文件信息

方法描述
public String getName() ---返回由此抽象路径名表示的文件或目录的名称。
public String getParent()---返回此抽象路径名的父路径名的路径名字符串,如果此路径名没有指定父目录,则返回 null
public File getParentFile() ---返回此抽象路径名的父路径名的抽象路径名,如果此路径名没有指定父目录,则返回 null
public String getPath() -----将此抽象路径名转换为一个路径名字符串。
public boolean isAbsolute() ---测试此抽象路径名是否为绝对路径名。
public String getAbsolutePath() ----返回抽象路径名的绝对路径名字符串。
public boolean canRead()--- 测试应用程序是否可以读取此抽象路径名表示的文件。
public boolean canWrite()--- 测试应用程序是否可以修改此抽象路径名表示的文件。
public boolean exists() ----测试此抽象路径名表示的文件或目录是否存在。
public boolean isDirectory()---- 测试此抽象路径名表示的文件是否是一个目录。
public boolean isFile() ----测试此抽象路径名表示的文件是否是一个标准文件。
public long lastModified() ---返回此抽象路径名表示的文件最后一次被修改的时间。
public long length() ---返回由此抽象路径名表示的文件的长度。
public boolean createNewFile() throws IOException --当且仅当不存在具有此抽象路径名指定的名称的文件时,原子地创建由此抽象路径名指定的一个新的空文件。
public boolean delete() ----删除此抽象路径名表示的文件或目录。
public void deleteOnExit() ----在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。
public String[] list() ---返回由此抽象路径名所表示的目录中的文件和目录的名称所组成字符串数组。
public String[] list(FilenameFilter filter) 返---回由包含在目录中的文件和目录的名称所组成的字符串数组,这一目录是通过满足指定过滤器的抽象路径名来表示的。
public File[] listFiles() -返回一个抽象路径名数组,这些路径名表示此抽象路径名所表示目录中的文件。
public File[] listFiles(FileFilter filter) --返回表示此抽象路径名所表示目录中的文件和目录的抽象路径名数组,这些路径名满足特定过滤器。
public boolean mkdir() ---创建此抽象路径名指定的目录。
public boolean mkdirs() ---创建此抽象路径名指定的目录,包括创建必需但不存在的父目录。
public boolean renameTo(File dest) ---重新命名此抽象路径名表示的文件。
public boolean setLastModified(long time) ---设置由此抽象路径名所指定的文件或目录的最后一次修改时间。
public boolean setReadOnly() ---标记此抽象路径名指定的文件或目录,以便只可对其进行读操作。
public static File createTempFile(String prefix, String suffix, File directory) throws IOException--- 在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。
public static File createTempFile(String prefix, String suffix) throws IOException --在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。
public int compareTo(File pathname) ---按字母顺序比较两个抽象路径名。
public int compareTo(Object o) ---按字母顺序比较抽象路径名与给定对象。
public boolean equals(Object obj) ----测试此抽象路径名与给定对象是否相等。
public String toString() ----返回此抽象路径名的路径名字符串。
public boolean isHidden() --- 判断文件是否是隐藏文件

文件输入/输出流

程序运行期间,大部分数据都在内存中进行操作,当程序结束或关闭时,这些数据将消失,如果需要将数据永久保存,可使用文件输入/输出流与指定的文件建立连接,将需要的数据永久保存到文件中。

FileInputStream和FileOutputStream类

FileInputStream和FileOutputStream类都是用来操作磁盘文件,分别是InputStream和OutputStream类的子类,提供文件的读取和写入能力。

构造方法

  • FileInputStream(String name)---使用给定的文件名创建一个FileInputStream对象

  • FileInputStream(File file) --- 使用File对象创建FileInputStream对象

  • FileOutputStream(String name)---使用给定的文件名创建一个FileOutputStream对象

  • FileOutputStream(File file) --- 使用File对象创建FileOutputStream对象

FileReader类和FileWriter类

使用FileInputStream和FileOutputStream类读取和写入内容只能对字节或字节数组,由于汉字在文件中占用两个字节,如果使用字节流,读取不好可能会出现乱码现象。FileReader类和FileWriter类可避免这种现象

FileReader流顺序的读取文件,只要不关闭流,调用read()方法就顺序的读取源中其余内容,直到源的末尾或流关闭。

带缓存的输入/输出流

BufferedInputStream 与 BufferedOutputStream类

构造函数

  • BufferedInputStream(InputStream in)---创建一个带有32字节的缓存流

  • BufferedInputStream(InputStream in,int size)---按指定的大小来创建缓存区

  • BufferedOutputStream(OutputStream in)---创建一个带有32字节的缓存流

  • BufferedOutputStream(OutputStream in,int size)---按指定的大小来创建缓存区

BufferedOutputStream输出信息和用OutputStream输出信息完全相同,但BufferedOutputStream类有一个flush()方法用来将缓存区的数据强制输出完。

注:flush()方法就是用于即使在缓存区没有满的情况下,也将缓存区的内容强制写入到外设,习惯上称这个过程为刷新。flush方法只对缓存区的OutputStream类的子类有效。当调用close()方法时,系统在关闭流之前,也会将缓存区中信息刷新到磁盘文件中。

BufferedReader与 BufferedWriter类

BufferedReader类常用方法

  • read() -- 读取单个字符

  • readline() --- 读取一个文本行,并将其返回字符串。若无数据可读则返回null

BufferedWriter类中的方法否返回void,常用方法

  • write(String s,int off,int len) --- 写入字符串的某一部分

  • flush() --- 刷新该流的缓存

  • newLine() -- 写入一个行分隔符

使用BufferedWriter类的write方法时,数据并没有立即被写入到输出流,而是首先进入缓存区中,如果想立刻将缓存区中的数据写入输出流,一定要调用flush()方法。

数据输入/输出流

DataInputStream类与DataOutputStream类允许应用程序以与机器无关的方法从底层输入流中读取基本Java数据类型。也就是说,当读取一个数据时,不必再关心这个数值应当是哪种字节。

构造方法

  • DataInputStream(InputStream in) --使用指定的基础InputStream创建一个DataInputStream

  • DataOutputStream(OutputStream out) -- 创建一个新的数据输出流,将数据写入指定基础输出流

DataOutputStream类写入字符串的方法

  • writeBytes(String s) -- Java字符是Unicode编码,是双字节的,将字符串中的每一个字符的低字节内容写入目标设备中

  • writeChars(String s) --- 将字符串中的每一个字符的两个字节的内容都写到目标设备中

  • writeUTF(String s) --- 将字符串按照UTF编码后的字节长度写入目标设备,然后才是每一个字节的UTF编码

只有readUTF()方法返回字符串:因为只有writeUTF()方法向目标设备中写入字符串的长度,所以也能准确读回写入字符串

ZIP压缩输入/输出流

ZipEntry、ZipInputStream、ZipOutputStream(java.util.zip) 3个类实现ZIP数据压缩方式

压缩文件

ZipOutputStream类对象,可将文件压缩为.zip文件

构造方法:ZipOutputStream(OutputStream out)

常用方法:

  • putNextEntry(ZipEntry e) --开始写一个新的ZipEntry,并将流内的位置移至此entry所指数据的开头

  • write(byte[] b,int off,int len) -- 将字节数组写入当前ZIP条目数据

  • finish()---完成写入ZIP输出流的内容,无需关闭它所配合的OutputStream

  • setComment(String comment) -- 可设置此ZIP文件的注释文字

package IOStream;
​
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
​
public class MyZip { //ZipOutputStream类对象实现文件夹压缩
​
    private void zip(String zipFileName, File inputFile) throws Exception{
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFileName));
        zip(out,inputFile,"");
        System.out.println("压缩中...");
        out.close();
    }
    private void zip(ZipOutputStream out,File f,String base) throws Exception{  //方法重载
        if(f.isDirectory()){ //判断抽象路径名表示的文件是否是一个目录
            File[] fl = f.listFiles(); //获取路径数组
​
            out.putNextEntry(new ZipEntry(base+"/"));  //写入此目录的entry
            base = base.length()==0?"":base+"/"; //判断参数是否为空
            for(int i=0;i<fl.length;i++){  //循环遍历数组中的文件
                zip(out,fl[i],base+fl[i]);
            }
​
        }else{
            out.putNextEntry(new ZipEntry(base));  //创建新的进入点
            FileInputStream in = new FileInputStream(f);
            int b; //定义int型变量
            System.out.println(base);
            while((b=in.read())!=-1){
                out.write(b);  //如果没有到达流的尾部,将字节写入当前ZIP条目
            }
            in.close();
        }
    }
​
    public static void main(String[] args) {
        MyZip book = new MyZip();
        try{
            //调用方法,参数为压缩后的文件和要压缩的文件
            book.zip("hello.zip",new File("hello"));
            System.out.println("压缩完成");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
​

解压缩ZIP文件

ZipInputStream类可读取ZIP压缩格式的文件,包括已压缩和未压缩的条目(entry)

ZipInputStream构造方法:ZipInputStream(InputStream in)

常用方法:

  • read(byte[] b,int off,int len) -- 读取目标b数组内off偏移量的位置,长度是len字节

  • available() -- 判断是否已读完目前enrty所指定的数据,已读完返回0,否则返回1

  • closeEntry() -- 关闭当前ZIP条目并定位流以读取下一个条目

  • skip(long n) -- 跳过当前ZIP条目中指定的字节数

  • getNextEntry() -- 读取下一个ZipEntry,并将流内的位置移至该entry所指数据的开头

  • createZipEntry(String name) -- 以指定的name参数新建一个ZipEntry对象

注:使用ZipInputStream类来解压文件,必须先使用ZipInputStream类的getNextEntry方法来获取其内的第一个ZipEntry

import java.io.File;
import java.io.FileInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
​
public class InZIP {  //解压文件
    public static void main(String[] args) {
        ZipInputStream zin;
        try{
            //实例化对象,指明要解压的文件
            zin = new ZipInputStream(new FileInputStream("hello.zip"));
            //使用ZipInputStream类来解压文件,必须先使用ZipInputStream类的getNextEntry方法来获取其内的第一个ZipEntry
            ZipEntry entry = zin.getNextEntry(); //获取下一个 ZipEntry
            while(((entry = zin.getNextEntry())!=null)&&!entry.isDirectory()){
                //如果entry不为空并且不在同一目录下
                File file = new File("G:\\Project\\JAVA\\"+entry.getName()); //获取文件目录
                System.out.println(file);
                if(!file.exists()){ //如果文件不存在
                    file.mkdirs();//创建文件所在文件夹
                    file.createNewFile(); //创建文件
                }
                zin.closeEntry();
                System.out.println(entry.getName()+"解压成功");
            }
            zin.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

六、反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

获取class类对象的三种方式

  • class java.lang.String-----对象名.getClass()

  • class java.lang.String-----类名.class

  • class java.lang.String-----Class.forName(...)

Class类的常用方法

  • getName() 一个Class对象描述了一个特定类的属性,Class类中最常用的方法getName以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。

  • newInstance() Class还有一个有用的方法可以为类创建一个实例,这个方法叫做newInstance()。例如:x.getClass.newInstance(),创建了一个同x一样类型的新实例。newInstance()方法调用默认构造器(无参数构造器)初始化新建对象。

  • getClassLoader() 返回该类的类加载器。

  • getComponentType() --- 返回表示数组组件类型的 Class。

  • getSuperclass() --- 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。

  • isArray() --- 判定此 Class 对象是否表示一个数组类

通过反射可访问的主要描述信息

组成部分 访问方法 返回值类型 说明
包路径 getPackage() Package对象 获得该类的存放路径
类名称 getName() String对象 获得该类的名称
继承类 getSuperclass() Class对象 获得该类继承的类
实现接口 getInterfaces() Class型数组 获得该类实现的所有接口
构造方法 getConstructors() Constructor型数组 获得所有权限为public的构造方法
getConstructor(Class<?>...parameterTypes) Constructor对象 获得权限为public的指定构造方法
getDeclaredConstructors() Constructor型数组 获得所有构造方法,按声明顺序返回
getDeclaredConstructor(Class<?>...parameterTypes) Constructor对象 获得指定构造方法
方法 getMethods() Method型数组 获得所有权限为public的方法
getMethod(String name,Class<?>...parameterTypes) Method对象 获得权限为public的指定方法
getDeclaredMethods() Method型数组 获得所有方法,按声明顺序返回
getDeclaredMethods(String name,Class<?>...parameterTypes) Method对象 获得指定方法
成员变量 getFileds() Filed型数组 获得所有权限为public的成员变量
getFiled(String name) Filed对象 获得权限为public的指定成员变量
getDeclaredFileds() Filed型数组 获得所有成员变量,按声明顺序返回
getDeclaredFiled(String name) Filed对象 获得指定成员变量
内部类 getClasses() Class型数组 获得所有权限为public的内部类
getDeclaredClasses() Class型数组 获得所有内部类
内部类的声明类 getDeclaringClass() Class对象 如果该类为内部类,则返回它的成员类,否则返回null

注:在通过方法getFileds()和getMethods()依次获得权限为public的成员变量和方法时,将包含从超类中继承到的成员变量和方法;而通过方法getDeclaredFileds()和getDeclaredMethods()只是获得在本类中定义的所有成员变量和方法。

访问构造函数

  • getConstructors()

  • getConstructor(Class<?>...parameterTypes)

  • getDeclaredConstructors()

  • getDeclaredConstructor(Class<?>...parameterTypes)

如果是访问指定的构造函数,需要根据该构造函数的入口参数的类型来访问。例如,访问一个入口参数类型依次为String和int型的构造函数:

objectClass.getDeclaredConstructor(String.class,int.class);
​
objectClass.getDeclaredConstructor(new Class[]{String.classs,int.class});

Constructor类的常用方法

  • isVarArgs() --- 查看该构造函数是否允许带有可变数量的参数,如果允许则返回true,否则返回false

  • getParameterTypes() -- 按照声明顺序以Class数组的形式获得该构造方法的各个参数的类型

  • getExceptionTypes() -- 以Class数组的形式获得该构造方法可能抛出的异常类型

  • newInstance(Object...initargs) -- 通过该构造方法利用指定参数创建一个该类的对象,如果未设置参数则表示采用默认无参数的构造方法

  • setAccessible(boolean flag) -- 如果该构造方法的权限为private,默认不允许通过反射利用newInstance(Object...initargs)方法创建对象。如果先执行该方法,并将入口参数设为true,则允许创建。

  • getModifiers() -- 获得可以解析出该构造方法所采用修饰符的整数

通过java.lang.reflect.Modifier类可以解析出getModifiers()方法的返回值所表示的修饰符信息

Modifier类的常用解析方法

  • isPublic(int mod) --- 查看是否被public修饰符修饰,返回true/false

  • isProtected(int mod) -- 查看是否被protected修饰符修饰,返回true/false

  • isPrivate(int mod) --- 查看是否被private修饰符修饰,返回true/false

  • isStatic(int mod) -- 查看是否被static修饰符修饰,返回true/false

  • isFinal(int mod) -- 查看是否被final修饰符修饰,返回true/false

  • toString(int mod) -- 以字符串的形式返回该修饰符

访问成员变量

  • getFileds()

  • getFiled(String name)

  • getDeclaredFileds()

  • getDeclaredFiled(String name)

返回Field类型的对象或者数组

Filed类常用方法

  • getName() --- 获得该成员变量的名称

  • getType() --- 获得表示该成员变量类型的Class对象

  • get(Object obj) --- 获得指定对象obj中成员变量的值,返回值为Object型

  • set(Object obj,Object value) -- 将指定对象obj中成员变量的值设置为value

  • getInt(Object obj) -- 获得指定对象obj中类型为int的成员变量的值

  • setInt(Object obj,int i) -- 将指定对象obj中类型为int的成员变量的值设为i

  • getFloat(Object obj) -- 获得指定对象obj中类型为float的成员变量的值

  • setFloat(Object obj,float f) -- 将指定对象obj中类型为float的成员变量的值设为f

  • getBoolean(Object obj) -- 获得指定对象obj中类型为boolean的成员变量的值

  • setBoolean(Object obj,boolean b) -- 将指定对象obj中类型为boolean的成员变量的值设为b

  • setAccessible(boolean flag) 此方法可以设置是否忽略权限限制直接访问private等私有权限的成员变量

  • getModifiers() -- 获得可以解析出该成员变量所采用修饰符的整数

访问方法

  • getMethods()

  • getMethod(String name,Class<?>...parameterTypes)

  • getDeclaredMethods()

  • getDeclaredMethods(String name,Class<?>...parameterTypes)

返回Method类型的对象或数组

Method类的常用方法

  • getName() --- 获得该方法的名称

  • getParameterTypes() -- 按照声明顺序以Class数组的形式获得该方法的各个参数的类型

  • getReturnType() ---以class对象的形式获得该方法的返回值的类型

  • getExceptionTypes() -- 以Class数组的形式获得该方法可能抛出的异常类型

  • invoke(Object obj,Object...args) --利用指定参数args执行指定对象obj中的方法,返回值为Object类型

  • isVarArgs() --- 查看该函数是否允许带有可变数量的参数,如果允许则返回true,否则返回false

  • getModifiers() -- 获得可以解析出该方法所采用修饰符的整数

Annotation

参考:Java反射:使用Annotation功能_pan_junbiao的博客-CSDN博客

定义Annotation类型,@interface关键字(继承了Java.lang.annotation.Annotation接口)

//定义一个Annotation类型
public @interface NoMemberAnnotation{
​
}
//这样未包含任何成员的Annotation类型称为marker annotation 
//可以定义包含成员变量的类型有String、Class、primitive、enumberated和annotation以及所列类型的数组,如果所定义的Annotation类型中只包含一个成员,通常将成员名称命名为value
​
public @interface DefaultValueAnnotation{
    //可以为成员设置默认值
    String describe() default "<默认值>";
    Class type() default void.class;
}

在定义Annotation类型时,还可以通过Annotation类型@Target来设置Annotation类型适用的程序元素种类。如果未设置@Target,则表示适用于所有程序元素。枚举类ElementType中的枚举常量用来设置@Target。

枚举类ElementType中的枚举常量

 通过Annotation类型@Retention可以设置Annotation类型的有效范围。枚举类RetentionPolicy中的枚举常量用来设置@Retention。如果未设置@Retention,Annotation的有效范围为枚举常量CLASS表示的范围。

枚举类RetentionPolicy中的枚举常量

 访问Annotation信息 如果在定义Annotation类型时将@Retention设置为RetentionPolicy.RUNTIME,那么在运行程序时通过反射就可以获取到相关的Annotation信息,如获取构造方法、字段和方法的Annotation信息。

类Constructor、Field和Method均继承了AccessibleObject类,在AccessibleObject中定义了3个关于Annotation的方法,其中方法isAnnotationPresent(Class<? extends Annotation> annotationClass)用来查看是否添加了指定类型的Annotation,如果是则返回true,否则返回false;方法getAnnotation(Class<T> annotationClass)用来获得指定类型的Annotation,如果存在则返回相应的对象,否则返回null;方法getAnnotations()用来获得所有的Annotation,该方法将返回一个Annotation数组。

在类Constructor和Method中还定义了方法getParameterAnnotations(),用来获得为所有参数添加的Annotation,将以Annotation类型的二维数组返回,在数组中的顺序与声明的顺序相同,如果没有参数则返回一个长度为0的数组;如果存在未添加Annotation的参数,将用一个长度为0的嵌套数组占位。

七、枚举类型和泛型

枚举

继承于java.lang.Enum类

使用枚举类型定义常量方式:

//enum为定义枚举类型关键字
public enum Constants{
     Constants_A,
     Constants_B,
     Constants_C
}
​
//程序中调用枚举类型常量的方法:Constants.Constants_A
​
//枚举类型也可以在类的内部以内部类的形式定义
public class A{
    enum B{
        Constants_A,
        Constants_B,
    }
}

枚举类型常用方法

方法名 含义 使用方法
values() 可以将枚举类型成员以数组的形式返回 枚举类型名称.values()
valueOf() 实现将普通字符串转换为枚举实例 枚举类型名称.valueOf("abc")
compareTo() 用于比较两个枚举对象在定义时的顺序 枚举对象.compareTo(枚举对象)
ordinal() 用于得到枚举成员的位置索引 枚举对象.ordinal()

枚举类型声明提供了一种用户友好的变量定义方法,枚举了某种数据类型所有可能出现的值。

特点

  • 类型安全

  • 紧凑有效的数据定义

  • 可以和程序其他部分完美交互

  • 运行效率高

泛型

在泛型之前,Java提供了对Object的引用”任意化“操作,这种任意化操作就是对Object引用进行”向下转型“及”向上转型“操作,但某些强制类型转换的错误也许不会被编译器捕捉,而在运行后出现异常,可见强制类型转换存在安全隐患,所以提供泛型机制解决此问题。

语法:类名<T> T代表一个类型的名称,容器元素使用E表达

使用泛型定义的类在声明该类对象时可以根据不同的需求指定<T>真正的类型,而在使用类中的方法传递或返回数据类型时将不再需要进行类型转换操作。

泛型的用法

  • 定义泛型类时声明多个类型

//1
MutiOverClass<T1,T2>
//2
MutiOverClass:泛型类名称
    
MutiOverClass<Boolean,Float> = new MutiOverClass<Boolean,Float>();
  • 定义泛型类时声明数组类型

public class OverClass<T> {  //定义泛型类
    private T[] array; //定义泛型数组
    public void SetT(T[] array){
        this.array = array;
    }
    public T[] getT(){
        return array;
    }
    public static void main(String[] args) {
        OverClass<String> a = new OverClass<String>();//泛型实例,字符串型数组
        String[] array = {"成员1","成员2","成员3"};
        a.SetT(array);
        for(int i =0;i<a.getT().length;i++){
            System.out.println(a.getT()[i]);  //返回数组中的值
        }
    }
}
  • 集合类声明容器的元素

可以使用K和V两个字符代表容器中的键值与键值对应的具体值

import java.util.HashMap;
import java.util.Map;
​
public class MultiOverClass<K,V>{  //定义泛型集合
    public Map<K,V> m = new HashMap<K,V>();   //定义一个HashMap实例
    public void put(K k,V v){
        m.put(k,v); //将对应的键值对存入集合对象中
    }
    public V get(K k){
        return m.get(k);
    }
​
    public static void main(String[] args) {
        MultiOverClass<Integer,String> mu =new MultiOverClass<Integer,String>();
        for(int i=0;i<5;i++){
            mu.put(i,"我是集合成员"+i);  //循环存入键值对给集合
        }
        for(int i=0;i<mu.m.size();i++){
            System.out.println(mu.get(i));  //获取集合中的值
        }
    }
}

被泛型化的集合类--可在主方法中直接使用,不用定义泛型类

  • ArrayList ----泛型定义ArrayList<E>

  • HashMap----泛型定义HashMap<K,V>

  • HashSet----泛型定义HashSet<E>

  • Vector ---- 泛型定义Vector<E>

泛型的高级用法

  • 限制泛型可用类型

class  类名称<T extends anyClass>   //anyClass表示某个接口或类
    
使用泛型限制后,泛型类的类型必须实现或继承了anyClass这个接口或类,无论anyClass是接口还是类,在进行泛型限制时都必须使用extends关键字
public class LimitClass<T extends List> {
    public static void main(String[] args) {
        //实例化已经实现List接口的类
        LimitClass<ArrayList> l1 = new LimitClass<ArrayList>();
        LimitClass<LinkedList> l2 = new LimitClass<LinkedList>();
//        LimitClass<HashMap> l3 = new LimitClass<HashMap>(); //报错,因为HashMap没有实现List接口
    }
}

当没有使用extends关键字限制泛型类类型时,默认Object类下的所有子类都可以实例化泛型类对象。

  • 使用类型通配符

类型通配符的主要作用是在创建一个泛型类对象时限制这个泛型类的类型实现或继承某个接口或类的子类。

//语法
泛型类名称<? extends List> a =null; 
​
//<? extends List> 表示类型未知,当需要使用该泛型对象时,可以单独实例化
public class CommonClass<T> { //类型通配符泛型
    //将该泛型实例放在方法的参数中
    public void doSomething(CommonClass<? extends List> a){
​
    }
    public static void main(String[] args) {
        CommonClass<? extends List> a =null;
        a = new CommonClass<ArrayList>();
        a = new CommonClass<LinkedList>();
​
        //示例
        List<String> l1 = new ArrayList<String>(); //实例化一个ArrayList 对象
        l1.add("成员"); //在集合中添加内容
        List<?> l2 =l1; //使用通配符声明的名称实例化的对象不能对其加入新的信息,只能获取和删除
        List<?> l3 = new LinkedList<Integer>();
        System.out.println(l2.get(0));  //获取集合中的第一个值
        l1.set(0,"成员改变");
//        l2.set(0,"成员改变");  //报错
​
    }
}

泛型类的限制除了可以向下限制之外,还可以进行向上限制,只需要在定义时使用super关键字即可。

A<? super List> a =null; 这样定义后对象a只接受List接口或上层父类类型,如a=new A<Object>();

  • 继承泛型类与实现泛型接口

    定义为泛型的类也可以被继承与实现

    public class ExtendClass<T1>{
         
    }
    class SubClass<T1,T2,T3> extends ExtendClass<T1>{
        
    }
    ​
    //定义的泛型接口也可以被实现
    interface i<T1>{
        
    }
    class SubClass2<T1,T2,T3> implements i<T1>{
        
    }

总结

  • 泛型的类型参数只能是类类型(Integer,Long,Short,Boolean,Byte,Character,Double,Float),不可以是简单类型,如A<int>这种泛型定义就是错误的

  • 泛型的类型个数可以是多个

  • 可以使用extends关键字限制泛型的类型

  • 可以使用通配符限制泛型的类型

八、多线程

Windows操作系统是多任务操作系统,它以进程为单位。一个进程是一个包含有自身地址的程序,每个独立执行的程序都称为进程,也就是正在执行的程序。系统可以分配给每个进程一段有限的使用CPU的时间,CPU在这段时间中执行某个进程,然后下一个时间片又跳转至另一个进程中去执行。由于CPU转换较快,所以使得每个进程好像是同时执行一样。

一个线程则是进程中的执行流程,一个进程可以同时包括多个线程,每个线程也可以得到一小段程序的执行时间,这样一个进程就可以具有多个并发执行的线程。在单线程中,程序代码按照调用顺序依次往下执行,如果需要一个进程同时完成多段代码的操作,就需要多线程。

多线程的两种实现方式

继承java.lang.Thread类

启动一个新线程需要建立Thread实例

构造方法

  • public Thread(String threadName) --- 创建一个名称为threadName的线程对象

  • public Thread()

实现java.lang.Runnable接口

如果需要继承其他类(非Thread类),而且还要使当前类实现多线程,那么可以通过Runnable接口来实现。

//语法
public class Thread extends Object implements Runnable

实现Runnable接口的程序会创建一个Thread对象,并将Runnable对象与Thread对象相关联。Thread类中有以下构造方法

  • public Thread(Runnable r)

  • puble Thread(Runnable r,Sting name)

使用Runnable接口启动新的线程的步骤:

  • 建立Runnable对象

  • 使用参数为Runnable对象的构造函数创建Thread实例

  • 调用start()方法启动线程

线程的生命周期

  • 出生状态:线程被创建时处于的状态,在用户使用该线程实例调用start()方法之前都处于出生状态

  • 就绪状态:当用户调用start()方法后,线程处于就绪状态(可执行状态)

  • 运行状态:当线程得到系统资源后就进入运行状态

  • 等待状态:当处于运行状态下的线程调用Thread类中的wait()方法时,该线程便进入等待状态,进入等待状态的线程必须调用Thread类中的notify()方法才能被唤醒,而notifyAll()方法是将所有处于等待状态下的线程唤醒。

  • 休眠状态:当线程调用Thread类中的sleep()方法时,则会进入休眠状态

  • 阻塞状态:如果一个线程在运行状态下发出输入/输出请求,该线程将进入阻塞状态,在其等待输入/输出结束时线程进入就绪状态,对于阻塞的线程来说,即使系统资源空闲,线程依然不能回到运行状态。

  • 死亡状态:当线程的run()方法执行完毕时,线程进入死亡状态。

线程处于就绪状态方法

  • 调用sleep()方法

  • 调用wait()方法

  • 等待输入/输出完成

当线程处于就绪状态后,再次进入运行状态:

  • 线程调用notify()方法

  • 线程调用notifyAll()方法

  • 线程调用interrupt()方法

  • 线程的休眠时间结束

  • 输入/输出结束

操作线程的方法

  • 线程的休眠:sleep()方法控制线程的休眠,需要一个参数指定该线程休眠的时间,时间以毫秒为单位

  • 线程的加入:join()方法实现某个线程加入到另外一个线程,另一个线程会等待该线程执行完毕后再继续执行

  • 线程的中断:在run()方法中使用无限循环的形式,然后使用一个布尔型标记控制循环的停止(stop()方法已废弃)

  • 线程的礼让:yield()方法只是给当前处于运行状态的线程一个提醒,告知它可以将资源礼让给其他线程,但仅仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。yield方法使具有同样优先级的线程有进入可执行状态的机会,当当前线程放弃执行权时会再度回到就绪状态。对于支持多任务的操作系统来说,不需要调用yield方法,因为操作系统会为线程自动分配CPU时间片来执行。

线程的优先级

Thread类中包含的成员变量代表了线程的某些优先级,每个线程的优先级都在Thread.MIN_PRIORATY(1)~Thread.MAX_PRIORATY(10)只见那,默认情况下都是Thread.NORM_PRIORITY(5)。每个新产生的线程都继承了父线程的优先级。

线程的优先级可以使用setPriority()方法调整,如果设置的优先级不在1~10之内,将产生IllegleArgumentException异常。

线程同步

在编写多线程程序时,应该考虑线程安全问题。实质上线程安全问题来源于两个线程同时存取单一对象的数据

线程同步机制:基本上所有解决多线程资源冲突问题的方法都是采用给定时间只允许一个线程访问共享资源,这时就需要给共享资源上一道锁。

java提供同步机制使用synchronized关键字,可以有效防止冲突。

同步块方法:

syncronized(Object){
     //共享资源
}

通常将共享资源放置在syncronized定义的区域内,这样当其他线程也获取到这个所时,必须等待锁被释放时才能进入该区域。Object为任意一个对象,每个对象都存在一个标志位,并具有两个值,分别为0和1.一个线程运行到同步块时首先检查该对象的标志位,如果为0状态,表明此同步块中存在其他线程在运行。这时该线程处于就绪状态,直到处于同步块中的线程执行完同步块中的代码为止。这时该对象的标志位被设置为1,该进程才能执行同步块中的代码,并将Object对象的标志位设置为0,防止其他线程执行同步块中的代码。

同步方法:

syncronized void f(){ //方法前面修饰syncronized关键字的方法
​
}

当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步的方法执行完毕后才能被执行。必须将每个能访问共享资源的方法修饰为syncronized,否则就会出错。

九、网路通信

Internet网络依靠TCP/IP协议,在全球范围内实现不同的硬件结构、不同的操作系统、不同网络系统的互连。TCP/IP模式是一种层次结构,共分为四层。

两个高级协议:传输控制协议(Transmission Control Protocal,TCP)和用户数据报协议(User Datagram Protocal,UDP)

TCP协议是一种以固接连线为基础的协议,它提供两台计算机间可靠的数据传送。TCP可以保证从一端数据送至连接的另一端时,数据能够确实送达,而且抵达的数据的排列顺序和送出时的顺序相同,因此,TCP协议适合可靠性要求比较高的场合。(拨打电话)

UDP是无连接通信协议,不保证可靠数据传输,但能够向若干个目标发送数据,接收发自若干个源的数据。UDP是以独立发送数据包的方式进行。(邮递员送信)UDP协议适合对数据准确性要求不高的场合。

端口:一般而言,一台计算机只有单一的连到网络的物理连接,所有的数据都通过此连接对内、对外送达特定的计算机。网络程序设计中的端口并非真实的物理存在,而是一个假想的连接装置。规定0~65535之间的整数。HTTP服务一般使用80端口,FTP服务使用21端口。客户机通过不同的端口来确定连接到服务器的哪项服务。

套接字:网络程序中的套接字(Socket)用于将应用程序与端口连接起来。是一个假想的连接装置。就像插座用于连接电器与电线

TCP程序设计

InetAddress类

java.net.Address类与IP地址相关,利用该类获取IP地址、主机地址等信息

  • getByName(String host)---获取与Host相应的的InetAddress对象

  • getHostAddress()---获取InetAddress对象所含的IP地址

  • getHostName()---获取此IP地址的主机名

  • getLocalHost()---返回本地主机的InetAddress对象

ServerSocket类

java.net.ServerSocket类用来表示服务器套接字,其主要功能是等待来自网络上的“请求”,它可通过指定的端口来等待连接的套接字。服务器套接字一次可以与一个套接字连接。如果多台客户机同时提出连接请求,服务器套接字会将请求连接的客户机存入队列中,然后取出一个套接字与服务器新建的套接字连接起来。若请求连接数大于最大容纳数,则多出的请求被拒绝。队列的默认大小为50。

构造函数(都抛出IOException)

  • ServerSocket()---创建非绑定服务器套接字

  • ServerSocket(int port)---创建绑定到特定端口的服务器套接字

  • ServerSocket(int port,int backlog)---利用指定的backlog创建服务器套接字并将其绑定到指定的本地端口号

  • ServerSocket(int port,int backlog,InetAddress bindAddress)---利用指定的端口、侦听backlog和要绑定到本地IP地址创建服务器。适用于计算机有多块网卡和多个IP地址的情况,用于明确在哪块网卡或IP地址上等待客户的连接请求

常用方法

  • accep()---等待客户机的连接,若连接则创建一套接字

  • isBound()---判断ServerSocket的绑定状态

  • getInetAddress()---返回此服务器套接字的本地地址

  • isClosed()---返回服务器套接字的关闭状态

  • close()---关闭服务器套接字

  • bind(SocketAddress endpoint)---将ServerSocket绑定到特定地址(IP地址和端口号)

  • getPort()----返回此套接字连接到的远程端口

服务器端和客户端交互过程

  • 服务器程序创建一个ServerSocket服务器端套接字,调用accept()方法等待客户机来连接

  • 客户端程序创建一个Socket,请求与服务器建立连接

  • 服务器接收客户机的连接请求,同时创建一个新的Socket与客户建立连接。服务器继续等待新的请求

//实现TCP单向通信
public class MyTcp {  //服务器端程序
    private BufferedReader render;  //字符输入流
    private ServerSocket server;  //服务器端套接字
    private Socket socket;  //客户端套接字
    void getserver(){
        try{
            server = new ServerSocket(8998);  //实例化Socket对象
            System.out.println("服务器套接字创建成功");
            while(true){
                System.out.println("等待客户机连接");
                socket = server.accept();
                render = new BufferedReader(new InputStreamReader(socket.getInputStream())); //服务器通过输入流接收数据
                getClientMessage();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //获取客户端信息
    private void getClientMessage(){
        try{
            while(true){ //如果套接字是连接状态
                System.out.println("客户机:"+render.readLine());
            }
        }catch(Exception e){
           e.printStackTrace();
        }
        try{
            if(render!=null){  //关闭流
                render.close();
            }
            if(socket!=null){ //关闭套接字
                socket.close();
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
​
    public static void main(String[] args) {
        MyTcp tcp = new MyTcp();
        tcp.getserver();  //调用方法
    }
}
public class MyClient extends JFrame {  //客户端程序
    private PrintWriter writer;  //字符输出流
    Socket socket;  //客户端套接字
    private JTextArea ta = new JTextArea();
    private JTextField tf = new JTextField();
    Container cc;
    public MyClient(String title){  //构造方法
        super(title);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        cc = this.getContentPane();
        final JScrollPane scrollPane = new JScrollPane();
        scrollPane.setBorder(new BevelBorder(BevelBorder.RAISED));
        getContentPane().add(scrollPane,BorderLayout.CENTER);
        scrollPane.setViewportView(ta);
        cc.add(tf,"South");   //将文本框放在窗体的下部
        tf.addActionListener(new ActionListener() { //绑定事件
            @Override
            public void actionPerformed(ActionEvent e) {
                writer.println(tf.getText());  //将文本框中的信息写入流
                ta.append(tf.getText()+'\n');  //将文本框中的信息显示在文本域中
                ta.setSelectionEnd(ta.getText().length());
                tf.setText("");  //将文本框清空
            }
        });
    }
​
    private void connect(){
        ta.append("尝试连接\n"); //文本域中提示信息
        try{
            socket = new Socket("127.0.0.1",8998);  //实例化Socket
            writer = new PrintWriter(socket.getOutputStream(),true);  //客户机通过输出流发送数据
            ta.append("完成连接\n");
        }catch (Exception e){
            e.printStackTrace();
        }
​
    }
​
    public static void main(String[] args) {
        MyClient client = new MyClient("向服务器发送数据");
        client.setSize(300,250);
        client.setVisible(true);
        client.connect();
    }
}
​
运行服务器,再运行客户端,建立连接,向文本框输入信息,输入信息在服务器端输出。

UDP程序设计

基于UDP的信息传递快,但不提供可靠的保证。使用UDP传递数据时,用户无法知道数据能否到达主机,也不能确定到达目的地的顺序是否和发送的顺序相同。

基于UDP通信的基本模式:

  • 将数据打包(称为数据包),然后将数据包发往目的地

  • 接收别人发来的数据包,然后查看数据包

发送数据包:

  • 使用DatagramSocket()创建一个数据包套接字

  • 使用DatagramPacket(byte[] buf,int offset,int length,InetAddress address,int port)创建要发送的数据包

  • 使用DatagramSocket类的send()方法发送数据包

接收数据包:

  • 使用DatagramSocket(int port)创建数据包套接字,绑定到指定端口

  • 使用DatagramPacket(byte[] buf,int length)创建字节数组来接收数据包

  • 使用DatagramPacket类的receive()方法接收UDP包

DatagramPacket类

java.net包中的DatagramPacket类表示数据包。

构造方法

  • DatagramPacket(byte[] buf,int length)---创建DatagramPacket对象,指定内存空间和大小

  • DatagramPacket(byte[] buf,int length,InetAddress address,int port)--创建DatagramPacket对象,指定内存空间、大小、数据包的目标地址和端口(在发送数据时,必须指定接收方的Socket地址和端口号,所以使用此构造方法创建发送数据的DatagramPacket对象)

DatagramSocket类

java.net包中的DatagramSocket类用于表示发送和接收数据包的套接字

构造方法

  • DatagramSocket() -- 创建DatagramSocket对象,构造数据报套接字并将其绑定到本地主机上任何可用的端口

  • DatagramSocket(int port)-- 创建DatagramSocket对象,构造数据报套接字并将其绑定到本地主机上指定的端口(接收程序)

  • DatagramSocket(int port,InetAddress addr)-- 创建DatagramSocket对象,构造数据报套接字并将其绑定到指定的本地地址,适用于有多块网卡和多个IP地址的情况


篇幅过长了~  更多练习代码见链接

https://gitee.com/chen-xing1/java-study.githttps://gitee.com/chen-xing1/java-study.git

猜你喜欢

转载自blog.csdn.net/acx0000/article/details/128007969