0
点赞
收藏
分享

微信扫一扫

JavaSE笔记

双井暮色 2022-02-04 阅读 110

文章目录


前言

用于查漏补缺

一、集合

在这里插入图片描述

在这里插入图片描述

Collection

Collection接口的常用方法:
增加:add addAll
删除:clear remove
修改:
查看:iterator() size()
判断:contains  equals isEmpty
//创建对象:接口不能创建对象,利用实现类创建对象:
Collection col = new ArrayList();
//集合有一个特点:只能存放引用数据类型的数据,不能是基本数据类型
//基本数据类型自动装箱,对应包装类。int--->Integer
col.add(18);
col.add(12);
col.contains(117)
List list = Arrays.asList(new Integer[]{11, 15, 3, 7, 1});
col.addAll(list);//将另一个集合添加入col中
System.out.println(col);
//col.clear();清空集合
System.out.println("集合中元素的数量为:"+col.size());
System.out.println("集合是否为空:"+col.isEmpty());
boolean isRemove = col.remove(15);
System.out.println(col);
System.out.println("集合中数据是否被删除:"+isRemove);


//对集合遍历(对集合中元素进行查看)
//普通for循环不行
/*for(int i= 0;i<col.size();i++){
}*/
 //方式1:增强for循环
for(Object o:col){
    System.out.println(o);
}
//方式2:iterator()
Iterator it = col.iterator();
while(it.hasNext()){
     System.out.println(it.next());
}

List

List接口中常用方法:
增加:add
删除:remove  remove
修改:set
查看:get

        list.add(3, 66);//index=3为6,其他往后推
        list.set(3, 77);//index=3变为77

        list.remove(2);//在集合中存入的是Integer类型数据的时候,调用remove方法调用的是:remove(int index)
        list.remove("abc");
        Object o = list.get(0);

        //List集合 遍历:
        //方式1:普通for循环:
        for(int i = 0;i<list.size();i++){
            System.out.println(list.get(i));
        }
        //方式2:增强for循环:
        for(Object obj:list){
            System.out.println(obj);
        }
        //方式3:迭代器:
        Iterator it = list.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }

ArrayList
JDK1.7
1.在JDK1.7中:在调用构造器的时候给底层数组elementData初始化,数组初始化长度为102.当数组中的10个位置都满了的时候就开始进行数组的扩容,扩容长度为 原数组的1.5倍:

JDK1.8
1.JDK1.8底层依旧是Object类型的数组

Vector
1.底层Object数组

泛型

集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之 后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。
Collection, List, ArrayList 这个就是类型参数,即泛型。

如果不使用泛型的话,有缺点:
一般我们在使用的时候基本上往集合中存入的都是相同类型的数据–》便于管理,所以现在什么引用数据类型都可以存入集合,不方便!

加入泛型的优点:在编译时期就会对类型进行检查,不是泛型对应的类型就不可以添加入这个集合。

(1)使用了泛型以后,可以确定集合中存放数据的类型,在编译时期就可以检查出来。
(2)使用泛型你可能觉得麻烦,实际使用了泛型才会简单,后续的遍历等操作简单。
(3)泛型的类型:都是引用数据类型,不能是基本数据类型。
(4)

ArrayList<Integer> al = new ArrayList<Integer>();在JDK1.7以后可以写为:
ArrayList<Integer> al = new ArrayList<>();  --<>  ---钻石运算符

LinkedList

 增加 addFirst   addLast
     offer   offerFirst    offerLast
删除 poll
    pollFirst    pollLast //JDK1.6以后新出的方法,提高了代码的健壮性
    removeFirst    removeLast
修改
查看 element
   getFirst  getLast
   indexOf   lastIndexOf
   peek
   peekFirst   peekLast
判断

        LinkedList<String> list = new LinkedList<>();
        list.add("aaaaa");
        list.add("bbbbb");
        list.add("ccccc");
        list.add("ddddd");
        list.add("eeeee");
        list.add("bbbbb");
        list.add("fffff");
        list.addFirst("jj");
        list.addLast("hh");
        list.offer("kk");//添加元素在尾端
        list.offerFirst("pp");
        list.offerLast("rr");
        System.out.println(list);//LinkedList可以添加重复数据
        System.out.println(list.poll());//删除头上的元素并且将元素输出,建议用poll,poll是JDK1.6出的,提高代码健壮性
        System.out.println(list.pollFirst());
        System.out.println(list.pollLast());
        System.out.println(list.removeFirst());
        System.out.println(list.removeLast());
        System.out.println(list);//LinkedList可以添加重复数据
        /*list.clear();//清空集合


  //普通for循环:
        for(int i = 0;i<list.size();i++){
            System.out.println(list.get(i));
        }
        System.out.println("---------------------");
        //增强for:
        for(String s:list){
            System.out.println(s);
        }
        System.out.println("---------------------");
        //迭代器:
        /*Iterator<String> it = list.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }*/
        //下面这种方式好,节省内存
        for(Iterator<String> it = list.iterator();it.hasNext();){
            System.out.println(it.next());
        }


ArrayList
物理结构:紧密结构
逻辑结构:线性表(数组)

LinkedList
物理结构:跳转链表
逻辑结构:线性表(链表)

        ArrayList<String> list = new ArrayList<>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("ee");
        //在"cc"之后添加一个字符串"kk"
        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            if("cc".equals(it.next())){
                list.add("kk");
            }
        }
报Exception
出错原因:就是迭代器和list同时对集合进行操作

解决办法:事情让一个“人”做 --》引入新的迭代器:ListIterator
迭代和添加操作都是靠ListIterator来完成的
ListIterator<String> it = list.listIterator();
换成这个解决

Set

HashSet:唯一,无序
自定义的类型不满足 唯一,无序
比如Student
HashSet<Integer> hs = new HashSet<>();   System.out.println(hs.add(19));//true
 hs.add(5);
 hs.add(20);    System.out.println(hs.add(19));//false 这个19没有放入到集合中
        hs.add(41);
        hs.add(0);
        System.out.println(hs.size());//唯一,无序

底层:数组+链表=哈希表
放入HashSet中的数据要重写两个方法:HashCode和equals

LinkedHashSet:唯一,无序
在HashSet的基础上,多了一个总的链表,这个总链表将放入的元素串在一起,方便有序的遍历
额外增加了before after 用于指向前一个Entry 后一个Entry。也就是说,元素之间维持着一条总的链表数据结构

比较器

String类实现了Comparable接口,这个接口中有一个抽象方法compareTo,String类中重写这个方法即可

内部比较器
public class Student implements Comparable<Student>{
    private int age;
    private double height;
    private String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public double getHeight() {
        return height;
    }
    public void setHeight(double height) {
        this.height = height;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Student(int age, double height, String name) {
        this.age = age;
        this.height = height;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", height=" + height +
                ", name='" + name + '\'' +
                '}';
    }
    @Override
    public int compareTo(Student o) {
        //按照年龄进行比较:
        /*return this.getAge() - o.getAge();*/
        //按照身高比较
        /*return ((Double)(this.getHeight())).compareTo((Double)(o.getHeight()));*/
        //按照名字比较:
        return this.getName().compareTo(o.getName());
    }
}

新开一个testclass System.out.println(s1.compareTo(s2));

外部比较器

class BiJiao01 implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        //比较年龄:
        return o1.getAge()-o2.getAge();
    }
}
class BiJiao02 implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        //比较姓名:
        return o1.getName().compareTo(o2.getName());
    }
}

public class Test02 {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //比较两个学生:
        Student s1 = new Student(9,160.5,"alili");
        Student s2 = new Student(14,170.5,"bnana");
        //获取外部比较器:
        Comparator bj1 = new BiJiao03();
        System.out.println(bj1.compare(s1, s2));
    }
}

外部比较器和内部比较器 谁好呀?
答案:外部比较器,多态,扩展性好

外部比较器就是一个Student类,一个实现Comparator的类,重写compareTo方法,用类3test,实现多态

TreeSet:唯一,无序
        TreeSet<Integer> ts = new TreeSet<>();
        ts.add(12);
        ts.add(3);
        ts.add(7);
        ts.add(9);
        ts.add(3);
        ts.add(16);
        System.out.println(ts.size());
        System.out.println(ts);
无序:没有按照输入顺序进行输出

底层:二叉树
物理结构:跳转结构

加上比较器
public class Test03 {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //创建一个TreeSet:
        //利用外部比较器,必须自己制定:
        /*Comparator<Student> com = new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getName().compareTo(o2.getName());
            }
        };*/
        TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });//一旦指定外部比较器,那么就会按照外部比较器来比较
        ts.add(new Student(10,"elili"));
        ts.add(new Student(8,"blili"));
        ts.add(new Student(4,"alili"));
        ts.add(new Student(9,"elili"));
        ts.add(new Student(10,"flili"));
        ts.add(new Student(1,"dlili"));
        System.out.println(ts.size());
        System.out.println(ts);
    }
}

TreeSet底层的二叉树的遍历是按照升序的结果出现的,这个升序是靠中序遍历得到

Map

 增加:put(K key, V value)
 删除:clear() remove(Object key)
 修改:
查看:entrySet() get(Object key) keySet() size() values()
判断:containsKey(Object key) containsValue(Object value)
equals(Object o) isEmpty()

Map:无序,唯一
        Map<String,Integer> map = new HashMap<>();
        System.out.println(map.put("lili", 10101010));
        map.put("nana",12345234);
        map.put("feifei",34563465);
        System.out.println(map.put("lili", 34565677));
        map.put("mingming",12323);
        /*map.clear();清空*/
        /*map.remove("feifei");移除*/
        System.out.println(map.size());
        System.out.println(map);
        System.out.println(map.containsKey("lili"));
        System.out.println(map.containsValue(12323));
        Map<String,Integer> map2 = new HashMap<>();
        System.out.println(map2.put("lili", 10101010));
        map2.put("nana",12345234);
        map2.put("feifei",34563465);
        System.out.println(map2.put("lili", 34565677));
        map2.put("mingming2",12323);
        System.out.println(map==map2);
        System.out.println(map.equals(map2));//equals进行了重写,比较的是集合中的值是否一致
        System.out.println("判断是否为空:"+map.isEmpty());
        System.out.println(map.get("nana"));
        System.out.println("-----------------------------------");
        //keySet()对集合中的key进行遍历查看:
        Set<String> set = map.keySet();
        for(String s:set){
            System.out.println(s);
        }
        System.out.println("-----------------------------------");
        //values()对集合中的value进行遍历查看:
        Collection<Integer> values = map.values();
        for(Integer i:values){
            System.out.println(i);
        }
        System.out.println("-----------------------------------");
        //get(Object key) keySet()
        Set<String> set2 = map.keySet();
        for(String s:set2){
            System.out.println(map.get(s));
        }
        System.out.println("-----------------------------------");
        //entrySet()
        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        for(Map.Entry<String, Integer> e:entries){
            System.out.println(e.getKey()+"----"+e.getValue());
        }


//keySet()对集合中的key进行遍历查看:
        Set<String> set = map.keySet();
        for(String s:set){
            System.out.println(s);
        }

//values()对集合中的value进行遍历查看:
        Collection<Integer> values = map.values();
        for(Integer i:values){
            System.out.println(i);
        }

        //get(Object key) keySet()
        Set<String> set2 = map.keySet();
        for(String s:set2){
            System.out.println(map.get(s));
        }
        //entrySet()
        Set<Map.Entry<String, Integer>> entries = map.entrySet();
        for(Map.Entry<String, Integer> e:entries){
            System.out.println(e.getKey()+"----"+e.getValue());
        }

TreeMap:唯一,有序

外部比较器
Map<Student,Integer> map = new TreeMap<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return ((Double)(o1.getHeight())).compareTo((Double)(o2.getHeight()));
            }
        });
        map.put(new Student(19,"blili",170.5),1001);
        map.put(new Student(18,"blili",150.5),1003);
        map.put(new Student(19,"alili",180.5),1023);
        map.put(new Student(17,"clili",140.5),1671);
        map.put(new Student(10,"dlili",160.5),1891);
        System.out.println(map);
        System.out.println(map.size());

HashMap JDK1.2 效率高 线程不安全,key可以是null值
JDK1.0 效率低 线程安全 key不可以是null值

Collections工具类

//Collections不支持创建对象,因为构造器私有化了
        /*Collections cols = new Collections();*/
        //里面的属性和方法都是被static修饰,我们可以直接用类名.去调用即可:
        //常用方法:
        //addAll:
        ArrayList<String> list = new ArrayList<>();
        list.add("cc");
        list.add("bb");
        list.add("aa");
        Collections.addAll(list,"ee","dd","ff");
        Collections.addAll(list,new String[]{"gg","oo","pp"});
        System.out.println(list);
        //binarySearch必须在有序的集合中查找:--》排序:
        Collections.sort(list);//sort提供的是升序排列
        System.out.println(list);
        //binarySearch
        System.out.println(Collections.binarySearch(list, "cc"));
        //copy:替换方法
        ArrayList<String> list2 = new ArrayList<>();
        Collections.addAll(list2,"tt","ss");
        Collections.copy(list,list2);//将list2的内容替换到list上去
        System.out.println(list);
        System.out.println(list2);
        //fill 填充
        Collections.fill(list2,"yyy");
        System.out.println(list2);

Stack

Stack是Vector的子类,Vector里面两个重要的属性:
		/*
        Object[] elementData;底层依然是一个数组
        int elementCount;数组中的容量
         */
        Stack s = new Stack();
        s.add("A");
        s.add("B");
        s.add("C");
        s.add("D");
        System.out.println(s);//[A, B, C, D]
        System.out.println("栈是否为空:" + s.empty());
        System.out.println("查看栈顶的数据,但是不移除:" + s.peek());
        System.out.println(s);
        System.out.println("查看栈顶的数据,并且不移除:" + s.pop());
        System.out.println(s);
        s.push("D");//和add方法执行的功能一样,就是返回值不同
        System.out.println(s);

同步类容器

ArrayList,HashMap,线程不安全,现在想把线程不安全的集合转换为线程安全的集合

    public static void main(String[] args) {
        //ArrayList为案例:从线程不安全  转为线程安全:
        List list = Collections.synchronizedList(new ArrayList());
    }
    //加上这个线程不安全解决
    

ConcurrentHashMap

JDK5.0之后提供了多种并发类容器可以替代同步类容器,提升性能、吞吐量
ConcurrentHashMap替代HashMap、HashTable
ConcurrentSkipListMap替代TreeMap

 public static void main(String[] args) {
        //选择一个容器:
        ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
        
        //创建10个线程:
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    long startTime = System.currentTimeMillis();
                    for (int j = 0; j < 1000000; j++) {
                        map.put("test" + j , j);
                    }
                    long endTime = System.currentTimeMillis();
                    System.out.println("一共需要的时间:" + (endTime - startTime));
                }
            }).start();
        }
    }
 public static void main(String[] args) {
        //选择一个容器:
        //ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
        Hashtable map = new Hashtable();
        //创建10个线程:
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    long startTime = System.currentTimeMillis();
                    for (int j = 0; j < 1000000; j++) {
                        map.put("test" + j , j);
                    }
                    long endTime = System.currentTimeMillis();
                    System.out.println("一共需要的时间:" + (endTime - startTime));
                }
            }).start();
        }
    }
public static void main(String[] args) {
        //选择一个容器:
        //ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
        //Hashtable map = new Hashtable();
        HashMap map = new HashMap();
        //创建10个线程:
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    long startTime = System.currentTimeMillis();
                    for (int j = 0; j < 1000000; j++) {
                        map.put("test" + j , j);
                    }
                    long endTime = System.currentTimeMillis();
                    System.out.println("一共需要的时间:" + (endTime - startTime));
                }
            }).start();
        }
    }
 public static void main(String[] args) {
        //选择一个容器:
        //ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
        //Hashtable map = new Hashtable();
        HashMap oldmap = new HashMap();
        Map map = Collections.synchronizedMap(oldmap);
        //创建10个线程:
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    long startTime = System.currentTimeMillis();
                    for (int j = 0; j < 1000000; j++) {
                        map.put("test" + j , j);
                    }
                    long endTime = System.currentTimeMillis();
                    System.out.println("一共需要的时间:" + (endTime - startTime));
                }
            }).start();
        }
    }

总结:
ConcurrentHashMap:性能高,线程安全
Hashtable: 线程安全,性能低
HashMap:线程不安全,性能高
线程安全的HashMap:线程安全,性能低

COW并发容器

CopyOnWriteArrayList

 CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
        //添加方法:
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        System.out.println(list);//[1, 2, 3, 4]
        list.add(3);//add方法无论元素是否存在,都可以添加进去--》添加重复的元素
        System.out.println(list);//[1, 2, 3, 4, 3]
        //adj. 缺席的;缺少的;心不在焉的;茫然的
        list.addIfAbsent(33);//添加不存在的元素--》不可以添加重复的数据
        System.out.println(list);//[1, 2, 3, 4, 3, 33]

CopyOnWriteArraySet

 public static void main(String[] args) {
        //创建一个集合:
        CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();
        //在这里也体现出Set和List的本质区别,就在于是否重复
        //所以add方法直接不可以添加重复数据进去
        set.add(1);
        set.add(2);
        set.add(2);
        set.add(7);
        System.out.println(set);//[1, 2, 7]
        
    }

Queue

BlockingQueue
BlockingQueue继承Queue,Queue继承自Collection
BlockingQueue常见子类:
ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue,PriorityBlockingQueue,DelayQueue

Deque

        /*
        双端队列:
        Deque<E> extends Queue
        Queue一端放 一端取的基本方法  Deque是具备的
        在此基础上 又扩展了 一些 头尾操作(添加,删除,获取)的方法
         */
        Deque<String> d = new LinkedList<>() ;
        d.offer("A");
        d.offer("B");
        d.offer("C");
        System.out.println(d);//[A, B, C]
        d.offerFirst("D");
        d.offerLast("E");
        System.out.println(d);//[D, A, B, C, E]
        System.out.println(d.poll());
        System.out.println(d);//[A, B, C, E]
        System.out.println(d.pollFirst());
        System.out.println(d.pollLast());
        System.out.println(d);

二、IO流

File类

引入

【1】文件,目录:
文件:
内存中存放的数据在计算机关机后就会消失。要长久保存数据,就要使用硬盘、光盘、U 盘等设备。为了便于数据的管理和检索,引入了“文件”的概念。一篇文章、一段视频、一个可执行程序,都可以被保存为一个文件,并赋予一个文件名。操作系统以文件为单位管理磁盘中的数据。

一般来说,文件可分为文本文件、视频文件、音频文件、图像文件、可执行文件等多种类别,这是从文件的功能进行分类的。从数据存储的角度来说,所有的文件本质上都是一样的,都是由一个个字节组成的,归根到底都是 0、1 比特串。不同的文件呈现出不同的形态(有的是文本,有的是视频等等)

文件夹(目录):
成千上万个文件如果不加分类放在一起,用户使用起来显然非常不便,因此又引入了树形目录(目录也叫文件夹)的机制,可以把文件放在不同的文件夹中,文件夹中还可以嵌套文件夹,这就便于用户对文件进行管理和使用

【2】查看文件/目录的信息:
右键-属性

【3】在java程序中操纵 文件/目录 ?怎么办?
java程序,最典型的特点,面向对象,java程序最擅长的就是操作对象,盘符上的文件/目录,将它的各种信息进行了封装,封装为一个对象,
java程序最擅长的就是操纵对象,这个对象属于 —》File类

盘符上的文件—》封装为对象—》对象属于File类的对象–》有了这个对象,我们程序就可以直接操纵这个对象,通过这个对象获取文件的各种信息,还可以对文件进行创建 ,删除。

对文件进行操作

import java.io.File;
import java.io.IOException;
public class Test01 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //将文件封装为一个File类的对象:
        File f = new File("d:\\test.txt");
        File f1 = new File("d:\\test.txt");
        File f2 = new File("d:/test.txt");
        //File.separator属性帮我们获取当前操作系统的路径拼接符号
       //在windows,dos下,系统默认用“\”作为路径分隔符 ,在unix,url中,使用“/”作为路径分隔符。
        File f3 = new File("d:"+File.separator+"test.txt");//建议使用这种
        //常用方法:
        System.out.println("文件是否可读:"+f.canRead());
        System.out.println("文件是否可写:"+f.canWrite());
        System.out.println("文件的名字:"+f.getName());
        System.out.println("上级目录:"+f.getParent());
        System.out.println("是否是一个目录:"+f.isDirectory());
        System.out.println("是否是一个文件:"+f.isFile());
        System.out.println("是否隐藏:"+f.isHidden());
        System.out.println("文件的大小:"+f.length());
        System.out.println("是否存在:"+f.exists());
        /*if(f.exists()){//如果文件存在,将文件删除操作
            f.delete();
        }else{//如果不存在,就创建这个文件
            f.createNewFile();
        }*/
        System.out.println(f == f1);//比较两个对象的地址
        System.out.println(f.equals(f1));//比较两个对象对应的文件的路径
        //跟路径相关的:
        System.out.println("绝对路径:"+f.getAbsolutePath());
        System.out.println("相对路径:"+f.getPath());
        System.out.println("toString:"+f.toString());
        System.out.println("----------------------");
        File f5 = new File("demo.txt");
        if(!f5.exists()){
            f5.createNewFile();
        }
        //绝对路径指的就是:真实的一个精准的,完整的路径
        System.out.println("绝对路径:"+f5.getAbsolutePath());
        //相对路径:有一个参照物,相对这个参照物的路径。
        //在main方法中,相对位置指的就是:D:\IDEA_workspace\TestJavaSE
        //在junit的测试方法中,相对路径指的就是模块位置
        System.out.println("相对路径:"+f5.getPath());
        //toString的效果永远是  相对路径
        System.out.println("toString:"+f5.toString());
        File f6 = new File("a/b/c/demo.txt");
        if(!f5.exists()){
            f5.createNewFile();
        }
        System.out.println("绝对路径:"+f6.getAbsolutePath());
        System.out.println("相对路径:"+f6.getPath());
    }
}

对目录进行操作

import java.io.File;
public class Test02 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        //将目录封装为File类的对象:
        File f = new File("D:\\IDEA_workspace");
        System.out.println("文件是否可读:"+f.canRead());
        System.out.println("文件是否可写:"+f.canWrite());
        System.out.println("文件的名字:"+f.getName());
        System.out.println("上级目录:"+f.getParent());
        System.out.println("是否是一个目录:"+f.isDirectory());
        System.out.println("是否是一个文件:"+f.isFile());
        System.out.println("是否隐藏:"+f.isHidden());
        System.out.println("文件的大小:"+f.length());
        System.out.println("是否存在:"+f.exists());
        System.out.println("绝对路径:"+f.getAbsolutePath());
        System.out.println("相对路径:"+f.getPath());
        System.out.println("toString:"+f.toString());
        //跟目录相关的方法:
        File f2 = new File("D:\\a\\b\\c");
        //创建目录:
        //f2.mkdir();//创建单层目录
        //f2.mkdirs();//创建多层目录
        //删除:如果是删除目录的话,只会删除一层,并且前提:这层目录是空的,里面没有内容,如果内容就不会被删除
        f2.delete();
        //查看:
        String[] list = f.list();//文件夹下目录/文件对应的名字的数组
        for(String s:list){
            System.out.println(s);
        }
        System.out.println("=========================");
        File[] files = f.listFiles();//作用更加广泛
        for(File file:files){
            System.out.println(file.getName()+","+file.getAbsolutePath());
        }
    }
}

IO流

引入

【1】File类:封装文件/目录的各种信息,对目录/文件进行操作,但是我们不可以获取到文件/目录中的内容。
【2】引入:IO流:
I/O : Input/Output的缩写,用于处理设备之间的数据的传输。
【3】形象理解:IO流 当做一根 “管”:
在这里插入图片描述

【4】IO流的体系结构:

在这里插入图片描述

案例(后期补)

警告:不要用字符流去操作非文本文件

文本文件:.txt .java .c .cpp —》建议使用字符流操作
非文本文件:.jpg, .mp3 , .mp4 , .doc , .ppt —》建议使用字节流操作

利用try-catch-finally处理异常方式

import java.io.*;
public class Test04 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args)  {
        //1.有一个源文件
        File f1 = new File("d:\\Test.txt");
        //2.有一个目标文件:
        File f2 = new File("d:\\Demo.txt");
        //3.搞一个输入的管 怼到源文件上:
        FileReader fr = null;
        FileWriter fw = null;
        try {
            fr = new FileReader(f1);
            //4.搞一个输出的管,怼到目标文件上:
            fw = new FileWriter(f2);
            //5.开始动作:
            char[] ch = new char[5];
            int len = fr.read(ch);
            while(len!=-1){
                String s = new String(ch,0,len);
                fw.write(s);
                len = fr.read(ch);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //6.关闭流:(关闭流的时候,倒着关闭,后用先关)
            try {
                if(fw!=null){//防止空指针异常
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(fr!=null){
                    fr.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

FileInputStream读取文件中内容

【1】读取文本文件:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Test01 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //功能:利用字节流将文件中内容读到程序中来:
        //1.有一个源文件:
        File f = new File("D:\\Test.txt");
        //2.将一个字节流这个管 怼  到 源文件上:
        FileInputStream fis = new FileInputStream(f);
        //3.开始读取动作
        /*
        细节1:
        文件是utf-8进行存储的,所以英文字符 底层实际占用1个字节
        但是中文字符,底层实际占用3个字节。
        细节2:
        如果文件是文本文件,那么就不要使用字节流读取了,建议使用字符流。
        细节3:
        read()读取一个字节,但是你有没有发现返回值是 int类型,而不是byte类型?
        read方法底层做了处理,让返回的数据都是“正数”
        就是为了避免如果字节返回的是-1的话,那到底是读入的字节,还是到文件结尾呢。
         */
        int n = fis.read();
        while(n!=-1){
            System.out.println(n);
            n = fis.read();
        }
        //4.关闭流:
        fis.close();
    }
}

【2】利用字节流读取非文本文件:(以图片为案例:)–》一个字节一个字节的读取:

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Test02 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //功能:利用字节流将文件中内容读到程序中来:
        //1.有一个源文件:
        File f = new File("D:\\LOL.jpg");
        //2.将一个字节流这个管 怼  到 源文件上:
        FileInputStream fis = new FileInputStream(f);
        //3.开始读取动作
        int count = 0;//定义一个计数器,用来计读入的字节的个数
        int n = fis.read();
        while(n!=-1){
            count++;
            System.out.println(n);
            n = fis.read();
        }
        System.out.println("count="+count);
        //4.关闭流:
        fis.close();
    }
}

【3】利用字节类型的缓冲数组:

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Test03 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //功能:利用字节流将文件中内容读到程序中来:
        //1.有一个源文件:
        File f = new File("D:\\LOL.jpg");
        //2.将一个字节流这个管 怼  到 源文件上:
        FileInputStream fis = new FileInputStream(f);
        //3.开始读取动作
        //利用缓冲数组:(快递员的小车)
        byte[] b = new byte[1024*6];
        int len = fis.read(b);//len指的就是读取的数组中的有效长度
        while(len!=-1){
            //System.out.println(len);
            for(int i = 0;i<len;i++){
                System.out.println(b[i]);
            }
            len = fis.read(b);
        }
        //4.关闭流:
        fis.close();
    }
}

FileInputStream,FileOutputStream完成非文本文件的复制

【1】读入一个字节,写出一个字节:

import java.io.*;

public class Test04 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //功能:完成图片的复制:
        //1.有一个源图片
        File f1 = new File("d:\\LOL.jpg");
        //2.有一个目标图片:
        File f2 = new File("d:\\LOL2.jpg");
        //3.有一个输入的管道 怼 到 源文件:
        FileInputStream fis = new FileInputStream(f1);
        //4.有一个输出的管道 怼到  目标文件上:
        FileOutputStream fos = new FileOutputStream(f2);
        //5.开始复制:(边读边写)
        int n = fis.read();
        while(n!=-1){
            fos.write(n);
            n = fis.read();
        }
        //6.关闭流:(倒着关闭流,先用后关)
        fos.close();
        fis.close();
    }
}

【2】利用缓冲字节数组:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Test05 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //功能:完成图片的复制:
        //1.有一个源图片
        File f1 = new File("d:\\LOL.jpg");
        //2.有一个目标图片:
        File f2 = new File("d:\\LOL2.jpg");
        //3.有一个输入的管道 怼 到 源文件:
        FileInputStream fis = new FileInputStream(f1);
        //4.有一个输出的管道 怼到  目标文件上:
        FileOutputStream fos = new FileOutputStream(f2);
        //5.开始复制:(边读边写)
        //利用缓冲数组:
        byte[] b = new byte[1024*8];
        int len = fis.read(b);
        while(len!=-1){
            fos.write(b,0,len);
            len = fis.read(b);
        }
        //6.关闭流:(倒着关闭流,先用后关)
        fos.close();
        fis.close();
    }
}

缓冲字节流(处理流)-BufferedInputStream ,BufferedOutputStream

【1】读入一个字节,写出一个字节:
在这里插入图片描述

【2】利用缓冲字节数组:
在这里插入图片描述

【3】利用缓冲区:
在这里插入图片描述

想要完成上面的效果,单纯的靠FileInputStream,FileOutputStream是不可以完成的,这个时候就需要功能的加强,
这个加强就需要引入新的流(在FileInputStream,FileOutputStream外面再套一层流):BufferedInputStream ,BufferedOutputStream. ----->处理流

代码:

import java.io.*;
public class Test06 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //1.有一个源图片
        File f1 = new File("d:\\LOL.jpg");
        //2.有一个目标图片:
        File f2 = new File("d:\\LOL2.jpg");
        //3.有一个输入的管道 怼 到 源文件:
        FileInputStream fis = new FileInputStream(f1);
        //4.有一个输出的管道 怼到  目标文件上:
        FileOutputStream fos = new FileOutputStream(f2);
        //5.功能加强,在FileInputStream外面套一个管:BufferedInputStream:
        BufferedInputStream bis = new BufferedInputStream(fis);
        //6.功能加强,在FileOutputStream外面套一个管:BufferedOutputStream:
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        //7.开始动作 :
        byte[] b = new byte[1024*6];
        int len = bis.read(b);
        while(len!=-1){
            bos.write(b,0,len);
           /* bos.flush(); 底层已经帮我们做了刷新缓冲区的操作,不用我们手动完成:底层调用flushBuffer()*/
            len = bis.read(b);
        }
        //8.关闭流:
        //倒着关:
        //如果处理流包裹着节点流的话,那么其实只要关闭高级流(处理流),那么里面的字节流也会随之被关闭。
        bos.close();
        bis.close();
        /*fos.close();
        fis.close();*/
    }
}

比对非文本文件复制的三种方法的效率

【1】读入一个字节,写出一个字节:
在这里插入图片描述

【2】利用缓冲字节数组:
在这里插入图片描述

【3】利用缓冲区:

在这里插入图片描述

代码:

import java.io.*;
public class Test06 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //1.有一个源图片
        File f1 = new File("d:\\LOL.jpg");
        //2.有一个目标图片:
        File f2 = new File("d:\\LOL2.jpg");
        //3.有一个输入的管道 怼 到 源文件:
        FileInputStream fis = new FileInputStream(f1);
        //4.有一个输出的管道 怼到  目标文件上:
        FileOutputStream fos = new FileOutputStream(f2);
        //5.功能加强,在FileInputStream外面套一个管:BufferedInputStream:
        BufferedInputStream bis = new BufferedInputStream(fis);
        //6.功能加强,在FileOutputStream外面套一个管:BufferedOutputStream:
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        //7.开始动作 :
        long startTime = System.currentTimeMillis();
        byte[] b = new byte[1024];
        int len = bis.read(b);
        while(len!=-1){
            bos.write(b,0,len);
           /* bos.flush(); 底层已经帮我们做了刷新缓冲区的操作,不用我们手动完成:底层调用flushBuffer()*/
            len = bis.read(b);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("复制完成的时间为:"+(endTime-startTime));
        //8.关闭流:
        //倒着关:
        //如果处理流包裹着节点流的话,那么其实只要关闭高级流(处理流),那么里面的字节流也会随之被关闭。
        bos.close();
        bis.close();
        /*fos.close();
        fis.close();*/
    }
}

缓冲字符流(处理流)-BufferedReader,BufferedWriter完成文本文件的复制

import java.io.*;
public class Test07 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //1.有一个源文件:
        File f1 = new File("d:\\Test.txt");
        //2.有一个目标文件:
        File f2 = new File("d:\\Demo.txt");
        //3.需要一个管 怼到 源文件:
        FileReader fr = new FileReader(f1);
        //4.需要一根管怼到目标文件:
        FileWriter fw = new FileWriter(f2);
        //5.套一根管在输入字符流外面:
        BufferedReader br = new BufferedReader(fr);
        //6.套一根管在输出字符流外面:
        BufferedWriter bw = new BufferedWriter(fw);
        //7.开始动作:
        //方式1:读取一个字符,输出一个字符:
        /*int n = br.read();
        while(n!=-1){
            bw.write(n);
            n = br.read();
        }*/
        //方式2:利用缓冲数组:
        /*char[] ch = new char[30];
        int len = br.read(ch);
        while(len!=-1){
            bw.write(ch,0,len);
            len = br.read(ch);
        }*/
        //方式3:读取String:
        String str = br.readLine();//每次读取文本文件中一行,返回字符串
        while(str!=null){
            bw.write(str);
            //在文本文件中应该再写出一个换行:
            bw.newLine();//新起一行
            str = br.readLine();
        }
        //8.关闭流
        bw.close();
        br.close();
    }
}

转换流-InputStreamReader,OutputStreamWriter

【1】转换流:作用:将字节流和字符流进行转换。
【2】转换流 属于 字节流还是字符流?属于字符流
InputStreamReader :字节输入流 —》字符的输入流
OutputStreamWriter : 字符输出流 --》字节的输出流

【3】图解:
在这里插入图片描述

【4】将输入的字节流转换为输入的字符流,然后完成文件–》程序 :

import java.io.*;
public class Test01 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //文件---》程序:
        //1.有一个源文件:
        File f = new File("d:\\Test.txt");
        //2.需要一个输入的字节流接触文件:
        FileInputStream fis = new FileInputStream(f);
        //3.加入一个转换流,将字节流转换为字符流:(转换流属于一个处理流)
        //将字节转换为字符的时候,需要指定一个编码,这个编码跟文件本身的编码格式统一
        //如果编码格式不统一的话,那么在控制台上展示的效果就会出现乱码
        //InputStreamReader isr = new InputStreamReader(fis,"utf-8");
        //获取程序本身的编码--》utf-8
        InputStreamReader isr = new InputStreamReader(fis);
        //4.开始动作,将文件中内容显示在控制台:
        char[] ch = new char[20];
        int len = isr.read(ch);
        while(len!=-1){
            //将缓冲数组转为字符串在控制台上打印出来
            System.out.print(new String(ch,0,len));
            len = isr.read(ch);
        }
        //5.关闭流:
        isr.close();
    }
}

转换流-InputStreamReader,OutputStreamWriter实现文本文件的复制


import java.io.*;
public class Test02 {
    public static void main(String[] args) throws IOException {
        //1.有一个源文件
        File f1 = new File("d:\\Test.txt");
        //2.有一个目标文件:
        File f2 = new File("d:\\Demo.txt");
        //3.输入方向:
        FileInputStream fis = new FileInputStream(f1);
        InputStreamReader isr = new InputStreamReader(fis,"utf-8");
        //4.输出方向:
        FileOutputStream fos = new FileOutputStream(f2);
        OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
        //5.开始动作:
        char[] ch = new char[20];
        int len = isr.read(ch);
        while(len!=-1){
            osw.write(ch,0,len);
            len = isr.read(ch);
        }
        //6.关闭流:
        osw.close();
        isr.close();
    }
}

System类对IO流的支持

import java.io.*;
public class Test02 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //1.有一个源文件
        File f1 = new File("d:\\Test.txt");
        //2.有一个目标文件:
        File f2 = new File("d:\\Demo.txt");
        //3.输入方向:
        FileInputStream fis = new FileInputStream(f1);
        InputStreamReader isr = new InputStreamReader(fis,"utf-8");
        //4.输出方向:
        FileOutputStream fos = new FileOutputStream(f2);
        OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk");
        //5.开始动作:
        char[] ch = new char[20];
        int len = isr.read(ch);
        while(len!=-1){
            osw.write(ch,0,len);
            len = isr.read(ch);
        }
        //6.关闭流:
        osw.close();
        isr.close();
    }
}

练习:键盘录入内容输出到文件中

【1】解决思路:
在这里插入图片描述

【2】代码:

import java.io.*;
public class Test03 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //1.先准备输入方向:
        //键盘录入:
        InputStream in = System.in;//属于字节流
        //字节流--》字符流:
        InputStreamReader isr = new InputStreamReader(in);
        //在isr外面再套一个缓冲流:
        BufferedReader br = new BufferedReader(isr);
        //2.再准备输出方向:
        //准备目标文件
        File f = new File("d:\\Demo1.txt");
        FileWriter fw = new FileWriter(f);
        BufferedWriter bw = new BufferedWriter(fw);
        //3.开始动作:
        String s = br.readLine();
        while(!s.equals("exit")){
            bw.write(s);
            bw.newLine();//文件中换行
            s = br.readLine();
        }
        //4.关闭流:
        bw.close();
        br.close();
    }
}

数据流-DataInputStream,DataOutputStream

【1】数据流:用来操作基本数据类型和字符串的
【2】
DataInputStream:将文件中存储的基本数据类型和字符串 写入 内存的变量中
DataOutputStream: 将内存中的基本数据类型和字符串的变量 写出 文件中

【3】代码:

利用DataOutputStream向外写出变量:

public class Test01 {
    public static void main(String[] args) throws IOException {
        //DataOutputStream:  将内存中的基本数据类型和字符串的变量 写出  文件中
        /*File f = new File("d:\\Demo2.txt");
        FileOutputStream fos = new FileOutputStream(f);
        DataOutputStream dos = new DataOutputStream(fos);*/
        DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File("d:\\Demo2.txt")));
        //向外将变量写到文件中去:
        dos.writeUTF("你好");
        dos.writeBoolean(false);
        dos.writeDouble(6.9);
        dos.writeInt(82);
        //关闭流:
        dos.close();
    }
}

在Demo2.txt文件中,我们看到:

在这里插入图片描述

发现:这个内容我们看不懂,是给程序看的

所以下面我们开始读取的程序:

import java.io.*;
public class Test02 {
    public static void main(String[] args) throws IOException {
        //DataInputStream:将文件中存储的基本数据类型和字符串  写入  内存的变量中
        DataInputStream dis = new DataInputStream(new FileInputStream(new File("d:\\Demo2.txt")));
        //将文件中内容读取到程序中来:
        System.out.println(dis.readUTF());
        System.out.println(dis.readBoolean());
        System.out.println(dis.readDouble());
        System.out.println(dis.readInt());
        //关闭流:
        dis.close();
    }
}

结果:

在这里插入图片描述

验证:那个文件,我们看不懂,程序看得懂
要求:
写出的类型跟读入的类型 必须 要匹配!

对象流-ObjectInputStream,ObjectOutputStream

【1】对象流:ObjectInputStream,ObjectInputStream
用于存储和读取基本数据类型数据或对象的处理流。
它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。

【2】序列化和反序列化:
ObjectOutputStream 类 : 把内存中的Java对象转换成平台无关的二进制数据,从而允许把这种二进制数据持久地保存在磁盘上,或通过网络将这种二进制数据传输到另一个网络节点。----》序列化
用ObjectInputStream类 : 当其它程序获取了这种二进制数据,就可以恢复成原来的Java对象。----》反序列化

【3】代码:操作字符串对象:
首先将一个字符串对象写到文件中去:----》序列化

public class Test01 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:\\Demo3.txt")));
        //将内存中的字符串写出到文件中:
        oos.writeObject("你好");
        //关闭流:
        oos.close();
    }
}

查看文件:
在这里插入图片描述

我们看不懂文件的内容,但是程序是可以看懂的,所以可以写一个程序读文件中内容:----》反序列化

public class Test02 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //将文件中保存的字符串 读入到 内存:
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:\\Demo3.txt")));
        //读取:
        String s = (String)(ois.readObject());
        System.out.println(s);
        //关闭流:
        ois.close();
    }
}

控制台:
在这里插入图片描述

【4】代码:操作自定义类的对象:
自定义的Person类:

public class Person {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

测试类:

public class Test01 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //序列化:将内存中对象 ---》 文件:
        //有一个对象:
        Person p = new Person("lili",19);
        //有对象流:
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:\\Demo4.txt")));
        //向外写:
        oos.writeObject(p);
        //关闭流:
        oos.close();
    }
}

运行的时候发现出现异常:
在这里插入图片描述

出现异常的原因:
你想要序列化的那个对象对应的类,必须要实现一个接口:

接口内部,什么都没有,这种接口叫 标识接口。
起到标识作用,标识什么呢?只要实现这个接口的类的对象才能序列化,否则不可以。

解决办法:将Person 实现这个标识接口就可以:

public class Person implements Serializable {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

测试:发现序列化成功,Person具备了序列化的能力。

在这里插入图片描述

这个二进制数据我们看不懂,但是程序可以看懂,所以我们可以用程序实现 反序列化操作:
将这个对象 恢复到内存中来:

public class Test02 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:\\Demo4.txt")));
        //读入内存:
        Person p = (Person)(ois.readObject());
        System.out.println(p/*.toString()*/);
        //关闭流:
        ois.close();
    }
}

结果:
因为我们没有重写toString方法,所以结果为:
在这里插入图片描述

证明了反序列化成功: 将二进制数据 --》内存

【5】serialVersionUID:
凡是实现Serializable接口(标识接口)的类都有一个表示序列化版本标识符的静态常量:
➢private static final long serialVersionUID;
➢serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序加化时是否兼容。
➢如果类没有显示定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议,显式声明。

➢简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

我现在在Person类中加入toString方法:

public class Person implements Serializable {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Person() {
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

再次运行测试类:
出现异常:
在这里插入图片描述

出现异常的原因:
在这里插入图片描述

解决:给这个类 加入一个 序列号:serialVersionUID

在这里插入图片描述

【6】IDEA中配置序列化版本号:
在这里插入图片描述

在Person类上:alt+enter:
在这里插入图片描述

回车即可生成
在这里插入图片描述

【7】序列化细节:
(1)被序列化的类的内部的所有属性,必须是可序列化的 (基本数据类型都是可序列化的)
在这里插入图片描述
在这里插入图片描述

(2)static,transient修饰的属性 不可以被序列化。

public class Person implements Serializable {
    private static final long serialVersionUID = 8027651838638826533L;
    private transient String name;
    private static int age;
    private Famaily f = new Famaily();
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Person() {
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", f=" + f + ",age=" + age +
                '}';
    }
}

结果:

在这里插入图片描述

三、多线程

程序,进程,线程

【1】程序,进程,线程
➢程序(program):是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。 (程序是静态的)

➢进程(process):是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。 (进程是动态的)是一个动的过程 ,进程的生命周期 : 有它自身的产生、存在和消亡的过程

➢线程(thread),进程可进一步细化为线程, 是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的。
在这里插入图片描述

【2】单核CPU与多核CPU的任务执行:

在这里插入图片描述

【3】并行和并发:
并行:多个CPU同时执行多个任务
并发:一个CPU“同时”执行多个任务(采用时间片切换)

创建线程的三种方式

注意:火车票问题后期补下

第一种:继承Thread类

【1】在学习多线程一章之前,以前的代码是单线程的吗?不是,以前也是有三个线程同时执行的。
在这里插入图片描述

【2】现在我想自己制造多线程—》创建线程 ??
线程类–》线程对象
在这里插入图片描述

 * 线程类叫:TestThread,不是说你名字中带线程单词你就具备多线程能力了(争抢资源能力)
 * 现在想要具备能力,继承一个类:Thread,具备了争抢资源的能力
 */
public class TestThread extends Thread{
    /*
    一会线程对象就要开始争抢资源了,这个线程要执行的任务到底是啥?这个任务你要放在方法中
    但是这个方法不能是随便写的一个方法,必须是重写Thread类中的run方法
    然后线程的任务/逻辑写在run方法中
     */
    @Override
    public void run() {
        //输出1-10
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(i);
        }
    }
}
public class Test {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //主线程中也要输出十个数:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println("main1-----"+i);
        }
        //制造其他线程,要跟主线程争抢资源:
        //具体的线程对象:子线程
        TestThread tt = new TestThread();
        //tt.run();//调用run方法,想要执行线程中的任务 -->这个run方法不能直接调用,直接调用就会被当做一个普通方法
        //想要tt子线程真正起作用比如要启动线程:
        tt.start();//start()是Thread类中的方法
        //主线程中也要输出十个数:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println("main2-----"+i);
        }
    }
}

第二种:实现Runnable接口

【1】代码:

 /* TestThread实现了这个接口,才会变成一个线程类
 */
public class TestThread implements Runnable{
    @Override
    public void run() {
        //输出1-10数字:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"----"+i);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //创建子线程对象:
        TestThread tt = new TestThread();
        Thread t = new Thread(tt,"子线程");
        t.start();
        //主线程里面也是打印1-10数字:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}

运行结果:
在这里插入图片描述

第三种:实现Callable接口

对比第一种和第二种创建线程的方式发现,无论第一种继承Thread类的方式还是第二种实现Runnable接口的方式,都需要有一个run方法,
但是这个run方法有不足:

(1)没有返回值
(2)不能抛出异常

基于上面的两个不足,在JDK1.5以后出现了第三种创建线程的方式:实现Callable接口:

实现Callable接口好处:(1)有返回值 (2)能抛出异常
缺点:线程创建比较麻烦

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
 * @author : msb-zhaoss
 */
public class TestRandomNum implements Callable<Integer> {
    /*
    1.实现Callable接口,可以不带泛型,如果不带泛型,那么call方式的返回值就是Object类型
    2.如果带泛型,那么call的返回值就是泛型对应的类型
    3.从call方法看到:方法有返回值,可以跑出异常
     */
    @Override
    public Integer call() throws Exception {
        return new Random().nextInt(10);//返回10以内的随机数
    }
}
class Test{
    //这是main方法,程序的入口
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //定义一个线程对象:
        TestRandomNum trn = new TestRandomNum();
        FutureTask ft = new FutureTask(trn);
        Thread t = new Thread(ft);
        t.start();
        //获取线程得到的返回值:
        Object obj = ft.get();
        System.out.println(obj);
    }
}

线程的生命周期

【1】线程声明周期:线程开始–》线程消亡
【2】线程经历哪些阶段:
在这里插入图片描述

线程常见方法

(1)start() : 启动当前线程,表面上调用start方法,实际在调用线程里面的run方法
(2)run() : 线程类 继承 Thread类 或者 实现Runnable接口的时候,都要重新实现这个run方法,run方法里面是线程要执行的内容
(3)currentThread :Thread类中一个静态方法:获取当前正在执行的线程
(4)setName 设置线程名字
(5)getName 读取线程名字

设置优先级

【1】同优先级别的线程,采取的策略就是先到先服务,使用时间片策略
【2】如果优先级别高,被CPU调度的概率就高
【3】级别:1-10 默认的级别为5
在这里插入图片描述

【4】代码:

public class TestThread01 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(i);
        }
    }
}
class TestThread02 extends Thread{
    @Override
    public void run() {
        for (int i = 20; i <= 30 ; i++) {
            System.out.println(i);
        }
    }
}
class Test{
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //创建两个子线程,让这两个子线程争抢资源:
        TestThread01 t1 = new TestThread01();
        t1.setPriority(10);//优先级别高
        t1.start();
        TestThread02 t2 = new TestThread02();
        t2.setPriority(1);//优先级别低
        t2.start();
    }
}
join

join方法:当一个线程调用了join方法,这个线程就会先被执行,它执行结束以后才可以去执行其余的线程。
注意:必须先start,再join才有效。

public class TestThread extends Thread {
    public TestThread(String name){
        super(name);
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(this.getName()+"----"+i);
        }
    }
}
class Test{
    //这是main方法,程序的入口
    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <= 100 ; i++) {
            System.out.println("main-----"+i);
            if(i == 6){
                //创建子线程:
                TestThread tt = new TestThread("子线程");
                tt.start();
                tt.join();//“半路杀出个程咬金”
            }
        }
    }
}
sleep

https://go.zbj.com/news/20146.html (段子)

【1】sleep : 人为的制造阻塞事件

public class Test01 {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("00000000000000");
    }
}

【2】案例:完成秒表功能:

import javafx.scene.input.DataFormat;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test02 {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //2.定义一个时间格式:
        DateFormat df = new SimpleDateFormat("HH:mm:ss");
        while(true){
            //1.获取当前时间:
            Date d = new Date();
            //3.按照上面定义的格式将Date类型转为指定格式的字符串:
            System.out.println(df.format(d));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
setDaemon

【1】设置伴随线程
将子线程设置为主线程的伴随线程,主线程停止的时候,子线程也不要继续执行了
案例:皇上 --》驾崩 —》妃子陪葬

public class TestThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 1000 ; i++) {
            System.out.println("子线程----"+i);
        }
    }
}
class Test{
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //创建并启动子线程:
        TestThread tt = new TestThread();
        tt.setDaemon(true);//设置伴随线程  注意:先设置,再启动
        tt.start();
        //主线程中还要输出1-10的数字:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println("main---"+i);
        }
    }
}

结果:
在这里插入图片描述

stop
public class Demo {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        for (int i = 1; i <= 100 ; i++) {
            if(i == 6){
                Thread.currentThread().stop();//过期方法,不建议使用
            }
            System.out.println(i);
        }
    }
}

线程安全问题

方法1:同步代码块

【1】同步代码块演示1:

public class BuyTicketThread implements Runnable {
    int ticketNum = 10;
    @Override
    public void run() {
        //此处有1000行代码
        for (int i = 1; i <= 100 ; i++) {
            synchronized (this){//把具有安全隐患的代码锁住即可,如果锁多了就会效率低 --》this就是这个锁
                if(ticketNum > 0){
                    System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
                }
            }
        }
        //此处有1000行代码
    }
}

【2】同步代码块演示2:

public class BuyTicketThread extends Thread {
    public BuyTicketThread(String name){
        super(name);
    }
    //一共10张票:
    static int ticketNum = 10;//多个对象共享10张票
    //每个窗口都是一个线程对象:每个对象执行的代码放入run方法中
    @Override
    public void run() {
        //每个窗口后面有100个人在抢票:
        for (int i = 1; i <= 100 ; i++) {
            synchronized (BuyTicketThread.class){//锁必须多个线程用的是同一把锁!!!
                if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票
                    System.out.println("我在"+this.getName()+"买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");
                }
            }
        }
    }
}

【3】同步监视器总结:
总结1:认识同步监视器(锁子) ----- synchronized(同步监视器){ }
1)必须是引用数据类型,不能是基本数据类型
2)也可以创建一个专门的同步监视器,没有任何业务含义
3)一般使用共享资源做同步监视器即可
4)在同步代码块中不能改变同步监视器对象的引用

5)尽量不要String和包装类Integer做同步监视器
6)建议使用final修饰同步监视器

总结2:同步代码块的执行过程
1)第一个线程来到同步代码块,发现同步监视器open状态,需要close,然后执行其中的代码
2)第一个线程执行过程中,发生了线程切换(阻塞 就绪),第一个线程失去了cpu,但是没有开锁open
3)第二个线程获取了cpu,来到了同步代码块,发现同步监视器close状态,无法执行其中的代码,第二个线程也进入阻塞状态
4)第一个线程再次获取CPU,接着执行后续的代码;同步代码块执行完毕,释放锁open
5)第二个线程也再次获取cpu,来到了同步代码块,发现同步监视器open状态,拿到锁并且上锁,由阻塞状态进入就绪状态,再进入运行状态,重复第一个线程的处理过程(加锁)
强调:同步代码块中能发生CPU的切换吗?能!!! 但是后续的被执行的线程也无法执行同步代码块(因为锁仍旧close)

总结3:其他
1)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,其他线程无法访问其中的任何一个代码块
2)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块, 但是没有锁住使用其他同步监视器的代码块,其他线程有机会访问其他同步监视器的代码块

方法2:同步方法

【1】代码展示:

public class BuyTicketThread implements Runnable {
    int ticketNum = 10;
    @Override
    public void run() {
        //此处有1000行代码
        for (int i = 1; i <= 100 ; i++) {
            buyTicket();
        }
        //此处有1000行代码
    }
    public synchronized void buyTicket(){//锁住的是this
        if(ticketNum > 0){
            System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
        }
    }
}
public class BuyTicketThread extends Thread {
    public BuyTicketThread(String name){
        super(name);
    }
    //一共10张票:
    static int ticketNum = 10;//多个对象共享10张票
    //每个窗口都是一个线程对象:每个对象执行的代码放入run方法中
    @Override
    public void run() {
        //每个窗口后面有100个人在抢票:
        for (int i = 1; i <= 100 ; i++) {
            buyTicket();
        }
    }
    public static synchronized void buyTicket(){//锁住的  同步监视器: BuyTicketThread.class
        if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票
            System.out.println("我在"+Thread.currentThread().getName()+"买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");
        }
    }
}

【2】总结:
总结1:
多线程在争抢资源,就要实现线程的同步(就要进行加锁,并且这个锁必须是共享的,必须是唯一的。
咱们的锁一般都是引用数据类型的。

目的:解决了线程安全问题。

总结2:关于同步方法

  1. 不要将run()定义为同步方法
  2. 非静态同步方法的同步监视器是this
    静态同步方法的同步监视器是 类名.class 字节码信息对象
  3. 同步代码块的效率要高于同步方法
    原因:同步方法是将线程挡在了方法的外部,而同步代码块锁将线程挡在了代码块的外部,但是却是方法的内部
  4. 同步方法的锁是this,一旦锁住一个方法,就锁住了所有的同步方法;同步代码块只是锁住使用该同步监视器的代码块,而没有锁住使用其他监视器的代码块
方法3:Lock锁

【1】Lock锁引入:
JDK1.5后新增新一代的线程同步方式:Lock锁
与采用synchronized相比,lock可提供多种锁方案,更灵活

synchronized是Java中的关键字,这个关键字的识别是靠JVM来识别完成的呀。是虚拟机级别的。
但是Lock锁是API级别的,提供了相应的接口和对应的实现类,这个方式更灵活,表现出来的性能优于之前的方式。

【2】代码演示:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BuyTicketThread implements Runnable {
    int ticketNum = 10;
    //拿来一把锁:
    Lock lock = new ReentrantLock();//多态  接口=实现类  可以使用不同的实现类
    @Override
    public void run() {
        //此处有1000行代码
        for (int i = 1; i <= 100 ; i++) {
            //打开锁:
            lock.lock();
            try{
                if(ticketNum > 0){
                    System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
                }
            }catch (Exception ex){
                ex.printStackTrace();
            }finally {
                //关闭锁:--->即使有异常,这个锁也可以得到释放
                lock.unlock();
            }
        }
        //此处有1000行代码
    }
}

【3】 Lock和synchronized的区别

    1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁
    2.Lock只有代码块锁,synchronized有代码块锁和方法锁
    3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

【4】优先使用顺序:

    Lock----同步代码块(已经进入了方法体,分配了相应资源)----同步方法(在方法体之外)
线程同步优缺点

【1】对比:
线程安全,效率低
线程不安全,效率高

【2】可能造成死锁:
死锁

【3】代码演示:

public class TestDeadLock implements Runnable {
    public int flag = 1;
    static Object o1 = new Object(),o2 = new Object();
        
        
    public void run(){
        System.out.println("flag=" + flag);
        // 当flag==1锁住o1
        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                // 只要锁住o2就完成
                synchronized (o2) {
                    System.out.println("2");
                }
            }
        }
        // 如果flag==0锁住o2
        if (flag == 0) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                // 只要锁住o1就完成
                synchronized (o1) {
                    System.out.println("3");
                }
            }
        }
    }
        
        
    public static void main(String[] args) {
        // 实例2个线程类
        TestDeadLock td1 = new TestDeadLock();
        TestDeadLock td2 = new TestDeadLock();
        td1.flag = 1;
        td2.flag = 0;
        // 开启2个线程
        Thread t1 = new Thread(td1);
        Thread t2 = new Thread(td2);
        t1.start();
        t2.start();
    }
}

【4】解决方法: 减少同步资源的定义,避免嵌套同步

线程通信问题

应用场景:生产者和消费者问题
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
在这里插入图片描述

代码结果展示:
在这里插入图片描述

代码:
1.商品:属性:品牌 ,名字
2.线程1:生产者
3.线程2:消费者

分解1

出现问题:
1.生产者和消费者没有交替输出

2.打印数据错乱
哈尔滨 - null
费列罗啤酒
哈尔滨巧克力
----没有加同步

代码展示:

public class Product {//商品类
    //品牌
    private String brand;
    //名字
    private String name;
    //setter,getter方法;
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
public class ProducerThread extends Thread{//生产者线程
    //共享商品:
    private Product p;
    public ProducerThread(Product p) {
        this.p = p;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {//生产十个商品 i:生产的次数
            if(i % 2 == 0){
                //生产费列罗巧克力
                p.setBrand("费列罗");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                p.setName("巧克力");
            }else{
                //生产哈尔滨啤酒
                p.setBrand("哈尔滨");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                p.setName("啤酒");
            }
            //将生产信息做一个打印:
            System.out.println("生产者生产了:" + p.getBrand() + "---" + p.getName());
        }
    }
}
public class CustomerThread extends Thread{//消费者线程
    //共享商品:
    private Product p;
    public CustomerThread(Product p) {
        this.p = p;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {//i:消费次数
            System.out.println("消费者消费了:" + p.getBrand() + "---" + p.getName());
        }
    }
}
public class Test {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //共享的商品:
        Product p = new Product();
        //创建生产者和消费者线程:
        ProducerThread pt = new ProducerThread(p);
        CustomerThread ct = new CustomerThread(p);
        pt.start();
        ct.start();
    }
}
分解2

【1】利用同步代码块解决问题:

public class ProducerThread extends Thread{//生产者线程
    //共享商品:
    private Product p;
    public ProducerThread(Product p) {
        this.p = p;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {//生产十个商品 i:生产的次数
            synchronized (p){
                if(i % 2 == 0){
                    //生产费列罗巧克力
                    p.setBrand("费列罗");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    p.setName("巧克力");
                }else{
                    //生产哈尔滨啤酒
                    p.setBrand("哈尔滨");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    p.setName("啤酒");
                }
                //将生产信息做一个打印:
                System.out.println("生产者生产了:" + p.getBrand() + "---" + p.getName());
            }
        }
    }
}
public class CustomerThread extends Thread{//消费者线程
    //共享商品:
    private Product p;
    public CustomerThread(Product p) {
        this.p = p;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {//i:消费次数
            synchronized (p){
                System.out.println("消费者消费了:" + p.getBrand() + "---" + p.getName());
            }
        }
    }
}

【2】利用同步方法解决问题:

public class Product {//商品类
    //品牌
    private String brand;
    //名字
    private String name;
    //setter,getter方法;
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    //生产商品
    public synchronized void setProduct(String brand,String name){
        this.setBrand(brand);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        //将生产信息做一个打印:
        System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());
    }
    //消费商品:
    public synchronized void getProduct(){
        System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());
    }
}
public class CustomerThread extends Thread{//消费者线程
    //共享商品:
    private Product p;
    public CustomerThread(Product p) {
        this.p = p;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {//i:消费次数
            p.getProduct();;
        }
    }
}
public class ProducerThread extends Thread{//生产者线程
    //共享商品:
    private Product p;
    public ProducerThread(Product p) {
        this.p = p;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10 ; i++) {//生产十个商品 i:生产的次数
            if(i % 2 == 0){
                p.setProduct("费列罗","巧克力");
            }else{
                p.setProduct("哈尔滨","啤酒");
            }
        }
    }
}
分解3

【1】原理:
在这里插入图片描述

【2】代码:

public class Product {//商品类
    //品牌
    private String brand;
    //名字
    private String name;
    //引入一个灯:true:红色  false 绿色
    boolean flag = false;//默认情况下没有商品 让生产者先生产  然后消费者再消费
    //setter,getter方法;
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    //生产商品
    public synchronized void setProduct(String brand,String name){
        if(flag == true){//灯是红色,证明有商品,生产者不生产,等着消费者消费
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //灯是绿色的,就生产:
        this.setBrand(brand);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        //将生产信息做一个打印:
        System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());
        //生产完以后,灯变色:变成红色:
        flag = true;
        //告诉消费者赶紧来消费:
        notify();
    }
    //消费商品:
    public synchronized void getProduct(){
        if(!flag){//flag == false没有商品,等待生产者生产:
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //有商品,消费:
        System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());
        //消费完:灯变色:
        flag = false;
        //通知生产者生产:
        notify();
    }
}

【3】原理:
在这里插入图片描述

注意:wait方法和notify方法 是必须放在同步方法或者同步代码块中才生效的 (因为在同步的基础上进行线程的通信才是有效的)
注意:sleep和wait的区别:sleep进入阻塞状态没有释放锁,wait进入阻塞状态但是同时释放了锁
【4】线程生命周期完整图:在这里插入图片描述

Lock锁情况下的线程通信

Condition是在Java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。

它的更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition

一个Condition包含一个等待队列。一个Lock可以产生多个Condition,所以可以有多个等待队列。
在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而Lock(同步器)拥有一个同步队列和多个等待队列。

Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。

调用Condition的await()、signal()、signalAll()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

· Conditon中的await()对应Object的wait();

· Condition中的signal()对应Object的notify();
· Condition中的signalAll()对应Object的notifyAll()。

void await() throws InterruptedException

造成当前线程在接到信号或被中断之前一直处于等待状态。

与此 Condition 相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下四种情况之一 以前,当前线程将一直处于休眠状态:

· 其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;或者
· 其他某个线程调用此 Condition 的 signalAll() 方法;或者
· 其他某个线程中断当前线程,且支持中断线程的挂起;或者
· 发生“虚假唤醒”

在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁。在线程返回时,可以保证它保持此锁。

void signal()

唤醒一个等待线程。

如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。

void signalAll()

唤醒所有等待线程。

如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。

更改代码:
在这里插入图片描述

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Product {//商品类
    //品牌
    private String brand;
    //名字
    private String name;
    //声明一个Lock锁:
    Lock lock = new ReentrantLock();
    //搞一个生产者的等待队列:
    Condition produceCondition = lock.newCondition();
    //搞一个消费者的等待队列:
    Condition consumeCondition = lock.newCondition();
    //引入一个灯:true:红色  false 绿色
    boolean flag = false;//默认情况下没有商品 让生产者先生产  然后消费者再消费
    //setter,getter方法;
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    //生产商品
    public void setProduct(String brand,String name){
        lock.lock();
        try{
            if(flag == true){//灯是红色,证明有商品,生产者不生产,等着消费者消费
                try {
                    //wait();
                    //生产者阻塞,生产者进入等待队列中
                    produceCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //灯是绿色的,就生产:
            this.setBrand(brand);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setName(name);
            //将生产信息做一个打印:
            System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());
            //生产完以后,灯变色:变成红色:
            flag = true;
            //告诉消费者赶紧来消费:
            //notify();
            consumeCondition.signal();
        }finally {
            lock.unlock();
        }
    }
    //消费商品:
    public void getProduct(){
        lock.lock();
        try{
            if(!flag){//flag == false没有商品,等待生产者生产:
                try {
                   // wait();
                    //消费者等待,消费者线程进入等待队列:
                    consumeCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //有商品,消费:
            System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());
            //消费完:灯变色:
            flag = false;
            //通知生产者生产:
            //notify();
            produceCondition.signal();
        }finally {
            lock.unlock();
        }
    }
}

四、网络编程

引入

【1】网络编程:
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。
设备之间在网络中进行数据的传输,发送/接收数据。
在这里插入图片描述
2】通信两个重要的要素:IP+PORT
在这里插入图片描述

域名:www.baidu.com ------>DNS服务器解析 ----> IP地址

【3】设备之间进行传输的时候,必须遵照一定的规则 —》通信协议:
在这里插入图片描述
在这里插入图片描述
【4】TCP协议:可靠的
建立连接: 三次握手
在这里插入图片描述
释放连接:四次挥手
在这里插入图片描述
【5】UDP协议:不可靠的
在这里插入图片描述

InetAddress,InetSocketAddress

前情提要:File —》 封装盘符一个文件
【1】InetAddress —》 封装了IP

public class Test01 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws UnknownHostException {
        //封装IP:
        //InetAddress ia = new InetAddress();不能直接创建对象,因为InetAddress()被default修饰了。
        InetAddress ia = InetAddress.getByName("192.168.199.217");
        System.out.println(ia);
        InetAddress ia2 = InetAddress.getByName("localhost");//localhost指代的是本机的ip地址
        System.out.println(ia2);
        InetAddress ia3 = InetAddress.getByName("127.0.0.1");//127.0.0.1指代的是本机的ip地址
        System.out.println(ia3);
        InetAddress ia4 = InetAddress.getByName("LAPTOP-CRIVSRRU");//封装计算机名
        System.out.println(ia4);
        InetAddress ia5 = InetAddress.getByName("www.mashibing.com");//封装域名
        System.out.println(ia5);
        System.out.println(ia5.getHostName());//获取域名
        System.out.println(ia5.getHostAddress());//获取ip地址
    }
}

【2】InetSocketAddress —》封装了IP,端口号

public class Test02 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        InetSocketAddress isa = new InetSocketAddress("192.168.199.217",8080);
        System.out.println(isa);
        System.out.println(isa.getHostName());
        System.out.println(isa.getPort());
        InetAddress ia = isa.getAddress();
        System.out.println(ia.getHostName());
        System.out.println(ia.getHostAddress());
    }
}

网络通信原理–套接字

在这里插入图片描述

基于TCP的网络编程

功能:模拟网站的登录,客户端录入账号密码,然后服务器端进行验证。

功能分解1:单向通信

功能:客户端发送一句话到服务器:

客户端:

public class TestClient {//客户端
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //1.创建套接字:指定服务器的ip和端口号:
        Socket s = new Socket("192.168.199.217",8888);
        //2.对于程序员来说,向外发送数据 感受 --》利用输出流:
        OutputStream os = s.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        //利用这个OutputStream就可以向外发送数据了,但是没有直接发送String的方法
        //所以我们又在OutputStream外面套了一个处理流:DataOutputStream
        dos.writeUTF("你好");
        //3.关闭流  +  关闭网络资源:
        dos.close();
        os.close();
        s.close();
    }
}

服务器:

public class TestServer {//服务器
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //1.创建套接字: 指定服务器的端口号
        ServerSocket ss = new ServerSocket(8888);
        //2.等着客户端发来的信息:
        Socket s = ss.accept();//阻塞方法:等待接收客户端的数据,什么时候接收到数据,什么时候程序继续向下执行。
        //accept()返回值为一个Socket,这个Socket其实就是客户端的Socket
        //接到这个Socket以后,客户端和服务器才真正产生了连接,才真正可以通信了
        //3.感受到的操作流:
        InputStream is = s.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        //4.读取客户端发来的数据:
        String str = dis.readUTF();
        System.out.println("客户端发来的数据为:"+str);
        
        //5.关闭流+关闭网络资源:
        dis.close();
        is.close();
        s.close();
        ss.close();
    }
}

测试:
(1)先开启客户端还是先开启服务器:先开服务器,再开启客户端
侧面验证:先开客户端:出错:

功能分解2:双向通信

服务器端:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TestServer {//服务器
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //1.创建套接字: 指定服务器的端口号
        ServerSocket ss = new ServerSocket(8888);
        //2.等着客户端发来的信息:
        Socket s = ss.accept();//阻塞方法:等待接收客户端的数据,什么时候接收到数据,什么时候程序继续向下执行。
        //accept()返回值为一个Socket,这个Socket其实就是客户端的Socket
        //接到这个Socket以后,客户端和服务器才真正产生了连接,才真正可以通信了
        //3.感受到的操作流:
        InputStream is = s.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        //4.读取客户端发来的数据:
        String str = dis.readUTF();
        System.out.println("客户端发来的数据为:"+str);
        //向客户端输出一句话:---》操作流---》输出流
        OutputStream os = s.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeUTF("你好,我是服务器端,我接受到你的请求了");
        //5.关闭流+关闭网络资源:
        dos.close();
        os.close();
        dis.close();
        is.close();
        s.close();
        ss.close();
    }
}

客户端:

import java.io.*;
import java.net.Socket;
public class TestClient {//客户端
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //1.创建套接字:指定服务器的ip和端口号:
        Socket s = new Socket("192.168.199.217",8888);
        //2.对于程序员来说,向外发送数据 感受 --》利用输出流:
        OutputStream os = s.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        //利用这个OutputStream就可以向外发送数据了,但是没有直接发送String的方法
        //所以我们又在OutputStream外面套了一个处理流:DataOutputStream
        dos.writeUTF("你好");
        //接收服务器端的回话--》利用输入流:
        InputStream is = s.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        String str = dis.readUTF();
        System.out.println("服务器端对我说:"+str);
        //3.关闭流  +  关闭网络资源:
        dis.close();
        is.close();
        dos.close();
        os.close();
        s.close();
    }
}

注意:关闭防火墙

功能分解3:对象流传送

封装的User类:

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 9050691344308365540L;
    private String name;
    private String pwd;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
    public User(String name, String pwd) {
        this.name = name;
        this.pwd = pwd;
    }
}

客户端:

import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TestClient {//客户端
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        //1.创建套接字:指定服务器的ip和端口号:
        Socket s = new Socket("192.168.199.217",8888);
        //录入用户的账号和密码:
        Scanner sc = new Scanner(System.in);
        System.out.println("请录入您的账号:");
        String name = sc.next();
        System.out.println("请录入您的密码:");
        String pwd = sc.next();
        //将账号和密码封装为一个User的对象:
        User user = new User(name,pwd);
        //2.对于程序员来说,向外发送数据 感受 --》利用输出流:
        OutputStream os = s.getOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(user);
        //接收服务器端的回话--》利用输入流:
        InputStream is = s.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        boolean b = dis.readBoolean();
        if(b){
            System.out.println("恭喜,登录成功");
        }else{
            System.out.println("对不起,登录失败");
        }
        //3.关闭流  +  关闭网络资源:
        dis.close();
        is.close();
        oos.close();
        os.close();
        s.close();
    }
}

服务器:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TestServer {//服务器
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //1.创建套接字: 指定服务器的端口号
        ServerSocket ss = new ServerSocket(8888);
        //2.等着客户端发来的信息:
        Socket s = ss.accept();//阻塞方法:等待接收客户端的数据,什么时候接收到数据,什么时候程序继续向下执行。
        //accept()返回值为一个Socket,这个Socket其实就是客户端的Socket
        //接到这个Socket以后,客户端和服务器才真正产生了连接,才真正可以通信了
        //3.感受到的操作流:
        InputStream is = s.getInputStream();
        ObjectInputStream ois = new ObjectInputStream(is);
        //4.读取客户端发来的数据:
        User user = (User)(ois.readObject());
        //对对象进行验证:
        boolean flag = false;
        if(user.getName().equals("娜娜")&&user.getPwd().equals("123123")){
            flag = true;
        }
        //向客户端输出结果:---》操作流---》输出流
        OutputStream os = s.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeBoolean(flag);
        //5.关闭流+关闭网络资源:
        dos.close();
        os.close();
        ois.close();
        is.close();
        s.close();
        ss.close();
    }
}
功能分解4:加入完整的处理异常方式

服务器端:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TestServer {//服务器
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        //1.创建套接字: 指定服务器的端口号
        ServerSocket ss = null;
        Socket s = null;
        InputStream is = null;
        ObjectInputStream ois = null;
        OutputStream os = null;
        DataOutputStream dos = null;
        try {
            ss = new ServerSocket(8888);
            //2.等着客户端发来的信息:
            s = ss.accept();//阻塞方法:等待接收客户端的数据,什么时候接收到数据,什么时候程序继续向下执行。
            //accept()返回值为一个Socket,这个Socket其实就是客户端的Socket
            //接到这个Socket以后,客户端和服务器才真正产生了连接,才真正可以通信了
            //3.感受到的操作流:
            is = s.getInputStream();
            ois = new ObjectInputStream(is);
            //4.读取客户端发来的数据:
            User user = (User)(ois.readObject());
            //对对象进行验证:
            boolean flag = false;
            if(user.getName().equals("娜娜")&&user.getPwd().equals("123123")){
                flag = true;
            }
            //向客户端输出结果:---》操作流---》输出流
            os = s.getOutputStream();
            dos = new DataOutputStream(os);
            dos.writeBoolean(flag);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            //5.关闭流+关闭网络资源:
            try {
                if(dos!=null){
                    dos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(os!=null){
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(ois!=null){
                    ois.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(is!=null){
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(s!=null){
                    s.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(ss!=null){
                    ss.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        
    }
}

客户端:

import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class TestClient {//客户端
    //这是一个main方法,是程序的入口:
    public static void main(String[] args){
        //1.创建套接字:指定服务器的ip和端口号:
        Socket s = null;
        OutputStream os = null;
        ObjectOutputStream oos = null;
        InputStream is = null;
        DataInputStream dis = null;
        try {
            s = new Socket("192.168.199.217",8888);
            //录入用户的账号和密码:
            Scanner sc = new Scanner(System.in);
            System.out.println("请录入您的账号:");
            String name = sc.next();
            System.out.println("请录入您的密码:");
            String pwd = sc.next();
            //将账号和密码封装为一个User的对象:
            User user = new User(name,pwd);
            //2.对于程序员来说,向外发送数据 感受 --》利用输出流:
            os = s.getOutputStream();
            oos = new ObjectOutputStream(os);
            oos.writeObject(user);
            //接收服务器端的回话--》利用输入流:
            is = s.getInputStream();
            dis = new DataInputStream(is);
            boolean b = dis.readBoolean();
            if(b){
                System.out.println("恭喜,登录成功");
            }else{
                System.out.println("对不起,登录失败");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            //3.关闭流  +  关闭网络资源:
            try {
                if(dis!=null){
                    dis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(is!=null){
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(oos!=null){
                    oos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(os!=null){
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(s!=null){
                    s.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
功能分解5:多线程接收用户请求

遗留问题:服务器针对一个请求服务,之后服务器就关闭了(程序自然结束了)

现在需要解决:服务器必须一直在监听 ,一直开着,等待客户端的请求

在当前代码中,客户端不用动了

更改服务器代码:

import java.io.*;
import java.net.Socket;
public class ServerThread extends Thread {//线程:专门处理客户端的请求
    InputStream is = null;
    ObjectInputStream ois = null;
    OutputStream os = null;
    DataOutputStream dos = null;
    Socket s = null;
    public ServerThread(Socket s){
        this.s = s;
    }
    @Override
    public void run() {
        try{
            //2.等着客户端发来的信息:
            is = s.getInputStream();
            ois = new ObjectInputStream(is);
            //4.读取客户端发来的数据:
            User user = (User)(ois.readObject());
            //对对象进行验证:
            boolean flag = false;
            if(user.getName().equals("娜娜")&&user.getPwd().equals("123123")){
                flag = true;
            }
            //向客户端输出结果:---》操作流---》输出流
            os = s.getOutputStream();
            dos = new DataOutputStream(os);
            dos.writeBoolean(flag);
        }catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            try {
                if(dos!=null){
                    dos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(os!=null){
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(ois!=null){
                    ois.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(is!=null){
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TestServer {//服务器
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        System.out.println("服务器启动了");
        //1.创建套接字: 指定服务器的端口号
        ServerSocket ss = null;
        Socket s = null;
        int count = 0;//定义一个计数器,用来计数  客户端的请求
        try {
            ss = new ServerSocket(8888);
            while(true){//加入死循环,服务器一直监听客户端是否发送数据
                s = ss.accept();//阻塞方法:等待接收客户端的数据,什么时候接收到数据,什么时候程序继续向下执行。
                //每次过来的客户端的请求 靠 线程处理:
                new ServerThread(s).start();
                count++;
                //输入请求的客户端的信息:
                System.out.println("当前是第"+count+"个用户访问我们的服务器,对应的用户是:"+s.getInetAddress());
            }
        } catch (IOException  e) {
            e.printStackTrace();
        }
    }
}

基于UDP的网络编程

TCP:

客户端:Socket 程序感受到的 使用流 :输出流
服务器端: ServerSocket —>Socket 程序感受到的 使用流 :输入流
(客户端和服务器端地位不平等。)

UDP:
发送方:DatagramSocket 发送:数据包 DatagramPacket
接收方:DatagramSocket 接收:数据包 DatagramPacket
(发送方和接收方的地址是平等的。)

UDP案例: 完成网站的咨询聊天

功能分解1:单向通信

发送方:

import java.io.IOException;
import java.net.*;
public class TestSend {//发送方:
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        System.out.println("学生上线。。。");
        //1.准备套接字: 指定发送方的端口号
        DatagramSocket ds = new DatagramSocket(8888);
        //2.准备数据包
        String str = "你好";
        byte[] bytes = str.getBytes();
        /*
        需要四个参数:
        1.指的是传送数据转为字节数组
        2.字节数组的长度
        3.封装接收方的ip
        4.指定接收方的端口号
         */
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("localhost"),9999);
        //发送:
        ds.send(dp);
        //关闭资源
        ds.close();
    }
}

接收方:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class TestReceive {//接收方
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        System.out.println("老师上线了。。");
        //1.创建套接字:指定接收方的端口
        DatagramSocket ds = new DatagramSocket(9999);
        //2.有一个空的数据包,打算用来接收  对方传过来的数据包:
        byte[] b = new byte[1024];
        DatagramPacket dp = new DatagramPacket(b,b.length);
        //3.接收对方的数据包,然后放入我们的dp数据包中填充
        ds.receive(dp);//接收完以后 dp里面就填充好内容了
        //4.取出数据:
        byte[] data = dp.getData();
        String s = new String(data,0,dp.getLength());//dp.getLength()数组包中的有效长度
        System.out.println("学生对我说:"+s);
        //5.关闭资源:
        ds.close();
    }
}
功能分解2:双向通信

发送方:

import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class TestSend {//发送方:
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        System.out.println("学生上线。。。");
        //1.准备套接字: 指定发送方的端口号
        DatagramSocket ds = new DatagramSocket(8888);
        //2.准备数据包
        Scanner sc = new Scanner(System.in);
        System.out.print("学生:");
        String str = sc.next();
        byte[] bytes = str.getBytes();
        /*
        需要四个参数:
        1.指的是传送数据转为Z字节数组
        2.字节数组的长度
        3.封装接收方的ip
        4.指定接收方的端口号
         */
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("localhost"),9999);
        //发送:
        ds.send(dp);
        //接收老师发送回来的信息:
        byte[] b = new byte[1024];
        DatagramPacket dp2 = new DatagramPacket(b,b.length);
        ds.receive(dp2);//接收完以后 dp2里面就填充好内容了
        //取出数据:
        byte[] data = dp2.getData();
        String s = new String(data,0,dp2.getLength());//dp.getLength()数组包中的有效长度
        System.out.println("老师对我说:"+s);
        //关闭资源
        ds.close();
    }
}

接收方:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;
public class TestReceive {//接收方
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws IOException {
        System.out.println("老师上线了。。");
        //1.创建套接字:指定接收方的端口
        DatagramSocket ds = new DatagramSocket(9999);
        //2.有一个空的数据包,打算用来接收  对方传过来的数据包:
        byte[] b = new byte[1024];
        DatagramPacket dp = new DatagramPacket(b,b.length);
        //3.接收对方的数据包,然后放入我们的dp数据包中填充
        ds.receive(dp);//接收完以后 dp里面就填充好内容了
        //4.取出数据:
        byte[] data = dp.getData();
        String s = new String(data,0,dp.getLength());//dp.getLength()数组包中的有效长度
        System.out.println("学生对我说:"+s);
        //老师进行回复:
        Scanner sc = new Scanner(System.in);
        System.out.print("老师:");
        String str = sc.next();
        byte[] bytes = str.getBytes();
        //封装数据,并且指定学生的ip和端口号
        DatagramPacket dp2 = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("localhost"),8888);
        //发送:
        ds.send(dp2);
        //5.关闭资源:
        ds.close();
    }
}
功能分解3:加入完整的处理异常方式

发送方:

import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class TestSend {//发送方:
    //这是一个main方法,是程序的入口:
    public static void main(String[] args)  {
        System.out.println("学生上线。。。");
        //1.准备套接字: 指定发送方的端口号
        DatagramSocket ds = null;
        try {
            ds = new DatagramSocket(8888);
            //2.准备数据包
            Scanner sc = new Scanner(System.in);
            System.out.print("学生:");
            String str = sc.next();
            byte[] bytes = str.getBytes();
        /*
        需要四个参数:
        1.指的是传送数据转为Z字节数组
        2.字节数组的长度
        3.封装接收方的ip
        4.指定接收方的端口号
         */
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("localhost"),9999);
            //发送:
            ds.send(dp);
            //接收老师发送回来的信息:
            byte[] b = new byte[1024];
            DatagramPacket dp2 = new DatagramPacket(b,b.length);
            ds.receive(dp2);//接收完以后 dp2里面就填充好内容了
            //取出数据:
            byte[] data = dp2.getData();
            String s = new String(data,0,dp2.getLength());//dp.getLength()数组包中的有效长度
            System.out.println("老师对我说:"+s);
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            ds.close();
        }
    }
}

接收方:

import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class TestReceive {//接收方
    //这是一个main方法,是程序的入口:
    public static void main(String[] args){
        System.out.println("老师上线了。。");
        //1.创建套接字:指定接收方的端口
        DatagramSocket ds = null;
        try {
            ds = new DatagramSocket(9999);
            //2.有一个空的数据包,打算用来接收  对方传过来的数据包:
            byte[] b = new byte[1024];
            DatagramPacket dp = new DatagramPacket(b,b.length);
            //3.接收对方的数据包,然后放入我们的dp数据包中填充
            ds.receive(dp);//接收完以后 dp里面就填充好内容了
            //4.取出数据:
            byte[] data = dp.getData();
            String s = new String(data,0,dp.getLength());//dp.getLength()数组包中的有效长度
            System.out.println("学生对我说:"+s);
            //老师进行回复:
            Scanner sc = new Scanner(System.in);
            System.out.print("老师:");
            String str = sc.next();
            byte[] bytes = str.getBytes();
            //封装数据,并且指定学生的ip和端口号
            DatagramPacket dp2 = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("localhost"),8888);
            //发送:
            ds.send(dp2);
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //5.关闭资源:
            ds.close();
        }
    }
}
功能分解4:正常通信

发送方:

import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class TestSend {//发送方:
    //这是一个main方法,是程序的入口:
    public static void main(String[] args)  {
        System.out.println("学生上线。。。");
        //1.准备套接字: 指定发送方的端口号
        DatagramSocket ds = null;
        try {
            ds = new DatagramSocket(8888);
            while(true){
                //2.准备数据包
                Scanner sc = new Scanner(System.in);
                System.out.print("学生:");
                String str = sc.next();
                byte[] bytes = str.getBytes();
        /*
        需要四个参数:
        1.指的是传送数据转为Z字节数组
        2.字节数组的长度
        3.封装接收方的ip
        4.指定接收方的端口号
         */
                DatagramPacket dp = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("localhost"),9999);
                //发送:
                ds.send(dp);
                if(str.equals("byebye")){
                    System.out.println("学生下线。。");
                    break;
                }
                //接收老师发送回来的信息:
                byte[] b = new byte[1024];
                DatagramPacket dp2 = new DatagramPacket(b,b.length);
                ds.receive(dp2);//接收完以后 dp2里面就填充好内容了
                //取出数据:
                byte[] data = dp2.getData();
                String s = new String(data,0,dp2.getLength());//dp.getLength()数组包中的有效长度
                System.out.println("老师对我说:"+s);
            }
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            ds.close();
        }
    }
}

接收方:

import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class TestReceive {//接收方
    //这是一个main方法,是程序的入口:
    public static void main(String[] args){
        System.out.println("老师上线了。。");
        //1.创建套接字:指定接收方的端口
        DatagramSocket ds = null;
        try {
            ds = new DatagramSocket(9999);
            while(true){
                //2.有一个空的数据包,打算用来接收  对方传过来的数据包:
                byte[] b = new byte[1024];
                DatagramPacket dp = new DatagramPacket(b,b.length);
                //3.接收对方的数据包,然后放入我们的dp数据包中填充
                ds.receive(dp);//接收完以后 dp里面就填充好内容了
                //4.取出数据:
                byte[] data = dp.getData();
                String s = new String(data,0,dp.getLength());//dp.getLength()数组包中的有效长度
                System.out.println("学生对我说:"+s);
                if(s.equals("byebye")){
                    System.out.println("学生已经下线了,老师也下线。。。");
                    break;
                }
                //老师进行回复:
                Scanner sc = new Scanner(System.in);
                System.out.print("老师:");
                String str = sc.next();
                byte[] bytes = str.getBytes();
                //封装数据,并且指定学生的ip和端口号
                DatagramPacket dp2 = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("localhost"),8888);
                //发送:
                ds.send(dp2);
            }
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //5.关闭资源:
            ds.close();
        }
    }
}

五、Junit_注解_枚举

Junit单元测试

引入

【1】软件测试的目的:
软件测试的目的是在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。
【2】测试分类:
(1)黑盒测试:
软件的黑盒测试意味着测试要在软件的接口处进行。这种方法是把测试对象看做一个黑盒子,测试人员完全不考虑程序内部的逻辑结构和内部特性,只依据程序的需求规格说明书,检查程序的功能是否符合它的功能说明。因此黑盒测试又叫功能测试。
(2)白盒测试:—》Junit属于白盒测试。
软件的白盒测试是对软件的过程性细节做细致的检查。这种方法是把测试对象看做一个打开的盒子,它允许测试人员利用程序内部的逻辑结构及有关信息,设计或选择测试用例,对程序的所有逻辑路径进行测试,通过在不同点检查程序状态,确定实际状态是否与预期的状态一致。因此白盒测试又称为结构测试。
在这里插入图片描述

没有Junit如何测试

在没有使用Junit的时候,缺点:
(1)测试一定走main方法,是程序的入口,main方法的格式必须不能写错。
(2)要是在同一个main方法中测试的话,那么不需要测试的东西必须注释掉。
(3)测试逻辑如果分开的话,需要定义多个测试类,麻烦。
(4)业务逻辑和测试代码,都混淆了。

public class Calculator {
    //加法:
    public int add(int a,int b){
        return a+b;
    }
    //减法:
    public int sub(int a,int b){
        return a-b;
    }
}
public class Test {
    public static void main(String[] args) {
        //测试加法:
        Calculator cal = new Calculator();
        int result = cal.add(10, 20);
        System.out.println(result);
        //测试减法:
       /* int result = cal.sub(30, 10);
        System.out.println(result);*/
    }
}
public class Test02 {
    public static void main(String[] args) {
        Calculator cal = new Calculator();
        //测试减法:
        int result = cal.sub(30, 10);
        System.out.println(result);
    }
}
Junit的使用

【1】一般测试和业务做一个分离,分离为不同的包:
建议起名:公司域名倒着写+test
以后测试类就单独放在这个包下
【2】测试类的名字:****Test —>见名知意
【3】测试方法的定义–》这个方法可以独立运行,不依托于main方法
建议:
名字:testAdd() testSub() 见名知意
参数:无参
返回值:void

【4】测试方法定义完以后,不能直接就独立运行了,必须要在方法前加入一个注解: @Test
【5】导入Junit的依赖的环境:

    //测试add方法
    @Test
    public void testAdd(){
        System.out.println("测试add方法");
        Calculator cal = new Calculator();
        int result = cal.add(10, 30);
        System.out.println(result);
    }
    //测试sub方法
    @Test
    public void testSub(){
        System.out.println("测试sub方法");
        Calculator cal = new Calculator();
        int result = cal.sub(10, 30);
        System.out.println(result);
    }

【6】判定结果:
绿色:正常结果
红色:出现异常

【7】即使出现绿色效果,也不意味着你的测试就通过了,因为代码中逻辑也可能出现问题,这种情况怎么解决呢?
加入断言

    //测试add方法
    @Test
    public void testAdd(){
        System.out.println("测试add方法");
        Calculator cal = new Calculator();
        int result = cal.add(10, 30);
        //System.out.println(result);--》程序的运行结果可以不关注
        //加入断言:预测一下结果,判断一下我预测的结果和 实际的结果是否一致:
        Assert.assertEquals(40,result);//第一个参数:预测结果  第二个参数:实际结果
    }
    //测试sub方法
    @Test
    public void testSub(){
        System.out.println("测试sub方法");
        Calculator cal = new Calculator();
        int result = cal.sub(10, 30);
        System.out.println(result);
    }
@Before,@After

@Before:
某一个方法中,加入了@Before注解以后,那么这个方法中的功能会在测试方法执行前先执行
一般会在@Beforer修饰的那个方法中加入:加入一些申请资源的代码:申请数据库资源,申请IO资源,申请网络资源。。。

@After:
某一个方法中,加入了@After注解以后,那么这个方法中的功能会在测试方法执行后先执行
一般会在@After修饰的那个方法中加入:加入释放资源的代码:释放数据库资源,释放IO资源,释放网络资源

    @Before
    public void init(){
        System.out.println("方法执行开始了。。。");
    }
    @After
    public void close(){
        System.out.println("方法执行结束了。。。");
    }
    //测试add方法
    @Test
    public void testAdd(){
        System.out.println("测试add方法");
        Calculator cal = new Calculator();
        int result = cal.add(10, 30);
        //System.out.println(result);--》程序的运行结果可以不关注
        //加入断言:预测一下结果,判断一下我预测的结果和 实际的结果是否一致:
        Assert.assertEquals(40,result);//第一个参数:预测结果  第二个参数:实际结果
    }
    //测试sub方法
    @Test
    public void testSub(){
        System.out.println("测试sub方法");
        Calculator cal = new Calculator();
        int result = cal.sub(10, 30);
        System.out.println(result);
    }
}

注解

引入

【1】历史:
JDK5.0 新增 — 注解(Annotation),也叫元数据

【2】什么是注解?

注解其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用注解,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

使用注解时要在其前面增加@符号,并把该注解当成一个修饰符使用。用于修饰它支持的程序元素。

【3】注解的重要性:

Annotation 可以像修饰符一样被使用,可用于修饰包,类,构造器,方法,成员变量,参数,局部变量的声明,这些信息被保存在Annotation的"name=value"对中。在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/ArIdroid中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。未来的开发模式都是基于注解的,JPA(java的持久化API)是基于注解的,Spring2.5以. E都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上可以说 :框架=注解+反射+设计模式。

Junit注解

@Test
@Before
@After

    @Before
    public void init(){
        System.out.println("方法执行开始了。。。");
    }
    @After
    public void close(){
        System.out.println("方法执行结束了。。。");
    }
    @Test
    public void testAdd(){
        System.out.println("测试add方法");
        Calculator cal = new Calculator();
        int result = cal.add(10, 30);
        Assert.assertEquals(40,result);//第一个参数:预测结果  第二个参数:实际结果
    }
文档相关的注解

说明注释允许你在程序中嵌入关于程序的信息。你可以使用 javadoc 工具软件来生成信息,并输出到HTML文件中。
说明注释,使你更加方便的记录你的程序信息。
文档注解我们一般使用在文档注释中,配合javadoc工具
javadoc 工具软件识别以下标签:
在这里插入图片描述
其中注意:

Ø @param @return和@exception这三个标记都是只用于方法的。

Ø @param的格式要求: @param 形参名 形参类型 形参说明

Ø @return的格式要求: @return 返回值类型返回值说明,如果方法的返回值类型是void就不能写

Ø @exception的格式要求: @exception 异常类型异常说明

Ø @param和@exception可以并列多个

JDK内置的3个注解
@Override:限定重写父类方法,该注解只能用于方法



public class Person {
    public void eat(){
        System.out.println("父类eat..");
    }
}


public class Student extends Person {
    /*
    @Override的作用:限定重写的方法,只要重写方法有问题,就有错误提示。
     */
    @Override
    public void eat(){
        System.out.println("子类eat..");
    }
}



@Deprecated:用于表示所修饰的元素(,方法,构造器,属性等)已过时。通常是因为所修饰的结构危险或存在更好的选择



public class Student extends Person {
    /*
    @Override的作用:限定重写的方法,只要重写方法有问题,就有错误提示。
     */
    @Override
    public void eat(){
        System.out.println("子类eat..");
    }
    /*
    在方法前加入@Deprecated,这个方法就会变成一个废弃方法/过期方法/过时方法
     */
    @Deprecated
    public void study(){
        System.out.println("学习。。");
    }
}



在这里插入图片描述

@SuppressWarnings:抑制编译器警告



public class Test02 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        @SuppressWarnings("unused")
        int age = 10;
        
        int num = 10;
        System.out.println(num);
        @SuppressWarnings({"unused","rwatypes"})
        ArrayList al = new ArrayList();
    }
}
实现替代配置文件功能的注解
在servlet3.0之前的配置:


package com.bjsxt.servlet;

import javax.servlet.*;
import java.io.IOException;

public class HelloServlet implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

   
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service方法被调用了...");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--配置Servlet-->
    <!--配置Servlet的信息-->
    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.bjsxt.servlet.HelloServlet</servlet-class>
    </servlet>
    <!--配置Servlet的映射路径-->
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <!--http://localhost:8080/01-hello-servlet/hello-->
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>
在servlet3.0之后使用注解:替代配置文件。



package com.bjsxt.servlet;

import javax.servlet.*;
import java.io.IOException;

@WebServlet("/hello")
public class HelloServlet implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    /**
    * 用于提供服务, 接收请求, 处理响应
    *
    * @param servletRequest
    * @param servletResponse
    * @throws ServletException
    * @throws IOException
    */
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service方法被调用了...");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}
自定义注解

【1】自定义注解使用很少,一般情况下都是用现成的注解。
【2】如何自定义注解:

发现定义的注解的声明使用的关键字:@interface,跟接口没有一点关系。
【3】注解的内部:
以@SuppressWarnings为例,发现内部:

这value是属性还是方法?
答案:看上去是无参数方法,实际上理解为一个成员变量,一个属性
无参数方法名字–》成员变量的名字
无参数方法的返回值–》成员变量的类型
这个参数叫 配置参数

无参数方法的类型:基本数据类型(八种),String,枚举,注解类型,还可以是以上类型对应的数组。

PS:注意:如果只有一个成员变量的话,名字尽量叫value。

【4】使用注解:

(1)使用注解的话,如果你定义了配置参数,就必须给配置参数进行赋值操作:

@MyAnnotation(value={"abc","def","hij"})
public class Person {
}

(2)如果只有一个参数,并且这个参数的名字为value的话,那么value=可以省略不写。

@MyAnnotation({"abc","def","hij"})
public class Person {
}

(3)如果你给配置参数设置默认的值了,那么使用的时候可以无需传值:

public @interface MyAnnotation2 {
    String value() default "abc";
}

使用:

@MyAnnotation2
@MyAnnotation({"abc","def","hij"})
public class Person {
}

(4)一个注解的内部是可以不定义配置参数的:

public @interface MyAnnotation3 {
}

内部没有定义配置参数的注解–》可以叫做标记

内部定义配置参数的注解–》元数据

【5】注解的使用:

元注解

元注解是用于修饰其它注解的注解。

举例:
在这里插入图片描述
JDK5.0提供了四种元注解:Retention, Target, Documented, Inherited

Retention

@Retention:用于修饰注解,用于指定修饰的那个注解的生命周期,@Rentention包含一个RetentionPolicy枚举类型的成员变量,使用@Rentention时必须为该value成员变量指定值:

➢RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释,在.class文件中不会保留注解信息

案例:
在这里插入图片描述
在这里插入图片描述

反编译查看字节码文件:发现字节码文件中没有MyAnnotation这个注解:
在这里插入图片描述

➢RetentionPolicy.CLASS:在class文件中有效(即class保留),保留在.class文件中,但是当运行Java程序时,他就不会继续加载了,不会保留在内存中,JVM不会保留注解。如果注解没有加Retention元注解,那么相当于默认的注解就是这种状态。

案例:
在这里插入图片描述
在这里插入图片描述

反编译看字节码文件,字节码文件中带有MyAnnotation注解:

在这里插入图片描述

➢RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java程序时,JVM会保留注释,加载在内存中了,那么程序可以通过反射获取该注释。

Target

用于修饰注解的注解,用于指定被修饰的注解能用于修饰哪些程序元素。@Target也包含一个名为value的成员变量。

案例:

@Target({TYPE,CONSTRUCTOR,METHOD})
public @interface MyAnnotation4 {
}

使用:
在这里插入图片描述

Documented(很少)

用于指定被该元注解修饰的注解类将被javadoc工具提取成文档。默认情况下,javadoc是 不包括注解的,但是加上了这个注解生成的文档中就会带着注解了

案例:

如果:Documented注解修饰了Deprecated注解,
在这里插入图片描述

那么Deprecated注解就会在javadoc提取的时候,提取到API中:

在这里插入图片描述

Inherited(极少)

被它修饰的Annotation将具有继承性。如果某个类使用了被

@Inherited修饰的Annotation,则其子类将自动具有该注解。

案例:

注解:如果MyAnno注解使用了@Inherited之后,就具备了继承性,那么相当于子类Student也使用了这个MyAnno
在这里插入图片描述

父类:
在这里插入图片描述

子类:
在这里插入图片描述

枚举

引入

自定义枚举类:(JDK1.5之前自定义枚举类)

 private final String seasonName ;//季节名字
    private final String seasonDesc ;//季节描述
    //利用构造器对属性进行赋值操作:
    //构造器私有化,外界不能调用这个构造器,只能Season内部自己调用
    private Season(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }
    //提供枚举类的有限的  确定的对象:
    public static final Season SPRING = new Season("春天","春暖花开");
    public static final Season SUMMER = new Season("夏天","烈日炎炎");
    public static final Season AUTUMN = new Season("秋天","硕果累累");
    public static final Season WINTER = new Season("冬天","冰天雪地");
    //额外因素:
    public String getSeasonName() {
        return seasonName;
    }
    public String getSeasonDesc() {
        return seasonDesc;
    }
    //toString();
    @Override
    public String toString() {
        return "Season{" +
                "seasonName='" + seasonName + '\'' +
                ", seasonDesc='" + seasonDesc + '\'' +
                '}';
    }
Test
 public static void main(String[] args) {
        Season summer = Season.SUMMER;
        System.out.println(summer/*.toString()*/);
        System.out.println(summer.getSeasonName());
    }

JDK1.5以后使用enum关键字创建枚举类

public enum Season {
    //提供枚举类的有限的  确定的对象:--->enum枚举类要求对象(常量)必须放在最开始位置
    //多个对象之间用,进行连接,最后一个对象后面用;结束
    SPRING("春天","春暖花开"),
    SUMMER("夏天","烈日炎炎"),
    AUTUMN("秋天","硕果累累"),
    WINTER("冬天","冰天雪地");
    //属性:
    private final String seasonName ;//季节名字
    private final String seasonDesc ;//季节描述
    //利用构造器对属性进行赋值操作:
    //构造器私有化,外界不能调用这个构造器,只能Season内部自己调用
    private Season(String seasonName, String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }
    //额外因素:
    public String getSeasonName() {
        return seasonName;
    }
    public String getSeasonDesc() {
        return seasonDesc;
    }
    //toString();
    @Override
    public String toString() {
        return "Season{" +
                "seasonName='" + seasonName + '\'' +
                ", seasonDesc='" + seasonDesc + '\'' +
                '}';
    }
}
 public static void main(String[] args) {
        Season winter = Season.WINTER;
        System.out.println(winter);
        //enum关键字对应的枚举类的上层父类是 :java.lang.Enum
        //但是我们自定义的枚举类的上层父类:Object
        System.out.println(Season.class.getSuperclass().getName());//java.lang.Enum
    }

在源码中经常看到别人定义的枚举类形态:

public enum Season {
    SPRING,
    SUMMER,
    AUTUMN,
    WINTER;
}

为什么这么简单:因为这个枚举类底层没有属性,属性,构造器,toString,get方法都删掉不写了,然后案例来说应该
写为:SPRING() 现在连()可以省略 就变成 SPRING
看到的形态就剩:常量名(对象名)

案例:
Thread中的枚举类:State

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,
        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,
        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,
        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,
        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,
        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

Enum类常用方法

        //用enum关键字创建的Season枚举类上面的父类是:java.lang.Enum,常用方法子类Season可以直接拿过来使用:
        //toString();--->获取对象的名字
        Season autumn = Season.AUTUMN;
        System.out.println(autumn/*.toString()*/);//AUTUMN
        System.out.println("--------------------");
        //values:返回枚举类对象的数组
        Season[] values = Season.values();
        for(Season s:values){
            System.out.println(s/*.toString()*/);
        }
        System.out.println("--------------------");
        //valueOf:通过对象名字获取这个枚举对象
        //注意:对象的名字必须传正确,否则抛出异常
        Season autumn1 = Season.valueOf("AUTUMN");
        System.out.println(autumn1);

枚举类实现接口

定义一个接口:

public interface TestInterface {
    void show();
}

枚举类实现接口,并且重写show方法:

public enum Season implements TestInterface {
    SPRING,
    SUMMER,
    AUTUMN,
    WINTER;
    @Override
    public void show() {
        System.out.println("这是Season....");
    }
}

测试类:

public class Test {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        Season autumn = Season.AUTUMN;
        autumn.show();
        Season summer = Season.SUMMER;
        summer.show();
    }
}

上面发现所有的枚举对象,调用这个show方法的时候走的都是同一个方法,结果都一样:

但是现在我想要:不同的对象 调用的show方法也不同:

import java.sql.SQLOutput;
public enum Season implements TestInterface {
    SPRING{
        @Override
        public void show() {
            System.out.println("这是春天。。。");
        }
    },
    SUMMER{
        @Override
        public void show() {
            System.out.println("这是夏天。。");
        }
    },
    AUTUMN{
        @Override
        public void show() {
            System.out.println("这是秋天");
        }
    },
    WINTER{
        @Override
        public void show() {
            System.out.println("这是冬天");
        }
    };
    /*@Override
    public void show() {
        System.out.println("这是Season....");
    }*/
}

测试类:

public class Test {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        Season autumn = Season.AUTUMN;
        autumn.show();
        Season summer = Season.SUMMER;
        summer.show();
    }
}

在这里插入图片描述

实际应用

public class Person {
    //属性:
    private int age;
    private String name;
    private Gender sex;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Gender getSex() {
        return sex;
    }
    public void setSex(Gender sex) {
        this.sex = sex;
    }
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}
public enum Gender {,;
}
public class Test {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        Person p = new Person();
        p.setAge(19);
        p.setName("lili");
        p.setSex(Gender.);//传入枚举类Gender的对象:-->在入口处对参数进行了限制
        System.out.println(p);
    }
}

还可以通过枚举结合switch处理:

public class Test02 {
    public static void main(String[] args) {
        Gender sex = Gender.;
        //switch后面的()中可以传入枚举类型
        //switch后面的():int,short,byte,char,String ,枚举
        switch (sex){
            case:
                System.out.println("是个女孩");
                break;
            case:
                System.out.println("是个男孩");
                break;
        }
    }
}

六、反射

美团外卖 —>付款 —》要么用微信支付 要么用支付宝支付

在这里插入图片描述
在这里插入图片描述
多态确实可以提高代码的扩展性,但是:扩展性没有达到最好。
怎么没有达到最好:上面的分支,还是需要手动的删除或者添加。
解决办法:反射机制
利用反射实现上述功能:
在这里插入图片描述

概念体会反射

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

在编译后产生字节码文件的时候,类加载器子系统通过二进制字节流,负责从文件系统加载class文件。
在执行程序(java.exe)时候,将字节码文件读入JVM中—>这个过程叫做类的加载。然后在内存中对应创建一个java.lang.Class对象–>这个对象会被放入字节码信息中,这个Class对象,就对应加载那个字节码信息,这个对象将被作为程序访问方法区中的这个类的各种数据的外部接口。
所以:我们可以通过这个对象看到类的结构,这个对象就好像是一面镜子,透过镜子看到类的各种信息,我们形象的称之为反射
这种“看透”class的能力(the ability of the program to examine itself)被称为introspection(内省、内观、反省)。Reflection和introspection是常被并提的两个术语。

说明:在运行期间,如果我们要产生某个类的对象,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。
如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象。

补充:
动态语膏vs静态语言
1、动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以
被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运
行时代码可以根据某些条件改变自身结构。
主要动态语言: Object-C、 C#、JavaScript、 PHP、 Python、 Erlang 。
2、静态语言
与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、
C++。

所以Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动
态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。
Java的动态性让编程的时候更加灵活!

获取字节码信息的四种形式

在这里插入图片描述

可以作为Class类实例的种类

Class类的具体的实例:
(1)类:外部类,内部类
(2)接口
(3)注解
(4)数组
(5)基本数据类型
(6)void
在这里插入图片描述

获取运行时类的完整结构

补充完善上面提供的丰富的类
//作为一个父类
public class Person implements Serializable {
    //属性
    private int age;
    public String name;
    //方法
    private void eat(){
        System.out.println("Person---eat");
    }
    public void sleep(){
        System.out.println("Person---sleep");
    }
}
//Student作为子类
@MyAnnotation(value="hello")
public class Student extends Person implements MyInterface{
    //属性:
    private int sno;//学号
    double height;//身高
    protected double weight;//体重
    public double score;//成绩
    //方法:
    @MyAnnotation(value="himethod")
    public String showInfo(){
        return "我是一名三好学生";
    }
    public String showInfo(int a,int b){
        return "重载方法====我是一名三好学生";
    }
    private void work(){
        System.out.println("我以后会找工作--》成为码农  程序员 程序猿  程序媛");
    }
    void happy(){
        System.out.println("做人最重要的就是开心每一天");
    }
    protected int getSno(){
        return sno;
    }
    //构造器
    public Student(){
        System.out.println("空参构造器");
    }
    private Student(int sno){
        this.sno = sno;
    }
    Student(int sno,double weight){
        this.sno = sno;
        this.weight = weight;
    }
    protected Student(int sno,double height,double weight){
        this.sno = sno;
    }
    @Override
    @MyAnnotation(value="hellomyMethod")
    public void myMethod() {
        System.out.println("我重写了myMethod方法。。");
    }
    @Override
    public String toString() {
        return "Student{" +
                "sno=" + sno +
                ", height=" + height +
                ", weight=" + weight +
                ", score=" + score +
                '}';
    }
}
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
/*
@Target:定义当前注解能够修饰程序中的哪些元素
@Retention:定义注解的声明周期
 */
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value();//属性
}
public interface MyInterface {//自定义的接口
    //随便定义一个抽象方法:
    void myMethod();
}

获取构造器和创建对象
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test01 {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //获取字节码信息:
        Class cls = Student.class;
        //通过字节码信息可以获取构造器:
        //getConstructors只能获取当前运行时类的被public修饰的构造器
        Constructor[] c1 = cls.getConstructors();
        for(Constructor c:c1){
            System.out.println(c);
        }
        System.out.println("-------------------");
        //getDeclaredConstructors:获取运行时类的全部修饰符的构造器
        Constructor[] c2 = cls.getDeclaredConstructors();
        for(Constructor c:c2){
            System.out.println(c);
        }
        System.out.println("-------------------");
        //获取指定的构造器:
        //得到空构造器
        Constructor con1 = cls.getConstructor();
        System.out.println(con1);
        //得到两个参数的有参构造器:
        Constructor con2 = cls.getConstructor(double.class, double.class);
        System.out.println(con2);
        //得到一个参数的有参构造器:并且是private修饰的
        Constructor con3 = cls.getDeclaredConstructor(int.class);
        System.out.println(con3);
        //有了构造器以后我就可以创建对象:
        Object o1 = con1.newInstance();
        System.out.println(o1);
        Object o2 = con2.newInstance(180.5, 170.6);
        System.out.println(o2);
    }
}
获取属性和对属性进行赋值
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class Test02 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        //获取运行时类的字节码信息:
        Class cls = Student.class;
        //获取属性:
        //getFields:获取运行时类和父类中被public修饰的属性
        Field[] fields = cls.getFields();
        for(Field f:fields){
            System.out.println(f);
        }
        System.out.println("---------------------");
        //getDeclaredFields:获取运行时类中的所有属性
        Field[] declaredFields = cls.getDeclaredFields();
        for(Field f:declaredFields){
            System.out.println(f);
        }
        System.out.println("---------------------");
        //获取指定的属性:
        Field score = cls.getField("score");
        System.out.println(score);
        Field sno = cls.getDeclaredField("sno");
        System.out.println(sno);
        System.out.println("---------------------");
        //属性的具体结构:
        //获取修饰符
        /*int modifiers = sno.getModifiers();
        System.out.println(modifiers);
        System.out.println(Modifier.toString(modifiers));*/
        System.out.println(Modifier.toString(sno.getModifiers()));
        //获取属性的数据类型:
        Class clazz = sno.getType();
        System.out.println(clazz.getName());
        //获取属性的名字:
        String name = sno.getName();
        System.out.println(name);
        System.out.println("-------------------------------");
        //给属性赋值:(给属性设置值,必须要有对象)
        Field sco = cls.getField("score");
        Object obj = cls.newInstance();
        sco.set(obj,98);//给obj这个对象的score属性设置具体的值,这个值为98
        System.out.println(obj);
    }
}
获取方法和调用方法
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class Test03 {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        //获取字节码信息:
        Class cls = Student.class;
        //获取方法:
        //getMethods:获取运行时类的方法还有所有父类中的方法(被public修饰)
        Method[] methods = cls.getMethods();
        for(Method m:methods){
            System.out.println(m);
        }
        System.out.println("-----------------------");
        //getDeclaredMethods:获取运行时类中的所有方法:
        Method[] declaredMethods = cls.getDeclaredMethods();
        for(Method m:declaredMethods){
            System.out.println(m);
        }
        System.out.println("-----------------------");
        //获取指定的方法:
        Method showInfo1 = cls.getMethod("showInfo");
        System.out.println(showInfo1);
        Method showInfo2 = cls.getMethod("showInfo", int.class, int.class);
        System.out.println(showInfo2);
        Method work = cls.getDeclaredMethod("work",int.class);
        System.out.println(work);
        System.out.println("-----------------------");
        //获取方法的具体结构:
        /*
        @注解
        修饰符 返回值类型  方法名(参数列表) throws XXXXX{}
         */
        //名字:
        System.out.println(work.getName());
        //修饰符:
        int modifiers = work.getModifiers();
        System.out.println(Modifier.toString(modifiers));
        //返回值:
        System.out.println(work.getReturnType());
        //参数列表:
        Class[] parameterTypes = work.getParameterTypes();
        for(Class c:parameterTypes){
            System.out.println(c);
        }
        //获取注解:
        Method myMethod = cls.getMethod("myMethod");
        Annotation[] annotations = myMethod.getAnnotations();
        for(Annotation a:annotations){
            System.out.println(a);
        }
        //获取异常:
        Class[] exceptionTypes = myMethod.getExceptionTypes();
        for(Class c:exceptionTypes){
            System.out.println(c);
        }
        //调用方法:
        Object o = cls.newInstance();
        myMethod.invoke(o);//调用o对象的mymethod方法
        System.out.println(showInfo2.invoke(o,12,45));;
    }
}
获取类的接口,所在包,注解

import java.lang.annotation.Annotation;
public class Test04 {
    public static void main(String[] args) {
        //获取字节码信息:
        Class cls = Student.class;
        //获取运行时类的接口:
        Class[] interfaces = cls.getInterfaces();
        for(Class c:interfaces){
            System.out.println(c);
        }
        //得到父类的接口:
        //先得到父类的字节码信息:
        Class superclass = cls.getSuperclass();
        //得到接口:
        Class[] interfaces1 = superclass.getInterfaces();
        for(Class c:interfaces1){
            System.out.println(c);
        }
        //获取运行时类所在的包:
        Package aPackage = cls.getPackage();
        System.out.println(aPackage);
        System.out.println(aPackage.getName());
        //获取运行类的注解:
        Annotation[] annotations = cls.getAnnotations();
        for(Annotation a:annotations){
            System.out.println(a);
        }
    }
}

举报

相关推荐

JavaSE笔记11

JavaSE笔记10

JavaSE笔记03

JavaSE学习笔记(9)

javaSE 笔记 day02

JavaSE目录(尚硅谷学习笔记)

0 条评论