Java基础全程学习笔记(四)

第13章:jdk5.0新特性:泛型#

1、在集合中使用泛型前后的对比#

1. 什么是泛型?
所谓泛型,就是允许在定义类、接口时通过一个`标识`表示类中某个`属性的类型`或者是某个方法的`返回值或参数的类型`。
这个类型参数将在使用时(例如,继承或实现这个接口、创建对象或调用方法时)确定(即传入实际的类型参数,也称为
类型实参)。

2. 在集合中使用泛型之前可能存在的问题
 问题1:添加的数据类型不安全
 问题2:繁琐:必须要使用向下转型。 还可能会报ClassCastException
	@Test
    public void test2(){
//        List<Integer> list = new ArrayList<Integer>();
        ArrayList<Integer> list = new ArrayList<>(); //jdk7.0新特性:类型推断
        //添加学生的成绩
        list.add(78);
        list.add(87);
        list.add(66);
        list.add(99);
        list.add(66);
        //1.如下的操作,编译不通过
//        list.add("AA");


        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            //2.不需要使用向下转型
            Integer score = iterator.next();

            System.out.println(score);
        }
    }

    @Test
    public void test3(){
        HashMap<String,Integer> map = new HashMap<>();

        map.put("Tom",78);
        map.put("Jerry",88);
        map.put("Jack",55);
        map.put("Rose",89);

//        map.put(56,"Tony");//编译不通过

        Set<Map.Entry<String,Integer>> entrySet = map.entrySet();
        Iterator<Map.Entry<String,Integer>> iterator = entrySet.iterator();
        while(iterator.hasNext()){
            Map.Entry<String,Integer> entry = iterator.next();
            System.out.println(entry.getKey() + "--->" + entry.getValue());
        }

    }

2、在其它结构中使用泛型#

  • 比较器:Comparable
public class Employee implements Comparable<Employee>{
    private String name;
    private int age;
    private MyDate Birthday;

    //省略get、set、构造器、toString()

    @Override
    public int compareTo(Employee o) {
//        if (this == o){
//            return 0;
//        }

        return this.name.compareTo((o.name));

    }
}
  • 比较器:Comparator
//定制排序
@Test
public void test2() {
    Employee e1 = new Employee("Tom", 23, new MyDate(1999, 12, 3));
    Employee e2 = new Employee("Jerry", 33, new MyDate(1990, 2, 3));
    Employee e3 = new Employee("Peter", 22, new MyDate(2000, 3, 5));
    Employee e4 = new Employee("NiPing", 23, new MyDate(2000, 12, 5));
    Employee e5 = new Employee("Fengyi", 20, new MyDate(2002, 9, 9));

    Comparator<Employee> comparator = new Comparator<>() {
        @Override
        public int compare(Employee e1, Employee e2) {
            if (e1 == e2) {
                return 0;
            }

            int yearDistance = e1.getBirthday().getYear() - e2.getBirthday().getYear();
            if (yearDistance != 0) {
                return yearDistance;
            }
            int monthDistance = e1.getBirthday().getMonth() - e2.getBirthday().getMonth();
            if (monthDistance != 0) {
                return monthDistance;
            }
            return e1.getBirthday().getDay() - e2.getBirthday().getDay();



        }
    };

    TreeSet<Employee> treeSet = new TreeSet<>(comparator);
    treeSet.add(e1);
    treeSet.add(e2);
    treeSet.add(e3);
    treeSet.add(e4);
    treeSet.add(e5);

    Iterator<Employee> iterator = treeSet.iterator();
    while (iterator.hasNext()) {
        Employee e = iterator.next();
        System.out.println(e);
    }
}

3、如何自定义泛型类、泛型接口、泛型方法#

1. 自定义泛型类\接口
1.1 格式

class A<T>{}

interface B<T>{}

1.2 使用说明
> 声明的泛型类,在实例化时可以不使用类的泛型。
> 声明泛型类以后,可以在类的内部结构中,使用类的泛型参数。比如:属性、方法、构造器
> 何时指明具体的类的泛型参数类型呢?① 类的实例化 ② 提供子类时
> 泛型参数类型只能是引用数据类型,不能使用基本数据类型。
> 一旦指定类的泛型参数的具体类型以后,则凡是使用类的泛型参数的位置,都确定为具体的泛型参数的类型。
  如果实例化时未指定泛型参数的具体类型,则默认看做是Object类型。
> 泛型类中,使用泛型参数的属性、方法是不能声明为static的。




2. 自定义泛型方法
2.1 问题:在泛型类的方法中,使用了类的泛型参数。那么此方法是泛型方法吗?

2.2 格式
权限修饰符 <T> 返回值类型 方法名(形参列表){}

2.3 举例
public <E> ArrayList<E> copyFromArrayToList(E[] arr){

2.4 说明
> 泛型方法所在的类是否是一个泛型类,都可以。
> 泛型方法中的泛型参数通常是在调用此方法时,指明其具体的类型。
  一旦指明了其泛型的类型,则方法内部凡是使用方法泛型参数的位置都指定为具体的泛型类型。
> 泛型方法可以根据需要声明为static

4、泛型在继承上的体现#

1. 类A是类B的父类,则G<A> 与 G<B>的关系:没有继承上的关系。
比如:ArrayList<String>的实例就不能赋值给ArrayList<Object>


2. 类A是类B的父类或接口,A<G> 与 B<G>的关系:仍然满足继承或实现关系。意味着可以使用多态。
比如:将ArrayList<String>的类型的变量赋值给List<String>的变量,是可以的。

5、通配符、有条件限制的通配符的使用#

1. 通配符: ?

2. 使用说明:
2.1 举例:
List<?> list1 = null;
List<Object> list2 = new ArrayList<Object>();

List<String> list3 = null;

list1 = list2;
list1 = list3;

2.2 说明:可以将List<?>看做是List<Object> 、 List<String>结构共同的父类

3. 读写数据的特点
> 读取:可以读数据,但是读取的数据的类型是Object类型。
> 写入:不能向集合中添加数据。特例:null

4. 有限制条件的通配符

   List<? extends A> : 可以将List<A> 或 List<SubA>赋值给List<? extends A>。其中SubA是A类的子类
   List <? super A> : 可以将List<A> 或 List<SuperA>赋值给List<? super A>。其中SuperA是A类的父类

5. 有限制条件的统配符的读写操作(难、了解)
见代码

第14章:数据结构与集合源码#

1、数据结构#

  • 数据结构的研究对象
1. 数据结构概念:
数据结构,就是一种程序设计优化的方法论,研究数据的`逻辑结构`和`物理结构`以及它们之间相互关系,
并对这种结构定义相应的`运算`,目的是加快程序的执行速度、减少内存占用的空间。

2. 数据结构的研究对象
研究对象1:数据间逻辑关系
> 集合结构
> 线性结构:一对一关系
> 树形结构:一对多关系
> 图形结构:多对多关系


研究对象2:数据的存储结构
> 结构1:顺序结构
> 结构2:链式结构
> 结构3:索引结构
> 结构4:散列结构

开发中更关注存储结构:
> 线性表:数组、单向链表、双向链表、栈、队列等
> 树:二叉树、B+树
> 图:无序图、有序图
> 散列表:HashMap、HashSet


研究对象3:
- 分配资源,建立结构,释放资源
- 插入和删除
- 获取和遍历
- 修改和排序
  • 常见的数据存储结构
3. 常见存储结构之:数组


4. 常见存储结构之:链表
4.1 单向链表
class Node{
    Object data;
    Node next;

    public Node(){}

    public Node(Object data){
        this.data = data;
    }

    public Node(Object data,Node next){
        this.data = data;
        this.next = next;
    }

}

举例:
Node node1 = new Node("AA");
Node node2 = new Node("BB");
node1.next = node2; //尾插法

node2.next = node1;//头插法
或
Node node2 = new Node("BB",node1);



4.2 双向链表
class Node{
    Object data;
    Node prev;
    Node next;

    public Node(Object data){
        this.data = data;
    }

    public Node(Node prev,Object data,Node next){
        this.prev = prev;
        this.data = data;
        this.next = next;
    }

}

举例:
Node node1 = new Node("AA");
Node node2 = new Node(node1,"BB",null);
Node node3 = new Node(node2,"CC",null);

node1.next = node2;
node2.next = node3;


5. 常见存储结构之:二叉树
class Node{
    Object data;
    Node left;
    Node right;

    public Node(Object data){
        this.data = data;
    }

    public Node(Node left,Object data,Node right){
        this.left = left;
        this.data = data;
        this.right = right;
    }

}

举例:
Node node1 = new Node("AA");
Node node2 = new Node("BB");
Node node3 = new Node("CC");
node1.left = node2;
node1.right = node3;


或者:
class Node{
    Object data;
    Node left;
    Node right;
    Node parent;

    public Node(Object data){
        this.data = data;
    }

    public Node(Node left,Object data,Node right){
        this.left = left;
        this.data = data;
        this.right = right;
    }

    public Node(Node parent,Node left,Object data,Node right){
        this.parent = parent;
        this.left = left;
        this.data = data;
        this.right = right;
    }

}

举例:
Node node1 = new Node("AA");
Node node2 = new Node(node1,null,"BB",null);
Node node3 = new Node(node1,null,"CC",null);
node1.left = node2;
node1.right = node3;


6. 常见存储结构之:栈 (先进后出、后进先出、FILO、LIFO)
(ADT:abstract data type,栈可以使用数组、链表生成)

class Stack{

    Object[] values;
    int size;//记录添加的元素个数

    public Stack(int capacity){
        values = new Object[capacity];
    }

    //入栈
    public void push(Object ele){
        if(size >= values.length){
            throw new RuntimeException("栈已满,添加失败");
        }

        values[size] = ele;
        size++;
    }
    //出栈
    public Object pop(){
        if(size <= 0){
            throw new RuntimeException("栈已空,弹出栈操作失败");
        }

        Object returnValue = values[size - 1];
        values[size - 1] = null;
        size--;
        return returnValue;
    }

}



7. 常见存储结构之:队列(先进先出、FIFO)
(ADT:abstract data type,栈可以使用数组、链表生成)

class Queue{

    Object[] values;
    int size;//记录添加的元素个数

    public Queue(int capacity){
        values = new Object[capacity];
    }

    //添加元素
    public void add(Object ele){
        if(size >= values.length){
            throw new RuntimeException("队列已满,添加失败");
        }

        values[size] = ele;
        size++;
    }
    //获取元素
    public Object get(){
        if(size <= 0){
            throw new RuntimeException("队列已空,获取数据失败");
        }

        Object returnValue = values[0];

        for(int i = 0;i < size - 1;i++){
            values[i] = values[i + 1];
        }

        values[size - 1] = null;
        size--;
        return returnValue;
    }

}

2、集合源码#

  • List的实现
一、ArrayList
1. ArrayList的特点:
主要实现类;线程不安全的,效率高;底层使用Object[]存储
对于频繁的查找、尾部添加,性能较高,时间复杂度O(1)


2. ArrayList源码解析:
2.1 jdk7版本:(以jdk1.7.0_07为例)
ArrayList list = new ArrayList(); //底层创建了一个长度为10的Object[]:Object[] elementData = new Object[10];
list.add("AA");//elementData[0] = "AA";
...
当添加第11个元素时:由于容量不够,需要扩容,默认扩容为原来的1.5倍。


2.2 jdk8版本:(以jdk1.8.0_271为例)
ArrayList list = new ArrayList(); //底层并没有创建长度为10的Object[]数组,而是Object[] elementData = {};
list.add("AA");//当首次添加元素时,底层创建长度为10的数组,赋给elementData,同时elementData[0] = "AA";
...
当添加第11个元素时:由于容量不够,需要扩容,默认扩容为原来的1.5倍。


类比:1.7类似于饿汉式;1.8类似于懒汉式。

二、Vector
1. Vector的特点:
古老的实现类;线程安全的,效率低;底层使用Object[]存储


2. Vector源码解析:(以jdk1.8.0_271为例)

Vector在jdk1.8中初始化时就创建了长度为10的Object[]数组。当添加元素到满的时候,默认扩容为原来的2倍。





三、LinkedList
1. LinkedList的特点:
使用双向链表存储数据;
对于频繁的删除、插入操作,性能较高,时间复杂度为O(1)


2. LinkedList在jdk8中的源码解析:
LinkedList list = new LinkedList(); //底层没有做什么操作
list.add("AA");//内部创建一个Node对象a,LinkedList内部的属性first、last都指向对象a
list.add("BB");//内部创建一个Node对象b,对象a的next指向对象b,对象b的prev指向对象a,last指向对象b。


内部声明的Node如下:
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}


3. LinkedList是否存在扩容问题?没有!


四、启示与开发建议
> Vector基本不用,效率低,使用ArrayList替换
> 对于频繁的删除、插入操作,使用LinkedList替换ArrayList
> 除此之外,我们首推ArrayList。
    > new ArrayList() / new ArrayList(int capacity) (推荐,避免出现不必要的多次扩容)
  • HashMap的底层实现
一、HashMap
1. HashMap中元素的特点
> HashMap中的所有的key彼此之间不相同,且无序。多个key构成一个Set。--->key所在的类要重写equals()、hashCode()
> HashMap中的所有的value彼此之间可以相同,且无序。多个value构成一个Collection。--> value所在的类要重写equals()
> HashMap中的一个key-value构成一个Entry。
> HashMap中的所有的entry彼此之间不相同,且无序。多个entry构成一个Set。


2. HashMap源码解析
2.1 jdk7中创建对象和添加数据过程(以JDK1.7.0_07为例说明):数组+单向链表

HashMap map = new HashMap();//底层创建了长度为16的数组:Entry[] table = new Entry[16];

map.put("AA",67);//添加过程如下。

将(key1,value1)添加到map中:
1)通过key1所在的hashCode(),计算得到key1的哈希值1,此哈希值1经过某种算法(hash())以后得到哈希值2。
此哈希值2经过某种算法(indexFor())以后,得到key1-value1在数组table中的存储位置i。
2)判断table[i] 是否为空。
    如果为空,key1-value1添加成功。  --->添加成功1
    如果不为空,假设已有元素(key0,value0),则需要继续比较。 ----> 哈希冲突
3)    比较key1的哈希值2与key0的哈希值2是否相等。
         如果两个哈希值2不相等。则认为key1-value1与key0-value0不相同。key1-value1添加成功。  --->添加成功2
         如果两个哈希值2相同,则需要继续比较。
4)         调用key1所在类的equals(),将key0放入equals()的形参中。看返回值。
                如果返回值为false,则key1和key0不同,则key1-value1添加成功。   --->添加成功3
                如果返回值为true,则认为key1和key0相同,则value1替换value0。理解为修改成功。

说明:
添加成功1:将key1-value1封装在entry的对象中,将此对象放入数组的位置
添加成功2、3:key1-value1封装在entry的对象1中,与key0-value0封装的entry对象0构成单向链表的结构。
jdk7中是entry对象1指向entry对象0,entry对象1放在数组里。

...
不断的添加,添加到什么情况时会扩容呢?一旦达到临界值(且索引i的位置上恰好还有元素),就考虑扩容。默认扩容为原来的2倍。
(源码为:if ((size >= threshold) && (null != table[bucketIndex])) 条件下扩容)。


2.2 jdk8与jdk7的不同之处(以jdk1.8.0_271为例):
1)HashMap map = new HashMap(); 底层并没有创建长度为16的数组。
2)调用put(k,v)添加元素。如果是首次添加,则底层默认创建长度为16的table[]
3)jdk8中HashMap内部使用Node[]替换Entry[]。
4)如果要添加的key1-value1在经过一系列判断后,确定能添加到索引i的位置。此时,采用尾插法。
  即原有的此索引i位置上的链表的最后一个元素指向新要添加的key1-value1。 "七上八下"
5)如果索引i位置上的元素达到8了,且数组的长度达到64的情况下,索引i位置上的多个元素要改为使用红黑树存储。
  目的:为了提升查找的效率。(链表情况下查找的复杂度:O(n),红黑树的查找的复杂度:O(logN))

  当索引i位置上的元素个数少于6个时,会将此索引i位置上的红黑树改为单向链表。




2.3 属性字段:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认的初始容量 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;  //默认加载因子
static final int TREEIFY_THRESHOLD = 8; //默认树化阈值8,当链表的长度达到这个值后,要考虑树化
static final int UNTREEIFY_THRESHOLD = 6;//默认反树化阈值6,当树中结点的个数达到此阈值后,要考虑变为链表

//当单个的链表的结点个数达到8,并且table的长度达到64,才会树化。
//当单个的链表的结点个数达到8,但是table的长度未达到64,会先扩容
static final int MIN_TREEIFY_CAPACITY = 64; //最小树化容量64

transient Node<K,V>[] table; //数组
transient int size;  //记录有效映射关系的对数,也是Entry对象的个数
int threshold; //阈值,当size达到阈值时,考虑扩容
final float loadFactor; //加载因子,影响扩容的频率


二、LinkedHashMap
1. LinkedHashMap 与 HashMap 的关系:继承关系。在HashMap的Node的基础上,增加了一对双向链表,记录
添加的先后顺序。



2. 底层结构:
重写了如下的方法:
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}
其中:
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

拓展:
HashSet的底层实现原理?

第15章:File类与IO流#

1. File类的使用#

1. File类的理解
> File声明在java.io包下的。
> File类的对象可以表示一个文件或一个文件目录。
> File类中包含了关于文件、文件目录的新建、删除、重命名、查询所在路径、获取文件大小等方法。
  但是不包含读写文件内部内容的方法。要想读写文件内容,我们需要使用IO流。
> File类的对象常作为IO流读写数据的端点出现:常作为IO流的构造器的形参出现。


2. 内部api使用说明
2.1 构造器
* `public File(String pathname) ` :以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。
* `public File(String parent, String child) ` :以parent为父路径,child为子路径创建File对象。
* `public File(File parent, String child)` :根据一个父File对象和子文件路径创建File对象


2.2 方法
 1、获取文件和目录基本信息
* public String getName() :获取名称
* public String getPath() :获取路径
* `public String getAbsolutePath()`:获取绝对路径
* public File getAbsoluteFile():获取绝对路径表示的文件
* `public String getParent()`:获取上层文件目录路径。若无,返回null
* public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
* public long lastModified() :获取最后一次的修改时间,毫秒值

2、列出目录的下一级

* public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。
* public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。

3、File类的重命名功能
- public boolean renameTo(File dest):把文件重命名为指定的文件路径。

4、判断功能的方法
- `public boolean exists()` :此File表示的文件或目录是否实际存在。
- `public boolean isDirectory()` :此File表示的是否为目录。
- `public boolean isFile()` :此File表示的是否为文件。
- public boolean canRead() :判断是否可读
- public boolean canWrite() :判断是否可写
- public boolean isHidden() :判断是否隐藏

5、创建、删除功能
- `public boolean createNewFile()` :创建文件。若文件存在,则不创建,返回false。
- `public boolean mkdir()` :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
- `public boolean mkdirs()` :创建文件目录。如果上层文件目录不存在,一并创建。
- `public boolean delete()` :删除文件或者文件夹
  删除注意事项:① Java中的删除不走回收站。② 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录。

3. 概念:
绝对路径:在windows操作系统中,以盘符开始的路径。

相对路径:相较于某个指定路径下的具体路径。
        单元测试方法:相较于当前的module
        main():相较于当前的工程

2. IO流的概述#

  • 流的分类
    • 流的流向:输入流、输出流
    • 操作的数据单位:字节流、字符流
    • 角色的不同:节点流、处理流
  • 整个流这一章涉及到的具体的流的使用,操作的步骤都是标准规范的。
    • 步骤1:创建File的对象
    • 步骤2:创建流的对象,构造器中需要传入File的对象
    • 步骤3:读取、写出操作的细节
    • 步骤4:关闭资源

3. 文件流的使用#

  • FileInputStream与FileOutputStream、FileReader与FileWriter

  • 注意点1:

注意点:
> 因为涉及到资源的关闭,所有异常的处理需要使用try-catch-finally结构替换throws
> 对于输入流来讲,File对象对应的物理磁盘上的文件必须存在。否则,报FileNotFoundException
  对于输出流来讲,File对象对应的物理磁盘上的文件可以不存在。
        > 如果不存在,则在输出的过程中,会自动创建指定名的文件
        > 如果存在,如果使用的是FileWriter(File file)或FileWriter(File file,false)构造器,则在输出的过程中,会覆盖已有的文件
          如果存在,如果使用的是FileWriter(File file,true)构造器,则在输出的过程中,会在现有文件末尾追加内容。
> 务必记得关闭资源,否则出现内存泄漏
  • 注意点2:
> FileReader \ FileWriter :主要用来处理文本文件
  对于非文本文件的处理是失败的。

> FileInputStream \ FileOutputStream:主要用来处理非文本文件。

> 文本文件:.txt、.java、.c、.cpp、.py
  非文本文件:.doc、.jpg、.png、.avi、.mp3、.mp4、.ppt

4. 处理流之一:缓冲流#

  • BufferedInputStream与BufferedOutputStream、BufferedReader与BufferedWriter

  • 作用:加快文件的读写效率。

  • 原理:内部提供了缓冲区(数组实现的),减少和磁盘交互的次数。

  • 4个缓冲流                   使用的方法
    处理非文本文件的字节流:
    BufferedInputStream        read(byte[] buffer)
    BufferedOutputStream       write(byte[] buffer,0,len) \ flush()
    
    处理文本文件的字符流:
    BuffferedReader            read(char[] buffer)\readLine()
    BufferedWriter             write(char[] buffer,0,len) \ flush()
    
    3. 实现的步骤
    
    第1步:创建File的对象、流的对象(包括文件流、缓冲流)
    
    第2步:使用缓冲流实现 读取数据 或 写出数据的过程(重点)
        读取:int read(char[] cbuf/byte[] buffer) : 每次将数据读入到cbuf/buffer数组中,并返回读入到数组中的字符的个数
        写出:void write(String str)/write(char[] cbuf):将str或cbuf写出到文件中
             void write(byte[] buffer) 将byte[]写出到文件中
    
    第3步:关闭资源
    

5. 处理流之二:转换流#

  • InputStreamReader和OutputStreamWriter

  • 1. 复习
    
    字符编码:字符、字符串 ----> 字节、字节数组。对应着编码集
    字符解码:字节、字节数组 ---> 字符、字符串。对应着解码集
    
    2. 如果希望程序在读取文本文件时,不出现乱码,需要注意什么?
    使用的解码集必须与当初保存文本文件使用的编码集一致。
    

6. 处理流之三:对象流#

  • 了解:数据流:DataInputStream 、DataOutputStream

    • 读写8种基本数据类型的变量、String、字节数组
  • 掌握:ObjectInputStream、ObjectOutputStream

    • 读写8种基本数据类型的变量、对象(readObject();writeObject(Object obj))
  • 掌握:对象的序列化机制

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

    • 序列化过程:将内存中的Java对象转换为二进制流,保存在文件中或通过网络传输出去。
      使用ObjectOutputStream
      
      反序列化过程:将文件中或者通过网络接收到的二进制流转换为内存中的Java对象
      使用ObjectInputStream
      
  • 熟悉:自定义类要想实现序列化机制,需要满足:

    > 必须实现接口Serializable。 (此接口中没有抽象方法,称为标识接口)
    > 类中必须显式声明一个全局常量serialVersionUID,用于唯一标识当前类本身。
      如果不显式声明,系统会自动分配一个serialVersionUID,但是此属性在类修改的情况下,可能被改变。不建议使用默认情况。
    > 自定义类的所有属性也必须是可以序列化的。满足上述的两个条件。
      特别的:基本数据类型、String类型本身已经是可以序列化的了。
    
    
    6.注意点:
    > 类中声明为static或transient的变量,不能实现序列化。
    

7. 其它流的使用#

  • 标准的输入、输出流
System.in: 默认的输入设备:键盘
System.out: 默认的输出设备:显示屏
  • 打印流
PrintStream和PrintWriter
  • 使用第三方框架

    • apache-common包

第16章:网络编程#

1. 网络编程概述#

1. 要想实现网络通信,需要解决的三个问题:
- 问题1:如何准确地定位网络上一台或多台主机
- 问题2:如何定位主机上的特定的应用
- 问题3:找到主机后,如何可靠、高效地进行数据传输


2. 实现网络传输的三个要素:(对应解决三个问题)
> 通信要素1:IP地址。对应着解决定位网络上主机的问题
> 通信要素2:端口号。区分同一台主机上的不同进程。
> 通信要素3:通信协议。规范通信的规则,进而实现可靠、高效地进行数据传输

2. 要素1:InetAddress类的使用#

1. 作用:准确地定位网络上一台或多台主机


2. IP地址分类
> IP地址分类方式1 :IPv4 和 IPv6

> IP地址分类方式2:公网地址( 万维网使用)和 私有地址( 局域网使用)
      > 192.168.开头的就是私有地址

3. 本地回路地址:127.0.0.1  --->  localhost

4. 域名:  www.atguigu.com    www.baidu.com   www.jd.com
          www.mi.com   www.vip.com
          
5. InetAddress的使用
5.1 作用:InetAddress类的一个实例表示一个具体的ip地址。


5.2 实例化方式与常用方法
> 实例化:getByName(String host) / getLocalHost()
> 方法:getHostName() / getHostAddress()          

3. 要素2:端口号#

> 唯一标识设备中的进程(应用程序)
> 不同的进程,需要使用不同的端口号
> 用两个字节表示的整数,它的取值范围是0~65535

4. 要素3:网络通信协议#

1. 网络通信协议的目的:实现双方可靠、高效的数据传输。


2. 网络参考模型
> OSI参考模型(7层,过于理想化)
> TCP/IP参考模型
    > 应用层:HTTP、FTP
    > 传输层:TCP、UDP
    > 网络层:IP
    > 物理+数据链路层

5. TCP网络编程、UDP网络编程#

  • 熟悉:TCP的三次握手、四次挥手。
  • 例题
    • 例题1:客户端发送内容给服务端,服务端将内容打印到控制台上。

    • 例题2:客户端发送文件给服务端,服务端将文件保存在本地。

    • 例题3:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。并关闭相应的连接。

6. URL网络编程#

URL(Uniform Resource Locator):
1. 作用:
统一资源定位符,它表示 Internet 上某一资源的地址。

2. URL的格式:
http://127.0.0.1:8080/examples/ym.png
应用层协议  ip地址  端口号  资源地址

3. URL类的实例化及常用方法


4. 下载指定的URL的资源到本地(了解)

第17章:反射机制#

1. Class 的使用#

  • 掌握:Class的理解
  • 掌握:获取Class的实例(前三种)
/*
* 获取Class实例的几种方式(掌握前三种)
* */
@Test
public void test1() throws ClassNotFoundException {
    //1.调用类的静态属性(.class)
    Class clazz1 = User.class;
    System.out.println(clazz1);

    //2. 通过对象调用getClass()
    User u1 = new User();
    Class clazz2 = u1.getClass();
    System.out.println(clazz2);

    System.out.println(clazz1 == clazz2); //true

    //3. 调用Class的静态方法forName(String className)
    Class clazz3 = Class.forName("com.atguigu02._class.User");
    System.out.println(clazz1 == clazz3);//true

    //4. (了解)使用类的加载器,调用loadClass(String className)
    Class clazz4 = ClassLoader.getSystemClassLoader().loadClass("com.atguigu02._class.User");
    System.out.println(clazz1 == clazz4);

}
  • 熟悉:Class可以指向哪些具体的结构?
    • 类、接口、注解、枚举、基本数据类型、数组、void等

2. 类的加载与类的加载器#

  • 类的加载过程:
过程1:装载(Loading)
将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成。

过程2:链接(Linking)
 ①验证Verify:确保加载的类信息符合JVM规范,例如:以cafebabe开头,没有安全方面的问题。
 ②准备Prepare:正式为类变量(static)分配内存并`设置类变量默认初始值`的阶段,这些内存都将在方法区中进行分配。
 ③解析Resolve:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

过程3:初始化(Initialization)
  - 执行`类构造器<clinit>()方法`的过程。`类构造器<clinit>()方法`是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
  - 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
  - 虚拟机会保证一个`类的<clinit>()方法`在多线程环境中被正确加锁和同步。
  • 了解:类的加载器
1. 常见的类的加载器
> 引导类加载器(Bootstrap ClassLoader):负责系统核心api的加载。
> 扩展类加载器(ExtClassLoader):jdk目录下的jre/lib/ext目录下的api
> 应用程序类加载器(或系统类加载器 AppClassLoader):用户自定义的类的默认的类的加载器

2. 相互之间的关系:不是继承关系。
但是我们经常说:应用程序类加载器的父类加载器是扩展类加载器;扩展类加载器的父类加载器是引导类加载器。
为什么习惯这样称谓呢?涉及到类的加载机制:双亲委派机制。


class SystemClassLoader{
    ExtClassLoader loader;
    public SystemClassLoader(ExtClassLoader loader){
        this.loader = loader;
    }
}

3.(掌握)使用类的加载器获取流,并读取配置文件信息
 /*
    * (掌握)使用类的加载器获取流,并读取配置文件信息
    * */
    @Test
    public void test3() throws Exception {

        Properties pros = new Properties();
        //方式1:默认的文件所在当前module下
//        FileInputStream is = new FileInputStream(new File("jdbc.properties"));
        FileInputStream is = new FileInputStream(new File("src/jdbc1.properties"));

        //方式2:默认的文件所在当前module的src下
//        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
//        InputStream is = systemClassLoader.getResourceAsStream("jdbc1.properties");



        pros.load(is);

        String name = pros.getProperty("name");
        String password = pros.getProperty("password");
        System.out.println(name + ":" + password);


    }

3. 反射的应用1:创建运行时类的对象(掌握)#

  • jdk8及之前:Class的newInstance()
1.1 如何实现?
调用Class的方法:newInstance(),返回一个运行时类的实例。

1.2 要想创建对象成功,需要满足:
> 对应的运行时类中必须提供一个空参的构造器
> 对应的运行时类的空参的构造器的访问权限必须够

1.3 回忆:JavaBean中要求给当前类提供一个公共的空参的构造器。有什么用?
> 子类继承父类以后,子类的构造器中在没有显式声明this(形参列表)或super(形参列表)的情况下,默认调用父类空参的构造器
> 提供空参的构造器,便于通过反射的方式动态的创建指定类的对象。


1.4 在jdk9中标识为过时,替换成什么结构:
clazz.getDeclaredConstructor().newInstance()进行替换。
  • jdk8之后:调用指定的构造器,设置构造器的访问权限,创建对象

4. 反射的应用2:获取运行时类的完整结构#

  • 了解:获取所有的属性、所有的方法、所有的构造器、声明的注解等。
  • 熟悉:获取运行时类的父类、实现的接口们、所在的包、带泛型的父类、父类的泛型等

5. 反射的应用3:调用指定的属性、方法、构造器#

(掌握)反射的应用3:调用指定的结构:指定的属性、方法、构造器

1.1 调用指定的属性:
步骤1:获取指定名称的属性:调用Class类中的getDeclaredField(String fieldName)
步骤2:确保此属性是可访问的:调用Field类中的setAccessible(true);
步骤3:获取此属性的值:调用Field类的get(Object obj)
      设置此属性的值:调用Field类的set(Object obj,Object fieldValue)

1.2 调用指定的方法
步骤1:获取运行时类中指明的方法:调用Class类的getDeclaredMethod(String methodName,Class ... paramTypes)
步骤2:确保此方法是可访问的:调用Method类中的setAccessible(true);
步骤3:调用此方法:调用Method类的invoke()方法,此方法的返回值即为invoke方法调用者对应的方法的返回值。

1.3 调用指定的构造器
步骤1:获取指定的构造器:调用Class类的getDeclaredConstructor(Class ... paramTypes)
步骤2:确保此构造器是可访问的:调用Constructor类中的setAccessible(true);
步骤3:调用此构造器,创建运行时类的对象:调用Constructor类中的newInstance(Object ... values);

6. 反射的应用4:注解的使用(了解)#

略。为了后期学习框架做准备

7. 体会反射的动态性#

见课后练习题。

第18章:JDK8-17新特性#

1. 新特性概述#

> 角度1:新的语法规则 (多关注)
  比如:lambda表达式、enum、annotation、自动拆箱装箱、接口中的默认方法和静态方法、switch表达式、record等

> 角度2:增加、过时、删除API
  比如:新的日期时间的API、Optional、String、HashMap、Stream API

> 角度3:底层的优化、JVM参数的调整、GC的变化、内存结构(永久代--->元空间)

2. JDK8:lambda表达式、方法引用等#

重点!

3. JDK8:Stream API的使用#

重点!

4. 其它新特性#

  • 熟悉:Optional的使用
  • try-catch、switch表达式、var、instanceof模式匹配、密封类等。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值