• 【高级语法篇】Java必备基础(思维导图+代码)



    在这里插入图片描述

    ⭐代码和思维导图详见仓库:Sivan_Xin的代码仓库—Java语法。如果觉得有帮助的话,可以帮作者点一个star⭐哦~

    🎉 关于Java语法部分的知识体系主要分三篇文章进行讲解:
    🎉【基础语法篇】【面向对象篇】【高级语法篇】
    🎉大家可以在日常的学习生活中进行参考,笔记内容并不是绝对权威,所以请保持自己独立思考的能力。

    本文共16000字,读完约需30分钟。

    • 思维导图以其中一张为例:在这里插入图片描述

    多线程

    基本概念

    • 程序、进程、线程
      程序:静态的代码块。
      进程:进程作为资源分配的单位,也就是运行起来的程序,存在生命周期。
      线程:进程进一步细化为线程,是程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程。每个进程有不同的内存区域,而线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器。

    多个线程可以对内存进行操作,这时就可能会出现一些问题。一个Java程序至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。

    并行与并发:
    并行:多个CPU同时执行多个任务。
    并发:一个CPU同时执行多个任务。比如:秒杀、多个人做同一件事。

    问:何时需要多线程?

    • 程序需要同时执行两个或多个任务。
    • 程序需要实现一些需要等待的任务,如用户输入、文件读写等。
    • 需要后台运行的程序。

    线程的创建和使用

    创建多线程方式一:使用Thread类。
    1. 创建一个继承Thread类的子类。
    2. 重写Thread类的run方法 -->将线程执行的操作声明在run中。
    3. 创建Thread类的子类的对象。
    4. 通过对象调用start方法。
    start方法的作用:1. 启动线程 2. 调用run方法。

    创建多线程方式二:实现Runnable接口。
    1. 实现Runnable接口
    2. 重写run()方法
    3. 创建实现接口的类的对象
    4. 使用Thread,将对象传入构造器中,new新的线程对象
    5. 使用线程对象调用start()方法

    • 两者比较
      开发中:优先选择实现Runnable接口的方式。
      原因:
      1. 没有类的单继承性的局限性
      2. Runnable更适合来处理多个线程有共享数据的情况。

    相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

    • Thread类中的常用方法
      1. start():启动当前线程;调用当前线程的run()
      2. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
      3. currentThread():静态方法,返回执行当前代码的线程
      4. getName():获取当前线程的名字
      5. setName():设置当前线程的名字
      6. yield():释放当前cpu的执行权
      7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
      8. stop():已过时。当执行此方法时,强制结束当前线程。
      9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
      10. isAlive():判断当前线程是否存活
    • 线程的调度

    时间片策略; 抢占式:高优先级抢占低优先级的线程。

    • 线程的优先级
    1. 线程的三大优先级
      MAX_PRIORITY:10

      MIN _PRIORITY:1

      NORM_PRIORITY:5 -->默认优先级
      高优先级的线程要抢占低优先级线程cpu的执行权。

    2. 如何获取和设置当前线程的优先级:
      getPriority():获取线程的优先级
      setPriority(int p):设置线程的优先级

    说明: 只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。

    线程的通信

    线程与线程之间可以进行通信交互。

    wait()方法:使当前线程阻塞,并将锁释放。与sleep不同,sleep只是将线程休眠,并没有释放锁。
    notify()方法:释放正在wait()的线程。
    notifyAll()方法:释放所有正在wait()的线程,按照优先级释放。

    • 注意
    1. 这三个方法只能出现在同步代码块或同步方法中。
    2. 调用者必须是同步代码块或同步方法中的同步监视器。
    3. 三者是定义在java.lang.Object类中的。

    线程的生命周期

    两张图片:
    在这里插入图片描述

    在这里插入图片描述

    Thread.State枚举类中定义了线程的几种状态:

    • 新建 :当一个Thread类或子类对象被声明创建时。
    • 就绪:处于新建状态的线程被start()后,进入线程队列等待CPU时间片,此时已经具备运行条件。
    • 运行:线程被调度,run()方法定义了线程的操作和功能。
    • 阻塞:线程被人为挂起或执行输入输出操作时。
    • 死亡:线程完成工作或线程被提前强制性中值。

    线程的同步

    • 为什么使用线程的同步?
      多个线程对账本的共享,会造成操作的不完整性,会破坏数据。
      Java中会通过同步机制,解决线程安全问题。

    synchronized

    • 方式一:同步代码块
     synchronized(同步监视器){
     //说明:1. 操作共享数据的代码,即为需要被同步的代码。
     //       2. 共享数据:多个线程共同操作的变量。
     //       3. 同步监视器(锁):任意一个类的对象,但是多个线程必须共用同一把锁。
     //     在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 方式二:同步方法
    1. 如果操作共享数据的代码完整的声明在一个方法中,不妨将此方法声明为同步的。
    2. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
    3. 非静态的同步方法,同步监视器是:this [使用Runnable]
      静态的同步方法,同步监视器是:当前类本身 [使用Thread]
    • 同步的好处与局限性
      同步解决了线程的安全问题,但是在操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。

    • 线程的死锁问题
      不同的线程分别占据对方需要的资源(同步监视器对象)不放弃,就形成了线程的死锁。出现死锁后,不会出现异常,不会出现提示,只是线程都处于阻塞状态,无法继续。

    Lock(锁)

    从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。ReentrantLock类实现了Lock接口,在实现线程安全的控制中,比较常用的是ReentrantLock。

    JDK5.0新增线程创建方式

    • 新增方式一:实现Callable接口
    1. 相比run()方法,可以有返回值。
    2. 方法可以抛出异常。
    3. 支持泛型的返回值。
    4. 需要借助FutureTask类,比如获取返回结果等。

    另外的,Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口。

    • 新增方式二:使用线程池
      背景:对于经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
      思路:提前创建好多个线程,放入线程池,使用时直接获取,使用完返回池中。
      好处:
    1. 提高响应速度(减少创建新线程所需的时间)
    2. 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
    3. 便于线程管理
    • 线程池相关API:
      ExecutorService类、Executors工具类、ThreadPoolExecutor类。

    推荐使用ThreadPoolExecutor类来创建线程池,避免使用ExecutorService类造成的OOM。

    Java集合框架

    概述

    Java 集合框架主要包括两种类型的容器:

    • 单列集合框架:Collection接口,在该接口下还有两个子接口ListSet
      其中,List存储有序、可重复元素(动态数组)。Set存储无序、不可重复元素。
    • 双列集合框架:Map,存储(key—value)映射。

    通用语句:ArrayList<E> objectName = new ArrayList<>;

    Collection接口

    Collection的使用

    • 常用API
      参考官方文档/代码即可。
    • 集合<——>数组
      集合—>数组:toArray方法。
      数组—>集合:Arrays.asList方法。官方文档中是这么介绍的:返回由指定数组支持的固定大小的列表。此方法与Collection.toArray()结合使用,作为基于数组和基于集合的API之间的桥梁。

    补充知识:

    • ArrayList,HashSet,HashMap等都可以用sout进行输出。因为在类的方法中重写了toString方法。
    • 可变形参
      语法:数据类型···变量名。
      Arrays.asList()方法中传入的是一个可变形参。表示传入的参数可以是一个或多个数/数组。

    iterator(迭代器)

    iterator可用于迭代实现了Collection接口的集合。

    • 迭代器执行原理
      我们这里可以把iterator对象看成一个指针。
      调用next() 1. 指针下移 2. 返回下移后的元素。
      调用hasNext() 往下看还有没有其他元素。

    补充:for:each循环遍历集合就是内部调用迭代器来实现的。

    子接口

    List接口

    List接口:存储有序的、可重复的数据——动态数组。
    接口的具体实现类:ArrayListLinkedList、Vector。

    • 常用API
      参考官方文档/代码即可。

    • LinkedList的源码分析

    1. 使用无参构造器LinkedList list = new LinkedList()—> 内部声明了Node类型的first和last属性。
    2. 添加操作,创建了一个Node对象,存入数据。
    • Vector的源码分析
      Vector的源码分析:jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
      在扩容方面,默认扩容为原来的数组长度的2倍。

    Set接口

    主要存储无序的、不可重复的数据,Set中没有新的方法,都是从Collection继承而来的。
    向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()。

    • 理解无序和不可重复
    1. 无序性:存储在数据的底层数组中并非按照数组的索引来添加,而是根据数据的哈希值决定。
    2. 不可重复性:保证添加相同的元素时,使用equals()判断不能返回true。

    Set的具体实现类:
    HashSet、LinkedHashSet、TreeSet

    • 具体实现类

    • HashSet
      Set的主要实现类,线程不安全,可以存储null值。基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。

    • LinkedHashSet
      HashSet的子类。在添加数据的同时,每个数据还维护了pre和next两个引用。LinkedHashSet的底层是一个数组+双向链表。意义和好处是LinkedHashSet中的元素顺序可以保证,也就是说遍历序和插入序是一致的

    • TreeSet
      向TreeSet中添加数据,要求是相同类的对象。TreeSet的底层使用红黑树。

    • TreeSet的两种排序方式
      自然排序(需要实现Comparable接口,重写compareTo()方法) 和 定制排序(构造器传参Comparator)
      如果使用定制排序,就向TreeSet的构造函数中传入一个Comparator对象。
    1. 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0。不再是equals()。
    2. 定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals()。

    Map

    Map存储双列数据(key—value)(以HashMap为例)
    key使用Set存储,无序,不能重复。——>存储自定义的类的对象时,要重写equals和hashCode。
    value使用Collection存储可重复,无序的数据。——>存储自定义的类的对象时,要重写equals方法。
    Entry用Set容器存储,无序,不可重复。调用put方法时放进去的就是Map.Entry对象。(key和value)

    • 具体实现类
    • HashMap:是Map的主要实现类,线程不安全,效率高。可以存储null的数据。
      • LinkedHashMap:在遍历map元素时,可以按照添加的顺序来遍历 。
    • TreeMap:保证按照添加的key—value进行排序,实现排序遍历。此时考虑key的自然排序和定制排序。底层使用红黑树。
    • Hashtable:作为古老的实现类,线程安全,效率低。不能存储null的key—value。
    • Properties:properties类继承于Hashtable类,实现了Map等接口。常用来处理配置文件。key和value都是String类型。配置文件后缀:properties。

    Map常用API
    可查阅文档/代码仓库。

    Collections工具类

    Collections类直接继承于Object接口,是集合类的一个工具类。这个类不能被实例化(私有构造器),所有的方法都是静态的(可通过类名调用)。包含有关集合操作的静态方法,可以实现对各种集合的搜索、排序、线程安全等。

    泛型

    概述

    • 泛型是什么?
      元素类型不确定,所以设计一个参数,这个参数叫做泛型。
      例如ArrayList< E>,< E>就是传入一个泛型的意思。
      总之,就是通过一个标识,影响方法的参数类型。
    • 使用泛型的作用?
      1.编译时会进行类型检查,保证数据的安全。
      2.避免了类型转换异常出现的情况。
    • 使用泛型的注意?
      1. 使用泛型,不可以用基本数据类型,必须用其包装类
      2. 泛型只是一个标签,不一定传入的都是基本数据类型的包装类,也可以是其他类。
      3. Java集合接口和集合类jdk1.5之后都被修改为带泛型的结构。
      4. 子类继承父类时,父类指明了泛型类型,子类实例化对象时不需要使用泛型。
      5. 子类继承父类时,父类没有指明泛型类型,子类实例化对象时需要使用泛型。
      6. 异常类不能是泛型的。
      7. 静态方法中不能使用泛型。
      8. 泛型的嵌套(HashMap构造迭代器):
        Map<String,Integer> map = new HashMap<>();
    
    Iterator <Map.Entry<String,Integer>> iterator = map.entrySet().iterator();
    
    while(iterator.hasNext()){
                Map.Entry<String, Integer> e = iterator.next();
                String key = e.getKey();
                Integer value = e.getValue();
                System.out.println(key + "----" + value);
            }      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    自定义泛型类、泛型方法、泛型继承

    • 自定义泛型程序举例:
    //定义带泛型的类
    public class Student<T> {
        int age;
        String name;
        T describe;
        public Student(int age, String name, T describe) {
            this.age = age;
            this.name = name;
            this.describe = describe;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    //1. 继承父类时,子类指定泛型的类型,此时子类不是泛型。例:
    public class Generic extends fatherGeneric<String>
    
    //接口类似。如果一个类实现的接口给出了具体的泛型,该类不需要写出泛型,反之该类需要给出泛型。
    
    class class -name <type-param-list> implements interface -name <type-arg-list> {
    
    //2. 继承父类时,子类没有指定泛型的类型,此时子类是泛型。
    public class Generic <T>extends Student<T>{
    
        //泛型类的构造器不用加<>
        public Generic(int age, String name, T describe) {
            super(age, name, describe);
        }
        
       
        //泛型方法:
        //泛型方法与所在类是否是泛型无关。
        //泛型方法可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
        public static <E> List toArray(E []e){
            List<E> list=new ArrayList<>();
            for(E e1 :e){
                list.add(e1);
            }
            return list;
        }
    
        //静态方法中,不能使用泛型。泛型是在实例化对象的时候(调用构造器)使用的,
        // 而静态方法在加载类的时候就优先加载了(静态优先,构造随后)。
        public static int Demo(T t){  //错误
    
        }
    
        public static void main(String[] args) {
            //实例化对象时,要使用泛型。
            Generic<String> generic=new Generic<>(12,"Wang","good");
            List<Integer> list=Generic.toArray(new Integer[]{1,2,3});
            System.out.println(list);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 继承中的泛型
    1. 虽然类A是类B的父类,但是G< A> 和G< B >二者不具备子父类关系,二者是并列关系。
      例如:
        List<Object> list1 = null;
        List<String> list2 = new ArrayList<>();
        //编译不通过
        // list1 = list2;
    
    • 1
    • 2
    • 3
    • 4
    1. 类A是类B的父类,A 和B 二者是子类和父类的关系。
      例如:
        List<String> list1 = null;
        ArrayList<String> list2 = null;
        //编译通过
        list1 = list2;
    
    • 1
    • 2
    • 3
    • 4

    泛型的通配符

    • 通配符
      类A是类B的父类,G< A>和G< B>是没有关系的,二者共同的父类是:G<?>
     //通配符的使用举例
           ArrayList<Object> list=new ArrayList<>();
           ArrayList<String> arrayList=new ArrayList<>();
           ArrayList<?> a=new ArrayList<>();
           //G<?>是G<A> G<B>的父类,故可以父类引用指向子类对象。
           a=list;
            //添加(写入):对于Arraylist<?>就不能向其内部添加数据。
            //查找(读取):对于Arraylist<?>可以读取数据,返回Object类型。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 有限制条件的通配符
      <? extends Person>
      表示?可以接受小于等于Person的类。(-无穷 ,Person],例如Student等…

      <? super Person>
      表示?可以接受大于等于Person的类。[Person,+无穷),例如Object等…

    • 添加操作和查找操作
      添加操作(以List.add为例)
      当执行添加操作时,只可以添加小于等于当前区间的子类。(添加Student、Person;不可以添加Object)
      查找操作(以List.get为例)
      当执行查找操作时,使用当前区间的最大类来接受值。(用Object类来接收)

    IO流

    File文件类

    作用: Java中使用File类的对象来充当文件夹/文件目录。
    注意: 在File类中,只是对文件进行创建、删除、重命名等操作,并未进行读取和修改文件内容(IO流)。

    • 文件引用路径:
    1. 相对路径:相对于当前包的位置,文件在当前包下创建。
    2. 绝对路径:包含盘符在内的文件或文件目录的路径。

    相对路径写在main方法下,会对应着当前工程,进而创建文件在当前整体工程下。并非当前Moudle下。

    IO流原理及流的分类

    • IO流原理
      IO是input/output的缩写,用于处理设备之间的数据传输。在Java程序中,对于数据的输入/输出操作是以“流”的形式进行的。

    • 流的分类
      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-47doCRZL-1656395185361)(en-resource://database/1322:1)]

    1. 四个抽象基类(按操作的数据分):
    • 字节流:InputStream/OutputStream。(图片,101010)
    • 字符流:Reader/Writer。(文字,字节的基础上编码)
    1. 按流的角色分为:
    • 节点流(文件流):FileInputStream/FileOutputStream、FileReader/FileWriter。
    • 处理流:除了节点流的其他流。

    节点流

    在这里插入图片描述

    节点流等同于流的基础,所有其他处理流都是建立在节点流的基础之上的。
    FileInputStream/FileOutputStream(字节)FileReader/FileWriter(字符)
    注意:写入到文件中时(write),文件不需要实际存在,会自动创建。

    • 流的通用操作
      1. 实例化文件对象。
      2. 创建节点流。
      3. 创建处理流(如有需求)。
      4. 具体操作。
      5. 关闭流。

    处理流

    缓冲流

    BufferedInputStream、BufferedOutputStream/BufferedReader、BufferedWriter。

    • 作用
      提高流的读取、写入的速度。
      提高读写速度的原因:内部提供了一个缓冲区。
      使用flush方法可以强制将缓冲区的内容全部写入到输出流。

    常用BufferedReader提供的readLine方法来读取一行字符。

    转换流

    解码(破解):字节——>字符数组、字符串
    编码:字符数组——>字节
    InputStreamReader:将字节的输入流转换为字符的输入流(解码)

    OutputStreamWriter:将字符的输出流转换为字节的输出流(编码)

    • 作用
      字节流和字符流之间的转换,可以实现文件编码的转换。

    对象流

    对象的序列化

    • 序列化机制
      对象序列化机制允许把内存中的Java对象转换成二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。

    简单来说,序列化就是对象转换成二进制流,保存在内存中。
    反序列化就是将二进制流转换为对象输出。

    • Java对象可序列化应满足的条件
      1. 实现接口:Serializable
      2. 当前类提供一个全局常量:serialVersionUID(标识作用)。
      3. 保证当前类的内部也需要实现可序列化(基本数据类型本身就是可序列化的)。

    对象流的使用

    ObjectInputStream(反序列化)和ObjectOutputStream(序列化)

    • 作用
      用于存储和读取基本数据类型或对象的处理流,可以把Java中的对象写入到数据中,也可以把对象从数据源中还原。
      ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量。

    其他流

    标准输入输出流

    字节流。
    System.in:标准的输入流,默认从键盘输入。
    System.out:标准的输出流,默认从控制台输出。
    System.exit(int status):终止当前正在运行的Java虚拟机,这个status表示退出的状态码,非零表示异常终止。(可以返回给其他进程的调用者一个调用的返回码,以根据返回码采取不同的策略。)

    注意:不管status为何值程序都会退出,和return 相比有不同的是:return是回到上一层,而System.exit(status)是回到最上层。

    打印流

    字符流。
    PrintStream、PrintWriter提供了一系列重载的print() 和 println()。

    数据流

    字节流。
    DataInputStream 和 DataOutputStream
    作用:用于读入或写出基本数据类型的变量或字符串。
    注意:想要写出文件内容,不可以双击打开,应该使用DataOutputSream来写出文件。

    随机存取文件流

    直接继承Object类,实现了DataInput和DataOutput接口。

    • 构造器和方法
      调用RandomAccessFile构造器会传入两个参数,一个是File文件,另一个是mod
      r:只读; rw:既可以读入,也可以写出。
      方法:write():文件覆盖的操作; seek(pos):调整当前指针pos的位置。
    • 作用
      既可以输入,又可以输出的流。
      RandomAccessFile:写入文件时,对于原有文件会从头开始覆盖。如果文件不存在,会自动创建文件。可以较方便地实现文件的插入操作

    NIO.2

    NIO将以更加高效的方式进行文件的读写操作。
    Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套是网络编程NIO。

    1. Path可以看成是File类的升级版本,实际引用的资源也可以不存在。
    2. Paths 类提供的静态 get() 方法用来获取 Path 对象。
    3. Files 用于操作文件或目录的工具类。
    • 在以前IO操作都是这样写的:
      import java.io.File;
      File file = new File(“index.html”);
    • 但在Java7 中,我们可以这样写:
      import java.nio.file.Path;
      import java.nio.file.Paths;
      Path path = Paths.get(“index.html”);

    网络编程

    网络编程概述

    在开始介绍网络编程之前,先介绍一些基本的网络概念:
    IP: 唯一的标识 Internet 上的计算机(通信实体)
    IPv4:4个字节组成,如192.168.0.1。
    IPv6:16个字节组成。
    域名: www.baidu.com等,可以通过域名来访问IP地址。
    DNS: 域名解析服务器,可以解析域名。
    本地回路地址: 127.0.0.1 域名:localhost

    端口号: 标识正在计算机上运行的进程(程序)。被规定一个为16位的整数。(0~65535)。不同的进程应该有不同的端口号。

    Socket: 端口号和IP地址组合得出一个网络套接字。

    TCP协议: 传输前:采用“三次握手”的方式,点对点通信,形成数据通道,保证可靠。
    发送结束:释放已建立的链接,但是效率比较低,“四次挥手”断开链接。

    UDP协议: 不需要建立连接,不可靠的链接,发送结束无需释放资源,开销小速度快。

    URL: 统一资源定位符,对应着互联网的某一资源地址。
    一个URL类就表示一个URL。具体方法见仓库。

    • URL格式:
      http://localhost:8080/examples/beauty.jpg?username=Tom
      协议 主机名 端口号 资源地址 参数列表

    拓展:端口分类:

    • 公认端口:被预先定义的通信服务占用。比如HTTP占用端口80。(0~1023)
    • 注册端口:分配给用户进程或应用程序。比如MySQL占用端口3306。比如tomcat的端口号为8080。(1024~49151)
    • 动态/私有端口:(49152~65535)

    网络编程目的:
    直接或间接通过网络协议与其他计算机实现数据交换,进行通讯。

    • 如何准确定位主机,与主机上特定的应用?——通信双方地址:IP和端口号
    • 找到主机后如何可靠的进行数据传输?
      ——网络通信协议:TCP/IP参考模型

    • 通信要素1:IP和端口号
      InetAddress类的对象,就表示一个IP地址。一个IP地址标识互联网上的一台主机。
      具体方法见仓库。
    • 通信要素2:网络协议
      传输层:TCP/UDP协议。网络层:IP协议。

    TCP、UDP、URL网络编程

    TCP网络编程练习一:客户端发送信息给服务端,服务端将数据显示在控制台上。
    客户端:

    1. 创建InetAddress对象和Socket对象,指明服务器端(现在是本地)IP地址和端口号。
    2. 使用Socket对象获取输出流。(输出流:将文字从程序输出到网络。)
    3. 关闭流。

    服务端:

    1. 创建服务端ServerSocket对象,指明自己端口号。
    2. 创建Socket对象,ServerSocket对象调用accept()方法接受来自客户端的socket。
    3. 使用Socket对象获取输入流。(输入流:将网络中的信息读取到程序。)
    4. 创建输出流。(输出流:进一步将程序中的信息写入到内存中。)
      注:这里比较特殊,使用ByteArrayOutputStream流读取文字信息到内存。
    5. 关闭流。
      TCP网络编程练习二:客户端向服务端发送一个图片,并且服务端保存图片到本地。
      TCP网络编程练习三:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。
      与练习一类似,详情参见代码仓库即可。

    UDP网络编程练习:客户端发送信息给服务端,服务端将数据显示在控制台上。
    客户端:使用DatagramSocket的方法【send】发送DatagramPacket的packet。
    服务端:使用DatagramSocket的方法【receive】接收DatagramPacket的packet。


    URL网络编程练习:

    1. 创建URL对象。
    2. 打开HttpsURLConnection类的对象的链接。
    3. 使用HttpsURLConnection类的对象打开连接。
    4. 使用HttpsURLConnection类的对象获取输入流,将URL资源从网络输入到程序。
    5. 创建输出流,将URL资源输出到内存中。

    Java反射机制

    Java反射机制概述

    • 初识反射
      Java Reflection 被视为动态语言的关键,允许程序在执行期借助于Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性方法(包括声明为私有的属性和方法)。
      加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。类也是一种Class的实例,可见Java中万事万物皆对象。
      体会反射的动态性:在编译时,无法确定具体所需的类的对象,只有在运行时才可以确定,这里就可以用到反射。动态,通俗点来说就是在程序运行时代码可以根据某些条件改变自身结构。
    • 与反射相关的API
      java.lang.Class :代表一个类
      java.lang.reflect.Method:代表类的方法
      java.lang.reflect.Field:代表类的成员变量
      java.lang.reflect.Constructor:代表类的构造器

    获取Class类的实例以及创建运行时类的对象

    • 获取Class类的实例的三种方式
      1. 使用类名+.class
      2. 通过运行时类的对象,调用getclass()
      3. 调用Class的静态方法 forName(String classPath)
    • 创建运行时类的对象
      使用Class类的方法newInstance()可以创建运行时类的对象。其内部调用了运行时类的空参构造器。
      要想此方法正常的创建运行时类的对象,要求:
      1. 运行时类必须提供空参的构造器。
      2. 空参的构造器的访问权限足够,通常为public。

    获取运行时类属性、方法

    • 获取一个运行时类的属性操作大体如下:

      1. 获取Class的实例(使用 .class)。
      2. 获取运行时类的对象(调用newInstance)。
      3. 获取指定属性(调用getDeclaredField)。
      4. 保证当前属性可访问(调用setAccessible)。
      5. 设置指定对象属性(调用set方法)。
    • 常见的第三步的其他操作:
      属性:getDeclaredField(String)—获取指定属性,String为属性名。
      方法:getDeclaredMethod(String,参数的Class实例)—获取指定方法,String为方法名。

    • 常见的第五步的其他操作:
      属性:set(obj)—设置对象属性值、get()—获取对象属性值。
      方法:invoke(obj,args)—表示给方法传参,args就是传递的参数,obj表示该方法属于哪个运行时类的对象。invoke的返回值为方法的返回值。

    获取运行时类的完整结构

    关于运行时类,我们可以通过一些方法来获取类的结构,比如:

    • 获取当前运行时类的属性(Field)——权限修饰符 数据类型 变量名

    • 获取当前运行时类的方法(Method)——注解 权限修饰符 返回值类型 方法名 参数类型 异常类型

    • 获取当前运行时类的构造器(Constructor)、父类(Superclass)、带泛型的父类(GenericSuperclass)的泛型(ActualTypeArauments)、接口(Interfaces)、所在的包(Package)、类声明的注解(Annotations)。

    此内容仅了解即可,不要求掌握,因为在上文调用运行时类的指定结构中用到了。

    ClassLoader的理解

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jkr2nINN-1656395185362)(en-resource://database/765:1)]

    • 类的加载
      1. 程序经过javac.exe命令编译以后,会生成一个或多个字节码文件(.class结尾,一个类就有一个.class文件)。
      2. 接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中,此过程就称为类的加载。
        加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。也可以说,Class的实例就对应着一个运行时类。
    • 类的加载过程
      当程序使用某个类时,会进行以下初始化:
      类的加载——> 类的链接——> 类的初始化
      将class文件加载到内存中 ->为类变量分配内存,设置默认初始值 -> 给类变量进行赋值

    类加载器的作用:把类装载进内存。
    了解类加载器以及读取配置文件的两种造流方式:代码见仓库。

    反射的应用:动态代理

    • 代理模式
      代理模式:代理模式是Java中使用较多的一种设计模式,代理设计就是为其他对象提供一种代理以控制对这个对象的访问,也就是通过代理类来调用其他对象的方法。
      应用场景:
      安全代理:屏蔽对真实角色的直接访问。
      远程代理:通过代理类处理远程方法调用(RMI)。
      ……
      分类:
      静态代理:专门针对某一接口来处理的代理。
      动态代理:在程序运行时根据需要动态创建目标类的代理对象。

    动态代理面向切面编程(AOP)到框架的时候再深入学习。
    动态代理的应用:Spring 的 AOP 、加事务、加权限、加日志。

    ⭐码字不易,求个关注⭐
    ⭐点个收藏不迷路哦~⭐

  • 相关阅读:
    沙箱对抗之反沙箱技巧
    CRM系统主要包括哪些功能?
    Golang 基础面试题 01
    视频编解码(七)之FOURCC和YUV关系简介
    【FTP工具】FileZila安装以及使用详解
    rabbitmq
    java集合,栈
    我的创作纪念日
    设计模式—桥接模式
    GCC基本使用
  • 原文地址:https://blog.csdn.net/weixin_62633072/article/details/125500843