01_java

1.接收输入

import java.util.Scanner;

public class Scann {
    public static void main(String[] args) {
        System.out.println("等待输入");
        Scanner sc = new Scanner(System.in);
        int num = sc.nextInt();
        System.out.println(num);
    }
}

2.数组

初始化声明

public class arrTest {
    public static void main(String[] args) {
        //一维
        int[] ids;//声明
        //1.1 静态初始化:数组的初始化和数组元素的赋值操作同时进行
        ids = new int[]{1001, 1002, 1003, 1004};
        //1.2动态初始化:数组的初始化和数组元素的赋值操作分开进行
        String[] names = new String[5];

        //可以是变量
        int num = 10;
        int[] nums = new int[num];

        //二维
        //静态初始化
        int[][] arr1 = new int[][]{{1, 2, 3}, {4, 5}, {6, 7, 8}};
        //动态初始化1
        String[][] arr2 = new String[3][2];
        //动态初始化2
        String[][] arr3 = new String[3][];
        //也是正确的写法:
        int[] arr4[] = new int[][]{{1, 2, 3}, {4, 5, 9, 10}, {6, 7, 8}};


    }
}

数组默认值

一维
  • 数组元素是整型:0
  • 数组元素是浮点型:0.0
  • 数组元素是char型:0或’\u0000’,而非’0’
  • 数组元素是boolean型:false
  • 数组元素是引用数据类型:null
二维
  • 规定:二维数组分为外层数组的元素,内层数组的元素

  • 针对于初始化方式一:比如:

    int[][] arr = new int[4][3];
    
  • 外层元素的初始化值为:地址值

  • 内层元素的初始化值为:与一维数组初始化情况相同

  • 针对于初始化方式二:比如:

    int[][] arr = new int[4][];
    
  • 外层元素的初始化值为:null

  •  内层元素的初始化值为:不能调用,否则报错。
    

数组常见方法使用

进行导包

import java.util.Arrays;
int arr11[] = new int[]{21, 43, 542, 432, 4, 2, 5, 1};
int arr22[] = new int[]{32, 43, 4, 1, 4, 76, 54, 68, 4};

// 1.判断数组是否相等
System.out.println(Arrays.equals(arr11, arr22));
// 2.输出数组信息
System.out.println(Arrays.toString(arr11));

// 3.将指定值填充到数组中
Arrays.fill(arr11, 2);
System.out.println(Arrays.toString(arr11));
// 4.对数组进行排序
Arrays.sort(arr22);
System.out.println(Arrays.toString(arr22));
// 5.堆排序好的数组用二分法检索指定值
int index = Arrays.binarySearch(arr22, 1);
System.out.println(index);

3.对象,类

基本使用

注:静态类加载比非静态优先,因此静态类只能调用静态方法

Person

public class Person {
    String name;
    int age;

    public  void  run(){
        System.out.println("跑跑跑");
    }


    public Person(String name, int age) {
        this.name = name;
        this.age = 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;
    }
}

Test

public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("rokned", 22);
        System.out.println(p1.name + " " + p1.age);
        p1.run();
    }
}

方法重载

public void run() {
    System.out.println("跑跑跑");
}

//方法重载
public void run(int n) {
    System.out.println("跑" + n + "圈");
}

可变参数

//可变参数
public  void run(int ...ns){
    for(int temp:ns){
        System.out.println(temp);
    }
}

继承

不支持多继承

public class Student extends Person{
    public Student(String name, int age) {
        super(name, age);
    }

    @Override
    public void run() {
        super.run();
        System.out.println("学生跑");
    }
}

多态(向上转型)

多态的作用:可以减少重复代码

在这里插入图片描述

父类引用指向子类对象

调用方法时,只能调用父类中有的方法,如果子类有重写,则调用重写的。

//多态
Person p3 = new Student("duotai", 21);
p3.run();
//        p3.eat();会报错,父类中没有eat方法

向下转型

//向下转型,使用强转
Student p4 = (Student) p3;
p4.eat();//此时可以调用子类独有的方法

instanceof

a instanceof A 判断a是否是A的实例

//instanceof
System.out.println((p4 instanceof Student));	//true
System.out.println((p4 instanceof Person));		//true
System.out.println((p4 instanceof Object));		//true

包装类

在这里插入图片描述

int num = 10;
//基本转包装,其他类型同理
Integer in1 = new Integer(num);
Integer in2 = new Integer("123");
Integer in3 = new Integer(123);


//包装转基本,其他类型同理
int i1 = in1.intValue();


//自动拆装箱
Integer in4 = i1;
int i2 = in4;

//字符串转换
int num1 = 10;
String s1  = num1 +"";
int num2 = Integer.parseInt(s1);
System.out.println(num2);

抽象类

子类必须实现抽象父类中的方法。

abstract class EarthPeople {
    //抽象类不能实例化,一定有构造器

    public EarthPeople() {
    }

    //抽象类可以没有抽象方法,但是有抽象方法的一定是抽象类
    public abstract void fly();
}

接口

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

可以多个接口相接,必须实现接口中的方法,使用implements进行实现

public interface gun {
    //接口不能定义构造器,不可以实例化
    public void shoot();
}

内部类

和普通类类似,不过定义在另一个类中

可以调用外部类结构,同时静态不能调用非静态。

public class Inner {
    //静态成员内部类
    static class AA {

    }

    //非静态成员内部类
    class BB {

    }
}

调用:

//创建静态类实例对象
Inner.AA aa = new Inner.AA();
//创建非静态类实例对象
//        Inner.BB bb = new Inner.BB(); 错误
Inner in = new Inner();
Inner.BB bb = in.new BB();

4.异常

try-catch-finally

try{
    String str = "123";
    str = "abc";
    int num = Integer.parseInt(str);
}
//一个try可以写多个catch
catch (NumberFormatException e){
    System.out.println("出错了");
}
catch (Exception e){
    System.out.println("出大错了");
}
finally {
    //可选,一定会执行的代码
}

throws

抛出可能的异常,但是还是会报错

public static void main(String[] args) throws Exception{
    String str = "123";
    str = "abc";
    int num = Integer.parseInt(str);
}

throw

可以手动抛出异常

public static void main(String[] args) throws Exception {
    throw new Exception("输出异常");
}

5.多线程

创建方式一:继承Thread

一个线程只能执行一个start

public class ThreadTest {
    public static void main(String[] args) {
        //3.创建实例
        MyThread mt = new MyThread();
        //4.调用start
        mt.start();
        for (int i = 0; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + "-------");
        }
    }
}

//1.继承thread
class MyThread extends Thread {
    //2.重写run方法
    @Override
    public void run() {
        for (int i = 0; i <= 20; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

创建方式二:实现Runnable接口

public class ThreadTest1 {
    public static void main(String[] args) {

        MyThread1 myThread1 = new MyThread1();
        Thread t1 = new Thread(myThread1);
        t1.start();

        for (int i = 0; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + "-------");
        }
    }
}

class  MyThread1 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 20; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

创建方式三:Callable接口

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableTest {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();

        try {
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }


}

//1.创建一个实现Callable的实现类
class NumThread implements Callable {
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

创建方式四:线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

class NumberThread implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

public class ThreadPool {

    public static void main(String[] args) {
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //设置线程池的属性
        //        System.out.println(service.getClass());
        //        service1.setCorePoolSize(15);
        //        service1.setKeepAliveTime();


        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable

        //        service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }
}

线程安全

同步代码块

使用同步代码块,防止线程同时操作一个数据

优点:解决了安全问题,缺点:操作同步代码时,只能有一个线程参与,相当于单线程过程,效率低

synchronized (this),runnable可以使用this,thread不能使用this,可以使用反射,也可以定义一个静态对象

括号内是一把锁,可以是任意对象。但一定只能有一把锁,不然没有作用

public class Ticket implements Runnable {
    private int tick = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (tick > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "号窗口买票,票号为:" + tick--);
                } else {
                    break;
                }
            }
        }
    }
}

class TicketTest {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        Thread thread1 = new Thread(ticket);
        Thread thread2 = new Thread(ticket);
        Thread thread3 = new Thread(ticket);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();

    }
}

同步方法
public class Ticket3 implements Runnable {
    private int tick = 100;
    private boolean isFlag = true;

    @Override
    public void run() {
        while (isFlag) {
            show();

        }
    }

    public synchronized void show() {//同步show方法,继承Thread类方法一样,只需同步方法即可,同时需要给方法加static关键字,确保不会创建多个对象
        if (tick > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "号窗口买票,票号为:" + tick--);
        } else {
            isFlag = false;
        }
    }
}

class TicketTest3 {
    public static void main(String[] args) {
        Ticket3 ticket = new Ticket3();

        Thread thread1 = new Thread(ticket);
        Thread thread2 = new Thread(ticket);
        Thread thread3 = new Thread(ticket);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();

    }
}

Lock锁
import java.util.concurrent.locks.ReentrantLock;

class Window implements Runnable{

    private int ticket = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true){
            try{

                //2.调用锁定方法lock()
                lock.lock();

                if(ticket > 0){

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }finally {
                //3.调用解锁方法:unlock()
                lock.unlock();
            }

        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

Thread常用方法

在这里插入图片描述

优先级设置

在这里插入图片描述

wait,notify,notifyall

wait():使线程进入阻塞,并释放同步监视器(锁)

notify():唤醒一个线程,如果有多个线程wait,则唤醒优先级高的

notifyall():唤醒所有线程

这三个方法必须在同步代码块或同步方法中,调用者必须是其中的同步监视器,否则会报错,他们定义在java.lang.Object类中

6.常用类

字符串

String

不可变性

在这里插入图片描述

实例化方法

在这里插入图片描述

//通过字面量定义的方式:此时的s1和s2的数据javaEE声明在方法区中的字符串常量池中。
String s1 = "javaEE";
String s2 = "javaEE";
//通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间中开辟空间以后对应的地址值。
String s3 = new String("javaEE");
String s4 = new String("javaEE");

System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false

拼接对比
  1. 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
  2. 只要其中一个是变量,结果就在堆中。
  3. 如果拼接的结果调用 intern() 方法,返回值就在常量池中

在这里插入图片描述

String s1 = "javaEE";
String s2 = "hadoop";

String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop";
String s5 = s1 + "hadoop";
String s6 = "javaEE" + s2;
String s7 = s1 + s2;

System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//false
System.out.println(s3 == s7);//false
System.out.println(s5 == s6);//false
System.out.println(s5 == s7);//false
System.out.println(s6 == s7);//false

String s8 = s6.intern();//返回值得到的s8使用的常量值中已经存在的“javaEEhadoop”
System.out.println(s3 == s8);//true
****************************
String s1 = "javaEEhadoop";
String s2 = "javaEE";
String s3 = s2 + "hadoop";
System.out.println(s1 == s3);//false

final String s4 = "javaEE";//s4:常量
String s5 = s4 + "hadoop";
System.out.println(s1 == s5);//true


常用操作
操作
int length():返回字符串的长度:  return value.length

char charAt(int index): 返回某索引处的字符  return value[index]

boolean isEmpty() :判断是否是空字符串: return value.length == 0

String toLowerCase() :使用默认语言环境,将 String 中的所字符转换为小写

String toUpperCase() :使用默认语言环境,将 String 中的所字符转换为大写

String trim() :返回字符串的副本,忽略前导空白和尾部空白

boolean equals(Object obj) :比较字符串的内容是否相同

boolean equalsIgnoreCase(String anotherString) :与  equals()  方法类似,忽略大小写

String concat(String str) :将指定字符串连接到此字符串的结尾。 等价于用  +

int compareTo(String anotherString) :比较两个字符串的大小

String substring(int beginIndex) :返回一个新的字符串,它是此字符串的从beginIndex 开始截取到最后的一个子字符串。
String substring(int beginIndex, int endIndex)  :返回一个新字符串,它是此字符串从 beginIndex 开始截取到 endIndex (不包含)的一个子字符串。
    
String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所 oldChar 得到的。
String replace(CharSequence target, CharSequence replacement):使用指定的字符串替换此字符串中出现的目标字符串。
    
String replaceAll(String regex, String replacement):使用给定的 replacement 替换此字符串所匹配给定的正则表达式的子字符串。
String replaceFirst(String regex, String replacement):使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。

boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。
    
String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。
String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。
判断
boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束
    
boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始
    
boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始
查找
boolean contains(CharSequence s):当且仅当此字符串包含指定的 char 值序列时,返回 true
    
int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引
    
int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始
    
int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引
    
int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
注:indexOf 和 lastIndexOf 方法如果未找到都是返回-1
转换
String --> char[]:调用StringtoCharArray() 
char[] --> String:调用String的构造器 new String(charArray)
    
编码:String --> byte[]:调用StringgetBytes(),可选不同编码
解码:byte[] --> String:调用String的构造器
说明:解码时,要求解码使用的字符集必须与编码时使用的字符集一致,否则会出现乱码。

stringbuffer,stringbuilder是可变的字符序列
stringbuffer线程安全的,效率低,stringbuilder反之
String -->StringBufferStringBuilder: 调用StringBufferStringBuilder构造器
StringBufferStringBuilder -->String:
①调用String构造器; ②StringBufferStringBuildertoString()
StringBuffer和StringBuilder

是可变的字符序列

StringBuilder和 StringBuffer非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样,只是StringBuilder类没有加线程锁,执行效率更高。

构造器
StringBuffer():初始容量为16的字符串缓冲区
StringBuffer(int size):构造指定容量的字符串缓冲区
StringBuffer(String str):将内容初始化为指定字符串内容
常用方法
StringBuffer append(xxx):提供了很多的 append() 方法,用于进行字符串拼接
    
StringBuffer delete(int start,int end):删除指定位置的内容
    
StringBuffer replace(int start, int end, String str):把[start,end)位置替换为str
                                                       
StringBuffer insert(int offset, xxx):在指定位置插入xxx
                                                       
StringBuffer reverse() :把当前字符序列逆转

public int indexOf(String str):返回子串的下标
                                                       
public String substring(int start,int end):返回一个从start开始到end索引结束的左闭右开区间的子字符串
                                                       
public int length():获取字符串的长度
                                                       
public char charAt(int n ):返回指定位置的字符
                                                       
public void setCharAt(int n ,char ch):设置指定位置的字符

执行效率

String、StringBuffer、StringBuilder三者的执行效率

从高到低排列:StringBuilder > StringBuffer > String

时间

Date

Date():使用无参的构造器创建对象可以获取本地当前时间

@Test
public void dateTest(){
    //构造器一:Date():创建一个对应当前时间的Date对象
    Date date1 = new Date();
    System.out.println(date1.toString());//Sun Apr 19 13:35:12 CST 2020
    System.out.println(date1.getTime());//1587274512876

    //构造器二:创建指定毫秒数的Date对象
    Date date2 = new Date(15872745176L);
    System.out.println(date2.toString());
    System.out.println("-----------------------");

    //创建java.sql.Date对象,对应数据库中日期类型变量
    java.sql.Date date3 = new java.sql.Date(1587274512876L);
    System.out.println(date3.toString());

    //如何将java.util.Date对象转换为java.sql.Date对象
    Date date4 = new Date();
    //第一种方式,存在问题:java.util.Date cannot be cast to java.sql.Date
    //        java.sql.Date date6 = (java.sql.Date) date4;
    //        System.out.println(date6);
    //第二种方式
    java.sql.Date date5 = new java.sql.Date(date4.getTime());
    System.out.println(date5);
}
SimpleDateFormat

时间格式化

@Test
public void test2() throws ParseException {
    //实例化Date对象
    Date date1 = new Date();
    //实例化SimpleDateFormate对象,并设置显示格式
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:aaa");
    //格式化date对象
    String format = simpleDateFormat.format(date1);
    System.out.println(format.toString());//2020-09-19 02:09:下午
    //解析:要求字符串必须是符合SimpleDateFormat识别的格式(通过构造器参数体现),
    //否则,抛异常
    Date date2 = simpleDateFormat.parse("2020-04-20 14:20:下午");
    System.out.println(date2.toString());//Tue Jan 21 02:20:00 CST 2020
}

DateTimeFormatter
@Test
public void test3(){
    //        方式一:预定义的标准格式。
    //        如:ISO_LOCAL_DATE_TIME;ISO_LOCAL_DATE;ISO_LOCAL_TIME
    DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
    //格式化:日期-->字符串
    LocalDateTime localDateTime = LocalDateTime.now();
    String str1 = formatter.format(localDateTime);
    System.out.println(localDateTime);//2020-04-21T19:13:13.530
    System.out.println(str1);//2020-04-21T19:13:13.53

    //解析:字符串 -->日期
    TemporalAccessor parse = formatter.parse("2000-04-21T19:13:13.53");
    System.out.println(parse);//{},ISO resolved to 2000-04-21T19:13:13.530
    //        方式二:
    //        本地化相关的格式。如:ofLocalizedDateTime()
    //        FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT :适用于LocalDateTime
    DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);
    //格式化
    String str2 = formatter1.format(localDateTime);
    System.out.println(str2);//2020年4月21日 下午07时16分57秒

    //      本地化相关的格式。如:ofLocalizedDate()
    //      FormatStyle.FULL / FormatStyle.LONG / FormatStyle.MEDIUM / FormatStyle.SHORT : 适用于LocalDate
    DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
    //格式化
    String str3 = formatter2.format(LocalDate.now());
    System.out.println(str3);//2020-4-21

    //       重点: 方式三:自定义的格式。如:ofPattern(“yyyy-MM-dd hh:mm:ss”)
    DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
    String Str4 = formatter3.format(LocalDateTime.now());
    System.out.println(Str4);//2020-04-21 07:24:04

    TemporalAccessor accessor = formatter3.parse("2020-02-03 05:23:06");
    System.out.println(accessor);//{SecondOfMinute=6, HourOfAmPm=5, NanoOfSecond=0, MicroOfSecond=0, MinuteOfHour=23, MilliOfSecond=0},ISO resolved to 2020-02-03
}

Calendar

日历类

注意: 获取月份时:一月是0,二月是1,以此类推,12月是11 获取星期时:周日是1,周二是2,。。。周六是7

Calendar calendar = Calendar.getInstance();
//        System.out.println(calendar.getClass());

//2.常用方法
//get()
int days = calendar.get(Calendar.DAY_OF_MONTH);//获取本月第几天
System.out.println(days);
System.out.println(calendar.get(Calendar.DAY_OF_YEAR));//获取本年第几天

//set()
//calendar可变性
calendar.set(Calendar.DAY_OF_MONTH,22);//设置本月第几天
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);

//add()
calendar.add(Calendar.DAY_OF_MONTH,-3);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);

//getTime():日历类---> Date
Date date = calendar.getTime();
System.out.println(date);

//setTime():Date ---> 日历类
Date date1 = new Date();
calendar.setTime(date1);
days = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(days);


LocalDate / LocalTime / LocalDateTime

在这里插入图片描述

@Test
public void test1(){
    //now():获取当前的日期、时间、日期时间
    LocalDate localDate = LocalDate.now();
    LocalTime localTime = LocalTime.now();
    LocalDateTime localDateTime = LocalDateTime.now();

    System.out.println(localDate);//2020-04-21
    System.out.println(localTime);//18:52:54.929
    System.out.println(localDateTime);//2020-04-21T18:52:54.929

    //of():设置指定的年、月、日、时、分、秒。没有偏移量
    LocalDateTime localDateTime1 = LocalDateTime.of(2020,10,6,12,13,12);
    System.out.println(localDateTime1);//2020-10-06T12:13:12

    //getXxx():获取相关的属性
    System.out.println(localDateTime.getDayOfMonth());//21
    System.out.println(localDateTime.getDayOfWeek());//TUESDAY
    System.out.println(localDateTime.getMonth());//APRIL
    System.out.println(localDateTime.getMonthValue());//4
    System.out.println(localDateTime.getMinute());//52

    //体现不可变性
    //withXxx():设置相关的属性
    LocalDate localDate1 = localDate.withDayOfMonth(22);
    System.out.println(localDate);//2020-04-21
    System.out.println(localDate1);//2020-04-22

    LocalDateTime localDateTime2 = localDateTime.withHour(4);
    System.out.println(localDateTime);//2020-04-21T18:59:17.484
    System.out.println(localDateTime2);//2020-04-21T04:59:17.484

    //不可变性
    LocalDateTime localDateTime3 = localDateTime.plusMonths(3);
    System.out.println(localDateTime);//2020-04-21T18:59:17.484
    System.out.println(localDateTime3);//2020-07-21T18:59:17.484

    LocalDateTime localDateTime4 = localDateTime.minusDays(6);
    System.out.println(localDateTime);//2020-04-21T18:59:17.484
    System.out.println(localDateTime4);//2020-04-15T18:59:17.484
}

Instant时间点
@Test
public void test2(){
    //now():获取本初子午线对应的标准时间
    Instant instant = Instant.now();
    System.out.println(instant);//2020-04-21T11:03:21.469Z

    //添加时间的偏移量
    OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
    System.out.println(offsetDateTime);//2020-04-21T19:03:21.469+08:00

    //toEpochMilli():获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数  ---> Date类的getTime()
    long milli = instant.toEpochMilli();
    System.out.println(milli);//1587467105795

    //ofEpochMilli():通过给定的毫秒数,获取Instant实例  -->Date(long millis)
    Instant instant1 = Instant.ofEpochMilli(1587467105795L);
    System.out.println(instant1);//2020-04-21T11:05:05.795Z
}

比较器

自然排序

实现Comparable接口,重写compareTo方法

public class Goods implements  Comparable{
    private String name;
    private double price;
    //指明商品比较大小的方式:照价格从低到高排序,再照产品名称从高到低排序
    @Override
    public int compareTo(Object o) {
        //        System.out.println("**************");
        if(o instanceof Goods){
            Goods goods = (Goods)o;
            //方式一:
            if(this.price > goods.price){
                return 1;
            }else if(this.price < goods.price){
                return -1;
            }else{
                //                return 0;
                return this.name.compareTo(goods.name);
            }
            //方式二:
            //           return Double.compare(this.price,goods.price);
        }
        //        return 0;
        throw new RuntimeException("传入的数据类型不一致!");
    }
    // getter、setter、toString()、构造器:省略
}


定制排序

创建Comparator对象,重写compare方法

arrays.sort(arr,new Comparator(){...})
Comparator com = new Comparator() {
    //指明商品比较大小的方式:照产品名称从低到高排序,再照价格从高到低排序
    @Override
    public int compare(Object o1, Object o2) {
        if(o1 instanceof Goods && o2 instanceof Goods){
            Goods g1 = (Goods)o1;
            Goods g2 = (Goods)o2;
            if(g1.getName().equals(g2.getName())){
                return -Double.compare(g1.getPrice(),g2.getPrice());
            }else{
                return g1.getName().compareTo(g2.getName());
            }
        }
        throw new RuntimeException("输入的数据类型不一致");
    }
}

System

可以直接使用在这里插入图片描述

Math

在这里插入图片描述

枚举类

定义
//使用enum关键字枚举类
enum Season1 {
    //1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
    SPRING("春天","春暖花开"),
    SUMMER("夏天","夏日炎炎"),
    AUTUMN("秋天","秋高气爽"),
    WINTER("冬天","冰天雪地");

    //2.声明Season对象的属性:private final修饰
    private final String seasonName;
    private final String seasonDesc;

    //2.私化类的构造器,并给对象属性赋值

    private Season1(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    //4.其他诉求1:获取枚举类对象的属性
    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }

}
常用方法
Season1 summer = Season1.SUMMER;
//toString():返回枚举类对象的名称
System.out.println(summer.toString());

System.out.println("****************");
//values():返回所的枚举类对象构成的数组
Season1[] values = Season1.values();
for(int i = 0;i < values.length;i++){
    System.out.println(values[i]);
}
System.out.println("****************");
Thread.State[] values1 = Thread.State.values();
for (int i = 0; i < values1.length; i++) {
    System.out.println(values1[i]);
}

//valueOf(String objName):返回枚举类中对象名是objName的对象。
Season1 winter = Season1.valueOf("WINTER");
//如果没objName的枚举类对象,则抛异常:IllegalArgumentException
//        Season1 winter = Season1.valueOf("WINTER1");
System.out.println(winter);

实现接口
interface Info{
    void show();
}

//使用enum关键字枚举类
enum Season1 implements Info{
    //1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
    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("大约在冬季");
        }
    };
}

7.注解

框架 = 注解 + 反射机制 + 设计模式

自定义注解

注解需要配上反射才有意义

public @interface MyAnnotation {
    String value() default "wuhu";
}

元注解

修饰注解的注解

在这里插入图片描述

在这里插入图片描述

可重复注解:

在注解上声明@Repeatable

在这里插入图片描述

8.集合

集合的分类

在这里插入图片描述

集合框架

|----Collection接口:单列集合,用来存储一个一个的对象
     |----List接口:存储有序的、可重复的数据。  -->“动态”数组
           |----ArrayList:作为List接口的主要实现类,线程不安全的,效率高;底层采用Object[] elementData数组存储
           |----LinkedList:对于频繁的插入删除操作,使用此类效率比ArrayList效率高底层采用双向链表存储
           |----Vector:作为List的古老实现类,线程安全的,效率低;底层采用Object[]数组存储
           
     |----Set接口:存储无序的、不可重复的数据   -->数学概念上的“集合”
           |----HashSet:作为Set接口主要实现类;线程不安全;可以存null|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加顺序遍历;对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
           |----TreeSet:可以按照添加对象的指定属性,进行排序。

|----Map:双列数据,存储key-value对的数据   ---类似于高中的函数:y = f(x)
     |----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
          |----LinkedHashMap:保证在遍历map元素时,可以照添加的顺序实现遍历。
                    原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
                    对于频繁的遍历操作,此类执行效率高于HashMap|----TreeMap:保证照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序
                      底层使用红黑树
     |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
          |----Properties:常用来处理配置文件。key和value都是String类型


Collection

常用方法

注:元素中equals方法基本要重写

添加
add(Object obj)
addAll(Collection coll)	//将集合元素添加到集合中

获取有效元素个数
int size()

清空集合
void clear()

是否为空集合
boolean isEmpty()

是否包含某个元素
boolean contains(Object obj):是通过元素的equals方法来判断是否是同一个对象
boolean containsAll(Collection c):也是调用元素的equals方法来比较的。用两个两个集合的元素逐一比较

删除
boolean remove(Object obj):通过元素的equals方法判断是否是要删除的那个元素。只会删除找到的第一个元素
boolean removeAll(Collection coll):取当前集合的差集

取两个集合的交集
boolean retainAll(Collection c):把交集的结果存在当前的集合中,不影响c

集合是否相等
boolean equals(Object obj)

转换成对象数组
Object [] toArray()

集合数组转换
//集合 --->数组:toArray()
Object[] arr = coll.toArray();
for(int i = 0;i < arr.length;i++){
    System.out.println(arr[i]);
}

//拓展:数组 --->集合:调用Arrays类的静态方法asList(T ... t)
List<String> list = Arrays.asList(new String[]{"AA", "BB", "CC"});
System.out.println(list);

List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//1,将可变参数作为一个整体,输出有问题

List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2,分别作为两个,输出没问题
遍历
方式1:迭代器
Iterator iterator = coll.iterator();
//hasNext():判断是否还下一个元素
while(iterator.hasNext()){
    //next():①指针下移 ②将下移以后集合位置上的元素返回
    System.out.println(iterator.next());
}
//iterator.remove()
@Test
public void test3(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new Person("Jerry",20));
    coll.add("Tom"
            );
    coll.add(false);

    //删除集合中"Tom"
    Iterator iterator = coll.iterator();
    while (iterator.hasNext()){
        //            iterator.remove();
        Object obj = iterator.next();
        if("Tom".equals(obj)){
            iterator.remove();
            //                iterator.remove();
        }

    }
    //将指针重新放到头部,遍历集合
    iterator = coll.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
}
方式2:foreach
@Test
public void test1(){
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(456);
    coll.add(new Person("Jerry",20));
    coll.add(new String("Tom"));
    coll.add(false);

    //for(集合元素的类型 局部变量 : 集合对象)
    
    for(Object obj : coll){
        System.out.println(obj);
    }
}
List

存储序有序的、可重复的数据。

基本方法
void add(int index, Object ele):在index位置插入ele元素
    
boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
    
Object get(int index):获取指定index位置的元素
    
int indexOf(Object obj):返回obj在集合中首次出现的位置
    
int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
    
Object remove(int index):移除指定index位置的元素,并返回此元素
    
Object set(int index, Object ele):设置指定index位置的元素为ele
    
List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
ArrayList
@Test
public void test1() {
    Collection coll = new ArrayList();
    coll.add(123);
    coll.add(345);
    coll.add(new User("Tom", 34));
    coll.add(new User("Tom"));
    coll.add(false);
    //iterator()遍历ArrayList集合
    Iterator iterator = coll.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
}
linkedList
  • 对与对于频繁的插入和删除元素操作,建议使用LinkedList类,效率更高
  • 双向链表
//新增方法
void addFirst(Object obj)
void addLast(Object obj)
Object getFirst()
Object getlast()
Object removeFirst()
Object removeLast()
Set

用于存放无序的、不可重复的元素

Set接口是Collection的子接口,set接口没有提供额外的方法

存储的数据在底层数组中并非照数组索引的顺序添加,而是根据数据的哈希值决定的。

HashSet
  • Hashset是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。

  • HashSet具有以下特点:

    • 不能保证元素的排列顺序
    • HashSet不是线程安全的
    • 集合元素可以是null
    • 对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则
LinkedHashSet
  • LinkedhashSet是HashSet的子类
  • LinkedhashSet根据元素的hashCode值来决定元素的存储位置但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
  • LinkedhashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。
TreeSet
  • Treeset是SortedSet接口的实现类,TreeSet可以确保集合元素处于排序状态。
  • TreeSet底层使用红黑树结构存储数据
@Test
public void test1(){
    Set treeSet = new TreeSet();//如果要定制排序,需要传入TreeSet(xxx)
    treeSet.add(new User("Tom",34));
    treeSet.add(new User("Jarry",23));
    treeSet.add(new User("mars",38));
    treeSet.add(new User("Jane",56));
    treeSet.add(new User("Jane",60));
    treeSet.add(new User("Bruce",58));

    Iterator iterator = treeSet.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
}
//新增方法
Object first()

Object last()

Object lower(object e)	//返回小于e的最大值

Object higher(object e)//返回大于e的最小值

SortedSet subSet(fromElement, toElement)

SortedSet headSet(toElement)

SortedSet tailSet(fromElement)

Map

  • Map中的key用set来存放,不允许重复,即同一个Map对象所对应的类,须重写 hashCode()equals() 方法
  • 常用 String类作为Map的“键”
  • key和value之间存在单向一对一关系,即通过指定的key总能找到唯一的、确定的value
  • 一组键值对作为一个entry
  • Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是Map接口使用频率最高的实现类
常用方法
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
    
Object remove(Object key):移除指定key的key-value对,并返回value
    
void clear():清空当前map中的所有数据
    
Object get(Object key):获取指定key对应的value
    
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
    
int size():返回map中key-value对的个数
    
boolean isEmpty():判断当前map是否为空
    
boolean equals(Object obj):判断当前map和参数对象obj是否相等
    
Set keySet():返回所有key构成的Set集合
    
Collection values():返回所有value构成的Collection集合
    
Set entrySet():返回所有key-value对构成的Set集合
HashMap
  • HashMap是Map接口使用频率最高的实现类。
  • 允许使用null键和null值,与 HashSet一样,不保证映射的顺序。
  • 所有的key构成的集合是set:无序的、不可重复的。所以,key所在的类要重写equals()和 hashCode()
@Test
public void test1(){
    Map map = new HashMap();
    map.put(null,123);
}
LinkedHashMap
@Test
public void test2(){
    Map map = new LinkedHashMap();
    map.put(123,"AA");
    map.put(345,"BB");
    map.put(12,"CC");

    System.out.println(map);
} 
TreeMap
  • TreeMap存储Key-Value对时,需要根据key-value对进行排序。TreeMap可以保证所有的 Key-Value对处于有序状态。
  • TreeSet底层使用红黑树结构存储数据
  • TreeMap的Key的排序:
    • 自然排序: TreeMap的所有的Key必须实现Comparable接口,而且所有的Key应该是同一个类的对象,否则将会抛出ClasssCastEXception()
    • 定制排序:创建 TreeMap时,传入一个 Comparator对象,该对象负责对TreeMap中的所有key进行排序。此时不需要Map的Key实现Comparable接口

Collections工具类

Collections是一个操作Set、List和Map等集合的工具类

reverse(List):反转 List 中元素的顺序
    
shuffle(List):对 List 集合元素进行随机排序
    
sort(List):根据元素的自然顺序对指定 List 集合元素升序排序'
sort(ListComparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
    
swap(Listintint):将指定 list 集合中的 i 处元素和 j 处元素进行交换
    
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(CollectionComparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
    
Object min(Collection)
Object min(CollectionComparator)
    
int frequency(CollectionObject):返回指定集合中指定元素的出现次数
    
void copy(List dest,List src):将src中的内容复制到dest中
//注意创建时需要分配大小
List dest = Arrays.asList(new Object[list.size()]);
Collections.copy(dest,list);

boolean replaceAll(List list,Object oldVal,Object newVal)使用新值替换 List 对象的所有旧值

线程安全

在这里插入图片描述

//返回的list1即为线程安全的List
List list1 = Collections.synchronizedList(list);

9.泛型

基本使用

  1. 解决元素存储的安全性问题。
  2. 解决获取数据元素时,需要类型强制转换的问题。
//在集合中使用泛型,以ArrayList为例
@Test
public void test1(){
    ArrayList<String> list = new ArrayList<>();
    list.add("AAA");
    list.add("BBB");
    list.add("FFF");
    list.add("EEE");
    list.add("CCC");
    //遍历方式一:
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
    System.out.println("-------------");
    //便利方式二:
    for (String str:
         list) {
        System.out.println(str);
    }
}
@Test
//在集合中使用泛型的情况:以HashMap为例
public void test2(){
    Map<String,Integer> map = new HashMap<>();//jdk7新特性:类型推断
    map.put("Tom",26);
    map.put("Jarry",30);
    map.put("Bruce",28);
    map.put("Davie",60);
    //嵌套循环
    Set<Map.Entry<String, Integer>> entries = map.entrySet();
    Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();

    while (iterator.hasNext()){
        Map.Entry<String, Integer> entry = iterator.next();
        String key = entry.getKey();
        Integer value = entry.getValue();
        System.out.println(key+"="+value);
    }
}

自定义泛型结构

T,K,V,不代表值,而是表示类型。这里使用任意字母都可以。常用T表示,是Type的缩写。

public class Mygeneric<T> {
    T orderT;

    public T getOrderT() {
        return orderT;
    }

    public void setOrderT(T orderT) {
        this.orderT = orderT;
    }

    public Mygeneric() {
    }

    public Mygeneric(T orderT) {
        this.orderT = orderT;
    }
}
public class Mytest {
    @Test
    public void MyG() {
        Mygeneric mg = new Mygeneric();
        mg.setOrderT(123);
        System.out.println(mg.getOrderT());
        mg.setOrderT("wh");
        System.out.println(mg.getOrderT());
    }
}

注意

异常类不能是泛型的。

如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。

泛型的指定中不能使用基本数据类型,可以使用包装类替换。

在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。 (即静态方法中不可以使用本类定义的泛型参数)

/**
 * 自定义泛型类Order
 */
class Order<T> {
    private String orderName;
    private int orderId;
    //使用T类型定义变量
    private T orderT;

    public Order() {
    }
    //使用T类型定义构造器
    public Order(String orderName, int orderId, T orderT) {
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }

    //这个不是泛型方法
    public T getOrderT() {
        return orderT;
    }
    //这个不是泛型方法
    public void setOrderT(T orderT) {
        this.orderT = orderT;
    }
    //这个不是泛型方法
    @Override
    public String toString() {
        return "Order{" +
                "orderName='" + orderName + '\'' +
                ", orderId=" + orderId +
                ", orderT=" + orderT +
                '}';
    }
//    //静态方法中不能使用类的泛型。
//    public static void show(T orderT){
//        System.out.println(orderT);
//    }

//    //try-catch中不能是泛型的。
//    public void show(){
//        try {
//
//        }catch (T t){
//
//        }
//    }

    //泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。
    //换句话说,泛型方法所属的类是不是泛型类都没有关系。
    //泛型方法,可以声明为静态的。
    // 原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
    //<E>表示是一个泛型,而不是类型,去掉会报错
    public static <E> List<E> copyFromArryToList(E[] arr) {
        ArrayList<E> list = new ArrayList<>();
        for (E e :
                list) {
            list.add(e);
        }
        return list;
    }
}
  • 子类不保留父类的泛型:按需实现
    • 没有类型—擦除
    • 具体类型
  • 子类保留父类的泛型:泛型子类
    • 全部保留
    • 部分保留
  • 结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型
class Father<T1, T2> {
}

/**
 * 定义泛型子类Son
 * 情况一:继承泛型父类后不保留父类的泛型
 */
//1.没有指明类型  擦除
class Son1<A, B> extends Father {//等价于class Son1 extends Father<Object,Odject>{}
}

//2.指定具体类型
class Son2<A, B> extends Father<Integer, String> {
}

/**
 * 定义泛型子类Son
 * 情况二:继承泛型父类后保留泛型类型
 */
//1.全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {
}

//2.部分保留
class Son4<T2, A, B> extends Father<Integer,T2>{
}

通配符

使用类型通配符:?

比如:List<?>Map<?,?>

List<?>List<String>List<Object> 等各种泛型 List 的父类。

读取 List<?> 的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object

写入list中的元素时,不可以写入。因为我们不知道写入元素类型,我们不能向其中添加对象。 除了添加null之外。

@Test
public void test3(){
    List<Object> list1 = null;
    List<String> list2 = null;

    List<?> list = null;

    list = list1;
    list = list2;
    //编译通过
    //        print(list1);
    //        print(list2);

    //
    List<String> list3 = new ArrayList<>();
    list3.add("AA");
    list3.add("BB");
    list3.add("CC");
    list = list3;
    //添加(写入):对于List<?>就不能向其内部添加数据。
    //除了添加null之外。
    //        list.add("DD");
    //        list.add('?');

    list.add(null);

    //获取(读取):允许读取数据,读取的数据类型为Object。
    Object o = list.get(0);
    System.out.println(o);
}

public void print(List<?> list){
    Iterator<?> iterator = list.iterator();
    while(iterator.hasNext()){
        Object obj = iterator.next();
        System.out.println(obj);
    }
}

注意:

//注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
public static <?> void test(ArrayList<?> list){   
}
//注意点2:编译错误:不能用在泛型类的声明上
class GenericTypeClass<?>{   
}
//注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList<> list2  = new ArrayList<?>();

有限制的通配符

  • 通配符指定上限

    上限 extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即 <=

  • 通配符指定下限

    下限 super:使用时指定的类型不能小于操作的类,即 >=

@Test
public void test4(){

    List<? extends Person> list1 = null;
    List<? super Person> list2 = null;

    List<Student> list3 = new ArrayList<Student>();
    List<Person> list4 = new ArrayList<Person>();
    List<Object> list5 = new ArrayList<Object>();

    //小于等于Person
    list1 = list3;
    list1 = list4;
    //        list1 = list5;报错

    //大于等于Person
    //        list2 = list3;报错
    list2 = list4;
    list2 = list5;

    //读取数据:
    list1 = list3;
     //要大于等于Person才能接收
    Person p = list1.get(0);
    //编译不通过
    //Student s = list1.get(0);

    list2 = list4;
    //只能Object接收
    Object obj = list2.get(0);
    编译不通过
    //        Person obj = list2.get(0);

    //写入数据:
    //编译不通过
    //无法写入除了null的数据,因为list1中可能有比写入的数据还小的数据
    //        list1.add(new Student());

    //编译通过
    //可以写入Person以下的数据
    list2.add(new Person());
    list2.add(new Student());

}

10.I/O流

File

File基本使用
@Test
public void test1() {
    //构造器1
    File file1 = new File("hello.txt");
    File file2 = new File("E:\\workspace_idea\\JavaSenic\\IO\\hello.txt");
    System.out.println(file1);
    System.out.println(file2);

    //构造器2
    File file3 = new File("E:\\workspace_idea\\JavaSenior", "hello.txt");
    System.out.println(file3);

    //构造器3
    File file4 = new File("E:\\workspace_idea", "JavaSenior");
    File file5 = new File(file4, "hi.txt");
    System.out.println(file4);
}
常用方法
public String getAbsolutePath():获取绝对路径
public String getPath():获取路径
public String getName() :获取名称
public String getParent():获取上层文件目录路径。若无,返回 null
public long length() :获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified() :获取最后一次的修改时间,毫秒值

如下的两个方法适用于文件目录:
public String[] list() :获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles() :获取指定目录下的所有文件或者文件目录的File数组
    
public boolean renameTo(File dest):把文件重命名为指定的文件路径
想保证返回 true ,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
    
public boolean isDirectory():判断是否是文件目录
public boolean isFile() :判断是否是文件
public boolean exists() :判断是否存在
public boolean canRead() :判断是否可读
public boolean canWrite() :判断是否可写
public boolean isHidden() :判断是否隐藏

public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建

public boolean delete():删除文件或者文件夹
删除注意事项:Java中的删除不走回收站。
路径分隔符

windows和DOS系统默认使用 \ 来表示

UNIX和URL使用 / 来表示

Java程序支持跨平台运行,因此路径分隔符要慎用。

为了解决这个隐患,File类提供了一个常量: public static final String separator。根据操作系统,动态的提供分隔符。

//windows和DOS系统
File file1 = new File("E:\\io\\test.txt");
//UNIX和URL
File file = new File("E:/io/test.txt");
//java提供的常量
File file = new File("E:"+File.separator+"io"+File.separator+"test.txt");

流分类

操作数据单位:字节流、字符流

  • 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
  • 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理

流的角色:节点流、处理流

  • 节点流:直接从数据源或目的地读写数据。
  • 处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能。

在这里插入图片描述

在这里插入图片描述

红框为抽象基类,蓝框为常用IO流

在这里插入图片描述

节点流(文件流)

字符流读入
@Test
public void testFileReader1() {
    FileReader fr = null;
    try {
        //1.File类的实例化
        File file = new File("hello.txt");
        //2.FileReader流的实例化
        fr = new FileReader(file);
        //3.读入的操作
        //方式一,
        //            int data;
        //            while ((data = fr.read())  != -1){
        //                System.out.print((char)data);
        //            }

        //方式二:read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1,相当于几个几个一起读
        char[] cbuf = new char[5];
        int len;
        while ((len = fr.read(cbuf)) != -1) {
            String str = new String(cbuf, 0, len);
            System.out.print(str);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (fr != null) {
            //4.资源的关闭
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
字符流写出
@Test
public void Fw() {
    File file = new File("hello.txt");
    FileWriter fw = null;
    try {
        //如果是true则接在后面写,如果是false则覆盖
        fw = new FileWriter(file,true);
        fw.write("\nmy name is wuhu");
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
字节流
//使用字节流FileInputStream处理文本文件,可能出现乱码。
//主要处理非文本文件
@Test
public void testFileInputStream() {
    FileInputStream fis = null;
    try {
        //1. 造文件
        File file = new File("hello.txt");

        //2.造流
        fis = new FileInputStream(file);

        //3.读数据
        byte[] buffer = new byte[5];
        int len;//记录每次读取的字节的个数
        while((len = fis.read(buffer)) != -1){
            String str = new String(buffer,0,len);
            System.out.print(str);

        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(fis != null){
            //4.关闭资源
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

}

缓冲流

对字节流的包装

  • 作用:提供流的读取、写入的速度
  • 提高读写速度的原因:内部提供了一个缓冲区。默认情况下是8kb

在这里插入图片描述

@Test
public void testBufferedStream(){
    BufferedInputStream bis = null;
    BufferedOutputStream bos = null;
    try {
        //1.造文件
        File srcFile = new File("test.jpg");
        File destFile = new File("test4.jpg");
        //2.造流
        //2.1造节点流
        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(destFile);
        //2.2造缓冲流,可以合并书写
        bis = new BufferedInputStream(fis);
        bos = new BufferedOutputStream(fos);
        //3.文件读取、写出操作
        byte[] buffer = new byte[1024];
        int len;
        while ((len = bis.read(buffer)) != -1){
            bos.write(buffer,0,len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.关闭流
        if (bos != null){
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (bis != null){
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
@Test
public void testBufferedReaderBufferedWriter(){
    BufferedReader br = null;
    BufferedWriter bw = null;
    try {
        //创建文件和相应的流
        br = new BufferedReader(new FileReader(new File("dbcp.txt")));
        bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));
        //读写操作
        //方式一:使用char[]数组
        //            char[] cbuf = new char[1024];
        //            int len;
        //            while((len = br.read(cbuf)) != -1){
        //                bw.write(cbuf,0,len);
        //    //            bw.flush();
        //            }

        //方式二:使用String
        String data;
        while((data = br.readLine()) != null){
            //方法一:
            //                bw.write(data + "\n");//data中不包含换行符
            //方法二:
            bw.write(data);//data中不包含换行符
            bw.newLine();//提供换行的操作
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //关闭资源
        if(bw != null){
            try {
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(br != null){
            try {
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

转换流

转换流提供了在字节流和字符流之间的转换

Java API提供了两个转换流:

  • InputstreamReader:将 Inputstream 转换为 Reader
  • OutputStreamWriter:将 Writer 转换为 OutputStream

字节流中的数据都是字符时,转成字符流操作更高效。

很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能。

在这里插入图片描述

//综合使用InputStreamReader和OutputStreamWriter
@Test
public void test1() {
    InputStreamReader isr = null;
    OutputStreamWriter osw = null;
    try {
        //1.造文件、造流
        File file1 = new File("dbcp.txt");
        File file2 = new File("dbcp_gbk.txt");

        FileInputStream fis = new FileInputStream(file1);
        FileOutputStream fos = new FileOutputStream(file2);

        isr = new InputStreamReader(fis, "utf-8");
        osw = new OutputStreamWriter(fos, "gbk");

        //2.读写过程
        char[] cbuf = new char[20];
        int len;
        while ((len = isr.read(cbuf)) != -1){
            osw.write(cbuf,0,len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //3.关流
        if (isr != null){
            try {
                isr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (osw != null){
            try {
                osw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

对象流

  • ObjectOutputStream:内存中的对象—>存储中的文件、通过网络传输出去:序列化过程
  • ObjectInputStream:存储中的文件、通过网络接收过来 —>内存中的对象:反序列化过程
  • 序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。
  • 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
    • private static final long serialVersionUID;
    • serialVersionUID 用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容
    • 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID可能发生变化。故建议显式声明。
  • 简单来说,Java 的序列化机制是通过在运行时判断类的 serialversionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的 serialversionUID 与本地相应实体类的 serialversionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)
实现序列化

实现序列化的对象需要满足:

  1. 需要实现接口:Serializable(标识接口)
  2. 当前类提供一个全局常量:serialVersionUID(序列版本号)
  3. 除了当前类需要实现 Serializable 接口之外,还必须保证其内部所属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)

补充:ObjectOutputStreamObjectInputStream 不能序列化 statictransient 修饰的成员变量。(transient用于修饰不序列化的变量)

@Test
public void testObjectOutputStream() {
    ObjectOutputStream oos = null;

    try {
        //1.创建对象,创建流
        oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
        //2.操作流
        oos.writeObject(new String("我爱北京天安门"));
        oos.flush();//刷新操作

        oos.writeObject(new Person("王铭", 23));
        oos.flush();

        oos.writeObject(new Person("张学良", 23, new Account(5000)));
        oos.flush();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (oos != null) {
            //3.关闭流
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
反序列化
@Test
public void testObjectInputStream() {
    ObjectInputStream ois = null;
    try {
        ois = new ObjectInputStream(new FileInputStream("object.dat"));

        Object obj = ois.readObject();
        String str = (String) obj;

        Person p = (Person) ois.readObject();
        Person p1 = (Person) ois.readObject();

        System.out.println(str);
        System.out.println(p);
        System.out.println(p1);

    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        if (ois != null) {
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Person

public class Person implements Serializable {
    private static final long serialVersionUID = 45612345L;
    private String name;
    private int  age;
    private Account ac;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person(String name, int age, Account ac) {
        this.name = name;
        this.age = age;
        this.ac = ac;
    }
}

class Account implements Serializable{
    private static final long serialVersionUID = 45612312345L;

    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }
}

随机存取流

RandomAccessFile 直接继承于 java.lang.Object 类,实现了 DataInput 和DataOutput 接口

RandomAccessFile 既可以作为一个输入流,又可以作为一个输出流

RandomAccessFile 类支持“随机访问”的方式,程序可以直接跳到文件的任意地方来读、写文件

  • 支持只访问文件的部分内容
  • 可以向已存在的文件后追加内容

RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置

RandomaccessFile 类对象可以自由移动记录指针:

  • long getFilePointer():获取文件记录指针的当前位置
  • void seek(long pos):将文件记录指针定位到 pos 位置

访问模式:

  • r:以只读方式打开
  • rw:打开以便读取和写入
  • rwd:打开以便读取和写入;同步文件内容的更新
  • rws:打开以便读取和写入;同步文件内容和元数据的更新
文件读写
@Test
public void test1() {
    RandomAccessFile raf1 = null;
    RandomAccessFile raf2 = null;
    try {
        //1.创建对象,创建流
        raf1 = new RandomAccessFile(new File("test.jpg"),"r");
        raf2 = new RandomAccessFile(new File("test1.jpg"),"rw");
        //2.操作流
        byte[] buffer = new byte[1024];
        int len;
        while((len = raf1.read(buffer)) != -1){
            raf2.write(buffer,0,len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //3.关闭流
        if(raf1 != null){
            try {
                raf1.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(raf2 != null){
            try {
                raf2.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
文件插入
@Test
public void test2() {
    RandomAccessFile raf1 = null;
    try {
        raf1 = new RandomAccessFile(new File("hello.txt"), "rw");

        raf1.seek(3);//将指针调到角标为3的位置
        //方式一
        //保存指针3后面的所有数据到StringBuilder中
        //            StringBuilder builder = new StringBuilder((int) new File("hello.txt").length());
        //            byte[] buffer = new byte[20];
        //            int len;
        //            while ((len = raf1.read(buffer)) != -1) {
        //                builder.append(new String(buffer, 0, len));
        //            }
        //            raf1.seek(3);
        //            raf1.write("xyz".getBytes());
        //            raf1.write(builder.toString().getBytes());

        //方式二
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[20];
        int len;
        while ((len = raf1.read(buffer)) != -1){
            baos.write(buffer,0,len);
        }
        //            System.out.println(baos.toString());
        //调回指针,写入“xyz”
        raf1.seek(3);
        raf1.write("xyz".getBytes());
        //将StringBuilder中的数据写入到文件中
        raf1.write(baos.toString().getBytes());

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (raf1 != null) {
            try {
                raf1.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

NIO

Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO
|-----java.nio.channels.Channel
      |---- FileChannel:处理本地文件
      |---- SocketChannel:TCP网络编程的客户端的Channel
      |---- ServerSocketChannel:TCP网络编程的服务器端的Channel
      |---- DatagramChannel:UDP网络编程中发送端和接收端的Channel
Path
Paths 类提供的静态 get() 方法用来获取 Path 对象:
static Path get(String first, String.more):用于将多个字符串串连成路径
static Path get(URI uri):返回指定 uri 对应的 Path 路径
@Test
public void test1(){
    Path path1 = Paths.get("hello.txt");//new File(String filepath)
    Path path2 = Paths.get("E:\\", "test\\test1\\haha.txt");//new File(String parent,String filename);
    Path path3 = Paths.get("E:\\", "test");
    System.out.println(path1);
    System.out.println(path2);
    System.out.println(path3);
}
String toString() : 返回调用 Path 对象的字符串表示形式
boolean startsWith(String path) : 判断是否以 path 路径开始
boolean endsWith(String path) : 判断是否以 path 路径结束
boolean isAbsolute() : 判断是否是绝对路径
Path getParent() :返回 Path 对象包含整个路径,不包含 Path 对象指定的文件路径
Path getRoot() :返回调用 Path 对象的根路径
Path getFileName() : 返回与调用 Path 对象关联的文件名
int getNameCount() : 返回 Path 根目录后面元素的数量
Path getName(int idx) : 返回指定索引位置 idx 的路径名称
Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
Path resolve(Path p) :合并两个路径,返回合并后的路径对应的 Path 对象
File toFile():Path 转化为 File 类的对象
File

用于操作文件或目录的工具类

Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
要想复制成功,要求 path1 对应的物理上的文件存在。path1 对应的文件没有要求。
Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);

Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
要想执行成功,要求 path 对应的物理上的文件目录不存在。一旦存在,抛出异常。
Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
要想执行成功,要求 path 对应的物理上的文件不存在。一旦存在,抛出异常。

void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错
void deleteIfExists(Path path) : Path 对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束

Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
要想执行成功,src 对应的物理上的文件需要存在,dest 对应的文件没有要求。

long size(Path path) : 返回 path 指定文件的大小
boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
    
boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
不要求此 path 对应的物理文件存在。
boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件
boolean isHidden(Path path) : 判断是否是隐藏文件
要求此 path 对应的物理上的文件需要存在。才可判断是否隐藏。否则,抛异常。
boolean isReadable(Path path) : 判断文件是否可读
boolean isWritable(Path path) : 判断文件是否可写
InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
DirectoryStream<Path> newDirectoryStream(Path path) : 打开 path 指定的目录

StandardOpenOption.READ:表示对应的 Channel 是可读的。

StandardOpenOption.WRITE:表示对应的 Channel 是可写的。

StandardOpenOption.CREATE:如果要写出的文件不存在,则创建。如果存在,忽略

StandardOpenOption.CREATE_NEW:如果要写出的文件不存在,则创建。如果存在,抛异常

@Test
public void test3() throws IOException{
    Path path1 = Paths.get("d:\\nio", "hello.txt");

    //		InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
    InputStream inputStream = Files.newInputStream(path1, StandardOpenOption.READ);

    //		OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
    OutputStream outputStream = Files.newOutputStream(path1, StandardOpenOption.WRITE,StandardOpenOption.CREATE);


    //		SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
    SeekableByteChannel channel = Files.newByteChannel(path1, StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);

    //		DirectoryStream<Path>  newDirectoryStream(Path path) : 打开 path 指定的目录
    Path path2 = Paths.get("e:\\teach");
    DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path2);
    Iterator<Path> iterator = directoryStream.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }
}

11.网络

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

  • Socket 分类
    • 流套接字(stream socket):使用TCP提供可依赖的字节流服务
    • 数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务

TCP

例1:发送信息
//客户端
@Test
public void client() {
    Socket socket = null;
    OutputStream os = null;
    try {
        //1.创建Socket对象,指明服务器端的ip和端口号
        InetAddress inet = InetAddress.getByName("127.0.0.1");
        socket = new Socket(inet, 8899);
        //2.获取一个输出流,用于输出数据
        os = socket.getOutputStream();
        //3.写出数据的操作
        os.write("你好,我是客户端".getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.资源的关闭
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

//服务端
@Test
public void server() {

    ServerSocket ss = null;
    Socket socket = null;
    InputStream is = null;
    ByteArrayOutputStream baos = null;
    try {
        //1.创建服务器端的ServerSocket,指明自己的端口号
        ss = new ServerSocket(8899);
        //2.调用accept()表示接收来自于客户端的socket
        socket = ss.accept();
        //3.获取输入流
        is = socket.getInputStream();
        //不建议这样写,可能会有乱码
        //        byte[] buffer = new byte[1024];
        //        int len;
        //        while((len = is.read(buffer)) != -1){
        //            String str = new String(buffer,0,len);
        //            System.out.print(str);
        //        }
        //4.读取输入流中的数据
        baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[5];
        int len;
        while ((len = is.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        System.out.println(baos.toString());
        System.out.println("收到了来自于:" + socket.getInetAddress().getHostAddress() + "的数据");

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (baos != null) {
            //5.关闭资源
            try {
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (ss != null) {
            try {
                ss.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
例2:发送图片保存并返回信息
@Test
public void client() throws IOException {
    //1.
    Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9090);
    //2.
    OutputStream os = socket.getOutputStream();
    //3.
    FileInputStream fis = new FileInputStream(new File("beauty.jpg"));
    //4.
    byte[] buffer = new byte[1024];
    int len;
    while((len = fis.read(buffer)) != -1){
        os.write(buffer,0,len);
    }
    //关闭数据的输出
    socket.shutdownOutput();

    //5.接收来自于服务器端的数据,并显示到控制台上
    InputStream is = socket.getInputStream();
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] bufferr = new byte[20];
    int len1;
    while((len1 = is.read(buffer)) != -1){
        baos.write(buffer,0,len1);
    }

    System.out.println(baos.toString());

    //6.
    fis.close();
    os.close();
    socket.close();
    baos.close();
}

/*
        这里涉及到的异常,应该使用try-catch-finally处理
         */
@Test
public void server() throws IOException {
    //1.
    ServerSocket ss = new ServerSocket(9090);
    //2.
    Socket socket = ss.accept();
    //3.
    InputStream is = socket.getInputStream();
    //4.
    FileOutputStream fos = new FileOutputStream(new File("beauty2.jpg"));
    //5.
    byte[] buffer = new byte[1024];
    int len;
    while((len = is.read(buffer)) != -1){
        fos.write(buffer,0,len);
    }

    System.out.println("图片传输完成");

    //6.服务器端给予客户端反馈
    OutputStream os = socket.getOutputStream();
    os.write("你好,美女,照片我已收到,非常漂亮!".getBytes());

    //7.
    fos.close();
    is.close();
    socket.close();
    ss.close();
    os.close();
}

UDP

以数据报方式进行传输

//发送端
@Test
public void sender() throws IOException {
    DatagramSocket socket = new DatagramSocket();
    String str = "我是UDP方式发送的导弹";
    byte[] data = str.getBytes();
    InetAddress inet = InetAddress.getLocalHost();
    //创建数据报
    DatagramPacket packet = new DatagramPacket(data, 0, data.length, inet, 9090);
    //发送
    socket.send(packet);
    socket.close();
}
//接收端
@Test
public void receiver() throws IOException {
    DatagramSocket socket = new DatagramSocket(9090);
    byte[] buffer = new byte[100];
    //创建数据报
    DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length);
    //接收
    socket.receive(packet);
    //打印
    System.out.println(new String(packet.getData(), 0, packet.getLength()));
    socket.close();
}

Url

public String getProtocol() 获取该 URL 的协议名
public String getHost() 获取该 URL 的主机名
public String getPort() 获取该 URL 的端口号
public String getPath() 获取该 URL 的文件路径
public String getFile() 获取该 URL 的文件名
public String getQuery() 获取该 URL 的查询名

通过url下载

public static void main(String[] args) {
    HttpURLConnection urlConnection = null;
    InputStream is = null;
    FileOutputStream fos = null;
    try {
        //创建url
        URL url = new URL("http://localhost:8080/examples/beauty.jpg");
        //创建连接
        urlConnection = (HttpURLConnection) url.openConnection();
        //开启连接
        urlConnection.connect();
        //创建传输流
        is = urlConnection.getInputStream();
        //创建接收流
        fos = new FileOutputStream("beauty3.jpg");
        byte[] buffer = new byte[1024];
        int len;
        while ((len = is.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
        System.out.println("下载完成");
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //关闭资源
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (fos != null) {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (urlConnection != null) {
            urlConnection.disconnect();
        }
    }
}

12.反射

加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

通常的方式:引入需要的“包类”名称---->通过 new 实例化---->获得实例化对象

反射的方式:实例化对象----> getClass() 方法---->得到完整的“包类”名称

反射的动态性

只有当程序运行时我们才能知道调用的类

@Test
public void test2(){
    for(int i = 0;i < 100;i++){
        int num = new Random().nextInt(2);//0,1
        String classPath = "";
        switch(num){
            case 0:
                classPath = "java.util.Date";
                break;
            case 1:
                classPath = "java.lang.Object";
                break;
        }
        try {
            Object obj = getInstance(classPath);
            System.out.println(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

/*
        创建一个指定类的对象。
        classPath:指定类的全类名
         */
public Object getInstance(String classPath) throws Exception {
    Class clazz =  Class.forName(classPath);
    return clazz.newInstance();
}

反射功能

@Test
public void test1() throws Exception {
    Class<Person> clazz = Person.class;
    //1.通过反射,创建Person类对象
    Constructor<Person> cons = clazz.getConstructor(String.class, int.class);
    Person person = cons.newInstance("Tom", 12);
    System.out.println(person);//Person{name='Tom', age=12}

    //2.通过反射,调用对象指定的属性、方法
    //调用属性
    Field age = clazz.getDeclaredField("age");
    age.setAccessible(true);
    age.set(person, 10);
    System.out.println(person.toString());//Person{name='Tom', age=10}

    //调用方法
    Method show = clazz.getDeclaredMethod("show");
    show.invoke(person);//my name is Tom and age is 10

    System.out.println("===================================");
    //通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性
    //调用私有的构造器
    Constructor<Person> cons1 = clazz.getDeclaredConstructor(String.class);
    cons1.setAccessible(true);
    Person p1 = cons1.newInstance("Bruce");
    System.out.println(p1);//Person{name='Bruce', age=0}

    //调用私有的属性
    Field name = clazz.getDeclaredField("name");
    name.setAccessible(true);
    name.set(p1, "Jarry");
    System.out.println(p1);

    //调用私有的方法
    Method nation = clazz.getDeclaredMethod("showNation", String.class);
    nation.setAccessible(true);
    Object nation1 = (String) nation.invoke(p1, "中国");//相当于String nation = p1.showNation("China")
    System.out.println(nation1);//I come from China
}

Class

Class 本身也是一个类

Class 对象只能由系统建立对象

一个加载的类在 JVM 中只会有一个 Class 实例

一个 Class 对象对应的是一个加载到 JVM 中的一个 .class 文件

每个类的实例都会记得自己是由哪个 Class 实例所生成

通过 Class 可以完整地得到一个类中的所有被加载的结构

Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class 对象

类加载过程

程序经过 javac.exe 命令以后,会生成一个或多个字节码文件(.class 结尾)。接着我们使用 java.exe 命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为 Class 的一个实例。

换句话说,Class 的实例就对应着一个运行时类。

加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。

常用方法

在这里插入图片描述

获取Class实例方式

1)已知具体的类,通过类的 class 属性获取,该方法最为安全可靠,程序性能最高 实例:Class clazz = String.class;

2)已知某个类的实例,调用该实例的 getclass() 方法获取 Class 对象 实例:Class clazz=person.getclass();

3)已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName() 获取,可能抛出 ClassNotFoundException(比较常用)

4)通过类加载器 ClassLoader cl = this.getclass().getClassLoader(); Class clazz = cl.loadClass("类的全类名");

@Test
public void test2() throws ClassNotFoundException {
    //方式一:调用运行时类的属性:.class
    Class<Person> clazz1 = Person.class;
    System.out.println(clazz1);

    //方式二:通过运行时类的对象,调用getClass()
    Person p1 = new Person();
    Class<? extends Person> clazz2 = p1.getClass();
    System.out.println(clazz2);

    //方式三:调用Class的静态方法:forName(String classPath)
    Class<?> clazz3 = Class.forName("Person");//类的全类名
    System.out.println(clazz3);

    System.out.println(clazz1 == clazz2);//true
    System.out.println(clazz1 == clazz3);//true
    //方式四:使用类的加载器:ClassLoader  (了解)
    ClassLoader classLoader = C_GetClass.class.getClassLoader();
    Class<?> clazz4 = classLoader.loadClass("Person");
    System.out.println(clazz4);//class cn.bruce.java.Person
    System.out.println(clazz1 == clazz4);//true
}
Class代表结构
@Test
public void test3(){
    Class c1 = Object.class;
    Class c2 = Comparable.class;
    Class c3 = String[].class;
    Class c4 = int[][].class;
    Class c5 = ElementType.class;
    Class c6 = Override.class;
    Class c7 = int.class;
    Class c8 = void.class;
    Class c9 = Class.class;
    int[] i1 = new int[10];
    int[] i2 = new int[100];
    Class c10 = i1.getClass();
    Class c11 = i2.getClass();
    // 只要数组的元素类型与维度一样,就是同一个Class
    System.out.println(c10 == c11);//true
}
类加载器

class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口。

类缓存:标准的 JavaSE 类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些 Class 对象

分类

在这里插入图片描述

@Test
public void test1(){
    //对于自定义类,使用系统类加载器进行加载
    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    System.out.println(classLoader);
    //调用系统类加载器的getParent():获取扩展类加载器
    ClassLoader classLoader1 = classLoader.getParent();
    System.out.println(classLoader1);
    //调用扩展类加载器的getParent():无法获取引导类加载器
    //引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
    ClassLoader classLoader2 = classLoader1.getParent();
    System.out.println(classLoader2);
    ClassLoader classLoader3 = String.class.getClassLoader();
    System.out.println(classLoader3);
}
使用Classloader加载src目录下的配置文件
@Test
public void test3(){
    Properties pros = new Properties();
    //读取配置文件的方式一:
    //此时的文件默认在当前的module下。
    //                FileInputStream fis = null;
    //                try {
    //                    fis = new FileInputStream("jdbc.properties");
    //                    pros.load(fis);
    //                } catch (IOException e) {
    //                    e.printStackTrace();
    //                } finally {
    //                    if (fis != null) {
    //                        try {
    //                            fis.close();
    //                        } catch (IOException e) {
    //                            e.printStackTrace();
    //                        }
    //                    }
    //                }

    //读取配置文件的方式二:使用ClassLoader
    //配置文件默认识别为:当前module的src下
    ClassLoader classLoader = E_Properties.class.getClassLoader();
    InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
    try {
        pros.load(is);
    } catch (IOException e) {
        e.printStackTrace();
    }

    String user = pros.getProperty("user");
    String password = pros.getProperty("password");
    System.out.println("user = " + user + " password = " + password);
}
创建运行时类的对象

newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。

要想此方法正常的创建运行时类的对象,要求:

  • 运行时类必须提供空参的构造器
  • 空参的构造器的访问权限得够。通常,设置为 public

javabean 中要求提供一个 public 的空参构造器。原因:

  • 便于通过反射,创建运行时类的对象
  • 便于子类继承此运行时类时,默认调用 super() 时,保证父类此构造器
//newInstance()
@Test
public void test1() throws Exception {
    //方式一
    Class<Person> clazz1 = Person.class;
    //方式二
    Class<Person> clazz2 = (Class<Person>) Class.forName("cn.bruce.java.Person");

    Person person1 = clazz1.newInstance();
    Person person2 = clazz2.newInstance();
    System.out.println(person1);
    System.out.println(person2);

}
获取运行时类的结构

我们可以通过反射,获取对应的运行时类中所有的属性、方法、构造器、父类、接口、父类的泛型、包、注解、异常等。。。。

实现的全部接口: public Class<?>[] getInterfaces() 确定此对象所表示的类或接口实现的接口。

所继承的父类: public Class<? Super T> getSuperclass() 返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。

全部的构造器:
public Constructor<T>[] getConstructors()
返回此 Class 对象所表示的类的所有 public 构造方法

public Constructor<T>[] getDeclaredConstructors()
返回此Class对象表示的类声明的所有构造方法。
在Constructor类中:
取得修饰符:public int getModifiers();
取得方法名称: public String getName();
取得参数的类型: public Class<?> getParameterTypes();

全部的方法:
public Method[] getDeclaredMethods()
返回此Class对象所表示的类或接口的全部方法
public Method[] getMethods()
返回此 Class 对象所表示的类或接口的 public 的方法
Method 类中:

public Class<?> getReturnType():取得全部的返回值
public Class<?>[] getParameterTypes():取得全部的参数
public int getModifiers():取得修饰符
public Class<?> [] getEXceptionTypes():取得异常信息

全部的 Field:
public Field[] getFields()
返回此 Class 对象所表示的类或接口的 publicFieldpublic Field[] getDeclaredFields()
返回此 Class 对象所表示的类或接口的全部 Field
Field 方法中

public int getModifiers():以整数形式返回此 Field 的修饰符
public Class<?> getType():得到 Field 的属性类型
public String getName():返回 Field 的名称。

Annotation 相关
get Annotation(Class<T> annotationClass)
getDeclaredAnnotations()
    
泛型相关
获取父类泛型类型:Type getGenericSuperclass()
泛型类型:ParameterizedType
获取实际的泛型类型参数数组:getActualTypeArguments()

类所在的包 Package getPackage()

详细实现见代码

调用运行时类的指定结构
调用指定属性
@Test
public void testField() throws Exception {
    Class clazz = Person.class;

    //创建运行时类的对象
    Person p = (Person) clazz.newInstance();
    //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
    Field name = clazz.getDeclaredField("name");
    //2.保证当前属性是可访问的
    name.setAccessible(true);
    //3.获取、设置指定对象的此属性值
    name.set(p,"Tom");
    System.out.println(name.get(p));
}
调用指定方法

Object invoke(object obj,Object… args)方法:

Object对应原方法的返回值,若原方法无返回值,此时返回 null

若原方法若为静态方法,此时形参 Object obj 可为 null

若原方法形参列表为空,则 Object[] argsnull

若原方法声明为 private,则需要在调用此 invoke() 方法前,显式调用方法对象的 setAccessible(true) 方法,将可访问 private 的方法。

  • MethodFieldConstructor 对象都有 setAccessible() 方法。
  • setAccessible 是启动和禁用访问安全检查的开关
@Test
public void testMethod() throws Exception {
    Class<Person> clazz = Person.class;
    //创建运行时类的对象
    Person person = clazz.newInstance();
    /*
        1.获取指定的某个方法
        getDeclaredMethod():参数1 :指明获取的方法的名称  参数2:指明获取的方法的形参列表
         */
    Method showNation = clazz.getDeclaredMethod("showNation", String.class);
    //2.保证当前方法是可访问的
    showNation.setAccessible(true);
    /*
        3. 调用方法的invoke():参数1:方法的调用者  参数2:给方法形参赋值的实参
        invoke()的返回值即为对应类中调用的方法的返回值。
         */
    Object returnValue = showNation.invoke(person, "CHN");
    System.out.println(returnValue);

    System.out.println("*************如何调用静态方法*****************");
    Method showDesc = clazz.getDeclaredMethod("showDesc");
    showDesc.setAccessible(true);
    //如果调用的运行时类中的方法没有返回值,则此invoke()返回null
    //Object returnVal = showDesc.invoke(null);
    Object returnVal = showDesc.invoke(Person.class);
    System.out.println(returnVal);
}
调用指定构造器
@Test
public void testConstructor() throws Exception {
    Class clazz = Person.class;
    //private Person(String name)
    /*
        1.获取指定的构造器
        getDeclaredConstructor():参数:指明构造器的参数列表
         */
    Constructor constructor = clazz.getDeclaredConstructor(String.class);
    //2.保证此构造器是可访问的
    constructor.setAccessible(true);
    //3.调用此构造器创建运行时类的对象
    Person per = (Person) constructor.newInstance("Tom");
    System.out.println(per);
}

动态代理

静态地理

静态代理缺点

① 代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。

② 每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。

interface ClothFactory{
    void produceCloth();
}

//被代理类
class NikeClothFactory implements ClothFactory{

    @Override
    public void produceCloth() {
        System.out.println("Nike 生产衣服");
    }
}
//代理类
class ProxyClothFactory implements ClothFactory{

    private ClothFactory factory;//用被代理类对象进行实例化

    public ProxyClothFactory(ClothFactory factory) {
        this.factory = factory;
    }
    @Override
    public void produceCloth() {
        System.out.println("代理工厂做一些准备工作");

        factory.produceCloth();

        System.out.println("代理工厂做一些后续的收尾工作");

    }
}
//测试
public class StaticProxyTest {
    public static void main(String[] args) {

        //创建被代理类的对象
        ClothFactory nike = new NikeClothFactory();

        //创建代理类的对象
        ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nike);

        proxyClothFactory.produceCloth();
    }
}
动态代理

动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。

(通过 Proxy.newProxyInstance() 实现)

问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法。

(通过 InvocationHandler 接口的实现类及其方法 invoke() )

动态代理实现步骤:

  1. 创建一个实现接口 InvocationHandler 的类,它必须实现invoke方法,以完成代理的具体操作。
  2. 创建被代理类以及接口
  3. 通过Proxy的静态方法 newProxyInstance(ClassLoader loader, Class<?>...interface, InvocationHandler h) 创建一个接口代理
  4. 通过代理类的实例调用被代理类的方法

interface Human {
    String getBelief();

    void eat(String food);
}

//被代理类
class SuperMan implements Human {
    @Override
    public String getBelief() {
        return "I believe I can fly!";
    }

    @Override
    public void eat(String food) {
        System.out.println("I like eat " + food);
    }
}
/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
 */
class ProxyFactory {
    //调用此方法,返回一个代理类的对象。解决问题一
    public static Object getProxyInstance(Object obj) {
        MyInvocationHanlder hanlder = new MyInvocationHanlder();
        hanlder.bind(obj);
        //newProxyInstance(ClassLoader loader,@NotNull Class<?>[] interfaces,@NotNull reflect.InvocationHandler h)
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), hanlder);

    }
}

//创建继承了InvocationHandler接口的类,解决问题二
class MyInvocationHanlder implements InvocationHandler {
    private Object obj;//需要使用被代理类的对象进行赋值

    public void bind(Object obj) {
        this.obj = obj;
    }

    //当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
    //将被代理类要执行的方法a的功能就声明在invoke()中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法
        //obj:被代理类的对象
        Object returnValue = method.invoke(obj, args);

        //上述方法的返回值就作为当前类中的invoke()的返回值。
        return returnValue;
    }
}

//测试动态代理
public class H_ADP {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        //proxyInstance:代理类的对象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat("火锅");
    }
}

Junit

导入junit包,创建新文件,带上@Test注解

在这里插入图片描述

补充

在这里插入图片描述

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值